WIP X-plore b: 가운데 버튼 길게 누르기 = 짝 동작 (복사 ↔ 이동, 삭제 → 영구 삭제로 묻기) (검토 대기)

15 비교표 P1 b (X-plore 의 버튼 길게 누르기). 확인 창은 그대로 - 영구 삭제는 처음 고른 칸만 영구로 (Shift+Delete 와 같음).
풍선 도움말에 "길게 누르면 …". Rsync 화면은 없음. 시험: explorer_test "X-plore b".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xplore_p3
RyuJongJin 2026-10-17 23:05:26 +09:00
parent 818b4a0615
commit f9cd5f98c9
5 changed files with 54 additions and 1 deletions

View File

@ -730,6 +730,7 @@
"기억한 복사가 없습니다. 파일 탐색기에서 복사 · 이동하면 여기에 자동으로 등록됩니다.": "No remembered copies. Copies · moves in the file explorer are added here automatically.",
"기타 즐겨찾기": "Other bookmarks",
"길게 누르기 · 오른쪽 클릭은 늘 기능 메뉴 (복사 · 이동 · 삭제 · 이름 변경 …). Ctrl + 클릭: 여러 개 고르기": "Long press · right click always opens the action menu (copy · move · delete · rename …). Ctrl + click: select several",
"길게 누르면 {0}": "long-press: {0}",
"길이": "Length",
"길이 {0}": "Length {0}",
"끄기": "Off",

View File

@ -730,6 +730,7 @@
"기억한 복사가 없습니다. 파일 탐색기에서 복사 · 이동하면 여기에 자동으로 등록됩니다.": "記憶したコピーはありません。ファイルエクスプローラーでコピー · 移動すると自動で登録されます。",
"기타 즐겨찾기": "その他のブックマーク",
"길게 누르기 · 오른쪽 클릭은 늘 기능 메뉴 (복사 · 이동 · 삭제 · 이름 변경 …). Ctrl + 클릭: 여러 개 고르기": "長押し · 右クリックは常に機能メニュー (コピー · 移動 · 削除 · 名前の変更 …)。Ctrl + クリック: 複数選択",
"길게 누르면 {0}": "長押しで{0}",
"길이": "長さ",
"길이 {0}": "長さ {0}",
"끄기": "オフ",

View File

@ -730,6 +730,7 @@
"기억한 복사가 없습니다. 파일 탐색기에서 복사 · 이동하면 여기에 자동으로 등록됩니다.": "没有记住的复制。在文件浏览器中复制 · 移动后会自动登记到这里。",
"기타 즐겨찾기": "其他书签",
"길게 누르기 · 오른쪽 클릭은 늘 기능 메뉴 (복사 · 이동 · 삭제 · 이름 변경 …). Ctrl + 클릭: 여러 개 고르기": "长按 · 右键总是打开功能菜单 (复制 · 移动 · 删除 · 重命名 …)。Ctrl + 点击: 多选",
"길게 누르면 {0}": "长按: {0}",
"길이": "时长",
"길이 {0}": "时长 {0}",
"끄기": "关闭",

View File

