WIP 264 · 265 이동 · 복사 끝: 알림에 "n개는 원본에 남음" · 펼쳐 둔 원본 · 대상 안쪽까지 다시 읽기 (없어진 폴더는 접음) (검토 대기)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
묶음6
RyuJongJin 2026-10-12 09:05:34 +09:00
parent 92919aae93
commit 8228d04a6e
2 changed files with 60 additions and 1 deletions

View File

@ -1407,6 +1407,30 @@ class _ExplorerPageState extends State<ExplorerPage> {
}
}
/// 265: 복사 · 이동 뒤 - 대상 · 원본의 위 폴더와, 펼쳐 둔 원본 · 대상 안쪽까지 두 창 모두 다시 읽는다.
/// (예전에는 원본의 위 폴더만 읽어, 합치다 남은 원본 폴더 아래에 옮겨진 파일이 계속 보였다) 없어진 폴더는 접는다
Future<void> _refreshAfterTransfer(List<String> sources, String dest) async {
for (final x in _panes) {
final dirs = {
dest,
...sources.map(vDirname),
for (final k in x.cache.keys)
if (isSameOrInside(k, dest) || sources.any((s) => isSameOrInside(k, s))) k,
};
for (final d in dirs) {
if (!mounted) return;
if (!x.cache.containsKey(d)) continue;
if (!await vExists(d)) {
x.cache.remove(d);
x.expanded.remove(d);
continue;
}
// 180: 비우지 않고 새로 읽은 것으로 바꾼다 (자리 · 스크롤 그대로)
await _load(x, d, force: true);
}
}
}
/// 바뀐 폴더를 두 창 모두 다시 읽는다
Future<void> _refreshAll(Iterable<String> dirs) async {
for (final pane in _panes) {
@ -2050,7 +2074,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
if (!mounted) return false;
// 180: 표시만 지우고 선택 모드는 그대로 (꺼지면 동그라미 칸 · 아래 막대가 사라져 자리가 바뀐다)
pane.marked.clear();
await _refreshAll([dest, ...sources.map(p.dirname)]);
await _refreshAfterTransfer(sources, dest);
// 넣은 폴더가 창의 지금 폴더면 펼쳐서 새 항목이 보이게
for (final x in _panes) {
if (samePath(x.current, dest)) {
@ -2065,6 +2089,9 @@ class _ExplorerPageState extends State<ExplorerPage> {
? trf('끝나지 못했습니다: {0}', [error])
: [
trf('{0}개 항목을 {1}', [job.made.length, move ? tr('옮겼습니다.') : tr('복사했습니다.')]),
// 264: 작업 기록에만 있던 것도 알림에 (폴더를 합칠 때 받는 쪽이 더 새 파일 등)
if (move && job.leftInSource > 0) trf('{0}개는 원본에 남음 (건너뜀)', [job.leftInSource]),
if (!move && job.keptNewer > 0) trf('받는 쪽이 더 새것 {0}개 건너뜀', [job.keptNewer]),
if (job.skipped.isNotEmpty) trf('같은 이름이라 건너뜀: {0}', [_namesOf(job.skipped)]),
if (skippedHere.isNotEmpty) trf('이미 이 폴더에 있어 건너뜀: {0}', [_namesOf(skippedHere)]),
].join(' · '));

View File

@ -108,6 +108,38 @@ void main() {
expect(File(p.join(right, 'doc.txt')).readAsStringSync(), 'new');
});
testWidgets('264 · 265: 폴더를 합쳐 옮긴 뒤 - 알림에 "원본에 남음", 펼쳐 둔 원본 폴더는 다시 읽어 옮겨진 파일이 보이지 않는다', (tester) async {
final d = p.join(left, 'D');
File(p.join(d, 'same.txt'))
..createSync(recursive: true)
..writeAsStringSync('old')
..setLastModifiedSync(DateTime(2026, 1, 1));
File(p.join(d, 'add.txt')).writeAsStringSync('a');
File(p.join(right, 'D', 'same.txt'))
..createSync(recursive: true)
..writeAsStringSync('NEWER');
c.settings.copyConflict = 'overwrite';
tester.view.physicalSize = const Size(1600, 900);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c))));
await settle(tester, () => find.text('D').evaluate().length >= 2);
// 왼쪽 D 를 눌러 펼치고 (고른 것) 안의 add.txt 가 보이게
await tester.runAsync(() => tester.tap(find.text('D').first));
await settle(tester, () => find.text('add.txt').evaluate().isNotEmpty);
expect(find.text('add.txt'), findsOneWidget);
await tester.runAsync(() => tester.tap(find.text('이동').first));
await settle(tester, () => find.widgetWithText(FilledButton, '이동').evaluate().isNotEmpty);
await tester.runAsync(() => tester.tap(find.widgetWithText(FilledButton, '이동')));
await settle(tester, () => find.byType(LinearProgressIndicator).evaluate().isEmpty && find.byType(SnackBar).evaluate().isNotEmpty);
expect(File(p.join(right, 'D', 'add.txt')).existsSync(), isTrue);
expect(File(p.join(d, 'same.txt')).existsSync(), isTrue, reason: '받는 쪽이 더 새것이라 원본에 남음');
expect(find.textContaining('1개는 원본에 남음 (건너뜀)'), findsOneWidget, reason: '264: 알림에도');
expect(find.text('add.txt'), findsNothing, reason: '265: 원본 D 를 다시 읽음 (오른쪽 D 는 펼치지 않음)');
expect(find.text('same.txt'), findsOneWidget);
await calm(tester);
});
testWidgets('48 보충: 처음 열면 아무것도 고르지 않은 상태 - 바로 [복사] 를 누르면 "복사할 항목을 고르세요" (폴더 통째로 복사하지 않음)', (tester) async {
tester.view.physicalSize = const Size(1600, 900);
tester.view.devicePixelRatio = 1;