Show extras for music albums (#1426)

## Description
Adds the standard extras row to the album detail page if there are any
extras

I also tried to get Jellyfin to recognize artist extras, but it did not
work, so this just adds extras for albums

### Related issues
Closes #1409 

### Testing
Emulator

## Screenshots
N/A, same extras row

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-05-21 15:31:43 -04:00 committed by GitHub
parent fa914845f4
commit 223d2d1f71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -45,10 +45,12 @@ import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ExtrasItem
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.ExtrasService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.ImageUrlService
import com.github.damontecres.wholphin.services.MediaManagementService import com.github.damontecres.wholphin.services.MediaManagementService
@ -60,6 +62,7 @@ import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.DefaultItemFields import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
import com.github.damontecres.wholphin.ui.cards.ExtrasRow
import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.components.ContextMenu import com.github.damontecres.wholphin.ui.components.ContextMenu
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
@ -123,6 +126,7 @@ class AlbumViewModel
private val userPreferencesService: UserPreferencesService, private val userPreferencesService: UserPreferencesService,
private val backdropService: BackdropService, private val backdropService: BackdropService,
private val imageUrlService: ImageUrlService, private val imageUrlService: ImageUrlService,
private val extrasService: ExtrasService,
@Assisted itemId: UUID, @Assisted itemId: UUID,
) : MusicViewModel(itemId, context, api, musicService, navigationManager, mediaManagementService) { ) : MusicViewModel(itemId, context, api, musicService, navigationManager, mediaManagementService) {
@AssistedFactory @AssistedFactory
@ -213,6 +217,14 @@ class AlbumViewModel
} }
} }
} }
viewModelScope.launchIO {
val extras = extrasService.getExtras(itemId)
if (extras.isNotEmpty()) {
_state.update {
it.copy(extras = extras)
}
}
}
} catch (ex: Exception) { } catch (ex: Exception) {
_state.update { it.copy(loading = LoadingState.Error(ex)) } _state.update { it.copy(loading = LoadingState.Error(ex)) }
} }
@ -281,6 +293,7 @@ data class AlbumState(
val similar: List<BaseItem>, val similar: List<BaseItem>,
val loading: LoadingState, val loading: LoadingState,
val musicVideos: List<BaseItem?> = emptyList(), val musicVideos: List<BaseItem?> = emptyList(),
val extras: List<ExtrasItem> = emptyList(),
val canDelete: Boolean = false, val canDelete: Boolean = false,
) { ) {
companion object { companion object {
@ -291,7 +304,8 @@ data class AlbumState(
private const val HEADER_ROW = 0 private const val HEADER_ROW = 0
private const val SONG_ROW = HEADER_ROW + 1 private const val SONG_ROW = HEADER_ROW + 1
private const val MUSIC_VIDEO_ROW = SONG_ROW + 1 private const val MUSIC_VIDEO_ROW = SONG_ROW + 1
private const val SIMILAR_ROW = MUSIC_VIDEO_ROW + 1 private const val EXTRAS_ROW = SONG_ROW + 1
private const val SIMILAR_ROW = EXTRAS_ROW + 1
@Composable @Composable
fun AlbumDetailsPage( fun AlbumDetailsPage(
@ -545,6 +559,19 @@ fun AlbumDetailsPage(
) )
} }
} }
if (state.extras.isNotEmpty()) {
item {
ExtrasRow(
extras = state.extras,
onClickItem = { index, extra ->
position = RowColumn(EXTRAS_ROW, index)
viewModel.navigationManager.navigateTo(extra.destination)
},
onLongClickItem = { _, _ -> },
modifier = Modifier.focusRequester(focusRequesters[EXTRAS_ROW]),
)
}
}
if (state.similar.isNotEmpty()) { if (state.similar.isNotEmpty()) {
item { item {
ItemRow( ItemRow(