JJ_MKVMaker/lib/platform/android/android_storage.dart

93 lines
3.5 KiB
Dart

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';
/// Android 저장소 전체 접근 (MainActivity.kt 의 "jj_mkvmaker/android")
class AndroidAccess {
static const _ch = MethodChannel('jj_mkvmaker/android');
static Future<bool> hasAllFiles() async {
try {
return await _ch.invokeMethod<bool>('hasAllFilesAccess') ?? false;
} catch (_) {
return false;
}
}
/// 설정 화면 (모든 파일 접근 허용) 을 연다
static Future<void> request() async {
try {
await _ch.invokeMethod<void>('requestAllFilesAccess');
} catch (_) {}
}
/// 쓸 수 있는 저장소: (경로, 이름, 빼낼 수 있는지). 내장 저장소가 먼저.
/// 읽지 못하면 내장 저장소만.
static Future<List<(String, String, bool)>> volumes() async {
try {
final list = await _ch.invokeMethod<List<Object?>>('storageVolumes') ?? const [];
final out = [
for (final v in list.cast<Map<Object?, Object?>>())
(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(), '내장 저장소', false)];
}
/// 내장 저장소 맨 위 (예: /storage/emulated/0)
static Future<String> storageRoot() async {
try {
return await _ch.invokeMethod<String>('storageRoot') ?? '/storage/emulated/0';
} catch (_) {
return '/storage/emulated/0';
}
}
}
/// Android: 실제 파일 경로가 필요하다 (MKV 를 동영상 옆 jj_mkv 폴더에 만든다).
/// 시스템 파일 선택기는 사본을 앱 캐시에 만들어 큰 동영상에 맞지 않으므로 앱 안 파일 고르기 화면을 쓴다.
class AndroidStorageService extends DesktopStorageService {
/// 파일 고르기 화면을 띄울 곳 (main 에서 정함)
static GlobalKey<NavigatorState>? navigatorKey;
Future<List<String>> _pick(String title, List<String> 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<List<String>> pickVideos() => _pick('동영상 선택', videoExtensions);
@override
Future<List<String>> pickSubtitles({String? initialDirectory}) =>
_pick('자막 파일 선택', subtitleExtensions, initialDirectory: initialDirectory);
/// 임시 폴더: 캐시 폴더가 아니라 앱 데이터 폴더 아래 tmp.
/// 저장 공간이 모자라면 Android 가 캐시 폴더를 마음대로 비워, 쓰는 중인 임시 파일 (AI 음성 · 자막 사본) 이
/// 사라질 수 있다. 대신 앱을 켤 때 (처음 쓸 때) 지난 실행의 임시 파일을 지운다.
static Future<String>? _tmp;
@override
Future<String> 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;
}();
}