Merge commit '9f758ac' into next974

review_fix
RyuJongJin 2026-10-18 02:32:23 +09:00
commit 7c068ede3e
6 changed files with 203 additions and 5 deletions

View File

@ -543,9 +543,11 @@
"{0}개 고름": "{0} selected",
"{0}개 더 보기": "Show {0} more",
"{0}개 맞춤": "{0} synced",
"{0}개 목록에서 숨기기": "Hide {0} items from list",
"{0}개 보임": "{0} shown",
"{0}개 삭제": "Delete {0} items",
"{0}개 선택": "{0} selected",
"{0}개 숨기기 풀기": "Unhide {0} items",
"{0}개 이름 일괄 바꾸기": "Rename {0} items",
"{0}개 중 {1}개를 지우지 못했습니다": "Couldn't delete {1} of {0}",
"{0}개 지우고 맞추기": "Delete {0} and sync",
@ -1211,6 +1213,9 @@
"목록에서 동영상을 누르면 여기에 자세히 보입니다": "Tap a video in the list to see its details here",
"목록에서 빼기": "Remove from list",
"목록에서 빼기 (파일은 그대로)": "Remove from list (file is kept)",
"목록에서 숨겼습니다 ({0}개). 파일은 그대로이고, 목록 맨 아래의 [숨긴 항목 보이기] 로 다시 볼 수 있습니다.": "Hidden from the list ({0}). The files are unchanged - use [Show hidden items] at the bottom of the list to see them again.",
"목록에서 숨겼습니다 ({0}개). 파일은 그대로입니다.": "Hidden from the list ({0}). The files are unchanged.",
"목록에서 숨기기": "Hide from list",
"목록에서 제거": "Remove from list",
"목록에서 지우기 (파일은 그대로)": "Remove from list (files stay)",
"목록을 모두 비울까요?": "Clear the whole list?",
@ -1585,7 +1590,10 @@
"수정한 날짜": "Date modified",
"숨겨도 위쪽 ⋮ 메뉴에서 창 배치 · 버튼 구성을 바꿀 수 있습니다.": "Even when hidden, you can change the pane layout · buttons from the ⋮ menu at the top.",
"숨기기": "Hide",
"숨기기 풀기": "Unhide",
"숨긴 항목 (. 으로 시작) 보이기": "Show hidden items (starting with .)",
"숨긴 항목 {0}개 다시 숨기기": "Hide {0} hidden items again",
"숨긴 항목 {0}개 보이기": "Show {0} hidden items",
"숨긴 항목 숨기기": "Hide hidden items",
"숨김": "Hidden",
"숨은 다른 창으로": "To the hidden other pane",
@ -2376,6 +2384,7 @@
"지우기가 들어 있는 작업입니다": "This task deletes files",
"지우기가 들어간 실행이라, 비교하지 못하면 실행할 수 없습니다. 원본 · 대상을 확인한 뒤 [다시 비교] 를 누르세요.": "This run deletes files, so it can't run without a comparison. Check the source and target, then press [Compare again].",
"지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.": "This run deletes files, so you can run it after the list of deletions appears.",
"지우는 폴더 안에 목록에서 숨긴 항목 {0}개가 있습니다 - 함께 지워집니다": "The folders being deleted contain {0} items hidden from the list - they will be deleted too",
"지우면 휴지통으로": "Delete to the Recycle Bin",
"지우지 못한 파일: {0}": "Couldn't delete: {0}",
"지우지 못함: {0}": "Could not delete: {0}",

View File

@ -543,9 +543,11 @@
"{0}개 고름": "{0} 件選択",
"{0}개 더 보기": "さらに {0} 件表示",
"{0}개 맞춤": "{0} 件を同期",
"{0}개 목록에서 숨기기": "{0} 件をリストから隠す",
"{0}개 보임": "{0} 件表示",
"{0}개 삭제": "{0} 件を削除",
"{0}개 선택": "{0}個選択",
"{0}개 숨기기 풀기": "{0} 件の非表示をやめる",
"{0}개 이름 일괄 바꾸기": "{0} 件の名前を一括変更",
"{0}개 중 {1}개를 지우지 못했습니다": "{0} 個中 {1} 個を削除できませんでした",
"{0}개 지우고 맞추기": "{0} 個を削除して同期",
@ -1211,6 +1213,9 @@
"목록에서 동영상을 누르면 여기에 자세히 보입니다": "リストの動画をタップすると、ここに詳細が表示されます",
"목록에서 빼기": "リストから外す",
"목록에서 빼기 (파일은 그대로)": "リストから外す (ファイルはそのまま)",
"목록에서 숨겼습니다 ({0}개). 파일은 그대로이고, 목록 맨 아래의 [숨긴 항목 보이기] 로 다시 볼 수 있습니다.": "リストから隠しました ({0} 件)。ファイルはそのままで、リストの一番下の [隠した項目を表示] で再び見られます。",
"목록에서 숨겼습니다 ({0}개). 파일은 그대로입니다.": "リストから隠しました ({0} 件)。ファイルはそのままです。",
"목록에서 숨기기": "リストから隠す",
"목록에서 제거": "リストから削除",
"목록에서 지우기 (파일은 그대로)": "リストから削除 (ファイルはそのまま)",
"목록을 모두 비울까요?": "一覧をすべて空にしますか?",
@ -1585,7 +1590,10 @@
"수정한 날짜": "更新日時",
"숨겨도 위쪽 ⋮ 메뉴에서 창 배치 · 버튼 구성을 바꿀 수 있습니다.": "非表示にしても、上の ⋮ メニューからペインの配置 · ボタン構成を変えられます。",
"숨기기": "隠す",
"숨기기 풀기": "隠すのをやめる",
"숨긴 항목 (. 으로 시작) 보이기": "隠し項目 (. で始まる) を表示",
"숨긴 항목 {0}개 다시 숨기기": "隠した項目 {0} 件を再び隠す",
"숨긴 항목 {0}개 보이기": "隠した項目 {0} 件を表示",
"숨긴 항목 숨기기": "隠し項目を隠す",
"숨김": "非表示",
"숨은 다른 창으로": "隠れた別ペインへ",
@ -2376,6 +2384,7 @@
"지우기가 들어 있는 작업입니다": "削除を含む作業です",
"지우기가 들어간 실행이라, 비교하지 못하면 실행할 수 없습니다. 원본 · 대상을 확인한 뒤 [다시 비교] 를 누르세요.": "削除を含む実行のため、比較できないと実行できません。元・対象を確認してから [もう一度比較] を押してください。",
"지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.": "削除を含む実行のため、削除一覧が表示されてから実行できます。",
"지우는 폴더 안에 목록에서 숨긴 항목 {0}개가 있습니다 - 함께 지워집니다": "削除するフォルダーの中にリストから隠した項目が {0} 件あります - 一緒に削除されます",
"지우면 휴지통으로": "削除するとごみ箱へ",
"지우지 못한 파일: {0}": "削除できなかったファイル: {0}",
"지우지 못함: {0}": "削除できません: {0}",

View File

