diff --git a/assets/l10n/en.json b/assets/l10n/en.json index e92ab26..16f23c7 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -844,6 +844,7 @@ "다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다": "When you drop onto the other pane. Either way, the copy/move confirmation (same names etc.) still appears", "다른 창으로 복사": "Copy to other pane", "다른 창으로 이동": "Move to other pane", + "다른 폴더": "Other folder", "다른 폴더로 이동": "Move to another folder", "다른 프로그램이 쓰고 있거나 권한이 없을 수 있습니다. 나머지는 지웠습니다.": "Another program may be using them, or you may lack permission. The rest were deleted.", "다른 프로그램이 쓰고 있습니다": "Another program is using it", @@ -899,6 +900,7 @@ "단축키 해제": "Unregister hotkey", "닫기": "Close", "닫힘 (실행 안 함)": "Closed (not run)", + "담아 두고 붙여넣기 (기본)": "Hold and paste (default)", "답이 없습니다": "No answer", "대기": "Waiting", "대기 {0}개:": "{0} waiting:", @@ -1252,6 +1254,8 @@ "복사 · 이동 {0}개": "{0} copy · move", "복사 · 이동 {0}개 · {1}%": "Copy · move: {0} · {1}%", "복사 · 이동 {0}개가 아직 끝나지 않았습니다. 끝내면 멈추고, 복사하던 파일은 완성되지 않은 채로 남지 않게 지웁니다.": "{0} copy/move tasks have not finished. Exiting stops them, and the file being copied is removed so no incomplete file is left.", + "복사 · 이동 갈 곳": "Copy / move destination", + "복사 · 이동 갈 곳: {0}": "Copy / move to: {0}", "복사 · 이동 끝": "Copy · move finished", "복사 · 이동 멈춤 - 눌러서 보기": "Copy · move stopped - tap to see", "복사 · 이동 중입니다": "Copying · moving in progress", @@ -1433,6 +1437,8 @@ "숨긴 항목 (. 으로 시작) 보이기": "Show hidden items (starting with .)", "숨긴 항목 숨기기": "Hide hidden items", "숨김": "Hidden", + "숨은 다른 창으로": "To the hidden other pane", + "숨은 다른 창으로: X-plore 처럼 위에 \"갈 곳\" 을 늘 보이고 그 폴더로 바로 복사 · 이동합니다": "To the hidden other pane: like X-plore, the destination is always shown at the top and items are copied or moved straight there", "숨은 항목 표시": "Show hidden items", "숨은 항목을 감춥니다.": "Hidden items are hidden.", "숨은 항목을 보입니다.": "Hidden items are shown.", @@ -2145,6 +2151,7 @@ "지금 짝을 즐겨찾기에": "Add the current pair to favorites", "지금 폴더": "Current folder", "지금 폴더 추가": "Add current folder", + "지금 폴더로": "Use this folder", "지금 폴더를 즐겨찾기에": "Add the current folder to favorites", "지금 폴더에서 이름에 든 글자로 좁히기 (* · ? 패턴도 됨)": "Narrow this folder by text in the name (* and ? patterns work)", "지금 확인": "Check now", @@ -2447,6 +2454,7 @@ "한 장에 두 쪽이 붙은 그림을 반씩 넘겨 봅니다 (읽는 방향의 쪽부터)": "Pages through images with two pages side by side one half at a time (starting from the reading direction)", "한 쪽 맞추기": "Fit page", "한 창": "One pane", + "한 창일 때 복사 · 이동": "Copy / move with one pane", "한국어": "Korean", "한글": "Hangul", "할 일": "Task", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index ed9d71b..50edc67 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -844,6 +844,7 @@ "다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다": "もう一方のウィンドウにドロップしたとき。どちらでもコピー・移動の確認 (同じ名前など) は表示されます", "다른 창으로 복사": "もう一方のペインへコピー", "다른 창으로 이동": "もう一方のペインへ移動", + "다른 폴더": "別のフォルダー", "다른 폴더로 이동": "他のフォルダーに移動", "다른 프로그램이 쓰고 있거나 권한이 없을 수 있습니다. 나머지는 지웠습니다.": "他のプログラムが使用中か、権限がない可能性があります。残りは削除しました。", "다른 프로그램이 쓰고 있습니다": "他のプログラムが使用中です", @@ -899,6 +900,7 @@ "단축키 해제": "ショートカット解除", "닫기": "閉じる", "닫힘 (실행 안 함)": "閉じた (実行しない)", + "담아 두고 붙여넣기 (기본)": "保持して貼り付け (既定)", "답이 없습니다": "応答がありません", "대기": "待機", "대기 {0}개:": "待機 {0}個:", @@ -1252,6 +1254,8 @@ "복사 · 이동 {0}개": "コピー・移動 {0} 件", "복사 · 이동 {0}개 · {1}%": "コピー・移動 {0} 件 · {1}%", "복사 · 이동 {0}개가 아직 끝나지 않았습니다. 끝내면 멈추고, 복사하던 파일은 완성되지 않은 채로 남지 않게 지웁니다.": "コピー・移動 {0} 件がまだ終わっていません。終了すると止まり、コピー中のファイルは未完成のまま残らないよう削除します。", + "복사 · 이동 갈 곳": "コピー・移動先", + "복사 · 이동 갈 곳: {0}": "コピー・移動先: {0}", "복사 · 이동 끝": "コピー・移動 完了", "복사 · 이동 멈춤 - 눌러서 보기": "コピー・移動が停止 - タップして表示", "복사 · 이동 중입니다": "コピー・移動中です", @@ -1433,6 +1437,8 @@ "숨긴 항목 (. 으로 시작) 보이기": "隠し項目 (. で始まる) を表示", "숨긴 항목 숨기기": "隠し項目を隠す", "숨김": "非表示", + "숨은 다른 창으로": "隠れた別ペインへ", + "숨은 다른 창으로: X-plore 처럼 위에 \"갈 곳\" 을 늘 보이고 그 폴더로 바로 복사 · 이동합니다": "隠れた別ペインへ: X-plore のように上部に「移動先」を常に表示し、そのフォルダーへ直接コピー・移動します", "숨은 항목 표시": "隠し項目を表示", "숨은 항목을 감춥니다.": "隠し項目を隠します。", "숨은 항목을 보입니다.": "隠し項目を表示します。", @@ -2145,6 +2151,7 @@ "지금 짝을 즐겨찾기에": "今のペアをお気に入りに", "지금 폴더": "現在のフォルダー", "지금 폴더 추가": "今のフォルダーを追加", + "지금 폴더로": "このフォルダーに", "지금 폴더를 즐겨찾기에": "今のフォルダーをお気に入りに", "지금 폴더에서 이름에 든 글자로 좁히기 (* · ? 패턴도 됨)": "このフォルダーを名前に含む文字で絞り込み (* · ? パターンも可)", "지금 확인": "今すぐ確認", @@ -2447,6 +2454,7 @@ "한 장에 두 쪽이 붙은 그림을 반씩 넘겨 봅니다 (읽는 방향의 쪽부터)": "1 枚に 2 ページが並んだ画像を半分ずつめくって表示します (読む方向のページから)", "한 쪽 맞추기": "1 ページに合わせる", "한 창": "1 ペイン", + "한 창일 때 복사 · 이동": "1 ペイン時のコピー・移動", "한국어": "韓国語", "한글": "ハングル", "할 일": "内容", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 0d736d4..3dabdc4 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -844,6 +844,7 @@ "다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다": "拖放到另一个窗格时。无论哪种,都会显示复制/移动确认 (同名等)", "다른 창으로 복사": "复制到另一窗格", "다른 창으로 이동": "移动到另一窗格", + "다른 폴더": "其他文件夹", "다른 폴더로 이동": "移动到其他文件夹", "다른 프로그램이 쓰고 있거나 권한이 없을 수 있습니다. 나머지는 지웠습니다.": "可能有其他程序正在使用或没有权限。其余已删除。", "다른 프로그램이 쓰고 있습니다": "其他程序正在使用", @@ -899,6 +900,7 @@ "단축키 해제": "注销快捷键", "닫기": "关闭", "닫힘 (실행 안 함)": "已关闭 (未执行)", + "담아 두고 붙여넣기 (기본)": "暂存后粘贴 (默认)", "답이 없습니다": "没有应答", "대기": "等待", "대기 {0}개:": "等待 {0} 个:", @@ -1252,6 +1254,8 @@ "복사 · 이동 {0}개": "复制 · 移动 {0} 个", "복사 · 이동 {0}개 · {1}%": "复制 · 移动 {0} 个 · {1}%", "복사 · 이동 {0}개가 아직 끝나지 않았습니다. 끝내면 멈추고, 복사하던 파일은 완성되지 않은 채로 남지 않게 지웁니다.": "{0} 个复制 · 移动尚未完成。退出会停止它们,并删除正在复制的文件,以免留下不完整的文件。", + "복사 · 이동 갈 곳": "复制 · 移动目标", + "복사 · 이동 갈 곳: {0}": "复制 · 移动到: {0}", "복사 · 이동 끝": "复制 · 移动 已完成", "복사 · 이동 멈춤 - 눌러서 보기": "复制 · 移动已停止 - 点按查看", "복사 · 이동 중입니다": "正在复制 · 移动", @@ -1433,6 +1437,8 @@ "숨긴 항목 (. 으로 시작) 보이기": "显示隐藏项目 (以 . 开头)", "숨긴 항목 숨기기": "隐藏隐藏项目", "숨김": "隐藏", + "숨은 다른 창으로": "到隐藏的另一窗格", + "숨은 다른 창으로: X-plore 처럼 위에 \"갈 곳\" 을 늘 보이고 그 폴더로 바로 복사 · 이동합니다": "到隐藏的另一窗格: 像 X-plore 一样在顶部始终显示\"目标\",并直接复制 · 移动到该文件夹", "숨은 항목 표시": "显示隐藏项目", "숨은 항목을 감춥니다.": "已隐藏隐藏项目。", "숨은 항목을 보입니다.": "已显示隐藏项目。", @@ -2145,6 +2151,7 @@ "지금 짝을 즐겨찾기에": "将当前一对加入收藏", "지금 폴더": "当前文件夹", "지금 폴더 추가": "添加当前文件夹", + "지금 폴더로": "使用当前文件夹", "지금 폴더를 즐겨찾기에": "将当前文件夹加入收藏", "지금 폴더에서 이름에 든 글자로 좁히기 (* · ? 패턴도 됨)": "按名称中的文字筛选当前文件夹 (也可用 * · ? 模式)", "지금 확인": "立即检查", @@ -2447,6 +2454,7 @@ "한 장에 두 쪽이 붙은 그림을 반씩 넘겨 봅니다 (읽는 방향의 쪽부터)": "将一张图片中并排的两页分成两半逐页浏览 (从阅读方向的那页开始)", "한 쪽 맞추기": "适应整页", "한 창": "单窗格", + "한 창일 때 복사 · 이동": "单窗格时的复制 · 移动", "한국어": "韩语", "한글": "韩文", "할 일": "任务", diff --git a/lib/app/settings.dart b/lib/app/settings.dart index 84a479a..fa3fa7f 100644 --- a/lib/app/settings.dart +++ b/lib/app/settings.dart @@ -394,6 +394,10 @@ class AppSettings { /// 창: 'dual' 두 창 (기본) · 'split' 왼쪽 폴더 트리 + 오른쪽 그 폴더의 파일 목록 · 'single' 한 창 String explorerLayout = 'auto'; + /// X-plore 비교 a: 한 창일 때 복사 · 이동 - 'clip' 담아 두었다가 붙여넣기 (지금 그대로, 처음 값) / + /// 'other' 숨은 다른 창의 폴더로 바로 (X-plore 처럼, 위에 "갈 곳" 줄). 예전 동작과 달라 처음 값은 그대로 (관리자 결정) + String explorerOnePaneCopy = 'clip'; + /// 누르기: 'select' 한 번 = 선택 · 두 번 = 열기 (기본) / 'open' 한 번 = 바로 열기. /// 길게 누르기 · 오른쪽 클릭은 늘 기능 메뉴. String explorerClick = Platform.isAndroid ? 'open' : 'select'; @@ -745,6 +749,7 @@ class AppSettings { 'torJavaScript': torJavaScript, 'torNoticeSeen': torNoticeSeen, 'explorerLayout': explorerLayout, + 'explorerOnePaneCopy': explorerOnePaneCopy, 'explorerClick': explorerClick, // 예전 기본값 (두 창 · 한 번 누르면 선택) 은 사용자가 고른 값이 아니므로 한 번 새 기본값으로 옮긴다 'explorerV2': true, @@ -954,6 +959,7 @@ class AppSettings { ..torJavaScript = j['torJavaScript'] != false ..torNoticeSeen = j['torNoticeSeen'] == true // 'auto' = 넓은 화면은 두 창, 좁은 화면 (폰 세로) 은 한 창. 예전 기본 'dual' 은 한 번 'auto' 로 + ..explorerOnePaneCopy = j['explorerOnePaneCopy'] == 'other' ? 'other' : 'clip' ..explorerLayout = const ['single', 'split', 'auto'].contains(j['explorerLayout']) ? j['explorerLayout'] as String : j['explorerLayout'] == 'dual' && j['explorerV2'] == true diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index b2ecd21..8d34998 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -30,6 +30,7 @@ import 'player_page.dart'; import 'rsync_setup.dart'; import 'theme.dart'; import 'file_error.dart'; +import 'folder_picker.dart' show pickFolderOrDav; import 'dav_external.dart'; import '../platform/windows/recycle_bin.dart'; import '../core/sync_preview.dart'; @@ -1627,7 +1628,45 @@ class _ExplorerPageState extends State { // ───────── 기능 ───────── _Pane get _pane => _panes[_active]; - _Pane? get _other => _dual ? _panes[1 - _active] : null; + _Pane? get _other => _dual || _hiddenTarget ? _panes[1 - _active] : null; + + /// X-plore 비교 a: 한 창인데 "숨은 다른 창으로" 복사 · 이동 (위에 갈 곳 줄) + bool get _hiddenTarget => !widget.rsync && !_dual && !_split && c.settings.explorerOnePaneCopy == 'other'; + + /// X-plore 비교 a: 한 창일 때 맨 위의 "→ 갈 곳" 줄 - 숨은 창의 폴더 · [지금 폴더로] · [다른 폴더] + Widget _targetBar() { + final t = _panes[1 - _active]; + return Material( + color: JjColors.accent.withValues(alpha: 0.10), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + child: Row(children: [ + const Icon(Icons.arrow_forward, size: 18, color: JjColors.accent), + const SizedBox(width: 6), + Expanded( + child: Tooltip( + message: _displayPath(t.current), + child: Text(trf('복사 · 이동 갈 곳: {0}', [_displayPath(t.current)]), + maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12.5)), + ), + ), + TextButton( + style: TextButton.styleFrom(visualDensity: VisualDensity.compact), + onPressed: () => _goTo(t, _pane.current), + child: Text(tr('지금 폴더로')), + ), + TextButton( + style: TextButton.styleFrom(visualDensity: VisualDensity.compact), + onPressed: () async { + final d = await pickFolderOrDav(context, tr('복사 · 이동 갈 곳'), t.current); + if (d != null && mounted) await _goTo(t, d); + }, + child: Text(tr('다른 폴더')), + ), + ]), + ), + ); + } /// 기능의 대상: 표시한 항목, 없으면 마지막으로 누른 항목 (저장 장치 맨 위는 빼고) List _targets(_Pane pane) { @@ -3462,7 +3501,10 @@ class _ExplorerPageState extends State { if (edge) bar, ] else ...[ if (middle) bar, - Expanded(child: keep(0, _paneView(0))), + Expanded( + child: _hiddenTarget + ? Column(children: [ListenableBuilder(listenable: _panes[1], builder: (_, _) => _targetBar()), Expanded(child: keep(0, _paneView(0)))]) + : keep(0, _paneView(0))), if (edge) bar, ], ]; diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index 461a448..00b4f2f 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -838,6 +838,19 @@ class _SettingsPageState extends State { onChanged: (v) => c.updateSettings((x) => x.explorerLayout = v!), ), ), + // X-plore 비교 a: 한 창일 때 복사 · 이동이 갈 곳 + SettingTile( + title: Text(tr('한 창일 때 복사 · 이동')), + subtitle: Text(tr('숨은 다른 창으로: X-plore 처럼 위에 "갈 곳" 을 늘 보이고 그 폴더로 바로 복사 · 이동합니다')), + trailing: DropdownButton( + value: s.explorerOnePaneCopy, + items: [ + DropdownMenuItem(value: 'clip', child: Text(tr('담아 두고 붙여넣기 (기본)'))), + DropdownMenuItem(value: 'other', child: Text(tr('숨은 다른 창으로'))), + ], + onChanged: (v) => c.updateSettings((x) => x.explorerOnePaneCopy = v!), + ), + ), // 복사 · 이동 방법 (현재 방식 · robocopy) · 속도 제한 CopySyncSettings(c: c), // WebDAV 서버 (탐색기 · Rsync 화면 위쪽 탭) diff --git a/test/explorer_test.dart b/test/explorer_test.dart index da0ed0e..2ab96f1 100644 --- a/test/explorer_test.dart +++ b/test/explorer_test.dart @@ -135,6 +135,30 @@ void main() { expect(File(p.join(left, 'doc.txt')).existsSync(), isTrue); }); + testWidgets('X-plore a: 한 창 + "숨은 다른 창으로" - 위에 갈 곳 줄 · [복사] 는 그 폴더로 · [지금 폴더로] 로 바꾸기 (처음 값은 담아 두기)', (tester) async { + expect(AppSettings().explorerOnePaneCopy, 'clip', reason: '처음 값은 지금 그대로'); + c.settings + ..explorerLayout = 'single' + ..explorerOnePaneCopy = 'other'; + await open(tester); + expect(find.textContaining('복사 · 이동 갈 곳'), findsOneWidget); + expect(find.textContaining(right), findsWidgets, reason: '숨은 창의 폴더'); + await act(tester, () => tester.tap(find.text('doc.txt'))); + await settle(tester, () => false, rounds: 15); + await act(tester, () => tester.tap(find.text('복사').first)); + await calm(tester); + await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '복사'))); + await settle(tester, () => File(p.join(right, 'doc.txt')).existsSync() && find.byType(LinearProgressIndicator).evaluate().isEmpty); + expect(File(p.join(right, 'doc.txt')).readAsStringSync(), 'hello'); + // [지금 폴더로] → 갈 곳이 지금 보는 폴더 (left) + await act(tester, () => tester.tap(find.text('지금 폴더로'))); + await settle(tester, () => c.settings.explorerPaths[1] == left, rounds: 50); + expect(c.settings.explorerPaths[1], left); + ScaffoldMessenger.of(tester.element(find.byType(ExplorerPage))).clearSnackBars(); + await calm(tester); + await tester.pump(const Duration(seconds: 2)); + }); + testWidgets('178 보충: 설정의 원본 지우기 옵션은 복사 창에서 빠진 까닭을 알린다 (이동 창에는 없음)', (tester) async { c.settings ..copyMethodFile = 'robocopy'