import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../../core/subtitle_detector.dart'; import '../../ui/android_file_browser.dart'; import '../windows/desktop_storage_service.dart'; import '../../l10n/tr.dart'; /// Android 저장소 전체 접근 (MainActivity.kt 의 "jj_mkvmaker/android") class AndroidAccess { static const _ch = MethodChannel('jj_mkvmaker/android'); static Future hasAllFiles() async { try { return await _ch.invokeMethod('hasAllFilesAccess') ?? false; } catch (_) { return false; } } /// 설정 화면 (모든 파일 접근 허용) 을 연다 static Future request() async { try { await _ch.invokeMethod('requestAllFilesAccess'); } catch (_) {} } /// 쓸 수 있는 저장소: (경로, 이름, 빼낼 수 있는지). 내장 저장소가 먼저. /// 읽지 못하면 내장 저장소만. static Future> volumes() async { try { final list = await _ch.invokeMethod>('storageVolumes') ?? const []; final out = [ for (final v in list.cast>()) (v['path'] as String, v['label'] as String? ?? '', v['removable'] == true), ]; out.sort((a, b) => (a.$3 ? 1 : 0) - (b.$3 ? 1 : 0)); if (out.isNotEmpty) return out; } catch (_) {} return [(await storageRoot(), tr('내장 저장소'), false)]; } /// 내장 저장소 맨 위 (예: /storage/emulated/0) static Future storageRoot() async { try { return await _ch.invokeMethod('storageRoot') ?? '/storage/emulated/0'; } catch (_) { return '/storage/emulated/0'; } } } /// Android: 실제 파일 경로가 필요하다 (MKV 를 동영상 옆 jj_mkv 폴더에 만든다). /// 시스템 파일 선택기는 사본을 앱 캐시에 만들어 큰 동영상에 맞지 않으므로 앱 안 파일 고르기 화면을 쓴다. class AndroidStorageService extends DesktopStorageService { /// 파일 고르기 화면을 띄울 곳 (main 에서 정함) static GlobalKey? navigatorKey; Future> _pick(String title, List extensions, {String? initialDirectory}) async { final ctx = navigatorKey?.currentContext; if (ctx == null) return []; return await showAndroidFileBrowser(ctx, title: title, extensions: extensions, initialDirectory: initialDirectory) ?? const []; } @override Future> pickVideos() => _pick(tr('동영상 선택'), videoExtensions); @override Future> pickSubtitles({String? initialDirectory}) => _pick(tr('자막 파일 선택'), subtitleExtensions, initialDirectory: initialDirectory); @override Future> pickFiles({required String title, required List extensions, String? initialDirectory}) => _pick(title, extensions, initialDirectory: initialDirectory); /// 임시 폴더: 캐시 폴더가 아니라 앱 데이터 폴더 아래 tmp. /// 저장 공간이 모자라면 Android 가 캐시 폴더를 마음대로 비워, 쓰는 중인 임시 파일 (AI 음성 · 자막 사본) 이 /// 사라질 수 있다. 대신 앱을 켤 때 (처음 쓸 때) 지난 실행의 임시 파일을 지운다. static Future? _tmp; @override Future tempDirectory() => _tmp ??= () async { final dir = Directory(p.join((await getApplicationSupportDirectory()).path, 'tmp')); if (await dir.exists()) { try { await dir.delete(recursive: true); } catch (_) {} } await dir.create(recursive: true); return dir.path; }(); }