WIP X-plore P3 연결: 길게 누르기 [해시] · [폴더 속성], 빠른 보기 [16진] (검토 대기)

관리자 지시 (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 <noreply@anthropic.com>
xplore_p3
RyuJongJin 2026-10-18 00:55:49 +09:00
parent b6e47178e3
commit fba28cb027
6 changed files with 57 additions and 0 deletions

View File

@ -71,6 +71,7 @@
"1/2 · AI 그림으로 다듬는 중": "1/2 · Refining with AI image", "1/2 · AI 그림으로 다듬는 중": "1/2 · Refining with AI image",
"10초 뒤로 (←)": "Back 10 seconds (←)", "10초 뒤로 (←)": "Back 10 seconds (←)",
"10초 앞으로 (→)": "Forward 10 seconds (→)", "10초 앞으로 (→)": "Forward 10 seconds (→)",
"16진": "Hex",
"16진 보기 · {0}": "Hex view · {0}", "16진 보기 · {0}": "Hex view · {0}",
"1배 (선명하게)": "1× (sharpen)", "1배 (선명하게)": "1× (sharpen)",
"1분 안": "under 1 min", "1분 안": "under 1 min",
@ -2413,6 +2414,7 @@
"폴더 복사 · 이동 방법": "Folder copy · move method", "폴더 복사 · 이동 방법": "Folder copy · move method",
"폴더 삭제: {0} (안의 {1}개 포함)": "Folder deleted: {0} (including {1} inside)", "폴더 삭제: {0} (안의 {1}개 포함)": "Folder deleted: {0} (including {1} inside)",
"폴더 선택": "Choose folder", "폴더 선택": "Choose folder",
"폴더 속성": "Folder properties",
"폴더 속성 · {0}": "Folder properties · {0}", "폴더 속성 · {0}": "Folder properties · {0}",
"폴더 수정": "Edit folder", "폴더 수정": "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", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · 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?", "해상도 올리기 모델을 받아야 합니다. 어느 것을 받을까요?": "An upscaling model is needed. Which one should be downloaded?",
"해상도를 올리지 못했습니다": "Couldn't upscale", "해상도를 올리지 못했습니다": "Couldn't upscale",
"해상도를 올린 그림 저장 폴더": "Folder for upscaled images", "해상도를 올린 그림 저장 폴더": "Folder for upscaled images",
"해시 (MD5 · SHA-256)": "Hash (MD5 · SHA-256)",
"해시 · {0}": "Hash · {0}", "해시 · {0}": "Hash · {0}",
"해제": "Unset", "해제": "Unset",
"허용": "Allow", "허용": "Allow",

View File

@ -71,6 +71,7 @@
"1/2 · AI 그림으로 다듬는 중": "1/2 · AI 画像で整え中", "1/2 · AI 그림으로 다듬는 중": "1/2 · AI 画像で整え中",
"10초 뒤로 (←)": "10秒戻る(←)", "10초 뒤로 (←)": "10秒戻る(←)",
"10초 앞으로 (→)": "10秒進む(→)", "10초 앞으로 (→)": "10秒進む(→)",
"16진": "16進",
"16진 보기 · {0}": "16進表示 · {0}", "16진 보기 · {0}": "16進表示 · {0}",
"1배 (선명하게)": "1倍 (くっきり)", "1배 (선명하게)": "1倍 (くっきり)",
"1분 안": "1 分以内", "1분 안": "1 分以内",
@ -2413,6 +2414,7 @@
"폴더 복사 · 이동 방법": "フォルダーのコピー · 移動方法", "폴더 복사 · 이동 방법": "フォルダーのコピー · 移動方法",
"폴더 삭제: {0} (안의 {1}개 포함)": "フォルダーを削除: {0}(中の {1}個を含む)", "폴더 삭제: {0} (안의 {1}개 포함)": "フォルダーを削除: {0}(中の {1}個を含む)",
"폴더 선택": "フォルダーを選択", "폴더 선택": "フォルダーを選択",
"폴더 속성": "フォルダーのプロパティ",
"폴더 속성 · {0}": "フォルダーのプロパティ · {0}", "폴더 속성 · {0}": "フォルダーのプロパティ · {0}",
"폴더 수정": "フォルダーを編集", "폴더 수정": "フォルダーを編集",
"폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "フォルダー内のファイルサイズの合計を一覧に表示します。大きなフォルダー・NAS は読み込みに時間がかかることがあります。長押しの [フォルダーサイズを計算] はいつでも使えます", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "フォルダー内のファイルサイズの合計を一覧に表示します。大きなフォルダー・NAS は読み込みに時間がかかることがあります。長押しの [フォルダーサイズを計算] はいつでも使えます",
@ -2508,6 +2510,7 @@
"해상도 올리기 모델을 받아야 합니다. 어느 것을 받을까요?": "解像度アップのモデルが必要です。どれをダウンロードしますか?", "해상도 올리기 모델을 받아야 합니다. 어느 것을 받을까요?": "解像度アップのモデルが必要です。どれをダウンロードしますか?",
"해상도를 올리지 못했습니다": "高解像度化できませんでした", "해상도를 올리지 못했습니다": "高解像度化できませんでした",
"해상도를 올린 그림 저장 폴더": "高解像度化した画像の保存フォルダー", "해상도를 올린 그림 저장 폴더": "高解像度化した画像の保存フォルダー",
"해시 (MD5 · SHA-256)": "ハッシュ (MD5 · SHA-256)",
"해시 · {0}": "ハッシュ · {0}", "해시 · {0}": "ハッシュ · {0}",
"해제": "解除", "해제": "解除",
"허용": "許可", "허용": "許可",

View File

@ -71,6 +71,7 @@
"1/2 · AI 그림으로 다듬는 중": "1/2 · 正在用 AI 图片优化", "1/2 · AI 그림으로 다듬는 중": "1/2 · 正在用 AI 图片优化",
"10초 뒤로 (←)": "后退 10 秒(←)", "10초 뒤로 (←)": "后退 10 秒(←)",
"10초 앞으로 (→)": "前进 10 秒(→)", "10초 앞으로 (→)": "前进 10 秒(→)",
"16진": "十六进制",
"16진 보기 · {0}": "十六进制查看 · {0}", "16진 보기 · {0}": "十六进制查看 · {0}",
"1배 (선명하게)": "1倍 (锐化)", "1배 (선명하게)": "1倍 (锐化)",
"1분 안": "1 分钟以内", "1분 안": "1 分钟以内",
@ -2413,6 +2414,7 @@
"폴더 복사 · 이동 방법": "文件夹复制 · 移动方式", "폴더 복사 · 이동 방법": "文件夹复制 · 移动方式",
"폴더 삭제: {0} (안의 {1}개 포함)": "已删除文件夹: {0}(含其中 {1} 个)", "폴더 삭제: {0} (안의 {1}개 포함)": "已删除文件夹: {0}(含其中 {1} 个)",
"폴더 선택": "选择文件夹", "폴더 선택": "选择文件夹",
"폴더 속성": "文件夹属性",
"폴더 속성 · {0}": "文件夹属性 · {0}", "폴더 속성 · {0}": "文件夹属性 · {0}",
"폴더 수정": "编辑文件夹", "폴더 수정": "编辑文件夹",
"폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "在列表中显示文件夹内文件大小的总和。大型文件夹或 NAS 可能需要较长时间读取。长按 [计算文件夹大小] 始终可用", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "在列表中显示文件夹内文件大小的总和。大型文件夹或 NAS 可能需要较长时间读取。长按 [计算文件夹大小] 始终可用",
@ -2508,6 +2510,7 @@
"해상도 올리기 모델을 받아야 합니다. 어느 것을 받을까요?": "需要放大模型。要下载哪一个?", "해상도 올리기 모델을 받아야 합니다. 어느 것을 받을까요?": "需要放大模型。要下载哪一个?",
"해상도를 올리지 못했습니다": "无法提高分辨率", "해상도를 올리지 못했습니다": "无法提高分辨率",
"해상도를 올린 그림 저장 폴더": "提高分辨率后图片的保存文件夹", "해상도를 올린 그림 저장 폴더": "提高分辨率后图片的保存文件夹",
"해시 (MD5 · SHA-256)": "哈希 (MD5 · SHA-256)",
"해시 · {0}": "哈希 · {0}", "해시 · {0}": "哈希 · {0}",
"해제": "解除", "해제": "解除",
"허용": "允许", "허용": "允许",

View File

@ -47,6 +47,7 @@ import '../l10n/tr.dart';
import 'explorer_bookmarks.dart'; import 'explorer_bookmarks.dart';
import 'explorer_search.dart'; import 'explorer_search.dart';
import 'quick_look.dart'; import 'quick_look.dart';
import 'file_tools.dart' show showFileHash, showFolderProperties;
import 'explorer_drag.dart'; import 'explorer_drag.dart';
import 'archive_ui.dart'; import 'archive_ui.dart';
import 'storage_page.dart'; import 'storage_page.dart';
@ -1605,6 +1606,9 @@ class _ExplorerPageState extends State<ExplorerPage> {
if (e.isDir) ('storage', Icons.pie_chart_outline, tr('저장 공간 분석')), if (e.isDir) ('storage', Icons.pie_chart_outline, tr('저장 공간 분석')),
// 80: 이 폴더 크기를 지금 계산 (설정이 "안 함" 이어도) // 80: 이 폴더 크기를 지금 계산 (설정이 "안 함" 이어도)
if (e.isDir) ('folderSize', Icons.data_usage, _sizes.busy(e.path) ? tr('폴더 크기 계산 멈추기') : tr('폴더 크기 계산')), 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 && dual) ('openOther', Icons.vertical_split_outlined, tr('다른 창에서 열기')),
if (e.isDir) ('playFolder', Icons.play_circle_outline, tr('이 폴더의 동영상 재생')), if (e.isDir) ('playFolder', Icons.play_circle_outline, tr('이 폴더의 동영상 재생')),
if (video || audio) ('playInternal', Icons.play_circle_outline, tr('내장 플레이어로 재생')), if (video || audio) ('playInternal', Icons.play_circle_outline, tr('내장 플레이어로 재생')),
@ -1705,6 +1709,10 @@ class _ExplorerPageState extends State<ExplorerPage> {
pane.focused = made; pane.focused = made;
pane.changed(); pane.changed();
} }
case 'props':
await showFolderProperties(context, e.path);
case 'hash':
await showFileHash(context, e.path);
case 'folderSize': case 'folderSize':
if (_sizes.busy(e.path)) { if (_sizes.busy(e.path)) {
_sizes.cancel(e.path); _sizes.cancel(e.path);

View File

@ -11,6 +11,7 @@ import '../core/models.dart';
import '../core/playlist.dart' show isAudioFile, isVideoFile; import '../core/playlist.dart' show isAudioFile, isVideoFile;
import '../core/vfs.dart'; import '../core/vfs.dart';
import '../l10n/tr.dart'; import '../l10n/tr.dart';
import 'file_tools.dart' show HexViewerPage;
import 'theme.dart'; import 'theme.dart';
/// 81: 빠른 보기에서 글로 보는 확장자 /// 81: 빠른 보기에서 글로 보는 확장자
@ -209,6 +210,12 @@ class _QuickLookState extends State<QuickLook> {
]), ]),
), ),
actions: [ actions: [
// X-plore P3: 16진 보기 (이 기기의 파일만 - 앞부분부터 나눠 읽기)
if (!e.isDir && !isDav(e.path))
TextButton(
onPressed: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => HexViewerPage(path: e.path))),
child: Text(tr('16진')),
),
TextButton(onPressed: () => Navigator.pop(context), child: Text(tr('닫기'))), TextButton(onPressed: () => Navigator.pop(context), child: Text(tr('닫기'))),
FilledButton( FilledButton(
onPressed: () { onPressed: () {

View File

@ -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/core/file_ops.dart' show formatSize;
import 'package:jj_mkvmaker/ui/explorer_page.dart'; import 'package:jj_mkvmaker/ui/explorer_page.dart';
import 'package:jj_mkvmaker/ui/explorer_search.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/quick_look.dart';
import 'package:jj_mkvmaker/ui/explorer_drag.dart'; import 'package:jj_mkvmaker/ui/explorer_drag.dart';
import 'package:jj_mkvmaker/ui/storage_page.dart'; import 'package:jj_mkvmaker/ui/storage_page.dart';
@ -160,6 +161,38 @@ void main() {
await tester.pump(const Duration(seconds: 2)); 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<void>.delayed(const Duration(milliseconds: 300)));
await tester.pump();
await act(tester, () => tester.pageBack());
await calm(tester);
});
testWidgets('X-plore b: 가운데 버튼 길게 누르기 = 짝 동작 - [복사] 길게 = 이동 창 · [삭제] 길게 = 영구 삭제 창', (tester) async { testWidgets('X-plore b: 가운데 버튼 길게 누르기 = 짝 동작 - [복사] 길게 = 이동 창 · [삭제] 길게 = 영구 삭제 창', (tester) async {
await open(tester); await open(tester);
await act(tester, () => tester.tap(find.text('doc.txt'))); await act(tester, () => tester.tap(find.text('doc.txt')));