From d6f3de130cef226b5bb998d1009d976912b0cfc7 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Sat, 10 Oct 2026 06:44:28 +0900 Subject: [PATCH] =?UTF-8?q?Rsync=20=ED=99=95=EC=9D=B8=20=EC=B0=BD:=201280?= =?UTF-8?q?=C3=97720=20=EC=97=90=EC=84=9C=20=EC=B2=B4=ED=81=AC=20=C2=B7=20?= =?UTF-8?q?=EA=B2=BD=EA=B3=A0=EA=B0=80=20=EB=B2=84=ED=8A=BC=EC=97=90=20?= =?UTF-8?q?=EA=B0=80=EB=A6=AC=EC=A7=80=20=EC=95=8A=EA=B2=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 미리 보기 높이를 화면 높이의 28% (140 이상) 로 - [원본 파일 지우기] 체크 · 경고를 미리 보기 위로 - 시험: 1280×720 에서 체크 · 경고가 버튼보다 위 Co-Authored-By: Claude Opus 5.5 --- lib/ui/explorer_page.dart | 67 +++++++++++++++++----------------- lib/ui/sync_preview_view.dart | 8 +++- test/explorer_webdav_test.dart | 10 +++-- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index 941d964..12ecd02 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -2057,6 +2057,40 @@ class _ExplorerPageState extends State { if (nested) Text(tr('원본이 대상 폴더 안에 있습니다: 원본의 내용이 대상 폴더 바로 아래에 복사됩니다 (원본 폴더는 그대로 남음).'), style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + // 1280×720 등 낮은 화면에서도 보이게: 원본 지우기 체크 · 경고는 비교 목록보다 위에 (178) + if (both) ...[ + Text(tr('양쪽을 함께: 받는 쪽이 더 새 파일은 덮어쓰지 않습니다 (-u).'), + style: const TextStyle(fontSize: 12, color: JjColors.textDim)), + if (optionMove) + Text(tr('양쪽 (⇄) 에서는 원본 파일을 지우지 않습니다 (옵션의 --remove-source-files 는 쓰지 않음).'), + style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), + ] else ...[ + CheckboxListTile( + contentPadding: EdgeInsets.zero, + value: removeSource, + onChanged: (v) => set(() { + removeSource = v ?? false; + tasks = shownTasks(removeSource); + preview = null; + previewError = null; + }), + title: Text(tr('원본 파일 지우기 (--remove-source-files)')), + subtitle: Text(tr('대상으로 옮긴 파일을 원본에서 지웁니다 (이동)')), + ), + if (removeSource) ...[ + // 178: 원본이 지워진다는 것을 분명히 (환경 설정 옵션 때문에 켜졌으면 그렇다고) + Text( + optionMove + ? tr('원본 파일이 지워집니다 (이동). 환경 설정의 rsync 옵션에 --remove-source-files 가 있어 켜 두었습니다 - 끄면 이번에는 원본을 남깁니다.') + : tr('원본 파일이 지워집니다 (이동).'), + style: const TextStyle(fontSize: 12, color: Colors.orangeAccent, fontWeight: FontWeight.w600)), + const SizedBox(height: 4), + Text(tr('끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:'), + style: const TextStyle(fontSize: 12, color: JjColors.textDim)), + const SizedBox(height: 6), + PruneChoice(value: prune, onChanged: (v) => set(() => prune = v)), + ], + ], // 72: 무엇이 바뀌는지 파일별로 (→ · ← · 받는 쪽에만 있음 · 지워짐) const SizedBox(height: 8), SyncPreviewView( @@ -2102,39 +2136,6 @@ class _ExplorerPageState extends State { if (_viaDav([l, r])) Text(tr('WebDAV: rsync 대신 앱이 직접 맞춥니다 (크기 · 바뀐 때 비교, 옵션 중 -u · --delete · --remove-source-files 를 따름).'), style: const TextStyle(fontSize: 12, color: JjColors.accent)), - if (both) ...[ - Text(tr('양쪽을 함께: 받는 쪽이 더 새 파일은 덮어쓰지 않습니다 (-u).'), - style: const TextStyle(fontSize: 12, color: JjColors.textDim)), - if (optionMove) - Text(tr('양쪽 (⇄) 에서는 원본 파일을 지우지 않습니다 (옵션의 --remove-source-files 는 쓰지 않음).'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)), - ] else ...[ - CheckboxListTile( - contentPadding: EdgeInsets.zero, - value: removeSource, - onChanged: (v) => set(() { - removeSource = v ?? false; - tasks = shownTasks(removeSource); - preview = null; - previewError = null; - }), - title: Text(tr('원본 파일 지우기 (--remove-source-files)')), - subtitle: Text(tr('대상으로 옮긴 파일을 원본에서 지웁니다 (이동)')), - ), - if (removeSource) ...[ - // 178: 원본이 지워진다는 것을 분명히 (환경 설정 옵션 때문에 켜졌으면 그렇다고) - Text( - optionMove - ? tr('원본 파일이 지워집니다 (이동). 환경 설정의 rsync 옵션에 --remove-source-files 가 있어 켜 두었습니다 - 끄면 이번에는 원본을 남깁니다.') - : tr('원본 파일이 지워집니다 (이동).'), - style: const TextStyle(fontSize: 12, color: Colors.orangeAccent, fontWeight: FontWeight.w600)), - const SizedBox(height: 4), - Text(tr('끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:'), - style: const TextStyle(fontSize: 12, color: JjColors.textDim)), - const SizedBox(height: 6), - PruneChoice(value: prune, onChanged: (v) => set(() => prune = v)), - ], - ], ]), ), actions: [ diff --git a/lib/ui/sync_preview_view.dart b/lib/ui/sync_preview_view.dart index 4f25867..b2dd572 100644 --- a/lib/ui/sync_preview_view.dart +++ b/lib/ui/sync_preview_view.dart @@ -69,9 +69,13 @@ class _SyncPreviewViewState extends State { /// 110: 비교 중 · 끝 · 오류 어느 때나 같은 높이 (창 크기가 바뀌어 [취소] · [실행] 자리가 움직이지 않게) static const height = 300.0; + /// 낮은 화면 (예: 1280×720) 에서는 화면 높이의 28% 까지만 - 아래의 [원본 파일 지우기] · 경고 · 버튼이 가려지지 않게. + /// 같은 화면 크기에서는 늘 같은 높이 (110) + double get _h => (MediaQuery.sizeOf(context).height * 0.28).clamp(140.0, height); + @override Widget build(BuildContext context) => - SizedBox(height: height, child: Align(alignment: Alignment.topLeft, child: _body())); + SizedBox(height: _h, child: Align(alignment: Alignment.topLeft, child: _body())); Widget _body() { final items = _items; @@ -116,7 +120,7 @@ class _SyncPreviewViewState extends State { if (items.isNotEmpty) Container( margin: const EdgeInsets.only(top: 6), - constraints: const BoxConstraints(maxHeight: height - 80), + constraints: BoxConstraints(maxHeight: _h - 80), decoration: BoxDecoration(color: JjColors.bg, borderRadius: BorderRadius.circular(6)), padding: const EdgeInsets.all(8), // 창 (AlertDialog) 이 크기를 미리 재므로 ListView 대신 (500줄까지) diff --git a/test/explorer_webdav_test.dart b/test/explorer_webdav_test.dart index 5cbb9b8..196e186 100644 --- a/test/explorer_webdav_test.dart +++ b/test/explorer_webdav_test.dart @@ -61,9 +61,9 @@ void main() { tmp.deleteSync(recursive: true); }); - Future pump(WidgetTester tester, Widget page) async { + Future pump(WidgetTester tester, Widget page, {Size size = const Size(1600, 900)}) async { HttpOverrides.global = null; // testWidgets 는 HttpClient 를 막아 둔다 - 이 시험은 실제 (로컬) 서버에 연결 - tester.view.physicalSize = const Size(1600, 900); + tester.view.physicalSize = size; tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: page))); @@ -123,7 +123,8 @@ void main() { ..rsyncOptions = '-av --remove-source-files' ..rsyncPaths = [left, 'dav://nas/']; DavRegistry.configure([s]); - await pump(tester, ExplorerPage(c: c, rsync: true)); + // 1280×720 (낮은 창) 에서도 체크 · 경고가 버튼에 가려지지 않게 + await pump(tester, ExplorerPage(c: c, rsync: true), size: const Size(1280, 720)); await settle(tester, () => find.text('sub').evaluate().isNotEmpty && find.text('원격').evaluate().isNotEmpty); await act(tester, () => tester.tap(find.text('sub'))); await act(tester, () => tester.tap(find.text('원격'))); @@ -136,6 +137,9 @@ void main() { expect(find.widgetWithText(FilledButton, '이동'), findsOneWidget); expect(find.textContaining('원본 파일이 지워집니다 (이동). 환경 설정의 rsync 옵션에'), findsOneWidget); expect(find.text('rsync -av --remove-source-files'), findsOneWidget); + final buttonsTop = tester.getRect(find.widgetWithText(FilledButton, '이동')).top; + expect(tester.getRect(find.byType(CheckboxListTile)).bottom, lessThan(buttonsTop)); + expect(tester.getRect(find.textContaining('원본 파일이 지워집니다')).bottom, lessThan(buttonsTop)); // 끄면 [실행] · 옵션 줄에서도 빠진다 await tester.tap(find.byType(CheckboxListTile)); await settle(tester, () => find.textContaining('비교하는 중').evaluate().isEmpty, rounds: 400);