WIP X-plore a: 한 창일 때 복사 · 이동 갈 곳 - 설정 "숨은 다른 창으로" 면 위에 갈 곳 줄 · 바로 복사 · 이동 (처음 값은 지금대로) (검토 대기)

사용자 "파일 탐색기는 X-plore 와 최대한 같게" (관리자 3a, 15 비교표 P1 a).
- X-plore 는 대상 창이 숨어도 파란 화살표 + 대상 이름을 보이고 그리로 복사 · 이동한다. 우리 한 창은 담아 두었다가 붙여넣기.
- 설정 "한 창일 때 복사 · 이동": 담아 두고 붙여넣기 (처음 값, 지금 그대로) / 숨은 다른 창으로.
  숨은 다른 창으로면 맨 위에 "→ 복사 · 이동 갈 곳: 폴더" 와 [지금 폴더로] · [다른 폴더], [복사] · [이동] 은 그 폴더로 (확인 창 그대로).
- 내 판단: 동작이 바뀌는 것이라 처음 값은 지금대로 (관리자 결정 - 예전과 부딪히는 것은 설정으로).
- 시험: explorer_test "X-plore a".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xplore_p3
RyuJongJin 2026-10-17 23:03:26 +09:00
parent 13c8e5f5bf
commit 818b4a0615
7 changed files with 111 additions and 2 deletions

View File

@ -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",

View File

@ -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 ペイン時のコピー・移動",
"한국어": "韓国語",
"한글": "ハングル",
"할 일": "内容",

View File

@ -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 @@
"한 장에 두 쪽이 붙은 그림을 반씩 넘겨 봅니다 (읽는 방향의 쪽부터)": "将一张图片中并排的两页分成两半逐页浏览 (从阅读方向的那页开始)",
"한 쪽 맞추기": "适应整页",
"한 창": "单窗格",
"한 창일 때 복사 · 이동": "单窗格时的复制 · 移动",
"한국어": "韩语",
"한글": "韩文",
"할 일": "任务",

View File

@ -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

View File

@ -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<ExplorerPage> {
// ───────── 기능 ─────────
_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<String> _targets(_Pane pane) {
@ -3462,7 +3501,10 @@ class _ExplorerPageState extends State<ExplorerPage> {
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,
],
];

View File

@ -838,6 +838,19 @@ class _SettingsPageState extends State<SettingsPage> {
onChanged: (v) => c.updateSettings((x) => x.explorerLayout = v!),
),
),
// X-plore 비교 a: 한 창일 때 복사 · 이동이 갈 곳
SettingTile(
title: Text(tr('한 창일 때 복사 · 이동')),
subtitle: Text(tr('숨은 다른 창으로: X-plore 처럼 위에 "갈 곳" 을 늘 보이고 그 폴더로 바로 복사 · 이동합니다')),
trailing: DropdownButton<String>(
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 화면 위쪽 탭)

View File

@ -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'