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
This commit is contained in:
parent
00490b14ec
commit
5bc874a9ff
9 changed files with 134 additions and 35 deletions
|
|
@ -9,6 +9,7 @@ import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.VideoType
|
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.GetItemsRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
||||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||||
|
|
@ -138,6 +139,22 @@ data class GetItemsFilter(
|
||||||
isFavorite = favorite,
|
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
|
* Merge another [GetItemsFilter] onto this one, replacing only unset values
|
||||||
*/
|
*/
|
||||||
|
|
@ -162,4 +179,5 @@ data class GetItemsFilter(
|
||||||
enum class GetItemsFilterOverride {
|
enum class GetItemsFilterOverride {
|
||||||
NONE,
|
NONE,
|
||||||
PERSON,
|
PERSON,
|
||||||
|
ARTIST,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,36 +230,36 @@ fun formatTypeName(type: BaseItemKind): Int =
|
||||||
BaseItemKind.PLAYLIST -> R.string.playlists
|
BaseItemKind.PLAYLIST -> R.string.playlists
|
||||||
BaseItemKind.PERSON -> R.string.people_title
|
BaseItemKind.PERSON -> R.string.people_title
|
||||||
BaseItemKind.BOX_SET -> R.string.collections
|
BaseItemKind.BOX_SET -> R.string.collections
|
||||||
BaseItemKind.AUDIO -> TODO()
|
BaseItemKind.AUDIO -> R.string.songs
|
||||||
BaseItemKind.CHANNEL -> R.string.channels
|
BaseItemKind.CHANNEL -> R.string.channels
|
||||||
BaseItemKind.GENRE -> R.string.genres
|
BaseItemKind.GENRE -> R.string.genres
|
||||||
BaseItemKind.LIVE_TV_CHANNEL -> R.string.channels
|
BaseItemKind.LIVE_TV_CHANNEL -> R.string.channels
|
||||||
BaseItemKind.MUSIC_ALBUM -> TODO()
|
BaseItemKind.MUSIC_ALBUM -> R.string.albums
|
||||||
BaseItemKind.MUSIC_ARTIST -> TODO()
|
BaseItemKind.MUSIC_ARTIST -> R.string.artists
|
||||||
BaseItemKind.MUSIC_GENRE -> TODO()
|
BaseItemKind.MUSIC_GENRE -> R.string.genres
|
||||||
BaseItemKind.MUSIC_VIDEO -> TODO()
|
BaseItemKind.MUSIC_VIDEO -> R.string.music_videos
|
||||||
BaseItemKind.PHOTO -> R.string.photos
|
BaseItemKind.PHOTO -> R.string.photos
|
||||||
BaseItemKind.PHOTO_ALBUM -> TODO()
|
BaseItemKind.PHOTO_ALBUM -> R.string.photos
|
||||||
BaseItemKind.PROGRAM -> TODO()
|
BaseItemKind.PROGRAM -> R.string.programs
|
||||||
BaseItemKind.RECORDING -> TODO()
|
BaseItemKind.RECORDING -> R.string.recordings
|
||||||
BaseItemKind.SEASON -> R.string.tv_seasons
|
BaseItemKind.SEASON -> R.string.tv_seasons
|
||||||
BaseItemKind.STUDIO -> R.string.studios
|
BaseItemKind.STUDIO -> R.string.studios
|
||||||
BaseItemKind.TRAILER -> R.string.trailers_title
|
BaseItemKind.TRAILER -> R.string.trailers_title
|
||||||
BaseItemKind.TV_CHANNEL -> R.string.channels
|
BaseItemKind.TV_CHANNEL -> R.string.channels
|
||||||
BaseItemKind.TV_PROGRAM -> TODO()
|
BaseItemKind.TV_PROGRAM -> R.string.programs
|
||||||
BaseItemKind.USER_ROOT_FOLDER -> TODO()
|
BaseItemKind.USER_ROOT_FOLDER -> R.string.folders_title
|
||||||
BaseItemKind.USER_VIEW -> TODO()
|
BaseItemKind.USER_VIEW -> R.string.user_views
|
||||||
BaseItemKind.YEAR -> TODO()
|
BaseItemKind.YEAR -> R.string.year
|
||||||
BaseItemKind.AGGREGATE_FOLDER -> TODO()
|
BaseItemKind.AGGREGATE_FOLDER -> R.string.folders_title
|
||||||
BaseItemKind.AUDIO_BOOK -> TODO()
|
BaseItemKind.AUDIO_BOOK -> R.string.audio_books
|
||||||
BaseItemKind.BASE_PLUGIN_FOLDER -> TODO()
|
BaseItemKind.BASE_PLUGIN_FOLDER -> R.string.folders_title
|
||||||
BaseItemKind.BOOK -> TODO()
|
BaseItemKind.BOOK -> R.string.books
|
||||||
BaseItemKind.CHANNEL_FOLDER_ITEM -> TODO()
|
BaseItemKind.CHANNEL_FOLDER_ITEM -> R.string.folders_title
|
||||||
BaseItemKind.COLLECTION_FOLDER -> TODO()
|
BaseItemKind.COLLECTION_FOLDER -> R.string.library
|
||||||
BaseItemKind.FOLDER -> TODO()
|
BaseItemKind.FOLDER -> R.string.folders_title
|
||||||
BaseItemKind.MANUAL_PLAYLISTS_FOLDER -> TODO()
|
BaseItemKind.MANUAL_PLAYLISTS_FOLDER -> R.string.playlists
|
||||||
BaseItemKind.LIVE_TV_PROGRAM -> TODO()
|
BaseItemKind.LIVE_TV_PROGRAM -> R.string.programs
|
||||||
BaseItemKind.PLAYLISTS_FOLDER -> TODO()
|
BaseItemKind.PLAYLISTS_FOLDER -> R.string.playlists
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
import com.github.damontecres.wholphin.util.DataLoadingState
|
import com.github.damontecres.wholphin.util.DataLoadingState
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
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.GetItemsRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.GetPersonsHandler
|
import com.github.damontecres.wholphin.util.GetPersonsHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
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.ImageType
|
||||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
import org.jellyfin.sdk.model.api.SortOrder
|
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.GetItemsRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
||||||
import org.jellyfin.sdk.model.serializer.toUUID
|
import org.jellyfin.sdk.model.serializer.toUUID
|
||||||
|
|
@ -373,6 +375,26 @@ class CollectionFolderViewModel
|
||||||
)
|
)
|
||||||
newPager
|
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(
|
private fun createGetItemsRequest(
|
||||||
|
|
|
||||||
|
|
@ -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.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
|
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
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.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.services.BackdropService
|
import com.github.damontecres.wholphin.services.BackdropService
|
||||||
import com.github.damontecres.wholphin.services.MusicService
|
import com.github.damontecres.wholphin.services.MusicService
|
||||||
|
|
@ -203,7 +204,7 @@ fun CollectionFolderMusic(
|
||||||
CollectionFolderFilter(
|
CollectionFolderFilter(
|
||||||
filter =
|
filter =
|
||||||
GetItemsFilter(
|
GetItemsFilter(
|
||||||
includeItemTypes = listOf(BaseItemKind.MUSIC_ARTIST),
|
override = GetItemsFilterOverride.ARTIST,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
showTitle = false,
|
showTitle = false,
|
||||||
|
|
|
||||||
|
|
@ -425,7 +425,7 @@ fun CollectionDetailsContent(
|
||||||
onClickPlay = onClickPlay,
|
onClickPlay = onClickPlay,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
onFocusPosition = { position ->
|
onFocusPosition = { position ->
|
||||||
Timber.v("onFocusPosition=%s", position)
|
// Timber.v("onFocusPosition=%s", position)
|
||||||
focusedItem =
|
focusedItem =
|
||||||
position.let {
|
position.let {
|
||||||
val key =
|
val key =
|
||||||
|
|
|
||||||
|
|
@ -51,18 +51,45 @@ fun CollectionRows(
|
||||||
if (row is HomeRowLoadingState.Success) {
|
if (row is HomeRowLoadingState.Success) {
|
||||||
// TODO not great to do this in the UI
|
// TODO not great to do this in the UI
|
||||||
val viewOptions =
|
val viewOptions =
|
||||||
if (type == BaseItemKind.EPISODE) {
|
when (type) {
|
||||||
HomeRowViewOptions(
|
BaseItemKind.EPISODE -> {
|
||||||
heightDp = Cards.HEIGHT_EPISODE,
|
HomeRowViewOptions(
|
||||||
episodeAspectRatio = AspectRatio.WIDE,
|
heightDp = Cards.HEIGHT_EPISODE,
|
||||||
showTitles = cardViewOptions.showTitles,
|
episodeAspectRatio = AspectRatio.WIDE,
|
||||||
useSeries = false,
|
showTitles = cardViewOptions.showTitles,
|
||||||
)
|
useSeries = false,
|
||||||
} else {
|
)
|
||||||
HomeRowViewOptions(
|
}
|
||||||
showTitles = cardViewOptions.showTitles,
|
|
||||||
)
|
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)
|
row.copy(viewOptions = viewOptions)
|
||||||
} else {
|
} else {
|
||||||
row
|
row
|
||||||
|
|
|
||||||
|
|
@ -489,6 +489,9 @@ class CollectionViewModel
|
||||||
BaseItemKind.EPISODE,
|
BaseItemKind.EPISODE,
|
||||||
BaseItemKind.VIDEO,
|
BaseItemKind.VIDEO,
|
||||||
BaseItemKind.BOX_SET,
|
BaseItemKind.BOX_SET,
|
||||||
|
BaseItemKind.MUSIC_ARTIST,
|
||||||
|
BaseItemKind.MUSIC_ALBUM,
|
||||||
|
BaseItemKind.MUSIC_VIDEO,
|
||||||
)
|
)
|
||||||
|
|
||||||
const val VIEW_OPTIONS_KEY = "CollectionViewOptions"
|
const val VIEW_OPTIONS_KEY = "CollectionViewOptions"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import kotlinx.serialization.modules.polymorphic
|
||||||
import kotlinx.serialization.modules.subclass
|
import kotlinx.serialization.modules.subclass
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
import org.jellyfin.sdk.api.client.Response
|
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.genresApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
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.api.client.extensions.userLibraryApi
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDtoQueryResult
|
import org.jellyfin.sdk.model.api.BaseItemDtoQueryResult
|
||||||
import org.jellyfin.sdk.model.api.GetProgramsDto
|
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.GetEpisodesRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetGenresRequest
|
import org.jellyfin.sdk.model.api.request.GetGenresRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||||
|
|
@ -367,6 +369,25 @@ object GetLiveTvChannelsRequestHandler : RequestHandler<GetLiveTvChannelsRequest
|
||||||
): Response<BaseItemDtoQueryResult> = api.liveTvApi.getLiveTvChannels(request)
|
): Response<BaseItemDtoQueryResult> = api.liveTvApi.getLiveTvChannels(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
object GetArtistsHandler : RequestHandler<GetArtistsRequest> {
|
||||||
|
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<BaseItemDtoQueryResult> = api.artistsApi.getArtists((request))
|
||||||
|
}
|
||||||
|
|
||||||
val requestSerializersModule =
|
val requestSerializersModule =
|
||||||
SerializersModule {
|
SerializersModule {
|
||||||
polymorphic(Any::class) {
|
polymorphic(Any::class) {
|
||||||
|
|
@ -382,5 +403,6 @@ val requestSerializersModule =
|
||||||
subclass(GetStudiosRequest::class)
|
subclass(GetStudiosRequest::class)
|
||||||
subclass(GetRecordingsRequest::class)
|
subclass(GetRecordingsRequest::class)
|
||||||
subclass(GetLiveTvChannelsRequest::class)
|
subclass(GetLiveTvChannelsRequest::class)
|
||||||
|
subclass(GetArtistsHandler::class)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -874,4 +874,10 @@
|
||||||
<string name="any_language">Any language</string>
|
<string name="any_language">Any language</string>
|
||||||
<string name="hash_matches">Hash matches</string>
|
<string name="hash_matches">Hash matches</string>
|
||||||
<string name="subtitles_hearing_impaired">SDH</string>
|
<string name="subtitles_hearing_impaired">SDH</string>
|
||||||
|
<string name="programs">TV programs</string>
|
||||||
|
<string name="recordings">Recordings</string>
|
||||||
|
<string name="folders_title">Folders</string>
|
||||||
|
<string name="user_views">User views</string>
|
||||||
|
<string name="books">Books</string>
|
||||||
|
<string name="audio_books">Audio books</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue