JJ_MKVMaker/lib/ui/explorer/path_dialog.dart

124 lines
5.2 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import '../../core/vfs.dart' show isDav;
import '../../l10n/tr.dart';
/// 약속 (E5d-3): 경로 입력 창 (37 · 146 · 179 · 190). 넣은 경로를 [checkEnteredPath] 로 고쳐 쓰고 확인해,
/// 갈 수 있으면 그 경로를 돌려주고 (창 닫힘), 아니면 칸 아래에 이유를 보인다 (창은 그대로 - 고쳐서 다시 넣게).
/// [android]: SMB 주소면 열지 않고 [SMB 서버 추가] · [WebDAV 서버 추가] 로 안내 ([PathDialog.addSmb] · [PathDialog.addDav] 를 돌려줌).
/// 179: SMB 경로 (smb:// · \\서버\공유)
bool looksLikeSmb(String path) => RegExp(r'^(smb|cifs)://', caseSensitive: false).hasMatch(path) || path.startsWith(r'\\');
/// 190: smb://서버/공유/폴더 (cifs:// 도) → \\서버\공유\폴더. SMB 주소가 아니면 null
String? smbToUnc(String path) {
final m = RegExp(r'^(smb|cifs)://(.+)$', caseSensitive: false).firstMatch(path.trim());
if (m == null) return null;
final parts = m.group(2)!.split(RegExp(r'[/\\]+')).where((s) => s.isNotEmpty).map(_decodeSmbPart).toList();
if (parts.isEmpty) return null;
return '\\\\${parts.join('\\')}';
}
/// %20 같은 주소 글자는 풀고, 한글 등 그대로 쓴 글자는 그대로
String _decodeSmbPart(String s) {
try {
return Uri.decodeComponent(s);
} catch (_) {
return s;
}
}
String get smbNotSupported => tr('네트워크 공유 (SMB) 는 경로로 바로 열지 않습니다. [SMB 서버 추가] 로 서버를 넣으면 탭으로 열립니다');
/// 146: 넣은 경로를 고쳐 쓰기 · 확인. 갈 수 있으면 그 경로, 아니면 칸 아래에 보일 이유
Future<(String?, String?)> checkEnteredPath(String input, {required bool android}) async {
var path = input.trim().replaceAll('"', '');
if (path.isEmpty) return (null, null);
// 179: Android 는 네트워크 공유 (SMB) 를 아직 못 연다 - 폴더가 없다고 하지 않고 WebDAV 로 안내
if (android && looksLikeSmb(path)) return (null, smbNotSupported);
// 190: Windows 는 smb:// · cifs:// 주소를 \\서버\공유 로 바꿔 연다 (주소가 틀린 줄 알지 않게)
final unc = Platform.isWindows ? smbToUnc(path) : null;
if (unc != null) path = unc;
if (Platform.isWindows && RegExp(r'^\\\\[^\\/]+[\\/]?$').hasMatch(path)) {
return (null, tr('네트워크 공유는 공유 폴더 이름까지 넣으세요 (예: \\\\NAS\\영상).'));
}
if (!isDav(path) && Platform.isWindows && RegExp(r'^[a-zA-Z]:$').hasMatch(path)) path = '$path\\';
bool ok;
try {
ok = isDav(path) || await Directory(path).exists();
} catch (_) {
ok = false;
}
if (!ok && unc != null) {
return (null, trf('폴더가 없거나 열 수 없습니다. Windows 의 네트워크 공유는 \\\\서버\\공유 형식입니다 (바꾼 경로: {0})', [unc]));
}
return ok ? (path, null) : (null, tr('폴더가 없거나 열 수 없습니다'));
}
class PathDialog extends StatefulWidget {
const PathDialog({super.key, required this.initial, required this.android});
final String initial;
final bool android;
/// 창이 이것을 돌려주면 WebDAV 서버 추가 창을 연다
static const addDav = '\u0000webdav';
/// 88: 이것 + 넣은 주소를 돌려주면 SMB 서버 추가 창 (주소로 채움)
static const addSmb = '\u0000smb:';
@override
State<PathDialog> createState() => _PathDialogState();
}
class _PathDialogState extends State<PathDialog> {
late final _ctl = TextEditingController(text: widget.initial)
..selection = TextSelection(baseOffset: 0, extentOffset: widget.initial.length);
String? _error;
bool _checking = false;
@override
void dispose() {
_ctl.dispose();
super.dispose();
}
Future<void> _submit() async {
setState(() => _checking = true);
final (path, error) = await checkEnteredPath(_ctl.text, android: widget.android);
if (!mounted) return;
setState(() {
_checking = false;
_error = error;
});
if (path != null) Navigator.pop(context, path);
}
@override
Widget build(BuildContext context) => AlertDialog(
scrollable: true,
title: Text(Platform.isWindows ? tr('경로 입력 (예: D:\\영상 · \\\\NAS\\공유)') : tr('경로 입력')),
content: TextField(
controller: _ctl,
autofocus: true,
decoration: InputDecoration(hintText: tr('경로'), errorText: _error, errorMaxLines: 3),
// 고치기 시작하면 앞의 오류는 지운다
onChanged: (_) {
if (_error != null) setState(() => _error = null);
},
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, '${PathDialog.addSmb}${_ctl.text}'), child: Text(tr('SMB 서버 추가'))),
],
TextButton(onPressed: () => Navigator.pop(context), child: Text(tr('취소'))),
FilledButton(onPressed: _checking ? null : _submit, child: Text(tr('확인'))),
],
);
}