From 5bc874a9ff52ba9dda85c210bdc4938c4a06966b Mon Sep 17 00:00:00 2001 From: Damontecres Date: Thu, 23 Jul 2026 14:23:48 -0400 Subject: [PATCH] Fix music artist listing & show music in collections (#1739) ## Description Switches to use the Artists API instead of Items API when fetching the list of artists in a library. Enables fetching artists, albums, and music videos in a collection. Also adds titles for the remaining `BaseItemKind` types for future proofing even if they aren't used anywhere in the app. ### Related issues Fixes #1733 Fixes #1734 Fixes #1735 ### Testing Emulator, created a new collection with the above. Also verified the artist query against the web UI ## Screenshots N/A ## AI or LLM usage None --- .../wholphin/data/model/GetItemsFilter.kt | 18 +++++++ .../damontecres/wholphin/ui/Formatting.kt | 44 ++++++++--------- .../ui/components/CollectionFolderView.kt | 22 +++++++++ .../ui/detail/CollectionFolderMusic.kt | 3 +- .../ui/detail/collection/CollectionDetails.kt | 2 +- .../ui/detail/collection/CollectionRows.kt | 49 ++++++++++++++----- .../detail/collection/CollectionViewModel.kt | 3 ++ .../wholphin/util/ApiRequestPager.kt | 22 +++++++++ app/src/main/res/values/strings.xml | 6 +++ 9 files changed, 134 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/GetItemsFilter.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/GetItemsFilter.kt index 792ec6ca..3746092b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/GetItemsFilter.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/GetItemsFilter.kt @@ -9,6 +9,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.VideoType +import org.jellyfin.sdk.model.api.request.GetArtistsRequest import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.api.request.GetPersonsRequest import org.jellyfin.sdk.model.serializer.UUIDSerializer @@ -138,6 +139,22 @@ data class GetItemsFilter( isFavorite = favorite, ) + fun applyTo(req: GetArtistsRequest) = + req.copy( + minCommunityRating = minCommunityRating, + isFavorite = favorite, + genreIds = genres, + personIds = persons, + studioIds = studios, + tags = tags, + officialRatings = officialRatings, + years = + buildSet { + years?.letNotEmpty(::addAll) + decades?.forEach { addAll(it..<(it + 10)) } + }, + ) + /** * Merge another [GetItemsFilter] onto this one, replacing only unset values */ @@ -162,4 +179,5 @@ data class GetItemsFilter( enum class GetItemsFilterOverride { NONE, PERSON, + ARTIST, } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt index a9ccd690..aeb22e95 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt @@ -230,36 +230,36 @@ fun formatTypeName(type: BaseItemKind): Int = BaseItemKind.PLAYLIST -> R.string.playlists BaseItemKind.PERSON -> R.string.people_title BaseItemKind.BOX_SET -> R.string.collections - BaseItemKind.AUDIO -> TODO() + BaseItemKind.AUDIO -> R.string.songs BaseItemKind.CHANNEL -> R.string.channels BaseItemKind.GENRE -> R.string.genres BaseItemKind.LIVE_TV_CHANNEL -> R.string.channels - BaseItemKind.MUSIC_ALBUM -> TODO() - BaseItemKind.MUSIC_ARTIST -> TODO() - BaseItemKind.MUSIC_GENRE -> TODO() - BaseItemKind.MUSIC_VIDEO -> TODO() + BaseItemKind.MUSIC_ALBUM -> R.string.albums + BaseItemKind.MUSIC_ARTIST -> R.string.artists + BaseItemKind.MUSIC_GENRE -> R.string.genres + BaseItemKind.MUSIC_VIDEO -> R.string.music_videos BaseItemKind.PHOTO -> R.string.photos - BaseItemKind.PHOTO_ALBUM -> TODO() - BaseItemKind.PROGRAM -> TODO() - BaseItemKind.RECORDING -> TODO() + BaseItemKind.PHOTO_ALBUM -> R.string.photos + BaseItemKind.PROGRAM -> R.string.programs + BaseItemKind.RECORDING -> R.string.recordings BaseItemKind.SEASON -> R.string.tv_seasons BaseItemKind.STUDIO -> R.string.studios BaseItemKind.TRAILER -> R.string.trailers_title BaseItemKind.TV_CHANNEL -> R.string.channels - BaseItemKind.TV_PROGRAM -> TODO() - BaseItemKind.USER_ROOT_FOLDER -> TODO() - BaseItemKind.USER_VIEW -> TODO() - BaseItemKind.YEAR -> TODO() - BaseItemKind.AGGREGATE_FOLDER -> TODO() - BaseItemKind.AUDIO_BOOK -> TODO() - BaseItemKind.BASE_PLUGIN_FOLDER -> TODO() - BaseItemKind.BOOK -> TODO() - BaseItemKind.CHANNEL_FOLDER_ITEM -> TODO() - BaseItemKind.COLLECTION_FOLDER -> TODO() - BaseItemKind.FOLDER -> TODO() - BaseItemKind.MANUAL_PLAYLISTS_FOLDER -> TODO() - BaseItemKind.LIVE_TV_PROGRAM -> TODO() - BaseItemKind.PLAYLISTS_FOLDER -> TODO() + BaseItemKind.TV_PROGRAM -> R.string.programs + BaseItemKind.USER_ROOT_FOLDER -> R.string.folders_title + BaseItemKind.USER_VIEW -> R.string.user_views + BaseItemKind.YEAR -> R.string.year + BaseItemKind.AGGREGATE_FOLDER -> R.string.folders_title + BaseItemKind.AUDIO_BOOK -> R.string.audio_books + BaseItemKind.BASE_PLUGIN_FOLDER -> R.string.folders_title + BaseItemKind.BOOK -> R.string.books + BaseItemKind.CHANNEL_FOLDER_ITEM -> R.string.folders_title + BaseItemKind.COLLECTION_FOLDER -> R.string.library + BaseItemKind.FOLDER -> R.string.folders_title + BaseItemKind.MANUAL_PLAYLISTS_FOLDER -> R.string.playlists + BaseItemKind.LIVE_TV_PROGRAM -> R.string.programs + BaseItemKind.PLAYLISTS_FOLDER -> R.string.playlists } /** diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderView.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderView.kt index 939be43b..56eb4bd2 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderView.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderView.kt @@ -67,6 +67,7 @@ import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.util.ApiRequestPager import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.ExceptionHandler +import com.github.damontecres.wholphin.util.GetArtistsHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetPersonsHandler import com.github.damontecres.wholphin.util.LoadingState @@ -92,6 +93,7 @@ import org.jellyfin.sdk.model.api.CollectionType import org.jellyfin.sdk.model.api.ImageType import org.jellyfin.sdk.model.api.ItemSortBy import org.jellyfin.sdk.model.api.SortOrder +import org.jellyfin.sdk.model.api.request.GetArtistsRequest import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.api.request.GetPersonsRequest import org.jellyfin.sdk.model.serializer.toUUID @@ -373,6 +375,26 @@ class CollectionFolderViewModel ) newPager } + + GetItemsFilterOverride.ARTIST -> { + val item = state.value.item.successValue + val request = + filter.applyTo( + GetArtistsRequest( + parentId = item?.id, + enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB), + ), + ) + val newPager = + ApiRequestPager( + api, + request, + GetArtistsHandler, + viewModelScope, + useSeriesForPrimary = useSeriesForPrimary, + ) + newPager + } } private fun createGetItemsRequest( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderMusic.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderMusic.kt index 431e8e8e..a95a1ea2 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderMusic.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderMusic.kt @@ -17,6 +17,7 @@ import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.CollectionFolderFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter +import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.MusicService @@ -203,7 +204,7 @@ fun CollectionFolderMusic( CollectionFolderFilter( filter = GetItemsFilter( - includeItemTypes = listOf(BaseItemKind.MUSIC_ARTIST), + override = GetItemsFilterOverride.ARTIST, ), ), showTitle = false, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionDetails.kt index 7366bf59..99db156f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionDetails.kt @@ -425,7 +425,7 @@ fun CollectionDetailsContent( onClickPlay = onClickPlay, modifier = Modifier.fillMaxSize(), onFocusPosition = { position -> - Timber.v("onFocusPosition=%s", position) +// Timber.v("onFocusPosition=%s", position) focusedItem = position.let { val key = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionRows.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionRows.kt index f1e85ee2..126a5765 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionRows.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionRows.kt @@ -51,18 +51,45 @@ fun CollectionRows( if (row is HomeRowLoadingState.Success) { // TODO not great to do this in the UI val viewOptions = - if (type == BaseItemKind.EPISODE) { - HomeRowViewOptions( - heightDp = Cards.HEIGHT_EPISODE, - episodeAspectRatio = AspectRatio.WIDE, - showTitles = cardViewOptions.showTitles, - useSeries = false, - ) - } else { - HomeRowViewOptions( - showTitles = cardViewOptions.showTitles, - ) + when (type) { + BaseItemKind.EPISODE -> { + HomeRowViewOptions( + heightDp = Cards.HEIGHT_EPISODE, + episodeAspectRatio = AspectRatio.WIDE, + showTitles = cardViewOptions.showTitles, + useSeries = false, + ) + } + + BaseItemKind.MUSIC_ARTIST, + BaseItemKind.MUSIC_ALBUM, + BaseItemKind.PLAYLIST, + BaseItemKind.AUDIO, + -> { + HomeRowViewOptions( + heightDp = Cards.HEIGHT_EPISODE, + aspectRatio = AspectRatio.SQUARE, + showTitles = cardViewOptions.showTitles, + ) + } + + BaseItemKind.VIDEO, + BaseItemKind.MUSIC_VIDEO, + -> { + HomeRowViewOptions( + heightDp = Cards.HEIGHT_EPISODE, + aspectRatio = AspectRatio.WIDE, + showTitles = cardViewOptions.showTitles, + ) + } + + else -> { + HomeRowViewOptions( + showTitles = cardViewOptions.showTitles, + ) + } } + row.copy(viewOptions = viewOptions) } else { row diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionViewModel.kt index 1576b745..cab18bfa 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionViewModel.kt @@ -489,6 +489,9 @@ class CollectionViewModel BaseItemKind.EPISODE, BaseItemKind.VIDEO, BaseItemKind.BOX_SET, + BaseItemKind.MUSIC_ARTIST, + BaseItemKind.MUSIC_ALBUM, + BaseItemKind.MUSIC_VIDEO, ) const val VIEW_OPTIONS_KEY = "CollectionViewOptions" diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/ApiRequestPager.kt b/app/src/main/java/com/github/damontecres/wholphin/util/ApiRequestPager.kt index 555a5a51..a87d79ec 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/ApiRequestPager.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/ApiRequestPager.kt @@ -10,6 +10,7 @@ import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.Response +import org.jellyfin.sdk.api.client.extensions.artistsApi import org.jellyfin.sdk.api.client.extensions.genresApi import org.jellyfin.sdk.api.client.extensions.itemsApi import org.jellyfin.sdk.api.client.extensions.liveTvApi @@ -21,6 +22,7 @@ import org.jellyfin.sdk.api.client.extensions.tvShowsApi import org.jellyfin.sdk.api.client.extensions.userLibraryApi import org.jellyfin.sdk.model.api.BaseItemDtoQueryResult import org.jellyfin.sdk.model.api.GetProgramsDto +import org.jellyfin.sdk.model.api.request.GetArtistsRequest import org.jellyfin.sdk.model.api.request.GetEpisodesRequest import org.jellyfin.sdk.model.api.request.GetGenresRequest import org.jellyfin.sdk.model.api.request.GetItemsRequest @@ -367,6 +369,25 @@ object GetLiveTvChannelsRequestHandler : RequestHandler = api.liveTvApi.getLiveTvChannels(request) } +@Serializable +object GetArtistsHandler : RequestHandler { + override fun prepare( + request: GetArtistsRequest, + startIndex: Int, + limit: Int, + enableTotalRecordCount: Boolean, + ): GetArtistsRequest = + request.copy( + startIndex = startIndex, + limit = limit, + ) + + override suspend fun execute( + api: ApiClient, + request: GetArtistsRequest, + ): Response = api.artistsApi.getArtists((request)) +} + val requestSerializersModule = SerializersModule { polymorphic(Any::class) { @@ -382,5 +403,6 @@ val requestSerializersModule = subclass(GetStudiosRequest::class) subclass(GetRecordingsRequest::class) subclass(GetLiveTvChannelsRequest::class) + subclass(GetArtistsHandler::class) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a9bbb1e0..aa33cf7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -874,4 +874,10 @@ Any language Hash matches SDH + TV programs + Recordings + Folders + User views + Books + Audio books