import 'package:flutter/material.dart'; import '../../core/file_ops.dart'; import '../../core/vfs.dart'; import '../../l10n/tr.dart'; import '../explorer_search.dart'; import '../theme.dart'; import 'delete_flow.dart'; import 'explorer_controller.dart'; import 'explorer_pane.dart'; import 'open_flow.dart'; import 'transfer_flow.dart'; /// 약속 (E5d-6): 찾기 (79) - 조건 창 → 결과 화면 (누르면 열기 · 재생, 고른 것만 복사 · 이동 · 지우기 - 탐색기의 흐름 그대로, [그 자리로]). /// 결과 화면에서 무엇을 한 뒤에는 위 줄 ([backBar]) 의 [찾기 결과로 돌아가기] 로 다시 찾지 않고 돌아간다 (그사이 지운 것은 빼고 옮긴 것은 새 자리로). /// 마지막 조건은 이번 실행 동안 다음 찾기의 처음 값. 돌아갈 결과가 바뀌면 알림 class ExplorerSearch extends ChangeNotifier { ExplorerSearch(this.ctl, this.opener, this.transfers, this.deletes, {required this.otherDir}); final ExplorerController ctl; final ExplorerOpener opener; final ExplorerTransfers transfers; final ExplorerDeleteFlow deletes; /// 복사 · 이동 갈 곳 (없으면 담아 두기) final String? Function() otherDir; void _setBack(({ExplorerPane pane, String root, SearchConditions cond, List found, bool done, Map moved})? v) { _back = v; notifyListeners(); } /// 79: 마지막 찾기 조건 (이번 실행 동안 다음 찾기 창의 처음 값) SearchConditions _last = const SearchConditions(); /// 79-a: 결과 화면에서 복사 · 이동 · 지우기 · [그 자리로] 를 한 뒤 [찾기 결과로 돌아가기] 로 다시 열 결과 (다음 찾기 · ✕ · 결과 화면을 그냥 닫으면 없어짐) ({ExplorerPane pane, String root, SearchConditions cond, List found, bool done, Map moved})? _back; /// 79: 찾기 - 이름 · 크기 · 바뀐 날 · 종류 조건 → 결과 목록 화면 (고르고 그대로 복사 · 이동 · 지우기, 누르면 열기 · 재생) Future open(BuildContext context, ExplorerPane pane) async { ctl ..active = ctl.panes.indexOf(pane) ..update(); final cond = await askSearchConditions(context, pane.current, _last); if (cond == null || !context.mounted) return; _last = cond; _setBack(null); await _page(context, pane, pane.current, cond); } /// 79-a: [찾기 결과로 돌아가기] - 다시 찾지 않고, 그사이 지운 것은 빼고 옮긴 것은 새 자리로 Future backToResults(BuildContext context) async { final back = _back; if (back == null) return; final now = []; for (final e in back.found) { if (await vExists(e.path)) { now.add(e); continue; } final dest = back.moved[e.path]; final there = dest == null ? null : vJoin(dest, vBasename(e.path)); if (there != null && await vExists(there)) { now.add(FileEntry(there, isDir: e.isDir, size: e.size, modified: e.modified)); } // 고른 폴더와 함께 옮겨진 안쪽 것: 옮긴 폴더 아래 같은 자리 else { for (final entry in back.moved.entries) { if (!isSameOrInside(e.path, entry.key)) continue; // 옮긴 폴더 이름 아래 남은 경로 (같은 꼴의 경로라 앞부분을 잘라 쓴다 - WebDAV 도) final rest = e.path.length > entry.key.length ? e.path.substring(entry.key.length + 1) : ''; final moved = rest.isEmpty ? vJoin(entry.value, vBasename(entry.key)) : vJoin(vJoin(entry.value, vBasename(entry.key)), rest); if (await vExists(moved)) now.add(FileEntry(moved, isDir: e.isDir, size: e.size, modified: e.modified)); break; } } } if (!context.mounted) return; await _page(context, back.pane, back.root, back.cond, initial: now, initialDone: back.done); } Future _page(BuildContext context, ExplorerPane pane, String root, SearchConditions cond, {List? initial, bool initialDone = true}) async { final other = otherDir(); final act = await Navigator.of(context).push(MaterialPageRoute( builder: (_) => SearchResultsPage( root: root, cond: cond, showHidden: ctl.c.settings.explorer.showHidden, otherDir: other, onOpen: (e) => opener.open(context, e.path, pane: pane), initial: initial, initialDone: initialDone, ), )); if (!context.mounted) return; // 결과 화면을 그냥 닫으면 돌아갈 결과도 없앤다 if (act == null || act.paths.isEmpty) { _setBack(null); return; } // 관리자 판단 (10/17): [그 자리로] 로 나가도 남긴다 - 한 곳을 확인하고 다음 결과로 가는 것이 가장 흔한 쓰임 { _setBack(( pane: pane, root: root, cond: cond, found: act.found, done: act.done, moved: act.kind == 'move' && other != null ? {for (final x in act.paths) x: other} : const {}, )); } switch (act.kind) { case 'reveal': final path = act.paths.first; final dir = await vIsDir(path); await ctl.goTo(pane, dir ? path : vDirname(path), fresh: true); pane.focused = path; pane.changed(); ctl.host.reveal(pane, path); // 고른 것만 대상 (154 · 155) - 탐색기의 복사 · 이동 · 지우기와 같은 확인 · 진행 패널 · 휴지통 case 'copy': await transfers.transfer(context, pane, act.paths, move: false, otherDir: otherDir()); case 'move': await transfers.transfer(context, pane, act.paths, move: true, otherDir: otherDir()); case 'delete': await deletes.run(context, pane, act.paths); } } /// 79-a: 탐색기 위의 [찾기 결과로 돌아가기] 줄 Widget? backBar(BuildContext context) { final back = _back; if (back == null) return null; return Material( color: JjColors.panelHigh, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row(children: [ Icon(Icons.manage_search, size: 18, color: JjColors.accent), const SizedBox(width: 6), Expanded( child: Text(trf('찾기 결과 ({0}) · {1}', [back.cond.summary, back.root]), maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 12, color: JjColors.textDim)), ), TextButton(onPressed: () => backToResults(context), child: Text(tr('찾기 결과로 돌아가기'))), IconButton( tooltip: tr('찾기 결과 닫기'), visualDensity: VisualDensity.compact, icon: const Icon(Icons.close, size: 16), onPressed: () => _setBack(null), ), ]), ), ); } }