diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 1673d48..373a0bf 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -1454,9 +1454,9 @@ "받지 않음": "Off", "받지 않음 (처음 쓸 때 내려받기 {0})": "Not downloaded (downloads on first use, {0})", "밝게": "Brighter", - "밝게 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "Light and Follow device are still in testing - colors on some screens may not fit yet", "밝기": "Brightness", "밝은 화면": "Light", + "밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "Light and Follow device are still in testing - colors on some screens may not fit yet", "밤 0~6시": "Night 0-6", "방법: {0}": "Method: {0}", "방식": "Type", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 102bf1e..c5a6475 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -1454,9 +1454,9 @@ "받지 않음": "取得しない", "받지 않음 (처음 쓸 때 내려받기 {0})": "未ダウンロード (初回使用時にダウンロード {0})", "밝게": "明るく", - "밝게 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "明るい・端末に合わせるはまだ試験中です - 一部の画面の色が合わない場合があります", "밝기": "明るさ", "밝은 화면": "明るい", + "밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "明るい・端末に合わせるはまだ試験中です - 一部の画面の色が合わない場合があります", "밤 0~6시": "夜 0~6 時", "방법: {0}": "方法: {0}", "방식": "方式", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 2dc7e9b..a8e9fb4 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -1454,9 +1454,9 @@ "받지 않음": "不获取", "받지 않음 (처음 쓸 때 내려받기 {0})": "未下载 (首次使用时下载 {0})", "밝게": "调亮", - "밝게 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "浅色和跟随设备仍在测试中 - 部分界面的颜色可能还不协调", "밝기": "亮度", "밝은 화면": "浅色", + "밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다": "浅色和跟随设备仍在测试中 - 部分界面的颜色可能还不协调", "밤 0~6시": "夜间 0~6 点", "방법: {0}": "方式: {0}", "방식": "方式", diff --git a/integration_test/copy_sync_shot_test.dart b/integration_test/copy_sync_shot_test.dart index 342424c..f34b43f 100644 --- a/integration_test/copy_sync_shot_test.dart +++ b/integration_test/copy_sync_shot_test.dart @@ -17,6 +17,8 @@ import 'package:jj_mkvmaker/ui/theme.dart'; /// 환경 설정 > 파일 탐색기 의 복사 · 동기화 부분을 실제 글꼴로 그려 PNG 로 (JJ_SHOT_DIR). 화면 배치 확인용. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌) + if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light); testWidgets('복사 · 동기화 설정 그림', (tester) async { final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path; diff --git a/integration_test/explorer_shot_test.dart b/integration_test/explorer_shot_test.dart index 0e51b00..fa1eef7 100644 --- a/integration_test/explorer_shot_test.dart +++ b/integration_test/explorer_shot_test.dart @@ -12,6 +12,7 @@ import 'package:jj_mkvmaker/ui/theme.dart'; /// 파일 탐색기 화면을 실제 글꼴 · 실제 FFmpeg (썸네일 · 동영상 정보) 로 그려 PNG 로 저장 (JJ_SHOT_DIR). /// 왼쪽 · 오른쪽 창 폴더: JJ_SHOT_LEFT · JJ_SHOT_RIGHT. 화면 배치 확인용. +/// JJ_SHOT_THEME=light 면 밝은 테마로 (⑦ - 파일 이름 끝에 _light) void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -21,6 +22,9 @@ void main() { final c = AppController(PlatformServices.create()); c.settings.explorerPaths = [env['JJ_SHOT_LEFT'] ?? Directory.current.path, env['JJ_SHOT_RIGHT'] ?? Directory.current.path]; final key = GlobalKey(); + final light = env['JJ_SHOT_THEME'] == 'light'; + JjColors.use(light ? Brightness.light : Brightness.dark); + addTearDown(() => JjColors.use(Brightness.dark)); for (final (name, size, layout, style) in [ ('wide', const Size(1600, 900), 'dual', 'xplore'), ('tall', const Size(900, 1400), 'dual', 'xplore'), @@ -45,7 +49,7 @@ void main() { final boundary = key.currentContext!.findRenderObject()! as RenderRepaintBoundary; final image = await tester.runAsync(() => boundary.toImage()); final bytes = await tester.runAsync(() => image!.toByteData(format: ui.ImageByteFormat.png)); - File('$out/explorer_$name.png').writeAsBytesSync(bytes!.buffer.asUint8List()); + File('$out/explorer_$name${light ? '_light' : ''}.png').writeAsBytesSync(bytes!.buffer.asUint8List()); await tester.pumpWidget(const SizedBox()); } tester.view.reset(); diff --git a/integration_test/explorer_text_shot_test.dart b/integration_test/explorer_text_shot_test.dart index 50bf186..9dd89ee 100644 --- a/integration_test/explorer_text_shot_test.dart +++ b/integration_test/explorer_text_shot_test.dart @@ -16,6 +16,8 @@ import 'package:path/path.dart' as p; /// 세 스타일 모두. 넘치면 (RenderFlex overflow) 시험이 실패한다 void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌) + if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light); testWidgets('40: 목록 글자 크기 · 줄 간격 화면 그림 (412 폭)', (tester) async { final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path; diff --git a/integration_test/monitor_shot_test.dart b/integration_test/monitor_shot_test.dart index 9e994c7..68e9b13 100644 --- a/integration_test/monitor_shot_test.dart +++ b/integration_test/monitor_shot_test.dart @@ -19,6 +19,8 @@ import 'package:path/path.dart' as p; /// 모니터링 화면 (복사 진행 중 · lsync · 일정 격자) 을 실제 글꼴로 그려 PNG 로 (JJ_SHOT_DIR). 화면 배치 확인용. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌) + if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light); testWidgets('모니터링 화면 그림', (tester) async { final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path; diff --git a/integration_test/settings_shot_test.dart b/integration_test/settings_shot_test.dart index 76a7607..d02ac85 100644 --- a/integration_test/settings_shot_test.dart +++ b/integration_test/settings_shot_test.dart @@ -15,6 +15,8 @@ import 'package:jj_mkvmaker/ui/theme.dart'; /// 환경 설정 화면을 실제 글꼴로 그려 PNG 로 저장 (JJ_SHOT_DIR). 화면 배치 확인용. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌) + if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light); testWidgets('환경 설정 화면 그림', (tester) async { final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path; diff --git a/integration_test/windows_p3_shot_test.dart b/integration_test/windows_p3_shot_test.dart index b9961aa..e9b18ad 100644 --- a/integration_test/windows_p3_shot_test.dart +++ b/integration_test/windows_p3_shot_test.dart @@ -15,6 +15,8 @@ import 'package:path/path.dart' as p; /// 크기 412 (폰 세로) · 960, 언어 ko · en · ja. 시험 파일은 임시 폴더에만 (끝나면 지움). 실행: flutter test integration_test\windows_p3_shot_test.dart -d windows void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌) + if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light); final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_p3'); for (final lang in ['ko', 'en', 'ja']) { for (final (w, h) in [(412, 900), (960, 600)]) { diff --git a/integration_test/windows_xplore_shot_test.dart b/integration_test/windows_xplore_shot_test.dart index f4d8384..d2a34ac 100644 --- a/integration_test/windows_xplore_shot_test.dart +++ b/integration_test/windows_xplore_shot_test.dart @@ -22,6 +22,8 @@ import 'package:path/path.dart' as p; /// 크기 412 (폰 세로, 한 창) · 960 (두 창), 언어 ko · en. 시험 파일은 임시 폴더에만, 설정은 메모리에만. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌) + if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light); final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_xplore'); for (final lang in ['ko', 'en']) { diff --git a/integration_test/windows_xplore_tools_shot_test.dart b/integration_test/windows_xplore_tools_shot_test.dart index 240b0b2..82e9497 100644 --- a/integration_test/windows_xplore_tools_shot_test.dart +++ b/integration_test/windows_xplore_tools_shot_test.dart @@ -22,6 +22,8 @@ import 'package:path/path.dart' as p; /// (JJ_SHOT_DIR, 없으면 임시 폴더). 크기 412 · 960, 언어 ko · en. 시험 파일은 임시 폴더에만. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌) + if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light); final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_xptools'); for (final lang in ['ko', 'en']) { diff --git a/lib/ui/ai_image_page.dart b/lib/ui/ai_image_page.dart index 4019dc4..b8c7525 100644 --- a/lib/ui/ai_image_page.dart +++ b/lib/ui/ai_image_page.dart @@ -224,7 +224,7 @@ class _AiImagePageState extends State with RouteAware { _engineRow(), if (!_local) // 인터넷 서비스 · 자기 서버를 쓸 때는 늘 알린다 - _notice(Icons.public, Colors.orangeAccent, + _notice(Icons.public, JjColors.warning, trf('그림 · 프롬프트가 기기 밖으로 나갑니다 ({0})', [_service!.label])), if (_local && !_modelsReady) _notice(Icons.download_outlined, JjColors.accent, @@ -287,7 +287,7 @@ class _AiImagePageState extends State with RouteAware { Padding( padding: const EdgeInsets.only(top: 8), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(_error!, style: TextStyle(color: _cancelled ? JjColors.textDim : Colors.redAccent)), + Text(_error!, style: TextStyle(color: _cancelled ? JjColors.textDim : JjColors.alert)), if (_detail.isNotEmpty) TextButton( onPressed: () => setState(() => _showDetail = !_showDetail), diff --git a/lib/ui/ai_settings.dart b/lib/ui/ai_settings.dart index 52a028a..7b3471e 100644 --- a/lib/ui/ai_settings.dart +++ b/lib/ui/ai_settings.dart @@ -181,7 +181,7 @@ class _AiSettingsState extends State { free == null ? trf('필요한 공간 {0}', [AiStore.sizeText(need)]) : trf('필요한 공간 {0} · 남은 공간 {1}', [AiStore.sizeText(need), AiStore.sizeText(free)]), - style: TextStyle(fontSize: 12, color: free != null && free < need ? Colors.redAccent : JjColors.textDim), + style: TextStyle(fontSize: 12, color: free != null && free < need ? JjColors.alert : JjColors.textDim), ); }, ), @@ -189,7 +189,7 @@ class _AiSettingsState extends State { if (_error != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text(_error!, style: const TextStyle(color: Colors.redAccent)), + child: Text(_error!, style: TextStyle(color: JjColors.alert)), ), for (final f in _files) _fileRow(f), const Divider(), diff --git a/lib/ui/android_file_browser.dart b/lib/ui/android_file_browser.dart index 942115e..680990c 100644 --- a/lib/ui/android_file_browser.dart +++ b/lib/ui/android_file_browser.dart @@ -268,7 +268,7 @@ class _FileBrowserState extends State<_FileBrowser> { : ListView(children: [ for (final d in _dirs) ListTile( - leading: const Icon(Icons.folder, color: Colors.amber), + leading: Icon(Icons.folder, color: JjColors.amber), title: Text(p.basename(d.path)), onTap: () => _open(d.path), ), diff --git a/lib/ui/archive_ui.dart b/lib/ui/archive_ui.dart index 2998349..83d5fe3 100644 --- a/lib/ui/archive_ui.dart +++ b/lib/ui/archive_ui.dart @@ -189,12 +189,12 @@ Future extractArchiveFlow(BuildContext context, AppController c, String const SizedBox(height: 6), Text(trf('풀 수 없는 이름 {0}개 (건너뜀): {1}', [plan.badNames.length, plan.badNames.take(5).map((x) => '${x.$1} (${x.$2})').join(', ')]), - style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)), + style: TextStyle(color: JjColors.warning, fontSize: 12)), ], if (plan.links.isNotEmpty) ...[ const SizedBox(height: 6), Text(trf('링크 {0}개는 만들지 않고 건너뜁니다: {1}', [plan.links.length, plan.links.take(5).join(', ')]), - style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)), + style: TextStyle(color: JjColors.warning, fontSize: 12)), ], if (plan.encrypted) ...[ const SizedBox(height: 6), @@ -223,7 +223,7 @@ Future extractArchiveFlow(BuildContext context, AppController c, String c.settings.copySkipNewer ? tr('덮어쓴 파일은 되돌릴 수 없습니다 (받는 쪽이 더 새 파일은 그대로 둡니다).') : tr('덮어쓴 파일은 되돌릴 수 없습니다.'), - style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)), + style: TextStyle(color: JjColors.warning, fontSize: 12)), // 관리자 판단: 풀린 크기가 빈 공간보다 크면 풀기 전에 알리고 묻는다 (압축 폭탄 · 공간 부족) if (short) Text(trf('풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?', [formatSize(plan.totalSize), formatSize(f)]), diff --git a/lib/ui/bookmark_ui.dart b/lib/ui/bookmark_ui.dart index 6e62b3a..64a5a93 100644 --- a/lib/ui/bookmark_ui.dart +++ b/lib/ui/bookmark_ui.dart @@ -31,7 +31,7 @@ List> folderItems(BookmarksController bm, {String? excl child: Padding( padding: EdgeInsets.only(left: depth * 14.0), child: Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(depth == 0 ? Icons.folder_special_outlined : Icons.folder_outlined, size: 16, color: Colors.amber), + Icon(depth == 0 ? Icons.folder_special_outlined : Icons.folder_outlined, size: 16, color: JjColors.amber), const SizedBox(width: 6), Text(f.title), ]), @@ -368,7 +368,7 @@ class _BookmarkDraggableState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), child: Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(n.isFolder ? Icons.folder : Icons.public, size: 16, color: n.isFolder ? Colors.amber : null), + Icon(n.isFolder ? Icons.folder : Icons.public, size: 16, color: n.isFolder ? JjColors.amber : null), const SizedBox(width: 6), Text(n.title, style: TextStyle(fontSize: 12, color: JjColors.text)), ]), @@ -610,7 +610,7 @@ class _BookmarkManagerPageState extends State { ), ), Icon(depth == 0 ? Icons.folder_special : (selected ? Icons.folder_open : Icons.folder), - size: 18, color: Colors.amber), + size: 18, color: JjColors.amber), const SizedBox(width: 8), Expanded(child: Text(f.title, maxLines: 1, overflow: TextOverflow.ellipsis)), ]), @@ -711,7 +711,7 @@ class _BookmarkManagerPageState extends State { BookmarkDraggable( n: n, onMenu: (at) => _menu(n, folder, at), - child: Icon(n.isFolder ? Icons.folder : Icons.public, size: 20, color: n.isFolder ? Colors.amber : JjColors.textDim), + child: Icon(n.isFolder ? Icons.folder : Icons.public, size: 20, color: n.isFolder ? JjColors.amber : JjColors.textDim), ), ]), title: Text(n.title, maxLines: 1, overflow: TextOverflow.ellipsis), @@ -762,7 +762,7 @@ class _BookmarkManagerPageState extends State { leading: BookmarkDraggable( n: n, onMenu: (at) => _menu(n, bm.tree.parentOf(n.id) ?? bm.tree.bar, at), - child: Icon(n.isFolder ? Icons.folder : Icons.public, size: 20, color: n.isFolder ? Colors.amber : JjColors.textDim), + child: Icon(n.isFolder ? Icons.folder : Icons.public, size: 20, color: n.isFolder ? JjColors.amber : JjColors.textDim), ), title: Text(n.title, maxLines: 1, overflow: TextOverflow.ellipsis), subtitle: Text( diff --git a/lib/ui/browser_page.dart b/lib/ui/browser_page.dart index 3f9ba52..eb8dfc6 100644 --- a/lib/ui/browser_page.dart +++ b/lib/ui/browser_page.dart @@ -870,7 +870,7 @@ class _BrowserPageState extends State with RouteAware { suffixIcon: IconButton( tooltip: starred ? keyHint(tr('즐겨찾기 수정'), 'Ctrl+D') : keyHint(tr('즐겨찾기 추가'), 'Ctrl+D'), icon: Icon(starred ? Icons.star : Icons.star_border, size: 18, - color: starred ? Colors.amber : JjColors.textDim), + color: starred ? JjColors.amber : JjColors.textDim), onPressed: _star, ), ), @@ -1653,7 +1653,7 @@ class _BookmarkBar extends StatelessWidget { menuChildren: _folderMenu(context, n), builder: (context, ctl, _) => TextButton.icon( onPressed: () => ctl.isOpen ? ctl.close() : ctl.open(), - icon: const Icon(Icons.folder, size: 15, color: Colors.amber), + icon: Icon(Icons.folder, size: 15, color: JjColors.amber), label: Text(label, style: const TextStyle(fontSize: 12)), ), ), @@ -1693,7 +1693,7 @@ class _BookmarkBar extends StatelessWidget { for (final c in f.children!) c.isFolder ? SubmenuButton( - leadingIcon: const Icon(Icons.folder, size: 16, color: Colors.amber), + leadingIcon: Icon(Icons.folder, size: 16, color: JjColors.amber), menuChildren: _folderMenu(context, c), child: Text(c.title), ) @@ -1911,7 +1911,7 @@ class _BookmarkPanelState extends State { n: n, onMenu: menuAt, child: Icon(n.isFolder ? Icons.folder : Icons.public, - size: 18, color: n.isFolder ? Colors.amber : JjColors.textDim), + size: 18, color: n.isFolder ? JjColors.amber : JjColors.textDim), ), ]), title: Text(n.title, maxLines: 1, overflow: TextOverflow.ellipsis), diff --git a/lib/ui/cleanup_dialog.dart b/lib/ui/cleanup_dialog.dart index 7cbd6bd..e5889af 100644 --- a/lib/ui/cleanup_dialog.dart +++ b/lib/ui/cleanup_dialog.dart @@ -89,7 +89,7 @@ class _CleanupDialogState extends State<_CleanupDialog> { ), ]), subtitle: Text(g.skipped ?? g.hint, - style: TextStyle(fontSize: 12, color: g.skipped != null ? Colors.amber : JjColors.textDim)), + style: TextStyle(fontSize: 12, color: g.skipped != null ? JjColors.amberText : JjColors.textDim)), ), ]), ), diff --git a/lib/ui/copy_sync_settings.dart b/lib/ui/copy_sync_settings.dart index 25dfe90..7401b2b 100644 --- a/lib/ui/copy_sync_settings.dart +++ b/lib/ui/copy_sync_settings.dart @@ -327,10 +327,10 @@ class _LiveSyncTile extends StatelessWidget { // 34: 안쪽 → 바깥은 지우기 없이만, 바깥 → 안쪽은 안 됨 (끝없이 돎) if (LiveSync.nestingProblem(LiveSyncPair(src!, dst!, delete: delete), allowInnerToOuter: c.settings.allowInnerToOuter) case final nest?) - Text(nest, style: const TextStyle(fontSize: 12, color: Colors.redAccent)) + Text(nest, style: TextStyle(fontSize: 12, color: JjColors.alert)) else if (isSameOrInside(src!, dst!)) Text(tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (지우기는 쓸 수 없음).'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), const SizedBox(height: 12), Wrap(spacing: 6, children: [ for (final m in CopyMethod.values) diff --git a/lib/ui/dlna_page.dart b/lib/ui/dlna_page.dart index 9c0890f..c760f4d 100644 --- a/lib/ui/dlna_page.dart +++ b/lib/ui/dlna_page.dart @@ -255,7 +255,7 @@ class _DlnaPageState extends State { final e = _entries[i]; return ListTile( dense: true, - leading: Icon(_icon(e), color: e.isContainer ? Colors.amber : JjColors.textDim), + leading: Icon(_icon(e), color: e.isContainer ? JjColors.amber : JjColors.textDim), title: Text(e.title, maxLines: 1, overflow: TextOverflow.ellipsis), subtitle: e.isContainer ? (e.childCount == null ? null : Text(trf('{0}개', [e.childCount!]))) diff --git a/lib/ui/downloads_page.dart b/lib/ui/downloads_page.dart index 8cb5490..1dd5ea0 100644 --- a/lib/ui/downloads_page.dart +++ b/lib/ui/downloads_page.dart @@ -289,7 +289,7 @@ class _DownloadRow extends StatelessWidget { DownloadState.queued => (tr('대기 중'), JjColors.textDim), DownloadState.downloading when t.extra['waiting'] == true => (tr('대기 중'), JjColors.textDim), DownloadState.downloading => (tr('받는 중'), JjColors.accent), - DownloadState.paused => (tr('일시정지'), Colors.amber), + DownloadState.paused => (tr('일시정지'), JjColors.amberText), DownloadState.done => (tr('완료'), JjColors.success), DownloadState.failed => (tr('실패'), JjColors.danger), DownloadState.cancelled => (tr('취소됨'), JjColors.textDim), @@ -321,7 +321,7 @@ class _DownloadRow extends StatelessWidget { Text( '${trf('트래커 · 피어에 닿지 않습니다 ({0}초째)', [((DateTime.now().millisecondsSinceEpoch - ((t.extra['stallSince'] as int?) ?? 0)) / 1000).round()])} · ' '${trf('이유 후보: {0}', [[for (final r in (t.extra['stallReasons'] as List?) ?? const []) stallReasonText('$r')].join(' · ')])}', - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), if (((t.extra['stallReasons'] as List?) ?? const []).contains('dns') && d.useSystemDns != null) TextButton(style: _small, onPressed: () => d.useSystemDns!(), child: Text(tr('시스템 DNS 로 바꾸기'))), TextButton(style: _small, onPressed: () => d.retryStalled(t), child: Text(tr('다시 시도'))), @@ -404,10 +404,10 @@ class DownloadProgressBar extends StatelessWidget { // 주황으로 "멈춤" - 움직이는 "준비 중…" 이면 받는 중인 것처럼 보였다 final stalled = t.state == DownloadState.downloading && t.extra['stalled'] == true; final color = switch (t.state) { - _ when stalled => Colors.orangeAccent, + _ when stalled => JjColors.warning, DownloadState.done => JjColors.success, DownloadState.failed => JjColors.danger, - DownloadState.paused => Colors.amber, + DownloadState.paused => JjColors.amberText, DownloadState.cancelled => JjColors.textDim, _ => JjColors.accent, }; diff --git a/lib/ui/explorer_bookmarks.dart b/lib/ui/explorer_bookmarks.dart index 12b1c6c..fccf4d8 100644 --- a/lib/ui/explorer_bookmarks.dart +++ b/lib/ui/explorer_bookmarks.dart @@ -93,7 +93,7 @@ class _BookmarksDialogState extends State<_BookmarksDialog> { for (final b in mine) ListTile( dense: true, - leading: Icon(b.isPair ? Icons.compare_arrows : Icons.star, color: Colors.amber, size: 20), + leading: Icon(b.isPair ? Icons.compare_arrows : Icons.star, color: JjColors.amber, size: 20), title: Text(b.name, maxLines: 1, overflow: TextOverflow.ellipsis), subtitle: Text(b.isPair ? '${vDisplay(b.path)}\n⇄ ${vDisplay(b.right!)}' : vDisplay(b.path), maxLines: b.isPair ? 2 : 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11)), diff --git a/lib/ui/explorer_keys_page.dart b/lib/ui/explorer_keys_page.dart index 9472ab6..fbc955a 100644 --- a/lib/ui/explorer_keys_page.dart +++ b/lib/ui/explorer_keys_page.dart @@ -129,7 +129,7 @@ class _KeyCaptureDialogState extends State<_KeyCaptureDialog> { Padding( padding: const EdgeInsets.only(top: 8), child: Text(trf('이미 "{0}" 에 쓰는 키입니다 - 저장하면 그 동작의 이 키는 없어집니다', [explorerKeyLabel(clash)]), - style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)), + style: TextStyle(color: JjColors.warning, fontSize: 12)), ), ]), ), diff --git a/lib/ui/explorer_look.dart b/lib/ui/explorer_look.dart index fb90372..2a685c0 100644 --- a/lib/ui/explorer_look.dart +++ b/lib/ui/explorer_look.dart @@ -100,7 +100,7 @@ int columnHideLevel(ExplorerStyle style, double width, {bool marks = false, doub final x = e.ext; switch (style) { case ExplorerStyle.xplore: - if (e.isDir) return (open ? Icons.folder_open : Icons.folder, Colors.amber); + if (e.isDir) return (open ? Icons.folder_open : Icons.folder, JjColors.amber); return ( switch (x) { _ when _subs.contains(x) => Icons.subtitles_outlined, @@ -128,16 +128,17 @@ int columnHideLevel(ExplorerStyle style, double width, {bool marks = false, doub if (_sheets.contains(x)) return (Icons.table_chart, const Color(0xFF4CAF7A)); if (_slides.contains(x)) return (Icons.slideshow, const Color(0xFFE8804F)); if (_programs.contains(x)) return (x == 'apk' ? Icons.android : Icons.terminal, const Color(0xFF8BC34A)); - if (_texts.contains(x)) return (Icons.description, const Color(0xFFB0BEC5)); - return (Icons.insert_drive_file, const Color(0xFFB0BEC5)); + // ⑦ 밝은 화면: 옅은 회색은 흰 바탕에서 안 보여 진하게 + if (_texts.contains(x)) return (Icons.description, JjColors.isLight ? const Color(0xFF78909C) : const Color(0xFFB0BEC5)); + return (Icons.insert_drive_file, JjColors.isLight ? const Color(0xFF78909C) : const Color(0xFFB0BEC5)); case ExplorerStyle.totalcmd: // Total Commander 느낌: 단순한 작은 아이콘, 폴더는 노랑, 실행 파일은 초록 if (e.isDir) return (Icons.folder, const Color(0xFFE8C547)); if (_programs.contains(x)) return (Icons.settings_applications, const Color(0xFF7CB342)); if (isVideoFile(e.path)) return (Icons.movie, const Color(0xFF64B5F6)); if (_images.contains(x)) return (Icons.image, const Color(0xFF64B5F6)); - if (_archives.contains(x)) return (Icons.inventory_2, const Color(0xFFBCAAA4)); - return (Icons.description, const Color(0xFFCFD8DC)); + if (_archives.contains(x)) return (Icons.inventory_2, JjColors.isLight ? const Color(0xFF8D6E63) : const Color(0xFFBCAAA4)); + return (Icons.description, JjColors.isLight ? const Color(0xFF78909C) : const Color(0xFFCFD8DC)); } } diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index 248f1be..17114cd 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -612,7 +612,7 @@ class _ExplorerPageState extends State { ? tr('목록 · 보기 · 꺼내기 (복사) 만 됩니다. 시스템 · 다른 앱의 개인 자료가 있으니 꺼낸 파일을 조심해서 다루세요') : tr('목록 · 보기 · 꺼내기 (복사) 만 됩니다. 다른 앱의 데이터가 있을 수 있으니 꺼낸 파일을 조심해서 다루세요'), actions: const [], - color: Colors.orangeAccent, + color: JjColors.warning, ); } @@ -623,7 +623,7 @@ class _ExplorerPageState extends State { context: context, builder: (ctx) => AlertDialog( scrollable: true, - icon: const Icon(Icons.security, color: Colors.orangeAccent), + icon: Icon(Icons.security, color: JjColors.warning), title: Text(tr('루트 폴더 보기 (읽기 전용)')), content: Text(tr('루트 (su) 권한으로 시스템 · 다른 앱의 폴더 전체를 봅니다. 다른 앱의 개인 자료 (대화 · 로그인 정보 · 비밀번호 등) 도 ' '보일 수 있습니다. 이 앱은 읽기만 하고 (쓰기 · 지우기 · 이름 바꾸기 없음), 꺼낸 파일은 고른 곳에만 둡니다.')), @@ -662,7 +662,7 @@ class _ExplorerPageState extends State { context: context, builder: (ctx) => AlertDialog( scrollable: true, - icon: const Icon(Icons.admin_panel_settings_outlined, color: Colors.orangeAccent), + icon: Icon(Icons.admin_panel_settings_outlined, color: JjColors.warning), title: Text(tr('시스템 폴더 보기 (읽기 전용)')), content: Text(tr('Shizuku 의 권한으로 Android/data · obb 처럼 막힌 폴더를 봅니다. 다른 앱의 데이터 (대화 · 로그인 정보 등) 가 ' '있을 수 있습니다. 이 앱은 읽기만 하고 (쓰기 · 지우기 · 이름 바꾸기 없음), 꺼낸 파일은 고른 곳에만 둡니다.')), @@ -726,7 +726,10 @@ class _ExplorerPageState extends State { required String title, required String body, required List actions, - Color color = Colors.redAccent}) => + Color? color}) => + _noticeBox(icon, title, body, actions, color ?? JjColors.alert); + + Widget _noticeBox(IconData icon, String title, String body, List actions, Color color) => Container( width: double.infinity, margin: const EdgeInsets.fromLTRB(6, 6, 6, 0), @@ -764,7 +767,7 @@ class _ExplorerPageState extends State { if (list.isEmpty) return null; return _notice( icon: Icons.broken_image_outlined, - color: Colors.orangeAccent, + color: JjColors.warning, title: trf('만들다 만 파일 {0}개', [list.length]), body: tr('복사 · 동기화가 중간에 끊겨 남은 임시 파일입니다 (.jjpart). 완성된 파일이 아니므로 지워도 됩니다. ' '같은 파일을 다시 복사하면 새로 만듭니다.'), @@ -1531,8 +1534,8 @@ class _ExplorerPageState extends State { volume, false, ), - _Row.favorites => (Icons.star, Colors.amber, tr('즐겨찾기'), pane.favoritesOpen), - _ => (Icons.star_outline, Colors.amber, fav!.name, null), + _Row.favorites => (Icons.star, JjColors.amber, tr('즐겨찾기'), pane.favoritesOpen), + _ => (Icons.star_outline, JjColors.amber, fav!.name, null), }; void tap() { setState(() => _active = _panes.indexOf(pane)); @@ -2990,19 +2993,19 @@ class _ExplorerPageState extends State { Padding( padding: const EdgeInsets.only(top: 8), child: Text(trf('그중 폴더 {0}개 - 안의 파일 · 폴더도 모두 함께', [paths.where(vIsDirSync).length]), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ), if (hiddenInside > 0) Padding( padding: const EdgeInsets.only(top: 8), child: Text(trf('지우는 폴더 안에 목록에서 숨긴 항목 {0}개가 있습니다 - 함께 지워집니다', [hiddenInside]), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ), if (local && !canRecycle) Padding( padding: const EdgeInsets.only(top: 8), child: Text(tr('이 위치 (네트워크 드라이브 · 네트워크 공유 · USB 메모리 등) 에는 휴지통이 없어 바로 지워집니다.'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ), if (canTrash && !permanent) Padding( @@ -3080,7 +3083,7 @@ class _ExplorerPageState extends State { context: context, builder: (ctx) => AlertDialog( scrollable: true, - icon: const Icon(Icons.warning_amber_rounded, color: Colors.orangeAccent), + icon: Icon(Icons.warning_amber_rounded, color: JjColors.warning), title: Text(trf('{0}개를 휴지통으로 옮기지 못했습니다', [notTrashed.length])), content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ for (final (x, why) in notTrashed.take(10)) Text('· ${vBasename(x)} - $why', style: const TextStyle(fontSize: 12)), @@ -3112,7 +3115,7 @@ class _ExplorerPageState extends State { context: context, builder: (ctx) => AlertDialog( scrollable: true, - icon: const Icon(Icons.error_outline, color: Colors.redAccent), + icon: Icon(Icons.error_outline, color: JjColors.alert), title: Text(trf('{0}개 중 {1}개를 지우지 못했습니다', [paths.length, failed.length + tooLong.length])), content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 144: 긴 경로는 이유 한 줄 + 이름만 (영구 삭제는 아래 버튼으로) @@ -3138,7 +3141,7 @@ class _ExplorerPageState extends State { if (tooLong.isNotEmpty) ...[ TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('그대로 두기'))), FilledButton( - style: FilledButton.styleFrom(backgroundColor: Colors.redAccent), + style: FilledButton.styleFrom(backgroundColor: JjColors.alert), onPressed: () => Navigator.pop(ctx, true), child: Text(trf('영구 삭제 ({0}개, 되돌릴 수 없음)', [tooLong.length])), ), @@ -3501,7 +3504,7 @@ class _ExplorerPageState extends State { return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 12), Text(trf('같은 이름이 이미 있습니다: {0}', [_namesOf([for (final x in clashes) x.src])]), - style: const TextStyle(color: Colors.orangeAccent)), + style: TextStyle(color: JjColors.warning)), // 172: 두 파일의 크기 · 바뀐 날 (3개까지) for (final x in clashes.take(3)) ...[ Padding( @@ -3522,7 +3525,7 @@ class _ExplorerPageState extends State { : c.settings.copySkipNewer ? trf('안쪽에 받는 쪽이 더 새 파일 {0}개 - 합쳐도 건너뜁니다 (원본에 남음)', [n.data]) : trf('안쪽에 받는 쪽이 더 새 파일 {0}개 - 합치면 예전 것으로 덮어씁니다', [n.data]), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent), + style: TextStyle(fontSize: 12, color: JjColors.warning), ), ), ), @@ -3751,14 +3754,14 @@ class _ExplorerPageState extends State { removeSource && !both ? tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래로 옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로).') : tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), // 1280×720 등 낮은 화면에서도 보이게: 원본 지우기 체크 · 경고는 비교 목록보다 위에 (178) if (both) ...[ Text(keepOptionsTogether(tr('양쪽을 함께: 받는 쪽이 더 새 파일은 덮어쓰지 않습니다 (-u).')), style: TextStyle(fontSize: 12, color: JjColors.textDim)), if (optionMove) Text(tr('양쪽 (⇄) 에서는 원본 파일을 지우지 않습니다 (옵션의 --remove-source-files 는 쓰지 않음).'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ] else ...[ CheckboxListTile( contentPadding: EdgeInsets.zero, @@ -3778,7 +3781,7 @@ class _ExplorerPageState extends State { optionMove ? tr('원본 파일이 지워집니다 (이동). 환경 설정의 rsync 옵션에 --remove-source-files 가 있어 켜 두었습니다 - 끄면 이번에는 원본을 남깁니다.') : tr('원본 파일이 지워집니다 (이동).'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent, fontWeight: FontWeight.w600)), + style: TextStyle(fontSize: 12, color: JjColors.warning, fontWeight: FontWeight.w600)), const SizedBox(height: 4), Text(tr('끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:'), style: TextStyle(fontSize: 12, color: JjColors.textDim)), @@ -3824,7 +3827,7 @@ class _ExplorerPageState extends State { ), if (nestedDelete()) Text(tr('원본이 대상 안에 있으면 지우기 (--delete) 를 함께 쓸 수 없습니다 (대상의 다른 파일이 모두 지워집니다).'), - style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)), + style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)), // 110: 이 줄은 늘 같은 자리를 차지한다 (글이 사라져 창 높이가 바뀌면 버튼이 움직인다) SizedBox( height: 34, @@ -3833,7 +3836,7 @@ class _ExplorerPageState extends State { previewError != null ? tr('지우기가 들어간 실행이라, 비교하지 못하면 실행할 수 없습니다. 원본 · 대상을 확인한 뒤 [다시 비교] 를 누르세요.') : tr('지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent), + style: TextStyle(fontSize: 12, color: JjColors.warning), ) : null, ), @@ -5485,7 +5488,7 @@ class _ExplorerPageState extends State { icon: Icon( c.settings.folderBookmarks.any((b) => !b.isPair && samePath(b.path, pane.current)) ? Icons.star : Icons.star_outline, size: 16, - color: Colors.amber), + color: JjColors.amber), onPressed: () => _bookmarks(pane), ), // 76: 빠른 필터 (켜져 있으면 강조) @@ -5562,7 +5565,7 @@ class _ExplorerPageState extends State { // 165: 열 수 없는 드라이브는 숨기지 않고 "연결 안 됨" - 누르면 다시 연결 (다시 읽기) label: _driveOk[path] == false ? Text('$label · ${_removableDrives.contains(path) ? tr('비어 있음') : tr('연결 안 됨')} ⟳', - style: TextStyle(color: _removableDrives.contains(path) ? JjColors.textDim : Colors.orangeAccent)) + style: TextStyle(color: _removableDrives.contains(path) ? JjColors.textDim : JjColors.warning)) : Text(label), onPressed: () { setState(() => _active = _panes.indexOf(pane)); @@ -5830,7 +5833,7 @@ class _ExplorerPageState extends State { if (slim) const SizedBox.shrink() else if (look.rich && e.isDir && pane.errors[e.path] != null) - Text(tr('읽을 수 없음'), style: dim.copyWith(color: Colors.redAccent)) + Text(tr('읽을 수 없음'), style: dim.copyWith(color: JjColors.alert)) // 80: 열 없는 모양 (X-plore · 좁은 화면) 은 폴더 이름 아래에 크기 else if (e.isDir && !row.isRoot && !row.isUp && (!look.columns || compact) && _dirSize(pane, e) != null) Text(_dirSize(pane, e)!, style: dim, maxLines: 1), diff --git a/lib/ui/explorer_search.dart b/lib/ui/explorer_search.dart index 3028e42..691877e 100644 --- a/lib/ui/explorer_search.dart +++ b/lib/ui/explorer_search.dart @@ -451,13 +451,13 @@ class _SearchResultsPageState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: Text(tr('처음 찾을 때 끝까지 찾기 전의 결과입니다. 더 찾으려면 다시 찾으세요.'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ), if (_paused) Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ - Text(trf('{0}개에서 멈췄습니다', [_found.length]), style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + Text(trf('{0}개에서 멈췄습니다', [_found.length]), style: TextStyle(fontSize: 12, color: JjColors.warning)), OutlinedButton(onPressed: _more, child: Text(trf('{0}개 더 보기', [widget.chunk]))), ]), ), @@ -468,7 +468,7 @@ class _SearchResultsPageState extends State { tilePadding: EdgeInsets.zero, dense: true, title: Text(trf('읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다', [_skipped.length]), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), children: [ for (final (dir, why) in _skipped.take(50)) Align( diff --git a/lib/ui/file_tools.dart b/lib/ui/file_tools.dart index 3e83386..996d8eb 100644 --- a/lib/ui/file_tools.dart +++ b/lib/ui/file_tools.dart @@ -96,7 +96,7 @@ Future showFileHash(BuildContext context, String path) async { Padding( padding: const EdgeInsets.only(top: 6), child: Text(match ? tr('같습니다 ✔') : tr('다릅니다 - 파일이 바뀌었거나 다른 파일입니다'), - style: TextStyle(color: match ? Colors.greenAccent : JjColors.danger)), + style: TextStyle(color: match ? JjColors.positive : JjColors.danger)), ), ], ]), diff --git a/lib/ui/folder_picker.dart b/lib/ui/folder_picker.dart index ef65349..72e4ac9 100644 --- a/lib/ui/folder_picker.dart +++ b/lib/ui/folder_picker.dart @@ -11,6 +11,7 @@ import '../core/webdav.dart'; import '../l10n/tr.dart'; import 'android_file_browser.dart'; import 'file_error.dart'; +import 'theme.dart'; /// 폴더 고르기. Android 는 앱 안 화면 (내장 저장소 · SD 카드 · USB 를 실제 경로로 고를 수 있음), PC 는 Windows 폴더 선택 창 Future pickFolder(BuildContext context, String title, [String? initial]) => Platform.isAndroid @@ -94,9 +95,9 @@ class _DavFolderDialogState extends State<_DavFolderDialog> { final locked = isLockedError(error); return Center( child: Column(mainAxisSize: MainAxisSize.min, children: [ - Icon(locked ? Icons.lock_outline : Icons.cloud_off_outlined, color: Colors.redAccent), + Icon(locked ? Icons.lock_outline : Icons.cloud_off_outlined, color: JjColors.alert), const SizedBox(height: 8), - Text(title, textAlign: TextAlign.center, style: const TextStyle(color: Colors.redAccent, fontWeight: FontWeight.w600)), + Text(title, textAlign: TextAlign.center, style: TextStyle(color: JjColors.alert, fontWeight: FontWeight.w600)), if (body.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 4), child: Text(body, textAlign: TextAlign.center)), const SizedBox(height: 10), FilledButton.tonal( diff --git a/lib/ui/ftp_settings.dart b/lib/ui/ftp_settings.dart index ec2c731..41b4570 100644 --- a/lib/ui/ftp_settings.dart +++ b/lib/ui/ftp_settings.dart @@ -291,7 +291,7 @@ Future editFtpServer(BuildContext context, AppController c, {FtpServ ), ], if (testing) const LinearProgressIndicator(), - if (result != null) Text(result!, style: TextStyle(fontSize: 12, color: passed ? Colors.greenAccent : Colors.redAccent)), + if (result != null) Text(result!, style: TextStyle(fontSize: 12, color: passed ? JjColors.positive : JjColors.alert)), ]), ), actions: [ @@ -300,7 +300,7 @@ Future editFtpServer(BuildContext context, AppController c, {FtpServ onPressed: () async { if (await deleteFtpServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx); }, - style: TextButton.styleFrom(foregroundColor: Colors.redAccent), + style: TextButton.styleFrom(foregroundColor: JjColors.alert), child: Text(tr('지우기')), ), TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))), diff --git a/lib/ui/master_prompt.dart b/lib/ui/master_prompt.dart index 1c98a07..a031a9d 100644 --- a/lib/ui/master_prompt.dart +++ b/lib/ui/master_prompt.dart @@ -116,7 +116,7 @@ class _MasterDialogState extends State<_MasterDialog> { Padding( padding: const EdgeInsets.only(top: 8), child: Text(trf('여러 번 틀려 {0}초 뒤에 다시 넣을 수 있습니다', [wait.inSeconds + 1]), - style: const TextStyle(color: Colors.redAccent)), + style: TextStyle(color: JjColors.alert)), ), if (_busy) const Padding(padding: EdgeInsets.only(top: 8), child: LinearProgressIndicator()), if (!widget.forChange) diff --git a/lib/ui/monitor_page.dart b/lib/ui/monitor_page.dart index c8b498a..984132c 100644 --- a/lib/ui/monitor_page.dart +++ b/lib/ui/monitor_page.dart @@ -120,7 +120,7 @@ Future _liveDeleteDialog(BuildContext context, LiveSync live, LiveSyncPai // 관리자 3a 판단 (10/20): 확인한 쌍인데 옵션이 바뀌어 지울 것이 늘어 다시 묻는 것 if (!now && live.reasked.contains(LiveSync.keyOf(x))) ...[ Text(tr('지우기를 확인한 뒤 rsync 옵션이 바뀌어 지울 것이 늘었습니다. 다시 확인해 주세요.'), - style: const TextStyle(fontWeight: FontWeight.w600, color: Colors.orangeAccent)), + style: TextStyle(fontWeight: FontWeight.w600, color: JjColors.warning)), const SizedBox(height: 6), ], Text(trf('"{0}" 에는 없고 대상 "{1}" 에만 있는 항목입니다. 지우기 포함으로 맞추면 대상에서 지워집니다 (되돌릴 수 없음).', @@ -238,7 +238,7 @@ class _DiskInfo extends StatelessWidget { child: LinearProgressIndicator( minHeight: 6, value: used, - color: used > 0.9 ? Colors.redAccent : JjColors.accent, + color: used > 0.9 ? JjColors.alert : JjColors.accent, backgroundColor: JjColors.border, ), ), @@ -524,7 +524,7 @@ class _CopyCardState extends State<_CopyCard> { if (running && t.move) const SizedBox(height: 10), if (t.move) Text(tr('실시간 동기화는 원본을 지우지 않습니다 (복사로 맞춤). 원본을 비우려면 lsync 의 [최종 정리] 를 쓰세요.'), - style: const TextStyle(color: Colors.orangeAccent)), + style: TextStyle(color: JjColors.warning)), ]), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))), @@ -558,12 +558,12 @@ class _CopyCardState extends State<_CopyCard> { // 235: 이동인데 원본에 남은 것이 있으면 "끝" 만 보이지 않게 (주황 · 남은 수) 'done' when t.lastLeft > 0 => ( Icons.warning_amber, - Colors.orangeAccent, + JjColors.warning, '${trf('끝 · {0} · 파일 {1}개', [_hm(t.lastRun), t.lastFiles])} · ${trf('{0}개는 원본에 남음 (건너뜀)', [t.lastLeft])}' ), - 'done' => (Icons.check_circle, Colors.greenAccent, trf('끝 · {0} · 파일 {1}개', [_hm(t.lastRun), t.lastFiles])), - 'failed' => (Icons.error, Colors.redAccent, trf('실패 · {0}', [_hm(t.lastRun)])), - 'cancelled' => (Icons.pause_circle, Colors.orangeAccent, trf('멈춤 · {0}', [_hm(t.lastRun)])), + 'done' => (Icons.check_circle, JjColors.positive, trf('끝 · {0} · 파일 {1}개', [_hm(t.lastRun), t.lastFiles])), + 'failed' => (Icons.error, JjColors.alert, trf('실패 · {0}', [_hm(t.lastRun)])), + 'cancelled' => (Icons.pause_circle, JjColors.warning, trf('멈춤 · {0}', [_hm(t.lastRun)])), _ => (Icons.schedule, JjColors.textDim, tr('아직 실행 안 함')), }; final optsHint = switch (CopyMethod.of(_method)) { @@ -612,7 +612,7 @@ class _CopyCardState extends State<_CopyCard> { Padding( padding: const EdgeInsets.only(left: 28, top: 4), child: Wrap(crossAxisAlignment: WrapCrossAlignment.center, spacing: 4, children: [ - Text(t.lastMessage, maxLines: 3, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11, color: Colors.redAccent)), + Text(t.lastMessage, maxLines: 3, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 11, color: JjColors.alert)), // 237: 원문 (예외 이름 · 명령 줄) 은 [자세히] 에서 - 보고 복사해 물어볼 수 있게 (다운로드 실패 줄 136 과 같게) if (t.lastDetail.trim().isNotEmpty) TextButton( @@ -660,13 +660,13 @@ class _CopyCardState extends State<_CopyCard> { child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( '${t.move ? tr('이동') : tr('복사')} · ${trf('실제 실행: {0}', [effectiveRunText(_method, _options.text.trim(), move: t.move, skipNewer: s.copySkipNewer)])}', - style: TextStyle(fontSize: 12, color: t.move ? Colors.orangeAccent : JjColors.textDim), + style: TextStyle(fontSize: 12, color: t.move ? JjColors.warning : JjColors.textDim), ), // 관리자 지시 (10/20, 28 확인): 고쳐 읽어도 알아볼 수 없는 옵션 (실행은 작업이 막는다) if (_method == 'rsync' && optionProblems(_options.text).isNotEmpty) OptionProblemNote(optionProblems(_options.text)), if (!t.move && optionsRemoveSource(_options.text)) Text(tr('복사 작업이라 칸의 원본 지우기 옵션 (--remove-source-files · /MOV) 은 쓰지 않습니다. 옮기려면 Rsync 화면에서 [원본 파일 지우기] 를 켜세요'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ]), ), SizedBox( @@ -781,7 +781,7 @@ class _LiveTabState extends State<_LiveTab> { PruneChoice(value: prune, onChanged: (v) => set(() => prune = v)), const SizedBox(height: 12), Text(tr('원본이 비므로 이 실시간 동기화는 끕니다. 진행은 [복사 · rsync] 탭에서 봅니다.'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ]), ), actions: [ @@ -928,12 +928,12 @@ class _LiveTabState extends State<_LiveTab> { ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Icon(e.empty ? Icons.folder_off_outlined : Icons.error_outline, color: Colors.redAccent, size: 20), + Icon(e.empty ? Icons.folder_off_outlined : Icons.error_outline, color: JjColors.alert, size: 20), const SizedBox(width: 8), Expanded( child: Text( e.empty ? tr('원본 폴더가 비어 있어 멈췄습니다') : tr('원본을 읽을 수 없어 멈췄습니다'), - style: const TextStyle(fontWeight: FontWeight.w600, color: Colors.redAccent), + style: TextStyle(fontWeight: FontWeight.w600, color: JjColors.alert), ), ), ]), @@ -1002,7 +1002,7 @@ class _LiveTabState extends State<_LiveTab> { : active ? Icons.sync : Icons.bedtime_outlined, - color: x.enabled && !held ? (active ? JjColors.accent : Colors.orangeAccent) : JjColors.textDim), + color: x.enabled && !held ? (active ? JjColors.accent : JjColors.warning) : JjColors.textDim), const SizedBox(width: 8), Expanded( // 67: 끝 폴더가 보이게 짧게 · 69: 길게 누르면 (오른쪽 클릭) 고치기 · 지우기 @@ -1037,16 +1037,16 @@ class _LiveTabState extends State<_LiveTab> { Padding( padding: const EdgeInsets.only(left: 32, top: 2), child: Text(tr('멈춤 (이번 실행 동안) · 시작 버튼을 누르면 다시 동기화합니다'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ), // 42: 지우기를 아직 확인하지 않았고 지울 것이 있다 → 지우지 않고 맞추는 중. 목록을 보고 결정 if (live != null && (live.toDelete[k]?.isNotEmpty ?? false)) Padding( padding: const EdgeInsets.only(left: 32, top: 4), child: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ - const Icon(Icons.warning_amber_rounded, size: 16, color: Colors.orangeAccent), + Icon(Icons.warning_amber_rounded, size: 16, color: JjColors.warning), Text(trf('대상에만 있는 {0}개를 지울지 확인이 필요합니다 (확인 전에는 지우지 않음)', [live.toDelete[k]!.length]), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), OutlinedButton( onPressed: () => confirmLiveDeletes(context, live, x), child: Text(tr('지울 목록 보고 결정')), @@ -1058,10 +1058,10 @@ class _LiveTabState extends State<_LiveTab> { Padding( padding: const EdgeInsets.only(left: 32, top: 4), child: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ - const Icon(Icons.block, size: 16, color: Colors.redAccent), + Icon(Icons.block, size: 16, color: JjColors.alert), Text(tr('이 옵션의 일부 거르기 규칙은 미리 볼 수 없어 자동으로 지우지 않습니다'), - style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)), - Text('(${live.noAutoDelete[k]!.map(unbreakableOption).join(' · ')})', style: const TextStyle(fontSize: 12, color: Colors.redAccent)), + style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)), + Text('(${live.noAutoDelete[k]!.map(unbreakableOption).join(' · ')})', style: TextStyle(fontSize: 12, color: JjColors.alert)), OutlinedButton( onPressed: () => confirmLiveDeleteNow(context, live, x), child: Text(tr('지금 지우기 (확인)')), @@ -1086,7 +1086,7 @@ class _LiveTabState extends State<_LiveTab> { // 257: 폰 세로에서 글이 줄을 바꿀 수 있게 (넘쳤다) Flexible( child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(scheduleSummary(x.schedule), style: TextStyle(fontSize: 12, color: active ? JjColors.accent : Colors.orangeAccent)), + Text(scheduleSummary(x.schedule), style: TextStyle(fontSize: 12, color: active ? JjColors.accent : JjColors.warning)), if (x.schedule.isNotEmpty) Text(x.schedule.join(' | '), style: TextStyle(fontSize: 11, fontFamily: 'monospace', color: JjColors.textDim)), Text(tr('누르면 일정 바꾸기'), style: TextStyle(fontSize: 10, color: JjColors.textDim)), @@ -1097,7 +1097,7 @@ class _LiveTabState extends State<_LiveTab> { ), if (st != null) Text('${tr('마지막')}: ${st.$1.hour.toString().padLeft(2, '0')}:${st.$1.minute.toString().padLeft(2, '0')} ${st.$2}', - style: TextStyle(fontSize: 12, color: live?.problems[k] != null ? Colors.redAccent : JjColors.textDim)), + style: TextStyle(fontSize: 12, color: live?.problems[k] != null ? JjColors.alert : JjColors.textDim)), _DiskInfo(path: x.target), ]), ), @@ -1116,11 +1116,11 @@ class _LiveTabState extends State<_LiveTab> { (live?.onlyTarget[k] ?? 0) > 0 ? trf('원본의 것은 모두 대상에 있습니다 (대상에만 있는 것 {0}개는 그대로 둠)', [live!.onlyTarget[k]]) : tr('맞출 것 없음 (원본과 대상이 같습니다)'), - style: const TextStyle(fontSize: 12, color: Colors.greenAccent)) + style: TextStyle(fontSize: 12, color: JjColors.positive)) : Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( trf('맞출 것 {0}개{1}', [pend.length >= 500 ? '500+' : pend.length, active ? '' : tr(' · 다음 시작 시간에 맞춥니다')]), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent), + style: TextStyle(fontSize: 12, color: JjColors.warning), ), const SizedBox(height: 4), Container( @@ -1131,7 +1131,7 @@ class _LiveTabState extends State<_LiveTab> { for (final f in pend.take(100)) Text(f, style: TextStyle( - fontSize: 11.5, fontFamily: 'monospace', color: f.startsWith('− ') ? Colors.redAccent : null)), + fontSize: 11.5, fontFamily: 'monospace', color: f.startsWith('− ') ? JjColors.alert : null)), if (pend.length > 100) Text(trf('… 외 {0}개', [pend.length - 100]), style: const TextStyle(fontSize: 11)), ]), ), diff --git a/lib/ui/schedule_editor.dart b/lib/ui/schedule_editor.dart index ec5af8b..fb786a3 100644 --- a/lib/ui/schedule_editor.dart +++ b/lib/ui/schedule_editor.dart @@ -230,7 +230,7 @@ class _WeekGridState extends State<_WeekGrid> { onTap: () => _toggleRow(d), child: Center( child: Text(names[d], - style: TextStyle(fontSize: 12, color: d == 0 ? Colors.redAccent : d == 6 ? Colors.lightBlueAccent : null)), + style: TextStyle(fontSize: 12, color: d == 0 ? JjColors.alert : d == 6 ? Colors.lightBlueAccent : null)), ), ), ), diff --git a/lib/ui/secret_issue.dart b/lib/ui/secret_issue.dart index fbbe3c8..e979f4d 100644 --- a/lib/ui/secret_issue.dart +++ b/lib/ui/secret_issue.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../app/app_controller.dart'; import '../app/settings.dart'; import '../l10n/tr.dart'; +import 'theme.dart'; /// 안전 저장소 문제 (40-1 · 40-3 · 40-5) 를 화면 위 알림으로 보여 준다: 생길 때마다 · 켤 때 이미 있으면 바로. /// 비밀번호를 평문으로 몰래 저장하지 않는 대신, 저장되지 않았다는 것을 분명히 알리고 [다시 시도] 를 둔다. @@ -45,7 +46,7 @@ void watchSecretIssue(GlobalKey messengerKey, AppControl MaterialBanner secretIssueBanner(SecretIssue issue, {required VoidCallback onRetry, required VoidCallback onClose}) { final lost = issue.kind == SecretIssueKind.lost; return MaterialBanner( - leading: const Icon(Icons.key_off_outlined, color: Colors.orangeAccent), + leading: Icon(Icons.key_off_outlined, color: JjColors.warning), content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( lost ? tr('저장된 비밀번호를 되살리지 못했습니다') : tr('이 기기의 안전 저장소를 쓸 수 없어 비밀번호를 저장하지 못했습니다'), @@ -61,10 +62,10 @@ MaterialBanner secretIssueBanner(SecretIssue issue, {required VoidCallback onRet // 91: 새로 넣은 비밀번호가 앱을 끄면 어떻게 되는지 분명히 if (issue.revertsToOld) Text(tr('새 비밀번호는 아직 저장되지 않았습니다. 앱을 끄면 예전 비밀번호로 돌아갑니다.'), - style: const TextStyle(color: Colors.orangeAccent, fontWeight: FontWeight.w600)), + style: TextStyle(color: JjColors.warning, fontWeight: FontWeight.w600)), if (issue.lostOnExit && !issue.revertsToOld) Text(tr('새로 넣은 비밀번호는 아직 저장되지 않았습니다. 앱을 끄면 사라집니다.'), - style: const TextStyle(color: Colors.orangeAccent, fontWeight: FontWeight.w600)), + style: TextStyle(color: JjColors.warning, fontWeight: FontWeight.w600)), ]), actions: [ if (!lost) TextButton(onPressed: onRetry, child: Text(tr('다시 시도'))), diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index ada19db..879ff1b 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -504,11 +504,11 @@ class _SettingsPageState extends State { const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), - decoration: BoxDecoration(border: Border.all(color: Colors.orangeAccent), borderRadius: BorderRadius.circular(4)), - child: Text(tr('시험'), style: const TextStyle(fontSize: 11, color: Colors.orangeAccent)), + decoration: BoxDecoration(border: Border.all(color: JjColors.warning), borderRadius: BorderRadius.circular(4)), + child: Text(tr('시험'), style: TextStyle(fontSize: 11, color: JjColors.warning)), ), ]), - subtitle: Text(tr('밝게 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다')), + subtitle: Text(tr('밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다')), trailing: DropdownButton( value: s.uiBrightness, items: [ @@ -1680,7 +1680,7 @@ Future confirmReadOnlyView(BuildContext context, {required IconData icon, return StatefulBuilder( builder: (ctx, set) => AlertDialog( scrollable: true, - icon: Icon(icon, color: Colors.orangeAccent), + icon: Icon(icon, color: JjColors.warning), title: Text(title), content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(body), diff --git a/lib/ui/sftp_settings.dart b/lib/ui/sftp_settings.dart index 1936e57..a71bf29 100644 --- a/lib/ui/sftp_settings.dart +++ b/lib/ui/sftp_settings.dart @@ -336,7 +336,7 @@ Future editSftpServer(BuildContext context, AppController c, {SftpS ), if (testing) const LinearProgressIndicator(), if (result != null) - Text(result!, style: TextStyle(fontSize: 12, color: passed ? Colors.greenAccent : Colors.redAccent)), + Text(result!, style: TextStyle(fontSize: 12, color: passed ? JjColors.positive : JjColors.alert)), ]), ), actions: [ @@ -345,7 +345,7 @@ Future editSftpServer(BuildContext context, AppController c, {SftpS onPressed: () async { if (await deleteSftpServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx); }, - style: TextButton.styleFrom(foregroundColor: Colors.redAccent), + style: TextButton.styleFrom(foregroundColor: JjColors.alert), child: Text(tr('지우기')), ), TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))), diff --git a/lib/ui/smb_settings.dart b/lib/ui/smb_settings.dart index e64f377..849d933 100644 --- a/lib/ui/smb_settings.dart +++ b/lib/ui/smb_settings.dart @@ -245,14 +245,14 @@ Future editSmbServer(BuildContext context, AppController c, {SmbServ const SizedBox(height: 8), if (testing) const LinearProgressIndicator(), if (result != null) - Text(result!, style: TextStyle(fontSize: 12, color: passed ? Colors.greenAccent : Colors.redAccent)), + Text(result!, style: TextStyle(fontSize: 12, color: passed ? JjColors.positive : JjColors.alert)), if (warning != null) Padding( padding: const EdgeInsets.only(top: 6), child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Icon(Icons.warning_amber_rounded, size: 18, color: Colors.orangeAccent), + Icon(Icons.warning_amber_rounded, size: 18, color: JjColors.warning), const SizedBox(width: 6), - Expanded(child: Text(warning!, style: const TextStyle(fontSize: 12, color: Colors.orangeAccent))), + Expanded(child: Text(warning!, style: TextStyle(fontSize: 12, color: JjColors.warning))), ]), ), ]), @@ -263,7 +263,7 @@ Future editSmbServer(BuildContext context, AppController c, {SmbServ onPressed: () async { if (await deleteSmbServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx); }, - style: TextButton.styleFrom(foregroundColor: Colors.redAccent), + style: TextButton.styleFrom(foregroundColor: JjColors.alert), child: Text(tr('지우기')), ), TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))), diff --git a/lib/ui/storage_chart.dart b/lib/ui/storage_chart.dart index 2c94bc7..7e85f52 100644 --- a/lib/ui/storage_chart.dart +++ b/lib/ui/storage_chart.dart @@ -107,10 +107,10 @@ class StorageTreemap extends StatelessWidget { Text(b.label, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.w600)), + style: TextStyle(fontSize: 12, color: JjColors.isLight && b.path == null ? JjColors.text : Colors.white, fontWeight: FontWeight.w600)), if (c.maxHeight >= 46) Text('${formatSize(b.size)} · ${pct.toStringAsFixed(pct < 10 ? 1 : 0)}%', - maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11, color: Colors.white70)), + maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 11, color: JjColors.isLight && b.path == null ? JjColors.textDim : Colors.white70)), ]), ), ), diff --git a/lib/ui/storage_page.dart b/lib/ui/storage_page.dart index 9121293..6ac8cd8 100644 --- a/lib/ui/storage_page.dart +++ b/lib/ui/storage_page.dart @@ -194,7 +194,7 @@ class _StorageAnalysisPageState extends State { if (s.unreadable > 0) trf('읽지 못한 폴더 {0}개 (크기가 실제보다 작을 수 있음)', [s.unreadable]), if (s.links > 0) trf('링크 {0}개는 세지 않음', [s.links]), ].join(' · '), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent), + style: TextStyle(fontSize: 12, color: JjColors.warning), ), ), Padding( @@ -226,7 +226,7 @@ class _StorageAnalysisPageState extends State { onSecondaryTapUp: (x) => _menuAt(x.globalPosition, d), child: ListTile( dense: true, - leading: const Icon(Icons.folder, color: Colors.amber), + leading: Icon(Icons.folder, color: JjColors.amber), title: Text(scanName(d), maxLines: 1, overflow: TextOverflow.ellipsis), subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), diff --git a/lib/ui/subtitle_search_dialog.dart b/lib/ui/subtitle_search_dialog.dart index f46a1df..cc73cd0 100644 --- a/lib/ui/subtitle_search_dialog.dart +++ b/lib/ui/subtitle_search_dialog.dart @@ -262,7 +262,7 @@ class _SearchDialogState extends State<_SearchDialog> { ), secondary: Row(mainAxisSize: MainAxisSize.min, children: [ if (s.hashMatch) _badge(tr('이 파일용'), JjColors.success), - if (s.machineTranslated) _badge(tr('기계 번역'), Colors.amber), + if (s.machineTranslated) _badge(tr('기계 번역'), JjColors.amberText), ]), ); }, diff --git a/lib/ui/sync_preview_view.dart b/lib/ui/sync_preview_view.dart index ab8d7f4..c00b288 100644 --- a/lib/ui/sync_preview_view.dart +++ b/lib/ui/sync_preview_view.dart @@ -84,7 +84,7 @@ class _SyncPreviewViewState extends State { if (_error != null) { return Row(children: [ Expanded( - child: Text(trf('비교하지 못했습니다: {0}', [_error!]), style: const TextStyle(fontSize: 12, color: Colors.redAccent)), + child: Text(trf('비교하지 못했습니다: {0}', [_error!]), style: TextStyle(fontSize: 12, color: JjColors.alert)), ), IconButton(tooltip: tr('다시 비교'), icon: const Icon(Icons.refresh, size: 18), onPressed: _run), ]); @@ -120,26 +120,26 @@ class _SyncPreviewViewState extends State { Expanded( child: Text( items.isEmpty ? tr('바뀌는 것 없음 (양쪽이 같습니다)') : parts.join(' · ') + (more ? tr(' · 더 있음') : ''), - style: TextStyle(fontSize: 12, color: items.isEmpty ? Colors.greenAccent : null), + style: TextStyle(fontSize: 12, color: items.isEmpty ? JjColors.positive : null), ), ), IconButton(tooltip: tr('다시 비교'), icon: const Icon(Icons.refresh, size: 18), onPressed: _run), ]), if (dels > 0) Text(keepOptionsTogether(trf('받는 쪽에서 {0}개가 지워집니다 (--delete, 되돌릴 수 없음)', [dels])), - style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)), + style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)), // 15 검토 F4: 받는 쪽에만 있는 받다 만 파일 (*.jjpart 등) 도 지워진다는 것을 따로 if (partials > 0) Text(trf('받다 만 파일 {0}개도 지워집니다', [partials]), - style: const TextStyle(fontSize: 12, color: Colors.redAccent)), + style: TextStyle(fontSize: 12, color: JjColors.alert)), // 211: 내용이 다른 같은 이름은 받는 쪽 파일이 바뀐다 - 목록의 "~" 만으로는 알아채기 어려웠다 if (overwrites > 0) Text(trf('받는 쪽에서 {0}개를 덮어씁니다 (내용이 다른 같은 이름 - 원래 것은 없어짐)', [overwrites]), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent, fontWeight: FontWeight.w600)), + style: TextStyle(fontSize: 12, color: JjColors.warning, fontWeight: FontWeight.w600)), // 229: 받는 쪽이 더 새 파일을 예전 원본으로 덮어쓰게 되면 따로 크게 (건너뛰기를 끄거나 rsync 에 -u 가 없을 때) if (overNewer > 0) Text(trf('그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다', [overNewer]), - style: const TextStyle(fontSize: 12.5, color: Colors.orangeAccent, fontWeight: FontWeight.w800)), + style: TextStyle(fontSize: 12.5, color: JjColors.warning, fontWeight: FontWeight.w800)), if (items.isNotEmpty) Container( margin: const EdgeInsets.only(top: 6), @@ -171,8 +171,8 @@ class _SyncPreviewViewState extends State { fontSize: 11.5, fontFamily: 'monospace', color: switch (x.action) { - PreviewAction.delete => Colors.redAccent, - PreviewAction.update || PreviewAction.skipDir => Colors.orangeAccent, + PreviewAction.delete => JjColors.alert, + PreviewAction.update || PreviewAction.skipDir => JjColors.warning, PreviewAction.onlyTarget || PreviewAction.skipNewer || PreviewAction.skipLink => JjColors.textDim, _ => null, }, @@ -253,7 +253,7 @@ Future confirmDeletingRun(BuildContext context, CopyTask t, {bool includeM Padding( padding: const EdgeInsets.only(top: 4), child: Text(tr('이동: 대상에 옮긴 파일은 원본에서 지워집니다'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ), const SizedBox(height: 8), // 15 검토 F2: 미리 볼 수 없는 거르기 규칙 (늘 같은 자리 - 비교 결과와 상관없이 옵션으로 정해짐) @@ -288,7 +288,7 @@ Future confirmDeletingRun(BuildContext context, CopyTask t, {bool includeM : deletes ? tr('지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.') : tr('원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent), + style: TextStyle(fontSize: 12, color: JjColors.warning), ) : null, ), @@ -362,7 +362,7 @@ class OptionProblemNote extends StatelessWidget { child: Text( // 28 지적 (10/20): 끝 문구는 문제 종류에 맞게 (따옴표 닫기 · - 로 시작 · 값 적기 · 곧은 글자로) optionProblemText(bad), - style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)), + style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)), ); } @@ -376,7 +376,7 @@ class FilterLimitNote extends StatelessWidget { @override Widget build(BuildContext context) => Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(trf('이 옵션의 일부 거르기 규칙은 미리 볼 수 없습니다 - 실제로 더 지워질 수 있습니다: {0}', [unsupported.map(unbreakableOption).join(' · ')]), - style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)), + style: TextStyle(fontSize: 12, color: JjColors.alert, fontWeight: FontWeight.w600)), if (deletes) CheckboxListTile( dense: true, diff --git a/lib/ui/theme.dart b/lib/ui/theme.dart index 389da51..50b5ad8 100644 --- a/lib/ui/theme.dart +++ b/lib/ui/theme.dart @@ -7,6 +7,12 @@ class JjPalette { /// 강조색 바탕 위 글자 (버튼 등) final Color onAccent; + + /// 주의 (주황) · 경고 (빨강) · 노랑 · 초록 강조 - 어두운 화면은 예전에 쓰던 Material 색 그대로 (orangeAccent · redAccent · amber · greenAccent) + final Color warning, alert, amber, positive; + + /// 노랑을 글자에 쓸 때 (밝은 화면은 [amber] (아이콘 · 폴더 · 별, 대비 3:1) 보다 진하게 - 글자 대비 4.5:1) + final Color amberText; const JjPalette({ required this.brightness, required this.bg, @@ -19,6 +25,11 @@ class JjPalette { required this.danger, required this.success, required this.onAccent, + required this.warning, + required this.alert, + required this.amber, + required this.positive, + required this.amberText, }); /// CapCut 참고 어두운 테마 (처음 값 - 예전 그대로) @@ -34,6 +45,11 @@ class JjPalette { danger: Color(0xFFFF5C6C), success: Color(0xFF3DD68C), onAccent: Colors.black, + warning: Color(0xFFFFAB40), + alert: Color(0xFFFF5252), + amber: Color(0xFFFFC107), + positive: Color(0xFF69F0AE), + amberText: Color(0xFFFFC107), ); /// X-plore 비교 5-1 ⑦ 밝은 테마 (관리자 지시 10/21, 시험). 흰 바탕에서 글자 대비 4.5:1 이상이 되게 강조 · 경고 · 성공을 어둡게 @@ -49,6 +65,11 @@ class JjPalette { danger: Color(0xFFC62833), success: Color(0xFF186B42), onAccent: Colors.white, + warning: Color(0xFFA14A00), + alert: Color(0xFFC62828), + amber: Color(0xFFB07800), + positive: Color(0xFF186B42), + amberText: Color(0xFF7A5900), ); } @@ -79,6 +100,11 @@ class JjColors { static Color get danger => _p.danger; static Color get success => _p.success; static Color get onAccent => _p.onAccent; + static Color get warning => _p.warning; + static Color get alert => _p.alert; + static Color get amber => _p.amber; + static Color get positive => _p.positive; + static Color get amberText => _p.amberText; } /// 색을 바꾼 뒤 모든 화면 조각을 다시 그린다 (상태는 그대로 - [JjColors] 를 직접 읽는 곳도 새 색으로) diff --git a/lib/ui/trash_page.dart b/lib/ui/trash_page.dart index 2eef8e9..0b84f25 100644 --- a/lib/ui/trash_page.dart +++ b/lib/ui/trash_page.dart @@ -133,7 +133,7 @@ class _TrashPageState extends State { value: on, onChanged: (v) => setState(() => v == true ? _picked.add(i.entryDir) : _picked.remove(i.entryDir)), secondary: Icon(i.isDir ? Icons.folder : Icons.insert_drive_file_outlined, - color: i.isDir ? Colors.amber : null), + color: i.isDir ? JjColors.amber : null), title: Text(i.name, maxLines: 1, overflow: TextOverflow.ellipsis), subtitle: Text('${i.original}\n${_date(i.deletedAt)} · ${formatSize(i.size)}', maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11)), diff --git a/lib/ui/update_dialog.dart b/lib/ui/update_dialog.dart index 57d1029..ad495fa 100644 --- a/lib/ui/update_dialog.dart +++ b/lib/ui/update_dialog.dart @@ -230,8 +230,8 @@ class _VersionPickerState extends State<_VersionPicker> { title: Row(children: [ Text('v${r.version}', style: const TextStyle(fontWeight: FontWeight.w600)), if (r.version == widget.current) _Badge(tr('지금'), JjColors.accent), - if (r.version == newest) _Badge(tr('최신'), Colors.greenAccent), - if (r.prerelease) _Badge(tr('시험판'), Colors.orangeAccent), + if (r.version == newest) _Badge(tr('최신'), JjColors.positive), + if (r.prerelease) _Badge(tr('시험판'), JjColors.warning), ]), subtitle: Text(r.zipUrl == null ? '${_date(r.published)} · ${tr('이 기기용 파일 없음')}' : _date(r.published), style: const TextStyle(fontSize: 11)), @@ -255,7 +255,7 @@ class _VersionPickerState extends State<_VersionPicker> { tr('Android 에서는 앱을 지운 뒤 예전 버전을 설치합니다. 받은 AI 모델 · 비밀번호 · 브라우저 로그인 등은 ' '사라집니다 (남는 것과 사라지는 것은 다음 창에서 보여 줍니다).'), ].join('\n'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ), const SizedBox(height: 8), Text(tr('바뀐 내용'), style: const TextStyle(fontWeight: FontWeight.w600)), @@ -547,8 +547,8 @@ Future _androidRollback(BuildContext context, AppController c, Updater up, Text(tr('남는 것'), style: const TextStyle(fontWeight: FontWeight.w600)), for (final x in kept) Text('· $x'), const SizedBox(height: 8), - Text(tr('사라지는 것'), style: const TextStyle(fontWeight: FontWeight.w600, color: Colors.orangeAccent)), - for (final x in lost) Text('· $x', style: const TextStyle(color: Colors.orangeAccent)), + Text(tr('사라지는 것'), style: TextStyle(fontWeight: FontWeight.w600, color: JjColors.warning)), + for (final x in lost) Text('· $x', style: TextStyle(color: JjColors.warning)), if (modelSize > 0) ...[ const SizedBox(height: 8), CheckboxListTile( @@ -569,7 +569,7 @@ Future _androidRollback(BuildContext context, AppController c, Updater up, ], if (!targetRestoresSettings) Text(trf('v{0} 은 저장해 둔 설정을 되살리지 못해 설정을 다시 해야 합니다.', [r.version]), - style: const TextStyle(color: Colors.orangeAccent)), + style: TextStyle(color: JjColors.warning)), ]), ), actions: [ @@ -598,7 +598,7 @@ Future _androidRollback(BuildContext context, AppController c, Updater up, actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))), FilledButton( - style: FilledButton.styleFrom(backgroundColor: Colors.redAccent), + style: FilledButton.styleFrom(backgroundColor: JjColors.alert), onPressed: () => Navigator.pop(ctx, true), child: Text(tr('되돌리기 계속')), ), diff --git a/lib/ui/vault_page.dart b/lib/ui/vault_page.dart index 9d071f3..e3264ea 100644 --- a/lib/ui/vault_page.dart +++ b/lib/ui/vault_page.dart @@ -346,7 +346,7 @@ class _VaultPageState extends State with WidgetsBindingObserver { style: TextStyle(fontSize: 12, color: JjColors.textDim)), for (final f in plan.failed.entries.take(3)) Text(trf('읽지 못해 빼고 넣습니다: {0} ({1})', [vBasename(f.key), f.value]), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent), maxLines: 2, overflow: TextOverflow.ellipsis), + style: TextStyle(fontSize: 12, color: JjColors.warning), maxLines: 2, overflow: TextOverflow.ellipsis), const SizedBox(height: 8), CheckboxListTile( contentPadding: EdgeInsets.zero, @@ -358,7 +358,7 @@ class _VaultPageState extends State with WidgetsBindingObserver { ), if (move) ...[ Text(tr('원본은 휴지통으로 갑니다 - 휴지통에 평문 (암호화하지 않은 원본) 이 남습니다'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), RadioGroup( groupValue: permanent, onChanged: (v) => set(() => permanent = v ?? false), diff --git a/lib/ui/webdav_settings.dart b/lib/ui/webdav_settings.dart index f4a6afa..eb2b22f 100644 --- a/lib/ui/webdav_settings.dart +++ b/lib/ui/webdav_settings.dart @@ -174,13 +174,13 @@ Future editDavServer(BuildContext context, AppController c, {DavServ Padding( padding: const EdgeInsets.only(top: 6), child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Icon(Icons.warning_amber_rounded, size: 18, color: Colors.orangeAccent), + Icon(Icons.warning_amber_rounded, size: 18, color: JjColors.warning), const SizedBox(width: 6), Expanded( child: Text( tr('http:// 주소는 아이디 · 비밀번호와 파일이 암호화되지 않고 그대로 갑니다. 같은 네트워크의 다른 사람이 볼 수 있으니 ' '서버가 https 를 지원하면 https:// 로 바꾸세요.'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + style: TextStyle(fontSize: 12, color: JjColors.warning)), ), ]), ), @@ -208,7 +208,7 @@ Future editDavServer(BuildContext context, AppController c, {DavServ Align( alignment: Alignment.centerLeft, child: Text(result!, - style: TextStyle(fontSize: 12, color: passed ? Colors.greenAccent : Colors.redAccent)), + style: TextStyle(fontSize: 12, color: passed ? JjColors.positive : JjColors.alert)), ), if (toHttps) Align( @@ -232,7 +232,7 @@ Future editDavServer(BuildContext context, AppController c, {DavServ onPressed: () async { if (await deleteDavServer(ctx, c, old) && ctx.mounted) Navigator.pop(ctx); }, - style: TextButton.styleFrom(foregroundColor: Colors.redAccent), + style: TextButton.styleFrom(foregroundColor: JjColors.alert), child: Text(tr('지우기')), ), TextButton(onPressed: !ok || testing ? null : test, child: Text(tr('연결 확인'))), diff --git a/test/theme_test.dart b/test/theme_test.dart index 725d50f..f6e352e 100644 --- a/test/theme_test.dart +++ b/test/theme_test.dart @@ -49,6 +49,8 @@ void main() { expect(JjColors.bg, const Color(0xFF141417)); expect(JjColors.accent, const Color(0xFF00C4CC)); expect(JjColors.textDim, const Color(0xFF8E8E99)); + // 예전에 쓰던 Material 색 그대로 (어두운 화면이 바뀌지 않게) + expect([JjColors.warning, JjColors.alert, JjColors.amber, JjColors.amberText, JjColors.positive], [Colors.orangeAccent, Colors.redAccent, Colors.amber, Colors.amber, Colors.greenAccent].map((c) => Color(c.toARGB32())).toList()); expect(resolveBrightness('dark', Brightness.light), Brightness.dark); expect(resolveBrightness('light', Brightness.dark), Brightness.light); expect(resolveBrightness('system', Brightness.light), Brightness.light); @@ -58,11 +60,15 @@ void main() { test('밝은 색: 바탕 · 칸 위 글자 대비 4.5:1 이상 (글 · 흐린 글 · 강조 · 경고 · 성공 · 강조 위 글자)', () { const l = JjPalette.light; for (final bg in [l.bg, l.panel, l.panelHigh]) { - for (final (name, fg) in [('text', l.text), ('textDim', l.textDim), ('accent', l.accent), ('danger', l.danger), ('success', l.success)]) { + for (final (name, fg) in [('text', l.text), ('textDim', l.textDim), ('accent', l.accent), ('danger', l.danger), ('success', l.success), ('warning', l.warning), ('alert', l.alert), ('amberText', l.amberText), ('positive', l.positive)]) { expect(contrast(fg, bg), greaterThanOrEqualTo(4.5), reason: '$name on ${bg.toARGB32().toRadixString(16)}'); } } expect(contrast(l.onAccent, l.accent), greaterThanOrEqualTo(4.5)); + // 아이콘 (폴더 · 별) 은 3:1 + for (final bg in [l.bg, l.panel, l.panelHigh]) { + expect(contrast(l.amber, bg), greaterThanOrEqualTo(3)); + } expect(buildTheme(Brightness.light).brightness, Brightness.light); expect(buildTheme(Brightness.light).scaffoldBackgroundColor, l.bg); expect(buildTheme(Brightness.dark).scaffoldBackgroundColor, JjPalette.dark.bg);