Skip to content

Commit 3ae6697

Browse files
authored
Merge pull request #2299 from theovilardo/fix/metadata-editing-crash-on-remote-songs
Large queue insertions
2 parents ef85bc0 + 93c2e16 commit 3ae6697

3 files changed

Lines changed: 80 additions & 21 deletions

File tree

app/src/main/java/com/theveloper/pixelplay/presentation/components/SongInfoBottomSheet.kt

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -437,21 +437,24 @@ fun SongInfoBottomSheet(
437437
text = song.title
438438
)
439439
}
440-
FilledTonalIconButton(
441-
modifier = Modifier
442-
.fillMaxHeight()
443-
.padding(vertical = 6.dp),
444-
colors = IconButtonDefaults.filledTonalIconButtonColors(
445-
containerColor = MaterialTheme.colorScheme.surfaceBright,
446-
contentColor = MaterialTheme.colorScheme.onSurface
447-
),
448-
onClick = { showEditSheet = true },
449-
) {
450-
Icon(
451-
modifier = Modifier.padding(horizontal = 8.dp),
452-
imageVector = Icons.Rounded.Edit,
453-
contentDescription = stringResource(R.string.cd_edit_song_metadata)
454-
)
440+
val isEditable = remember(song) { songInfoViewModel.isSongEditable(song) }
441+
if (isEditable) {
442+
FilledTonalIconButton(
443+
modifier = Modifier
444+
.fillMaxHeight()
445+
.padding(vertical = 6.dp),
446+
colors = IconButtonDefaults.filledTonalIconButtonColors(
447+
containerColor = MaterialTheme.colorScheme.surfaceBright,
448+
contentColor = MaterialTheme.colorScheme.onSurface
449+
),
450+
onClick = { showEditSheet = true },
451+
) {
452+
Icon(
453+
modifier = Modifier.padding(horizontal = 8.dp),
454+
imageVector = Icons.Rounded.Edit,
455+
contentDescription = stringResource(R.string.cd_edit_song_metadata)
456+
)
457+
}
455458
}
456459
}
457460
}

app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/SongInfoBottomSheetViewModel.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,27 @@ class SongInfoBottomSheetViewModel @Inject constructor(
254254
return transferStateStore.isSongSavedOnAllReachableWatches(songId)
255255
}
256256

257+
fun isSongEditable(song: Song): Boolean {
258+
if (getCloudProviderLabel(song.contentUriString) != null) return false
259+
260+
if (song.path.isNotBlank()) {
261+
val file = File(song.path)
262+
return file.exists() && file.isFile
263+
}
264+
265+
val uri = song.contentUriString
266+
return uri.startsWith("content://") || uri.startsWith("file://")
267+
}
268+
257269
private fun getCloudProviderLabel(contentUriString: String): String? {
270+
val normalized = contentUriString.lowercase().trim()
258271
return when {
259-
contentUriString.startsWith("telegram://") -> "Telegram"
260-
contentUriString.startsWith("netease://") -> "Netease Music"
261-
contentUriString.startsWith("qqmusic://") -> "QQ Music"
262-
contentUriString.startsWith("navidrome://") -> "Navidrome"
263-
contentUriString.startsWith("gdrive://") -> "Google Drive"
272+
normalized.startsWith("telegram://") || normalized.startsWith("telegram:") -> "Telegram"
273+
normalized.startsWith("netease://") || normalized.startsWith("netease:") -> "Netease Music"
274+
normalized.startsWith("qqmusic://") || normalized.startsWith("qqmusic:") -> "QQ Music"
275+
normalized.startsWith("navidrome://") || normalized.startsWith("navidrome:") -> "Navidrome"
276+
normalized.startsWith("gdrive://") || normalized.startsWith("gdrive:") -> "Google Drive"
277+
normalized.startsWith("jellyfin://") || normalized.startsWith("jellyfin:") -> "Jellyfin"
264278
else -> null
265279
}
266280
}

app/src/main/java/com/theveloper/pixelplay/utils/MediaStorePermissionHelper.kt

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,49 @@ object MediaStorePermissionHelper {
201201
uris: Collection<Uri>
202202
): IntentSender? {
203203
if (uris.isEmpty()) return null
204-
return MediaStore.createWriteRequest(context.contentResolver, uris).intentSender
204+
205+
// Filter out URIs that do not exist in the MediaStore database
206+
// to avoid IllegalArgumentException: Invalid Uri
207+
val existingIds = try {
208+
val projection = arrayOf(MediaStore.Files.FileColumns._ID)
209+
val idList = uris.mapNotNull { it.lastPathSegment?.toLongOrNull() }
210+
if (idList.isEmpty()) {
211+
emptySet()
212+
} else {
213+
val selection = "${MediaStore.Files.FileColumns._ID} IN (${idList.joinToString(",") { "?" }})"
214+
val selectionArgs = idList.map { it.toString() }.toTypedArray()
215+
context.contentResolver.query(
216+
MediaStore.Files.getContentUri("external"),
217+
projection,
218+
selection,
219+
selectionArgs,
220+
null
221+
)?.use { cursor ->
222+
val idSet = mutableSetOf<Long>()
223+
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
224+
while (cursor.moveToNext()) {
225+
idSet.add(cursor.getLong(idColumn))
226+
}
227+
idSet
228+
} ?: emptySet()
229+
}
230+
} catch (e: Exception) {
231+
emptySet()
232+
}
233+
234+
val validUris = uris.filter { uri ->
235+
val id = uri.lastPathSegment?.toLongOrNull()
236+
id != null && id in existingIds
237+
}
238+
239+
if (validUris.isEmpty()) return null
240+
241+
return try {
242+
MediaStore.createWriteRequest(context.contentResolver, validUris).intentSender
243+
} catch (e: Exception) {
244+
android.util.Log.e("MediaStorePermissionHelper", "Failed to create write request for URIs: $validUris", e)
245+
null
246+
}
205247
}
206248

207249
/**

0 commit comments

Comments
 (0)