저장 줄 · 복사 창 안내 · Android SMB 안내 (176 · 178 보충, 179)
- [보기] 로 비교 후 저장하면 작업 현황 줄이 "저장했습니다: 경로" + [열기] (저장하지 않으면 "…: 저장하지 않았습니다") - 복사 창: 설정 옵션의 원본 지우기 옵션 (/MOV · --remove-source-files 등) 은 복사에서 쓰지 않는다고 알림 (이동은 [이동]) - 179: Android 경로 입력에 smb:// · \서버 를 넣으면 "네트워크 공유 (SMB) 는 아직 지원하지 않습니다. WebDAV 탭을 쓰세요" + [WebDAV 서버 추가] - 시험: 저장 줄 · 복사 창 안내 · SMB 안내 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>fix_mkv
parent
e6556a086e
commit
5e5182178b
|
|
@ -351,6 +351,7 @@
|
|||
"{0}: 이 동영상을 {1} 로 옮깁니다": "{0}: moves this video to {1}",
|
||||
"{0}: 이미 이동 폴더에 있습니다": "{0}: already in the move folder",
|
||||
"{0}: 작업 중이라 옮기지 않았습니다": "{0}: not moved because it is being processed",
|
||||
"{0}: 저장하지 않았습니다": "{0}: not saved",
|
||||
"{0}: 체크한 동영상 {1}개를 {2} 로 옮깁니다": "{0}: moves the {1} checked videos to {2}",
|
||||
"{0}개": "{0}",
|
||||
"{0}개 · {1}": "{0} · {1}",
|
||||
|
|
@ -594,6 +595,7 @@
|
|||
"넘기는 방향: 왼쪽 → 오른쪽": "Page direction: left → right",
|
||||
"넣지 못했습니다: {0}": "Could not add: {0}",
|
||||
"네덜란드어": "Dutch",
|
||||
"네트워크 공유 (SMB) 는 아직 지원하지 않습니다. WebDAV 탭을 쓰세요": "Network shares (SMB) aren't supported yet. Use the WebDAV tab",
|
||||
"네트워크 공유는 공유 폴더 이름까지 넣으세요 (예: \\\\NAS\\영상).": "For a network share, include the shared folder name (e.g. \\\\NAS\\Videos).",
|
||||
"네팔어": "Nepali",
|
||||
"노랑": "Yellow",
|
||||
|
|
@ -2034,6 +2036,7 @@
|
|||
"환경 설정 열기": "Open settings",
|
||||
"환경 설정대로 {0} (환경 설정 > 파일 탐색기 > 같은 이름이 있을 때)": "As set in Settings, it {0} (Settings > File explorer > When a file with the same name exists)",
|
||||
"환경 설정을 열려면 마스터 비밀번호를 넣으세요. 앱을 끌 때까지 다시 묻지 않습니다.": "Enter the master password to open settings. It won't be asked again until the app is closed.",
|
||||
"환경 설정의 {0} 는 복사에서는 쓰지 않습니다 (이동은 [이동] 으로)": "{0} from Settings is not used when copying (to move, use [Move])",
|
||||
"회전": "Rotation",
|
||||
"회전 없음": "No rotation",
|
||||
"회차": "Episode",
|
||||
|
|
|
|||
|
|
@ -351,6 +351,7 @@
|
|||
"{0}: 이 동영상을 {1} 로 옮깁니다": "{0}: この動画を {1} に移動します",
|
||||
"{0}: 이미 이동 폴더에 있습니다": "{0}: 既に移動フォルダーにあります",
|
||||
"{0}: 작업 중이라 옮기지 않았습니다": "{0}: 作業中のため移動しませんでした",
|
||||
"{0}: 저장하지 않았습니다": "{0}: 保存しませんでした",
|
||||
"{0}: 체크한 동영상 {1}개를 {2} 로 옮깁니다": "{0}: チェックした動画 {1}個を {2} に移動します",
|
||||
"{0}개": "{0}個",
|
||||
"{0}개 · {1}": "{0}個・{1}",
|
||||
|
|
@ -594,6 +595,7 @@
|
|||
"넘기는 방향: 왼쪽 → 오른쪽": "めくる方向: 左 → 右",
|
||||
"넣지 못했습니다: {0}": "追加できませんでした: {0}",
|
||||
"네덜란드어": "オランダ語",
|
||||
"네트워크 공유 (SMB) 는 아직 지원하지 않습니다. WebDAV 탭을 쓰세요": "ネットワーク共有 (SMB) にはまだ対応していません。WebDAV タブを使ってください",
|
||||
"네트워크 공유는 공유 폴더 이름까지 넣으세요 (예: \\\\NAS\\영상).": "ネットワーク共有は共有フォルダー名まで入力してください (例: \\\\NAS\\動画)。",
|
||||
"네팔어": "ネパール語",
|
||||
"노랑": "黄色",
|
||||
|
|
@ -2034,6 +2036,7 @@
|
|||
"환경 설정 열기": "環境設定を開く",
|
||||
"환경 설정대로 {0} (환경 설정 > 파일 탐색기 > 같은 이름이 있을 때)": "設定どおり{0} (環境設定 > ファイルエクスプローラー > 同じ名前があるとき)",
|
||||
"환경 설정을 열려면 마스터 비밀번호를 넣으세요. 앱을 끌 때까지 다시 묻지 않습니다.": "環境設定を開くにはマスターパスワードを入力してください。アプリを終了するまで再度尋ねません。",
|
||||
"환경 설정의 {0} 는 복사에서는 쓰지 않습니다 (이동은 [이동] 으로)": "環境設定の {0} はコピーでは使いません (移動は [移動] で)",
|
||||
"회전": "回転",
|
||||
"회전 없음": "回転なし",
|
||||
"회차": "話数",
|
||||
|
|
|
|||
|
|
@ -351,6 +351,7 @@
|
|||
"{0}: 이 동영상을 {1} 로 옮깁니다": "{0}: 将此视频移到 {1}",
|
||||
"{0}: 이미 이동 폴더에 있습니다": "{0}: 已在移动文件夹中",
|
||||
"{0}: 작업 중이라 옮기지 않았습니다": "{0}: 正在处理,未移动",
|
||||
"{0}: 저장하지 않았습니다": "{0}:未保存",
|
||||
"{0}: 체크한 동영상 {1}개를 {2} 로 옮깁니다": "{0}: 将勾选的 {1} 个视频移到 {2}",
|
||||
"{0}개": "{0} 个",
|
||||
"{0}개 · {1}": "{0} 个 · {1}",
|
||||
|
|
@ -594,6 +595,7 @@
|
|||
"넘기는 방향: 왼쪽 → 오른쪽": "翻页方向: 左 → 右",
|
||||
"넣지 못했습니다: {0}": "无法加入: {0}",
|
||||
"네덜란드어": "荷兰语",
|
||||
"네트워크 공유 (SMB) 는 아직 지원하지 않습니다. WebDAV 탭을 쓰세요": "尚不支持网络共享(SMB)。请使用 WebDAV 标签",
|
||||
"네트워크 공유는 공유 폴더 이름까지 넣으세요 (예: \\\\NAS\\영상).": "网络共享请输入到共享文件夹名称 (例: \\\\NAS\\视频)。",
|
||||
"네팔어": "尼泊尔语",
|
||||
"노랑": "黄色",
|
||||
|
|
@ -2034,6 +2036,7 @@
|
|||
"환경 설정 열기": "打开设置",
|
||||
"환경 설정대로 {0} (환경 설정 > 파일 탐색기 > 같은 이름이 있을 때)": "按设置{0} (设置 > 文件资源管理器 > 存在同名项目时)",
|
||||
"환경 설정을 열려면 마스터 비밀번호를 넣으세요. 앱을 끌 때까지 다시 묻지 않습니다.": "要打开设置,请输入主密码。在关闭应用前不会再次询问。",
|
||||
"환경 설정의 {0} 는 복사에서는 쓰지 않습니다 (이동은 [이동] 으로)": "设置中的 {0} 不用于复制(要移动请用 [移动])",
|
||||
"회전": "旋转",
|
||||
"회전 없음": "不旋转",
|
||||
"회차": "集数",
|
||||
|
|
|
|||
|
|
@ -300,7 +300,13 @@ Future<String?> upscaleOne(BuildContext context, AppController c, {required Stri
|
|||
AiJobs.instance.done(text,
|
||||
view: () async {
|
||||
AiJobs.instance.clearDoneView();
|
||||
await _compareAndSave(nav, c, result, input: input, nameFrom: nameFrom, outDir: outDir);
|
||||
final saved = await _compareAndSave(nav, c, result, input: input, nameFrom: nameFrom, outDir: outDir);
|
||||
// 작업 현황 줄도 결과로 (어디에 저장했는지 + [열기])
|
||||
if (saved != null) {
|
||||
AiJobs.instance.done(trf('저장했습니다: {0}', [saved]), dir: p.dirname(saved));
|
||||
} else {
|
||||
AiJobs.instance.done(trf('{0}: 저장하지 않았습니다', [nameShort]));
|
||||
}
|
||||
},
|
||||
dispose: () {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ class ExplorerPage extends StatefulWidget {
|
|||
@visibleForTesting
|
||||
static List<(String, String)> Function()? debugVolumes;
|
||||
|
||||
/// 시험: 경로 입력을 Android 처럼 (SMB 안내, 179). null 이면 실제 기기
|
||||
@visibleForTesting
|
||||
static bool? debugAndroidPaths;
|
||||
|
||||
static const routeName = 'explorer';
|
||||
static const rsyncRouteName = 'rsync';
|
||||
static int _open = 0, _openRsync = 0;
|
||||
|
|
@ -1848,6 +1852,15 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
'\n\n${trf('방법: {0}', [tr(method.label)])}'
|
||||
'${method == CopyMethod.builtin ? '' : ' · ${withoutSourceRemovingOptions(task.options)}${move ? (method == CopyMethod.rsync ? ' --remove-source-files' : ' /MOVE') : ''}'}'
|
||||
'${task.bandwidthKBps > 0 ? ' · ${trf('속도 제한 {0} KB/s', [task.bandwidthKBps])}' : ''}'),
|
||||
// 178 보충: 환경 설정의 원본 지우기 옵션은 복사에서 뺀다 - 왜 빠졌는지 알림
|
||||
if (!move && method != CopyMethod.builtin && optionsRemoveSource(task.options))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Text(
|
||||
trf('환경 설정의 {0} 는 복사에서는 쓰지 않습니다 (이동은 [이동] 으로)',
|
||||
[splitOptions(task.options).where(isSourceRemovingOption).join(' ')]),
|
||||
style: const TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||
),
|
||||
FutureBuilder<List<NameClash>>(
|
||||
future: clashesFuture,
|
||||
builder: (ctx, snap) {
|
||||
|
|
@ -3083,6 +3096,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
setState(() => _active = _panes.indexOf(pane));
|
||||
final path = await showDialog<String>(context: context, builder: (_) => _PathDialog(initial: pane.current));
|
||||
if (path == null || !mounted) return;
|
||||
// 179: SMB 를 넣었다가 [WebDAV 서버 추가] 를 누름
|
||||
if (path == _PathDialog.addDav) return _editDav(pane, null);
|
||||
await _goTo(pane, path, fresh: true);
|
||||
}
|
||||
|
||||
|
|
@ -3090,6 +3105,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
static Future<(String?, String?)> checkEnteredPath(String input) async {
|
||||
var path = input.trim().replaceAll('"', '');
|
||||
if (path.isEmpty) return (null, null);
|
||||
// 179: Android 는 네트워크 공유 (SMB) 를 아직 못 연다 - 폴더가 없다고 하지 않고 WebDAV 로 안내
|
||||
if ((ExplorerPage.debugAndroidPaths ?? Platform.isAndroid) && looksLikeSmb(path)) return (null, smbNotSupported);
|
||||
if (Platform.isWindows && RegExp(r'^\\\\[^\\/]+[\\/]?$').hasMatch(path)) {
|
||||
return (null, tr('네트워크 공유는 공유 폴더 이름까지 넣으세요 (예: \\\\NAS\\영상).'));
|
||||
}
|
||||
|
|
@ -3732,10 +3749,18 @@ class _PatternDialogState extends State<_PatternDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
/// 179: SMB 경로 (smb:// · \\서버\공유)
|
||||
bool looksLikeSmb(String path) => RegExp(r'^(smb|cifs)://', caseSensitive: false).hasMatch(path) || path.startsWith(r'\\');
|
||||
|
||||
String get smbNotSupported => tr('네트워크 공유 (SMB) 는 아직 지원하지 않습니다. WebDAV 탭을 쓰세요');
|
||||
|
||||
class _PathDialog extends StatefulWidget {
|
||||
const _PathDialog({required this.initial});
|
||||
final String initial;
|
||||
|
||||
/// 창이 이것을 돌려주면 WebDAV 서버 추가 창을 연다
|
||||
static const addDav = '\u0000webdav';
|
||||
|
||||
@override
|
||||
State<_PathDialog> createState() => _PathDialogState();
|
||||
}
|
||||
|
|
@ -3778,6 +3803,9 @@ class _PathDialogState extends State<_PathDialog> {
|
|||
onSubmitted: (_) => _submit(),
|
||||
),
|
||||
actions: [
|
||||
// 179: SMB 를 넣었으면 바로 WebDAV 서버 추가로
|
||||
if (_error == smbNotSupported)
|
||||
TextButton(onPressed: () => Navigator.pop(context, _PathDialog.addDav), child: Text(tr('WebDAV 서버 추가'))),
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: Text(tr('취소'))),
|
||||
FilledButton(onPressed: _checking ? null : _submit, child: Text(tr('확인'))),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -406,6 +406,33 @@ void main() {
|
|||
expect(find.textContaining(right), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('179: Android 에서 SMB 경로를 넣으면 "아직 지원하지 않음 · WebDAV" 안내와 [WebDAV 서버 추가]', (tester) async {
|
||||
ExplorerPage.debugAndroidPaths = true;
|
||||
addTearDown(() => ExplorerPage.debugAndroidPaths = null);
|
||||
expect([for (final x in ['smb://nas/v', r'\\nas\v', 'SMB://x', '/storage/x', 'dav://a/']) looksLikeSmb(x)], [true, true, true, false, false]);
|
||||
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('doc.txt').evaluate().isNotEmpty);
|
||||
await tester.tap(find.byTooltip('경로 입력').first);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.widgetWithText(TextButton, 'WebDAV 서버 추가'), findsNothing);
|
||||
await tester.enterText(find.byType(TextField).last, 'smb://nas/영상');
|
||||
await tester.runAsync(() => tester.tap(find.widgetWithText(FilledButton, '확인')));
|
||||
await settle(tester, () => find.textContaining('SMB').evaluate().isNotEmpty);
|
||||
expect(find.text('네트워크 공유 (SMB) 는 아직 지원하지 않습니다. WebDAV 탭을 쓰세요'), findsOneWidget);
|
||||
expect(find.text('폴더가 없거나 열 수 없습니다'), findsNothing);
|
||||
// [WebDAV 서버 추가] → 서버 추가 창
|
||||
await tester.tap(find.widgetWithText(TextButton, 'WebDAV 서버 추가'));
|
||||
await settle(tester, () => find.text('서버 이름').evaluate().isNotEmpty || find.textContaining('주소').evaluate().isNotEmpty, rounds: 50);
|
||||
expect(find.widgetWithText(AlertDialog, 'WebDAV 서버 추가'), findsOneWidget);
|
||||
expect(find.textContaining('SMB'), findsNothing, reason: '경로 창은 닫힘');
|
||||
await tester.tap(find.text('취소').last);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(AlertDialog), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('37-2: 보던 폴더가 밖에서 지워지면 새로 읽을 때 남아 있는 상위 폴더로 옮기고 알린다', (tester) async {
|
||||
final deep = Directory(p.join(left, 'a', 'b'))..createSync(recursive: true);
|
||||
c.settings.explorerPaths = [deep.path, right];
|
||||
|
|
|
|||
|
|
@ -121,6 +121,29 @@ void main() {
|
|||
expect(File(p.join(left, 'doc.txt')).existsSync(), isTrue);
|
||||
});
|
||||
|
||||
testWidgets('178 보충: 설정의 원본 지우기 옵션은 복사 창에서 빠진 까닭을 알린다 (이동 창에는 없음)', (tester) async {
|
||||
c.settings
|
||||
..copyMethodFile = 'robocopy'
|
||||
..robocopyOptions = '/E /MOV';
|
||||
await open(tester);
|
||||
await act(tester, () => tester.tap(find.text('doc.txt')));
|
||||
await settle(tester, () => false, rounds: 15);
|
||||
await act(tester, () => tester.tap(find.text('복사').first));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('방법: robocopy'), findsOneWidget);
|
||||
expect(find.text('환경 설정의 /MOV 는 복사에서는 쓰지 않습니다 (이동은 [이동] 으로)'), findsOneWidget);
|
||||
await act(tester, () => tester.tap(find.text('취소').last));
|
||||
await tester.pumpAndSettle();
|
||||
await act(tester, () => tester.tap(find.text('이동').first));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('옮길까요?'), findsOneWidget);
|
||||
expect(find.textContaining('복사에서는 쓰지 않습니다'), findsNothing);
|
||||
await act(tester, () => tester.tap(find.text('취소').last));
|
||||
await tester.pumpAndSettle();
|
||||
expect(File(p.join(left, 'doc.txt')).existsSync(), isTrue);
|
||||
expect(File(p.join(right, 'doc.txt')).existsSync(), isFalse);
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
testWidgets('바로 실행 모드: 폴더를 누르면 펼치고, 파일을 누르면 바로 실행', (tester) async {
|
||||
c.settings.explorerClick = 'open';
|
||||
await open(tester);
|
||||
|
|
|
|||
|
|
@ -115,6 +115,9 @@ void main() {
|
|||
final saved = [for (final f in tmp.listSync(recursive: true).whereType<File>()) if (!p.equals(f.path, input)) f.path];
|
||||
expect(saved, isNotEmpty, reason: '저장됨 (원본 옆, 새 파일)');
|
||||
expect(AiJobs.instance.lastDoneView, isNull, reason: '[보기] 는 한 번');
|
||||
// 작업 현황 줄도 저장한 곳으로 ([열기] 가 그 폴더)
|
||||
expect(AiJobs.instance.lastDone, '저장했습니다: ${saved.single}');
|
||||
expect(AiJobs.instance.lastDoneDir, p.dirname(saved.single));
|
||||
ScaffoldMessenger.of(tester.element(find.text('올리기'))).clearSnackBars();
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue