import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../app/app_controller.dart'; import '../l10n/tr.dart'; import 'explorer_keys.dart'; import 'explorer_look.dart' show ExplorerStyle; import 'theme.dart'; /// X-plore 비교 5-1 ⑥ (관리자 지시 10/21): 파일 탐색기 단축키 바꾸기 - 동작마다 지금 키 · [바꾸기] (다음에 누른 키) · [처음 값으로]. /// 지금 고른 스타일의 처음 키를 보이고, 바꾼 키는 모든 스타일에 쓰인다 class ExplorerKeysPage extends StatelessWidget { final AppController c; const ExplorerKeysPage({super.key, required this.c}); static Future open(BuildContext context, AppController c) => Navigator.of(context).push(MaterialPageRoute(builder: (_) => ExplorerKeysPage(c: c))); bool get _tc => ExplorerStyle.of(c.settings.explorerStyle) == ExplorerStyle.totalcmd; Future _change(BuildContext context, String id) async { final picked = await showDialog( context: context, builder: (_) => _KeyCaptureDialog(c: c, action: id, tc: _tc), ); if (picked == null) return; await c.updateSettings((x) => x.explorerKeyOverrides = {...x.explorerKeyOverrides, id: _none(picked) ? '' : keySpec(picked)}); } static bool _none(SingleActivator a) => a.trigger == LogicalKeyboardKey.abort; @override Widget build(BuildContext context) => ListenableBuilder( listenable: c, builder: (context, _) { final ov = c.settings.explorerKeyOverrides; final keys = effectiveExplorerKeys(tc: _tc, overrides: ov); return Scaffold( appBar: AppBar( title: Text(tr('파일 탐색기 단축키')), actions: [ if (ov.isNotEmpty) TextButton( onPressed: () => c.updateSettings((x) => x.explorerKeyOverrides = {}), child: Text(tr('모두 처음 값으로')), ), ], ), body: ListView(children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), child: Text( trf('지금 스타일 ({0}) 의 키입니다. 바꾼 키는 모든 스타일에 쓰이고, 다른 동작과 겹치면 그 동작의 키는 없어집니다. Esc 는 바꿀 수 없습니다', [ExplorerStyle.of(c.settings.explorerStyle).label]), style: TextStyle(fontSize: 12, color: JjColors.textDim)), ), for (final id in explorerKeyActions) ListTile( dense: true, title: Text(explorerKeyLabel(id)), subtitle: Text( keys[id]?.isNotEmpty == true ? keys[id]!.map(keyText).join(' · ') : tr('(없음)'), style: keyTextStyle.copyWith(color: ov.containsKey(id) ? JjColors.accent : null), ), trailing: Wrap(spacing: 4, children: [ if (ov.containsKey(id)) TextButton( onPressed: () => c.updateSettings((x) => x.explorerKeyOverrides = {...x.explorerKeyOverrides}..remove(id)), child: Text(tr('처음 값으로')), ), OutlinedButton(onPressed: () => _change(context, id), child: Text(tr('바꾸기'))), ]), ), ]), ); }, ); } /// 다음에 누른 키를 받는 창 - 겹치면 알리고 [저장] 은 그래도 됨. [단축키 없음] 은 abort 글쇠로 돌려준다 (부르는 쪽이 '' 로 저장) class _KeyCaptureDialog extends StatefulWidget { final AppController c; final String action; final bool tc; const _KeyCaptureDialog({required this.c, required this.action, required this.tc}); @override State<_KeyCaptureDialog> createState() => _KeyCaptureDialogState(); } class _KeyCaptureDialogState extends State<_KeyCaptureDialog> { SingleActivator? _key; final _focus = FocusNode(); @override void dispose() { _focus.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final k = _key; final clash = k == null ? null : keyConflict(widget.action, k, tc: widget.tc, overrides: widget.c.settings.explorerKeyOverrides); return AlertDialog( title: Text(trf('{0} - 단축키', [explorerKeyLabel(widget.action)])), content: Focus( focusNode: _focus, autofocus: true, onKeyEvent: (_, e) { if (e is! KeyDownEvent || isModifierOnly(e.logicalKey)) return KeyEventResult.handled; setState(() => _key = activatorFromEvent(e)); return KeyEventResult.handled; }, child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(tr('쓸 키를 누르세요 (Ctrl · Shift · Alt 와 함께 눌러도 됩니다)')), const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration(border: Border.all(color: JjColors.border), borderRadius: BorderRadius.circular(6)), child: Text(k == null ? '…' : keyText(k), style: keyTextStyle.copyWith(fontSize: 16)), ), if (clash == 'escape') Padding( padding: const EdgeInsets.only(top: 8), child: Text(tr('Esc 는 선택 · 빠른 필터를 닫는 키라 쓸 수 없습니다'), style: TextStyle(color: JjColors.danger, fontSize: 12)), ) else if (clash != null) Padding( padding: const EdgeInsets.only(top: 8), child: Text(trf('이미 "{0}" 에 쓰는 키입니다 - 저장하면 그 동작의 이 키는 없어집니다', [explorerKeyLabel(clash)]), style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)), ), ]), ), actions: [ TextButton( onPressed: () => Navigator.pop(context, const SingleActivator(LogicalKeyboardKey.abort)), child: Text(tr('단축키 없음')), ), TextButton(onPressed: () => Navigator.pop(context), child: Text(tr('취소'))), FilledButton(onPressed: k == null || clash == 'escape' ? null : () => Navigator.pop(context, k), child: Text(tr('저장'))), ], ); } }