mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-04-01 01:20:21 +02:00
- Add SAF tree picker and persistent URI storage in settings - Implement SAF file operations: exists, delete, stat, copy, create - Update download pipeline to support SAF content URIs - Add fallback to app-private storage when SAF write fails - Support SAF in library scan with DocumentFile traversal - Add history item repair for missing SAF URIs - Create file_access.dart utilities for abstracted file operations - Update Tidal/Qobuz/Amazon/Extensions for SAF-aware output - Add runPostProcessingV2 API for SAF content URIs - Update screens (album, artist, queue, track) for SAF awareness Resolves Android 10+ scoped storage permission issues
67 lines
1.8 KiB
Dart
67 lines
1.8 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:open_filex/open_filex.dart';
|
|
import 'package:spotiflac_android/services/platform_bridge.dart';
|
|
import 'package:spotiflac_android/utils/mime_utils.dart';
|
|
|
|
class FileAccessStat {
|
|
final int? size;
|
|
final DateTime? modified;
|
|
|
|
const FileAccessStat({this.size, this.modified});
|
|
}
|
|
|
|
bool isContentUri(String? path) {
|
|
return path != null && path.startsWith('content://');
|
|
}
|
|
|
|
Future<bool> fileExists(String? path) async {
|
|
if (path == null || path.isEmpty) return false;
|
|
if (isContentUri(path)) {
|
|
return PlatformBridge.safExists(path);
|
|
}
|
|
return File(path).exists();
|
|
}
|
|
|
|
Future<void> deleteFile(String? path) async {
|
|
if (path == null || path.isEmpty) return;
|
|
if (isContentUri(path)) {
|
|
await PlatformBridge.safDelete(path);
|
|
return;
|
|
}
|
|
try {
|
|
await File(path).delete();
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<FileAccessStat?> fileStat(String? path) async {
|
|
if (path == null || path.isEmpty) return null;
|
|
if (isContentUri(path)) {
|
|
final stat = await PlatformBridge.safStat(path);
|
|
final exists = stat['exists'] as bool? ?? true;
|
|
if (!exists) return null;
|
|
return FileAccessStat(
|
|
size: stat['size'] as int?,
|
|
modified: stat['modified'] != null
|
|
? DateTime.fromMillisecondsSinceEpoch(stat['modified'] as int)
|
|
: null,
|
|
);
|
|
}
|
|
|
|
final stat = await FileStat.stat(path);
|
|
if (stat.type == FileSystemEntityType.notFound) return null;
|
|
return FileAccessStat(size: stat.size, modified: stat.modified);
|
|
}
|
|
|
|
Future<void> openFile(String path) async {
|
|
if (isContentUri(path)) {
|
|
await PlatformBridge.openContentUri(path, mimeType: '');
|
|
return;
|
|
}
|
|
final mimeType = audioMimeTypeForPath(path);
|
|
final result = await OpenFilex.open(path, type: mimeType);
|
|
if (result.type != ResultType.done) {
|
|
throw Exception(result.message);
|
|
}
|
|
}
|