@ -543,9 +543,11 @@
"{0}개 고름": "已选 {0} 个",
"{0}개 더 보기": "再显示 {0} 个",
"{0}개 맞춤": "已同步 {0} 个",
"{0}개 목록에서 숨기기": "从列表中隐藏 {0} 项",
"{0}개 보임": "显示 {0} 项",
"{0}개 삭제": "删除 {0} 项",
"{0}개 선택": "已选 {0} 个",
"{0}개 숨기기 풀기": "取消隐藏 {0} 项",
"{0}개 이름 일괄 바꾸기": "批量重命名 {0} 项",
"{0}개 중 {1}개를 지우지 못했습니다": "{0} 个中有 {1} 个无法删除",
"{0}개 지우고 맞추기": "删除 {0} 个并同步",
@ -1211,6 +1213,9 @@
"목록에서 동영상을 누르면 여기에 자세히 보입니다": "点按列表中的视频即可在此查看详情",
"목록에서 빼기": "从列表中移除",
"목록에서 빼기 (파일은 그대로)": "从列表中移除 (保留文件)",
"목록에서 숨겼습니다 ({0}개). 파일은 그대로이고, 목록 맨 아래의 [숨긴 항목 보이기] 로 다시 볼 수 있습니다.": "已从列表中隐藏 ({0} 项)。文件本身未改变,可用列表底部的 [显示已隐藏项] 再次查看。",
"목록에서 숨겼습니다 ({0}개). 파일은 그대로입니다.": "已从列表中隐藏 ({0} 项)。文件本身未改变。",
"목록에서 숨기기": "从列表中隐藏",
"목록에서 제거": "从列表中移除",
"목록에서 지우기 (파일은 그대로)": "从列表中删除 (文件保留)",
"목록을 모두 비울까요?": "清空整个列表吗?",
@ -1585,7 +1590,10 @@
"수정한 날짜": "修改日期",
"숨겨도 위쪽 ⋮ 메뉴에서 창 배치 · 버튼 구성을 바꿀 수 있습니다.": "即使隐藏,也可以在顶部 ⋮ 菜单中更改窗格布局 · 按钮配置。",
"숨기기": "隐藏",
"숨기기 풀기": "取消隐藏",
"숨긴 항목 (. 으로 시작) 보이기": "显示隐藏项目 (以 . 开头)",
"숨긴 항목 {0}개 다시 숨기기": "再次隐藏 {0} 个已隐藏项",
"숨긴 항목 {0}개 보이기": "显示 {0} 个已隐藏项",
"숨긴 항목 숨기기": "隐藏隐藏项目",
"숨김": "隐藏",
"숨은 다른 창으로": "到隐藏的另一窗格",
@ -2376,6 +2384,7 @@
"지우기가 들어 있는 작업입니다": "此任务包含删除",
"지우기가 들어간 실행이라, 비교하지 못하면 실행할 수 없습니다. 원본 · 대상을 확인한 뒤 [다시 비교] 를 누르세요.": "此操作包含删除,无法比较时不能执行。请检查源和目标后按 [重新比较]。",
"지우기가 들어간 실행이라, 지울 목록이 나온 뒤에 실행할 수 있습니다.": "此操作包含删除,需在显示删除列表后才能执行。",
"지우는 폴더 안에 목록에서 숨긴 항목 {0}개가 있습니다 - 함께 지워집니다": "要删除的文件夹中有 {0} 个从列表中隐藏的项 - 将一并删除",
"지우면 휴지통으로": "删除时移到回收站",
"지우지 못한 파일: {0}": "无法删除的文件: {0}",
"지우지 못함: {0}": "无法删除: {0}",

View File

