설정 A 분야 8: 웹 브라우저 (BrowserSettings) - 키 10
- 이미 옮긴 분야 뒤의 같은 이름 (s.tor.homeUrl) 은 바꾸지 않음 - browser_page 의 _CefView 자기 필드 cefWhenElevated 는 그대로 (같은 이름이라 잘못 바뀐 것을 되돌림) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>modular
parent
6beeb22e43
commit
7c6320165a
|
|
@ -33,7 +33,7 @@ void main() {
|
|||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..webViewDataDir = dataDir
|
||||
..homeUrl = url;
|
||||
..browser.homeUrl = url;
|
||||
final bm = BookmarksController(p.join(dataDir, 'bm.json'));
|
||||
final d = DownloadManager(backends: const [], settings: () => c.settings, readClipboard: () async => null);
|
||||
final navKey = GlobalKey<NavigatorState>();
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ void main() {
|
|||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..webViewDataDir = p.join(data, 'webview')
|
||||
..browserEngine = 'chrome'
|
||||
..browser.engine = 'chrome'
|
||||
// 207: 이 PC 처럼 관리자 권한으로 실행 중이면 설정을 켠 때만 Chrome 엔진이 돈다 (--do-not-de-elevate)
|
||||
..cefWhenElevated = true;
|
||||
..browser.cefWhenElevated = true;
|
||||
final answered = <String>[];
|
||||
final logs = <String>[];
|
||||
final g = NetGuard.instance
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ void main() {
|
|||
c.settings
|
||||
..webViewDataDir = dataDir
|
||||
..uiLanguage = 'ko'
|
||||
..webTranslate = true;
|
||||
..browser.translate = true;
|
||||
final bm = BookmarksController(p.join(dataDir, 'bm.json'));
|
||||
await tester.pumpWidget(MaterialApp(home: BrowserPage(c: c, bookmarks: bm, initialUrl: url)));
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ void main() {
|
|||
// 3. JavaScript 끄기 → 페이지를 다시 읽고, 페이지 스크립트는 돌지 않는다
|
||||
expect(scriptRuns, greaterThan(0));
|
||||
final loads = pageLoads, runs = scriptRuns;
|
||||
await c.updateSettings((s) => s.webJavaScript = false);
|
||||
await c.updateSettings((s) => s.browser.javaScript = false);
|
||||
await waitServer(() => pageLoads > loads);
|
||||
await tester.runAsync(() => Future<void>.delayed(const Duration(seconds: 3)));
|
||||
// ignore: avoid_print
|
||||
|
|
@ -148,7 +148,7 @@ void main() {
|
|||
|
||||
// 5. 다시 켜기 → 다시 읽고 페이지 스크립트가 돈다, 번역도 다시
|
||||
final runs2 = scriptRuns;
|
||||
await c.updateSettings((s) => s.webJavaScript = true);
|
||||
await c.updateSettings((s) => s.browser.javaScript = true);
|
||||
await waitServer(() => scriptRuns > runs2);
|
||||
expect(scriptRuns, greaterThan(runs2));
|
||||
d = await waitFor((d) => d['late']!.isNotEmpty && hangul.hasMatch(d['h']!), 'JavaScript 다시 켬');
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import '../services/app_shell.dart' show appIconOf;
|
|||
import '../core/remote_idle.dart';
|
||||
import 'settings/records.dart';
|
||||
import 'settings/settings_group.dart';
|
||||
import 'settings/browser_settings.dart';
|
||||
import 'settings/download_settings.dart';
|
||||
import 'settings/reader_settings.dart';
|
||||
import 'settings/ai_image_settings.dart';
|
||||
|
|
@ -26,6 +27,7 @@ import 'settings/explorer_settings.dart';
|
|||
export 'settings/records.dart';
|
||||
export 'settings/settings_group.dart';
|
||||
export 'settings/settings_store.dart';
|
||||
export 'settings/browser_settings.dart';
|
||||
export 'settings/download_settings.dart';
|
||||
export 'settings/reader_settings.dart';
|
||||
export 'settings/ai_image_settings.dart';
|
||||
|
|
@ -46,14 +48,8 @@ class AppSettings {
|
|||
/// 73: 파일 탐색기 · Rsync 즐겨찾기 (폴더, Rsync 는 짝)
|
||||
List<FolderBookmark> folderBookmarks = [];
|
||||
|
||||
/// 웹 브라우저에서 YouTube 광고 자동 건너뛰기 (건너뛰기 버튼 누르기 · 건너뛸 수 없는 광고는 빨리 감기 · 소리 끄기)
|
||||
bool youtubeAdSkip = true;
|
||||
|
||||
/// YouTube 페이지의 광고 배너 · 광고 영역 숨기기
|
||||
bool youtubeAdHide = true;
|
||||
|
||||
/// 웹 브라우저 위에 다른 화면이 올라오거나 다른 화면으로 가면 페이지의 동영상 · 소리를 멈춤 (끄면 계속 들림)
|
||||
bool webPauseOnLeave = true;
|
||||
|
||||
|
||||
|
||||
|
|
@ -92,7 +88,7 @@ class AppSettings {
|
|||
/// 폰 세로 (좁은 화면) MKV 목록 크기: 'auto' 목록은 내용만큼 · 자세히 칸에 최소 높이 (처음 값) · 'small' · 'large' (관리자 판단 10/17)
|
||||
String mkvCompactList = 'auto';
|
||||
|
||||
/// 처음 화면: 'home' = 지금처럼 MKV 화면 (기본), 'browser' = 웹 브라우저 ([homeUrl]), 'files' = 파일 탐색기
|
||||
/// 처음 화면: 'home' = 지금처럼 MKV 화면 (기본), 'browser' = 웹 브라우저 ([browser.homeUrl]), 'files' = 파일 탐색기
|
||||
String startScreen = 'home';
|
||||
|
||||
/// 화면 (글자 · 버튼) 크기 배율. [uiScale] 은 지금 크기 (위쪽 막대의 − · + 로 바꿈),
|
||||
|
|
@ -127,22 +123,11 @@ class AppSettings {
|
|||
|
||||
/// 프로그램이 켜져 있을 때 탐색기에서 연 동영상 재생: 'same' 켜져 있는 창에서 / 'new' 새 재생 창
|
||||
String openFileWindow = 'same';
|
||||
String homeUrl = 'https://www.youtube.com/';
|
||||
|
||||
/// 앱 안 브라우저 엔진: 'edge' (WebView2, 기본). 'chrome' 은 내려받아 연결 (준비 중)
|
||||
String browserEngine = 'edge';
|
||||
|
||||
/// 207: 관리자 권한으로 실행 중일 때도 내장 Chrome 엔진을 쓸지 (처음 꺼짐 - 관리자 판단: 보안이 먼저, CEF 는 샌드박스가 꺼져 있다)
|
||||
bool cefWhenElevated = false;
|
||||
|
||||
/// "외부 브라우저로 열기" 에 쓸 브라우저: 'system' · 'chrome' · 'firefox' · 'edge' · 'whale'
|
||||
String externalBrowser = 'system';
|
||||
|
||||
/// 앱 안 브라우저: 다른 언어로 된 웹 페이지를 화면 언어 ([uiLanguage]) 로 자동 번역 (Google 번역)
|
||||
bool webTranslate = false;
|
||||
|
||||
/// 앱 안 브라우저: 페이지의 JavaScript 실행 (끄면 스크립트 없이 글 · 그림만)
|
||||
bool webJavaScript = true;
|
||||
|
||||
// ── 네트워크 · DNS (사용자 요청 10/10: "DNS 주소를 강제로 내가 설정한 주소로", 기본 8.8.8.8 · 보조 8.8.4.4 - 관리자 판단) ──
|
||||
|
||||
|
|
@ -253,8 +238,6 @@ class AppSettings {
|
|||
/// 66: 좌우로 밀 때 끝에서 처음으로 (끄면 끝에서 멈춤)
|
||||
bool swipeWrap = true;
|
||||
|
||||
/// 66-2: 웹 브라우저에서 화면 옮기기 (위쪽 막대를 좌우로) 안내를 한 번 보였는지
|
||||
bool browserSwipeHinted = false;
|
||||
|
||||
/// 저장 공간 정리 창에서 미리 체크할 묶음 (작업 임시 파일 · 받다 만 다운로드)
|
||||
List<String> cleanupPrechecked = ['work', 'download'];
|
||||
|
|
@ -403,8 +386,11 @@ class AppSettings {
|
|||
/// 다운로드 · YouTube (yt-dlp · aria2)
|
||||
final download = DownloadSettings();
|
||||
|
||||
/// 웹 브라우저 (앱 안 · 외부)
|
||||
final browser = BrowserSettings();
|
||||
|
||||
/// 설정 분야들 (설정 A) - 저마다 자기 키를 읽고 쓴다. toJson · fromJson 은 이 목록을 돌며 합치기만 한다
|
||||
List<SettingsGroup> get groups => [explorer, network, tor, copy, aiImage, reader, download];
|
||||
List<SettingsGroup> get groups => [explorer, network, tor, copy, aiImage, reader, download, browser];
|
||||
|
||||
/// [plainSecrets]: 안전 저장소를 쓸 수 없을 때만 (예전 파일에 있던 비밀 값을 잃지 않게) 예전처럼 파일에 둔다
|
||||
Map<String, Object?> toJson({bool plainSecrets = false}) {
|
||||
|
|
@ -414,9 +400,6 @@ class AppSettings {
|
|||
'mkvOutputRoot': mkvOutputRoot,
|
||||
'moveTargets': [for (final t in moveTargets) t.toJson()],
|
||||
'folderBookmarks': [for (final b in folderBookmarks) b.toJson()],
|
||||
'youtubeAdSkip': youtubeAdSkip,
|
||||
'youtubeAdHide': youtubeAdHide,
|
||||
'webPauseOnLeave': webPauseOnLeave,
|
||||
'minimizeToTray': minimizeToTray,
|
||||
'closeAction': closeAction,
|
||||
'showHotkey': showHotkey,
|
||||
|
|
@ -448,12 +431,6 @@ class AppSettings {
|
|||
'appIcon': appIcon,
|
||||
'uiLanguagesAdded': uiLanguagesAdded,
|
||||
'openFileWindow': openFileWindow,
|
||||
'homeUrl': homeUrl,
|
||||
'browserEngine': browserEngine,
|
||||
'cefWhenElevated': cefWhenElevated,
|
||||
'externalBrowser': externalBrowser,
|
||||
'webTranslate': webTranslate,
|
||||
'webJavaScript': webJavaScript,
|
||||
'zipLevel': zipLevel,
|
||||
'wifiShareMinutes': wifiShareMinutes,
|
||||
'wifiShareTailscale': wifiShareTailscale,
|
||||
|
|
@ -472,7 +449,6 @@ class AppSettings {
|
|||
'imageExts': imageExts,
|
||||
'startMenuShortcut': startMenuShortcut,
|
||||
'swipeWrap': swipeWrap,
|
||||
'browserSwipeHinted': browserSwipeHinted,
|
||||
'cleanupPrechecked': cleanupPrechecked,
|
||||
'migrationNotice': migrationNotice,
|
||||
'allowInnerToOuter': allowInnerToOuter,
|
||||
|
|
@ -542,9 +518,6 @@ class AppSettings {
|
|||
// 예전 판의 이동 폴더 하나 → "이동" 버튼으로
|
||||
if (j['moveTargets'] == null && j['moveTargetDir'] is String) MoveTarget('이동', j['moveTargetDir'] as String),
|
||||
]
|
||||
..youtubeAdSkip = j['youtubeAdSkip'] as bool? ?? true
|
||||
..youtubeAdHide = j['youtubeAdHide'] as bool? ?? true
|
||||
..webPauseOnLeave = j['webPauseOnLeave'] as bool? ?? true
|
||||
..minimizeToTray = j['minimizeToTray'] as bool? ?? true
|
||||
..closeAction = const ['background', 'quit', 'ask'].contains(j['closeAction']) ? j['closeAction'] as String : 'background'
|
||||
..showHotkey = j['showHotkey'] as String? ?? 'Ctrl+Shift+X'
|
||||
|
|
@ -567,12 +540,6 @@ class AppSettings {
|
|||
? j['screenOrientation'] as String
|
||||
: 'auto'
|
||||
..openFileWindow = j['openFileWindow'] == 'new' ? 'new' : 'same'
|
||||
..homeUrl = j['homeUrl'] as String? ?? 'https://www.youtube.com/'
|
||||
..browserEngine = j['browserEngine'] as String? ?? 'edge'
|
||||
..cefWhenElevated = j['cefWhenElevated'] == true
|
||||
..externalBrowser = j['externalBrowser'] as String? ?? 'system'
|
||||
..webTranslate = j['webTranslate'] as bool? ?? false
|
||||
..webJavaScript = j['webJavaScript'] as bool? ?? true
|
||||
..vaultEnabled = j['vaultEnabled'] == true
|
||||
..shizukuEnabled = j['shizukuEnabled'] == true
|
||||
..rootEnabled = j['rootEnabled'] == true
|
||||
|
|
@ -602,7 +569,6 @@ class AppSettings {
|
|||
: [...defaultImageExtensions]
|
||||
..startMenuShortcut = j['startMenuShortcut'] != false
|
||||
..swipeWrap = j['swipeWrap'] != false
|
||||
..browserSwipeHinted = j['browserSwipeHinted'] == true
|
||||
..cleanupPrechecked = j['cleanupPrechecked'] is List
|
||||
? [for (final x in j['cleanupPrechecked'] as List) '$x']
|
||||
: ['work', 'download']
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
|
||||
import 'settings_group.dart';
|
||||
|
||||
/// 약속 (설정 A): 환경 설정 > 웹 브라우저 (앱 안 · 외부) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다.
|
||||
/// 설정 하나를 더할 때는 이 파일만 고친다. 저장 파일의 키 이름은 예전 그대로 (평평한 키)
|
||||
class BrowserSettings implements SettingsGroup {
|
||||
/// 웹 브라우저에서 YouTube 광고 자동 건너뛰기 (건너뛰기 버튼 누르기 · 건너뛸 수 없는 광고는 빨리 감기 · 소리 끄기)
|
||||
bool adSkip = true;
|
||||
|
||||
/// YouTube 페이지의 광고 배너 · 광고 영역 숨기기
|
||||
bool adHide = true;
|
||||
|
||||
/// 웹 브라우저 위에 다른 화면이 올라오거나 다른 화면으로 가면 페이지의 동영상 · 소리를 멈춤 (끄면 계속 들림)
|
||||
bool pauseOnLeave = true;
|
||||
|
||||
String homeUrl = 'https://www.youtube.com/';
|
||||
|
||||
/// 앱 안 브라우저 엔진: 'edge' (WebView2, 기본). 'chrome' 은 내려받아 연결 (준비 중)
|
||||
String engine = 'edge';
|
||||
|
||||
/// 207: 관리자 권한으로 실행 중일 때도 내장 Chrome 엔진을 쓸지 (처음 꺼짐 - 관리자 판단: 보안이 먼저, CEF 는 샌드박스가 꺼져 있다)
|
||||
bool cefWhenElevated = false;
|
||||
|
||||
/// "외부 브라우저로 열기" 에 쓸 브라우저: 'system' · 'chrome' · 'firefox' · 'edge' · 'whale'
|
||||
String external = 'system';
|
||||
|
||||
/// 앱 안 브라우저: 다른 언어로 된 웹 페이지를 화면 언어 ([uiLanguage]) 로 자동 번역 (Google 번역)
|
||||
bool translate = false;
|
||||
|
||||
/// 앱 안 브라우저: 페이지의 JavaScript 실행 (끄면 스크립트 없이 글 · 그림만)
|
||||
bool javaScript = true;
|
||||
|
||||
/// 66-2: 웹 브라우저에서 화면 옮기기 (위쪽 막대를 좌우로) 안내를 한 번 보였는지
|
||||
bool swipeHinted = false;
|
||||
|
||||
@override
|
||||
void write(Map<String, Object?> j, {required bool plainSecrets}) {
|
||||
j['youtubeAdSkip'] = adSkip;
|
||||
j['youtubeAdHide'] = adHide;
|
||||
j['webPauseOnLeave'] = pauseOnLeave;
|
||||
j['homeUrl'] = homeUrl;
|
||||
j['browserEngine'] = engine;
|
||||
j['cefWhenElevated'] = cefWhenElevated;
|
||||
j['externalBrowser'] = external;
|
||||
j['webTranslate'] = translate;
|
||||
j['webJavaScript'] = javaScript;
|
||||
j['browserSwipeHinted'] = swipeHinted;
|
||||
}
|
||||
|
||||
@override
|
||||
void read(Map<String, dynamic> j) {
|
||||
adSkip = j['youtubeAdSkip'] as bool? ?? true;
|
||||
adHide = j['youtubeAdHide'] as bool? ?? true;
|
||||
pauseOnLeave = j['webPauseOnLeave'] as bool? ?? true;
|
||||
homeUrl = j['homeUrl'] as String? ?? 'https://www.youtube.com/';
|
||||
engine = j['browserEngine'] as String? ?? 'edge';
|
||||
cefWhenElevated = j['cefWhenElevated'] == true;
|
||||
external = j['externalBrowser'] as String? ?? 'system';
|
||||
translate = j['webTranslate'] as bool? ?? false;
|
||||
javaScript = j['webJavaScript'] as bool? ?? true;
|
||||
swipeHinted = j['browserSwipeHinted'] == true;
|
||||
}
|
||||
}
|
||||
|
|
@ -486,7 +486,7 @@ Future<void> setupNetwork(AppController c, String dataDir, {bool second = false}
|
|||
if (Platform.isAndroid) {
|
||||
WebProxy.log = c.note;
|
||||
// 환경 설정 "다른 화면으로 가면 소리 멈춤" 이 꺼져 있으면 일반 브라우저가 뒤에 있어도 DNS 문을 열어 둔다
|
||||
WebProxy.keepPlayingWhenHidden = () => !c.settings.webPauseOnLeave;
|
||||
WebProxy.keepPlayingWhenHidden = () => !c.settings.browser.pauseOnLeave;
|
||||
try {
|
||||
await WebProxy.apply();
|
||||
} catch (e) {
|
||||
|
|
@ -499,10 +499,10 @@ Future<void> setupNetwork(AppController c, String dataDir, {bool second = false}
|
|||
);
|
||||
}
|
||||
var last = c.settings.network.config.key;
|
||||
var lastPause = c.settings.webPauseOnLeave;
|
||||
var lastPause = c.settings.browser.pauseOnLeave;
|
||||
c.addListener(() async {
|
||||
if (Platform.isAndroid && c.settings.webPauseOnLeave != lastPause) {
|
||||
lastPause = c.settings.webPauseOnLeave;
|
||||
if (Platform.isAndroid && c.settings.browser.pauseOnLeave != lastPause) {
|
||||
lastPause = c.settings.browser.pauseOnLeave;
|
||||
await WebProxy.apply();
|
||||
}
|
||||
final now = c.settings.network.config.key;
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
/// 207: Chrome 엔진 [다시 시도] 때마다 새로
|
||||
int _cefTry = 0;
|
||||
|
||||
late String _url = widget.initialUrl ?? BrowserPage._lastUrl ?? widget.c.settings.homeUrl;
|
||||
late String _url = widget.initialUrl ?? BrowserPage._lastUrl ?? widget.c.settings.browser.homeUrl;
|
||||
String _title = '';
|
||||
double _progress = 1;
|
||||
|
||||
|
|
@ -355,7 +355,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
static final _translator = WebPageTranslator();
|
||||
|
||||
/// 이 화면에서 번역 중인지. 처음엔 설정을 따르고, 도구 막대의 [번역] 버튼으로 바꾼다.
|
||||
late bool _trOn = widget.c.settings.webTranslate;
|
||||
late bool _trOn = widget.c.settings.browser.translate;
|
||||
|
||||
/// 버튼으로 직접 켰다: 이미 화면 언어로 보이는 페이지도 번역한다
|
||||
bool _trForce = false;
|
||||
|
|
@ -374,12 +374,12 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
if (!mounted) return;
|
||||
_applyCookieExport();
|
||||
final s = widget.c.settings;
|
||||
if (s.webJavaScript != _lastJs) {
|
||||
_lastJs = s.webJavaScript;
|
||||
if (s.browser.javaScript != _lastJs) {
|
||||
_lastJs = s.browser.javaScript;
|
||||
_nav?.setJavaScript(_lastJs);
|
||||
}
|
||||
if (s.webTranslate != _lastTranslate) {
|
||||
_lastTranslate = s.webTranslate;
|
||||
if (s.browser.translate != _lastTranslate) {
|
||||
_lastTranslate = s.browser.translate;
|
||||
_setTranslate(_lastTranslate, force: false);
|
||||
} else if (s.uiLanguage != _lastLang && _trOn) {
|
||||
_translateNow(); // 다른 언어로 다시 번역 (스크립트가 앞의 번역을 되돌린 뒤 번역)
|
||||
|
|
@ -448,17 +448,17 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
super.initState();
|
||||
BrowserPage._open++;
|
||||
final s = widget.c.settings;
|
||||
_lastTranslate = s.webTranslate;
|
||||
_lastJs = s.webJavaScript;
|
||||
_lastTranslate = s.browser.translate;
|
||||
_lastJs = s.browser.javaScript;
|
||||
_lastLang = s.uiLanguage;
|
||||
_applyCookieExport();
|
||||
widget.c.addListener(_onSettings);
|
||||
if (_trOn) _translateNow();
|
||||
// 66-2: 브라우저 가운데는 페이지가 좌우로 밀리므로 화면 옮기기는 위쪽 막대에서 - 처음 한 번만 알린다.
|
||||
// 알림 (SnackBar) 은 다른 알림 (다운로드 등) 을 뒤로 밀어 기다리게 하므로 위쪽 막대 아래의 한 줄로
|
||||
if (s.swipeNav && !s.browserSwipeHinted) {
|
||||
if (s.swipeNav && !s.browser.swipeHinted) {
|
||||
_swipeHint = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => widget.c.updateSettings((x) => x.browserSwipeHinted = true));
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => widget.c.updateSettings((x) => x.browser.swipeHinted = true));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -580,7 +580,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
void didPushNext() {
|
||||
_visible = false;
|
||||
// 환경 설정 > 웹 브라우저 > "다른 화면으로 가면 페이지 소리 멈춤" (끄면 다른 화면에서도 계속 들림)
|
||||
if (widget.c.settings.webPauseOnLeave) _nav?.pauseMedia();
|
||||
if (widget.c.settings.browser.pauseOnLeave) _nav?.pauseMedia();
|
||||
}
|
||||
|
||||
/// 설정 화면 등에서 돌아왔다: 바뀐 광고 설정을 지금 페이지에 적용
|
||||
|
|
@ -595,13 +595,13 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
void _applyAdSettings() {
|
||||
if (!(Uri.tryParse(_url)?.host ?? '').endsWith('youtube.com')) return;
|
||||
final s = widget.c.settings;
|
||||
_nav?.runScript(youtubeAdScript(skip: s.youtubeAdSkip, hide: s.youtubeAdHide, badge: tr('광고 건너뛰는 중…')));
|
||||
_nav?.runScript(youtubeAdScript(skip: s.browser.adSkip, hide: s.browser.adHide, badge: tr('광고 건너뛰는 중…')));
|
||||
}
|
||||
|
||||
/// MKV 화면 등으로 가며 브라우저 화면이 닫힌다: 살려 둔 웹뷰에서 소리만 나지 않게 멈춘다
|
||||
@override
|
||||
void didPop() {
|
||||
if (widget.c.settings.webPauseOnLeave) _nav?.pauseMedia();
|
||||
if (widget.c.settings.browser.pauseOnLeave) _nav?.pauseMedia();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -670,7 +670,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
}
|
||||
|
||||
void _openExternal(String url) =>
|
||||
widget.c.services.shell.openUrl(url, browser: widget.c.settings.externalBrowser);
|
||||
widget.c.services.shell.openUrl(url, browser: widget.c.settings.browser.external);
|
||||
|
||||
// ───────── 즐겨찾기 ─────────
|
||||
|
||||
|
|
@ -774,26 +774,26 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
Expanded(
|
||||
child: _withDnsBlock(widget.viewBuilder?.call(_host, _url) ??
|
||||
// 환경 설정에서 Chrome 을 고르고 엔진이 이번 실행에 준비되어 있으면 Chrome, 아니면 Edge
|
||||
(widget.c.settings.browserEngine == 'chrome' && CefRuntime.readyThisRun
|
||||
(widget.c.settings.browser.engine == 'chrome' && CefRuntime.readyThisRun
|
||||
// 207: 관리자 권한이면 처음 값으로 Chrome 엔진을 시작하지 않는다 (보안) - 이유와 고를 것
|
||||
? Elevation.elevated && !widget.c.settings.cefWhenElevated
|
||||
? Elevation.elevated && !widget.c.settings.browser.cefWhenElevated
|
||||
? _CefElevatedBlocked(c: widget.c)
|
||||
// Chrome 엔진은 JavaScript 설정을 만든 뒤에 바꿀 수 없어 바뀌면 새로 만든다
|
||||
: _CefView(
|
||||
key: ValueKey('${widget.c.settings.webJavaScript}|$_cefTry'),
|
||||
key: ValueKey('${widget.c.settings.browser.javaScript}|$_cefTry'),
|
||||
host: _host,
|
||||
initialUrl: _url,
|
||||
dataDir: widget.c.settings.webViewDataDir,
|
||||
javaScript: widget.c.settings.webJavaScript,
|
||||
javaScript: widget.c.settings.browser.javaScript,
|
||||
// 207: 시작하지 못하면 사용자가 바로 고를 수 있게
|
||||
onUseEdge: () => widget.c.updateSettings((x) => x.browserEngine = 'edge'),
|
||||
onUseEdge: () => widget.c.updateSettings((x) => x.browser.engine = 'edge'),
|
||||
onRetry: () => setState(() => _cefTry++),
|
||||
cefWhenElevated: widget.c.settings.cefWhenElevated)
|
||||
cefWhenElevated: widget.c.settings.browser.cefWhenElevated)
|
||||
: _EdgeView(
|
||||
host: _host,
|
||||
initialUrl: _url,
|
||||
dataDir: widget.c.settings.webViewDataDir,
|
||||
javaScript: widget.c.settings.webJavaScript))),
|
||||
javaScript: widget.c.settings.browser.javaScript))),
|
||||
),
|
||||
]),
|
||||
),
|
||||
|
|
@ -852,7 +852,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
_progress < 1
|
||||
? btn(Icons.close, tr('중지'), () => _nav?.stop())
|
||||
: btn(Icons.refresh, keyHint(tr('새로고침'), 'F5'), () => _nav?.reload()),
|
||||
if (!compact) btn(Icons.home_outlined, tr('홈'), () => _go(widget.c.settings.homeUrl)),
|
||||
if (!compact) btn(Icons.home_outlined, tr('홈'), () => _go(widget.c.settings.browser.homeUrl)),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
|
|
@ -940,7 +940,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
tooltip: tr('더 보기'),
|
||||
icon: const Icon(Icons.more_vert, size: 20),
|
||||
onSelected: (v) => switch (v) {
|
||||
'home' => _go(widget.c.settings.homeUrl),
|
||||
'home' => _go(widget.c.settings.browser.homeUrl),
|
||||
'translate' => _setTranslate(!_trOn, force: true),
|
||||
'external' => _openExternal(_url),
|
||||
'work' => _toggleWork(),
|
||||
|
|
@ -1349,7 +1349,7 @@ class _CefElevatedBlocked extends StatelessWidget {
|
|||
const SizedBox(height: 16),
|
||||
Wrap(spacing: 12, runSpacing: 8, alignment: WrapAlignment.center, children: [
|
||||
FilledButton(
|
||||
onPressed: () => c.updateSettings((x) => x.browserEngine = 'edge'),
|
||||
onPressed: () => c.updateSettings((x) => x.browser.engine = 'edge'),
|
||||
child: Text(tr('Edge 엔진으로 바꾸기')),
|
||||
),
|
||||
OutlinedButton(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ Future<bool> chooseChromeEngine(BuildContext context, AppController c) async {
|
|||
);
|
||||
if (installed != true || !context.mounted) return false;
|
||||
}
|
||||
await c.updateSettings((s) => s.browserEngine = 'chrome');
|
||||
await c.updateSettings((s) => s.browser.engine = 'chrome');
|
||||
if (!context.mounted) return true;
|
||||
if (!CefRuntime.readyThisRun) {
|
||||
final restart = await showDialog<bool>(
|
||||
|
|
@ -147,7 +147,7 @@ Future<bool> confirmCefWhenElevated(BuildContext context, AppController c) async
|
|||
),
|
||||
);
|
||||
if (ok != true) return false;
|
||||
await c.updateSettings((x) => x.cefWhenElevated = true);
|
||||
await c.updateSettings((x) => x.browser.cefWhenElevated = true);
|
||||
c.note(tr('관리자 권한일 때도 Chrome 엔진 쓰기를 켰습니다 (권장하지 않음).'));
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ class _DnsSettingsState extends State<DnsSettings> {
|
|||
if (!desk) tr('· Android 의 yt-dlp 새 버전 확인 (다운로드 도구 안에서 따로 연결)'),
|
||||
].join('\n')),
|
||||
),
|
||||
if (desk && s.browserEngine == 'chrome' && CefRuntime.readyThisRun)
|
||||
if (desk && s.browser.engine == 'chrome' && CefRuntime.readyThisRun)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: Text(tr('Chrome 엔진은 앱을 다시 시작하면 바뀐 설정을 씁니다.'), style: TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||
|
|
|
|||
|
|
@ -32,29 +32,29 @@ class _BrowserSettingsSectionState extends State<BrowserSettingsSection> {
|
|||
Widget build(BuildContext context) {
|
||||
final s = c.settings;
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [
|
||||
settingsTextTile(tr('홈 주소'), s.homeUrl, tr('웹 브라우저의 시작 · 🏠 주소'),
|
||||
(v) => c.updateSettings((x) => x.homeUrl = v.trim().isEmpty ? 'https://www.youtube.com/' : v.trim())),
|
||||
settingsTextTile(tr('홈 주소'), s.browser.homeUrl, tr('웹 브라우저의 시작 · 🏠 주소'),
|
||||
(v) => c.updateSettings((x) => x.browser.homeUrl = v.trim().isEmpty ? 'https://www.youtube.com/' : v.trim())),
|
||||
SwitchListTile(
|
||||
value: s.youtubeAdSkip,
|
||||
onChanged: (v) => c.updateSettings((x) => x.youtubeAdSkip = v),
|
||||
value: s.browser.adSkip,
|
||||
onChanged: (v) => c.updateSettings((x) => x.browser.adSkip = v),
|
||||
title: Text(tr('YouTube 광고 자동 건너뛰기')),
|
||||
subtitle: Text(tr('앱 안 웹 브라우저: [건너뛰기] 를 대신 누르고, 건너뛸 수 없는 광고는 소리를 끄고 빨리 넘깁니다 ' '(넘기는 동안 화면 위쪽에 "광고 건너뛰는 중…")')),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.youtubeAdHide,
|
||||
onChanged: (v) => c.updateSettings((x) => x.youtubeAdHide = v),
|
||||
value: s.browser.adHide,
|
||||
onChanged: (v) => c.updateSettings((x) => x.browser.adHide = v),
|
||||
title: Text(tr('YouTube 광고 배너 숨기기')),
|
||||
subtitle: Text(tr('목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다')),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.webPauseOnLeave,
|
||||
onChanged: (v) => c.updateSettings((x) => x.webPauseOnLeave = v),
|
||||
value: s.browser.pauseOnLeave,
|
||||
onChanged: (v) => c.updateSettings((x) => x.browser.pauseOnLeave = v),
|
||||
title: Text(tr('다른 화면으로 가면 페이지 소리 멈춤')),
|
||||
subtitle: Text(tr('끄면 환경 설정 · 다운로드 목록 · 다른 화면을 보는 동안에도 웹 페이지의 소리가 계속 들립니다')),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.webTranslate,
|
||||
onChanged: (v) => c.updateSettings((x) => x.webTranslate = v),
|
||||
value: s.browser.translate,
|
||||
onChanged: (v) => c.updateSettings((x) => x.browser.translate = v),
|
||||
title: Text(tr('웹 페이지 자동 번역')),
|
||||
subtitle: Text(trf(
|
||||
'다른 언어로 된 페이지를 화면 언어 ({0}) 로 번역합니다. 페이지의 글을 Google 번역으로 보냅니다. '
|
||||
|
|
@ -62,8 +62,8 @@ class _BrowserSettingsSectionState extends State<BrowserSettingsSection> {
|
|||
[I18nController.nativeName(s.uiLanguage)])),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.webJavaScript,
|
||||
onChanged: (v) => c.updateSettings((x) => x.webJavaScript = v),
|
||||
value: s.browser.javaScript,
|
||||
onChanged: (v) => c.updateSettings((x) => x.browser.javaScript = v),
|
||||
title: Text(tr('JavaScript 사용')),
|
||||
subtitle: Text(desk
|
||||
? tr('끄면 페이지의 스크립트를 실행하지 않습니다 (광고 · 추적이 줄지만 YouTube 처럼 스크립트로 만든 사이트는 보이지 않음). '
|
||||
|
|
@ -75,10 +75,10 @@ class _BrowserSettingsSectionState extends State<BrowserSettingsSection> {
|
|||
SettingTile(
|
||||
title: Text(tr('브라우저 엔진 (앱 안)')),
|
||||
subtitle: Text(
|
||||
trf('Edge: Windows 에 들어 있어 따로 설치하지 않습니다. 여기서 YouTube 에 로그인하면 다운로드에도 그 로그인이 쓰입니다.\n' 'Chrome: 고르면 약 {0}MB 를 내려받고 다시 시작한 뒤 쓸 수 있습니다 ' '(Google 로그인은 막힐 수 있음).' '{1}' '{2}', [CefRuntime.approxDownloadMb, CefRuntime.installed ? trf(' 지금 설치됨 ({0}MB)', [CefRuntime.installedMb()]) : '', s.browserEngine == 'chrome' && !CefRuntime.readyThisRun ? tr(' · 다시 시작해야 Chrome 으로 바뀝니다') : ''])),
|
||||
trf('Edge: Windows 에 들어 있어 따로 설치하지 않습니다. 여기서 YouTube 에 로그인하면 다운로드에도 그 로그인이 쓰입니다.\n' 'Chrome: 고르면 약 {0}MB 를 내려받고 다시 시작한 뒤 쓸 수 있습니다 ' '(Google 로그인은 막힐 수 있음).' '{1}' '{2}', [CefRuntime.approxDownloadMb, CefRuntime.installed ? trf(' 지금 설치됨 ({0}MB)', [CefRuntime.installedMb()]) : '', s.browser.engine == 'chrome' && !CefRuntime.readyThisRun ? tr(' · 다시 시작해야 Chrome 으로 바뀝니다') : ''])),
|
||||
isThreeLine: true,
|
||||
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
if (s.browserEngine == 'edge' && CefRuntime.installed && !CefRuntime.readyThisRun)
|
||||
if (s.browser.engine == 'edge' && CefRuntime.installed && !CefRuntime.readyThisRun)
|
||||
IconButton(
|
||||
tooltip: tr('내려받은 Chrome 엔진 지우기'),
|
||||
icon: Icon(Icons.delete_outline, color: JjColors.textDim),
|
||||
|
|
@ -89,7 +89,7 @@ class _BrowserSettingsSectionState extends State<BrowserSettingsSection> {
|
|||
},
|
||||
),
|
||||
DropdownButton<String>(
|
||||
value: s.browserEngine,
|
||||
value: s.browser.engine,
|
||||
items: [
|
||||
DropdownMenuItem(value: 'edge', child: Text(tr('Edge (내장)'))),
|
||||
DropdownMenuItem(
|
||||
|
|
@ -101,7 +101,7 @@ class _BrowserSettingsSectionState extends State<BrowserSettingsSection> {
|
|||
await chooseChromeEngine(context, c);
|
||||
if (mounted) setState(() {});
|
||||
} else {
|
||||
await c.updateSettings((x) => x.browserEngine = 'edge');
|
||||
await c.updateSettings((x) => x.browser.engine = 'edge');
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
@ -109,12 +109,12 @@ class _BrowserSettingsSectionState extends State<BrowserSettingsSection> {
|
|||
),
|
||||
// 207: 관리자 권한일 때도 Chrome 엔진 쓰기 (처음 꺼짐, 켤 때 경고)
|
||||
SwitchListTile(
|
||||
value: s.cefWhenElevated,
|
||||
value: s.browser.cefWhenElevated,
|
||||
onChanged: (v) async {
|
||||
if (v) {
|
||||
await confirmCefWhenElevated(context, c);
|
||||
} else {
|
||||
await c.updateSettings((x) => x.cefWhenElevated = false);
|
||||
await c.updateSettings((x) => x.browser.cefWhenElevated = false);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
|
|
@ -127,10 +127,10 @@ class _BrowserSettingsSectionState extends State<BrowserSettingsSection> {
|
|||
title: Text(tr('외부 브라우저')),
|
||||
subtitle: Text(tr('"외부 브라우저로 열기" 에 쓸 브라우저 (이 PC 에 설치된 것)')),
|
||||
trailing: DropdownButton<String>(
|
||||
value: s.externalBrowser,
|
||||
value: s.browser.external,
|
||||
items: [
|
||||
DropdownMenuItem(value: 'system', child: Text(tr('Windows 기본 브라우저'))),
|
||||
for (final b in {...c.services.shell.installedBrowsers(), if (s.externalBrowser != 'system') s.externalBrowser})
|
||||
for (final b in {...c.services.shell.installedBrowsers(), if (s.browser.external != 'system') s.browser.external})
|
||||
DropdownMenuItem(value: b, child: Text(switch (b) {
|
||||
'chrome' => 'Chrome',
|
||||
'firefox' => 'Firefox',
|
||||
|
|
@ -139,7 +139,7 @@ class _BrowserSettingsSectionState extends State<BrowserSettingsSection> {
|
|||
_ => b,
|
||||
})),
|
||||
],
|
||||
onChanged: (v) => c.updateSettings((x) => x.externalBrowser = v!),
|
||||
onChanged: (v) => c.updateSettings((x) => x.browser.external = v!),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -633,7 +633,7 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('위쪽 막대를 좌우로 밀면'), findsOneWidget);
|
||||
expect(find.byType(SnackBar), findsNothing, reason: '알림 (다운로드 등) 을 뒤로 밀지 않게 알림이 아니라 한 줄');
|
||||
expect(c.settings.browserSwipeHinted, isTrue);
|
||||
expect(c.settings.browser.swipeHinted, isTrue);
|
||||
await tester.tap(find.byTooltip('안내 닫기'));
|
||||
await tester.pump();
|
||||
expect(find.textContaining('위쪽 막대를 좌우로 밀면'), findsNothing);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ void main() {
|
|||
dir = Directory.systemTemp.createTempSync('jj_cef_elev_');
|
||||
c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..browserEngine = 'chrome'
|
||||
..browser.engine = 'chrome'
|
||||
..webViewDataDir = p.join(dir.path, 'webview');
|
||||
bm = BookmarksController(p.join(dir.path, 'bookmarks.json'));
|
||||
CefRuntime.readyThisRun = true;
|
||||
|
|
@ -52,7 +52,7 @@ void main() {
|
|||
expect(find.text('관리자 권한으로 실행 중이라 Chrome 엔진을 쓸 수 없습니다 (보안)'), findsOneWidget);
|
||||
await t.tap(find.text('Edge 엔진으로 바꾸기'));
|
||||
await t.pump();
|
||||
expect(c.settings.browserEngine, 'edge');
|
||||
expect(c.settings.browser.engine, 'edge');
|
||||
await t.pumpWidget(const SizedBox());
|
||||
});
|
||||
|
||||
|
|
@ -64,12 +64,12 @@ void main() {
|
|||
expect(find.textContaining('샌드박스 (웹 페이지를 가두는 보호) 가 꺼져'), findsOneWidget);
|
||||
await t.tap(find.text('취소'));
|
||||
await t.pumpAndSettle();
|
||||
expect(c.settings.cefWhenElevated, isFalse);
|
||||
expect(c.settings.browser.cefWhenElevated, isFalse);
|
||||
await t.tap(find.text('그래도 Chrome 엔진 쓰기…'));
|
||||
await t.pumpAndSettle();
|
||||
await t.tap(find.text('그래도 쓰기'));
|
||||
await t.pumpAndSettle();
|
||||
expect(c.settings.cefWhenElevated, isTrue);
|
||||
expect(c.settings.browser.cefWhenElevated, isTrue);
|
||||
expect(find.text('관리자 권한으로 실행 중이라 Chrome 엔진을 쓸 수 없습니다 (보안)'), findsNothing);
|
||||
await t.pumpWidget(const SizedBox());
|
||||
});
|
||||
|
|
@ -86,13 +86,13 @@ void main() {
|
|||
expect(find.text('다시 시도'), findsOneWidget);
|
||||
await t.tap(find.text('Edge 엔진으로 바꾸기'));
|
||||
await t.pump();
|
||||
expect(c.settings.browserEngine, 'edge');
|
||||
expect(c.settings.browser.engine, 'edge');
|
||||
await t.pumpWidget(const SizedBox());
|
||||
});
|
||||
|
||||
test('설정: 관리자 권한일 때 Chrome 엔진 쓰기는 처음 꺼짐 · 저장 · 틀린 값은 꺼짐', () {
|
||||
expect(AppSettings().cefWhenElevated, isFalse);
|
||||
expect(AppSettings.fromJson((AppSettings()..cefWhenElevated = true).toJson()).cefWhenElevated, isTrue);
|
||||
expect(AppSettings.fromJson({'cefWhenElevated': 'yes'}).cefWhenElevated, isFalse);
|
||||
expect(AppSettings().browser.cefWhenElevated, isFalse);
|
||||
expect(AppSettings.fromJson((AppSettings()..browser.cefWhenElevated = true).toJson()).browser.cefWhenElevated, isTrue);
|
||||
expect(AppSettings.fromJson({'cefWhenElevated': 'yes'}).browser.cefWhenElevated, isFalse);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,8 +175,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('66-2: 웹 브라우저 화면 옮기기 안내는 한 번만 (설정에 남음)', () {
|
||||
expect(AppSettings().browserSwipeHinted, isFalse);
|
||||
expect(AppSettings.fromJson((AppSettings()..browserSwipeHinted = true).toJson()).browserSwipeHinted, isTrue);
|
||||
expect(AppSettings().browser.swipeHinted, isFalse);
|
||||
expect(AppSettings.fromJson((AppSettings()..browser.swipeHinted = true).toJson()).browser.swipeHinted, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('174: MKV 화면이 맨 처음 화면이 아니어도 (홈 화면 = 탐색기 등) MKV 아이콘이 "지금 여기"', (tester) async {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ void main() {
|
|||
expect(s.tripleTapAction, 'move');
|
||||
expect(s.davExternalOpen, 'ask');
|
||||
// 이미 있던 설정의 기본값도 결정대로
|
||||
expect([s.webPauseOnLeave, s.autoCheckUpdates, s.youtubeAdSkip, s.youtubeAdHide, s.runInBackground, s.swipeNav],
|
||||
expect([s.browser.pauseOnLeave, s.autoCheckUpdates, s.browser.adSkip, s.browser.adHide, s.runInBackground, s.swipeNav],
|
||||
everyElement(isTrue));
|
||||
// 탐색기 처음 배치 = 두 창 (규칙 7 · 관리자 결정 3, 10/22)
|
||||
expect([s.copy.liveSyncOnStart, s.copy.liveSyncIntervalSec, s.explorer.layout, s.reader.zipComic], ['auto', 30, 'dual', false]);
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ void main() {
|
|||
// 닫으면 설정을 바꿔도 (같은 상황) 다시 뜨지 않는다
|
||||
await tester.tap(find.text('닫기'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.runAsync(() => c.updateSettings((s) => s.homeUrl = 'https://x.example'));
|
||||
await tester.runAsync(() => c.updateSettings((s) => s.browser.homeUrl = 'https://x.example'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('새 비밀번호는 아직 저장되지 않았습니다. 앱을 끄면 예전 비밀번호로 돌아갑니다.'), findsNothing);
|
||||
DavRegistry.configure([]);
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ void main() {
|
|||
// 예전 판: 모르는 항목을 지우고 저장
|
||||
File(file).writeAsStringSync(jsonEncode({'homeUrl': 'https://old.example'}));
|
||||
final s = await store().load();
|
||||
expect(s.homeUrl, 'https://old.example');
|
||||
expect(s.browser.homeUrl, 'https://old.example');
|
||||
expect(s.webdavServers.single.url, 'https://nas');
|
||||
expect(s.webdavServers.single.password, 'pw');
|
||||
expect(s.openSubtitlesKey, 'KEY-2');
|
||||
|
|
@ -104,7 +104,7 @@ void main() {
|
|||
final st = store();
|
||||
final s = await st.load();
|
||||
expect(s.openSubtitlesKey, 'KEY-3');
|
||||
await st.save(s..homeUrl = 'https://x.example');
|
||||
await st.save(s..browser.homeUrl = 'https://x.example');
|
||||
expect(text(file), contains('KEY-3'));
|
||||
expect(text(file), contains('plain'));
|
||||
expect(secrets.values, {'dav.pw.d1': 'kept'});
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ void main() {
|
|||
|
||||
test('저장: 임시 파일에 쓴 뒤 바꿔치기 · 직전 정상본은 .bak · 남는 임시 파일 없음', () async {
|
||||
final store = SettingsStore(file);
|
||||
await store.save(AppSettings()..homeUrl = 'https://a.example');
|
||||
await store.save(AppSettings()..homeUrl = 'https://b.example');
|
||||
expect((await SettingsStore(file).load()).homeUrl, 'https://b.example');
|
||||
await store.save(AppSettings()..browser.homeUrl = 'https://a.example');
|
||||
await store.save(AppSettings()..browser.homeUrl = 'https://b.example');
|
||||
expect((await SettingsStore(file).load()).browser.homeUrl, 'https://b.example');
|
||||
expect(File('$file.bak').readAsStringSync(), contains('https://a.example'));
|
||||
expect(File('$file.tmp').existsSync(), isFalse);
|
||||
// 여러 번 빠르게 저장해도 마지막 것이 남는다 (순서대로)
|
||||
await Future.wait([for (var i = 0; i < 5; i++) store.save(AppSettings()..homeUrl = 'https://$i.example')]);
|
||||
expect((await SettingsStore(file).load()).homeUrl, 'https://4.example');
|
||||
await Future.wait([for (var i = 0; i < 5; i++) store.save(AppSettings()..browser.homeUrl = 'https://$i.example')]);
|
||||
expect((await SettingsStore(file).load()).browser.homeUrl, 'https://4.example');
|
||||
});
|
||||
|
||||
test('관리자 지시 (10/21): 보통 저장은 이 판이 만드는 꼴의 열쇠만 지운다 - 모르는 꼴 (새 판의 dav.token.x 등) 은 남김 · 지운 서버의 아는 열쇠만', () async {
|
||||
|
|
@ -98,11 +98,11 @@ void main() {
|
|||
|
||||
test('깨진 설정 파일: 백업으로 되살리고, 깨진 파일은 보관 · 알림 정보', () async {
|
||||
final store = SettingsStore(file);
|
||||
await store.save(AppSettings()..homeUrl = 'https://good.example');
|
||||
await store.save(AppSettings()..homeUrl = 'https://good2.example'); // .bak = good
|
||||
await store.save(AppSettings()..browser.homeUrl = 'https://good.example');
|
||||
await store.save(AppSettings()..browser.homeUrl = 'https://good2.example'); // .bak = good
|
||||
File(file).writeAsStringSync('{"homeUrl": "https://half'); // 반쯤 쓰다 끊긴 파일
|
||||
final s = await store.load();
|
||||
expect(s.homeUrl, 'https://good.example');
|
||||
expect(s.browser.homeUrl, 'https://good.example');
|
||||
expect(store.problem!.restoredFromBackup, isTrue);
|
||||
final kept = store.problem!.brokenCopy!;
|
||||
expect(File(kept).readAsStringSync(), '{"homeUrl": "https://half');
|
||||
|
|
@ -115,7 +115,7 @@ void main() {
|
|||
File(file).writeAsStringSync('not json');
|
||||
final store = SettingsStore(file);
|
||||
final s = await store.load();
|
||||
expect(s.homeUrl, AppSettings().homeUrl);
|
||||
expect(s.browser.homeUrl, AppSettings().browser.homeUrl);
|
||||
expect(store.problem!.restoredFromBackup, isFalse);
|
||||
expect(File(store.problem!.brokenCopy!).readAsStringSync(), 'not json');
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ void main() {
|
|||
File(file).deleteSync();
|
||||
File('$file.bak').writeAsStringSync('{"homeUrl": "https://bak.example"}');
|
||||
final again = SettingsStore(file);
|
||||
expect((await again.load()).homeUrl, 'https://bak.example');
|
||||
expect((await again.load()).browser.homeUrl, 'https://bak.example');
|
||||
expect(again.problem!.restoredFromBackup, isTrue);
|
||||
// 정상이면 알림 없음
|
||||
await again.save(AppSettings());
|
||||
|
|
|
|||
|
|
@ -96,13 +96,13 @@ void main() {
|
|||
|
||||
test('설정: 번역 (기본 끔) · JavaScript (기본 켬) 저장 · 읽기', () {
|
||||
final d = AppSettings();
|
||||
expect(d.webTranslate, isFalse);
|
||||
expect(d.webJavaScript, isTrue);
|
||||
expect(d.browser.translate, isFalse);
|
||||
expect(d.browser.javaScript, isTrue);
|
||||
final s = AppSettings.fromJson(jsonDecode(jsonEncode((AppSettings()
|
||||
..webTranslate = true
|
||||
..webJavaScript = false)
|
||||
..browser.translate = true
|
||||
..browser.javaScript = false)
|
||||
.toJson())) as Map<String, dynamic>);
|
||||
expect(s.webTranslate, isTrue);
|
||||
expect(s.webJavaScript, isFalse);
|
||||
expect(s.browser.translate, isTrue);
|
||||
expect(s.browser.javaScript, isFalse);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ console.log(JSON.stringify(r));
|
|||
void main() {
|
||||
test('설정: 광고 건너뛰기 · 숨기기 기본 켜짐, 저장', () {
|
||||
final s = AppSettings();
|
||||
expect([s.youtubeAdSkip, s.youtubeAdHide], [true, true]);
|
||||
expect([s.browser.adSkip, s.browser.adHide], [true, true]);
|
||||
s
|
||||
..youtubeAdSkip = false
|
||||
..youtubeAdHide = false;
|
||||
..browser.adSkip = false
|
||||
..browser.adHide = false;
|
||||
final back = AppSettings.fromJson(s.toJson());
|
||||
expect([back.youtubeAdSkip, back.youtubeAdHide], [false, false]);
|
||||
expect([back.browser.adSkip, back.browser.adHide], [false, false]);
|
||||
expect(youtubeAdScript(skip: false, hide: true), contains('{skip: false, hide: true}'));
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue