설정 A 분야 13: 화면 · 앱 모양 (DisplaySettings) - 키 13 · clampUiScale → DisplaySettings.clampScale
- ComponentManifest 의 자기 필드 components 는 그대로 (같은 이름이라 잘못 바뀐 것을 되돌림) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>modular
parent
8d26d1af1e
commit
c5aa8c657b
|
|
@ -126,7 +126,7 @@ void main() {
|
|||
expect(TorBundle.windowsExe, isNotNull);
|
||||
expect(TorBundle.installedVersion(), TorBundle.version);
|
||||
c.settings
|
||||
..components = [...c.settings.components, 'tor']
|
||||
..display.components = [...c.settings.display.components, 'tor']
|
||||
..tor.noticeSeen = true;
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
builder: (context, child) => AppScope(controller: c, bookmarks: bm, child: child!),
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ void main() {
|
|||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..webViewDataDir = dataDir
|
||||
..uiLanguage = 'ko'
|
||||
..display.language = 'ko'
|
||||
..browser.translate = true;
|
||||
final bm = BookmarksController(p.join(dataDir, 'bm.json'));
|
||||
await tester.pumpWidget(MaterialApp(home: BrowserPage(c: c, bookmarks: bm, initialUrl: url)));
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ void main() {
|
|||
AppController plain() {
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..components = ['mkv', 'explorer', 'rsync']
|
||||
..navOrder = ['mkv', 'explorer', 'rsync'];
|
||||
..display.components = ['mkv', 'explorer', 'rsync']
|
||||
..display.navOrder = ['mkv', 'explorer', 'rsync'];
|
||||
return c;
|
||||
}
|
||||
|
||||
|
|
@ -90,8 +90,8 @@ void main() {
|
|||
final c = plain();
|
||||
// 실제 앱처럼 웹 브라우저도 화면 목록에 (그래야 위쪽 막대에 브라우저 아이콘이 있다)
|
||||
c.settings
|
||||
..components = ['mkv', 'browser', 'explorer', 'rsync']
|
||||
..navOrder = ['mkv', 'browser', 'explorer', 'rsync'];
|
||||
..display.components = ['mkv', 'browser', 'explorer', 'rsync']
|
||||
..display.navOrder = ['mkv', 'browser', 'explorer', 'rsync'];
|
||||
final base = Platform.environment['JJ_SHOT_TMP'];
|
||||
final dir = testTempDir('jj_shot_662_', base: base);
|
||||
final bm = BookmarksController(p.join(dir.path, 'bookmarks.json'));
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ void main() {
|
|||
t.view.physicalSize = const Size(1280, 800);
|
||||
t.view.devicePixelRatio = 1;
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') c.settings.uiBrightness = 'light';
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') c.settings.display.brightness = 'light';
|
||||
await t.runAsync(() => i18n.apply(lang, save: false));
|
||||
await t.runAsync(() => t.pumpWidget(RepaintBoundary(key: shotKey, child: JjMkvMakerApp(controller: c, navigatorKey: navKey))));
|
||||
await settle(t);
|
||||
|
|
@ -92,7 +92,7 @@ void main() {
|
|||
t.view.devicePixelRatio = 1;
|
||||
addTearDown(t.view.reset);
|
||||
final c = AppController(PlatformServices.create());
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') c.settings.uiBrightness = 'light';
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') c.settings.display.brightness = 'light';
|
||||
for (var i = 0; i < checked; i++) {
|
||||
final v = VideoItem(p.join(Directory.systemTemp.path, 'jj_i18n_none', 'v$i.mp4'));
|
||||
c.videos.add(v);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ void main() {
|
|||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..tor.noticeSeen = true
|
||||
..components = [...c.settings.components, 'tor'];
|
||||
..display.components = [...c.settings.display.components, 'tor'];
|
||||
final key = GlobalKey();
|
||||
|
||||
Future<void> shot(String name, Size size, Widget home, {Future<void> Function()? then}) async {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ void main() {
|
|||
createRecognizer: () => throw UnimplementedError(),
|
||||
createTranslator: () => throw UnimplementedError(),
|
||||
));
|
||||
c.settings.showLog = true;
|
||||
c.settings.display.showLog = true;
|
||||
c.settings.moveTargets = [MoveTarget('보관', p.join(tmp.path, '보관'))];
|
||||
for (var i = 1; i <= 4; i++) {
|
||||
c.videos.add(VideoItem(p.join(tmp.path, '아주 긴 이름의 동영상 파일 $i.mp4')));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 앱의 기능 묶음 (컴포넌트). 코드는 앱에 들어 있고, 환경 설정 > 컴포넌트에서 설치 (필요한 파일을 받아 켬) · 제거 (끔) 한다.
|
||||
/// 화면이 있는 것 ([page]) 은 위쪽 이동 버튼 · 좌우로 밀기로 오간다 (순서는 [AppSettings.navOrder]).
|
||||
/// 화면이 있는 것 ([page]) 은 위쪽 이동 버튼 · 좌우로 밀기로 오간다 (순서는 [AppSettings.display.navOrder]).
|
||||
class AppComponent {
|
||||
final String id;
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class I18nController extends ChangeNotifier {
|
|||
}
|
||||
|
||||
/// 고를 수 있는 언어: 기본 + 더한 것
|
||||
List<String> get available => [...builtIn, ...?_app?.settings.uiLanguagesAdded];
|
||||
List<String> get available => [...builtIn, ...?_app?.settings.display.languagesAdded];
|
||||
|
||||
/// 더할 수 있는 언어 (AI 번역 모델이 아는 언어 중 아직 없는 것)
|
||||
List<Language> get addable => [
|
||||
|
|
@ -119,7 +119,7 @@ class I18nController extends ChangeNotifier {
|
|||
}
|
||||
|
||||
/// 설정에서 고른 값 ('system' 이면 시스템 언어 따르기)
|
||||
String get chosen => _app?.settings.uiLanguage ?? uiLanguage;
|
||||
String get chosen => _app?.settings.display.language ?? uiLanguage;
|
||||
|
||||
/// 화면 언어 바꾸기 (설정에 저장하고 화면 전체를 다시 그린다). [system] 이면 기기 언어를 따른다
|
||||
Future<void> apply(String code, {bool save = true}) async {
|
||||
|
|
@ -129,8 +129,8 @@ class I18nController extends ChangeNotifier {
|
|||
final dict = code == 'ko' ? const <String, String>{} : await _read(code);
|
||||
final fallback = code == 'ko' || code == 'en' ? const <String, String>{} : await _read('en');
|
||||
setTranslations(code, dict, fallback);
|
||||
if (save && _app != null && _c.settings.uiLanguage != want) {
|
||||
await _c.updateSettings((x) => x.uiLanguage = want);
|
||||
if (save && _app != null && _c.settings.display.language != want) {
|
||||
await _c.updateSettings((x) => x.display.language = want);
|
||||
}
|
||||
rebuildAll();
|
||||
notifyListeners();
|
||||
|
|
@ -179,7 +179,7 @@ class I18nController extends ChangeNotifier {
|
|||
await Directory(_dir).create(recursive: true);
|
||||
await File(p.join(_dir, '${l.code}.json'))
|
||||
.writeAsString(const JsonEncoder.withIndent(' ').convert(dict));
|
||||
await _c.updateSettings((x) => x.uiLanguagesAdded = [...x.uiLanguagesAdded, l.code]);
|
||||
await _c.updateSettings((x) => x.display.languagesAdded = [...x.display.languagesAdded, l.code]);
|
||||
} finally {
|
||||
await translator.dispose();
|
||||
}
|
||||
|
|
@ -192,7 +192,7 @@ class I18nController extends ChangeNotifier {
|
|||
try {
|
||||
await File(p.join(_dir, '$code.json')).delete();
|
||||
} catch (_) {}
|
||||
await _c.updateSettings((x) => x.uiLanguagesAdded = [...x.uiLanguagesAdded]..remove(code));
|
||||
await _c.updateSettings((x) => x.display.languagesAdded = [...x.display.languagesAdded]..remove(code));
|
||||
if (uiLanguage == code) {
|
||||
await apply('ko');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@ import 'dart:io';
|
|||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'components.dart';
|
||||
import '../core/reader_sources.dart' show defaultImageExtensions;
|
||||
import '../core/download_detect.dart';
|
||||
import '../core/encode_options.dart';
|
||||
import '../core/playlist.dart';
|
||||
import '../services/app_shell.dart' show appIconOf;
|
||||
import 'settings/records.dart';
|
||||
import 'settings/settings_group.dart';
|
||||
import 'settings/display_settings.dart';
|
||||
import 'settings/remote_settings.dart';
|
||||
import 'settings/explorer_tool_settings.dart';
|
||||
import 'settings/vault_settings.dart';
|
||||
|
|
@ -26,6 +25,7 @@ import 'settings/explorer_settings.dart';
|
|||
export 'settings/records.dart';
|
||||
export 'settings/settings_group.dart';
|
||||
export 'settings/settings_store.dart';
|
||||
export 'settings/display_settings.dart';
|
||||
export 'settings/remote_settings.dart';
|
||||
export 'settings/explorer_tool_settings.dart';
|
||||
export 'settings/vault_settings.dart';
|
||||
|
|
@ -78,44 +78,19 @@ class AppSettings {
|
|||
/// 동시에 만드는 MKV 수 (0 = 무제한)
|
||||
int maxParallelJobs = 5;
|
||||
|
||||
/// 메인 화면 아래 작업 기록 창 보이기
|
||||
bool showLog = true;
|
||||
|
||||
/// 폰 세로 (좁은 화면) MKV 목록 크기: 'auto' 목록은 내용만큼 · 자세히 칸에 최소 높이 (처음 값) · 'small' · 'large' (관리자 판단 10/17)
|
||||
String mkvCompactList = 'auto';
|
||||
|
||||
/// 처음 화면: 'home' = 지금처럼 MKV 화면 (기본), 'browser' = 웹 브라우저 ([browser.homeUrl]), 'files' = 파일 탐색기
|
||||
String startScreen = 'home';
|
||||
|
||||
/// 화면 (글자 · 버튼) 크기 배율. [uiScale] 은 지금 크기 (위쪽 막대의 − · + 로 바꿈),
|
||||
/// [uiScaleDefault] 는 기본 크기 (환경 설정에서 정함, 가운데 숫자를 누르면 이 크기로 돌아감)
|
||||
double uiScale = 1.0;
|
||||
double uiScaleDefault = 1.0;
|
||||
// 18: 50 ~ 250% (사용자 결정)
|
||||
static const double uiScaleMin = 0.5, uiScaleMax = 2.5;
|
||||
|
||||
/// X-plore 비교 5-1 ⑦ 화면 밝기 (관리자 지시 10/21): 'dark' 어둡게 (처음 값 - 예전 그대로) · 'light' 밝게 · 'system' 기기 따라.
|
||||
/// 다 끝날 때까지 설정에 "시험" 표시. 모르는 값은 어둡게
|
||||
String uiBrightness = 'dark';
|
||||
static const uiBrightnessModes = ['dark', 'light', 'system'];
|
||||
static double clampUiScale(num? v) =>
|
||||
((v ?? 1.0).toDouble().clamp(uiScaleMin, uiScaleMax) * 20).round() / 20;
|
||||
|
||||
/// 탐색기에서 동영상을 열었을 때 (더블클릭 · 연결 프로그램): 'play' 바로 재생 / 'add' 편집 목록에 추가
|
||||
String openFileAction = 'play';
|
||||
|
||||
/// Android 화면 방향: 'auto' 기기 방향 따라 (기본) / 'landscape' 가로 고정 / 'portrait' 세로 고정.
|
||||
/// 값이 없거나 틀리면 자동 (제한 없는 쪽). 고정은 사용자가 환경 설정에서 고를 때만.
|
||||
String screenOrientation = 'auto';
|
||||
|
||||
/// 화면 언어 (ko · en · ja · zh-Hans, 또는 더한 언어)
|
||||
String uiLanguage = 'system';
|
||||
|
||||
/// 앱 아이콘 (appIconIds 중 하나: 'yellow' 기본 · 'black' · 'film_jj' · 'film' · 'blue')
|
||||
String appIcon = 'yellow';
|
||||
|
||||
/// 더한 화면 언어 (AI 로 자동 번역한 사전이 설정 폴더 l10n 에 있음)
|
||||
List<String> uiLanguagesAdded = [];
|
||||
|
||||
/// 프로그램이 켜져 있을 때 탐색기에서 연 동영상 재생: 'same' 켜져 있는 창에서 / 'new' 새 재생 창
|
||||
String openFileWindow = 'same';
|
||||
|
|
@ -186,14 +161,8 @@ class AppSettings {
|
|||
|
||||
|
||||
|
||||
/// 설치한 (켠) 컴포넌트 id (app/components.dart). 처음엔 기본으로 들어 있는 것 모두
|
||||
List<String> components = AppComponent.defaultInstalled;
|
||||
|
||||
/// 화면 순서 (위쪽 이동 버튼 · 화면 가운데를 좌우로 밀어 이동): 마지막 다음은 처음으로
|
||||
List<String> navOrder = AppComponent.defaultOrder;
|
||||
|
||||
/// 화면 가운데를 좌우로 밀어 다음 · 이전 화면으로
|
||||
bool swipeNav = true;
|
||||
|
||||
/// 그림 보기 (만화 보기) 대상 확장자 (파일 탐색기에서 두 번 누르면 보기로 연다)
|
||||
List<String> imageExts = [...defaultImageExtensions];
|
||||
|
|
@ -210,8 +179,6 @@ class AppSettings {
|
|||
|
||||
// ── 102: 사용자에게 물어 정한 것 · 관리자 1차 판단 (기본값 = 그 답) ──
|
||||
|
||||
/// 66: 좌우로 밀 때 끝에서 처음으로 (끄면 끝에서 멈춤)
|
||||
bool swipeWrap = true;
|
||||
|
||||
|
||||
/// 저장 공간 정리 창에서 미리 체크할 묶음 (작업 임시 파일 · 받다 만 다운로드)
|
||||
|
|
@ -336,8 +303,11 @@ class AppSettings {
|
|||
/// 원격 서버 (WebDAV · SFTP · SMB · FTP)
|
||||
final remote = RemoteSettings();
|
||||
|
||||
/// 화면 · 앱 모양 (배율 · 밝기 · 방향 · 언어 · 앱 아이콘 · 컴포넌트 · 화면 순서 · 밀기)
|
||||
final display = DisplaySettings();
|
||||
|
||||
/// 설정 분야들 (설정 A) - 저마다 자기 키를 읽고 쓴다. toJson · fromJson 은 이 목록을 돌며 합치기만 한다
|
||||
List<SettingsGroup> get groups => [explorer, network, tor, copy, aiImage, reader, download, browser, run, vault, tools, remote];
|
||||
List<SettingsGroup> get groups => [explorer, network, tor, copy, aiImage, reader, download, browser, run, vault, tools, remote, display];
|
||||
|
||||
/// [plainSecrets]: 안전 저장소를 쓸 수 없을 때만 (예전 파일에 있던 비밀 값을 잃지 않게) 예전처럼 파일에 둔다
|
||||
Map<String, Object?> toJson({bool plainSecrets = false}) {
|
||||
|
|
@ -361,28 +331,12 @@ class AppSettings {
|
|||
},
|
||||
'cookieScopeV2': true,
|
||||
'maxParallelJobs': maxParallelJobs,
|
||||
'showLog': showLog,
|
||||
'mkvCompactList': mkvCompactList,
|
||||
'startScreen': startScreen,
|
||||
'uiScale': uiScale,
|
||||
'uiScaleDefault': uiScaleDefault,
|
||||
'uiBrightness': uiBrightness,
|
||||
'openFileAction': openFileAction,
|
||||
'screenOrientation': screenOrientation,
|
||||
// 기본이 가로 고정이던 때의 값은 한 번 자동으로 옮긴다 (사용자가 정한 적 없는 가로 고정)
|
||||
'orientationV2': true,
|
||||
'uiLanguage': uiLanguage,
|
||||
'appIcon': appIcon,
|
||||
'uiLanguagesAdded': uiLanguagesAdded,
|
||||
'openFileWindow': openFileWindow,
|
||||
'mkvExisting': mkvExisting,
|
||||
'components': components,
|
||||
'aiImageV1': true,
|
||||
'robocopyLinksV1': true,
|
||||
'navOrder': navOrder,
|
||||
'swipeNav': swipeNav,
|
||||
'imageExts': imageExts,
|
||||
'swipeWrap': swipeWrap,
|
||||
'cleanupPrechecked': cleanupPrechecked,
|
||||
'migrationNotice': migrationNotice,
|
||||
'allowInnerToOuter': allowInnerToOuter,
|
||||
|
|
@ -439,39 +393,13 @@ class AppSettings {
|
|||
..openSubtitlesUser = j['openSubtitlesUser'] as String? ?? ''
|
||||
..openSubtitlesPassword = j['openSubtitlesPassword'] as String? ?? ''
|
||||
..maxParallelJobs = (j['maxParallelJobs'] as num?)?.toInt() ?? 5
|
||||
..showLog = j['showLog'] as bool? ?? true
|
||||
..mkvCompactList = const ['auto', 'small', 'large'].contains(j['mkvCompactList']) ? j['mkvCompactList'] as String : 'auto'
|
||||
..startScreen = j['startScreen'] as String? ?? 'home'
|
||||
..uiScale = clampUiScale(j['uiScale'] as num?)
|
||||
..uiScaleDefault = clampUiScale(j['uiScaleDefault'] as num?)
|
||||
..uiBrightness = uiBrightnessModes.contains(j['uiBrightness']) ? j['uiBrightness'] as String : 'dark'
|
||||
..openFileAction = j['openFileAction'] == 'add' ? 'add' : 'play'
|
||||
..uiLanguage = j['uiLanguage'] as String? ?? 'ko'
|
||||
..appIcon = appIconOf(j['appIcon'] as String?)
|
||||
..uiLanguagesAdded = [for (final x in (j['uiLanguagesAdded'] as List?) ?? const []) '$x']
|
||||
..screenOrientation = j['orientationV2'] == true && const ['landscape', 'portrait', 'auto'].contains(j['screenOrientation'])
|
||||
? j['screenOrientation'] as String
|
||||
: 'auto'
|
||||
..openFileWindow = j['openFileWindow'] == 'new' ? 'new' : 'same'
|
||||
..mkvExisting = const ['rename', 'overwrite', 'skip'].contains(j['mkvExisting']) ? j['mkvExisting'] as String : 'ask'
|
||||
..components = j['components'] is List
|
||||
? [
|
||||
// 225: 이 판이 모르는 구성 요소 (새 판의 Tor 등) 도 지우지 않고 그대로 둔다 - 예전 판을 거쳐 다시 올려도 켠 것이 남게 (40 과 같은 원칙).
|
||||
// 화면 · 버튼은 아는 것만 쓴다 (AppComponent.pages · contains)
|
||||
for (final x in j['components'] as List) if (x is String && x.isNotEmpty) x,
|
||||
// 123: 새 화면 "AI 그림" 은 예전 설정에도 한 번 켜서 넣는다 (끄기: 환경 설정 > 컴포넌트)
|
||||
if (j['aiImageV1'] != true && !(j['components'] as List).contains('aiimage')) 'aiimage',
|
||||
]
|
||||
: AppComponent.defaultInstalled
|
||||
..navOrder = j['navOrder'] is List
|
||||
// 225: 모르는 화면 id 도 순서에 그대로 (그 판으로 돌아가면 사용자가 정한 순서대로)
|
||||
? [for (final x in j['navOrder'] as List) if (x is String && x.isNotEmpty) x]
|
||||
: AppComponent.defaultOrder
|
||||
..swipeNav = j['swipeNav'] as bool? ?? true
|
||||
..imageExts = j['imageExts'] is List
|
||||
? [for (final x in j['imageExts'] as List) '$x'.toLowerCase()]
|
||||
: [...defaultImageExtensions]
|
||||
..swipeWrap = j['swipeWrap'] != false
|
||||
..cleanupPrechecked = j['cleanupPrechecked'] is List
|
||||
? [for (final x in j['cleanupPrechecked'] as List) '$x']
|
||||
: ['work', 'download']
|
||||
|
|
@ -559,7 +487,7 @@ class AppSettings {
|
|||
}
|
||||
|
||||
static List<String> _migrated(Map<String, dynamic> j, AppSettings s) => [
|
||||
if (j['orientationV2'] != true && j['screenOrientation'] == 'landscape' && s.screenOrientation == 'auto')
|
||||
if (j['orientationV2'] != true && j['screenOrientation'] == 'landscape' && s.display.orientation == 'auto')
|
||||
'orientation',
|
||||
if (j['explorerV2'] != true && j['explorerLayout'] == 'dual' && s.explorer.layout == 'auto') 'explorerLayout',
|
||||
if (j['explorerV2'] != true && j['explorerClick'] == 'select' && s.explorer.click == 'open') 'explorerClick',
|
||||
|
|
@ -580,8 +508,8 @@ class AppSettings {
|
|||
if (j.isNotEmpty && j['explorerRulesV1'] != true && Platform.isAndroid && j['explorerClick'] != 'select')
|
||||
'explorerClickRule',
|
||||
if (j.isNotEmpty && j['explorerRulesV1'] != true && (j['explorerLayout'] ?? 'auto') == 'auto') 'explorerLayoutRule',
|
||||
if (j.isNotEmpty && !j.containsKey('screenOrientation') && s.screenOrientation == 'auto' && Platform.isAndroid) 'orientation',
|
||||
if (j.isNotEmpty && !j.containsKey('swipeNav') && s.swipeNav) 'swipeNav',
|
||||
if (j.isNotEmpty && !j.containsKey('screenOrientation') && s.display.orientation == 'auto' && Platform.isAndroid) 'orientation',
|
||||
if (j.isNotEmpty && !j.containsKey('swipeNav') && s.display.swipeNav) 'swipeNav',
|
||||
if (j.isNotEmpty && !j.containsKey('runInBackground') && s.run.inBackground && Platform.isAndroid) 'background',
|
||||
// DNS 강제 (사용자 요청 10/10, 처음 값 직접 지정 8.8.8.8 · 8.8.4.4) 가 없던 판에서 올라오면 이름을 묻는 서버가 바뀐다 - 193 과 같이 한 번 알림
|
||||
if (j.isNotEmpty && !j.containsKey('dnsMode') && s.network.mode == 'custom') 'dnsForced',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
|
||||
import '../components.dart';
|
||||
import '../../services/app_shell.dart';
|
||||
import 'settings_group.dart';
|
||||
|
||||
/// 약속 (설정 A): 환경 설정 > 화면 · 앱 모양 (배율 · 밝기 · 방향 · 언어 · 앱 아이콘 · 컴포넌트 · 화면 순서 · 밀기) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다.
|
||||
/// 설정 하나를 더할 때는 이 파일만 고친다. 저장 파일의 키 이름은 예전 그대로 (평평한 키)
|
||||
class DisplaySettings implements SettingsGroup {
|
||||
/// 메인 화면 아래 작업 기록 창 보이기
|
||||
bool showLog = true;
|
||||
|
||||
/// 처음 화면: 'home' = 지금처럼 MKV 화면 (기본), 'browser' = 웹 브라우저 ([browser.homeUrl]), 'files' = 파일 탐색기
|
||||
String startScreen = 'home';
|
||||
|
||||
/// 화면 (글자 · 버튼) 크기 배율. [scale] 은 지금 크기 (위쪽 막대의 − · + 로 바꿈),
|
||||
/// [scaleDefault] 는 기본 크기 (환경 설정에서 정함, 가운데 숫자를 누르면 이 크기로 돌아감)
|
||||
double scale = 1.0;
|
||||
|
||||
double scaleDefault = 1.0;
|
||||
|
||||
/// X-plore 비교 5-1 ⑦ 화면 밝기 (관리자 지시 10/21): 'dark' 어둡게 (처음 값 - 예전 그대로) · 'light' 밝게 · 'system' 기기 따라.
|
||||
/// 다 끝날 때까지 설정에 "시험" 표시. 모르는 값은 어둡게
|
||||
String brightness = 'dark';
|
||||
static const brightnessModes = ['dark', 'light', 'system'];
|
||||
|
||||
/// Android 화면 방향: 'auto' 기기 방향 따라 (기본) / 'landscape' 가로 고정 / 'portrait' 세로 고정.
|
||||
/// 값이 없거나 틀리면 자동 (제한 없는 쪽). 고정은 사용자가 환경 설정에서 고를 때만.
|
||||
String orientation = 'auto';
|
||||
|
||||
/// 화면 언어 (ko · en · ja · zh-Hans, 또는 더한 언어)
|
||||
String language = 'system';
|
||||
|
||||
/// 앱 아이콘 (appIconIds 중 하나: 'yellow' 기본 · 'black' · 'film_jj' · 'film' · 'blue')
|
||||
String appIcon = 'yellow';
|
||||
|
||||
/// 더한 화면 언어 (AI 로 자동 번역한 사전이 설정 폴더 l10n 에 있음)
|
||||
List<String> languagesAdded = [];
|
||||
|
||||
/// 설치한 (켠) 컴포넌트 id (app/components.dart). 처음엔 기본으로 들어 있는 것 모두
|
||||
List<String> components = AppComponent.defaultInstalled;
|
||||
|
||||
/// 화면 순서 (위쪽 이동 버튼 · 화면 가운데를 좌우로 밀어 이동): 마지막 다음은 처음으로
|
||||
List<String> navOrder = AppComponent.defaultOrder;
|
||||
|
||||
/// 화면 가운데를 좌우로 밀어 다음 · 이전 화면으로
|
||||
bool swipeNav = true;
|
||||
|
||||
/// 66: 좌우로 밀 때 끝에서 처음으로 (끄면 끝에서 멈춤)
|
||||
bool swipeWrap = true;
|
||||
|
||||
// 18: 50 ~ 250% (사용자 결정)
|
||||
static const double scaleMin = 0.5, scaleMax = 2.5;
|
||||
|
||||
static double clampScale(num? v) =>
|
||||
((v ?? 1.0).toDouble().clamp(scaleMin, scaleMax) * 20).round() / 20;
|
||||
|
||||
@override
|
||||
void write(Map<String, Object?> j, {required bool plainSecrets}) {
|
||||
j['showLog'] = showLog;
|
||||
j['startScreen'] = startScreen;
|
||||
j['uiScale'] = scale;
|
||||
j['uiScaleDefault'] = scaleDefault;
|
||||
j['uiBrightness'] = brightness;
|
||||
j['screenOrientation'] = orientation;
|
||||
j['uiLanguage'] = language;
|
||||
j['appIcon'] = appIcon;
|
||||
j['uiLanguagesAdded'] = languagesAdded;
|
||||
j['components'] = components;
|
||||
j['navOrder'] = navOrder;
|
||||
j['swipeNav'] = swipeNav;
|
||||
j['swipeWrap'] = swipeWrap;
|
||||
// 기본이 가로 고정이던 때의 값은 한 번 자동으로 옮긴다 (사용자가 정한 적 없는 가로 고정)
|
||||
j['orientationV2'] = true;
|
||||
j['aiImageV1'] = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void read(Map<String, dynamic> j) {
|
||||
showLog = j['showLog'] as bool? ?? true;
|
||||
startScreen = j['startScreen'] as String? ?? 'home';
|
||||
scale = clampScale(j['uiScale'] as num?);
|
||||
scaleDefault = clampScale(j['uiScaleDefault'] as num?);
|
||||
brightness = brightnessModes.contains(j['uiBrightness']) ? j['uiBrightness'] as String : 'dark';
|
||||
language = j['uiLanguage'] as String? ?? 'ko';
|
||||
appIcon = appIconOf(j['appIcon'] as String?);
|
||||
languagesAdded = [for (final x in (j['uiLanguagesAdded'] as List?) ?? const []) '$x'];
|
||||
orientation = j['orientationV2'] == true && const ['landscape', 'portrait', 'auto'].contains(j['screenOrientation'])
|
||||
? j['screenOrientation'] as String
|
||||
: 'auto';
|
||||
components = j['components'] is List
|
||||
? [
|
||||
// 225: 이 판이 모르는 구성 요소 (새 판의 Tor 등) 도 지우지 않고 그대로 둔다 - 예전 판을 거쳐 다시 올려도 켠 것이 남게 (40 과 같은 원칙).
|
||||
// 화면 · 버튼은 아는 것만 쓴다 (AppComponent.pages · contains)
|
||||
for (final x in j['components'] as List) if (x is String && x.isNotEmpty) x,
|
||||
// 123: 새 화면 "AI 그림" 은 예전 설정에도 한 번 켜서 넣는다 (끄기: 환경 설정 > 컴포넌트)
|
||||
if (j['aiImageV1'] != true && !(j['components'] as List).contains('aiimage')) 'aiimage',
|
||||
]
|
||||
: AppComponent.defaultInstalled;
|
||||
navOrder = j['navOrder'] is List
|
||||
// 225: 모르는 화면 id 도 순서에 그대로 (그 판으로 돌아가면 사용자가 정한 순서대로)
|
||||
? [for (final x in j['navOrder'] as List) if (x is String && x.isNotEmpty) x]
|
||||
: AppComponent.defaultOrder;
|
||||
swipeNav = j['swipeNav'] as bool? ?? true;
|
||||
swipeWrap = j['swipeWrap'] != false;
|
||||
}
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ Future<void> main(List<String> args) async {
|
|||
final claimed = await instance.claim(request, waitForExit: args.contains('--restart'), forward: !newWindow);
|
||||
if (claimed == ClaimResult.unresponsive) {
|
||||
// 같은 데이터 위에 창이 두 개 뜨지 않게 묻는다 (처음 고른 것은 [닫기] - 관리자 결정 10/12)
|
||||
await i18n.apply(startSettings.uiLanguage, save: false);
|
||||
await i18n.apply(startSettings.display.language, save: false);
|
||||
if (askOpenAnyway(appTitle)) {
|
||||
await instance.takeOverAnyway(request);
|
||||
} else {
|
||||
|
|
@ -151,7 +151,7 @@ Future<void> main(List<String> args) async {
|
|||
final live = LiveSync(controller)..start(hold: controller.settings.copy.liveSyncOnStart != 'auto');
|
||||
// 화면 언어 (환경 설정 > 화면 언어)
|
||||
i18n.init(controller, dataDir);
|
||||
await i18n.apply(controller.settings.uiLanguage, save: false);
|
||||
await i18n.apply(controller.settings.display.language, save: false);
|
||||
|
||||
final bookmarks = BookmarksController();
|
||||
await bookmarks.load();
|
||||
|
|
@ -259,7 +259,7 @@ Future<void> main(List<String> args) async {
|
|||
if (nav != null) unawaited(DownloadsPage.open(nav, downloads));
|
||||
},
|
||||
);
|
||||
await services.shell.setAppIcon(controller.settings.appIcon);
|
||||
await services.shell.setAppIcon(controller.settings.display.appIcon);
|
||||
// 99: 시작 메뉴 바로 가기 (앱 ID) · jjmkvmaker:// 연결 - 알림이 JJ_MKVMaker 로 보이고 눌러서 열린다 (설정에서 끔)
|
||||
StartMenu.setProcessAppId();
|
||||
final shell0 = services.shell;
|
||||
|
|
@ -598,9 +598,9 @@ Future<void> runAndroid(String dataDir) async {
|
|||
final live = LiveSync(controller)..start(hold: controller.settings.copy.liveSyncOnStart != 'auto');
|
||||
// 화면 언어 (환경 설정 > 화면 언어)
|
||||
i18n.init(controller, dataDir);
|
||||
await i18n.apply(controller.settings.uiLanguage, save: false);
|
||||
await applyScreenOrientation(controller.settings.screenOrientation);
|
||||
unawaited(services.shell.setAppIcon(controller.settings.appIcon));
|
||||
await i18n.apply(controller.settings.display.language, save: false);
|
||||
await applyScreenOrientation(controller.settings.display.orientation);
|
||||
unawaited(services.shell.setAppIcon(controller.settings.display.appIcon));
|
||||
final bookmarks = BookmarksController();
|
||||
await bookmarks.load();
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
|
@ -649,7 +649,7 @@ Future<void> runAndroid(String dataDir) async {
|
|||
var hadAccess = await AndroidAccess.hasAllFiles();
|
||||
if (hadAccess) await allFilesGuideDue(controller, hasAccess: true);
|
||||
AppLifecycleListener(onResume: () async {
|
||||
await applyScreenOrientation(controller.settings.screenOrientation);
|
||||
await applyScreenOrientation(controller.settings.display.orientation);
|
||||
final now = await AndroidAccess.hasAllFiles();
|
||||
if (now && !hadAccess) await controller.reanalyzeFailed();
|
||||
if (now) await allFilesGuideDue(controller, hasAccess: true);
|
||||
|
|
@ -772,7 +772,7 @@ Future<void> runSecondWindow(List<String> files, String dataDir) async {
|
|||
await setupNetwork(controller, dataDir, second: true);
|
||||
// 화면 언어 (환경 설정 > 화면 언어)
|
||||
i18n.init(controller, dataDir);
|
||||
await i18n.apply(controller.settings.uiLanguage, save: false);
|
||||
await i18n.apply(controller.settings.display.language, save: false);
|
||||
final store = SettingsStore();
|
||||
controller.settings = await store.load();
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
|
@ -893,7 +893,7 @@ class _BrightnessHostState extends State<_BrightnessHost> with WidgetsBindingObs
|
|||
}
|
||||
|
||||
Brightness _want() =>
|
||||
resolveBrightness(widget.c.settings.uiBrightness, WidgetsBinding.instance.platformDispatcher.platformBrightness);
|
||||
resolveBrightness(widget.c.settings.display.brightness, WidgetsBinding.instance.platformDispatcher.platformBrightness);
|
||||
|
||||
@override
|
||||
void didChangePlatformBrightness() => _check();
|
||||
|
|
|
|||
|
|
@ -203,8 +203,8 @@ class AppNavButtons extends StatelessWidget {
|
|||
|
||||
/// 홈 화면 (환경 설정의 "홈 화면") 의 컴포넌트 id. 그 컴포넌트를 제거했으면 설치된 첫 화면.
|
||||
static String homeId(AppSettings s) {
|
||||
final want = switch (s.startScreen) { 'browser' => 'browser', 'files' => 'explorer', 'tor' => 'tor', _ => 'mkv' };
|
||||
final pages = AppComponent.pages(s.components, s.navOrder);
|
||||
final want = switch (s.display.startScreen) { 'browser' => 'browser', 'files' => 'explorer', 'tor' => 'tor', _ => 'mkv' };
|
||||
final pages = AppComponent.pages(s.display.components, s.display.navOrder);
|
||||
if (pages.any((p) => p.id == want)) return want;
|
||||
return pages.isEmpty ? 'mkv' : pages.first.id;
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ class AppNavButtons extends StatelessWidget {
|
|||
|
||||
/// 이 창에서 열 수 있는 화면 (브라우저 · 다운로드가 없는 새 창에서는 그 둘을 뺀다)
|
||||
static List<AppComponent> pagesOf(AppScope scope) => [
|
||||
for (final p in AppComponent.pages(scope.controller.settings.components, scope.controller.settings.navOrder))
|
||||
for (final p in AppComponent.pages(scope.controller.settings.display.components, scope.controller.settings.display.navOrder))
|
||||
if ((p.id != 'browser' || scope.bookmarks != null) && (p.id != 'downloads' || scope.downloads != null)) p,
|
||||
];
|
||||
|
||||
|
|
@ -263,7 +263,7 @@ class AppNavButtons extends StatelessWidget {
|
|||
final j = i + step;
|
||||
// 102 · 66: 끝에서 처음으로 돌기를 끄면 끝에서 멈춘다
|
||||
final wraps = j < 0 || j >= pages.length;
|
||||
if (!scope.controller.settings.swipeWrap && wraps) {
|
||||
if (!scope.controller.settings.display.swipeWrap && wraps) {
|
||||
_hint(context, step > 0 ? tr('마지막 화면입니다') : tr('첫 화면입니다'));
|
||||
return;
|
||||
}
|
||||
|
|
@ -409,7 +409,7 @@ class AppNavButtons extends StatelessWidget {
|
|||
};
|
||||
return Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
btn(
|
||||
Image.asset(appIconButtonAsset(scope?.controller.settings.appIcon),
|
||||
Image.asset(appIconButtonAsset(scope?.controller.settings.display.appIcon),
|
||||
width: labels ? 22 : 26, height: labels ? 22 : 26, filterQuality: FilterQuality.medium),
|
||||
trf('홈 화면 ({0}) · 환경 설정에서 바꿈', [homeName]),
|
||||
scope == null ? null : () => toHome(context),
|
||||
|
|
@ -463,7 +463,7 @@ class _SwipeNavState extends State<SwipeNav> {
|
|||
onHorizontalDragEnd: (d) {
|
||||
final v = d.primaryVelocity ?? 0;
|
||||
// 켜고 끄기는 밀 때 확인 (이 위젯은 설정이 바뀌어도 다시 그려지지 않을 수 있음)
|
||||
if (!scope.controller.settings.swipeNav || !_middle || v.abs() < 500) return;
|
||||
if (!scope.controller.settings.display.swipeNav || !_middle || v.abs() < 500) return;
|
||||
// 왼쪽으로 밀면 다음 화면, 오른쪽으로 밀면 이전 화면
|
||||
AppNavButtons.step(context, widget.current, v < 0 ? 1 : -1);
|
||||
},
|
||||
|
|
@ -536,13 +536,13 @@ class ScaleButtons extends StatelessWidget {
|
|||
final AppController c;
|
||||
const ScaleButtons({super.key, required this.c});
|
||||
|
||||
void _set(double v) => c.updateSettings((s) => s.uiScale = AppSettings.clampUiScale(v));
|
||||
void _set(double v) => c.updateSettings((s) => s.display.scale = DisplaySettings.clampScale(v));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ListenableBuilder(
|
||||
listenable: c,
|
||||
builder: (context, _) {
|
||||
final s = c.settings.uiScale;
|
||||
final s = c.settings.display.scale;
|
||||
Widget btn(IconData icon, String tip, VoidCallback? f) => SizedBox(
|
||||
width: 26,
|
||||
height: 32,
|
||||
|
|
@ -557,12 +557,12 @@ class ScaleButtons extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
btn(Icons.remove, tr('화면 작게'), s <= AppSettings.uiScaleMin + 0.001 ? null : () => _set(s - 0.1)),
|
||||
btn(Icons.remove, tr('화면 작게'), s <= DisplaySettings.scaleMin + 0.001 ? null : () => _set(s - 0.1)),
|
||||
Tooltip(
|
||||
message: trf('기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서', [(c.settings.uiScaleDefault * 100).round()]),
|
||||
message: trf('기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서', [(c.settings.display.scaleDefault * 100).round()]),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
onTap: () => _set(c.settings.uiScaleDefault),
|
||||
onTap: () => _set(c.settings.display.scaleDefault),
|
||||
child: SizedBox(
|
||||
width: 40,
|
||||
height: 28,
|
||||
|
|
@ -573,14 +573,14 @@ class ScaleButtons extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
btn(Icons.add, tr('화면 크게'), s >= AppSettings.uiScaleMax - 0.001 ? null : () => _set(s + 0.1)),
|
||||
btn(Icons.add, tr('화면 크게'), s >= DisplaySettings.scaleMax - 0.001 ? null : () => _set(s + 0.1)),
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 화면 전체를 [AppSettings.uiScale] 배로 그린다 (안쪽은 그만큼 작은 · 큰 화면이라고 여기고 배치).
|
||||
/// 화면 전체를 [AppSettings.display.scale] 배로 그린다 (안쪽은 그만큼 작은 · 큰 화면이라고 여기고 배치).
|
||||
/// 앱의 builder 에서 한 번 감싼다 - 대화 상자 · 메뉴 · 안내 글까지 모두 같은 배율.
|
||||
class UiScaler extends StatelessWidget {
|
||||
final AppController c;
|
||||
|
|
@ -591,7 +591,7 @@ class UiScaler extends StatelessWidget {
|
|||
Widget build(BuildContext context) => ListenableBuilder(
|
||||
listenable: c,
|
||||
builder: (context, _) {
|
||||
final s = c.settings.uiScale;
|
||||
final s = c.settings.display.scale;
|
||||
if ((s - 1).abs() < 0.001) return child;
|
||||
return LayoutBuilder(builder: (context, box) {
|
||||
final size = box.biggest / s;
|
||||
|
|
@ -655,11 +655,11 @@ class AppActions extends StatelessWidget {
|
|||
final s = controller?.settings;
|
||||
switch (v) {
|
||||
case 'bigger' when s != null:
|
||||
controller!.updateSettings((x) => x.uiScale = AppSettings.clampUiScale(s.uiScale + 0.1));
|
||||
controller!.updateSettings((x) => x.display.scale = DisplaySettings.clampScale(s.display.scale + 0.1));
|
||||
case 'smaller' when s != null:
|
||||
controller!.updateSettings((x) => x.uiScale = AppSettings.clampUiScale(s.uiScale - 0.1));
|
||||
controller!.updateSettings((x) => x.display.scale = DisplaySettings.clampScale(s.display.scale - 0.1));
|
||||
case 'default' when s != null:
|
||||
controller!.updateSettings((x) => x.uiScale = s.uiScaleDefault);
|
||||
controller!.updateSettings((x) => x.display.scale = s.display.scaleDefault);
|
||||
case 'exit':
|
||||
exit?.call();
|
||||
}
|
||||
|
|
@ -676,8 +676,8 @@ class AppActions extends StatelessWidget {
|
|||
value: 'default',
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.fit_screen_outlined),
|
||||
title: Text(trf('기본 크기 ({0}%)', [(controller.settings.uiScaleDefault * 100).round()])),
|
||||
subtitle: Text(trf('지금 {0}%', [(controller.settings.uiScale * 100).round()])),
|
||||
title: Text(trf('기본 크기 ({0}%)', [(controller.settings.display.scaleDefault * 100).round()])),
|
||||
subtitle: Text(trf('지금 {0}%', [(controller.settings.display.scale * 100).round()])),
|
||||
dense: true)),
|
||||
],
|
||||
if (exit != null) ...[
|
||||
|
|
|
|||
|
|
@ -381,10 +381,10 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
if (s.browser.translate != _lastTranslate) {
|
||||
_lastTranslate = s.browser.translate;
|
||||
_setTranslate(_lastTranslate, force: false);
|
||||
} else if (s.uiLanguage != _lastLang && _trOn) {
|
||||
} else if (s.display.language != _lastLang && _trOn) {
|
||||
_translateNow(); // 다른 언어로 다시 번역 (스크립트가 앞의 번역을 되돌린 뒤 번역)
|
||||
}
|
||||
_lastLang = s.uiLanguage;
|
||||
_lastLang = s.display.language;
|
||||
}
|
||||
|
||||
void _setTranslate(bool on, {required bool force}) {
|
||||
|
|
@ -450,13 +450,13 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
final s = widget.c.settings;
|
||||
_lastTranslate = s.browser.translate;
|
||||
_lastJs = s.browser.javaScript;
|
||||
_lastLang = s.uiLanguage;
|
||||
_lastLang = s.display.language;
|
||||
_applyCookieExport();
|
||||
widget.c.addListener(_onSettings);
|
||||
if (_trOn) _translateNow();
|
||||
// 66-2: 브라우저 가운데는 페이지가 좌우로 밀리므로 화면 옮기기는 위쪽 막대에서 - 처음 한 번만 알린다.
|
||||
// 알림 (SnackBar) 은 다른 알림 (다운로드 등) 을 뒤로 밀어 기다리게 하므로 위쪽 막대 아래의 한 줄로
|
||||
if (s.swipeNav && !s.browser.swipeHinted) {
|
||||
if (s.display.swipeNav && !s.browser.swipeHinted) {
|
||||
_swipeHint = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => widget.c.updateSettings((x) => x.browser.swipeHinted = true));
|
||||
}
|
||||
|
|
@ -520,7 +520,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
|
||||
/// Tor 브라우저로: 켜져 있으면 Tor 화면, 아니면 짧은 안내와 [환경 설정 > 컴포넌트]
|
||||
Future<void> _openTor() async {
|
||||
if (widget.c.settings.components.contains('tor')) {
|
||||
if (widget.c.settings.display.components.contains('tor')) {
|
||||
await TorPage.open(Navigator.of(context), c: widget.c);
|
||||
return;
|
||||
}
|
||||
|
|
@ -883,7 +883,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
Icons.translate,
|
||||
_trOn
|
||||
? tr('원문 보기 (번역 끄기)')
|
||||
: trf('이 페이지를 {0}(으)로 번역', [I18nController.nativeName(widget.c.settings.uiLanguage)]),
|
||||
: trf('이 페이지를 {0}(으)로 번역', [I18nController.nativeName(widget.c.settings.display.language)]),
|
||||
() => _setTranslate(!_trOn, force: true),
|
||||
color: _trOn ? JjColors.accent : null),
|
||||
const SizedBox(width: 4),
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class ViewerSettings extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = c.settings;
|
||||
if (!s.components.contains('viewer')) return const SizedBox();
|
||||
if (!s.display.components.contains('viewer')) return const SizedBox();
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
SettingTile(
|
||||
leading: const Icon(Icons.photo_library_outlined),
|
||||
|
|
@ -115,7 +115,7 @@ Future<void> _setDownloaded(BuildContext context, AppComponent x, bool on) async
|
|||
);
|
||||
if (ok != true) return;
|
||||
await store.remove(x.id);
|
||||
await c.updateSettings((v) => v.components = [for (final id in v.components) if (id != x.id) id]);
|
||||
await c.updateSettings((v) => v.display.components = [for (final id in v.display.components) if (id != x.id) id]);
|
||||
return;
|
||||
}
|
||||
final step = ValueNotifier<(String, double?)>((tr('목록을 받는 중'), null));
|
||||
|
|
@ -157,7 +157,7 @@ Future<void> _setDownloaded(BuildContext context, AppComponent x, bool on) async
|
|||
await dialog;
|
||||
step.dispose();
|
||||
if (done) {
|
||||
await c.updateSettings((v) => v.components = [for (final id in v.components) if (id != x.id) id, x.id]);
|
||||
await c.updateSettings((v) => v.display.components = [for (final id in v.display.components) if (id != x.id) id, x.id]);
|
||||
}
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.maybeOf(context)?.showSnackBar(SnackBar(
|
||||
|
|
@ -174,8 +174,8 @@ class ComponentSettings extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = c.settings;
|
||||
final installed = s.components;
|
||||
final pages = AppComponent.pages(installed, s.navOrder);
|
||||
final installed = s.display.components;
|
||||
final pages = AppComponent.pages(installed, s.display.navOrder);
|
||||
|
||||
Future<void> setInstalled(AppComponent x, bool on) async {
|
||||
if (x.needsDownload) return _setDownloaded(context, x, on);
|
||||
|
|
@ -186,8 +186,8 @@ class ComponentSettings extends StatelessWidget {
|
|||
?.showSnackBar(SnackBar(content: Text(tr('화면이 있는 컴포넌트는 하나 이상 남아 있어야 합니다.'))));
|
||||
return;
|
||||
}
|
||||
await c.updateSettings((v) => v.components = [
|
||||
for (final id in v.components) if (id != x.id) id,
|
||||
await c.updateSettings((v) => v.display.components = [
|
||||
for (final id in v.display.components) if (id != x.id) id,
|
||||
if (on) x.id,
|
||||
]);
|
||||
}
|
||||
|
|
@ -198,21 +198,21 @@ class ComponentSettings extends StatelessWidget {
|
|||
ids.insert(to, ids.removeAt(from));
|
||||
// 아직 켜지 않은 Tor 는 순서에 넣지 않는다 (켜면 웹 브라우저 바로 뒤에 - AppComponent.fullOrder).
|
||||
// 225: 이 판이 모르는 화면 (다른 판의 것) 은 지우지 않고 그 자리에
|
||||
c.updateSettings((v) => v.navOrder = AppComponent.reorder(v.navOrder,
|
||||
c.updateSettings((v) => v.display.navOrder = AppComponent.reorder(v.display.navOrder,
|
||||
[...ids, ...AppComponent.defaultOrder.where((x) => !ids.contains(x) && x != AppComponent.tor.id)]));
|
||||
}
|
||||
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
SwitchListTile(
|
||||
value: s.swipeNav,
|
||||
onChanged: (v) => c.updateSettings((x) => x.swipeNav = v),
|
||||
value: s.display.swipeNav,
|
||||
onChanged: (v) => c.updateSettings((x) => x.display.swipeNav = v),
|
||||
title: Text(tr('좌우로 밀어 화면 이동')),
|
||||
subtitle: Text(tr('화면 가운데를 왼쪽으로 밀면 다음 화면, 오른쪽으로 밀면 이전 화면 (마지막 다음은 처음으로).')),
|
||||
),
|
||||
// 102 · 66: 끝에서 처음으로 돌기 (기본 켜짐)
|
||||
SwitchListTile(
|
||||
value: s.swipeWrap,
|
||||
onChanged: s.swipeNav ? (v) => c.updateSettings((x) => x.swipeWrap = v) : null,
|
||||
value: s.display.swipeWrap,
|
||||
onChanged: s.display.swipeNav ? (v) => c.updateSettings((x) => x.display.swipeWrap = v) : null,
|
||||
title: Text(tr('끝에서 처음으로 돌기')),
|
||||
subtitle: Text(tr('마지막 화면에서 더 밀면 첫 화면으로 (끄면 끝에서 멈춤)')),
|
||||
),
|
||||
|
|
@ -302,7 +302,7 @@ Future<void> _setTor(BuildContext context, AppController c, bool on) async {
|
|||
return;
|
||||
}
|
||||
}
|
||||
await c.updateSettings((v) => v.components = [for (final id in v.components) if (id != TorBundle.id) id]);
|
||||
await c.updateSettings((v) => v.display.components = [for (final id in v.display.components) if (id != TorBundle.id) id]);
|
||||
return;
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
|
|
@ -353,7 +353,7 @@ Future<void> _setTor(BuildContext context, AppController c, bool on) async {
|
|||
return;
|
||||
}
|
||||
}
|
||||
await c.updateSettings((v) => v.components = [for (final id in v.components) if (id != TorBundle.id) id, TorBundle.id]);
|
||||
await c.updateSettings((v) => v.display.components = [for (final id in v.display.components) if (id != TorBundle.id) id, TorBundle.id]);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.maybeOf(context)?.showSnackBar(SnackBar(content: Text(trf('{0} 을(를) 설치했습니다.', [name]))));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class ExplorerItemMenu {
|
|||
final audio = !e.isDir && isAudioFile(e.path);
|
||||
final dav = isDav(e.path);
|
||||
// 그림 → PDF: 표시한 것 중 그림 (누른 것이 표시에 없으면 누른 것만)
|
||||
final viewer = ctl.c.settings.components.contains('viewer');
|
||||
final viewer = ctl.c.settings.display.components.contains('viewer');
|
||||
final picked = pane.sel.has(e.path) ? pane.sel.marked : [e.path];
|
||||
final images = [for (final x in picked) if (ctl.c.settings.imageExts.contains(extOf(x))) x];
|
||||
final ext = extOf(e.path);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class ExplorerOpener {
|
|||
}
|
||||
// 여는 길은 file_opener (E5c) 가 고른다
|
||||
switch (openRouteFor(path,
|
||||
components: ctl.c.settings.components,
|
||||
components: ctl.c.settings.display.components,
|
||||
imageExts: ctl.c.settings.imageExts,
|
||||
android: Platform.isAndroid,
|
||||
windows: Platform.isWindows,
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ class HomePage extends StatelessWidget {
|
|||
const Divider(height: 1),
|
||||
// 249: DNS 서버에 닿지 않는 동안 작업 기록 위에 (아래 안내를 놓쳐도 MKV 화면에서 보고 바꿀 수 있게)
|
||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: DnsFailureLine(c: c, compact: isCompact(context))),
|
||||
if (c.settings.showLog)
|
||||
if (c.settings.display.showLog)
|
||||
SizedBox(
|
||||
// 화면이 낮은 기기 (휴대폰 가로) 에서 목록 · 자세히 보기를 가리지 않도록 화면 높이에 맞춘다.
|
||||
// 256: 폰 가로 (높이 500 미만) 는 더 작게 - 목록이 한 줄만 보였다
|
||||
|
|
@ -107,13 +107,13 @@ class HomePage extends StatelessWidget {
|
|||
: (MediaQuery.sizeOf(context).height * 0.22).clamp(80.0, 140.0),
|
||||
child: _LogPanel(
|
||||
logs: c.logs,
|
||||
onClose: () => c.updateSettings((s) => s.showLog = false),
|
||||
onClose: () => c.updateSettings((s) => s.display.showLog = false),
|
||||
),
|
||||
)
|
||||
else
|
||||
_LogStrip(
|
||||
last: c.logs.isEmpty ? '' : c.logs.last,
|
||||
onOpen: () => c.updateSettings((s) => s.showLog = true),
|
||||
onOpen: () => c.updateSettings((s) => s.display.showLog = true),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ Future<void> qualityReaderPage(BuildContext context, AppController c, ReaderSour
|
|||
/// 지금 보는 그림 파일 (이 기기의 그림 파일일 때, AI 그림 컴포넌트가 켜져 있을 때). [hasPages] 볼 장이 있는지
|
||||
String? readerAiSource(AppController c, ReaderSource src, int page, {required bool hasPages}) {
|
||||
final s = src;
|
||||
if (!c.settings.components.contains('aiimage') || s is! ImageFilesSource || !hasPages) return null;
|
||||
if (!c.settings.display.components.contains('aiimage') || s is! ImageFilesSource || !hasPages) return null;
|
||||
final path = s.paths[page];
|
||||
return isDav(path) ? null : path;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ List<ReaderBarItem> readerBarItems(BuildContext context, AppController c, Reader
|
|||
final s = c.settings;
|
||||
final pdf = src is PdfSource ? src : null;
|
||||
final pictures = src is ImageFilesSource || src is ZipImagesSource;
|
||||
final aiOn = c.settings.components.contains('aiimage');
|
||||
final aiOn = c.settings.display.components.contains('aiimage');
|
||||
final playing = st.playing;
|
||||
final secs = s.reader.autoSeconds;
|
||||
ReaderBarItem one(ReaderAct a) => ReaderBarItem(48, [a]);
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class _BrowserSettingsSectionState extends State<BrowserSettingsSection> {
|
|||
subtitle: Text(trf(
|
||||
'다른 언어로 된 페이지를 화면 언어 ({0}) 로 번역합니다. 페이지의 글을 Google 번역으로 보냅니다. '
|
||||
'주소창 옆 번역 버튼으로 원문 · 번역을 바꿀 수 있습니다.',
|
||||
[I18nController.nativeName(s.uiLanguage)])),
|
||||
[I18nController.nativeName(s.display.language)])),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.browser.javaScript,
|
||||
|
|
|
|||
|
|
@ -77,15 +77,15 @@ class _GeneralSettingsSectionState extends State<GeneralSettingsSection> {
|
|||
title: Text(tr('홈 화면')),
|
||||
subtitle: Text(tr('프로그램을 켰을 때, 그리고 위쪽 왼쪽 JJ 아이콘을 눌렀을 때 보일 화면')),
|
||||
trailing: DropdownButton<String>(
|
||||
value: s.startScreen,
|
||||
value: s.display.startScreen,
|
||||
items: [
|
||||
DropdownMenuItem(value: 'home', child: Text(tr('MKV 화면 (기본)'))),
|
||||
DropdownMenuItem(value: 'browser', child: Text(tr('웹 브라우저 (홈 주소)'))),
|
||||
DropdownMenuItem(value: 'files', child: Text(tr('파일 탐색기'))),
|
||||
if (s.components.contains('tor') || s.startScreen == 'tor')
|
||||
if (s.display.components.contains('tor') || s.display.startScreen == 'tor')
|
||||
DropdownMenuItem(value: 'tor', child: Text(tr('Tor 브라우저'))),
|
||||
],
|
||||
onChanged: (v) => c.updateSettings((x) => x.startScreen = v!),
|
||||
onChanged: (v) => c.updateSettings((x) => x.display.startScreen = v!),
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
|
@ -118,37 +118,37 @@ class _DisplaySettingsSectionState extends State<DisplaySettingsSection> {
|
|||
title: Text(tr('화면 방향')),
|
||||
subtitle: Text(tr('가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)')),
|
||||
trailing: DropdownButton<String>(
|
||||
value: s.screenOrientation,
|
||||
value: s.display.orientation,
|
||||
items: [
|
||||
DropdownMenuItem(value: 'auto', child: Text(tr('자동 (기본)'))),
|
||||
DropdownMenuItem(value: 'landscape', child: Text(tr('가로 고정'))),
|
||||
DropdownMenuItem(value: 'portrait', child: Text(tr('세로 고정'))),
|
||||
],
|
||||
onChanged: (v) {
|
||||
c.updateSettings((x) => x.screenOrientation = v!);
|
||||
c.updateSettings((x) => x.display.orientation = v!);
|
||||
applyScreenOrientation(v!);
|
||||
},
|
||||
),
|
||||
),
|
||||
SettingTile(
|
||||
title: Text(tr('기본 화면 크기')),
|
||||
subtitle: Text(trf('글자 · 버튼 크기입니다. 위쪽 막대의 − · + 로 그때그때 바꿀 수 있고, 가운데 숫자를 누르면 이 크기로 돌아갑니다 ' '(지금 {0}%)', [(s.uiScale * 100).round()])),
|
||||
subtitle: Text(trf('글자 · 버튼 크기입니다. 위쪽 막대의 − · + 로 그때그때 바꿀 수 있고, 가운데 숫자를 누르면 이 크기로 돌아갑니다 ' '(지금 {0}%)', [(s.display.scale * 100).round()])),
|
||||
trailing: DropdownButton<double>(
|
||||
value: s.uiScaleDefault,
|
||||
value: s.display.scaleDefault,
|
||||
items: [
|
||||
for (var v = AppSettings.uiScaleMin; v <= AppSettings.uiScaleMax + 0.001; v += 0.1)
|
||||
for (var v = DisplaySettings.scaleMin; v <= DisplaySettings.scaleMax + 0.001; v += 0.1)
|
||||
DropdownMenuItem(
|
||||
value: AppSettings.clampUiScale(v),
|
||||
value: DisplaySettings.clampScale(v),
|
||||
child: Text('${(v * 100).round()}%${(v - 1).abs() < 0.001 ? tr(' (보통)') : ''}')),
|
||||
// 5% 단위로 저장된 값이 목록에 없을 때
|
||||
if (((s.uiScaleDefault * 100).round() % 10) != 0)
|
||||
if (((s.display.scaleDefault * 100).round() % 10) != 0)
|
||||
DropdownMenuItem(
|
||||
value: s.uiScaleDefault, child: Text('${(s.uiScaleDefault * 100).round()}%')),
|
||||
value: s.display.scaleDefault, child: Text('${(s.display.scaleDefault * 100).round()}%')),
|
||||
],
|
||||
// 기본 크기를 바꾸면 지금 크기도 그 크기로
|
||||
onChanged: (v) => c.updateSettings((x) => x
|
||||
..uiScaleDefault = v!
|
||||
..uiScale = v),
|
||||
..display.scaleDefault = v!
|
||||
..display.scale = v),
|
||||
),
|
||||
),
|
||||
// X-plore 비교 5-1 ⑦ (관리자 지시 10/21): 화면 밝기 - 처음 값 어둡게 (예전 그대로), 다 끝날 때까지 "시험"
|
||||
|
|
@ -164,13 +164,13 @@ class _DisplaySettingsSectionState extends State<DisplaySettingsSection> {
|
|||
]),
|
||||
subtitle: Text(tr('밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다')),
|
||||
trailing: DropdownButton<String>(
|
||||
value: s.uiBrightness,
|
||||
value: s.display.brightness,
|
||||
items: [
|
||||
DropdownMenuItem(value: 'dark', child: Text(tr('어두운 화면'))),
|
||||
DropdownMenuItem(value: 'light', child: Text(tr('밝은 화면'))),
|
||||
DropdownMenuItem(value: 'system', child: Text(tr('기기 따라'))),
|
||||
],
|
||||
onChanged: (v) => c.updateSettings((x) => x.uiBrightness = v!),
|
||||
onChanged: (v) => c.updateSettings((x) => x.display.brightness = v!),
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
|
@ -186,7 +186,7 @@ class AppIconTile extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final now = appIconOf(c.settings.appIcon);
|
||||
final now = appIconOf(c.settings.display.appIcon);
|
||||
return SettingTile(
|
||||
title: Text(tr('앱 아이콘')),
|
||||
subtitle: Padding(
|
||||
|
|
@ -205,7 +205,7 @@ class AppIconTile extends StatelessWidget {
|
|||
onTap: id == now
|
||||
? null
|
||||
: () {
|
||||
c.updateSettings((x) => x.appIcon = id);
|
||||
c.updateSettings((x) => x.display.appIcon = id);
|
||||
c.services.shell.setAppIcon(id);
|
||||
},
|
||||
child: Container(
|
||||
|
|
|
|||
|
|
@ -129,12 +129,12 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
_group('mkv', Icons.movie_creation_outlined, tr('MKV 만들기'), [MkvSettingsSection(c: c, desk: desk)]),
|
||||
_group('subtitle', Icons.subtitles_outlined, tr('자막 (AI · 인터넷)'), [SubtitleSettingsSection(c: c, desk: desk)]),
|
||||
// 123 · 121: 모델 받기 · 처리 장치 · 서버 · 서비스
|
||||
if (s.components.contains('aiimage'))
|
||||
if (s.display.components.contains('aiimage'))
|
||||
_group('ai', Icons.auto_awesome_outlined, tr('AI 그림'), [AiSettings(c: c)]),
|
||||
_group('play', Icons.play_circle_outline, tr('재생'), [PlaySettingsSection(c: c, desk: desk)]),
|
||||
_group('browser', Icons.public, tr('웹 브라우저'), [BrowserSettingsSection(c: c, desk: desk)]),
|
||||
// 사용자 요청 10/10: Tor 브라우저 · DNS 강제
|
||||
if (s.components.contains('tor'))
|
||||
if (s.display.components.contains('tor'))
|
||||
_group('tor', Icons.vpn_lock_outlined, tr('Tor 브라우저'), [TorSettings(c: c)]),
|
||||
_group('network', Icons.dns_outlined, tr('네트워크 · DNS'), [DnsSettings(c: c)]),
|
||||
_group('files', Icons.folder_copy_outlined, tr('파일 탐색기'), [FilesSettingsSection(c: c, desk: desk)]),
|
||||
|
|
@ -187,8 +187,8 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
final chips = [
|
||||
for (final id in _groupIds)
|
||||
if ((id != 'run' || desk) &&
|
||||
(id != 'ai' || c.settings.components.contains('aiimage')) &&
|
||||
(id != 'tor' || c.settings.components.contains('tor')))
|
||||
(id != 'ai' || c.settings.display.components.contains('aiimage')) &&
|
||||
(id != 'tor' || c.settings.display.components.contains('tor')))
|
||||
ActionChip(
|
||||
label: Text(_groupTitle(id), style: const TextStyle(fontSize: 13)),
|
||||
onPressed: () {
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ void main() {
|
|||
final s = AppSettings();
|
||||
expect((s.aiImage.engine, s.aiImage.device, s.aiImage.taesd, s.aiImage.width, s.aiImage.height, s.aiImage.steps, s.aiImage.cfg, s.aiImage.count),
|
||||
('local', 'auto', true, 512, 512, 4, 1.0, 1));
|
||||
expect(s.components, contains('aiimage'));
|
||||
expect(s.display.components, contains('aiimage'));
|
||||
final b = AppSettings.fromJson((AppSettings()
|
||||
..aiImage.width = 768
|
||||
..aiImage.steps = 8
|
||||
|
|
@ -485,9 +485,9 @@ void main() {
|
|||
.toJson());
|
||||
expect((b.aiImage.width, b.aiImage.steps, b.aiImage.taesd), (768, 8, false));
|
||||
final old = AppSettings.fromJson({'components': ['mkv', 'browser']});
|
||||
expect(old.components, contains('aiimage'));
|
||||
expect(old.display.components, contains('aiimage'));
|
||||
expect(old.migrated, contains('aiImage'));
|
||||
final off = AppSettings.fromJson((AppSettings()..components = ['mkv']).toJson());
|
||||
expect(off.components, isNot(contains('aiimage')), reason: '사용자가 끈 것은 그대로');
|
||||
final off = AppSettings.fromJson((AppSettings()..display.components = ['mkv']).toJson());
|
||||
expect(off.display.components, isNot(contains('aiimage')), reason: '사용자가 끈 것은 그대로');
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import 'package:jj_mkvmaker/services/app_shell.dart';
|
|||
|
||||
void main() {
|
||||
test('앱 아이콘 설정: 기본은 노랑, 저장 · 불러오기, 모르는 값은 기본으로', () {
|
||||
expect(AppSettings().appIcon, 'yellow');
|
||||
final s = AppSettings()..appIcon = 'black';
|
||||
expect(AppSettings.fromJson(s.toJson()).appIcon, 'black');
|
||||
expect(AppSettings.fromJson({'appIcon': 'nope'}).appIcon, 'yellow');
|
||||
expect(AppSettings.fromJson({}).appIcon, 'yellow');
|
||||
expect(AppSettings().display.appIcon, 'yellow');
|
||||
final s = AppSettings()..display.appIcon = 'black';
|
||||
expect(AppSettings.fromJson(s.toJson()).display.appIcon, 'black');
|
||||
expect(AppSettings.fromJson({'appIcon': 'nope'}).display.appIcon, 'yellow');
|
||||
expect(AppSettings.fromJson({}).display.appIcon, 'yellow');
|
||||
});
|
||||
|
||||
test('아이콘마다 그림 파일 (설정 미리보기 · 앱 안 버튼 · Windows ico · Android 런처) 이 있다', () {
|
||||
|
|
|
|||
|
|
@ -728,7 +728,7 @@ void main() {
|
|||
expect(find.byTooltip('MKV 화면 (지금 여기)'), findsOneWidget);
|
||||
|
||||
// 홈 화면을 웹 브라우저로 정하면 JJ 아이콘이 브라우저를 연다 (MKV 버튼은 그대로 있음)
|
||||
c.settings.startScreen = 'browser';
|
||||
c.settings.display.startScreen = 'browser';
|
||||
await tester.tap(find.byTooltip('다운로드 목록'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byTooltip(RegExp('^홈 화면 \\(웹 브라우저\\)')));
|
||||
|
|
@ -909,7 +909,7 @@ void main() {
|
|||
await tester.pump();
|
||||
await tester.pump();
|
||||
expect(tester.takeException(), isNull, reason: '두 줄로 바뀐 뒤에는 넘침 없음');
|
||||
final pct = tester.getRect(find.text('${(c.settings.uiScale * 100).round()}%'));
|
||||
final pct = tester.getRect(find.text('${(c.settings.display.scale * 100).round()}%'));
|
||||
for (final tip in ['작업 취소', '결과 폴더 열기: ${outputDirFor(file)}']) {
|
||||
final f = find.byTooltip(tip);
|
||||
expect(f, findsWidgets, reason: tip);
|
||||
|
|
|
|||
|
|
@ -18,36 +18,36 @@ AppController _plain() =>
|
|||
void main() {
|
||||
test('컴포넌트: 설치한 화면만 순서대로 · 홈 화면은 제거되면 첫 화면 · 설정 저장', () {
|
||||
final s = AppSettings();
|
||||
expect(s.components, containsAll(['mkv', 'browser', 'explorer', 'rsync', 'downloads', 'viewer']));
|
||||
expect(s.components, isNot(contains('docs'))); // 변환기를 받아야 하는 것은 처음엔 꺼짐
|
||||
expect([for (final p in AppComponent.pages(s.components, s.navOrder)) p.id],
|
||||
expect(s.display.components, containsAll(['mkv', 'browser', 'explorer', 'rsync', 'downloads', 'viewer']));
|
||||
expect(s.display.components, isNot(contains('docs'))); // 변환기를 받아야 하는 것은 처음엔 꺼짐
|
||||
expect([for (final p in AppComponent.pages(s.display.components, s.display.navOrder)) p.id],
|
||||
['mkv', 'browser', 'explorer', 'rsync', 'downloads', 'aiimage']);
|
||||
s
|
||||
..navOrder = ['explorer', 'mkv']
|
||||
..components = ['mkv', 'explorer', 'downloads'];
|
||||
expect([for (final p in AppComponent.pages(s.components, s.navOrder)) p.id], ['explorer', 'mkv', 'downloads']);
|
||||
s.startScreen = 'browser'; // 제거한 화면 → 설치된 첫 화면
|
||||
..display.navOrder = ['explorer', 'mkv']
|
||||
..display.components = ['mkv', 'explorer', 'downloads'];
|
||||
expect([for (final p in AppComponent.pages(s.display.components, s.display.navOrder)) p.id], ['explorer', 'mkv', 'downloads']);
|
||||
s.display.startScreen = 'browser'; // 제거한 화면 → 설치된 첫 화면
|
||||
expect(AppNavButtons.homeId(s), 'explorer');
|
||||
final back = AppSettings.fromJson(s.toJson());
|
||||
expect(back.components, ['mkv', 'explorer', 'downloads']);
|
||||
expect(back.navOrder, ['explorer', 'mkv']);
|
||||
expect(back.display.components, ['mkv', 'explorer', 'downloads']);
|
||||
expect(back.display.navOrder, ['explorer', 'mkv']);
|
||||
// 예전 설정 (컴포넌트 없음) 은 기본
|
||||
expect(AppSettings.fromJson({}).components, AppComponent.defaultInstalled);
|
||||
expect(AppSettings.fromJson({}).display.components, AppComponent.defaultInstalled);
|
||||
});
|
||||
|
||||
test('225: 이 판이 모르는 구성 요소 · 화면 id 는 지우지 않는다 (예전 판을 거쳐 다시 올려도 켠 것이 남게)', () {
|
||||
final j = {'aiImageV1': true, 'components': ['mkv', 'browser', 'future_screen'], 'navOrder': ['browser', 'future_screen', 'mkv']};
|
||||
final s = AppSettings.fromJson(j);
|
||||
expect(s.components, ['mkv', 'browser', 'future_screen']);
|
||||
expect(s.navOrder, ['browser', 'future_screen', 'mkv']);
|
||||
expect(s.display.components, ['mkv', 'browser', 'future_screen']);
|
||||
expect(s.display.navOrder, ['browser', 'future_screen', 'mkv']);
|
||||
// 화면 · 버튼에는 아는 것만
|
||||
expect([for (final p in AppComponent.pages(s.components, s.navOrder)) p.id], ['browser', 'mkv']);
|
||||
expect([for (final p in AppComponent.pages(s.display.components, s.display.navOrder)) p.id], ['browser', 'mkv']);
|
||||
// 저장했다 다시 읽어도 그대로
|
||||
final back = AppSettings.fromJson(s.toJson());
|
||||
expect(back.components, contains('future_screen'));
|
||||
expect(back.navOrder, contains('future_screen'));
|
||||
expect(back.display.components, contains('future_screen'));
|
||||
expect(back.display.navOrder, contains('future_screen'));
|
||||
// 순서 바꾸기 (explorer 를 맨 앞으로): future_screen 은 둘째 자리 그대로
|
||||
expect(AppComponent.reorder(s.navOrder, ['explorer', 'mkv', 'browser']), ['explorer', 'future_screen', 'mkv', 'browser']);
|
||||
expect(AppComponent.reorder(s.display.navOrder, ['explorer', 'mkv', 'browser']), ['explorer', 'future_screen', 'mkv', 'browser']);
|
||||
// 예전 순서에 없던 아는 화면은 뒤에
|
||||
expect(AppComponent.reorder(['future_screen', 'mkv'], ['mkv', 'explorer']), ['future_screen', 'mkv', 'explorer']);
|
||||
});
|
||||
|
|
@ -67,7 +67,7 @@ void main() {
|
|||
g.failure.value = DnsFailure('192.0.2.1', 'x', DateTime(2026, 10, 12, 1, 35));
|
||||
await tester.pump();
|
||||
expect(find.text('DNS 서버 (192.0.2.1) 에 닿지 않음 (01:35부터)'), findsOneWidget);
|
||||
await c.updateSettings((s) => s.showLog = !s.showLog);
|
||||
await c.updateSettings((s) => s.display.showLog = !s.display.showLog);
|
||||
await tester.pump();
|
||||
expect(find.text('DNS 서버 (192.0.2.1) 에 닿지 않음 (01:35부터)'), findsOneWidget);
|
||||
await tester.tap(find.text('시스템 DNS 로 바꾸기'));
|
||||
|
|
@ -87,11 +87,11 @@ void main() {
|
|||
expect(find.byTooltip('MKV 화면 (지금 여기)'), findsOneWidget);
|
||||
expect(find.byTooltip('파일 탐색기'), findsOneWidget);
|
||||
expect(find.byTooltip('Rsync'), findsOneWidget);
|
||||
await c.updateSettings((s) => s.components = ['mkv', 'explorer']);
|
||||
await c.updateSettings((s) => s.display.components = ['mkv', 'explorer']);
|
||||
await tester.pump();
|
||||
expect(find.byTooltip('Rsync'), findsNothing);
|
||||
expect(find.byTooltip('파일 탐색기'), findsOneWidget);
|
||||
await c.updateSettings((s) => s.components = ['mkv']);
|
||||
await c.updateSettings((s) => s.display.components = ['mkv']);
|
||||
await tester.pump();
|
||||
expect(find.byTooltip('파일 탐색기'), findsNothing);
|
||||
expect(find.byTooltip('MKV 화면 (지금 여기)'), findsOneWidget);
|
||||
|
|
@ -110,15 +110,15 @@ void main() {
|
|||
// 2번째 (웹 브라우저) 를 위로 → 맨 앞
|
||||
await tester.tap(find.byTooltip('위로').at(1));
|
||||
await tester.pump();
|
||||
expect(c.settings.navOrder.take(2), ['browser', 'mkv']);
|
||||
expect(c.settings.display.navOrder.take(2), ['browser', 'mkv']);
|
||||
// 맨 앞 (웹 브라우저) 를 아래로 → 다시 2번째
|
||||
await tester.tap(find.byTooltip('아래로').first);
|
||||
await tester.pump();
|
||||
expect(c.settings.navOrder.take(2), ['mkv', 'browser']);
|
||||
expect(c.settings.display.navOrder.take(2), ['mkv', 'browser']);
|
||||
// 마지막 (AI 그림) 을 위로 → 5번째
|
||||
await tester.tap(find.byTooltip('위로').last);
|
||||
await tester.pump();
|
||||
expect(c.settings.navOrder, ['mkv', 'browser', 'explorer', 'rsync', 'aiimage', 'downloads']);
|
||||
expect(c.settings.display.navOrder, ['mkv', 'browser', 'explorer', 'rsync', 'aiimage', 'downloads']);
|
||||
});
|
||||
|
||||
testWidgets('화면 가운데를 좌우로 밀면 순서대로 다음 · 이전 화면 (끝 다음은 처음)', (tester) async {
|
||||
|
|
@ -127,8 +127,8 @@ void main() {
|
|||
addTearDown(tester.view.reset);
|
||||
final c = _plain();
|
||||
c.settings
|
||||
..components = ['mkv', 'explorer', 'rsync']
|
||||
..navOrder = ['mkv', 'explorer', 'rsync'];
|
||||
..display.components = ['mkv', 'explorer', 'rsync']
|
||||
..display.navOrder = ['mkv', 'explorer', 'rsync'];
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
builder: (context, child) => AppScope(controller: c, child: child!),
|
||||
home: HomePage(c: c),
|
||||
|
|
@ -157,7 +157,7 @@ void main() {
|
|||
await swipe(600); // 오른쪽으로 → 이전 (끝으로)
|
||||
expect(find.byTooltip('Rsync (지금 여기)'), findsOneWidget);
|
||||
// 102: 끝에서 처음으로 돌기를 끄면 끝에서 멈춘다
|
||||
await c.updateSettings((s) => s.swipeWrap = false);
|
||||
await c.updateSettings((s) => s.display.swipeWrap = false);
|
||||
await tester.pump();
|
||||
await swipe(-600);
|
||||
expect(find.byTooltip('Rsync (지금 여기)'), findsOneWidget);
|
||||
|
|
@ -166,9 +166,9 @@ void main() {
|
|||
expect(find.byTooltip('파일 탐색기 (지금 여기)'), findsOneWidget);
|
||||
await swipe(-600);
|
||||
expect(find.byTooltip('Rsync (지금 여기)'), findsOneWidget);
|
||||
await c.updateSettings((s) => s.swipeWrap = true);
|
||||
await c.updateSettings((s) => s.display.swipeWrap = true);
|
||||
// 끄면 밀어도 그대로
|
||||
await c.updateSettings((s) => s.swipeNav = false);
|
||||
await c.updateSettings((s) => s.display.swipeNav = false);
|
||||
await tester.pump();
|
||||
await swipe(600);
|
||||
expect(find.byTooltip('Rsync (지금 여기)'), findsOneWidget);
|
||||
|
|
@ -185,8 +185,8 @@ void main() {
|
|||
addTearDown(tester.view.reset);
|
||||
final c = _plain();
|
||||
c.settings
|
||||
..components = ['mkv', 'explorer']
|
||||
..navOrder = ['mkv', 'explorer'];
|
||||
..display.components = ['mkv', 'explorer']
|
||||
..display.navOrder = ['mkv', 'explorer'];
|
||||
final navKey = GlobalKey<NavigatorState>();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
navigatorKey: navKey,
|
||||
|
|
|
|||
|
|
@ -27,23 +27,23 @@ void main() {
|
|||
test('기본값 = 정한 답 · 저장 · 읽기', () {
|
||||
final s = AppSettings();
|
||||
expect([
|
||||
s.run.startMenuShortcut, s.copy.syncStopToast, s.swipeWrap, s.migrationNotice, s.allowInnerToOuter,
|
||||
s.run.startMenuShortcut, s.copy.syncStopToast, s.display.swipeWrap, s.migrationNotice, s.allowInnerToOuter,
|
||||
s.rememberPasswords, s.recycleOnDelete, s.reader.systemBars,
|
||||
], everyElement(isTrue));
|
||||
expect(s.cleanupPrechecked, ['work', 'download']);
|
||||
expect(s.tripleTapAction, 'move');
|
||||
expect(s.remote.davExternalOpen, 'ask');
|
||||
// 이미 있던 설정의 기본값도 결정대로
|
||||
expect([s.browser.pauseOnLeave, s.run.autoCheckUpdates, s.browser.adSkip, s.browser.adHide, s.run.inBackground, s.swipeNav],
|
||||
expect([s.browser.pauseOnLeave, s.run.autoCheckUpdates, s.browser.adSkip, s.browser.adHide, s.run.inBackground, s.display.swipeNav],
|
||||
everyElement(isTrue));
|
||||
// 탐색기 처음 배치 = 두 창 (규칙 7 · 관리자 결정 3, 10/22)
|
||||
expect([s.copy.liveSyncOnStart, s.copy.liveSyncIntervalSec, s.explorer.layout, s.reader.zipComic], ['auto', 30, 'dual', false]);
|
||||
expect([AppSettings.uiScaleMin, AppSettings.uiScaleMax], [0.5, 2.5]);
|
||||
expect([DisplaySettings.scaleMin, DisplaySettings.scaleMax], [0.5, 2.5]);
|
||||
// 바꾼 값은 남는다
|
||||
final b = AppSettings.fromJson((AppSettings()
|
||||
..run.startMenuShortcut = false
|
||||
..copy.syncStopToast = false
|
||||
..swipeWrap = false
|
||||
..display.swipeWrap = false
|
||||
..migrationNotice = false
|
||||
..allowInnerToOuter = false
|
||||
..rememberPasswords = false
|
||||
|
|
@ -52,7 +52,7 @@ void main() {
|
|||
..tripleTapAction = 'none'
|
||||
..remote.davExternalOpen = 'url')
|
||||
.toJson());
|
||||
expect([b.run.startMenuShortcut, b.copy.syncStopToast, b.swipeWrap, b.migrationNotice, b.allowInnerToOuter, b.rememberPasswords,
|
||||
expect([b.run.startMenuShortcut, b.copy.syncStopToast, b.display.swipeWrap, b.migrationNotice, b.allowInnerToOuter, b.rememberPasswords,
|
||||
b.recycleOnDelete], everyElement(isFalse));
|
||||
expect([b.cleanupPrechecked, b.tripleTapAction, b.remote.davExternalOpen], [['logs'], 'none', 'url']);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ void main() {
|
|||
test('언어 바꾸기: tr · trf · 선택 목록 이름 · 언어 이름이 그 언어로, 한국어로 되돌리기', () async {
|
||||
expect(tr('MKV 만들기'), 'MKV 만들기');
|
||||
await i18n.apply('en');
|
||||
expect(c.settings.uiLanguage, 'en');
|
||||
expect(c.settings.display.language, 'en');
|
||||
expect(tr('MKV 만들기'), 'Make MKV');
|
||||
expect(trf('동영상 {0}개 · 전체 선택', [3]), '3 videos · select all');
|
||||
expect(PlaylistMode.folder.label, 'All videos in the same folder');
|
||||
|
|
@ -96,7 +96,7 @@ void main() {
|
|||
expect(tr('환경 설정'), '環境設定');
|
||||
await i18n.apply('ko');
|
||||
expect(tr('환경 설정'), '환경 설정');
|
||||
expect(AppSettings.fromJson(c.settings.toJson()).uiLanguage, 'ko');
|
||||
expect(AppSettings.fromJson(c.settings.toJson()).display.language, 'ko');
|
||||
});
|
||||
|
||||
test('58 · 102: 시스템 언어 따르기 - 설정에는 system, 화면은 기기 언어 (들어 있지 않으면 English), 처음 설치의 기본', () async {
|
||||
|
|
@ -106,10 +106,10 @@ void main() {
|
|||
expect(I18nController.systemCode('zh_CN'), 'zh-Hans');
|
||||
expect(I18nController.systemCode('en_US'), 'en');
|
||||
expect(I18nController.systemCode('fr_FR'), 'en');
|
||||
expect(AppSettings().uiLanguage, I18nController.system); // 처음 설치
|
||||
expect(AppSettings.fromJson({'uiLanguage': 'ko'}).uiLanguage, 'ko'); // 고른 언어는 그대로
|
||||
expect(AppSettings().display.language, I18nController.system); // 처음 설치
|
||||
expect(AppSettings.fromJson({'uiLanguage': 'ko'}).display.language, 'ko'); // 고른 언어는 그대로
|
||||
await i18n.apply(I18nController.system);
|
||||
expect(c.settings.uiLanguage, I18nController.system);
|
||||
expect(c.settings.display.language, I18nController.system);
|
||||
expect(uiLanguage, I18nController.systemCode());
|
||||
await i18n.apply('ko');
|
||||
});
|
||||
|
|
@ -130,14 +130,14 @@ void main() {
|
|||
expect(i18n.addable.map((l) => l.code), contains('fr'));
|
||||
expect(i18n.addable.map((l) => l.code), isNot(contains('en')));
|
||||
await i18n.addLanguage(fr);
|
||||
expect(c.settings.uiLanguagesAdded, ['fr']);
|
||||
expect(c.settings.display.languagesAdded, ['fr']);
|
||||
expect(i18n.available, ['ko', 'en', 'ja', 'zh-Hans', 'fr']);
|
||||
expect(File(p.join(dir.path, 'l10n', 'fr.json')).existsSync(), isTrue);
|
||||
await i18n.apply('fr');
|
||||
expect(tr('취소'), '[fra_Latn] Cancel');
|
||||
expect(trf('{0}개 선택', [2]), '[fra_Latn] 2 selected');
|
||||
await i18n.removeLanguage('fr');
|
||||
expect(c.settings.uiLanguagesAdded, isEmpty);
|
||||
expect(c.settings.display.languagesAdded, isEmpty);
|
||||
expect(uiLanguage, 'ko', reason: '쓰던 언어를 지우면 한국어로');
|
||||
expect(File(p.join(dir.path, 'l10n', 'fr.json')).existsSync(), isFalse);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ void main() {
|
|||
createTranslator: () => throw UnimplementedError(),
|
||||
));
|
||||
c.settings
|
||||
..showLog = true
|
||||
..display.showLog = true
|
||||
..mkvCompactList = size;
|
||||
for (var i = 0; i < n; i++) {
|
||||
c.videos.add(VideoItem('D:/v/v$i.mp4'));
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ void main() {
|
|||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings.showLog = true;
|
||||
c.settings.display.showLog = true;
|
||||
for (var i = 1; i <= 6; i++) {
|
||||
c.videos.add(VideoItem('D:\\v\\영상$i.mp4'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ void main() {
|
|||
createRecognizer: () => throw UnimplementedError(),
|
||||
createTranslator: () => throw UnimplementedError(),
|
||||
));
|
||||
c.settings.showLog = true;
|
||||
c.settings.display.showLog = true;
|
||||
c.settings.moveTargets = [MoveTarget('보관', r'D:\보관')];
|
||||
for (var i = 1; i <= 4; i++) {
|
||||
c.videos.add(VideoItem('D:/v/아주 긴 이름의 동영상 파일 $i.mp4'));
|
||||
|
|
|
|||
|
|
@ -163,14 +163,14 @@ void main() {
|
|||
|
||||
test('Android 화면 방향: 기본 자동, 가로 · 세로 고정 저장, 예전 기본 (가로 고정) 은 한 번 자동으로', () {
|
||||
final s = AppSettings();
|
||||
expect(s.screenOrientation, 'auto');
|
||||
expect(s.display.orientation, 'auto');
|
||||
for (final m in ['portrait', 'auto', 'landscape']) {
|
||||
s.screenOrientation = m;
|
||||
expect(AppSettings.fromJson(s.toJson()).screenOrientation, m);
|
||||
s.display.orientation = m;
|
||||
expect(AppSettings.fromJson(s.toJson()).display.orientation, m);
|
||||
}
|
||||
expect(AppSettings.fromJson({'screenOrientation': 'sideways', 'orientationV2': true}).screenOrientation, 'auto');
|
||||
expect(AppSettings.fromJson({'screenOrientation': 'sideways', 'orientationV2': true}).display.orientation, 'auto');
|
||||
// 예전 설정 파일 (기본 가로 고정 시절) → 자동
|
||||
expect(AppSettings.fromJson({'screenOrientation': 'landscape'}).screenOrientation, 'auto');
|
||||
expect(AppSettings.fromJson({'screenOrientation': 'landscape'}).display.orientation, 'auto');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -200,11 +200,11 @@ void main() {
|
|||
expect(find.byTooltip('작업 기록 숨기기'), findsOneWidget);
|
||||
await tester.tap(find.byTooltip('작업 기록 숨기기'));
|
||||
await tester.pump();
|
||||
expect(c.settings.showLog, isFalse);
|
||||
expect(c.settings.display.showLog, isFalse);
|
||||
expect(find.text('작업 기록 보기'), findsOneWidget);
|
||||
await tester.tap(find.text('작업 기록 보기'));
|
||||
await tester.pump();
|
||||
expect(c.settings.showLog, isTrue);
|
||||
expect(c.settings.display.showLog, isTrue);
|
||||
expect(find.byTooltip('작업 기록 숨기기'), findsOneWidget);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,16 +25,16 @@ class _Shell extends NoopShell {
|
|||
|
||||
void main() {
|
||||
test('화면 크기 설정: 범위 · 5% 단위 · 저장', () {
|
||||
expect(AppSettings.clampUiScale(null), 1.0);
|
||||
expect(AppSettings.clampUiScale(0.1), 0.5);
|
||||
expect(AppSettings.clampUiScale(9), 2.5);
|
||||
expect(AppSettings.clampUiScale(2.5), 2.5);
|
||||
expect(AppSettings.clampUiScale(1.1000000001), 1.1);
|
||||
expect(DisplaySettings.clampScale(null), 1.0);
|
||||
expect(DisplaySettings.clampScale(0.1), 0.5);
|
||||
expect(DisplaySettings.clampScale(9), 2.5);
|
||||
expect(DisplaySettings.clampScale(2.5), 2.5);
|
||||
expect(DisplaySettings.clampScale(1.1000000001), 1.1);
|
||||
final s = AppSettings()
|
||||
..uiScale = 1.2
|
||||
..uiScaleDefault = 0.9;
|
||||
..display.scale = 1.2
|
||||
..display.scaleDefault = 0.9;
|
||||
final back = AppSettings.fromJson(s.toJson());
|
||||
expect([back.uiScale, back.uiScaleDefault], [1.2, 0.9]);
|
||||
expect([back.display.scale, back.display.scaleDefault], [1.2, 0.9]);
|
||||
});
|
||||
|
||||
testWidgets('화면 크기 버튼: − · + 로 10% 씩, 숫자를 누르면 기본 크기로, 화면이 실제로 커진다', (tester) async {
|
||||
|
|
@ -61,7 +61,7 @@ void main() {
|
|||
await tester.pump();
|
||||
await tester.tap(find.byTooltip('화면 크게'));
|
||||
await tester.pump();
|
||||
expect(c.settings.uiScale, 1.2);
|
||||
expect(c.settings.display.scale, 1.2);
|
||||
expect(find.text('120%'), findsOneWidget);
|
||||
// 화면에 그려지는 크기가 1.2 배
|
||||
final r = tester.getRect(find.byKey(const Key('box')));
|
||||
|
|
@ -71,17 +71,17 @@ void main() {
|
|||
|
||||
await tester.tap(find.byTooltip('화면 작게'));
|
||||
await tester.pump();
|
||||
expect(c.settings.uiScale, 1.1);
|
||||
expect(c.settings.display.scale, 1.1);
|
||||
|
||||
// 기본 크기 90% 로 정해 두고 숫자를 누르면 그 크기로
|
||||
c.settings.uiScaleDefault = 0.9;
|
||||
c.settings.display.scaleDefault = 0.9;
|
||||
await tester.tap(find.text('110%'));
|
||||
await tester.pump();
|
||||
expect(c.settings.uiScale, 0.9);
|
||||
expect(c.settings.display.scale, 0.9);
|
||||
expect(find.text('90%'), findsOneWidget);
|
||||
|
||||
// 끝까지 줄이면 − 가 꺼진다
|
||||
c.settings.uiScale = AppSettings.uiScaleMin;
|
||||
c.settings.display.scale = DisplaySettings.scaleMin;
|
||||
c.updateSettings((_) {});
|
||||
await tester.pump();
|
||||
expect(tester.widget<IconButton>(find.widgetWithIcon(IconButton, Icons.remove)).onPressed, isNull);
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ void main() {
|
|||
'imageExts': ['jpg'],
|
||||
'runInBackground': false,
|
||||
});
|
||||
expect(old.screenOrientation, 'auto');
|
||||
expect(old.display.orientation, 'auto');
|
||||
// 규칙 7 · 결정 3 (10/22): 처음 배치가 두 창이 되어 예전 'dual' 은 그대로 (옮겼다는 알림 없음)
|
||||
expect(old.explorer.layout, 'dual');
|
||||
expect(old.reader.zipComic, isFalse);
|
||||
|
|
@ -149,10 +149,10 @@ void main() {
|
|||
// 다시 저장 · 읽기: 이제 사용자가 고른 값으로 보고 그대로 · 알림 없음
|
||||
final again = AppSettings.fromJson((old
|
||||
..explorer.layout = 'dual'
|
||||
..screenOrientation = 'landscape')
|
||||
..display.orientation = 'landscape')
|
||||
.toJson());
|
||||
expect(again.explorer.layout, 'dual');
|
||||
expect(again.screenOrientation, 'landscape');
|
||||
expect(again.display.orientation, 'landscape');
|
||||
expect(again.migrated, isEmpty);
|
||||
// 처음 설치 (빈 설정) 는 알릴 것 없음
|
||||
expect(AppSettings.fromJson({}).migrated, isEmpty);
|
||||
|
|
|
|||
|
|
@ -41,11 +41,11 @@ void main() {
|
|||
tearDown(() => JjColors.use(Brightness.dark));
|
||||
|
||||
test('처음 값은 어둡게 (예전 색 그대로) · 모르는 값 · 없는 값도 어둡게 · 고른 값은 그대로', () {
|
||||
expect(AppSettings().uiBrightness, 'dark');
|
||||
expect(AppSettings.fromJson({}).uiBrightness, 'dark');
|
||||
expect(AppSettings.fromJson({'uiBrightness': 'neon'}).uiBrightness, 'dark');
|
||||
expect(AppSettings.fromJson({'uiBrightness': 'light'}).uiBrightness, 'light');
|
||||
expect(AppSettings.fromJson(AppSettings.fromJson({'uiBrightness': 'system'}).toJson()).uiBrightness, 'system');
|
||||
expect(AppSettings().display.brightness, 'dark');
|
||||
expect(AppSettings.fromJson({}).display.brightness, 'dark');
|
||||
expect(AppSettings.fromJson({'uiBrightness': 'neon'}).display.brightness, 'dark');
|
||||
expect(AppSettings.fromJson({'uiBrightness': 'light'}).display.brightness, 'light');
|
||||
expect(AppSettings.fromJson(AppSettings.fromJson({'uiBrightness': 'system'}).toJson()).display.brightness, 'system');
|
||||
expect(JjColors.bg, const Color(0xFF141417));
|
||||
expect(JjColors.accent, const Color(0xFF00C4CC));
|
||||
expect(JjColors.textDim, const Color(0xFF8E8E99));
|
||||
|
|
@ -98,7 +98,7 @@ void main() {
|
|||
BuildContext ctx() => t.element(find.byType(Scaffold).first);
|
||||
expect(Theme.of(ctx()).brightness, Brightness.dark);
|
||||
expect(JjColors.isLight, isFalse);
|
||||
await t.runAsync(() => c.updateSettings((x) => x.uiBrightness = 'light'));
|
||||
await t.runAsync(() => c.updateSettings((x) => x.display.brightness = 'light'));
|
||||
await t.pump();
|
||||
await t.pump();
|
||||
expect(JjColors.isLight, isTrue);
|
||||
|
|
@ -106,7 +106,7 @@ void main() {
|
|||
expect(Theme.of(ctx()).scaffoldBackgroundColor, JjPalette.light.bg);
|
||||
// 기기 따라
|
||||
t.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
|
||||
await t.runAsync(() => c.updateSettings((x) => x.uiBrightness = 'system'));
|
||||
await t.runAsync(() => c.updateSettings((x) => x.display.brightness = 'system'));
|
||||
await t.pump();
|
||||
await t.pump();
|
||||
expect(JjColors.isLight, isFalse);
|
||||
|
|
@ -114,7 +114,7 @@ void main() {
|
|||
await t.pump();
|
||||
await t.pump();
|
||||
expect(JjColors.isLight, isTrue, reason: '기기 밝기가 바뀌면 따라감');
|
||||
await t.runAsync(() => c.updateSettings((x) => x.uiBrightness = 'dark'));
|
||||
await t.runAsync(() => c.updateSettings((x) => x.display.brightness = 'dark'));
|
||||
await t.pump();
|
||||
await t.pump();
|
||||
expect(JjColors.isLight, isFalse);
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ void main() {
|
|||
final ids = [for (final p in AppComponent.pages(['mkv', 'browser', 'tor', 'explorer'], ['explorer', 'mkv', 'browser'])) p.id];
|
||||
expect(ids, ['explorer', 'mkv', 'browser', 'tor']);
|
||||
expect([for (final p in AppComponent.pages(['mkv', 'browser', 'tor'], ['browser', 'mkv'])) p.id], ['browser', 'tor', 'mkv']);
|
||||
final s = AppSettings()..startScreen = 'tor';
|
||||
final s = AppSettings()..display.startScreen = 'tor';
|
||||
expect(AppNavButtons.homeId(s), 'mkv', reason: '설치하지 않았으면 첫 화면');
|
||||
s.components = [...s.components, 'tor'];
|
||||
s.display.components = [...s.display.components, 'tor'];
|
||||
expect(AppNavButtons.homeId(s), 'tor');
|
||||
});
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ void main() {
|
|||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..tor.noticeSeen = true
|
||||
..components = [...c.settings.components, 'tor'];
|
||||
..display.components = [...c.settings.display.components, 'tor'];
|
||||
await t.pumpWidget(MaterialApp(
|
||||
navigatorKey: navKey,
|
||||
builder: (context, child) => AppScope(controller: c, child: child!),
|
||||
|
|
|
|||
Loading…
Reference in New Issue