Rsync 확인 창: 1280×720 에서 체크 · 경고가 버튼에 가리지 않게

- 미리 보기 높이를 화면 높이의 28% (140 이상) 로
- [원본 파일 지우기] 체크 · 경고를 미리 보기 위로
- 시험: 1280×720 에서 체크 · 경고가 버튼보다 위

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix_mkv
RyuJongJin 2026-10-10 06:44:28 +09:00
parent 4f10cf167f
commit d6f3de130c
3 changed files with 47 additions and 38 deletions

View File

@ -2057,6 +2057,40 @@ class _ExplorerPageState extends State<ExplorerPage> {
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<ExplorerPage> {
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: [

View File

@ -69,9 +69,13 @@ class _SyncPreviewViewState extends State<SyncPreviewView> {
/// 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<SyncPreviewView> {
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줄까지)

View File

@ -61,9 +61,9 @@ void main() {
tmp.deleteSync(recursive: true);
});
Future<void> pump(WidgetTester tester, Widget page) async {
Future<void> 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);