Merge commit '546b915' into next974
commit
5038a60b87
|
|
@ -1217,6 +1217,9 @@
|
|||
"목록에서 숨겼습니다 ({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",
|
||||
"목록에서 숨긴 항목 ({0}개)": "Items hidden from the list ({0})",
|
||||
"목록에서 숨긴 항목 다시 숨기기": "Hide the hidden items again",
|
||||
"목록에서 숨긴 항목 보이기 ({0}개)": "Show items hidden from the list ({0})",
|
||||
"목록에서 제거": "Remove from list",
|
||||
"목록에서 지우기 (파일은 그대로)": "Remove from list (files stay)",
|
||||
"목록을 모두 비울까요?": "Clear the whole list?",
|
||||
|
|
@ -1598,6 +1601,7 @@
|
|||
"숨긴 항목 {0}개 다시 숨기기": "Hide {0} hidden items again",
|
||||
"숨긴 항목 {0}개 보이기": "Show {0} hidden items",
|
||||
"숨긴 항목 숨기기": "Hide hidden items",
|
||||
"숨긴 항목이 없습니다": "No hidden items",
|
||||
"숨김": "Hidden",
|
||||
"숨은 다른 창으로": "To the hidden other pane",
|
||||
"숨은 다른 창으로: X-plore 처럼 위에 \"갈 곳\" 을 늘 보이고 그 폴더로 바로 복사 · 이동합니다": "To the hidden other pane: like X-plore, the destination is always shown at the top and items are copied or moved straight there",
|
||||
|
|
@ -2575,6 +2579,7 @@
|
|||
"파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "You can also change it from the ⋮ menu at the top of the file explorer (side by side · top and bottom · button bar position · buttons)",
|
||||
"파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "File explorer panes: two panes → by screen size (one pane on a portrait phone). To revert: Settings > File explorer > Pane layout",
|
||||
"파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "File explorer: a single tap now opens a file right away (long-press to select or for the menu). To change: Settings > File explorer > Tap",
|
||||
"파일 탐색기의 길게 누르기 [목록에서 숨기기] 로 숨긴 것. 파일은 그대로이고 목록에서만 빠집니다": "Hidden with long-press [Hide from list] in the file explorer. The files are unchanged; they are only left out of the list",
|
||||
"파일 폴더": "File folder",
|
||||
"파일 확인 중…": "Checking files…",
|
||||
"파일만 골랐을 때": "When only files are selected",
|
||||
|
|
@ -2660,6 +2665,7 @@
|
|||
"풀 수 없는 이름 {0}개 (건너뜀): {1}": "{0} names can't be used on this device (skipped): {1}",
|
||||
"풀기": "Extract",
|
||||
"풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?": "The extracted size ({0}) is larger than the free space ({1}). Extract anyway?",
|
||||
"풀면 파일 탐색기 목록에 다시 보입니다. 파일 자체는 바꾸지 않습니다.": "Unhidden items show in the file explorer list again. The files themselves are not changed.",
|
||||
"풀지 못했습니다: {0}": "Could not extract: {0}",
|
||||
"품질 올리기": "Improve quality",
|
||||
"품질 올리기 (이 장)": "Improve quality (this page)",
|
||||
|
|
|
|||
|
|
@ -1217,6 +1217,9 @@
|
|||
"목록에서 숨겼습니다 ({0}개). 파일은 그대로이고, 목록 맨 아래의 [숨긴 항목 보이기] 로 다시 볼 수 있습니다.": "リストから隠しました ({0} 件)。ファイルはそのままで、リストの一番下の [隠した項目を表示] で再び見られます。",
|
||||
"목록에서 숨겼습니다 ({0}개). 파일은 그대로입니다.": "リストから隠しました ({0} 件)。ファイルはそのままです。",
|
||||
"목록에서 숨기기": "リストから隠す",
|
||||
"목록에서 숨긴 항목 ({0}개)": "リストから隠した項目 ({0} 件)",
|
||||
"목록에서 숨긴 항목 다시 숨기기": "リストから隠した項目を再び隠す",
|
||||
"목록에서 숨긴 항목 보이기 ({0}개)": "リストから隠した項目を表示 ({0} 件)",
|
||||
"목록에서 제거": "リストから削除",
|
||||
"목록에서 지우기 (파일은 그대로)": "リストから削除 (ファイルはそのまま)",
|
||||
"목록을 모두 비울까요?": "一覧をすべて空にしますか?",
|
||||
|
|
@ -1598,6 +1601,7 @@
|
|||
"숨긴 항목 {0}개 다시 숨기기": "隠した項目 {0} 件を再び隠す",
|
||||
"숨긴 항목 {0}개 보이기": "隠した項目 {0} 件を表示",
|
||||
"숨긴 항목 숨기기": "隠し項目を隠す",
|
||||
"숨긴 항목이 없습니다": "隠した項目はありません",
|
||||
"숨김": "非表示",
|
||||
"숨은 다른 창으로": "隠れた別ペインへ",
|
||||
"숨은 다른 창으로: X-plore 처럼 위에 \"갈 곳\" 을 늘 보이고 그 폴더로 바로 복사 · 이동합니다": "隠れた別ペインへ: X-plore のように上部に「移動先」を常に表示し、そのフォルダーへ直接コピー・移動します",
|
||||
|
|
@ -2575,6 +2579,7 @@
|
|||
"파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "ファイルエクスプローラー上部の ⋮ メニューからも変えられます (左右 · 上下 · ボタン列の位置 · ボタン構成)",
|
||||
"파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "ファイルエクスプローラーの画面: 2 画面 → 画面サイズに合わせる (縦向きのスマホは 1 画面)。元に戻す: 環境設定 > ファイルエクスプローラー > 画面配置",
|
||||
"파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラー: ファイルを 1 回タップするとすぐ開きます (選択 · メニューは長押し)。変更: 環境設定 > ファイルエクスプローラー > タップ",
|
||||
"파일 탐색기의 길게 누르기 [목록에서 숨기기] 로 숨긴 것. 파일은 그대로이고 목록에서만 빠집니다": "ファイルエクスプローラーの長押し [リストから隠す] で隠したもの。ファイルはそのままで、リストから外れるだけです",
|
||||
"파일 폴더": "ファイル フォルダー",
|
||||
"파일 확인 중…": "ファイルを確認中…",
|
||||
"파일만 골랐을 때": "ファイルだけを選んだとき",
|
||||
|
|
@ -2660,6 +2665,7 @@
|
|||
"풀 수 없는 이름 {0}개 (건너뜀): {1}": "展開できない名前 {0} 件 (スキップ): {1}",
|
||||
"풀기": "展開",
|
||||
"풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?": "展開後のサイズ ({0}) が空き容量 ({1}) より大きいです。それでも展開しますか?",
|
||||
"풀면 파일 탐색기 목록에 다시 보입니다. 파일 자체는 바꾸지 않습니다.": "隠すのをやめるとファイルエクスプローラーのリストに再び表示されます。ファイル自体は変更しません。",
|
||||
"풀지 못했습니다: {0}": "展開できませんでした: {0}",
|
||||
"품질 올리기": "画質を上げる",
|
||||
"품질 올리기 (이 장)": "画質を上げる (このページ)",
|
||||
|
|
|
|||
|
|
@ -1217,6 +1217,9 @@
|
|||
"목록에서 숨겼습니다 ({0}개). 파일은 그대로이고, 목록 맨 아래의 [숨긴 항목 보이기] 로 다시 볼 수 있습니다.": "已从列表中隐藏 ({0} 项)。文件本身未改变,可用列表底部的 [显示已隐藏项] 再次查看。",
|
||||
"목록에서 숨겼습니다 ({0}개). 파일은 그대로입니다.": "已从列表中隐藏 ({0} 项)。文件本身未改变。",
|
||||
"목록에서 숨기기": "从列表中隐藏",
|
||||
"목록에서 숨긴 항목 ({0}개)": "从列表中隐藏的项 ({0} 项)",
|
||||
"목록에서 숨긴 항목 다시 숨기기": "再次隐藏已从列表中隐藏的项",
|
||||
"목록에서 숨긴 항목 보이기 ({0}개)": "显示从列表中隐藏的项 ({0} 项)",
|
||||
"목록에서 제거": "从列表中移除",
|
||||
"목록에서 지우기 (파일은 그대로)": "从列表中删除 (文件保留)",
|
||||
"목록을 모두 비울까요?": "清空整个列表吗?",
|
||||
|
|
@ -1598,6 +1601,7 @@
|
|||
"숨긴 항목 {0}개 다시 숨기기": "再次隐藏 {0} 个已隐藏项",
|
||||
"숨긴 항목 {0}개 보이기": "显示 {0} 个已隐藏项",
|
||||
"숨긴 항목 숨기기": "隐藏隐藏项目",
|
||||
"숨긴 항목이 없습니다": "没有已隐藏的项",
|
||||
"숨김": "隐藏",
|
||||
"숨은 다른 창으로": "到隐藏的另一窗格",
|
||||
"숨은 다른 창으로: X-plore 처럼 위에 \"갈 곳\" 을 늘 보이고 그 폴더로 바로 복사 · 이동합니다": "到隐藏的另一窗格: 像 X-plore 一样在顶部始终显示\"目标\",并直接复制 · 移动到该文件夹",
|
||||
|
|
@ -2575,6 +2579,7 @@
|
|||
"파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "也可以在文件浏览器顶部的 ⋮ 菜单中更改 (左右 · 上下 · 按钮栏位置 · 按钮配置)",
|
||||
"파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "文件浏览器窗格: 双窗格 → 按屏幕大小 (竖屏手机为单窗格)。恢复: 设置 > 文件浏览器 > 窗格布局",
|
||||
"파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器:单击文件会立即打开(选择 · 菜单请长按)。更改:设置 > 文件浏览器 > 点按",
|
||||
"파일 탐색기의 길게 누르기 [목록에서 숨기기] 로 숨긴 것. 파일은 그대로이고 목록에서만 빠집니다": "在文件浏览器中长按 [从列表中隐藏] 隐藏的项。文件本身不变,只是不在列表中显示",
|
||||
"파일 폴더": "文件夹",
|
||||
"파일 확인 중…": "正在检查文件…",
|
||||
"파일만 골랐을 때": "只选择文件时",
|
||||
|
|
@ -2660,6 +2665,7 @@
|
|||
"풀 수 없는 이름 {0}개 (건너뜀): {1}": "{0} 个名称无法在本设备使用 (跳过): {1}",
|
||||
"풀기": "解压",
|
||||
"풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?": "解压后大小 ({0}) 大于剩余空间 ({1})。仍要解压吗?",
|
||||
"풀면 파일 탐색기 목록에 다시 보입니다. 파일 자체는 바꾸지 않습니다.": "取消隐藏后会重新显示在文件浏览器列表中。不会更改文件本身。",
|
||||
"풀지 못했습니다: {0}": "无法解压: {0}",
|
||||
"품질 올리기": "提升画质",
|
||||
"품질 올리기 (이 장)": "提升画质 (本页)",
|
||||
|
|
|
|||
|
|
@ -416,6 +416,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
pane.addListener(_onPane);
|
||||
}
|
||||
_resumeReload = AppLifecycleListener(onResume: () => unawaited(_reloadOpen(localOnly: true)));
|
||||
_hiddenSeen = c.settings.explorerHiddenItems;
|
||||
c.addListener(_onSettings);
|
||||
_init();
|
||||
}
|
||||
|
||||
|
|
@ -447,6 +449,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
c.removeListener(_onSettings);
|
||||
_keys.dispose();
|
||||
_sizes.dispose();
|
||||
if (widget.rsync) {
|
||||
|
|
@ -1230,26 +1233,67 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
return Platform.isWindows ? n.toLowerCase() : n;
|
||||
}
|
||||
|
||||
/// X-plore 숨기기: 옮기거나 이름을 바꾼 것 ([moved] 원래 → 새 경로, 그 안의 것도) 은 숨긴 목록도 새 경로로,
|
||||
/// 지운 것 ([gone], 그 안의 것도 - 실제로 없어진 것만) 은 목록에서 뺀다 (관리자 결정 10/18)
|
||||
Future<void> _followHidden({Iterable<(String, String)> moved = const [], Iterable<String> gone = const []}) async {
|
||||
final list = c.settings.explorerHiddenItems;
|
||||
if (list.isEmpty || (moved.isEmpty && gone.isEmpty)) return;
|
||||
final out = <String>[];
|
||||
var changed = false;
|
||||
for (final h in list) {
|
||||
if (gone.any((g) => isSameOrInside(h, g)) && !await vExists(h)) {
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
var now = h;
|
||||
for (final (from, to) in moved) {
|
||||
if (samePath(h, from)) {
|
||||
now = to;
|
||||
break;
|
||||
}
|
||||
if (isSameOrInside(h, from)) {
|
||||
now = vJoin(to, isDav(h) ? h.substring(from.length).replaceAll('\\', '/').replaceFirst(RegExp('^/+'), '') : p.relative(h, from: from));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (now != h) changed = true;
|
||||
if (!out.any((x) => _hideKey(x) == _hideKey(now))) out.add(now);
|
||||
}
|
||||
if (changed) await c.updateSettings((s) => s.explorerHiddenItems = out);
|
||||
}
|
||||
|
||||
/// X-plore 숨기기: 목록 맨 아래의 [숨긴 항목 n개 보이기] · [다시 숨기기] 를 누르면 (이번 실행 동안, 모든 창에)
|
||||
bool _showAppHidden = false;
|
||||
|
||||
void _toggleAppHidden() {
|
||||
setState(() => _showAppHidden = !_showAppHidden);
|
||||
for (final q in _panes) {
|
||||
q.changed();
|
||||
}
|
||||
}
|
||||
|
||||
/// 설정 화면에서 숨긴 목록을 바꾸면 (풀기) 바로 목록에 보이게
|
||||
List<String>? _hiddenSeen;
|
||||
void _onSettings() {
|
||||
final now = c.settings.explorerHiddenItems;
|
||||
if (identical(now, _hiddenSeen) || !mounted) return;
|
||||
_hiddenSeen = now;
|
||||
setState(() {});
|
||||
for (final q in _panes) {
|
||||
q.changed();
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
onTap: _toggleAppHidden,
|
||||
child: InkWell(
|
||||
onTap: tap,
|
||||
onTap: _toggleAppHidden,
|
||||
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),
|
||||
|
|
@ -2499,6 +2543,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
if (name == null || name == vBasename(path)) return;
|
||||
try {
|
||||
final now = await FileOps.rename(path, name);
|
||||
await _followHidden(moved: [(path, now)]);
|
||||
await _refreshAll([vDirname(path)]);
|
||||
for (final x in _panes) {
|
||||
if (x.marked.remove(path)) x.marked.add(now);
|
||||
|
|
@ -2548,6 +2593,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
|
||||
/// 바꾼 뒤: 목록을 다시 읽고, 표시 · 초점을 새 이름으로
|
||||
Future<void> _afterBatchRename(_Pane pane, List<(String, String)> moved) async {
|
||||
await _followHidden(moved: moved);
|
||||
await _refreshAll({for (final (a, _) in moved) vDirname(a)});
|
||||
for (final x in _panes) {
|
||||
for (final (from, to) in moved) {
|
||||
|
|
@ -2659,10 +2705,13 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
// X-plore 비교 e: 앱 휴지통으로 보낸 것 · 옮기지 못한 것 (영구 삭제할지 묻는다)
|
||||
final trashed = <TrashItem>[];
|
||||
final notTrashed = <(String, String)>[];
|
||||
// X-plore 숨기기: 휴지통에서 [되돌리기] 할 수 있는 것 (숨긴 목록에 남겨 되살리면 그대로 숨김)
|
||||
final undoable = <String>[];
|
||||
for (final x in paths) {
|
||||
if (!permanent && canTrash) {
|
||||
try {
|
||||
trashed.add(await trash.moveToTrash(x));
|
||||
undoable.add(x);
|
||||
recycled++;
|
||||
} on TrashMoveException catch (e) {
|
||||
notTrashed.add((x, '${e.cause}'));
|
||||
|
|
@ -2795,6 +2844,11 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
: SnackBarAction(label: tr('되돌리기'), onPressed: () => _undoRecycle(sent, since)),
|
||||
));
|
||||
}
|
||||
// X-plore 숨기기: 영구로 지운 것 (그 안 포함) 은 숨긴 목록에서 뺀다. 휴지통으로 보낸 것은 되돌리기 뒤에도 숨김이 되게 남긴다
|
||||
await _followHidden(gone: [
|
||||
for (final x in paths)
|
||||
if (!sent.contains(x) && !undoable.contains(x) && !failed.any((f) => f.$1 == x)) x,
|
||||
]);
|
||||
for (final x in _panes) {
|
||||
x.marked.removeAll(paths);
|
||||
if (_selecting && x.marked.isEmpty) _selecting = false;
|
||||
|
|
@ -2953,10 +3007,23 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
}
|
||||
|
||||
/// 52: 이 화면을 떠난 사이에 시작된 (또는 앞의 화면이 시작한) 작업을 다시 보이고, 끝나면 새로 읽고 막대를 내린다
|
||||
/// X-plore 숨기기: 옮긴 것은 숨긴 목록도 새 자리로 (원래 자리에 없고 새 자리에 있는 것만 - 같은 이름 바꾸기 · 실패는 그대로)
|
||||
Future<void> _followMovedJob(TransferJob job) async {
|
||||
if (!job.move || job.contents || c.settings.explorerHiddenItems.isEmpty) return;
|
||||
final moved = <(String, String)>[];
|
||||
for (final s in job.sources) {
|
||||
final to = vJoin(job.dest, vBasename(s));
|
||||
if (!await vExists(s) && await vExists(to)) moved.add((s, to));
|
||||
}
|
||||
await _followHidden(moved: moved);
|
||||
}
|
||||
|
||||
void _adoptJob(TransferJob job) {
|
||||
if (_jobs.contains(job)) return;
|
||||
setState(() => _jobs = [..._jobs, job]);
|
||||
unawaited(job.done.then((_) async {
|
||||
if (!mounted) return;
|
||||
await _followMovedJob(job);
|
||||
if (!mounted) return;
|
||||
await _refreshAll([job.dest, ...job.sources.map(vDirname)]);
|
||||
for (final x in _panes) {
|
||||
|
|
@ -3171,6 +3238,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
setState(() => _jobs = [..._jobs.where((j) => !j.finished), job]);
|
||||
await job.done;
|
||||
final error = job.error;
|
||||
await _followMovedJob(job);
|
||||
if (!mounted) return false;
|
||||
// 180: 표시만 지우고 선택 모드는 그대로 (꺼지면 동그라미 칸 · 아래 막대가 사라져 자리가 바뀐다)
|
||||
pane.marked.clear();
|
||||
|
|
@ -4226,10 +4294,10 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: JjColors.textDim)),
|
||||
),
|
||||
),
|
||||
PopupMenuButton<ExplorerButton>(
|
||||
PopupMenuButton<Object>(
|
||||
tooltip: tr('창 배치 · 버튼 구성'),
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onSelected: _button,
|
||||
onSelected: (v) => v is ExplorerButton ? _button(v) : _toggleAppHidden(),
|
||||
itemBuilder: (_) => [
|
||||
for (final b in [
|
||||
if (!widget.rsync) ...[ExplorerButton.layout, ExplorerButton.buttons],
|
||||
|
|
@ -4242,6 +4310,20 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
value: b,
|
||||
child: Row(children: [Icon(b.icon, size: 18), const SizedBox(width: 12), Text(tr(b.label))]),
|
||||
),
|
||||
// X-plore 숨기기: 큰 폴더를 펼쳐 목록 맨 아래가 멀 때도 (관리자 결정 10/18)
|
||||
if (!widget.rsync && c.settings.explorerHiddenItems.isNotEmpty)
|
||||
PopupMenuItem(
|
||||
value: 'appHidden',
|
||||
child: Row(children: [
|
||||
Icon(_showAppHidden ? Icons.visibility_off_outlined : Icons.visibility_outlined, size: 18),
|
||||
const SizedBox(width: 12),
|
||||
Flexible(
|
||||
child: Text(_showAppHidden
|
||||
? tr('목록에서 숨긴 항목 다시 숨기기')
|
||||
: trf('목록에서 숨긴 항목 보이기 ({0}개)', [c.settings.explorerHiddenItems.length])),
|
||||
),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../app/app_controller.dart';
|
||||
import '../core/file_ops.dart' show samePath;
|
||||
import '../core/vfs.dart' show vBasename, vDirname;
|
||||
import '../l10n/tr.dart';
|
||||
import 'path_label.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// X-plore 숨기기 (관리자 결정 10/18): 환경 설정 > 파일 탐색기 [목록에서 숨긴 항목] - 숨긴 것을 보고 하나씩 · 모두 풀기.
|
||||
/// 파일은 건드리지 않는다 (목록에서만 다시 보이게)
|
||||
Future<void> showHiddenItems(BuildContext context, AppController c) => showDialog<void>(
|
||||
context: context,
|
||||
builder: (ctx) => StatefulBuilder(builder: (ctx, set) {
|
||||
final list = c.settings.explorerHiddenItems;
|
||||
Future<void> unhide(List<String> which) async {
|
||||
await c.updateSettings((s) => s.explorerHiddenItems = [
|
||||
for (final x in s.explorerHiddenItems)
|
||||
if (!which.any((w) => samePath(w, x))) x,
|
||||
]);
|
||||
set(() {});
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
title: Text(trf('목록에서 숨긴 항목 ({0}개)', [list.length])),
|
||||
content: SizedBox(
|
||||
width: 520,
|
||||
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(tr('풀면 파일 탐색기 목록에 다시 보입니다. 파일 자체는 바꾸지 않습니다.'),
|
||||
style: const TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||
const SizedBox(height: 8),
|
||||
if (list.isEmpty) Text(tr('숨긴 항목이 없습니다')),
|
||||
for (final x in list)
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
dense: true,
|
||||
title: Text(vBasename(x), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
subtitle: Text(friendlyPath(vDirname(x)), maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
trailing: IconButton(
|
||||
tooltip: tr('숨기기 풀기'),
|
||||
icon: const Icon(Icons.visibility_outlined),
|
||||
onPressed: () => unhide([x]),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
actions: [
|
||||
if (list.length > 1) TextButton(onPressed: () => unhide([...list]), child: Text(tr('모두 풀기'))),
|
||||
FilledButton(onPressed: () => Navigator.pop(ctx), child: Text(tr('닫기'))),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
|
@ -44,6 +44,7 @@ import '../app/master_lock.dart';
|
|||
import 'toast_status.dart';
|
||||
import 'license_texts.dart';
|
||||
import 'subtitle_language_picker.dart';
|
||||
import 'hidden_items_dialog.dart';
|
||||
|
||||
/// 동시 작업 수 고르기: 1 · 5 · 10 · 무한(0) · 직접 입력
|
||||
class CountSelector extends StatelessWidget {
|
||||
|
|
@ -894,6 +895,14 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
value: s.explorerRetapCollapse,
|
||||
onChanged: (v) => c.updateSettings((x) => x.explorerRetapCollapse = v),
|
||||
),
|
||||
// X-plore 숨기기: 목록에서 숨긴 것 보기 · 풀기 (관리자 결정 10/18)
|
||||
ListTile(
|
||||
title: Text(trf('목록에서 숨긴 항목 ({0}개)', [s.explorerHiddenItems.length])),
|
||||
subtitle: Text(tr('파일 탐색기의 길게 누르기 [목록에서 숨기기] 로 숨긴 것. 파일은 그대로이고 목록에서만 빠집니다')),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
enabled: s.explorerHiddenItems.isNotEmpty,
|
||||
onTap: () => showHiddenItems(context, c),
|
||||
),
|
||||
// X-plore 비교 a: 한 창일 때 복사 · 이동이 갈 곳
|
||||
SettingTile(
|
||||
title: Text(tr('한 창일 때 복사 · 이동')),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import 'package:jj_mkvmaker/services/app_shell.dart';
|
|||
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/hidden_items_dialog.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';
|
||||
|
|
@ -1631,6 +1632,78 @@ void main() {
|
|||
await calm(tester);
|
||||
});
|
||||
|
||||
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);
|
||||
await act(tester, () => tester.enterText(find.byType(TextField), 'sub2'));
|
||||
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '확인')));
|
||||
await settle(tester, () => find.text('sub2').evaluate().isNotEmpty);
|
||||
expect(c.settings.explorerHiddenItems, [p.join(left, 'sub2', 'inner.txt')]);
|
||||
await idle(tester);
|
||||
await tester.longPress(find.text('sub2'));
|
||||
await calm(tester);
|
||||
await act(tester, () => tester.tap(find.byTooltip('삭제').last));
|
||||
await calm(tester);
|
||||
await tester.tap(find.text('휴지통을 거치지 않고 영구 삭제 (Shift+Delete)'));
|
||||
await calm(tester);
|
||||
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '영구 삭제')));
|
||||
await settle(tester, () => find.text('sub2').evaluate().isEmpty);
|
||||
await settle(tester, () => c.settings.explorerHiddenItems.isEmpty);
|
||||
expect(c.settings.explorerHiddenItems, isEmpty);
|
||||
});
|
||||
|
||||
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 settle(tester, () => find.widgetWithText(FilledButton, '이동').evaluate().isNotEmpty);
|
||||
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '이동')));
|
||||
await settle(tester, () => c.settings.explorerHiddenItems.first != p.join(left, 'sub', 'inner.txt'));
|
||||
expect(File(p.join(right, 'sub', 'inner.txt')).existsSync(), isTrue);
|
||||
expect(c.settings.explorerHiddenItems, [p.join(right, 'sub', 'inner.txt')]);
|
||||
await idle(tester);
|
||||
});
|
||||
|
||||
testWidgets('X-plore 숨기기: 위 막대 ⋮ [목록에서 숨긴 항목 보이기] · 환경 설정의 [목록에서 숨긴 항목] 에서 풀기', (tester) async {
|
||||
final doc = p.join(left, 'doc.txt');
|
||||
c.settings
|
||||
..explorerLayout = 'split'
|
||||
..explorerHiddenItems = [doc];
|
||||
tester.view.physicalSize = const Size(1600, 900);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c))));
|
||||
await settle(tester, () => find.text('숨긴 항목 1개 보이기').evaluate().isNotEmpty);
|
||||
expect(find.text('doc.txt'), findsNothing);
|
||||
await act(tester, () => tester.tap(find.byTooltip('창 배치 · 버튼 구성')));
|
||||
await calm(tester);
|
||||
await act(tester, () => tester.tap(find.text('목록에서 숨긴 항목 보이기 (1개)')));
|
||||
await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty);
|
||||
expect(find.text('숨긴 항목 1개 다시 숨기기'), findsOneWidget);
|
||||
await act(tester, () => tester.tap(find.byTooltip('창 배치 · 버튼 구성')));
|
||||
await calm(tester);
|
||||
await act(tester, () => tester.tap(find.text('목록에서 숨긴 항목 다시 숨기기')));
|
||||
await settle(tester, () => find.text('doc.txt').evaluate().isEmpty);
|
||||
// 설정 화면의 목록에서 풀면 탐색기 목록에 바로 보인다
|
||||
final ctx = tester.element(find.byType(ExplorerPage));
|
||||
unawaited(showHiddenItems(ctx, c));
|
||||
await calm(tester);
|
||||
expect(find.text('목록에서 숨긴 항목 (1개)'), findsOneWidget);
|
||||
await act(tester, () => tester.tap(find.byTooltip('숨기기 풀기')));
|
||||
await calm(tester);
|
||||
expect(c.settings.explorerHiddenItems, isEmpty);
|
||||
expect(find.text('숨긴 항목이 없습니다'), findsOneWidget);
|
||||
await act(tester, () => tester.tap(find.text('닫기')));
|
||||
await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty);
|
||||
expect(find.textContaining('숨긴 항목'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('80: 크기순 정렬이면 폴더 크기를 계산해 보이고, "안 함" 이어도 길게 누르기 [폴더 크기 계산]', (tester) async {
|
||||
File(p.join(left, 'sub', 'big.bin')).writeAsBytesSync(List.filled(3000, 1));
|
||||
c.settings
|
||||
|
|
|
|||
Loading…
Reference in New Issue