목록에서 두 번 누르면 도로 나오던 것 고침 · TC Enter = 열기
- 원인 (실제 앱): 목록 줄 (TC · '폴더 + 파일 목록') 의 폴더 · '..' 는 한 번에 열려, 두 번 누르면 두 번째 누르기가 같은 자리의 '..' 에 맞아 도로 나왔다 - TC 모양: 폴더 · '..' 도 한 번 = 고르기, 두 번 = 열기 (ClickPolicy doubleOpens - Total Commander 와 같게) - TC 모양: Enter = 열기 (폴더는 들어가기, 파일은 열기) - 바꿀 수 있는 키 동작 'open', 처음 키는 TC 모양일 때만 Enter - '폴더 + 파일 목록': 한 번 열기는 그대로, 연 직후 두 번 누르기 시간 (300ms) 안의 다음 누르기는 버림 (마지막 틀 시각으로 잼) - 약속 시험: 누르기 표 (TC) · TC 한 번 / Enter / '..' 두 번 · 목록 배치 곧바로 두 번째 누르기 · 시간 지난 '..' 한 번 - 점검 시험: TC 를 두 번 누르기 · Enter 로 되돌림 (실제 시간에서 들어가기 · 나오기 됨), 목록 배치 단계 더함 - 214장 2회 같음 (base10) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>modular
parent
afe15620bd
commit
6f9d8f6284
|
|
@ -2023,6 +2023,7 @@
|
|||
"열 수 없습니다: {0}": "Cannot open: {0}",
|
||||
"열 수 없음": "Cannot open",
|
||||
"열기": "Open",
|
||||
"열기 (폴더는 들어가기)": "Open (enter folders)",
|
||||
"열기 · 재생": "Open · Play",
|
||||
"열어 본 때: {0}": "Accessed: {0}",
|
||||
"영구 삭제": "Delete permanently",
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,7 @@
|
|||
"열 수 없습니다: {0}": "開けません: {0}",
|
||||
"열 수 없음": "開けません",
|
||||
"열기": "開く",
|
||||
"열기 (폴더는 들어가기)": "開く (フォルダーは中へ)",
|
||||
"열기 · 재생": "開く・再生",
|
||||
"열어 본 때: {0}": "アクセス日時: {0}",
|
||||
"영구 삭제": "完全に削除",
|
||||
|
|
|
|||
|
|
@ -2023,6 +2023,7 @@
|
|||
"열 수 없습니다: {0}": "无法打开: {0}",
|
||||
"열 수 없음": "无法打开",
|
||||
"열기": "打开",
|
||||
"열기 (폴더는 들어가기)": "打开 (文件夹则进入)",
|
||||
"열기 · 재생": "打开 · 播放",
|
||||
"열어 본 때: {0}": "访问时间: {0}",
|
||||
"영구 삭제": "永久删除",
|
||||
|
|
|
|||
|
|
@ -120,12 +120,26 @@ void main() {
|
|||
await settle(3);
|
||||
}
|
||||
|
||||
// 복사 · 이동 · 지우기는 받는 폴더의 시각을 지금으로 바꾼다 - TC 는 폴더 날짜 칸이 있어 다시 고정하고 새로 읽는다 (Ctrl+R)
|
||||
Future<void> fixDirTimes() async {
|
||||
await t.runAsync(() => Process.run('powershell', [
|
||||
'-NoProfile',
|
||||
'-Command',
|
||||
"Get-ChildItem -LiteralPath '${tmp.path}' -Recurse -Directory | ForEach-Object { \$_.LastWriteTime = [datetime]'2026-10-22 09:00' }",
|
||||
]));
|
||||
await t.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await t.sendKeyEvent(LogicalKeyboardKey.keyR);
|
||||
await t.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||
await settle();
|
||||
}
|
||||
|
||||
Future<void> jobsGone() async {
|
||||
for (var i = 0; i < 100 && find.byType(TransferJobView).evaluate().isNotEmpty; i++) {
|
||||
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 100)));
|
||||
await t.pump(const Duration(milliseconds: 100));
|
||||
}
|
||||
await settle();
|
||||
if (style == 'totalcmd') await fixDirTimes();
|
||||
}
|
||||
|
||||
// 한 단계: 실패해도 다음 단계로 (못 찾은 것 자체가 점검 결과)
|
||||
|
|
@ -270,25 +284,11 @@ void main() {
|
|||
});
|
||||
// TC 전용 (관리자 지시 10/23): 목록 들어가고 나오기 (..) · 아래 단추 줄 F5 복사 · F6 이동 · F8 삭제 · 지운 뒤 초점
|
||||
if (style == 'totalcmd') {
|
||||
// 복사 · 이동 · 지우기는 받는 폴더의 시각을 지금으로 바꾼다 - TC 는 폴더 날짜 칸이 있어 다시 고정하고 새로 읽는다 (Ctrl+R)
|
||||
Future<void> fixDirTimes() async {
|
||||
await t.runAsync(() => Process.run('powershell', [
|
||||
'-NoProfile',
|
||||
'-Command',
|
||||
"Get-ChildItem -LiteralPath '${tmp.path}' -Recurse -Directory | ForEach-Object { \$_.LastWriteTime = [datetime]'2026-10-22 09:00' }",
|
||||
]));
|
||||
await t.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await t.sendKeyEvent(LogicalKeyboardKey.keyR);
|
||||
await t.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||
await settle();
|
||||
}
|
||||
|
||||
Future<void> fkey(String tip) async {
|
||||
await t.tap(find.byTooltip(tip));
|
||||
await settle();
|
||||
if (find.byType(Dialog).evaluate().isNotEmpty) await okDialog();
|
||||
await jobsGone();
|
||||
await fixDirTimes();
|
||||
await jobsGone(); // TC 는 jobsGone 이 폴더 시각을 다시 고정한다
|
||||
}
|
||||
|
||||
await noticeTimesOut();
|
||||
|
|
@ -307,18 +307,26 @@ void main() {
|
|||
await settle();
|
||||
}
|
||||
});
|
||||
// 들어가기 · 나오기 (".." 두 번 누르기는 단위 약속 시험 explorer_tc_look_test 가 본다 - 이 실제 시간 시험에서는 두 번 누르기가
|
||||
// 열기로 잡히지 않았다): 줄 메뉴 [열기] 로 들어가고, 위 폴더 키 (Backspace) 로 나온다
|
||||
await step('TC - [Docs] 메뉴 열기 (들어가기)', () async {
|
||||
await t.tap(txt('Docs'), buttons: kSecondaryMouseButton);
|
||||
// 들어가기 · 나오기 (관리자 10/23: TC 는 한 번 = 고르기, 두 번 · Enter = 열기). 자리를 한 번만 재고 실제 시간 60ms 사이로 두 번
|
||||
Future<void> doubleTapOn(Finder f) async {
|
||||
final at = t.getCenter(f);
|
||||
await t.tapAt(at);
|
||||
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 60)));
|
||||
await t.tapAt(at);
|
||||
await settle();
|
||||
await t.tap(find.text('열기').last);
|
||||
await settle();
|
||||
});
|
||||
await step('TC - Backspace (나오기)', () async {
|
||||
await t.sendKeyEvent(LogicalKeyboardKey.backspace);
|
||||
}
|
||||
|
||||
await step('TC - [Docs] 한 번 (고르기만)', () async {
|
||||
await t.tap(txt('Docs'));
|
||||
await settle(10);
|
||||
});
|
||||
await step('TC - Enter (들어가기)', () async {
|
||||
await t.sendKeyEvent(LogicalKeyboardKey.enter);
|
||||
await settle();
|
||||
});
|
||||
await step('TC - .. 두 번 (나오기)', () => doubleTapOn(leftOf(find.text('..'))));
|
||||
await step('TC - [Docs] 두 번 (들어가기)', () => doubleTapOn(txt('Docs')));
|
||||
await step('TC - .. 두 번 (다시 나오기)', () => doubleTapOn(leftOf(find.text('..'))));
|
||||
await step('TC - notes 고르고 F5 복사', () async {
|
||||
await t.tap(txt('notes.txt'));
|
||||
await settle(10); // 한 번 누르기는 두 번 누르기 기다림 (약 300ms) 뒤에 처리된다
|
||||
|
|
@ -338,6 +346,21 @@ void main() {
|
|||
});
|
||||
files('TC 단계 뒤');
|
||||
}
|
||||
// 관리자 10/23: "폴더 + 파일 목록" 배치 - 목록의 폴더는 한 번에 열리고, 습관처럼 곧바로 누른 두 번째 누르기는 버린다 (도로 나오지 않음)
|
||||
if (style == 'xplore' && w >= 960) {
|
||||
await step('목록 배치 - 폴더 + 파일 목록으로', () async {
|
||||
await c.updateSettings((x) => x.explorer.layout = 'split');
|
||||
await settle(20);
|
||||
});
|
||||
await step('목록 배치 - 오른쪽 목록 Movies 한 번 + 곧바로 두 번째', () async {
|
||||
final row = find.text('Movies').last; // 오른쪽 목록의 줄
|
||||
final at = t.getCenter(row);
|
||||
await t.tapAt(at);
|
||||
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 60)));
|
||||
await t.tapAt(at);
|
||||
await settle(20);
|
||||
});
|
||||
}
|
||||
// 끝 상태: 실제 파일
|
||||
final left = [
|
||||
for (final e in tmp.listSync(recursive: true))
|
||||
|
|
|
|||
|
|
@ -65,27 +65,32 @@ class ClickPolicy {
|
|||
/// 두 번째 누르기로 방금 펼친 것을 접지 않게).
|
||||
/// 사용자 설정 (처음 값은 규칙대로):
|
||||
/// [tapOpens] 파일 한 번 누르기 = 바로 실행 (설정 "바로 열기", 이때 두 번 누르기는 받지 않음) ·
|
||||
/// [retapCollapses] 펼친 폴더를 다시 누르면 접기 (끄면 [ClickAction.expand] - 화면은 펼친 폴더를 지금 폴더로만)
|
||||
/// [retapCollapses] 펼친 폴더를 다시 누르면 접기 (끄면 [ClickAction.expand] - 화면은 펼친 폴더를 지금 폴더로만) ·
|
||||
/// [doubleOpens] 폴더 · ".." 도 한 번 = 고르기, 두 번 = 열기 (Total Commander 모양 - 한 번에 열면 두 번 누른 사람은 두 번째 누르기가
|
||||
/// 같은 자리의 ".." 에 맞아 도로 나왔다, 관리자 10/23)
|
||||
static ClickAction decide(Press press, ItemKind kind,
|
||||
{required bool selectMode,
|
||||
bool expanded = false,
|
||||
bool list = false,
|
||||
bool repeat = false,
|
||||
bool tapOpens = false,
|
||||
bool retapCollapses = true}) {
|
||||
bool retapCollapses = true,
|
||||
bool doubleOpens = false}) {
|
||||
final real = kind == ItemKind.file || kind == ItemKind.folder;
|
||||
switch (press) {
|
||||
case Press.longPress:
|
||||
case Press.secondary:
|
||||
return real ? ClickAction.menu : ClickAction.none;
|
||||
case Press.doubleTap:
|
||||
// 두 번 = 실행 (파일만, 선택 모드 밖). 폴더 · 선택 모드 · 바로 열기는 한 번 누르기로 이미 처리됨
|
||||
// 두 번 = 실행 (파일만, 선택 모드 밖). 폴더 · 선택 모드 · 바로 열기는 한 번 누르기로 이미 처리됨.
|
||||
// [doubleOpens] 면 폴더 · ".." 도 두 번 = 열기
|
||||
if (doubleOpens && !selectMode && (kind == ItemKind.folder || kind == ItemKind.up)) return ClickAction.open;
|
||||
return kind == ItemKind.file && !selectMode && !tapOpens ? ClickAction.open : ClickAction.none;
|
||||
case Press.tap:
|
||||
if (kind == ItemKind.up) return ClickAction.open;
|
||||
if (kind == ItemKind.up) return doubleOpens ? ClickAction.focus : ClickAction.open;
|
||||
if (selectMode && real) return ClickAction.toggleMark;
|
||||
if (kind == ItemKind.file) return tapOpens ? ClickAction.open : ClickAction.focus;
|
||||
if (list && kind == ItemKind.folder) return ClickAction.open;
|
||||
if (list && kind == ItemKind.folder) return doubleOpens ? ClickAction.focus : ClickAction.open;
|
||||
if (!expanded || !retapCollapses) return ClickAction.expand;
|
||||
return repeat ? ClickAction.none : ClickAction.collapse;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart' show kDoubleTapTimeout;
|
||||
import 'package:flutter/scheduler.dart' show SchedulerBinding;
|
||||
import 'package:flutter/services.dart' show HardwareKeyboard;
|
||||
|
||||
import '../../app/archive_browse.dart';
|
||||
|
|
@ -9,6 +10,7 @@ import '../../core/explorer/tree_rows.dart';
|
|||
import '../../core/file_ops.dart' show FileEntry, isSameOrInside, samePath;
|
||||
import '../../core/selection.dart';
|
||||
import '../../core/vfs.dart' show isDav, vDirname;
|
||||
import '../explorer_look.dart' show ExplorerStyle;
|
||||
import '../file_error.dart' show explainFileError;
|
||||
import 'explorer_controller.dart';
|
||||
import 'explorer_pane.dart';
|
||||
|
|
@ -50,6 +52,9 @@ class ExplorerSelect extends ChangeNotifier {
|
|||
ctl.update();
|
||||
}
|
||||
|
||||
/// TC 모양의 목록: 폴더 · ".." 도 두 번 · Enter 로 연다 ([ClickPolicy.decide] 의 doubleOpens)
|
||||
bool doubleOpens({required bool list}) => list && !ctl.rsync && ctl.look == ExplorerStyle.totalcmd;
|
||||
|
||||
/// 줄의 종류 ([ClickPolicy] 에 넘김)
|
||||
static ItemKind kindOf(TreeRow row) => row.isUp
|
||||
? ItemKind.up
|
||||
|
|
@ -73,6 +78,12 @@ class ExplorerSelect extends ChangeNotifier {
|
|||
if (!selecting) setSelecting(true);
|
||||
return toggleMark(pane, e);
|
||||
}
|
||||
// 관리자 10/23: 목록에서 한 번 눌러 폴더를 연 직후, 두 번 누르기 시간 안에 온 다음 누르기는 버린다 - 습관처럼 두 번 누르면
|
||||
// 두 번째 누르기가 같은 자리에 온 ".." · 다른 줄에 맞아 도로 나오거나 엉뚱한 것을 열었다. 마지막 틀 시각으로 잰다 (누르기의 잉크가
|
||||
// 틀을 그려 누를 때마다 새로 정해진다 · 시험은 시험 시간)
|
||||
final frame = SchedulerBinding.instance.currentSystemFrameTimeStamp;
|
||||
final opened = _listOpenedAt;
|
||||
if (list && opened != null && frame - opened < kDoubleTapTimeout) return;
|
||||
final now = clock();
|
||||
final last = _lastFolderTap;
|
||||
_lastFolderTap = (e.path, now);
|
||||
|
|
@ -83,10 +94,15 @@ class ExplorerSelect extends ChangeNotifier {
|
|||
list: list,
|
||||
repeat: repeat,
|
||||
tapOpens: !selectMode,
|
||||
retapCollapses: ctl.c.settings.explorer.retapCollapse);
|
||||
retapCollapses: ctl.c.settings.explorer.retapCollapse,
|
||||
doubleOpens: doubleOpens(list: list));
|
||||
if (list && act == ClickAction.open && (row.isUp || e.isDir)) _listOpenedAt = frame;
|
||||
await _act(pane, row, act, list: list);
|
||||
}
|
||||
|
||||
/// 목록에서 한 번 누르기로 폴더 · ".." 를 연 틀 시각 (그 직후의 두 번째 누르기를 버리려고)
|
||||
Duration? _listOpenedAt;
|
||||
|
||||
Future<void> _act(ExplorerPane pane, TreeRow row, ClickAction act, {bool list = false}) async {
|
||||
final e = row.entry;
|
||||
switch (act) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../../core/file_ops.dart' show samePath;
|
||||
import '../../core/vfs.dart' show vDirname;
|
||||
import '../../core/vfs.dart' show vDirname, vIsDirSync;
|
||||
import '../explorer_look.dart' show ExplorerStyle;
|
||||
import '../explorer_keys.dart' show defaultExplorerKeys, effectiveExplorerKeys;
|
||||
import 'explorer_buttons.dart';
|
||||
import 'explorer_controller.dart';
|
||||
|
|
@ -105,6 +106,17 @@ class ExplorerInput {
|
|||
// 75: 지금 창의 뒤로 · 앞으로
|
||||
'back': () => ctl.travel(ctl.panes[ctl.active], forward: false),
|
||||
'forward': () => ctl.travel(ctl.panes[ctl.active], forward: true),
|
||||
// TC 모양 Enter: 지금 줄을 연다 - 폴더 (".." 은 위 폴더) 는 들어가기, 파일은 열기
|
||||
'open': () {
|
||||
final pane = ctl.pane;
|
||||
final f = pane.focused;
|
||||
if (f == null) return;
|
||||
if (vIsDirSync(f)) {
|
||||
ctl.goTo(pane, f);
|
||||
} else {
|
||||
flows.opener.open(context, f, pane: pane);
|
||||
}
|
||||
},
|
||||
'switchPane': () {
|
||||
ctl
|
||||
..active = dual() ? 1 - ctl.active : 0
|
||||
|
|
@ -117,7 +129,7 @@ class ExplorerInput {
|
|||
if (const {'delete', 'deletePermanent', 'rename', 'refresh', 'filter', 'up', 'back', 'forward', 'switchPane'}.contains(e.key))
|
||||
e.key: e.value,
|
||||
}
|
||||
: effectiveExplorerKeys(tc: _tcKeys, overrides: ctl.c.settings.explorer.keyOverrides);
|
||||
: effectiveExplorerKeys(tc: _tcKeys, tcLook: ctl.look == ExplorerStyle.totalcmd, overrides: ctl.c.settings.explorer.keyOverrides);
|
||||
return {
|
||||
for (final e in keys.entries)
|
||||
if (cb[e.key] != null)
|
||||
|
|
|
|||
|
|
@ -237,7 +237,8 @@ class ExplorerRowView extends StatelessWidget {
|
|||
onTap: () => sel.onTap(pane, row, list: list),
|
||||
// 선택 모드: 두 번 누르면 열기 (바로 열기 모드는 두 번 누르기를 받지 않음 - 한 번 누르기가 늦어지지 않게)
|
||||
// 폴더는 한 번 누르기로 바로 펼치므로 두 번 누르기를 받지 않는다 (기다리지 않게)
|
||||
onDoubleTap: ClickPolicy.decide(Press.doubleTap, ExplorerSelect.kindOf(row), selectMode: sel.selecting, tapOpens: !sel.selectMode) ==
|
||||
onDoubleTap: ClickPolicy.decide(Press.doubleTap, ExplorerSelect.kindOf(row),
|
||||
selectMode: sel.selecting, tapOpens: !sel.selectMode, doubleOpens: sel.doubleOpens(list: list)) ==
|
||||
ClickAction.open
|
||||
? () => sel.onOpen(pane, row, list: list)
|
||||
: null,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import '../l10n/tr.dart';
|
|||
/// 바꾼 키가 다른 동작의 키와 같으면 바꾼 쪽이 이기고 그 동작은 그 키를 잃는다 (설정 화면이 먼저 알린다). Esc 는 바꾸지 않는다.
|
||||
const explorerKeyActions = <String>[
|
||||
'copy', 'move', 'newFolder', 'delete', 'deletePermanent', 'rename', 'refresh', 'quickLook', //
|
||||
'clipCopy', 'clipCut', 'paste', 'selectAll', 'mark', 'markNext', 'filter', 'up', 'back', 'forward', 'switchPane',
|
||||
'clipCopy', 'clipCut', 'paste', 'selectAll', 'mark', 'markNext', 'filter', 'up', 'back', 'forward', 'switchPane', 'open',
|
||||
];
|
||||
|
||||
/// 동작 이름 (화면 글)
|
||||
|
|
@ -32,11 +32,14 @@ String explorerKeyLabel(String id) => switch (id) {
|
|||
'back' => tr('뒤로'),
|
||||
'forward' => tr('앞으로'),
|
||||
'switchPane' => tr('다른 창으로 옮겨 가기'),
|
||||
'open' => tr('열기 (폴더는 들어가기)'),
|
||||
_ => id,
|
||||
};
|
||||
|
||||
/// 처음 키. [tc] Total Commander 키 (규칙 1, 10/22: Windows 는 스타일과 상관없이 TC 키 + Ctrl+C · X · V · A)
|
||||
Map<String, List<SingleActivator>> defaultExplorerKeys({required bool tc}) => {
|
||||
/// 처음 키. [tc] Total Commander 키 (규칙 1, 10/22: Windows 는 스타일과 상관없이 TC 키 + Ctrl+C · X · V · A).
|
||||
/// [tcLook] Total Commander 모양 - Enter = 열기 (관리자 10/23, 다른 모양의 Enter 는 그대로 - 처음 키 없음)
|
||||
Map<String, List<SingleActivator>> defaultExplorerKeys({required bool tc, bool tcLook = false}) => {
|
||||
if (tcLook) 'open': [const SingleActivator(LogicalKeyboardKey.enter), const SingleActivator(LogicalKeyboardKey.numpadEnter)],
|
||||
'delete': [const SingleActivator(LogicalKeyboardKey.delete), if (tc) const SingleActivator(LogicalKeyboardKey.f8)],
|
||||
'deletePermanent': [const SingleActivator(LogicalKeyboardKey.delete, shift: true)],
|
||||
'rename': [const SingleActivator(LogicalKeyboardKey.f2)],
|
||||
|
|
@ -93,8 +96,8 @@ String keyText(SingleActivator a) {
|
|||
}
|
||||
|
||||
/// 바꾼 것을 넣은 동작마다의 키. 바꾼 키는 다른 동작에서 뺀다
|
||||
Map<String, List<SingleActivator>> effectiveExplorerKeys({required bool tc, required Map<String, String> overrides}) {
|
||||
final out = {for (final e in defaultExplorerKeys(tc: tc).entries) e.key: [...e.value]};
|
||||
Map<String, List<SingleActivator>> effectiveExplorerKeys({required bool tc, bool tcLook = false, required Map<String, String> overrides}) {
|
||||
final out = {for (final e in defaultExplorerKeys(tc: tc, tcLook: tcLook).entries) e.key: [...e.value]};
|
||||
final mine = <String, SingleActivator?>{};
|
||||
for (final e in overrides.entries) {
|
||||
if (!explorerKeyActions.contains(e.key)) continue;
|
||||
|
|
@ -115,9 +118,9 @@ Map<String, List<SingleActivator>> effectiveExplorerKeys({required bool tc, requ
|
|||
}
|
||||
|
||||
/// [action] 에 [key] 를 주면 키를 잃는 다른 동작 (없으면 null). Esc 는 쓸 수 없음 ('escape')
|
||||
String? keyConflict(String action, SingleActivator key, {required bool tc, required Map<String, String> overrides}) {
|
||||
String? keyConflict(String action, SingleActivator key, {required bool tc, bool tcLook = false, required Map<String, String> overrides}) {
|
||||
if (key.trigger == LogicalKeyboardKey.escape) return 'escape';
|
||||
final now = effectiveExplorerKeys(tc: tc, overrides: overrides);
|
||||
final now = effectiveExplorerKeys(tc: tc, tcLook: tcLook, overrides: overrides);
|
||||
for (final e in now.entries) {
|
||||
if (e.key != action && e.value.any((x) => sameKey(x, key))) return e.key;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class ExplorerKeysPage extends StatelessWidget {
|
|||
listenable: c,
|
||||
builder: (context, _) {
|
||||
final ov = c.settings.explorer.keyOverrides;
|
||||
final keys = effectiveExplorerKeys(tc: _tc, overrides: ov);
|
||||
final keys = effectiveExplorerKeys(tc: _tc, tcLook: _tc, overrides: ov);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(tr('파일 탐색기 단축키')),
|
||||
|
|
@ -101,7 +101,7 @@ class _KeyCaptureDialogState extends State<_KeyCaptureDialog> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final k = _key;
|
||||
final clash = k == null ? null : keyConflict(widget.action, k, tc: widget.tc, overrides: widget.c.settings.explorer.keyOverrides);
|
||||
final clash = k == null ? null : keyConflict(widget.action, k, tc: widget.tc, tcLook: widget.tc, overrides: widget.c.settings.explorer.keyOverrides);
|
||||
return AlertDialog(
|
||||
title: Text(trf('{0} - 단축키', [explorerKeyLabel(widget.action)])),
|
||||
content: Focus(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show LogicalKeyboardKey;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jj_mkvmaker/ui/explorer/tc_key_bar.dart';
|
||||
import 'package:jj_mkvmaker/ui/explorer_page.dart';
|
||||
|
|
@ -98,4 +99,37 @@ void main() {
|
|||
final focusedRow = find.byWidgetPredicate((w) => w is Material && w.shape is Border);
|
||||
expect(find.descendant(of: focusedRow, matching: find.text('e')), findsOneWidget, reason: '지운 줄의 다음 줄 (e.txt) 이 지금 줄 (왼쪽 강조 막대)');
|
||||
});
|
||||
|
||||
testWidgets('TC: 폴더 한 번 = 고르기 (들어가지 않음) · Enter = 들어가기 · ".." 두 번 = 나오기 (관리자 10/23)', (t) async {
|
||||
c.settings.explorer.style = 'totalcmd';
|
||||
await open(t);
|
||||
await settle(t, () => find.text('[sub]').evaluate().isNotEmpty);
|
||||
await act(t, () => t.tap(find.text('[sub]').first));
|
||||
await settle(t, () => false, rounds: 15);
|
||||
expect(find.text('inner'), findsNothing, reason: '한 번 누르기는 고르기만');
|
||||
expect(find.text('[sub]'), findsWidgets);
|
||||
await act(t, () => t.sendKeyEvent(LogicalKeyboardKey.enter));
|
||||
await settle(t, () => find.text('inner').evaluate().isNotEmpty);
|
||||
expect(find.text('inner'), findsOneWidget, reason: 'Enter 로 들어감');
|
||||
await doubleTap(t, find.text('..').first);
|
||||
await settle(t, () => find.text('[sub]').evaluate().isNotEmpty);
|
||||
expect(find.text('inner'), findsNothing, reason: '".." 두 번으로 나옴');
|
||||
});
|
||||
|
||||
testWidgets('폴더 + 파일 목록: 한 번 눌러 폴더를 연 직후 두 번째 누르기는 버린다 (도로 나오지 않음) - 한 번 열기는 그대로', (t) async {
|
||||
c.settings.explorer.layout = 'split'; // 하네스는 X-plore
|
||||
await open(t);
|
||||
final list = find.text('sub').last; // 오른쪽 목록의 줄
|
||||
await act(t, () => t.tap(list));
|
||||
await t.pump(const Duration(milliseconds: 50));
|
||||
// 습관처럼 두 번째 누르기: 같은 자리 (열린 폴더의 ".." 자리)
|
||||
await act(t, () => t.tapAt(t.getCenter(list)));
|
||||
await settle(t, () => find.text('inner.txt').evaluate().isNotEmpty);
|
||||
expect(find.text('inner.txt'), findsWidgets, reason: '한 번 누르기로 열렸고 두 번째 누르기에 도로 나오지 않음');
|
||||
await calm(t);
|
||||
await t.pump(const Duration(seconds: 1));
|
||||
await act(t, () => t.tap(find.text('..').last));
|
||||
await settle(t, () => find.text('inner.txt').evaluate().isEmpty);
|
||||
expect(find.text('inner.txt'), findsNothing, reason: '시간이 지난 뒤의 ".." 한 번은 그대로 위로');
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,16 @@ void main() {
|
|||
}
|
||||
test('사용자 설정: 바로 열기 · 다시 눌러 접지 않기', () {
|
||||
expect(ClickPolicy.decide(Press.tap, ItemKind.file, selectMode: false, tapOpens: true), ClickAction.open);
|
||||
// TC 모양 (doubleOpens, 관리자 10/23): 폴더 · ".." 도 한 번 = 고르기, 두 번 = 열기 (선택 모드는 그대로 표시)
|
||||
expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: false, list: true, doubleOpens: true), ClickAction.focus);
|
||||
expect(ClickPolicy.decide(Press.tap, ItemKind.up, selectMode: false, list: true, doubleOpens: true), ClickAction.focus);
|
||||
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.folder, selectMode: false, list: true, doubleOpens: true), ClickAction.open);
|
||||
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.up, selectMode: false, list: true, doubleOpens: true), ClickAction.open);
|
||||
expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: true, list: true, doubleOpens: true), ClickAction.toggleMark);
|
||||
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.folder, selectMode: true, list: true, doubleOpens: true), ClickAction.none);
|
||||
// 그 밖의 목록 (폴더 + 파일 목록) 은 한 번 = 열기 그대로
|
||||
expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: false, list: true), ClickAction.open);
|
||||
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.folder, selectMode: false, list: true), ClickAction.none);
|
||||
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.file, selectMode: false, tapOpens: true), ClickAction.none);
|
||||
expect(ClickPolicy.decide(Press.tap, ItemKind.file, selectMode: true, tapOpens: true), ClickAction.toggleMark);
|
||||
expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: false, expanded: true, retapCollapses: false),
|
||||
|
|
|
|||
Loading…
Reference in New Issue