import 'package:flutter/material.dart'; import '../app/app_controller.dart'; import '../core/file_ops.dart' show formatSize; import '../core/vfs.dart'; import '../core/webdav.dart' show DavRegistry; import '../l10n/tr.dart'; import 'theme.dart'; /// WebDAV 파일은 열거나 재생하려면 임시 폴더로 받는다 (받는 동안 진행 창 · 취소). 로컬은 그대로. 받지 못하면 null. Future fetchDav(BuildContext context, AppController c, String path) async { if (!isDav(path)) return path; final temp = await c.services.storage.tempDirectory(); if (!context.mounted) return null; final messenger = ScaffoldMessenger.maybeOf(context); final nav = Navigator.of(context); final progress = ValueNotifier<(int, int)>((0, 0)); var cancelled = false; final dialog = showDialog( context: context, barrierDismissible: false, builder: (ctx) => AlertDialog( scrollable: true, title: Text(isSftp(path) ? tr('SFTP 에서 받는 중') : isSmb(path) ? tr('SMB 에서 받는 중') : tr('WebDAV 에서 받는 중')), content: ValueListenableBuilder<(int, int)>( valueListenable: progress, builder: (_, v, _) => Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(vBasename(path)), const SizedBox(height: 10), LinearProgressIndicator(value: v.$2 > 0 ? v.$1 / v.$2 : null), const SizedBox(height: 6), Text('${formatSize(v.$1)}${v.$2 > 0 ? ' / ${formatSize(v.$2)}' : ''}', style: TextStyle(fontSize: 12, color: JjColors.textDim)), ]), ), actions: [ TextButton( onPressed: () { cancelled = true; Navigator.pop(ctx); }, child: Text(tr('취소')), ), ], ), ); try { final local = await vLocalCopy(path, temp, onProgress: (d, t) => progress.value = (d, t)); return cancelled ? null : local; } catch (e) { if (!cancelled) messenger?.showSnackBar(SnackBar(content: Text(trf('받지 못했습니다: {0}', [e])))); return null; } finally { if (!cancelled && nav.mounted) nav.pop(); await dialog; progress.dispose(); } } /// 로그인이 필요한 WebDAV 서버의 파일인지 (아이디나 비밀번호가 있음) bool davNeedsLogin(String path) { if (!isWebDav(path)) return false; final s = DavRegistry.server(DavPath.parse(path).server); return s != null && (s.user.isNotEmpty || s.password.isNotEmpty); } /// 외부 프로그램 · 다른 앱으로 연다 ([program]: 'system' 이나 실행 파일). /// 55: 다른 앱으로 넘기는 주소에는 아이디 · 비밀번호를 넣지 않는다. 그래서 로그인이 필요한 WebDAV 파일이면 /// 조용히 실패하지 않게 먼저 묻는다: [받아서 열기] (임시로 받은 뒤 열기 - 어느 앱이든 열림) · [주소로 열기] (받는 앱이 아이디를 물어야 열림). Future openExternalPlayable(BuildContext context, AppController c, String program, List files) async { // 88: SFTP 는 다른 앱에 넘길 주소가 없다 - 늘 받아서 연다 (묻지 않음) if (files.any(noStreamUrl)) { final local = []; for (final f in files) { if (!context.mounted) return; final l = noStreamUrl(f) ? await fetchDav(context, c, f) : vPlayable(f); if (l == null) return; // 취소 · 실패 local.add(l); } await c.services.shell.openExternal(program, local); return; } if (!files.any(davNeedsLogin)) { await c.services.shell.openExternal(program, files.map(vPlayable).toList()); return; } // 102: 환경 설정 (매번 묻기 · 늘 받아서 · 늘 주소로) final pick = switch (c.settings.davExternalOpen) { 'fetch' => 'fetch', 'url' => 'url', _ => await showDialog( context: context, builder: (ctx) => AlertDialog( scrollable: true, title: Text(tr('이 WebDAV 서버는 로그인이 필요합니다')), content: SizedBox( width: 520, child: Text(tr('다른 앱에는 아이디 · 비밀번호를 넘기지 않습니다.\n\n' '• 받아서 열기: 임시 폴더로 받은 뒤 엽니다. 어느 앱에서나 열리지만 다 받을 때까지 기다려야 합니다.\n' '• 주소로 열기: 받지 않고 바로 재생합니다. 그 앱이 아이디 · 비밀번호를 물으면 넣어 주세요 (VLC 등). ' '묻지 않는 앱에서는 열리지 않습니다.')), ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: Text(tr('취소'))), TextButton(onPressed: () => Navigator.pop(ctx, 'url'), child: Text(tr('주소로 열기'))), FilledButton(onPressed: () => Navigator.pop(ctx, 'fetch'), child: Text(tr('받아서 열기'))), ], ), ), }; if (pick == 'url') { await c.services.shell.openExternal(program, files.map(vPlayable).toList()); } else if (pick == 'fetch') { final local = []; for (final f in files) { if (!context.mounted) return; final l = await fetchDav(context, c, f); if (l == null) return; // 취소 · 실패 local.add(l); } await c.services.shell.openExternal(program, local); } }