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:
zarzet committed 2026-10-19 00:15:48 +07:00
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;
+6 -8
View File
@@ -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>{};
+2 -1
View File
@@ -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();
}
+1 -1
View File
@@ -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;
}
+2 -1
View File
@@ -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') ||
+3 -2
View File
@@ -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');
}
+2 -1
View File
@@ -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.
+3 -2
View File
@@ -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 {
+3 -3
View File
@@ -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;