JJ_MKVMaker/lib/ui/explorer/search_flow.dart

157 lines
6.8 KiB
Dart

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<FileEntry> found, bool done, Map<String, String> moved})? v) {
_back = v;
notifyListeners();
}
/// 79: 마지막 찾기 조건 (이번 실행 동안 다음 찾기 창의 처음 값)
SearchConditions _last = const SearchConditions();
/// 79-a: 결과 화면에서 복사 · 이동 · 지우기 · [그 자리로] 를 한 뒤 [찾기 결과로 돌아가기] 로 다시 열 결과 (다음 찾기 · ✕ · 결과 화면을 그냥 닫으면 없어짐)
({ExplorerPane pane, String root, SearchConditions cond, List<FileEntry> found, bool done, Map<String, String> moved})? _back;
/// 79: 찾기 - 이름 · 크기 · 바뀐 날 · 종류 조건 → 결과 목록 화면 (고르고 그대로 복사 · 이동 · 지우기, 누르면 열기 · 재생)
Future<void> 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<void> backToResults(BuildContext context) async {
final back = _back;
if (back == null) return;
final now = <FileEntry>[];
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<void> _page(BuildContext context, ExplorerPane pane, String root, SearchConditions cond, {List<FileEntry>? initial, bool initialDone = true}) async {
final other = otherDir();
final act = await Navigator.of(context).push<SearchAction>(MaterialPageRoute(
builder: (_) => SearchResultsPage(
root: root,
cond: cond,
showHidden: ctl.c.settings.explorerShowHidden,
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 <String, String>{},
));
}
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),
),
]),
),
);
}
}