@ -470,6 +470,10 @@ class AppSettings {
int zipLevel = 6;
bool explorerShowHidden = false;
/// X-plore 숨기기 (관리자 지시 10/18): 파일 탐색기 목록에서 숨긴 파일 · 폴더의 경로 (파일 자체는 바꾸지 않는다).
/// 지우기 · 이동 대상에서는 빠지지 않는다. 목록 맨 아래 [숨긴 항목 보이기] 로 다시 본다
List<String> explorerHiddenItems = [];
// ── 복사 · 이동 (파일 탐색기) · rsync · lsync (Rsync 화면) ──
/// 파일 탐색기의 복사 · 이동 방법: 'builtin' 현재 방식 (기본) · 'robocopy' (Windows). 파일만 고를 때 / 폴더가 들어 있을 때.
/// rsync 는 Rsync 화면에서 따로 (파일 탐색기에서는 쓰지 않음)
@ -818,6 +822,7 @@ class AppSettings {
'explorerSortPerFolder': explorerSortPerFolder,
'explorerFolderSorts': explorerFolderSorts,
'explorerShowHidden': explorerShowHidden,
'explorerHiddenItems': explorerHiddenItems,
'explorerPaths': explorerPaths,
'copyMethodFile': copyMethodFile,
'copyConflict': copyConflict,
@ -1057,6 +1062,7 @@ class AppSettings {
if (e.key is String && e.value is String) e.key as String: e.value as String,
}
..explorerShowHidden = j['explorerShowHidden'] as bool? ?? false
..explorerHiddenItems = [for (final x in (j['explorerHiddenItems'] as List?) ?? const []) if (x is String && x.isNotEmpty) x]
..explorerPaths = [for (final x in (j['explorerPaths'] as List?) ?? const []) '$x']
..copyMethodFile = _method(j['copyMethodFile'])
..copyConflict = const ['rename', 'overwrite', 'skip'].contains(j['copyConflict']) ? j['copyConflict'] as String : 'ask'

View File

@ -306,10 +306,12 @@ class _Row {
/// 파일 목록 맨 위의 ".." (상위 폴더로)
final bool isUp;
/// X-plore 비교 c: 트리 맨 위 항목 - [otherRoot] 다른 저장 장치 · 서버, [favorites] 즐겨찾기 묶음, [favorite] 즐겨찾기 하나
/// X-plore 비교 c: 트리 맨 위 항목 - [otherRoot] 다른 저장 장치 · 서버, [favorites] 즐겨찾기 묶음, [favorite] 즐겨찾기 하나.
/// X-plore 숨기기: [hiddenToggle] 목록 맨 아래의 [숨긴 항목 보이기] (숨긴 수 [count])
final int kind;
static const otherRoot = 1, favorites = 2, favorite = 3;
const _Row(this.entry, this.depth, {this.isRoot = false, this.isUp = false, this.kind = 0});
final int count;
static const otherRoot = 1, favorites = 2, favorite = 3, hiddenToggle = 4;
const _Row(this.entry, this.depth, {this.isRoot = false, this.isUp = false, this.kind = 0, this.count = 0});
}
class _ExplorerPageState extends State<ExplorerPage> {
@ -1163,11 +1165,17 @@ class _ExplorerPageState extends State<ExplorerPage> {
out.add(_Row(rootEntry, 0, isRoot: true));
// 76: 빠른 필터 - 지금 폴더 안만 거른다 (트리의 다른 곳은 그대로).
// 이름이 맞지 않아도 안에 맞는 것이 보이는 폴더는 남긴다 (어디에 있는지 보이게)
var hidden = 0;
bool walk(String dir, int depth) {
if (!pane.expanded.contains(dir)) return false;
var kept = false;
for (final e in pane.cache[dir] ?? const <FileEntry>[]) {
if (foldersOnly && !e.isDir) continue;
// X-plore 숨기기: 목록에서 숨긴 것 (그 안도) 은 [숨긴 항목 보이기] 를 켰을 때만
if (_appHidden(e.path)) {
hidden++;
if (!_showAppHidden) continue;
}
final at = out.length;
out.add(_Row(e, depth));
// X-plore 비교 f: 펼친 압축 (ZIP · 7z · RAR) 도 안을 폴더처럼
@ -1196,11 +1204,68 @@ class _ExplorerPageState extends State<ExplorerPage> {
}
}
}
// X-plore 숨기기: 펼친 폴더 안에 숨긴 것이 있으면 목록 맨 아래에 [숨긴 항목 n개 보이기] (X-plore 자리)
if (hidden > 0) out.add(_Row(FileEntry('\u0000hidden', isDir: false, modified: DateTime(0)), 0, kind: _Row.hiddenToggle, count: hidden));
return out;
}
/// X-plore 숨기기: [path] 가 목록에서 숨긴 것인지 (앱 설정의 목록 - 파일은 그대로)
bool _appHidden(String path) {
final list = c.settings.explorerHiddenItems;
if (list.isEmpty) return false;
if (!identical(list, _hiddenFrom)) {
_hiddenFrom = list;
_hiddenKeys = {for (final x in list) _hideKey(x)};
}
return _hiddenKeys.contains(_hideKey(path));
}
List<String>? _hiddenFrom;
Set<String> _hiddenKeys = const {};
/// 숨긴 목록에서 같은 경로를 찾는 열쇠 (Windows 는 대소문자 무시)
static String _hideKey(String x) {
if (isDav(x) || x.contains('://')) return x;
final n = p.normalize(x);
return Platform.isWindows ? n.toLowerCase() : n;
}
/// X-plore 숨기기: 목록 맨 아래의 [숨긴 항목 n개 보이기] · [다시 숨기기] 를 누르면 (이번 실행 동안, 모든 창에)
bool _showAppHidden = false;
Widget _hiddenToggleRow(_Row row) {
final look = _look;
final label = _showAppHidden ? trf('숨긴 항목 {0}개 다시 숨기기', [row.count]) : trf('숨긴 항목 {0}개 보이기', [row.count]);
void tap() {
setState(() => _showAppHidden = !_showAppHidden);
for (final q in _panes) {
q.changed();
}
}
return Semantics(
button: true,
label: label,
excludeSemantics: true,
onTap: tap,
child: InkWell(
onTap: tap,
child: Row(children: [
SizedBox(width: treeChevronWidth(defaultTargetPlatform) + 4),
Icon(_showAppHidden ? Icons.visibility_off_outlined : Icons.visibility_outlined, size: look.iconSize * 0.8, color: JjColors.textDim),
const SizedBox(width: 8),
Expanded(
child: Text(label,
maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: look.fontSize - 1, color: JjColors.textDim)),
),
]),
),
);
}
/// X-plore 비교 c: 트리 맨 위 항목 줄 (다른 저장 장치 · 서버 · 즐겨찾기) - 누르면 그곳으로, 즐겨찾기 묶음은 펼치기 · 접기
Widget _extraRow(_Pane pane, _Row row) {
if (row.kind == _Row.hiddenToggle) return _hiddenToggleRow(row);
final look = _look;
final path = row.entry.path;
final volume = _volumes.firstWhere((v) => samePath(v.$1, path), orElse: () => (path, vBasename(path))).$2;
@ -1359,7 +1424,11 @@ class _ExplorerPageState extends State<ExplorerPage> {
quickFilterMatch(vBasename(e.path), pane.filter);
/// 76: [dir] 에서 지금 보이는 것 (빠른 필터에 맞는 것)
List<FileEntry> _shown(_Pane pane, String dir) => [for (final x in pane.cache[dir] ?? const <FileEntry>[]) if (_passes(pane, x)) x];
/// X-plore 숨기기: 목록에서 숨긴 것은 [숨긴 항목 보이기] 를 켰을 때만
List<FileEntry> _shown(_Pane pane, String dir) => [
for (final x in pane.cache[dir] ?? const <FileEntry>[])
if (_passes(pane, x) && (_showAppHidden || !_appHidden(x.path))) x
];
/// 74: 마지막으로 표시한 것부터 [to] 까지 (보이는 순서대로) 모두 표시
void _markRange(_Pane pane, FileEntry to) {
@ -1843,6 +1912,10 @@ class _ExplorerPageState extends State<ExplorerPage> {
('toPdf', Icons.picture_as_pdf_outlined,
images.length == 1 ? tr('PDF 로 만들기') : trf('그림 {0}장을 PDF 로 만들기', [images.length])),
('select', Icons.check_circle_outline, tr('선택')),
// X-plore 숨기기: 목록에서만 숨긴다 (파일은 그대로) · 숨긴 것을 보이고 있으면 풀기
targets.every(_appHidden)
? ('appUnhide', Icons.visibility_outlined, many ? trf('{0}개 숨기기 풀기', [targets.length]) : tr('숨기기 풀기'))
: ('appHide', Icons.visibility_off_outlined, many ? trf('{0}개 목록에서 숨기기', [targets.length]) : tr('목록에서 숨기기')),
// 두 창이면 아이콘 줄의 복사 · 이동은 다른 창으로 - 담아 두었다가 원하는 폴더에 붙여넣기는 여기
if (dual) ('clipCopy', Icons.content_copy, tr('복사 (붙여넣기로)')),
if (dual) ('clipMove', Icons.content_cut, tr('이동 (붙여넣기로)')),
@ -1938,6 +2011,30 @@ class _ExplorerPageState extends State<ExplorerPage> {
pane.focused = made;
pane.changed();
}
case 'appHide':
await c.updateSettings((s) => s.explorerHiddenItems = [
...s.explorerHiddenItems,
for (final x in targets)
if (!_appHidden(x)) x,
]);
for (final q in _panes) {
q.marked.removeAll(targets);
q.changed();
}
if (mounted) setState(() {});
_snack(_showAppHidden
? trf('목록에서 숨겼습니다 ({0}개). 파일은 그대로입니다.', [targets.length])
: trf('목록에서 숨겼습니다 ({0}개). 파일은 그대로이고, 목록 맨 아래의 [숨긴 항목 보이기] 로 다시 볼 수 있습니다.', [targets.length]));
case 'appUnhide':
final keys = {for (final x in targets) _hideKey(x)};
await c.updateSettings((s) => s.explorerHiddenItems = [
for (final x in s.explorerHiddenItems)
if (!keys.contains(_hideKey(x))) x,
]);
for (final q in _panes) {
q.changed();
}
if (mounted) setState(() {});
case 'props':
await showFolderProperties(context, e.path);
case 'hash':
@ -2482,6 +2579,11 @@ class _ExplorerPageState extends State<ExplorerPage> {
final canTrash = trash != null && paths.every((x) => !isDav(x) && trash.trashDirFor(x) != null && !trash.isInTrash(x));
final canRecycle = (local && c.settings.recycleOnDelete && paths.every(hasRecycleBin)) || canTrash;
var permanent = !canRecycle || shift;
// X-plore 숨기기: 지우는 폴더 안에 목록에서 숨긴 것이 있으면 알린다 (숨긴 것도 함께 지워진다)
final hiddenInside = [
for (final h in c.settings.explorerHiddenItems)
if (paths.any((x) => !samePath(h, x) && isSameOrInside(h, x))) h
].length;
final ok = await showDialog<bool>(
context: context,
builder: (ctx) => StatefulBuilder(
@ -2508,6 +2610,12 @@ class _ExplorerPageState extends State<ExplorerPage> {
child: Text(trf('그중 폴더 {0}개 - 안의 파일 · 폴더도 모두 함께', [paths.where(vIsDirSync).length]),
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
),
if (hiddenInside > 0)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(trf('지우는 폴더 안에 목록에서 숨긴 항목 {0}개가 있습니다 - 함께 지워집니다', [hiddenInside]),
style: const TextStyle(fontSize: 12, color: Colors.orangeAccent)),
),
if (local && !canRecycle)
Padding(
padding: const EdgeInsets.only(top: 8),
@ -4533,9 +4641,12 @@ class _ExplorerPageState extends State<ExplorerPage> {
final entries = pane.cache[dir] ?? const <FileEntry>[];
final up = !samePath(dir, pane.root);
final shown = _shown(pane, dir);
final hidden = entries.where((e) => _appHidden(e.path)).length;
final rows = [
if (up) _Row(FileEntry(vDirname(dir), isDir: true, modified: DateTime(0)), 0, isUp: true),
for (final e in shown) _Row(e, 0),
// X-plore 숨기기: 맨 아래 [숨긴 항목 n개 보이기]
if (hidden > 0) _Row(FileEntry('\u0000hidden', isDir: false, modified: DateTime(0)), 0, kind: _Row.hiddenToggle, count: hidden),
];
final look = _look;
// 38: 창 높이를 알아야 안내 상자의 높이를 제한할 수 있다
@ -4567,7 +4678,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
controller: pane.listScroll,
itemCount: rows.length,
itemExtent: look.rowHeightOf(context),
itemBuilder: (_, k) => KeyedSubtree(key: _rowKey(rows[k]), child: _rowView(pane, rows[k], list: true)),
itemBuilder: (_, k) => KeyedSubtree(
key: _rowKey(rows[k]), child: rows[k].kind != 0 ? _extraRow(pane, rows[k]) : _rowView(pane, rows[k], list: true)),
findChildIndexCallback: _rowIndexOf(rows),
),
),
@ -5230,6 +5342,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
]),
),
);
// X-plore 숨기기: 숨긴 것을 보이고 있을 때 숨긴 줄은 흐리게
if (real && _showAppHidden && _appHidden(e.path)) line = Opacity(opacity: 0.5, child: line);
if (!drag) return line;
// 82: 폴더 줄 위에 놓으면 그 폴더 안으로 (놓을 수 있으면 강조)
if (e.isDir) {

View File

@ -1580,6 +1580,57 @@ void main() {
expect(AppSettings.fromJson({'explorerFolderSorts': 'broken'}).explorerFolderSorts, isEmpty, reason: '틀린 값은 비움');
});
testWidgets('X-plore 숨기기: 길게 누르기 [목록에서 숨기기] → 목록에서만 빠짐 (파일 그대로) · 맨 아래 [숨긴 항목 보이기] · [숨기기 풀기]', (tester) async {
// 폴더 + 파일 목록 배치: 오른쪽 목록은 그 폴더의 것만이라 짧다 (트리는 Temp 폴더 줄이 많아 맨 아래가 멀다)
c.settings.explorerLayout = 'split';
await open(tester);
final doc = p.join(left, 'doc.txt');
await tester.longPress(find.text('doc.txt'));
await calm(tester);
await act(tester, () => tester.tap(find.text('목록에서 숨기기')));
await settle(tester, () => find.text('doc.txt').evaluate().isEmpty);
expect(find.text('doc.txt'), findsNothing);
expect(File(doc).existsSync(), isTrue, reason: '파일은 바꾸지 않는다');
expect(c.settings.explorerHiddenItems, [doc]);
// 목록 맨 아래에 [숨긴 항목 1개 보이기]
expect(find.text('숨긴 항목 1개 보이기'), findsOneWidget);
await idle(tester);
// 보이기 → 흐리게 보이고, 메뉴는 [숨기기 풀기]
await act(tester, () => tester.tap(find.text('숨긴 항목 1개 보이기')));
await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty);
expect(find.text('숨긴 항목 1개 다시 숨기기'), findsOneWidget);
expect(find.ancestor(of: find.text('doc.txt'), matching: find.byType(Opacity)), findsOneWidget);
await tester.longPress(find.text('doc.txt'));
await calm(tester);
expect(find.text('목록에서 숨기기'), findsNothing);
await act(tester, () => tester.tap(find.text('숨기기 풀기')));
await settle(tester, () => find.textContaining('숨긴 항목').evaluate().isEmpty);
expect(c.settings.explorerHiddenItems, isEmpty);
expect(find.text('doc.txt'), findsOneWidget);
expect(find.ancestor(of: find.text('doc.txt'), matching: find.byType(Opacity)), findsNothing);
});
testWidgets('X-plore 숨기기: 숨긴 것이 든 폴더를 지울 때 확인 창에 알림 (숨긴 것도 함께 지워진다)', (tester) async {
c.settings.explorerHiddenItems = [p.join(left, 'sub', 'inner.txt')];
await open(tester);
await tester.longPress(find.text('sub'));
await calm(tester);
await act(tester, () => tester.tap(find.byTooltip('삭제').last));
await calm(tester);
expect(find.text('지우는 폴더 안에 목록에서 숨긴 항목 1개가 있습니다 - 함께 지워집니다'), findsOneWidget);
await act(tester, () => tester.tap(find.text('취소').last));
await calm(tester);
expect(File(p.join(left, 'sub', 'inner.txt')).existsSync(), isTrue);
// 다른 파일만 지울 때는 알리지 않는다
await tester.longPress(find.text('doc.txt'));
await calm(tester);
await act(tester, () => tester.tap(find.byTooltip('삭제').last));
await calm(tester);
expect(find.textContaining('목록에서 숨긴 항목'), findsNothing);
await act(tester, () => tester.tap(find.text('취소').last));
await calm(tester);
});
testWidgets('80: 크기순 정렬이면 폴더 크기를 계산해 보이고, "안 함" 이어도 길게 누르기 [폴더 크기 계산]', (tester) async {
File(p.join(left, 'sub', 'big.bin')).writeAsBytesSync(List.filled(3000, 1));
c.settings