124 lines
5.2 KiB
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('확인'))),
|
|
],
|
|
);
|
|
}
|