import 'dart:async'; import 'package:flutter/material.dart'; import '../../app/net_guard.dart'; import '../../l10n/tr.dart'; import '../theme.dart'; /// 250: 페이지를 DNS 서버에 닿지 않아 열지 못함 (주소 이름 · DNS 서버). 엔진의 오류 화면 위에 이유와 할 일을 보인다. /// 약속: [find] 가 막혔다고 보면 새 값을 주고 (같거나 아니면 null), [set] 으로 정하고 걷는다. /// 덮개가 보이는 동안은 NetGuard 의 아래 알림을 막는다 (248: 같은 안내 · [시스템 DNS 로 바꾸기] 가 두 개 보였다 - a4 933) class DnsBlockWatch { (String, String)? _blocked; /// 이번 읽기를 시작한 시각 (이보다 앞선 DNS 실패는 이 페이지와 상관없다) DateTime loadStartAt = DateTime.now(); (String, String)? get blocked => _blocked; /// 덮개는 여기서만 바꾼다 (새로 읽기 시작 · [다시 시도] 에서 걷는다 - Android 는 오류 뒤에 읽기 끝이 또 온다) void set((String, String)? v) { final g = NetGuard.instance.noticeBlockers; if (_blocked == null && v != null) g.value++; if (_blocked != null && v == null) g.value--; _blocked = v; } /// [error]: 엔진이 알려 준 본문 오류. 프록시 터널 실패이고 DNS 서버에 지금 닿지 않는 중이면, 그 이름의 실패 기록이 없어도 /// (Chromium 이 실패한 프록시를 기억해 다시 묻지 않는 때 등) DNS 때문으로 본다. (String, String)? find({required String host, String? error}) { if (host.isEmpty) return null; final g = NetGuard.instance; var server = g.dnsUnreachableFor(host, loadStartAt); final f = g.failure.value; if (server == null && f != null && error != null && (error.contains('TUNNEL') || error.contains('PROXY'))) server = f.server; if (server == null) return null; final next = (host, server); return next == _blocked ? null : next; } /// 화면을 닫을 때: 막아 둔 알림을 푼다 (닫는 중에는 다른 위젯을 바꾸지 않게 다음 차례에) void dispose() { if (_blocked != null) scheduleMicrotask(() => NetGuard.instance.noticeBlockers.value--); } } /// 250: DNS 실패면 웹 뷰 위에 덮는다 ([시스템 DNS 로 바꾸기] [다시 시도]). /// 트리 모양은 늘 같게 (Stack 첫 칸이 웹 뷰) - 덮개를 띄울 때 모양이 바뀌면 웹 뷰가 새로 만들어져 페이지를 다시 읽고, /// 다시 읽기가 시작되며 덮개가 걷혔다 (931 노트 10+ 에서 덮개가 보이지 않던 것, Windows 실제 시험에서 재현) class DnsBlockCover extends StatelessWidget { final Widget view; /// (주소 이름, DNS 서버). null 이면 웹 뷰만 final (String, String)? blocked; final VoidCallback onSystemDns; final VoidCallback onRetry; const DnsBlockCover({super.key, required this.view, required this.blocked, required this.onSystemDns, required this.onRetry}); @override Widget build(BuildContext context) { final b = blocked; return Stack(children: [ Positioned.fill(child: view), if (b != null) Positioned.fill( child: ColoredBox( color: JjColors.bg, child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 520), child: Column(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.dns_outlined, size: 40, color: Colors.orange), const SizedBox(height: 12), Text(trf('DNS 서버 ({0}) 에 닿지 않아 {1} 을(를) 열 수 없습니다.', [b.$2, b.$1]), textAlign: TextAlign.center, style: const TextStyle(fontSize: 16)), const SizedBox(height: 8), Text(tr('인터넷 연결이나 환경 설정 > 네트워크 · DNS 의 서버 주소를 확인하세요.'), textAlign: TextAlign.center, style: TextStyle(fontSize: 13, color: JjColors.textDim)), const SizedBox(height: 16), Wrap(spacing: 8, runSpacing: 8, alignment: WrapAlignment.center, children: [ FilledButton(onPressed: onSystemDns, child: Text(tr('시스템 DNS 로 바꾸기'))), OutlinedButton(onPressed: onRetry, child: Text(tr('다시 시도'))), ]), ]), ), ), ), ), ), ]); } }