From 20f9d083b227be63d2bd84c70d5c1d2eef0d616b Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Fri, 23 Oct 2026 05:39:39 +0900 Subject: [PATCH] =?UTF-8?q?E6=201:=20=ED=83=90=EC=83=89=EA=B8=B0=20?= =?UTF-8?q?=EB=AA=A8=EC=96=91=20=EC=B2=98=EC=9D=8C=20=EA=B0=92=EC=9D=84=20?= =?UTF-8?q?=EA=B8=B0=EA=B8=B0=EB=A7=88=EB=8B=A4=20(Windows=20=3D=20Total?= =?UTF-8?q?=20Commander)=20=C2=B7=20=EC=98=88=EC=A0=84=20=ED=8C=90?= =?UTF-8?q?=EC=9D=B4=20=EC=A0=80=EC=9E=A5=ED=95=9C=20X-plore=20=EB=8A=94?= =?UTF-8?q?=20=ED=95=9C=20=EB=B2=88=EB=A7=8C=20=EB=AC=BB=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ExplorerSettings: 처음 값 defaultStyle (Windows totalcmd · Android xplore). 예전 파일에 모양 키가 없으면 그때의 X-plore. Windows 에서 저장된 xplore 이고 물었다는 표시 (explorerStyleV2) 가 없으면 styleAskPending (저장 안 함) - style_ask: 탐색기를 열 때 한 번 [바꾸기] (Total Commander) / [그대로] (X-plore) - 창 밖으로 닫지 못함, 어느 쪽이든 다시 묻지 않음. 환경 설정에서 직접 고르면 묻지 않음 - 약속 시험 explorer_style_contract_test: 새 설치 · 저장된 xplore (키 없던 예전 파일 포함) · windows/totalcmd 안 물음 · [그대로] · [바꾸기] - X-plore 모양 (트리) 을 보는 기존 시험 14개 파일 + 공용 하네스에 style = 'xplore' 를 밝힘 (기대값 그대로, 관리자 승인 10/23) - 설정 JSON 골든: 새 키 explorerStyleV2 한 줄 · 처음 값 출력의 Windows 모양 totalcmd 만 바꿈. 사전 4개 (en · ja · zh-Hans) Co-Authored-By: Claude Opus 5.5 --- assets/l10n/en.json | 4 + assets/l10n/ja.json | 4 + assets/l10n/zh-Hans.json | 4 + lib/app/settings/explorer_settings.dart | 21 +++++- lib/ui/explorer/style_ask.dart | 33 ++++++++ lib/ui/explorer_page.dart | 3 + lib/ui/settings/files_section.dart | 5 +- test/dismiss_test.dart | 1 + test/explorer_conflict_test.dart | 1 + test/explorer_drives_test.dart | 1 + test/explorer_pane_view_contract_test.dart | 1 + test/explorer_parallel_test.dart | 10 +++ test/explorer_steady_test.dart | 1 + test/explorer_style_contract_test.dart | 75 +++++++++++++++++++ test/explorer_webdav_test.dart | 1 + test/fixtures/settings_golden_default.json | 3 +- test/fixtures/settings_golden_legacy_out.json | 1 + test/fixtures/settings_golden_out.json | 1 + test/root_test.dart | 1 + test/rsync_safety_test.dart | 1 + test/sftp_explorer_test.dart | 1 + test/shizuku_test.dart | 1 + test/support/explorer_harness.dart | 1 + test/sync_problem_test.dart | 1 + test/vault_page_test.dart | 1 + 25 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 lib/ui/explorer/style_ask.dart create mode 100644 test/explorer_style_contract_test.dart diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 424fcea..5290dd2 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -423,6 +423,7 @@ "Windows 에서 이 앱의 알림이 꺼져 있어 보이지 않습니다": "Notifications for this app are turned off in Windows, so they won't appear", "Windows 에서만 쓸 수 있습니다. 이 기기에서는 문서를 다른 앱으로 엽니다.": "Available on Windows only. On this device, documents open in another app.", "Windows 오류 0x{0}": "Windows error 0x{0}", + "Windows 의 처음 모양이 Total Commander 로 바뀌었습니다 (목록 · \"..\" · 아래 F3~F8 단추 줄). 지금 쓰는 X-plore 모양을 바꿀까요? 환경 설정 > 파일 탐색기 > 모양에서 언제든 바꿀 수 있습니다.": "The default look on Windows is now Total Commander (list · \"..\" · F3–F8 buttons at the bottom). Change the X-plore look you are using now? You can change it any time in Settings > File explorer > Look.", "Windows 탐색기": "Windows Explorer", "X-plore 스타일: 선택 표시 늘 보이기": "X-plore style: always show selection marks", "X-plore 스타일: 옆으로 밀면 창 바꾸기": "X-plore style: swipe sideways to switch panes", @@ -827,6 +828,7 @@ "규칙상 허용되지 않음": "not allowed by ruleset", "그 자리로": "Go to location", "그 파일 하나만": "Only that file", + "그대로": "Keep", "그대로 두기": "Keep", "그래도 Chrome 엔진 쓰기…": "Use the Chrome engine anyway…", "그래도 맞추기 ({0}개 지움)": "Sync anyway (delete {0})", @@ -2884,6 +2886,7 @@ "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "File explorer tap: single tap selects → single tap opens. To revert: Settings > File explorer > Tap", "파일 탐색기 단축키": "File explorer shortcuts", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "File explorer two-pane layout: follow screen shape → side by side. To revert: the [Columns ⇆ / Rows ⇅] button in the file explorer, or Pane layout", + "파일 탐색기 모양": "File explorer look", "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "Only the name and size text in the file explorer list. Multiplied by the app screen size and the device text size", "파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "Row height in the file explorer list. Compact rows may be hard to tap with a finger", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "You can also change it from the ⋮ menu at the top of the file explorer (side by side · top and bottom · button bar position · buttons)", @@ -2918,6 +2921,7 @@ "페이지 열기": "Open page", "페이지 지우기": "Delete page", "페이지를 바꾼 것을 저장할까요?": "Save the page changes?", + "편집": "Edit", "편집 목록 {0}개": "{0} in the edit list", "편집 목록에 있음": "In the edit list", "편집 목록에 추가": "Add to the edit list", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index b05d2a0..5016e2f 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -423,6 +423,7 @@ "Windows 에서 이 앱의 알림이 꺼져 있어 보이지 않습니다": "Windows でこのアプリの通知がオフのため表示されません", "Windows 에서만 쓸 수 있습니다. 이 기기에서는 문서를 다른 앱으로 엽니다.": "Windows でのみ使えます。この端末では文書を他のアプリで開きます。", "Windows 오류 0x{0}": "Windows エラー 0x{0}", + "Windows 의 처음 모양이 Total Commander 로 바뀌었습니다 (목록 · \"..\" · 아래 F3~F8 단추 줄). 지금 쓰는 X-plore 모양을 바꿀까요? 환경 설정 > 파일 탐색기 > 모양에서 언제든 바꿀 수 있습니다.": "Windows の既定の見た目が Total Commander になりました (一覧 · \"..\" · 下の F3~F8 ボタン列)。今使っている X-plore の見た目を変えますか? 設定 > ファイルエクスプローラー > 見た目でいつでも変えられます。", "Windows 탐색기": "Windows エクスプローラー", "X-plore 스타일: 선택 표시 늘 보이기": "X-plore スタイル: 選択マークを常に表示", "X-plore 스타일: 옆으로 밀면 창 바꾸기": "X-plore スタイル: 横にスワイプでペイン切り替え", @@ -827,6 +828,7 @@ "규칙상 허용되지 않음": "ルールで許可されていません", "그 자리로": "その場所へ", "그 파일 하나만": "そのファイルだけ", + "그대로": "このまま", "그대로 두기": "そのままにする", "그래도 Chrome 엔진 쓰기…": "それでも Chrome エンジンを使う…", "그래도 맞추기 ({0}개 지움)": "それでも同期 ({0} 個を削除)", @@ -2884,6 +2886,7 @@ "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラーのタップ: 1 回で選択 → 1 回ですぐ開く。元に戻す: 環境設定 > ファイルエクスプローラー > タップ", "파일 탐색기 단축키": "ファイルエクスプローラーのショートカット", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "ファイルエクスプローラーの 2 画面配置: 画面の形に合わせる → 左右。元に戻す: ファイルエクスプローラーの [左右 ⇆ / 上下 ⇅] ボタンまたは画面配置", + "파일 탐색기 모양": "ファイルエクスプローラーの見た目", "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "ファイルエクスプローラー一覧の名前・サイズの文字のみ。アプリの画面サイズ・端末の文字サイズと掛け合わされます", "파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "ファイルエクスプローラー一覧の行の高さ。狭くすると指でタップしにくい場合があります", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "ファイルエクスプローラー上部の ⋮ メニューからも変えられます (左右 · 上下 · ボタン列の位置 · ボタン構成)", @@ -2918,6 +2921,7 @@ "페이지 열기": "ページを開く", "페이지 지우기": "ページを削除", "페이지를 바꾼 것을 저장할까요?": "ページの変更を保存しますか?", + "편집": "編集", "편집 목록 {0}개": "編集リスト {0}個", "편집 목록에 있음": "編集リストにあり", "편집 목록에 추가": "編集リストに追加", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 64714fd..29662fe 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -423,6 +423,7 @@ "Windows 에서 이 앱의 알림이 꺼져 있어 보이지 않습니다": "Windows 中此应用的通知已关闭,因此不会显示", "Windows 에서만 쓸 수 있습니다. 이 기기에서는 문서를 다른 앱으로 엽니다.": "仅限 Windows。在本设备上会用其他应用打开文档。", "Windows 오류 0x{0}": "Windows 错误 0x{0}", + "Windows 의 처음 모양이 Total Commander 로 바뀌었습니다 (목록 · \"..\" · 아래 F3~F8 단추 줄). 지금 쓰는 X-plore 모양을 바꿀까요? 환경 설정 > 파일 탐색기 > 모양에서 언제든 바꿀 수 있습니다.": "Windows 的默认外观已改为 Total Commander (列表 · \"..\" · 底部 F3~F8 按钮栏)。要更改当前使用的 X-plore 外观吗? 可随时在 设置 > 文件浏览器 > 外观 中更改。", "Windows 탐색기": "Windows 资源管理器", "X-plore 스타일: 선택 표시 늘 보이기": "X-plore 样式: 始终显示选择标记", "X-plore 스타일: 옆으로 밀면 창 바꾸기": "X-plore 样式: 左右滑动切换窗格", @@ -827,6 +828,7 @@ "규칙상 허용되지 않음": "规则不允许", "그 자리로": "转到所在位置", "그 파일 하나만": "仅该文件", + "그대로": "保持不变", "그대로 두기": "保留", "그래도 Chrome 엔진 쓰기…": "仍然使用 Chrome 引擎…", "그래도 맞추기 ({0}개 지움)": "仍然同步 (删除 {0} 个)", @@ -2884,6 +2886,7 @@ "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器点击: 单击选择 → 单击直接打开。恢复: 设置 > 文件浏览器 > 点击", "파일 탐색기 단축키": "文件管理器快捷键", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "文件浏览器双窗格布局: 跟随屏幕形状 → 左右。恢复: 文件浏览器中的 [左右 ⇆ / 上下 ⇅] 按钮或窗格布局", + "파일 탐색기 모양": "文件浏览器外观", "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "仅文件管理器列表中的名称和大小文字。会与应用界面大小及设备文字大小相乘", "파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "文件管理器列表的行高。紧凑时可能难以用手指点按", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "也可以在文件浏览器顶部的 ⋮ 菜单中更改 (左右 · 上下 · 按钮栏位置 · 按钮配置)", @@ -2918,6 +2921,7 @@ "페이지 열기": "打开页面", "페이지 지우기": "删除页面", "페이지를 바꾼 것을 저장할까요?": "保存页面的更改吗?", + "편집": "编辑", "편집 목록 {0}개": "编辑列表 {0} 个", "편집 목록에 있음": "已在编辑列表中", "편집 목록에 추가": "加入编辑列表", diff --git a/lib/app/settings/explorer_settings.dart b/lib/app/settings/explorer_settings.dart index 7208613..6b11f38 100644 --- a/lib/app/settings/explorer_settings.dart +++ b/lib/app/settings/explorer_settings.dart @@ -45,9 +45,15 @@ class ExplorerSettings implements SettingsGroup { /// 길게 누르기 · 오른쪽 클릭은 늘 기능 메뉴. String click = 'select'; - /// 모양: 'xplore' (기본) · 'windows' Windows 탐색기 · 'totalcmd' Total Commander. - /// 규칙 1 의 Windows 처음 값 (Total Commander) 과 저장된 값 묻기 (결정 1) 는 E6 (TC 모양) 과 함께 (관리자 결정 10/22) - String style = 'xplore'; + /// 모양: 'xplore' X-plore · 'windows' Windows 탐색기 · 'totalcmd' Total Commander. + /// 처음 값은 기기마다 (규칙 1 · E6): Windows = Total Commander, Android = X-plore + String style = defaultStyle; + + static String get defaultStyle => Platform.isWindows ? 'totalcmd' : 'xplore'; + + /// E6 (결정 10/22 ①): Windows 에서 예전 판이 저장한 'xplore' 를 한 번 물을지 (처음 값이었는지 사용자가 고른 것인지 알 수 없어서). + /// 읽을 때 정하고 저장하지 않는다 - 물었다는 표시 (explorerStyleV2) 는 늘 저장한다 + bool styleAskPending = false; /// 두 창 배치: 'side' 좌우 (기본, 118 - 폰 세로에서도) · 'stacked' 위아래 · 'auto' 화면 모양 따라 (가로로 넓으면 좌우, 세로로 길면 위아래) String orientation = 'side'; @@ -123,6 +129,7 @@ class ExplorerSettings implements SettingsGroup { j['androidTrashDays'] = androidTrashDays; j['explorerClick'] = click; j['explorerStyle'] = style; + j['explorerStyleV2'] = true; j['explorerOrientation'] = orientation; j['explorerToolbar'] = toolbar; j['explorerButtons'] = buttons; @@ -174,7 +181,13 @@ class ExplorerSettings implements SettingsGroup { }; // 규칙 2: 두 기기 모두 한 번 = 선택. 규칙 이전 Android 의 'open' 은 예전 처음 값이라 한 번 'select' 로 click = j['explorerClick'] == 'open' && (j['explorerRulesV1'] == true || !Platform.isAndroid) ? 'open' : 'select'; - style = const ['windows', 'totalcmd'].contains(j['explorerStyle']) ? j['explorerStyle'] as String : 'xplore'; + // 예전 판의 파일에 모양이 없으면 그때의 처음 값 (X-plore) - 새로 설치 (빈 설정) 만 기기의 처음 값 + style = const ['xplore', 'windows', 'totalcmd'].contains(j['explorerStyle']) + ? j['explorerStyle'] as String + : j.isEmpty + ? defaultStyle + : 'xplore'; + styleAskPending = Platform.isWindows && j.isNotEmpty && j['explorerStyleV2'] != true && style == 'xplore'; // 118: 예전 기본 '화면 모양 따라' 는 한 번 좌우로 옮긴다 (사용자가 고른 위아래는 그대로) orientation = j['explorerOrientation'] == 'stacked' ? 'stacked' diff --git a/lib/ui/explorer/style_ask.dart b/lib/ui/explorer/style_ask.dart new file mode 100644 index 0000000..a7536da --- /dev/null +++ b/lib/ui/explorer/style_ask.dart @@ -0,0 +1,33 @@ +/// 약속 (E6, 결정 10/22 ①): Windows 에서 예전 판이 저장한 X-plore 모양을 한 번만 묻는다. +/// [ExplorerSettings.styleAskPending] 일 때만 창을 띄우고, [바꾸기] 면 Total Commander, [그대로] 면 X-plore 를 둔다. +/// 어느 쪽이든 다시 묻지 않는다 (물었다는 표시는 설정 저장에 늘 들어감). 창 밖을 눌러 닫지 못한다 (답을 골라야 함). +library; + +import 'package:flutter/material.dart'; + +import '../../app/app_controller.dart'; +import '../../l10n/tr.dart'; + +/// 물었으면 true (답과 상관없이). 물을 것이 없으면 false +Future askExplorerStyleOnce(BuildContext context, AppController c) async { + if (!c.settings.explorer.styleAskPending) return false; + c.settings.explorer.styleAskPending = false; // 창을 띄우는 동안 다시 부르면 또 띄우지 않게 + final change = await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => AlertDialog( + title: Text(tr('파일 탐색기 모양')), + content: Text(tr('Windows 의 처음 모양이 Total Commander 로 바뀌었습니다 (목록 · ".." · 아래 F3~F8 단추 줄). ' + '지금 쓰는 X-plore 모양을 바꿀까요? 환경 설정 > 파일 탐색기 > 모양에서 언제든 바꿀 수 있습니다.')), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('그대로'))), + FilledButton(onPressed: () => Navigator.pop(ctx, true), child: Text(tr('바꾸기'))), + ], + ), + ); + await c.updateSettings((x) { + x.explorer.styleAskPending = false; + if (change == true) x.explorer.style = 'totalcmd'; + }); + return true; +} diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index 672927e..2dca7f5 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -17,6 +17,7 @@ import '../core/thumb_cache.dart' show thumbMemoryForgetters; import 'explorer_drag.dart'; import '../core/wifi_share.dart' show WifiShare; import '../app/volumes.dart'; +import 'explorer/style_ask.dart'; import 'explorer/explorer_controller.dart'; import 'explorer/explorer_host.dart'; import 'explorer/explorer_pane.dart'; @@ -152,6 +153,8 @@ class _ExplorerPageState extends State implements ExplorerHost, Ex _kindSeen = c.settings.explorer.sortByKind; c.addListener(_onSettings); _init(); + // E6: 예전 판이 저장한 X-plore 모양이면 한 번 묻는다 (Windows - Rsync 화면은 묻지 않음) + if (!widget.rsync) afterFrame(() => mounted ? unawaited(askExplorerStyleOnce(context, c)) : null); } /// 232: 앱으로 돌아오면 (다른 앱 · 탐색기에서 파일을 바꾸고 오면) 보이는 폴더를 다시 읽는다 diff --git a/lib/ui/settings/files_section.dart b/lib/ui/settings/files_section.dart index 9743126..3bf7ba8 100644 --- a/lib/ui/settings/files_section.dart +++ b/lib/ui/settings/files_section.dart @@ -205,7 +205,10 @@ class _FilesSettingsSectionState extends State { for (final st in ExplorerStyle.values) DropdownMenuItem(value: st.name, child: Text(tr(st.label))), ], - onChanged: (v) => c.updateSettings((x) => x.explorer.style = v!), + // 직접 골랐으면 예전 모양을 묻지 않는다 (E6) + onChanged: (v) => c.updateSettings((x) => x.explorer + ..style = v! + ..styleAskPending = false), ), ), SettingTile( diff --git a/test/dismiss_test.dart b/test/dismiss_test.dart index c729e77..a814d4c 100644 --- a/test/dismiss_test.dart +++ b/test/dismiss_test.dart @@ -40,6 +40,7 @@ void main() { setUp(() { tmp = Directory.systemTemp.createTempSync('jj_dismiss_'); c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander }); tearDown(() => tmp.deleteSync(recursive: true)); diff --git a/test/explorer_conflict_test.dart b/test/explorer_conflict_test.dart index 552adcd..b8a3bd3 100644 --- a/test/explorer_conflict_test.dart +++ b/test/explorer_conflict_test.dart @@ -40,6 +40,7 @@ void main() { File(p.join(left, 'doc.txt')).setLastModifiedSync(DateTime(2026, 10, 9, 12)); File(p.join(right, 'doc.txt')).setLastModifiedSync(DateTime(2026, 10, 1, 8)); c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings.explorer.paths = [left, right]; final root = p.rootPrefix(tmp.path); device.volumes = () => [(root, root.replaceAll(RegExp(r'[\\/]+$'), ''))]; diff --git a/test/explorer_drives_test.dart b/test/explorer_drives_test.dart index 88c99c6..cb06fe8 100644 --- a/test/explorer_drives_test.dart +++ b/test/explorer_drives_test.dart @@ -29,6 +29,7 @@ void main() { ..writeAsStringSync('x'); Directory(p.join(tmp.path, 'right')).createSync(); c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings.explorer.paths = [p.join(tmp.path, 'left'), p.join(tmp.path, 'right')]; stuck = Completer(); dead = {r'R:\'}; diff --git a/test/explorer_pane_view_contract_test.dart b/test/explorer_pane_view_contract_test.dart index 863f4cb..b8ac570 100644 --- a/test/explorer_pane_view_contract_test.dart +++ b/test/explorer_pane_view_contract_test.dart @@ -51,6 +51,7 @@ void main() { Future<(ExplorerController, GlobalKey<_HostState>)> open(WidgetTester t, {double width = 800}) async { final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander final key = GlobalKey<_HostState>(); await t.binding.setSurfaceSize(Size(width, 600)); addTearDown(() => t.binding.setSurfaceSize(null)); diff --git a/test/explorer_parallel_test.dart b/test/explorer_parallel_test.dart index e5c6f12..cb41f54 100644 --- a/test/explorer_parallel_test.dart +++ b/test/explorer_parallel_test.dart @@ -28,6 +28,7 @@ void main() { File(p.join(left, 'doc.txt')).writeAsStringSync('hello'); Directory(right).createSync(); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.paths = [left, right] ..copy.bandwidthKBps = 512; // 큰 파일이 6초쯤 걸리게 @@ -68,6 +69,7 @@ void main() { Directory(p.join(r, 'rsub')).createSync(recursive: true); device.volumes = () => [(tmp.path, 'T:')]; final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.orientation = 'side' ..copy.rsyncPaths = [l, r]; @@ -108,6 +110,7 @@ void main() { Directory(p.join(r, 'rsub')).createSync(recursive: true); device.volumes = () => [(tmp.path, 'T:')]; final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.orientation = 'side' ..copy.rsyncPaths = [l, r]; @@ -144,6 +147,7 @@ void main() { Directory(r).createSync(recursive: true); device.volumes = () => [(tmp.path, 'T:')]; final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.orientation = 'side' ..copy.rsyncPaths = [l, r]; @@ -166,6 +170,7 @@ void main() { Directory(p.join(sd, 'R')).createSync(recursive: true); device.volumes = () => [(inner, '내장 저장소'), (sd, 'SD 카드')]; final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.orientation = 'side' ..copy.rsyncPaths = [p.join(inner, 'L'), p.join(sd, 'R')]; @@ -192,6 +197,7 @@ void main() { Directory(p.join(l, 'sub')).createSync(recursive: true); Directory(p.join(r, 'rsub')).createSync(recursive: true); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.layout = 'single' ..copy.rsyncPaths = [l, r]; @@ -225,6 +231,7 @@ void main() { ..writeAsStringSync('d'); File(p.join(l, 'top.txt')).writeAsStringSync('t'); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.layout = 'single' ..explorer.paths = [l, l]; @@ -258,6 +265,7 @@ void main() { ..writeAsStringSync('$i'); } final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.layout = 'single' ..explorer.paths = [dl, dl]; @@ -292,6 +300,7 @@ void main() { } File(p.join(l, 'zz_file.txt')).writeAsStringSync('t'); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.layout = 'dual' ..explorer.orientation = 'stacked' @@ -333,6 +342,7 @@ void main() { ..createSync(recursive: true) ..writeAsStringSync('a'); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.layout = 'single' ..explorer.paths = [l, l]; diff --git a/test/explorer_steady_test.dart b/test/explorer_steady_test.dart index cecbd0e..a5e92b4 100644 --- a/test/explorer_steady_test.dart +++ b/test/explorer_steady_test.dart @@ -36,6 +36,7 @@ void main() { // 저장 장치는 시험 폴더 하나 (실제 Temp 폴더의 많은 항목 사이에서 찾지 않게) device.volumes = () => [(tmp.path, 'T:')]; c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService(), shell: NoopShell())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..explorer.paths = [left, right] ..copy.methodFile = 'builtin' diff --git a/test/explorer_style_contract_test.dart b/test/explorer_style_contract_test.dart new file mode 100644 index 0000000..3a62a80 --- /dev/null +++ b/test/explorer_style_contract_test.dart @@ -0,0 +1,75 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:jj_mkvmaker/app/app_controller.dart'; +import 'package:jj_mkvmaker/app/settings.dart'; +import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart'; +import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart'; +import 'package:jj_mkvmaker/services/platform_services.dart'; +import 'package:jj_mkvmaker/ui/explorer/style_ask.dart'; + +/// E6 약속: 파일 탐색기 모양의 처음 값은 기기마다 (Windows = Total Commander) · 예전 판이 저장한 X-plore 는 한 번만 묻는다 +void main() { + final win = Platform.isWindows; + + test('처음 값: 새로 설치 (빈 설정) 는 기기의 처음 값 - Windows 는 Total Commander, 묻지 않음', () { + final s = AppSettings.fromJson({}); + expect(s.explorer.style, win ? 'totalcmd' : 'xplore'); + expect(s.explorer.styleAskPending, isFalse); + expect(AppSettings().explorer.style, ExplorerSettings.defaultStyle); + }); + + test('저장된 값: 예전 판의 xplore (또는 모양 키가 없던 예전 파일) 는 Windows 에서 한 번 묻고, 고른 windows · totalcmd 는 묻지 않음', () { + final old = AppSettings.fromJson({'explorerStyle': 'xplore', 'explorerLayout': 'dual'}); + expect([old.explorer.style, old.explorer.styleAskPending], ['xplore', win]); + final noKey = AppSettings.fromJson({'explorerLayout': 'dual'}); + expect([noKey.explorer.style, noKey.explorer.styleAskPending], ['xplore', win], reason: '그때의 처음 값 (X-plore) 을 덮지 않음'); + for (final v in ['windows', 'totalcmd']) { + final s = AppSettings.fromJson({'explorerStyle': v, 'explorerLayout': 'dual'}); + expect([s.explorer.style, s.explorer.styleAskPending], [v, false]); + } + final again = AppSettings.fromJson(old.toJson()); + expect([again.explorer.style, again.explorer.styleAskPending], ['xplore', false], reason: '한 번 저장하면 (물었다는 표시) 다시 묻지 않음'); + }); + + group('묻는 창 (askExplorerStyleOnce)', () { + AppController ctl(Map saved) => + AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())) + ..settings = AppSettings.fromJson(saved); + + Future ask(WidgetTester t, AppController c, {String? pick}) async { + final key = GlobalKey(); + await t.pumpWidget(MaterialApp(navigatorKey: key, home: const Scaffold(body: SizedBox()))); + final f = askExplorerStyleOnce(key.currentContext!, c); + await t.pumpAndSettle(); + if (pick != null) { + expect(find.text('파일 탐색기 모양'), findsOneWidget); + await t.tapAt(const Offset(5, 5)); // 창 밖을 눌러도 닫히지 않음 + await t.pumpAndSettle(); + expect(find.text('파일 탐색기 모양'), findsOneWidget); + await t.tap(find.text(pick)); + await t.pumpAndSettle(); + } + return f; + } + + testWidgets('[그대로] 면 X-plore 를 두고 다시 묻지 않음', (t) async { + final c = ctl({'explorerStyle': 'xplore', 'explorerLayout': 'dual'})..settings.explorer.styleAskPending = true; + expect(await ask(t, c, pick: '그대로'), isTrue); + expect([c.settings.explorer.style, c.settings.explorer.styleAskPending], ['xplore', false]); + expect(await ask(t, c), isFalse); + expect(find.text('파일 탐색기 모양'), findsNothing, reason: '두 번째는 창을 띄우지 않음'); + expect(c.settings.toJson()['explorerStyleV2'], isTrue); + }); + + testWidgets('[바꾸기] 면 Total Commander · 물을 것이 없으면 창 없음', (t) async { + final c = ctl({'explorerStyle': 'xplore', 'explorerLayout': 'dual'})..settings.explorer.styleAskPending = true; + expect(await ask(t, c, pick: '바꾸기'), isTrue); + expect([c.settings.explorer.style, c.settings.explorer.styleAskPending], ['totalcmd', false]); + final picked = ctl({'explorerStyle': 'windows', 'explorerLayout': 'dual'}); + expect(await ask(t, picked), isFalse); + expect(picked.settings.explorer.style, 'windows'); + }); + }); +} diff --git a/test/explorer_webdav_test.dart b/test/explorer_webdav_test.dart index ab523be..4175c38 100644 --- a/test/explorer_webdav_test.dart +++ b/test/explorer_webdav_test.dart @@ -56,6 +56,7 @@ void main() { device.volumes = () => [(root, root.replaceAll(RegExp(r'[\\/]+$'), ''))]; c = AppController(PlatformServices( mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService(), shell: shell)); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander }); tearDown(() async { DavRegistry.configure([]); diff --git a/test/fixtures/settings_golden_default.json b/test/fixtures/settings_golden_default.json index d58b9a3..eef2628 100644 --- a/test/fixtures/settings_golden_default.json +++ b/test/fixtures/settings_golden_default.json @@ -114,7 +114,8 @@ "explorerSortByKind": false, "explorerSortDesc": false, "explorerSortPerFolder": false, - "explorerStyle": "xplore", + "explorerStyle": "totalcmd", + "explorerStyleV2": true, "explorerSwipePanes": false, "explorerTextScale": 100, "explorerToolbar": "middle", diff --git a/test/fixtures/settings_golden_legacy_out.json b/test/fixtures/settings_golden_legacy_out.json index 90b6e02..6bc112d 100644 --- a/test/fixtures/settings_golden_legacy_out.json +++ b/test/fixtures/settings_golden_legacy_out.json @@ -126,6 +126,7 @@ "explorerSortDesc": false, "explorerSortPerFolder": false, "explorerStyle": "xplore", + "explorerStyleV2": true, "explorerSwipePanes": false, "explorerTextScale": 100, "explorerToolbar": "middle", diff --git a/test/fixtures/settings_golden_out.json b/test/fixtures/settings_golden_out.json index a81bd14..879cc2a 100644 --- a/test/fixtures/settings_golden_out.json +++ b/test/fixtures/settings_golden_out.json @@ -120,6 +120,7 @@ "explorerSortDesc": true, "explorerSortPerFolder": true, "explorerStyle": "xplore", + "explorerStyleV2": true, "explorerSwipePanes": true, "explorerTextScale": 107, "explorerToolbar": "middle", diff --git a/test/root_test.dart b/test/root_test.dart index bbe069e..ac35322 100644 --- a/test/root_test.dart +++ b/test/root_test.dart @@ -128,6 +128,7 @@ void main() { AppController ctl({bool on = true}) { final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..tools.rootEnabled = on ..explorer.paths = [tmp.path, tmp.path] diff --git a/test/rsync_safety_test.dart b/test/rsync_safety_test.dart index be9cb9e..b6235d9 100644 --- a/test/rsync_safety_test.dart +++ b/test/rsync_safety_test.dart @@ -36,6 +36,7 @@ void main() { await server.start(); final s = DavServer(id: 'nas', name: '집 NAS', url: server.url, user: 'user', password: 'pass'); c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..remote.webdavServers = [s] ..explorer.layout = 'single' diff --git a/test/sftp_explorer_test.dart b/test/sftp_explorer_test.dart index a52b096..7816d20 100644 --- a/test/sftp_explorer_test.dart +++ b/test/sftp_explorer_test.dart @@ -50,6 +50,7 @@ void main() { server = s; final local = Directory(p.join(tmp.path, 'local'))..createSync(); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander await tester.runAsync(() => c.updateSettings((x) => x ..remote.sftpServers = [SftpServer(id: 's1', name: 'NAS', host: '127.0.0.1', port: s!.port, user: 'jj', password: 'pw')] ..explorer.paths = ['sftp://s1/', local.path])); diff --git a/test/shizuku_test.dart b/test/shizuku_test.dart index 672f2a4..f0ca03c 100644 --- a/test/shizuku_test.dart +++ b/test/shizuku_test.dart @@ -111,6 +111,7 @@ void main() { AppController ctl({bool on = true}) { final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings ..tools.shizukuEnabled = on ..explorer.paths = [tmp.path, tmp.path] diff --git a/test/support/explorer_harness.dart b/test/support/explorer_harness.dart index b35c7da..0fc15a9 100644 --- a/test/support/explorer_harness.dart +++ b/test/support/explorer_harness.dart @@ -66,6 +66,7 @@ void useExplorerFixture() { _c = AppController(PlatformServices( mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService(), shell: shell)); c.settings.explorer.paths = [left, right]; + c.settings.explorer.style = 'xplore'; // E6: 이 시험들은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander }); // Windows: 방금 닫은 파일 (65 의 잠금 시험) 이나 색인 · 백신이 잠깐 잡고 있으면 지우기가 실패한다 - 될 때까지 기다린다 tearDown(() async { diff --git a/test/sync_problem_test.dart b/test/sync_problem_test.dart index fbcda4c..b477aa3 100644 --- a/test/sync_problem_test.dart +++ b/test/sync_problem_test.dart @@ -33,6 +33,7 @@ void main() { ..createSync(recursive: true) ..writeAsStringSync('2'); c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander }); tearDown(() => tmp.deleteSync(recursive: true)); diff --git a/test/vault_page_test.dart b/test/vault_page_test.dart index d43f02b..2def42b 100644 --- a/test/vault_page_test.dart +++ b/test/vault_page_test.dart @@ -48,6 +48,7 @@ void main() { AppController ctl() { final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.explorer.style = 'xplore'; // E6: 이 시험은 X-plore 모양 (트리) 을 본다 - Windows 처음 값은 Total Commander c.settings.vault.enabled = true; return c; }