@ -1718,6 +1718,21 @@ class _ExplorerPageState extends State<ExplorerPage> {
}
}
/// X-plore b: 가운데 버튼 길게 누르기 - 복사 ↔ 이동, 지우기 → 영구 삭제로 묻기 (확인 창은 그대로)
Future<void> _buttonLong(ExplorerButton b) async {
switch (b) {
case ExplorerButton.copy:
return _button(ExplorerButton.move);
case ExplorerButton.move:
return _button(ExplorerButton.copy);
case ExplorerButton.delete:
_shiftDelete = true;
return _button(ExplorerButton.delete);
default:
return;
}
}
Future<void> _button(ExplorerButton b) async {
final pane = _pane;
switch (b) {
@ -3461,6 +3476,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
final bar = s.explorerToolbar == 'hidden' && !widget.rsync
? null
: _Toolbar(
// X-plore b: 길게 누르기 = 짝 동작 (Rsync 화면은 없음)
onLongPress: widget.rsync ? null : _buttonLong,
// Rsync 화면: 늘 같은 버튼 (→ ← ⇄ 모니터링) 을 두 창 사이에
buttons: widget.rsync ? ExplorerButton.rsyncButtons : ExplorerButton.fromSettings(s.explorerButtons),
vertical: side && !rsyncOne,
@ -4511,12 +4528,16 @@ class _Toolbar extends StatelessWidget {
/// 좁아도 [더 보기] 로 보내지 않는 버튼 (창 배치 · 담아 둔 것이 있을 때 붙여넣기)
final Set<ExplorerButton> pinned;
/// X-plore b: 길게 누르기 = 짝 동작 (복사 ↔ 이동, 지우기 → 영구 삭제). 짝이 없는 버튼은 null
final void Function(ExplorerButton)? onLongPress;
const _Toolbar(
{required this.buttons,
required this.vertical,
required this.onPressed,
required this.enabled,
required this.selected,
this.onLongPress,
this.twoPanes = true,
this.pinned = const {ExplorerButton.orient},
bool? panesSide})
@ -4526,6 +4547,14 @@ class _Toolbar extends StatelessWidget {
/// 191 좁은 Rsync 화면 (창 하나씩, 버튼 줄은 가로) 은 좌우
final bool panesSide;
/// X-plore b: 길게 누르면 하는 짝 동작의 이름 (없으면 null)
static String? longPressLabel(ExplorerButton b) => switch (b) {
ExplorerButton.copy => '이동',
ExplorerButton.move => '복사',
ExplorerButton.delete => '영구 삭제',
_ => null,
};
/// 118: 지금 배치를 보여 준다 (누르면 반대로)
String _orientLabel() => !twoPanes ? '두 창으로' : panesSide ? '좌우 ⇆' : '위아래 ⇅';
String _orientTip() => !twoPanes
@ -4562,10 +4591,14 @@ class _Toolbar extends StatelessWidget {
Widget btn(ExplorerButton b) {
final on = enabled(b);
final sel = selected(b);
final pair = longPressLabel(b);
final long = onLongPress;
return Tooltip(
message: tr(b == ExplorerButton.orient ? _orientTip() : _label(b)),
message: tr(b == ExplorerButton.orient ? _orientTip() : _label(b)) +
(pair != null && long != null ? ' · ${trf('길게 누르면 {0}', [tr(pair)])}' : ''),
child: InkWell(
onTap: on ? () => onPressed(b) : null,
onLongPress: on && pair != null && long != null ? () => long(b) : null,
borderRadius: BorderRadius.circular(6),
child: SizedBox(
width: 72,

View File

@ -159,6 +159,23 @@ void main() {
await tester.pump(const Duration(seconds: 2));
});
testWidgets('X-plore b: 가운데 버튼 길게 누르기 = 짝 동작 - [복사] 길게 = 이동 창 · [삭제] 길게 = 영구 삭제 창', (tester) async {
await open(tester);
await act(tester, () => tester.tap(find.text('doc.txt')));
await settle(tester, () => false, rounds: 15);
await tester.longPress(find.text('복사').first);
await calm(tester);
expect(find.widgetWithText(FilledButton, '이동'), findsOneWidget, reason: '복사 길게 = 이동');
await tester.tap(find.text('취소').last);
await calm(tester);
await tester.longPress(find.text('삭제').first);
await calm(tester);
expect(find.widgetWithText(FilledButton, '영구 삭제'), findsOneWidget, reason: '삭제 길게 = 영구 삭제로 묻기');
await tester.tap(find.text('취소').last);
await calm(tester);
expect(File(p.join(left, 'doc.txt')).existsSync(), isTrue);
});
testWidgets('178 보충: 설정의 원본 지우기 옵션은 복사 창에서 빠진 까닭을 알린다 (이동 창에는 없음)', (tester) async {
c.settings
..copyMethodFile = 'robocopy'