248 다듬기: 아래 DNS 알림은 상태가 풀리면 닫고, 같은 안내를 보이는 화면 (브라우저 덮개 · 설정 DNS 칸) 에서는 띄우지 않음
a4 의 933 기기 확인 · 관리자 지시 (10/12, 이번 릴리스 전에): - DNS 를 고친 뒤 (정한 서버가 다시 답함 · 설정을 바꿈) 에도 옛 문구 "DNS 서버 (192.0.2.1) 에 닿지 않아…" 가 남아 설정의 DNS 칸을 가렸다 → 알림 내용 위젯 (DnsFailureNotice) 이 상태를 듣다가 풀리면 스스로 닫는다 (Flutter 의 알림 닫기는 "지금 보이는 알림" 에만 해야 해서, 보이는 동안 그려지는 내용이 닫는다). - 브라우저 덮개와 함께 떠 [시스템 DNS 로 바꾸기] 가 두 개 보였다 → 덮개 · 환경 설정의 DNS 칸이 열려 있는 동안은 띄우지 않고, 떠 있다가 그런 화면이 열리면 닫는다 (NetGuard.noticeBlockers). 상태는 설정 · 작업 기록 줄 (249) 에 그대로. - 버튼이 있는 알림이 저절로 사라지지 않는 것 (248 처음 지시) 은 그대로. - 시험: 상태 풀림 → 닫힘 · 막는 화면이 있으면 안 뜸 · 뜬 뒤 열리면 닫힘 · 설정 DNS 칸 · 덮개가 막는 수를 올리고 내림. 실제 Edge 통합 시험 다시 통과. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>통합
parent
f7d73da301
commit
c350499b79
|
|
@ -37,6 +37,9 @@ class NetGuard {
|
||||||
/// 249: 서버가 다시 답하거나 DNS 설정을 바꿀 때까지 남는다 - 아래 안내를 놓쳐도 환경 설정 > 네트워크 · DNS 와 작업 현황에 보인다.
|
/// 249: 서버가 다시 답하거나 DNS 설정을 바꿀 때까지 남는다 - 아래 안내를 놓쳐도 환경 설정 > 네트워크 · DNS 와 작업 현황에 보인다.
|
||||||
final failure = ValueNotifier<DnsFailure?>(null);
|
final failure = ValueNotifier<DnsFailure?>(null);
|
||||||
|
|
||||||
|
/// 248: 아래 알림을 띄우지 않을 화면이 열려 있는 수 (브라우저의 DNS 덮개 · 환경 설정의 DNS 칸 - 같은 안내 · 버튼을 이미 보인다)
|
||||||
|
final noticeBlockers = ValueNotifier<int>(0);
|
||||||
|
|
||||||
/// 작업 기록
|
/// 작업 기록
|
||||||
void Function(String)? log;
|
void Function(String)? log;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ import 'ui/downloads_page.dart';
|
||||||
import 'ui/exit_dialog.dart';
|
import 'ui/exit_dialog.dart';
|
||||||
import 'ui/home_page.dart';
|
import 'ui/home_page.dart';
|
||||||
import 'ui/monitor_page.dart' show askLiveSyncStart, MonitorPage;
|
import 'ui/monitor_page.dart' show askLiveSyncStart, MonitorPage;
|
||||||
|
import 'ui/network_settings.dart' show DnsFailureNotice;
|
||||||
import 'ui/player_page.dart';
|
import 'ui/player_page.dart';
|
||||||
import 'ui/setup_dialog.dart';
|
import 'ui/setup_dialog.dart';
|
||||||
import 'ui/update_dialog.dart';
|
import 'ui/update_dialog.dart';
|
||||||
|
|
@ -503,13 +504,15 @@ void watchDnsFailure(GlobalKey<ScaffoldMessengerState> messengerKey, AppControll
|
||||||
NetGuard.instance.failure.addListener(() {
|
NetGuard.instance.failure.addListener(() {
|
||||||
final f = NetGuard.instance.failure.value;
|
final f = NetGuard.instance.failure.value;
|
||||||
if (f == null) return;
|
if (f == null) return;
|
||||||
|
// 248: 같은 안내를 보이는 화면 (브라우저 덮개 · 설정의 DNS 칸) 이 열려 있으면 띄우지 않는다 (상태는 설정 · 작업 기록 줄에)
|
||||||
|
if (NetGuard.instance.noticeBlockers.value > 0) return;
|
||||||
messengerKey.currentState?.showSnackBar(SnackBar(
|
messengerKey.currentState?.showSnackBar(SnackBar(
|
||||||
// 248: 버튼이 있는 안내라 사용자가 닫거나 누를 때까지 둔다 (한 번만 뜨고, 상태는 설정 · 작업 현황에 남음 - 249)
|
// 248: 버튼이 있는 안내라 사용자가 닫거나 누를 때까지 둔다 (한 번만 뜨고, 상태는 설정 · 작업 현황에 남음 - 249)
|
||||||
persist: true,
|
persist: true,
|
||||||
showCloseIcon: true,
|
showCloseIcon: true,
|
||||||
// 사용자에게는 무엇이 안 되고 무엇을 할지만 (예외 원문은 작업 기록에 - NetGuard.reportError)
|
// 사용자에게는 무엇이 안 되고 무엇을 할지만 (예외 원문은 작업 기록에 - NetGuard.reportError).
|
||||||
content: Text(trf('DNS 서버 ({0}) 에 닿지 않아 주소를 찾지 못했습니다. 인터넷 연결이나 환경 설정 > 네트워크 · DNS 의 서버 주소를 확인하세요.',
|
// 상태가 풀리거나 같은 안내를 보이는 화면이 열리면 스스로 닫힌다 (248)
|
||||||
[f.server])),
|
content: DnsFailureNotice(failure: f),
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: tr('시스템 DNS 로 바꾸기'),
|
label: tr('시스템 DNS 로 바꾸기'),
|
||||||
onPressed: () => c.updateSettings((x) => x.dnsMode = 'system'),
|
onPressed: () => c.updateSettings((x) => x.dnsMode = 'system'),
|
||||||
|
|
|
||||||
|
|
@ -215,10 +215,18 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
||||||
/// [error]: 엔진이 알려 준 본문 오류. 프록시 터널 실패이고 DNS 서버에 지금 닿지 않는 중이면, 그 이름의 실패 기록이 없어도
|
/// [error]: 엔진이 알려 준 본문 오류. 프록시 터널 실패이고 DNS 서버에 지금 닿지 않는 중이면, 그 이름의 실패 기록이 없어도
|
||||||
/// (Chromium 이 실패한 프록시를 기억해 다시 묻지 않는 때 등) DNS 때문으로 본다.
|
/// (Chromium 이 실패한 프록시를 기억해 다시 묻지 않는 때 등) DNS 때문으로 본다.
|
||||||
/// 덮개는 여기서 걷지 않는다 (새로 읽기 시작 · [다시 시도] 에서) - Android 는 오류 뒤에 읽기 끝이 또 온다.
|
/// 덮개는 여기서 걷지 않는다 (새로 읽기 시작 · [다시 시도] 에서) - Android 는 오류 뒤에 읽기 끝이 또 온다.
|
||||||
|
/// 248: 덮개가 보이는 동안은 아래 알림을 띄우지 않는다 (같은 안내 · [시스템 DNS 로 바꾸기] 가 두 개 보였다 - a4 933)
|
||||||
|
void _setDnsBlocked((String, String)? v) {
|
||||||
|
final g = NetGuard.instance.noticeBlockers;
|
||||||
|
if (_dnsBlocked == null && v != null) g.value++;
|
||||||
|
if (_dnsBlocked != null && v == null) g.value--;
|
||||||
|
_dnsBlocked = v;
|
||||||
|
}
|
||||||
|
|
||||||
/// 250: 사용자가 다른 곳으로 가거나 다시 읽음 - 덮개를 걷는다 (다시 실패하면 또 뜬다)
|
/// 250: 사용자가 다른 곳으로 가거나 다시 읽음 - 덮개를 걷는다 (다시 실패하면 또 뜬다)
|
||||||
void _userNavigated() {
|
void _userNavigated() {
|
||||||
_loadStartAt = DateTime.now();
|
_loadStartAt = DateTime.now();
|
||||||
if (_dnsBlocked != null && mounted) setState(() => _dnsBlocked = null);
|
if (_dnsBlocked != null && mounted) setState(() => _setDnsBlocked(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkDnsBlocked({String? host, String? error}) {
|
void _checkDnsBlocked({String? host, String? error}) {
|
||||||
|
|
@ -233,7 +241,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
||||||
if (next == _dnsBlocked) return;
|
if (next == _dnsBlocked) return;
|
||||||
// 기기에서 "덮개를 띄웠는데 안 보임" 과 "띄우지 않음" 을 가릴 수 있게
|
// 기기에서 "덮개를 띄웠는데 안 보임" 과 "띄우지 않음" 을 가릴 수 있게
|
||||||
widget.c.note(trf('DNS 안내 덮개를 띄움 ({0} · DNS {1})', [h, server]));
|
widget.c.note(trf('DNS 안내 덮개를 띄움 ({0} · DNS {1})', [h, server]));
|
||||||
setState(() => _dnsBlocked = next);
|
setState(() => _setDnsBlocked(next));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 250: DNS 실패면 웹 뷰 위에 덮는다 ([시스템 DNS 로 바꾸기] [다시 시도]).
|
/// 250: DNS 실패면 웹 뷰 위에 덮는다 ([시스템 DNS 로 바꾸기] [다시 시도]).
|
||||||
|
|
@ -242,7 +250,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
||||||
Widget _withDnsBlock(Widget view) {
|
Widget _withDnsBlock(Widget view) {
|
||||||
final b = _dnsBlocked;
|
final b = _dnsBlocked;
|
||||||
void retry() {
|
void retry() {
|
||||||
setState(() => _dnsBlocked = null);
|
setState(() => _setDnsBlocked(null));
|
||||||
_nav?.reload();
|
_nav?.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -595,6 +603,8 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
// 화면을 닫는 중에는 다른 위젯 (아래 알림) 을 바꾸지 않게 다음 차례에
|
||||||
|
if (_dnsBlocked != null) scheduleMicrotask(() => NetGuard.instance.noticeBlockers.value--);
|
||||||
browserRouteObserver.unsubscribe(this);
|
browserRouteObserver.unsubscribe(this);
|
||||||
BrowserPage._open--;
|
BrowserPage._open--;
|
||||||
widget.c.removeListener(_onSettings);
|
widget.c.removeListener(_onSettings);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -42,6 +43,50 @@ class DnsFailureLine extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 248: 아래 알림 (SnackBar) 의 내용. 스스로 상태를 듣다가 DNS 가 다시 답하거나 설정을 바꿔 상태가 풀리면,
|
||||||
|
/// 또는 같은 안내를 보이는 화면 (브라우저 덮개 · 설정의 DNS 칸) 이 열리면 알림을 닫는다.
|
||||||
|
/// (알림 닫기는 "지금 보이는 알림" 에만 해야 해서, 보이는 동안 그려지는 내용 위젯이 스스로 닫는다)
|
||||||
|
class DnsFailureNotice extends StatefulWidget {
|
||||||
|
final DnsFailure failure;
|
||||||
|
const DnsFailureNotice({super.key, required this.failure});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DnsFailureNotice> createState() => _DnsFailureNoticeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DnsFailureNoticeState extends State<DnsFailureNotice> {
|
||||||
|
final g = NetGuard.instance;
|
||||||
|
bool _closing = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
g.failure.addListener(_check);
|
||||||
|
g.noticeBlockers.addListener(_check);
|
||||||
|
// 대기열에 있다가 차례가 왔을 때 이미 풀렸으면 바로 닫는다
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _check());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
g.failure.removeListener(_check);
|
||||||
|
g.noticeBlockers.removeListener(_check);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _check() {
|
||||||
|
if (_closing || !mounted) return;
|
||||||
|
if (g.failure.value == widget.failure && g.noticeBlockers.value == 0) return;
|
||||||
|
_closing = true;
|
||||||
|
ScaffoldMessenger.maybeOf(context)?.hideCurrentSnackBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Text(trf(
|
||||||
|
'DNS 서버 ({0}) 에 닿지 않아 주소를 찾지 못했습니다. 인터넷 연결이나 환경 설정 > 네트워크 · DNS 의 서버 주소를 확인하세요.',
|
||||||
|
[widget.failure.server]));
|
||||||
|
}
|
||||||
|
|
||||||
/// 환경 설정 > 네트워크 · DNS (사용자 요청 10/10): DNS 서버 강제 (처음: 직접 지정 8.8.8.8 · 8.8.4.4)
|
/// 환경 설정 > 네트워크 · DNS (사용자 요청 10/10): DNS 서버 강제 (처음: 직접 지정 8.8.8.8 · 8.8.4.4)
|
||||||
class DnsSettings extends StatefulWidget {
|
class DnsSettings extends StatefulWidget {
|
||||||
final AppController c;
|
final AppController c;
|
||||||
|
|
@ -59,6 +104,20 @@ class _DnsSettingsState extends State<DnsSettings> {
|
||||||
|
|
||||||
Future<void> _save(void Function(AppSettings x) f) => c.updateSettings(f);
|
Future<void> _save(void Function(AppSettings x) f) => c.updateSettings(f);
|
||||||
|
|
||||||
|
// 248: 이 칸이 보이는 동안은 아래 알림을 띄우지 않는다 (맨 위 줄 [DnsFailureLine] 이 같은 안내를 하고, 알림이 칸을 가렸다)
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
NetGuard.instance.noticeBlockers.value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// 화면을 닫는 중에는 다른 위젯을 바꾸지 않게 다음 차례에
|
||||||
|
scheduleMicrotask(() => NetGuard.instance.noticeBlockers.value--);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/// [지금 확인]: 지금 설정으로 이름 하나를 찾아 본다 (어느 서버가 답했는지)
|
/// [지금 확인]: 지금 설정으로 이름 하나를 찾아 본다 (어느 서버가 답했는지)
|
||||||
Future<void> _check() async {
|
Future<void> _check() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
||||||
|
|
@ -331,7 +331,10 @@ void main() {
|
||||||
final dir = Directory.systemTemp.createTempSync('jj_browser_dns_');
|
final dir = Directory.systemTemp.createTempSync('jj_browser_dns_');
|
||||||
addTearDown(() => dir.deleteSync(recursive: true));
|
addTearDown(() => dir.deleteSync(recursive: true));
|
||||||
final g = NetGuard.instance;
|
final g = NetGuard.instance;
|
||||||
addTearDown(() => g.failure.value = null);
|
addTearDown(() {
|
||||||
|
g.failure.value = null;
|
||||||
|
g.noticeBlockers.value = 0;
|
||||||
|
});
|
||||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||||
final bm = BookmarksController(p.join(dir.path, 'bookmarks.json'));
|
final bm = BookmarksController(p.join(dir.path, 'bookmarks.json'));
|
||||||
late BrowserHost host;
|
late BrowserHost host;
|
||||||
|
|
@ -360,12 +363,15 @@ void main() {
|
||||||
expect(find.text(msg), findsOneWidget);
|
expect(find.text(msg), findsOneWidget);
|
||||||
// 덮개를 띄워도 웹 뷰는 그대로 (새로 만들면 페이지를 다시 읽어 덮개가 걷혔다 - 931)
|
// 덮개를 띄워도 웹 뷰는 그대로 (새로 만들면 페이지를 다시 읽어 덮개가 걷혔다 - 931)
|
||||||
expect(_Probe.created, 0);
|
expect(_Probe.created, 0);
|
||||||
|
// 248: 덮개가 보이는 동안은 아래 알림을 막는다
|
||||||
|
expect(g.noticeBlockers.value, 1);
|
||||||
// [다시 시도]: 덮개를 걷고 다시 읽기
|
// [다시 시도]: 덮개를 걷고 다시 읽기
|
||||||
await tester.tap(find.text('다시 시도'));
|
await tester.tap(find.text('다시 시도'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.text(msg), findsNothing);
|
expect(find.text(msg), findsNothing);
|
||||||
expect(nav.reloads, 1);
|
expect(nav.reloads, 1);
|
||||||
expect(_Probe.created, 0);
|
expect(_Probe.created, 0);
|
||||||
|
expect(g.noticeBlockers.value, 0);
|
||||||
// 다른 이름은 막지 않는다 · 서버가 다시 답한 뒤에는 지난 실패로 막지 않는다
|
// 다른 이름은 막지 않는다 · 서버가 다시 답한 뒤에는 지난 실패로 막지 않는다
|
||||||
host.onProgress(0.1);
|
host.onProgress(0.1);
|
||||||
host.onUrl('https://ok.example/');
|
host.onUrl('https://ok.example/');
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import 'package:jj_mkvmaker/app/settings.dart';
|
||||||
import 'package:jj_mkvmaker/app/tor_bundle.dart';
|
import 'package:jj_mkvmaker/app/tor_bundle.dart';
|
||||||
import 'package:jj_mkvmaker/app/tor_service.dart';
|
import 'package:jj_mkvmaker/app/tor_service.dart';
|
||||||
import 'package:jj_mkvmaker/core/tor_control.dart';
|
import 'package:jj_mkvmaker/core/tor_control.dart';
|
||||||
|
import 'package:jj_mkvmaker/main.dart' show watchDnsFailure;
|
||||||
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
|
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
|
||||||
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
|
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
|
||||||
import 'package:jj_mkvmaker/services/platform_services.dart';
|
import 'package:jj_mkvmaker/services/platform_services.dart';
|
||||||
|
|
@ -363,6 +364,61 @@ void main() {
|
||||||
expect(find.textContaining('Tor 대기 중'), findsNothing);
|
expect(find.textContaining('Tor 대기 중'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('248: 아래 알림 - 상태가 풀리면 닫힘 · 같은 안내를 보이는 화면 (덮개 · 설정 DNS 칸) 이 열려 있으면 띄우지 않고, 열리면 닫힘', (t) async {
|
||||||
|
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||||
|
final g = NetGuard.instance;
|
||||||
|
addTearDown(() {
|
||||||
|
g.failure.value = null;
|
||||||
|
g.noticeBlockers.value = 0;
|
||||||
|
});
|
||||||
|
final key = GlobalKey<ScaffoldMessengerState>();
|
||||||
|
watchDnsFailure(key, c);
|
||||||
|
await t.pumpWidget(MaterialApp(scaffoldMessengerKey: key, home: const Scaffold(body: Text('home'))));
|
||||||
|
const text = 'DNS 서버 (192.0.2.1) 에 닿지 않아 주소를 찾지 못했습니다. 인터넷 연결이나 환경 설정 > 네트워크 · DNS 의 서버 주소를 확인하세요.';
|
||||||
|
Future<void> settle() async {
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
await t.pump(const Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) 뜬 뒤 상태가 풀리면 (정한 서버가 다시 답함 · 설정을 바꿈) 바로 닫힌다
|
||||||
|
g.failure.value = DnsFailure('192.0.2.1', 'x', DateTime.now());
|
||||||
|
await settle();
|
||||||
|
expect(find.text(text), findsOneWidget);
|
||||||
|
await t.pump(const Duration(minutes: 5));
|
||||||
|
expect(find.text(text), findsOneWidget, reason: '버튼이 있는 안내는 저절로 사라지지 않는다');
|
||||||
|
g.failure.value = null;
|
||||||
|
await settle();
|
||||||
|
expect(find.text(text), findsNothing);
|
||||||
|
|
||||||
|
// 2) 같은 안내를 보이는 화면이 열려 있으면 띄우지 않는다
|
||||||
|
g.noticeBlockers.value = 1;
|
||||||
|
g.failure.value = DnsFailure('192.0.2.1', 'x', DateTime.now());
|
||||||
|
await settle();
|
||||||
|
expect(find.text(text), findsNothing);
|
||||||
|
g.failure.value = null;
|
||||||
|
g.noticeBlockers.value = 0;
|
||||||
|
|
||||||
|
// 3) 떠 있는 동안 그런 화면이 열리면 닫힌다
|
||||||
|
g.failure.value = DnsFailure('192.0.2.1', 'x', DateTime.now());
|
||||||
|
await settle();
|
||||||
|
expect(find.text(text), findsOneWidget);
|
||||||
|
g.noticeBlockers.value = 1;
|
||||||
|
await settle();
|
||||||
|
expect(find.text(text), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('248: 환경 설정의 DNS 칸 · 브라우저 덮개가 열려 있는 동안 알림을 막는 수', (t) async {
|
||||||
|
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||||
|
final g = NetGuard.instance;
|
||||||
|
expect(g.noticeBlockers.value, 0);
|
||||||
|
await t.pumpWidget(MaterialApp(home: Scaffold(body: SingleChildScrollView(child: DnsSettings(c: c)))));
|
||||||
|
expect(g.noticeBlockers.value, 1);
|
||||||
|
await t.pumpWidget(const SizedBox());
|
||||||
|
await t.pump();
|
||||||
|
expect(g.noticeBlockers.value, 0);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('249: DNS 서버에 닿지 않는 동안 환경 설정 > 네트워크 · DNS 에 상태 줄 · [시스템 DNS 로 바꾸기]', (t) async {
|
testWidgets('249: DNS 서버에 닿지 않는 동안 환경 설정 > 네트워크 · DNS 에 상태 줄 · [시스템 DNS 로 바꾸기]', (t) async {
|
||||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||||
final g = NetGuard.instance;
|
final g = NetGuard.instance;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue