From fba28cb027451dc00ffd06101e6bf7626297aef9 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Sun, 18 Oct 2026 00:55:49 +0900 Subject: [PATCH] =?UTF-8?q?WIP=20X-plore=20P3=20=EC=97=B0=EA=B2=B0:=20?= =?UTF-8?q?=EA=B8=B8=EA=B2=8C=20=EB=88=84=EB=A5=B4=EA=B8=B0=20[=ED=95=B4?= =?UTF-8?q?=EC=8B=9C]=20=C2=B7=20[=ED=8F=B4=EB=8D=94=20=EC=86=8D=EC=84=B1]?= =?UTF-8?q?,=20=EB=B9=A0=EB=A5=B8=20=EB=B3=B4=EA=B8=B0=20[16=EC=A7=84]=20(?= =?UTF-8?q?=EA=B2=80=ED=86=A0=20=EB=8C=80=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 관리자 지시 (10/18): cd 의 도우미 (b6e4717, lib/ui/file_tools.dart) 를 탐색기에 연결. - 길게 누르기 메뉴 (폴더 크기 계산 옆 - cd 가 넣을 격자 · 글 뷰어 항목과 떨어진 곳): · 파일: [해시 (MD5 · SHA-256)] → showFileHash (진행 · 취소, 복사, 붙여 넣은 값과 비교) · 폴더: [폴더 속성] → showFolderProperties (파일 · 폴더 수 · 크기 · 링크 · 만든 · 바뀐 때) - 빠른 보기 (81) 창: [16진] → HexViewerPage (앞부분부터 나눠 읽기) - 내 판단: 셋 다 이 기기의 파일만 (원격 WebDAV 는 해시 · 16진 · 속성이 파일을 다 받아야 해서 메뉴에서 뺌) 시험: explorer_test 에 연결 시험 (해시 "hello" 의 MD5 · SHA-256 값, sub 의 "파일 1개 · 폴더 0개", 빠른 보기 → 16진 화면) - explorer · file_tools · quick_look 57 통과, analyze 문제 없음, l10n 빠짐 0. 캡처는 아직 (Windows 캡처 시험은 다음). j (SMB 찾기) 는 이 브랜치에 88 (SMB) 이 없어 smb_find (묶음7_88 위) 에서 (관리자 지시). Co-Authored-By: Claude Opus 5.5 --- assets/l10n/en.json | 3 +++ assets/l10n/ja.json | 3 +++ assets/l10n/zh-Hans.json | 3 +++ lib/ui/explorer_page.dart | 8 ++++++++ lib/ui/quick_look.dart | 7 +++++++ test/explorer_test.dart | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 57 insertions(+) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 8d455ed..5249f82 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -71,6 +71,7 @@ "1/2 · AI 그림으로 다듬는 중": "1/2 · Refining with AI image", "10초 뒤로 (←)": "Back 10 seconds (←)", "10초 앞으로 (→)": "Forward 10 seconds (→)", + "16진": "Hex", "16진 보기 · {0}": "Hex view · {0}", "1배 (선명하게)": "1× (sharpen)", "1분 안": "under 1 min", @@ -2413,6 +2414,7 @@ "폴더 복사 · 이동 방법": "Folder copy · move method", "폴더 삭제: {0} (안의 {1}개 포함)": "Folder deleted: {0} (including {1} inside)", "폴더 선택": "Choose folder", + "폴더 속성": "Folder properties", "폴더 속성 · {0}": "Folder properties · {0}", "폴더 수정": "Edit folder", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "Shows the total size of the files inside each folder. Large folders or a NAS can take a while to read. Long-press [Calculate folder size] always works", @@ -2508,6 +2510,7 @@ "해상도 올리기 모델을 받아야 합니다. 어느 것을 받을까요?": "An upscaling model is needed. Which one should be downloaded?", "해상도를 올리지 못했습니다": "Couldn't upscale", "해상도를 올린 그림 저장 폴더": "Folder for upscaled images", + "해시 (MD5 · SHA-256)": "Hash (MD5 · SHA-256)", "해시 · {0}": "Hash · {0}", "해제": "Unset", "허용": "Allow", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 36146db..4af21e1 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -71,6 +71,7 @@ "1/2 · AI 그림으로 다듬는 중": "1/2 · AI 画像で整え中", "10초 뒤로 (←)": "10秒戻る(←)", "10초 앞으로 (→)": "10秒進む(→)", + "16진": "16進", "16진 보기 · {0}": "16進表示 · {0}", "1배 (선명하게)": "1倍 (くっきり)", "1분 안": "1 分以内", @@ -2413,6 +2414,7 @@ "폴더 복사 · 이동 방법": "フォルダーのコピー · 移動方法", "폴더 삭제: {0} (안의 {1}개 포함)": "フォルダーを削除: {0}(中の {1}個を含む)", "폴더 선택": "フォルダーを選択", + "폴더 속성": "フォルダーのプロパティ", "폴더 속성 · {0}": "フォルダーのプロパティ · {0}", "폴더 수정": "フォルダーを編集", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "フォルダー内のファイルサイズの合計を一覧に表示します。大きなフォルダー・NAS は読み込みに時間がかかることがあります。長押しの [フォルダーサイズを計算] はいつでも使えます", @@ -2508,6 +2510,7 @@ "해상도 올리기 모델을 받아야 합니다. 어느 것을 받을까요?": "解像度アップのモデルが必要です。どれをダウンロードしますか?", "해상도를 올리지 못했습니다": "高解像度化できませんでした", "해상도를 올린 그림 저장 폴더": "高解像度化した画像の保存フォルダー", + "해시 (MD5 · SHA-256)": "ハッシュ (MD5 · SHA-256)", "해시 · {0}": "ハッシュ · {0}", "해제": "解除", "허용": "許可", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 0a790fa..569e4ab 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -71,6 +71,7 @@ "1/2 · AI 그림으로 다듬는 중": "1/2 · 正在用 AI 图片优化", "10초 뒤로 (←)": "后退 10 秒(←)", "10초 앞으로 (→)": "前进 10 秒(→)", + "16진": "十六进制", "16진 보기 · {0}": "十六进制查看 · {0}", "1배 (선명하게)": "1倍 (锐化)", "1분 안": "1 分钟以内", @@ -2413,6 +2414,7 @@ "폴더 복사 · 이동 방법": "文件夹复制 · 移动方式", "폴더 삭제: {0} (안의 {1}개 포함)": "已删除文件夹: {0}(含其中 {1} 个)", "폴더 선택": "选择文件夹", + "폴더 속성": "文件夹属性", "폴더 속성 · {0}": "文件夹属性 · {0}", "폴더 수정": "编辑文件夹", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "在列表中显示文件夹内文件大小的总和。大型文件夹或 NAS 可能需要较长时间读取。长按 [计算文件夹大小] 始终可用", @@ -2508,6 +2510,7 @@ "해상도 올리기 모델을 받아야 합니다. 어느 것을 받을까요?": "需要放大模型。要下载哪一个?", "해상도를 올리지 못했습니다": "无法提高分辨率", "해상도를 올린 그림 저장 폴더": "提高分辨率后图片的保存文件夹", + "해시 (MD5 · SHA-256)": "哈希 (MD5 · SHA-256)", "해시 · {0}": "哈希 · {0}", "해제": "解除", "허용": "允许", diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index b033aee..f174275 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -47,6 +47,7 @@ import '../l10n/tr.dart'; import 'explorer_bookmarks.dart'; import 'explorer_search.dart'; import 'quick_look.dart'; +import 'file_tools.dart' show showFileHash, showFolderProperties; import 'explorer_drag.dart'; import 'archive_ui.dart'; import 'storage_page.dart'; @@ -1605,6 +1606,9 @@ class _ExplorerPageState extends State { if (e.isDir) ('storage', Icons.pie_chart_outline, tr('저장 공간 분석')), // 80: 이 폴더 크기를 지금 계산 (설정이 "안 함" 이어도) if (e.isDir) ('folderSize', Icons.data_usage, _sizes.busy(e.path) ? tr('폴더 크기 계산 멈추기') : tr('폴더 크기 계산')), + // X-plore P3: 폴더 속성 (안의 파일 · 폴더 수 · 크기 · 만든 · 바뀐 때) · 파일 해시 (MD5 · SHA-256) - 이 기기의 것만 (원격은 다 받아야 해서 뺌) + if (e.isDir && !dav) ('props', Icons.fact_check_outlined, tr('폴더 속성')), + if (!e.isDir && !dav) ('hash', Icons.tag, tr('해시 (MD5 · SHA-256)')), if (e.isDir && dual) ('openOther', Icons.vertical_split_outlined, tr('다른 창에서 열기')), if (e.isDir) ('playFolder', Icons.play_circle_outline, tr('이 폴더의 동영상 재생')), if (video || audio) ('playInternal', Icons.play_circle_outline, tr('내장 플레이어로 재생')), @@ -1705,6 +1709,10 @@ class _ExplorerPageState extends State { pane.focused = made; pane.changed(); } + case 'props': + await showFolderProperties(context, e.path); + case 'hash': + await showFileHash(context, e.path); case 'folderSize': if (_sizes.busy(e.path)) { _sizes.cancel(e.path); diff --git a/lib/ui/quick_look.dart b/lib/ui/quick_look.dart index ea00442..af9d73e 100644 --- a/lib/ui/quick_look.dart +++ b/lib/ui/quick_look.dart @@ -11,6 +11,7 @@ import '../core/models.dart'; import '../core/playlist.dart' show isAudioFile, isVideoFile; import '../core/vfs.dart'; import '../l10n/tr.dart'; +import 'file_tools.dart' show HexViewerPage; import 'theme.dart'; /// 81: 빠른 보기에서 글로 보는 확장자 @@ -209,6 +210,12 @@ class _QuickLookState extends State { ]), ), actions: [ + // X-plore P3: 16진 보기 (이 기기의 파일만 - 앞부분부터 나눠 읽기) + if (!e.isDir && !isDav(e.path)) + TextButton( + onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => HexViewerPage(path: e.path))), + child: Text(tr('16진')), + ), TextButton(onPressed: () => Navigator.pop(context), child: Text(tr('닫기'))), FilledButton( onPressed: () { diff --git a/test/explorer_test.dart b/test/explorer_test.dart index df5d64a..0c2f7ad 100644 --- a/test/explorer_test.dart +++ b/test/explorer_test.dart @@ -15,6 +15,7 @@ import 'package:jj_mkvmaker/services/platform_services.dart'; import 'package:jj_mkvmaker/core/file_ops.dart' show formatSize; import 'package:jj_mkvmaker/ui/explorer_page.dart'; import 'package:jj_mkvmaker/ui/explorer_search.dart'; +import 'package:jj_mkvmaker/ui/file_tools.dart' show HexViewerPage; import 'package:jj_mkvmaker/ui/quick_look.dart'; import 'package:jj_mkvmaker/ui/explorer_drag.dart'; import 'package:jj_mkvmaker/ui/storage_page.dart'; @@ -160,6 +161,38 @@ void main() { await tester.pump(const Duration(seconds: 2)); }); + testWidgets('X-plore P3: 길게 누르기 [해시] (MD5 · SHA-256) · 폴더 [폴더 속성] · 빠른 보기 [16진]', (tester) async { + await open(tester); + // 해시: doc.txt = "hello" + await tester.longPress(find.text('doc.txt')); + await calm(tester); + await act(tester, () => tester.tap(find.text('해시 (MD5 · SHA-256)'))); + await settle(tester, () => find.textContaining('2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824').evaluate().isNotEmpty); + expect(find.textContaining('5d41402abc4b2a76b9719d911017c592'), findsOneWidget, reason: 'MD5'); + await act(tester, () => tester.tap(find.text('닫기').last)); + await calm(tester); + // 폴더 속성: sub 안에 inner.txt 하나 + await tester.longPress(find.text('sub')); + await calm(tester); + await act(tester, () => tester.tap(find.text('폴더 속성'))); + await settle(tester, () => find.text('파일 1개 · 폴더 0개').evaluate().isNotEmpty); + await act(tester, () => tester.tap(find.text('닫기').last)); + await calm(tester); + // 빠른 보기 → [16진] + await tester.longPress(find.text('doc.txt')); + await calm(tester); + await act(tester, () => tester.tap(find.text('미리 보기'))); + await settle(tester, () => find.text('16진').evaluate().isNotEmpty); + await act(tester, () => tester.tap(find.text('16진'))); + await settle(tester, () => find.byType(HexViewerPage).evaluate().isNotEmpty); + expect(find.byType(HexViewerPage), findsOneWidget); + // 16진 보기가 앞부분을 다 읽어 파일을 놓을 때까지 (실제 시간) 기다린 뒤 닫는다 - 열린 파일이 남으면 시험 폴더를 못 지운다 + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 300))); + await tester.pump(); + await act(tester, () => tester.pageBack()); + await calm(tester); + }); + testWidgets('X-plore b: 가운데 버튼 길게 누르기 = 짝 동작 - [복사] 길게 = 이동 창 · [삭제] 길게 = 영구 삭제 창', (tester) async { await open(tester); await act(tester, () => tester.tap(find.text('doc.txt')));