157 lines
6.8 KiB
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),
|
|
),
|
|
]),
|
|
),
|
|
);
|
|
}
|
|
}
|