JJ_MKVMaker/lib/ui/explorer/marks_bar.dart

143 lines
5.6 KiB
Dart

import 'package:flutter/material.dart';
import '../../core/explorer/tree_rows.dart' show globToRegExp;
import '../../core/file_ops.dart' show formatSize;
import '../../core/reader_sources.dart' show extOf;
import '../../core/vfs.dart' show vBasename;
import '../../l10n/tr.dart';
import '../adaptive_row.dart';
import '../theme.dart';
import 'explorer_pane.dart';
import 'explorer_select.dart';
import '../jj_field.dart';
/// 약속 (E5d-4): 선택 모드의 아래 막대 - 표시한 수 · 합계 크기 (읽어 둔 목록 기준, 폴더 크기는 빼고) ·
/// [고르기 도구 (반전 · 패턴 · 같은 확장자)] · [모두 선택] · [표시 지우기] · [선택 끝]. 고르는 일은 [ExplorerSelect].
/// [listDir]: "폴더 + 파일 목록" 의 오른쪽 목록이면 그 폴더 (아니면 창의 지금 폴더)
class ExplorerMarksBar extends StatelessWidget {
const ExplorerMarksBar({super.key, required this.sel, required this.pane, this.listDir});
final ExplorerSelect sel;
final ExplorerPane pane;
final String? listDir;
@override
Widget build(BuildContext context) {
final dir = listDir ?? pane.current;
// 74: 고른 개수와 합계 크기 (읽어 둔 목록 기준, 폴더 크기는 빼고)
var size = 0, folders = 0;
final byPath = {for (final l in pane.cache.values) for (final x in l) x.path: x};
for (final m in pane.sel.marked) {
final x = byPath[m];
if (x == null) continue;
if (x.isDir) {
folders++;
} else {
size += x.size;
}
}
final focusedExt = pane.focused == null || byPath[pane.focused]?.isDir != false ? '' : extOf(pane.focused!);
final info = <Widget>[
Icon(Icons.checklist, size: 18, color: JjColors.accent),
const SizedBox(width: 6),
Flexible(
child: Text(
[
trf('{0}개 표시함', [pane.sel.marked.length]),
if (size > 0) formatSize(size),
if (folders > 0) trf('폴더 {0}개 (크기 빼고)', [folders]),
].join(' · '),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12)),
),
];
final actions = <Widget>[
PopupMenuButton<String>(
tooltip: tr('고르기 도구'),
onSelected: (v) {
switch (v) {
case 'invert':
sel.invertMarks(pane, dir);
case 'pattern':
markPattern(context, sel, pane, dir);
case 'sameExt':
sel.markWhere(pane, dir, (x) => !x.isDir && extOf(x.path) == focusedExt);
}
},
itemBuilder: (_) => [
PopupMenuItem(value: 'invert', child: Text(tr('반전 (고른 것 ↔ 안 고른 것)'))),
PopupMenuItem(value: 'pattern', child: Text(tr('패턴으로 고르기 (예: *.srt)'))),
PopupMenuItem(
value: 'sameExt',
enabled: focusedExt.isNotEmpty,
child: Text(focusedExt.isEmpty ? tr('같은 확장자 모두') : trf('같은 확장자 모두 (.{0})', [focusedExt])),
),
],
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Row(mainAxisSize: MainAxisSize.min, children: [
Text(tr('고르기 도구'), style: TextStyle(fontSize: 13, color: JjColors.accent)),
Icon(Icons.arrow_drop_down, size: 18, color: JjColors.accent),
]),
),
),
TextButton(
onPressed: () => sel.markWhere(pane, listDir ?? pane.current, (_) => true),
child: Text(tr('모두 선택')),
),
TextButton(onPressed: () => sel.clearMarks(pane), child: Text(tr('표시 지우기'))),
TextButton(onPressed: () => sel.setSelecting(false), child: Text(tr('선택 끝'))),
];
return Container(
color: JjColors.panel,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
// 좁은 창 (폰 세로의 두 창 - 규칙 7, 점검 10/22) 은 두 줄로
child: AdaptiveRow(lead: info, trail: actions, minWidth: 560),
);
}
}
/// 74: 패턴으로 고르기 - 창에서 패턴을 받아 [dir] 에 보이는 것 중 맞는 것을 표시. 맞는 것이 없으면 알림 (Host)
Future<void> markPattern(BuildContext context, ExplorerSelect sel, ExplorerPane pane, String dir) async {
final pattern = await showDialog<String>(context: context, builder: (_) => const PatternDialog());
if (pattern == null || pattern.trim().isEmpty) return;
final re = globToRegExp(pattern);
final before = pane.sel.marked.length;
sel.markWhere(pane, dir, (x) => re.hasMatch(vBasename(x.path)));
if (pane.sel.marked.length == before) sel.ctl.host.snack(trf('"{0}" 에 맞는 항목이 없습니다', [pattern.trim()]));
}
/// 74: 패턴 넣기
class PatternDialog extends StatefulWidget {
const PatternDialog({super.key});
@override
State<PatternDialog> createState() => PatternDialogState();
}
class PatternDialogState extends State<PatternDialog> {
final _ctl = TextEditingController(text: '*.srt');
@override
void dispose() {
_ctl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => AlertDialog(
title: Text(tr('패턴으로 고르기')),
content: JjField(
controller: _ctl,
autofocus: true,
helper: tr('* 는 아무 글자, ? 는 한 글자. 여러 개는 ; 로 (예: *.srt; *.ass)'),
onSubmitted: (v) => Navigator.pop(context, v),
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: Text(tr('취소'))),
FilledButton(onPressed: () => Navigator.pop(context, _ctl.text), child: Text(tr('고르기'))),
],
);
}