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'; /// 약속 (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 = [ 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 = [ PopupMenuButton( 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 markPattern(BuildContext context, ExplorerSelect sel, ExplorerPane pane, String dir) async { final pattern = await showDialog(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 createState() => PatternDialogState(); } class PatternDialogState extends State { final _ctl = TextEditingController(text: '*.srt'); @override void dispose() { _ctl.dispose(); super.dispose(); } @override Widget build(BuildContext context) => AlertDialog( title: Text(tr('패턴으로 고르기')), content: TextField( controller: _ctl, autofocus: true, decoration: InputDecoration(helperText: 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('고르기'))), ], ); }