Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -437,21 +437,24 @@ fun SongInfoBottomSheet(
text = song.title
)
}
FilledTonalIconButton(
modifier = Modifier
.fillMaxHeight()
.padding(vertical = 6.dp),
colors = IconButtonDefaults.filledTonalIconButtonColors(
containerColor = MaterialTheme.colorScheme.surfaceBright,
contentColor = MaterialTheme.colorScheme.onSurface
),
onClick = { showEditSheet = true },
) {
Icon(
modifier = Modifier.padding(horizontal = 8.dp),
imageVector = Icons.Rounded.Edit,
contentDescription = stringResource(R.string.cd_edit_song_metadata)
)
val isEditable = remember(song) { songInfoViewModel.isSongEditable(song) }
if (isEditable) {
FilledTonalIconButton(
modifier = Modifier
.fillMaxHeight()
.padding(vertical = 6.dp),
colors = IconButtonDefaults.filledTonalIconButtonColors(
containerColor = MaterialTheme.colorScheme.surfaceBright,
contentColor = MaterialTheme.colorScheme.onSurface
),
onClick = { showEditSheet = true },
) {
Icon(
modifier = Modifier.padding(horizontal = 8.dp),
imageVector = Icons.Rounded.Edit,
contentDescription = stringResource(R.string.cd_edit_song_metadata)
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,27 @@ class SongInfoBottomSheetViewModel @Inject constructor(
return transferStateStore.isSongSavedOnAllReachableWatches(songId)
}

fun isSongEditable(song: Song): Boolean {
if (getCloudProviderLabel(song.contentUriString) != null) return false

if (song.path.isNotBlank()) {
val file = File(song.path)
return file.exists() && file.isFile
}

val uri = song.contentUriString
return uri.startsWith("content://") || uri.startsWith("file://")
}

private fun getCloudProviderLabel(contentUriString: String): String? {
val normalized = contentUriString.lowercase().trim()
return when {
contentUriString.startsWith("telegram://") -> "Telegram"
contentUriString.startsWith("netease://") -> "Netease Music"
contentUriString.startsWith("qqmusic://") -> "QQ Music"
contentUriString.startsWith("navidrome://") -> "Navidrome"
contentUriString.startsWith("gdrive://") -> "Google Drive"
normalized.startsWith("telegram://") || normalized.startsWith("telegram:") -> "Telegram"
normalized.startsWith("netease://") || normalized.startsWith("netease:") -> "Netease Music"
normalized.startsWith("qqmusic://") || normalized.startsWith("qqmusic:") -> "QQ Music"
normalized.startsWith("navidrome://") || normalized.startsWith("navidrome:") -> "Navidrome"
normalized.startsWith("gdrive://") || normalized.startsWith("gdrive:") -> "Google Drive"
normalized.startsWith("jellyfin://") || normalized.startsWith("jellyfin:") -> "Jellyfin"
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,49 @@ object MediaStorePermissionHelper {
uris: Collection<Uri>
): IntentSender? {
if (uris.isEmpty()) return null
return MediaStore.createWriteRequest(context.contentResolver, uris).intentSender

// Filter out URIs that do not exist in the MediaStore database
// to avoid IllegalArgumentException: Invalid Uri
val existingIds = try {
val projection = arrayOf(MediaStore.Files.FileColumns._ID)
val idList = uris.mapNotNull { it.lastPathSegment?.toLongOrNull() }
if (idList.isEmpty()) {
emptySet()
} else {
val selection = "${MediaStore.Files.FileColumns._ID} IN (${idList.joinToString(",") { "?" }})"
val selectionArgs = idList.map { it.toString() }.toTypedArray()
context.contentResolver.query(
MediaStore.Files.getContentUri("external"),
projection,
selection,
selectionArgs,
null
)?.use { cursor ->
val idSet = mutableSetOf<Long>()
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
while (cursor.moveToNext()) {
idSet.add(cursor.getLong(idColumn))
}
idSet
} ?: emptySet()
}
} catch (e: Exception) {
emptySet()
}

val validUris = uris.filter { uri ->
val id = uri.lastPathSegment?.toLongOrNull()
id != null && id in existingIds
}

if (validUris.isEmpty()) return null

return try {
MediaStore.createWriteRequest(context.contentResolver, validUris).intentSender
} catch (e: Exception) {
android.util.Log.e("MediaStorePermissionHelper", "Failed to create write request for URIs: $validUris", e)
null
}
}

/**
Expand Down
Loading