import 'package:flutter/material.dart'; import '../../app/volumes.dart' show reachable; import '../../l10n/tr.dart'; import '../explorer_bookmarks.dart'; import '../theme.dart'; import 'explorer_buttons.dart'; import 'explorer_controller.dart'; import 'explorer_pane.dart'; /// 약속 (E5d-6): 보기를 바꾸는 창 - 정렬 기준 · 즐겨찾기 (Rsync 는 짝) · 내역 · 창 배치 (좌우 ⇆ 위아래 한 번에 · 배치 창) · 버튼 구성. /// 고른 것은 설정에 저장하고 (취소면 그대로), 배치가 바뀌면 보던 폴더가 다시 보이게 한다. [twoPanes]: 지금 창이 둘인지 class ExplorerViewDialogs { ExplorerViewDialogs(this.ctl, {required this.twoPanes}); final ExplorerController ctl; final bool Function() twoPanes; /// 84: 정렬 기준 ("폴더마다 기억" 이면 지금 창의 폴더) Future sortDialog(BuildContext context) async { // 84: "폴더마다 기억" 이면 지금 창의 폴더 정렬 final dir = ctl.pane.current; var (by, desc) = ctl.sortOf(dir); final ok = await showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, set) => AlertDialog( scrollable: true, title: Text(tr('정렬 기준')), content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap(spacing: 6, children: [ for (final (v, label) in [ ('name', tr('이름')), ('date', tr('바뀐 날')), ('size', tr('크기')), ('type', tr('종류 (확장자)')), ]) ChoiceChip(label: Text(label), selected: by == v, onSelected: (_) => set(() => by = v)), ]), SwitchListTile(value: desc, title: Text(tr('거꾸로')), onChanged: (x) => set(() => desc = x)), if (ctl.c.settings.explorer.sortPerFolder) Text(trf('이 폴더에만: {0}', [ctl.displayPath(dir)]), style: TextStyle(fontSize: 12, color: JjColors.textDim)), ]), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))), FilledButton(onPressed: () => Navigator.pop(ctx, true), child: Text(tr('확인'))), ], ), ), ); if (ok != true) return; await ctl.setSort(dir, by, desc); } /// 73: 즐겨찾기 - 이 창으로 가기, Rsync 화면이면 짝 (왼쪽 · 오른쪽) 을 한 번에 Future openBookmarks(BuildContext context, ExplorerPane pane) async { ctl ..active = ctl.panes.indexOf(pane) ..update(); await showFolderBookmarks( context, ctl.c, current: pane.current, right: ctl.rsync ? ctl.panes[1].current : null, go: (dir) => ctl.goTo(pane, dir, fresh: true), goPair: ctl.rsync ? (a, b) { ctl.goTo(ctl.panes[0], a, fresh: true); ctl.goTo(ctl.panes[1], b, fresh: true); } : null, ); if (context.mounted) ctl.update(); } Future historyDialog(BuildContext context, ExplorerPane pane) async { final hist = ctl.c.settings.explorer.history.where(reachable).toList(); final pick = await showDialog( context: context, builder: (ctx) => SimpleDialog( title: Text(tr('내역 (최근 폴더)')), children: [ if (hist.isEmpty) Padding(padding: const EdgeInsets.all(16), child: Text(tr('없음'))), for (final h in hist) SimpleDialogOption( onPressed: () => Navigator.pop(ctx, h), child: Text(h, maxLines: 2, overflow: TextOverflow.ellipsis), ), ], ), ); if (pick != null) await ctl.goTo(pane, pick, fresh: true); } /// 118: 두 창이면 좌우 ⇆ 위아래 를 바꾸고, 한 창이면 두 창 (좌우) 으로. 한 번 눌러 바로 Future toggleOrientation(BuildContext context) async { final s = ctl.c.settings; if (!twoPanes()) { await ctl.c.updateSettings((x) => x ..explorer.layout = 'dual' ..explorer.orientation = 'side'); } else { // 지금 보이는 배치의 반대 ('화면 모양 따라' 였으면 지금 모양에서 정함) final size = MediaQuery.sizeOf(context); final nowSide = switch (s.explorer.orientation) { 'side' => true, 'stacked' => false, _ => size.width >= size.height, }; await ctl.c.updateSettings((x) => x.explorer.orientation = nowSide ? 'stacked' : 'side'); } if (!context.mounted) return; ctl.update(); for (final pane in ctl.panes) { ctl.host.reveal(pane, pane.current); } } Future layoutDialog(BuildContext context) async { final s = ctl.c.settings; var layout = s.explorer.layout, orient = s.explorer.orientation, bar = s.explorer.toolbar; final ok = await showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, set) { Widget group(String title, String value, List<(String, String)> items, void Function(String) on) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(top: 8, left: 4), child: Text(title, style: const TextStyle(fontWeight: FontWeight.w600)), ), Wrap(spacing: 6, children: [ for (final (v, label) in items) ChoiceChip(label: Text(label), selected: value == v, onSelected: (_) => set(() => on(v))), ]), ]); return AlertDialog( scrollable: true, title: Text(tr('창 배치')), content: SizedBox( width: 420, child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ group(tr('창'), layout, [ ('auto', tr('화면 크기 따라')), ('dual', tr('두 창 (기본)')), ('split', tr('폴더 + 파일 목록')), ('single', tr('한 창')), ], (v) => layout = v), if (layout == 'split') Padding( padding: const EdgeInsets.only(top: 6, left: 4), child: Text(tr('왼쪽에 폴더, 오른쪽에 왼쪽에서 고른 폴더의 파일들을 보여 줍니다 (Windows 탐색기처럼).'), style: TextStyle(fontSize: 12, color: JjColors.textDim)), ), if (layout != 'single') group(tr('두 창 배치'), orient, [ ('side', tr('좌우 (기본)')), ('stacked', tr('위아래')), ('auto', tr('화면 모양 따라')), ], (v) => orient = v), group(tr('기능 버튼 줄'), bar, [ ('middle', layout != 'single' ? tr('두 창 사이') : tr('왼쪽')), ('edge', tr('끝 (오른쪽 · 아래)')), ('hidden', tr('숨김')), ], (v) => bar = v), if (bar == 'hidden') Padding( padding: const EdgeInsets.only(top: 8, left: 4), child: Text(tr('숨겨도 위쪽 ⋮ 메뉴에서 창 배치 · 버튼 구성을 바꿀 수 있습니다.'), style: TextStyle(fontSize: 12, color: JjColors.textDim)), ), ]), ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))), FilledButton(onPressed: () => Navigator.pop(ctx, true), child: Text(tr('확인'))), ], ); }, ), ); if (ok != true) return; await ctl.c.updateSettings((x) => x ..explorer.layout = layout ..explorer.orientation = orient ..explorer.toolbar = bar); if (layout == 'single') { ctl ..active = 0 ..update(); } // 배치가 바뀌면 목록을 새로 그리므로 맨 위로 간다: 보던 폴더가 다시 보이게 for (final pane in ctl.panes) { ctl.host.reveal(pane, pane.current); } } Future buttonsDialog(BuildContext context) async { final current = ExplorerButton.fromSettings(ctl.c.settings.explorer.buttons); final order = [...current, ...ExplorerButton.explorerValues.where((b) => !current.contains(b))]; final on = {...current}; final ok = await showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, set) => AlertDialog( title: Text(tr('버튼 구성')), content: SizedBox( width: 380, height: 460, child: Column(children: [ Text(tr('보일 버튼을 고르고, 끌어서 순서를 바꿉니다.'), style: TextStyle(fontSize: 12, color: JjColors.textDim)), const SizedBox(height: 8), Expanded( child: ReorderableListView( buildDefaultDragHandles: true, onReorderItem: (a, b) => set(() => order.insert(b, order.removeAt(a))), children: [ for (final b in order) CheckboxListTile( key: ValueKey(b), dense: true, value: on.contains(b), secondary: Icon(b.icon), title: Text(tr(b.label)), controlAffinity: ListTileControlAffinity.leading, onChanged: (v) => set(() => v == true ? on.add(b) : on.remove(b)), ), ], ), ), ]), ), actions: [ TextButton( onPressed: () => set(() { order ..clear() ..addAll(ExplorerButton.defaultOrder); on ..clear() ..addAll(ExplorerButton.explorerValues); }), child: Text(tr('기본값')), ), TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))), FilledButton(onPressed: () => Navigator.pop(ctx, true), child: Text(tr('확인'))), ], ), ), ); if (ok != true) return; final names = [for (final b in order) if (on.contains(b)) b.name]; await ctl.c.updateSettings((x) => x.explorer.buttons = names.isEmpty ? [ExplorerButton.layout.name] : names); } }