259 lines
11 KiB
Dart
259 lines
11 KiB
Dart
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';
|
|
import '../jj_chip.dart';
|
|
|
|
/// 약속 (E5d-6): 보기를 바꾸는 창 - 정렬 기준 · 즐겨찾기 (Rsync 는 짝) · 내역 · 창 배치 (좌우 ⇆ 위아래 한 번에 · 배치 창) · 버튼 구성.
|
|
/// 고른 것은 설정에 저장하고 (취소면 그대로), 배치가 바뀌면 보던 폴더가 다시 보이게 한다. [twoPanes]: 지금 창이 둘인지
|
|
class ExplorerViewDialogs {
|
|
ExplorerViewDialogs(this.ctl, {required this.twoPanes});
|
|
|
|
final ExplorerController ctl;
|
|
final bool Function() twoPanes;
|
|
|
|
/// 84: 정렬 기준 ("폴더마다 기억" 이면 지금 창의 폴더)
|
|
Future<void> sortDialog(BuildContext context) async {
|
|
// 84: "폴더마다 기억" 이면 지금 창의 폴더 정렬
|
|
final dir = ctl.pane.current;
|
|
var (by, desc) = ctl.sortOf(dir);
|
|
final ok = await showDialog<bool>(
|
|
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('종류 (확장자)')),
|
|
])
|
|
JjChip(label: 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<void> 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<void> historyDialog(BuildContext context, ExplorerPane pane) async {
|
|
final hist = ctl.c.settings.explorer.history.where(reachable).toList();
|
|
final pick = await showDialog<String>(
|
|
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<void> 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<void> 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<bool>(
|
|
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)
|
|
JjChip(label: 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<void> buttonsDialog(BuildContext context) async {
|
|
final android = ctl.c.services.platform.isAndroid;
|
|
final current = ExplorerButton.fromSettings(ctl.c.settings.explorer.buttons, android: android);
|
|
final order = [...current, ...ExplorerButton.explorerValues(android: android).where((b) => !current.contains(b))];
|
|
final on = {...current};
|
|
final ok = await showDialog<bool>(
|
|
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(android: android));
|
|
on
|
|
..clear()
|
|
..addAll(ExplorerButton.explorerValues(android: android));
|
|
}),
|
|
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);
|
|
}
|
|
}
|