mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-20 22:41:38 +02:00
refactor(io): use isContentUri instead of inline content:// checks
The same startsWith check was inlined in 21 places across services and providers. They now call the existing helper, which has identical semantics (no trim, no case folding). No behavior change.
This commit is contained in:
1 parent
0797085385
commit
d6fd05c3a7
10 files changed
+27
-23
No files matched your search
@@ -248,7 +248,7 @@ extension _HistoryStartupMaintenance on DownloadHistoryNotifier {
|
||||
bool _supportsAudioMetadataProbe(String filePath) {
|
||||
final trimmed = filePath.trim().toLowerCase();
|
||||
if (trimmed.isEmpty) return false;
|
||||
if (trimmed.startsWith('content://')) return true;
|
||||
if (isContentUri(trimmed)) return true;
|
||||
return trimmed.endsWith('.flac') ||
|
||||
trimmed.endsWith('.m4a') ||
|
||||
trimmed.endsWith('.mp4') ||
|
||||
@@ -297,7 +297,7 @@ extension _HistoryStartupMaintenance on DownloadHistoryNotifier {
|
||||
trimmedPath.endsWith('.m4a') ||
|
||||
trimmedPath.endsWith('.mp4') ||
|
||||
trimmedPath.endsWith('.aac') ||
|
||||
trimmedPath.startsWith('content://'));
|
||||
isContentUri(trimmedPath));
|
||||
|
||||
if (hasResolvedSpecs && !isPlaceholderQualityLabel(item.quality)) {
|
||||
final needsComposerBackfill =
|
||||
@@ -382,7 +382,7 @@ extension _HistoryStartupMaintenance on DownloadHistoryNotifier {
|
||||
|
||||
String? tempPath;
|
||||
try {
|
||||
if (filePath.startsWith('content://')) {
|
||||
if (isContentUri(filePath)) {
|
||||
final treeUri = normalizeOptionalString(item.downloadTreeUri);
|
||||
final fileName = normalizeOptionalString(item.safFileName);
|
||||
if (treeUri == null || fileName == null) return null;
|
||||
|
||||
@@ -591,7 +591,7 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
}
|
||||
|
||||
String _displayNameForPath(String path) {
|
||||
if (path.startsWith('content://')) {
|
||||
if (isContentUri(path)) {
|
||||
try {
|
||||
final decoded = Uri.decodeComponent(Uri.parse(path).pathSegments.last);
|
||||
final relative = decoded.contains(':')
|
||||
@@ -611,7 +611,7 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
}
|
||||
|
||||
bool _looksLikeRemovablePath(String path) {
|
||||
if (path.startsWith('content://')) {
|
||||
if (isContentUri(path)) {
|
||||
try {
|
||||
final decoded = Uri.decodeComponent(Uri.parse(path).pathSegments.last);
|
||||
return !decoded.startsWith('primary:');
|
||||
@@ -636,7 +636,7 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
if (_availabilityProbe != null) {
|
||||
return await _availabilityProbe(path, bookmark: bookmark);
|
||||
}
|
||||
if (Platform.isAndroid && path.startsWith('content://')) {
|
||||
if (Platform.isAndroid && isContentUri(path)) {
|
||||
return await PlatformBridge.probeSafTreeReadAccess(path);
|
||||
}
|
||||
if (Platform.isIOS && bookmark != null && bookmark.trim().isNotEmpty) {
|
||||
@@ -970,7 +970,7 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
if (!ref.mounted || _scanCancelRequested) return;
|
||||
final effectiveFolderPath = resolvedPath ?? folderPath;
|
||||
|
||||
final isSaf = effectiveFolderPath.startsWith('content://');
|
||||
final isSaf = isContentUri(effectiveFolderPath);
|
||||
|
||||
final useStreamingFullScan =
|
||||
effectiveFolderPath.startsWith('network://') ||
|
||||
@@ -1777,9 +1777,7 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
}
|
||||
|
||||
if (isSaf) {
|
||||
final uris = legacyPaths
|
||||
.where((path) => path.startsWith('content://'))
|
||||
.toList();
|
||||
final uris = legacyPaths.where((path) => isContentUri(path)).toList();
|
||||
if (uris.isEmpty) {
|
||||
return const {};
|
||||
}
|
||||
@@ -1805,7 +1803,7 @@ class LocalLibraryNotifier extends Notifier<LocalLibraryState> {
|
||||
}
|
||||
|
||||
final paths = legacyPaths
|
||||
.where((path) => !path.startsWith('content://'))
|
||||
.where((path) => !isContentUri(path))
|
||||
.toList(growable: false);
|
||||
const chunkSize = 24;
|
||||
final backfilled = <String, int>{};
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:spotiflac_android/models/unified_library_item.dart';
|
||||
import 'package:spotiflac_android/providers/download_queue_provider.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
import 'package:spotiflac_android/services/music_player_service.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
|
||||
final musicPlayerRuntimeProvider = Provider<MusicPlayerRuntime>(
|
||||
(ref) => musicPlayerRuntime,
|
||||
@@ -301,7 +302,7 @@ PlayableMedia playableFromLocal(LocalLibraryItem item) {
|
||||
String? art;
|
||||
final cover = item.coverPath;
|
||||
if (cover != null && cover.trim().isNotEmpty) {
|
||||
art = cover.startsWith('http') || cover.startsWith('content://')
|
||||
art = cover.startsWith('http') || isContentUri(cover)
|
||||
? cover
|
||||
: Uri.file(cover).toString();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class PlaybackController extends Notifier<PlaybackState> {
|
||||
final value = cover.trim();
|
||||
if (value.isEmpty) return null;
|
||||
if (value.startsWith('http') ||
|
||||
value.startsWith('content://') ||
|
||||
isContentUri(value) ||
|
||||
value.startsWith('file://')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:spotiflac_android/models/download_result.dart';
|
||||
import 'package:spotiflac_android/utils/audio_format_utils.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/utils/int_utils.dart';
|
||||
|
||||
typedef DownloadCompletionAudio = ({
|
||||
@@ -30,7 +31,7 @@ Future<DownloadCompletionAudio> resolveDownloadCompletionAudio({
|
||||
normalizeAudioFormatValue(audioFormatForPath(filePath));
|
||||
var bitrate = download.bitrateKbps;
|
||||
final lowerPath = filePath.toLowerCase();
|
||||
if (filePath.startsWith('content://') ||
|
||||
if (isContentUri(filePath) ||
|
||||
const [
|
||||
'.flac',
|
||||
'.m4a',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:spotiflac_android/models/download_result.dart';
|
||||
import 'package:spotiflac_android/services/download_saf_file_replacer.dart';
|
||||
import 'package:spotiflac_android/utils/audio_format_utils.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
|
||||
/// Finalizes a requested lossless container using the measured audio stream.
|
||||
/// Native FLAC bytes can be renamed; FLAC-in-MP4 still needs remuxing.
|
||||
@@ -36,7 +37,7 @@ class DownloadContainerFinalizer {
|
||||
forceConversion = forceConversion || result.requiresContainerConversion;
|
||||
final outputExtension = result.outputExtension(path: filePath);
|
||||
final lowerPath = filePath.toLowerCase();
|
||||
final isSafSource = filePath.startsWith('content://');
|
||||
final isSafSource = isContentUri(filePath);
|
||||
final pathLooksLikeM4a =
|
||||
lowerPath.endsWith('.m4a') ||
|
||||
lowerPath.endsWith('.mp4') ||
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:spotiflac_android/services/download_saf_file_replacer.dart';
|
||||
import 'package:spotiflac_android/services/ffmpeg_models.dart';
|
||||
import 'package:spotiflac_android/utils/audio_conversion_utils.dart';
|
||||
import 'package:spotiflac_android/utils/audio_format_utils.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
|
||||
typedef DecryptDownloadFile =
|
||||
Future<String?> Function({
|
||||
@@ -120,7 +121,7 @@ class DownloadFileFinalizer {
|
||||
return DownloadDecryptionOutcome(filePath);
|
||||
}
|
||||
onStart?.call(descriptor.normalizedStrategy);
|
||||
if (useSaf && filePath.startsWith('content://')) {
|
||||
if (useSaf && isContentUri(filePath)) {
|
||||
if (treeUri == null || treeUri.isEmpty) {
|
||||
return const DownloadDecryptionOutcome(
|
||||
null,
|
||||
@@ -239,7 +240,7 @@ class DownloadFileFinalizer {
|
||||
onStart?.call();
|
||||
String? convertedPath;
|
||||
String? publishedName = convertedName;
|
||||
if (useSaf && filePath.startsWith('content://')) {
|
||||
if (useSaf && isContentUri(filePath)) {
|
||||
if (treeUri == null || treeUri.isEmpty) {
|
||||
throw StateError('Missing SAF tree for automatic conversion');
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
|
||||
final _log = AppLogger('M3uPlaylist');
|
||||
@@ -122,7 +123,7 @@ class M3uPlaylistService {
|
||||
static String? exportPathFor(String rawPath) {
|
||||
final trimmed = rawPath.trim();
|
||||
if (trimmed.isEmpty) return null;
|
||||
if (!trimmed.startsWith('content://')) return trimmed;
|
||||
if (!isContentUri(trimmed)) return trimmed;
|
||||
|
||||
try {
|
||||
// Uri.pathSegments already percent-decodes each segment once.
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:spotiflac_android/services/audio_analysis_jobs.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart' show AppLogger;
|
||||
|
||||
const musicHapticsSampleRate = 4000;
|
||||
@@ -177,7 +178,7 @@ class MusicHapticsAnalyzer {
|
||||
List<String> arguments,
|
||||
) async {
|
||||
final inputIndex = arguments.indexOf('-i') + 1;
|
||||
if (inputIndex > 0 && arguments[inputIndex].startsWith('content://')) {
|
||||
if (inputIndex > 0 && isContentUri(arguments[inputIndex])) {
|
||||
// Resolve only when this owned job starts. FFmpegKit opens and closes
|
||||
// its own SAF descriptor, independently of the player's short lease.
|
||||
final input = await FFmpegKitConfig.getSafParameterForRead(
|
||||
@@ -221,7 +222,7 @@ class MusicHapticsAnalyzer {
|
||||
Directory? work;
|
||||
try {
|
||||
String? key;
|
||||
if (path.startsWith('content://')) {
|
||||
if (isContentUri(path)) {
|
||||
// A document URI is not a filesystem path. Unknown modification times
|
||||
// still allow analysis, but cannot safely identify a cached revision.
|
||||
try {
|
||||
|
||||
@@ -1008,7 +1008,7 @@ class PlatformBridge {
|
||||
static Future<ContentUriPlaybackLease?> openContentUriPlaybackLease(
|
||||
String uri,
|
||||
) async {
|
||||
if (!Platform.isAndroid || !uri.startsWith('content://')) return null;
|
||||
if (!Platform.isAndroid || !isContentUri(uri)) return null;
|
||||
final result = await _channel.invokeMethod('safOpenPlaybackLease', {
|
||||
'uri': uri,
|
||||
});
|
||||
@@ -2688,7 +2688,7 @@ class PlatformBridge {
|
||||
}) async {
|
||||
final input = <String, dynamic>{};
|
||||
if (itemId != null && itemId.isNotEmpty) input['item_id'] = itemId;
|
||||
if (filePath.startsWith('content://')) {
|
||||
if (isContentUri(filePath)) {
|
||||
input['uri'] = filePath;
|
||||
} else {
|
||||
input['path'] = filePath;
|
||||
@@ -2704,7 +2704,7 @@ class PlatformBridge {
|
||||
final uri = result['new_file_uri'];
|
||||
final path = result['new_file_path'];
|
||||
final legacyPath = result['file_path'];
|
||||
if (filePath.startsWith('content://') && uri is String && uri.isNotEmpty) {
|
||||
if (isContentUri(filePath) && uri is String && uri.isNotEmpty) {
|
||||
result['file_path'] = uri;
|
||||
} else if (path is String && path.isNotEmpty) {
|
||||
result['file_path'] = path;
|
||||
|
||||
Reference in new issue
Block a user