From 954b86b84cff685b97116a70abcc5a1e8385e665 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:25:29 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AA=A8=EB=8B=88=ED=84=B0=EB=A7=81=20[?= =?UTF-8?q?=EC=8B=A4=ED=96=89]=20=EC=9D=98=20=EC=9D=B4=EB=8F=99=EB=8F=84?= =?UTF-8?q?=20=EB=AF=B8=EB=A6=AC=20=EB=B3=B4=EA=B8=B0=20=C2=B7=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20(210)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 관리자 지시 (데이터): 모니터링 카드의 [실행] 은 따로 묻는 창이 없어 이동 (원본 지우기) 을 곧바로 했다. 이동이면 "원본을 지우는 작업입니다 (이동)" 창에 옮길 목록을 보이고, 목록이 나온 뒤에만 [실행]. 복사만은 지금처럼 바로. 탐색기 이동 · Rsync 화면 · [최종 정리] 는 이미 자기 확인 창이 있어 두 번 묻지 않는다. Co-Authored-By: Claude Opus 5.5 --- assets/l10n/en.json | 3 +++ assets/l10n/ja.json | 3 +++ assets/l10n/zh-Hans.json | 3 +++ lib/ui/monitor_page.dart | 5 +++-- lib/ui/sync_preview_view.dart | 24 ++++++++++++++++++------ test/dismiss_test.dart | 26 ++++++++++++++++++++++++++ 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 6372734..69d693e 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -1358,6 +1358,8 @@ "원본으로": "Back to the original", "원본을 읽을 수 없어 멈춤": "Stopped: can't read the source", "원본을 읽을 수 없어 멈췄습니다": "Stopped because the source can't be read", + "원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.": "This run removes source files, so it can run once the list of files to move is shown.", + "원본을 지우는 작업입니다 (이동)": "This task removes source files (move)", "원본의 {0}배": "{0}× original", "원본의 빈 폴더 정리 중…": "Cleaning up empty folders in the source…", "원본의 파일을 모두 대상으로 옮기고 (원본에서 지움), 원본에 남은 빈 폴더를 지웁니다.": "Moves all files from the source to the target (deleting them from the source), then deletes the empty folders left in the source.", @@ -1478,6 +1480,7 @@ "이동 폴더 (환경 설정에서 바꿀 수 있음)": "Move folder (can be changed in Settings)", "이동 폴더로 (확인 뒤, 기본)": "To the move folder (after confirming, default)", "이동: {0} → {1}{2}": "Moved: {0} → {1}{2}", + "이동: 대상에 옮긴 파일은 원본에서 지워집니다": "Move: files moved to the destination are removed from the source", "이동: 옮길 폴더를 골라 이동 버튼을 만듭니다 (환경 설정 > MKV 만들기 > 이동 버튼에서 여러 개 만들 수 있음)": "Move: choose a folder to create a move button (you can make several in Settings > Make MKV > Move buttons)", "이동: 이 동영상을 이동 폴더로 옮깁니다{0}": "Move: moves this video to the move folder{0}", "이동: 체크한 동영상 {0}개를 이동 폴더로 옮깁니다": "Move: moves the {0} checked videos to the move folder", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index d60b7f6..4c6dcf6 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -1358,6 +1358,8 @@ "원본으로": "元の画像で", "원본을 읽을 수 없어 멈춤": "元を読めないため停止", "원본을 읽을 수 없어 멈췄습니다": "元を読めないため停止しました", + "원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.": "元のファイルを削除する実行のため、移動する一覧が表示されてから実行できます。", + "원본을 지우는 작업입니다 (이동)": "元のファイルを削除する作業です (移動)", "원본의 {0}배": "元の {0}倍", "원본의 빈 폴더 정리 중…": "元の空フォルダーを整理中…", "원본의 파일을 모두 대상으로 옮기고 (원본에서 지움), 원본에 남은 빈 폴더를 지웁니다.": "元のファイルをすべて対象へ移し (元から削除)、元に残った空フォルダーを削除します。", @@ -1478,6 +1480,7 @@ "이동 폴더 (환경 설정에서 바꿀 수 있음)": "移動フォルダー(環境設定で変更できます)", "이동 폴더로 (확인 뒤, 기본)": "移動フォルダーへ (確認後、既定)", "이동: {0} → {1}{2}": "移動: {0} → {1}{2}", + "이동: 대상에 옮긴 파일은 원본에서 지워집니다": "移動: 移動先に移したファイルは元の場所から削除されます", "이동: 옮길 폴더를 골라 이동 버튼을 만듭니다 (환경 설정 > MKV 만들기 > 이동 버튼에서 여러 개 만들 수 있음)": "移動: 移動先フォルダーを選んで移動ボタンを作ります(環境設定 > MKV 作成 > 移動ボタンで複数作れます)", "이동: 이 동영상을 이동 폴더로 옮깁니다{0}": "移動: この動画を移動フォルダーに移します{0}", "이동: 체크한 동영상 {0}개를 이동 폴더로 옮깁니다": "移動: チェックした動画 {0}個を移動フォルダーに移します", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index bd42ed7..05858bb 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -1358,6 +1358,8 @@ "원본으로": "回到原图", "원본을 읽을 수 없어 멈춤": "无法读取源,已停止", "원본을 읽을 수 없어 멈췄습니다": "无法读取源,已停止", + "원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.": "此操作会删除源文件,需在显示要移动的列表后才能执行。", + "원본을 지우는 작업입니다 (이동)": "此任务会删除源文件(移动)", "원본의 {0}배": "原始的 {0} 倍", "원본의 빈 폴더 정리 중…": "正在清理源中的空文件夹…", "원본의 파일을 모두 대상으로 옮기고 (원본에서 지움), 원본에 남은 빈 폴더를 지웁니다.": "将源中的所有文件移到目标 (从源中删除),然后删除源中剩下的空文件夹。", @@ -1478,6 +1480,7 @@ "이동 폴더 (환경 설정에서 바꿀 수 있음)": "移动目标文件夹(可在设置中更改)", "이동 폴더로 (확인 뒤, 기본)": "移到移动文件夹 (确认后,默认)", "이동: {0} → {1}{2}": "已移动: {0} → {1}{2}", + "이동: 대상에 옮긴 파일은 원본에서 지워집니다": "移动:已移到目标的文件会从源中删除", "이동: 옮길 폴더를 골라 이동 버튼을 만듭니다 (환경 설정 > MKV 만들기 > 이동 버튼에서 여러 개 만들 수 있음)": "移动: 选择目标文件夹以创建移动按钮(可在 设置 > 制作 MKV > 移动按钮 中创建多个)", "이동: 이 동영상을 이동 폴더로 옮깁니다{0}": "移动: 将此视频移到移动目标文件夹{0}", "이동: 체크한 동영상 {0}개를 이동 폴더로 옮깁니다": "移动: 将勾选的 {0} 个视频移到移动目标文件夹", diff --git a/lib/ui/monitor_page.dart b/lib/ui/monitor_page.dart index 607cb3f..b78b63a 100644 --- a/lib/ui/monitor_page.dart +++ b/lib/ui/monitor_page.dart @@ -332,8 +332,9 @@ class _CopyCardState extends State<_CopyCard> { } Future _run(CopyTask t) async { - // 104: 지우기가 들어 있으면 Rsync 화면과 같은 미리 보기를 거친다 (그사이 대상에 생긴 파일을 묻지 않고 지우지 않게) - if (!await confirmDeletingRun(context, t) || !mounted) return; + // 104: 지우기가 들어 있으면 Rsync 화면과 같은 미리 보기를 거친다 (그사이 대상에 생긴 파일을 묻지 않고 지우지 않게). + // 210: 이동 (원본 지우기) 도 - 카드의 [실행] 은 따로 묻는 창이 없어 곧바로 원본을 지웠다. 복사만은 지금처럼 바로 + if (!await confirmDeletingRun(context, t, includeMove: true) || !mounted) return; widget.c.note(trf('rsync 시작 ({0}): {1} → {2}', [tr('모니터링의 [실행]'), t.sources.join(', '), t.dest])); String? exe; if (t.method == 'rsync') { diff --git a/lib/ui/sync_preview_view.dart b/lib/ui/sync_preview_view.dart index b2dd572..4bade58 100644 --- a/lib/ui/sync_preview_view.dart +++ b/lib/ui/sync_preview_view.dart @@ -167,8 +167,11 @@ bool taskDeletes(CopyTask t) { /// 104: 지우기가 들어 있는 작업은 어디서 실행하든 (모니터링 [실행] · 옵션 저장 뒤 실행 · 최종 정리) Rsync 화면과 같은 미리 보기를 거친다. /// 지울 목록이 나온 뒤에만 [실행] 이 켜진다. 지우기가 없으면 묻지 않고 true -Future confirmDeletingRun(BuildContext context, CopyTask t) async { - if (!taskDeletes(t)) return true; +/// [includeMove] (210): 이동 (원본 파일 지우기) 도 미리 보기 · 확인을 거친다 - 모니터링의 [실행] 처럼 따로 묻는 창이 없을 때 +Future confirmDeletingRun(BuildContext context, CopyTask t, {bool includeMove = false}) async { + final deletes = taskDeletes(t); + final moves = includeMove && t.move; + if (!deletes && !moves) return true; List? preview; Object? error; final update = optionsUpdate(t.options); @@ -181,7 +184,7 @@ Future confirmDeletingRun(BuildContext context, CopyTask t) async { return AlertDialog( scrollable: true, actionsOverflowDirection: VerticalDirection.down, // 110: [취소] 가 먼저 - title: Text(tr('지우기가 들어 있는 작업입니다')), + title: Text(deletes ? tr('지우기가 들어 있는 작업입니다') : tr('원본을 지우는 작업입니다 (이동)')), content: SizedBox( width: 560, child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -191,10 +194,17 @@ Future confirmDeletingRun(BuildContext context, CopyTask t) async { Text('${vDisplay(s)} → ${vDisplay(dstOf(s))}', style: const TextStyle(fontSize: 11, color: JjColors.textDim)), ], Text('${t.method} ${t.options}', style: const TextStyle(fontSize: 12, fontFamily: 'monospace', color: JjColors.textDim)), + // 210: 이동이면 실행 뒤 원본에서 지워진다는 것을 분명히 + if (t.move) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text(tr('이동: 대상에 옮긴 파일은 원본에서 지워집니다'), + style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + ), const SizedBox(height: 8), SyncPreviewView( compute: () async => [ - for (final s in t.sources) ...await previewSync(s, dstOf(s), toRight: true, delete: true, update: update), + for (final s in t.sources) ...await previewSync(s, dstOf(s), toRight: true, delete: deletes, update: update), ], onStart: () => set(() { preview = null; @@ -213,7 +223,9 @@ Future confirmDeletingRun(BuildContext context, CopyTask t) async { ? Text( error != null ? tr('지우기가 들어간 실행이라, 비교하지 못하면 실행할 수 없습니다. 원본 · 대상을 확인한 뒤 [다시 비교] 를 누르세요.') - : tr('지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.'), + : deletes + ? tr('지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.') + : tr('원본을 지우는 실행이라, 옮길 목록이 나온 뒤에 실행할 수 있습니다.'), style: const TextStyle(fontSize: 12, color: Colors.orangeAccent), ) : null, @@ -223,7 +235,7 @@ Future confirmDeletingRun(BuildContext context, CopyTask t) async { actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))), FilledButton( - style: dels > 0 ? FilledButton.styleFrom(backgroundColor: JjColors.danger) : null, + style: dels > 0 || moves ? FilledButton.styleFrom(backgroundColor: JjColors.danger) : null, onPressed: preview == null ? null : () => Navigator.pop(ctx, true), child: Text(tr('실행')), // 110: 글자를 바꾸지 않는다 (버튼 자리가 움직이지 않게) ), diff --git a/test/dismiss_test.dart b/test/dismiss_test.dart index ed0b149..0d0b486 100644 --- a/test/dismiss_test.dart +++ b/test/dismiss_test.dart @@ -167,6 +167,32 @@ void main() { expect(find.text('아직 실행 안 함'), findsOneWidget); }); + testWidgets('210: 모니터링 [실행] 의 이동 (원본 지우기) 도 미리 보기 · 확인을 거친다 (취소하면 원본 그대로) · 복사는 바로', (tester) async { + final src = p.join(tmp.path, 'mv'), dst = p.join(tmp.path, 'mvd'); + final a = File(p.join(src, 'a.txt')) + ..createSync(recursive: true) + ..writeAsStringSync('a'); + Directory(dst).createSync(); + c.settings.copyTasks = [CopyTask(id: '1', sources: [src], dest: dst, contents: true, move: true, method: 'builtin')]; + tester.view.physicalSize = const Size(1500, 1000); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + await tester.pumpWidget(MaterialApp(home: MonitorPage(c: c))); + await tester.pump(); + await tester.tap(find.widgetWithText(FilledButton, '실행').first); + await tester.pump(const Duration(milliseconds: 300)); + expect(find.text('원본을 지우는 작업입니다 (이동)'), findsOneWidget); + expect(find.text('이동: 대상에 옮긴 파일은 원본에서 지워집니다'), findsOneWidget); + await settle(tester, () => find.textContaining('비교하는 중').evaluate().isEmpty, rounds: 200); + expect(find.textContaining('a.txt'), findsWidgets, reason: '옮길 목록'); + await tester.tap(find.text('취소').last); + await tester.pumpAndSettle(); + await settle(tester, () => false, rounds: 10); + expect(a.existsSync(), isTrue, reason: '취소하면 원본 그대로'); + expect(File(p.join(dst, 'a.txt')).existsSync(), isFalse); + expect(find.text('아직 실행 안 함'), findsOneWidget); + }); + testWidgets('105: 원본이 대상 안 + 창에서 [원본 파일 지우기] 로 지우기 옵션이 생기면 막는다', (tester) async { final outer = p.join(tmp.path, 'O'), inner = p.join(outer, 'in'); File(p.join(inner, 'n.txt'))