Merge Wholphin v1.1.0 while preserving Wolfskin features #27
6 changed files with 251 additions and 49 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.GetAlbumArtistsRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetArtistsRequest
|
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
|
||||||
|
|
@ -155,6 +156,22 @@ data class GetItemsFilter(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun applyTo(req: GetAlbumArtistsRequest) =
|
||||||
|
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
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
import com.github.damontecres.wholphin.util.BlockingList
|
import com.github.damontecres.wholphin.util.BlockingList
|
||||||
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.GetAlbumArtistsHandler
|
||||||
import com.github.damontecres.wholphin.util.GetArtistsHandler
|
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
|
||||||
|
|
@ -100,6 +101,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.GetAlbumArtistsRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetArtistsRequest
|
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
|
||||||
|
|
@ -151,6 +153,9 @@ class CollectionFolderViewModel
|
||||||
private val _state = MutableStateFlow(CollectionFolderState(viewOptions = defaultViewOptions))
|
private val _state = MutableStateFlow(CollectionFolderState(viewOptions = defaultViewOptions))
|
||||||
val state: StateFlow<CollectionFolderState> = _state
|
val state: StateFlow<CollectionFolderState> = _state
|
||||||
|
|
||||||
|
private val artistSource: ArtistSource
|
||||||
|
get() = state.value.viewOptions.artistSource
|
||||||
|
|
||||||
var position: Int
|
var position: Int
|
||||||
get() = savedStateHandle.get<Int>("position") ?: 0
|
get() = savedStateHandle.get<Int>("position") ?: 0
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
@ -291,7 +296,17 @@ class CollectionFolderViewModel
|
||||||
|
|
||||||
override fun saveViewOptions(viewOptions: ViewOptions) {
|
override fun saveViewOptions(viewOptions: ViewOptions) {
|
||||||
position = 0
|
position = 0
|
||||||
|
val artistSourceChanged = viewOptions.artistSource != artistSource
|
||||||
_state.update { it.copy(viewOptions = viewOptions) }
|
_state.update { it.copy(viewOptions = viewOptions) }
|
||||||
|
if (artistSourceChanged && state.value.filter.override == GetItemsFilterOverride.ARTIST) {
|
||||||
|
loadResults(
|
||||||
|
true,
|
||||||
|
state.value.sortAndDirection,
|
||||||
|
recursive,
|
||||||
|
state.value.filter,
|
||||||
|
useSeriesForPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
|
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
|
||||||
saveLibraryDisplayInfo(viewOptions = viewOptions)
|
saveLibraryDisplayInfo(viewOptions = viewOptions)
|
||||||
if (!viewOptions.showBackdrop) {
|
if (!viewOptions.showBackdrop) {
|
||||||
|
|
@ -441,6 +456,8 @@ class CollectionFolderViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
GetItemsFilterOverride.ARTIST -> {
|
GetItemsFilterOverride.ARTIST -> {
|
||||||
|
when (artistSource) {
|
||||||
|
ArtistSource.ARTIST -> {
|
||||||
ApiRequestPager(
|
ApiRequestPager(
|
||||||
api,
|
api,
|
||||||
createGetArtistsRequest(filter),
|
createGetArtistsRequest(filter),
|
||||||
|
|
@ -449,6 +466,18 @@ class CollectionFolderViewModel
|
||||||
useSeriesForPrimary = useSeriesForPrimary,
|
useSeriesForPrimary = useSeriesForPrimary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArtistSource.ALBUM_ARTIST -> {
|
||||||
|
ApiRequestPager(
|
||||||
|
api,
|
||||||
|
createGetAlbumArtistsRequest(filter),
|
||||||
|
GetAlbumArtistsHandler,
|
||||||
|
viewModelScope,
|
||||||
|
useSeriesForPrimary = useSeriesForPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -466,6 +495,17 @@ class CollectionFolderViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createGetAlbumArtistsRequest(filter: GetItemsFilter): GetAlbumArtistsRequest {
|
||||||
|
val item = state.value.item.successValue
|
||||||
|
return filter.applyTo(
|
||||||
|
GetAlbumArtistsRequest(
|
||||||
|
parentId = item?.id,
|
||||||
|
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
|
||||||
|
fields = SlimItemFields,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createGetItemsRequest(
|
private fun createGetItemsRequest(
|
||||||
sortAndDirection: SortAndDirection,
|
sortAndDirection: SortAndDirection,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
|
|
@ -537,6 +577,8 @@ class CollectionFolderViewModel
|
||||||
val filter = state.value.filter
|
val filter = state.value.filter
|
||||||
when (filter.override) {
|
when (filter.override) {
|
||||||
GetItemsFilterOverride.ARTIST -> {
|
GetItemsFilterOverride.ARTIST -> {
|
||||||
|
when (artistSource) {
|
||||||
|
ArtistSource.ARTIST -> {
|
||||||
GetArtistsHandler.countMatching(
|
GetArtistsHandler.countMatching(
|
||||||
api = api,
|
api = api,
|
||||||
request =
|
request =
|
||||||
|
|
@ -551,6 +593,23 @@ class CollectionFolderViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArtistSource.ALBUM_ARTIST -> {
|
||||||
|
GetAlbumArtistsHandler.countMatching(
|
||||||
|
api = api,
|
||||||
|
request =
|
||||||
|
createGetAlbumArtistsRequest(filter).copy(
|
||||||
|
enableImageTypes = null,
|
||||||
|
fields = null,
|
||||||
|
nameLessThan = letter.toString(),
|
||||||
|
limit = 0,
|
||||||
|
enableTotalRecordCount = true,
|
||||||
|
enableUserData = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetPersonsRequest has no nameLessThan or startIndex, so /Persons cannot be
|
// GetPersonsRequest has no nameLessThan or startIndex, so /Persons cannot be
|
||||||
// counted up to a letter at all
|
// counted up to a letter at all
|
||||||
GetItemsFilterOverride.PERSON -> {
|
GetItemsFilterOverride.PERSON -> {
|
||||||
|
|
@ -1127,6 +1186,7 @@ fun CollectionFolderViewContent(
|
||||||
ViewOptionsDialog(
|
ViewOptionsDialog(
|
||||||
viewOptions = state.viewOptions,
|
viewOptions = state.viewOptions,
|
||||||
defaultViewOptions = defaultViewOptions,
|
defaultViewOptions = defaultViewOptions,
|
||||||
|
showArtistSource = state.filter.override == GetItemsFilterOverride.ARTIST,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showViewOptions = false
|
showViewOptions = false
|
||||||
viewActions.saveViewOptions(state.viewOptions)
|
viewActions.saveViewOptions(state.viewOptions)
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ fun ViewOptionsDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onViewOptionsChange: (ViewOptions) -> Unit,
|
onViewOptionsChange: (ViewOptions) -> Unit,
|
||||||
defaultViewOptions: ViewOptions = ViewOptions(),
|
defaultViewOptions: ViewOptions = ViewOptions(),
|
||||||
|
showArtistSource: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
|
|
@ -72,11 +73,11 @@ fun ViewOptionsDialog(
|
||||||
window.setDimAmount(0f)
|
window.setDimAmount(0f)
|
||||||
}
|
}
|
||||||
val options =
|
val options =
|
||||||
remember(viewOptions.type) {
|
remember(viewOptions.type, showArtistSource) {
|
||||||
when (viewOptions.type) {
|
when (viewOptions.type) {
|
||||||
ViewOptionsType.GRID -> ViewOptions.GRID_OPTIONS
|
ViewOptionsType.GRID -> ViewOptions.buildGridOptions(showArtistSource)
|
||||||
ViewOptionsType.LIST -> ViewOptions.LIST_OPTIONS
|
ViewOptionsType.LIST -> ViewOptions.buildListOptions(showArtistSource)
|
||||||
ViewOptionsType.DENSE_LIST -> ViewOptions.LIST_OPTIONS
|
ViewOptionsType.DENSE_LIST -> ViewOptions.buildListOptions(showArtistSource)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -130,6 +131,12 @@ fun ViewOptionsDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Which Jellyfin artists list backs the Music > Artists tab */
|
||||||
|
enum class ArtistSource {
|
||||||
|
ARTIST,
|
||||||
|
ALBUM_ARTIST,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the customizable view changes from the user
|
* Stores the customizable view changes from the user
|
||||||
*
|
*
|
||||||
|
|
@ -146,6 +153,7 @@ data class ViewOptions(
|
||||||
val imageType: ViewOptionImageType = ViewOptionImageType.PRIMARY,
|
val imageType: ViewOptionImageType = ViewOptionImageType.PRIMARY,
|
||||||
val showTitles: Boolean = true,
|
val showTitles: Boolean = true,
|
||||||
val type: ViewOptionsType = ViewOptionsType.GRID,
|
val type: ViewOptionsType = ViewOptionsType.GRID,
|
||||||
|
val extras: ViewOptionsExtras = ViewOptionsExtras.None,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val ViewOptionsColumns =
|
val ViewOptionsColumns =
|
||||||
|
|
@ -250,12 +258,34 @@ data class ViewOptions(
|
||||||
valueToIndex = { it.ordinal },
|
valueToIndex = { it.ordinal },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val ViewOptionsArtistSource =
|
||||||
|
AppChoicePreference<ViewOptions, ArtistSource>(
|
||||||
|
title = R.string.artist_source,
|
||||||
|
defaultValue = ArtistSource.ARTIST,
|
||||||
|
displayValues = R.array.artist_sources,
|
||||||
|
getter = { it.artistSource },
|
||||||
|
setter = { viewOptions, value ->
|
||||||
|
viewOptions.copy(extras = ViewOptionsExtras.MusicLibrary(value))
|
||||||
|
},
|
||||||
|
indexToValue = { ArtistSource.entries[it] },
|
||||||
|
valueToIndex = { it.ordinal },
|
||||||
|
)
|
||||||
|
|
||||||
val ViewOptionsReset =
|
val ViewOptionsReset =
|
||||||
AppClickablePreference<ViewOptions>(
|
AppClickablePreference<ViewOptions>(
|
||||||
title = R.string.reset,
|
title = R.string.reset,
|
||||||
)
|
)
|
||||||
|
|
||||||
val GRID_OPTIONS =
|
/**
|
||||||
|
* Get the options for a grid [ViewOptions]
|
||||||
|
*/
|
||||||
|
fun buildGridOptions(showArtistSource: Boolean) =
|
||||||
|
buildList {
|
||||||
|
if (showArtistSource) add(ViewOptionsArtistSource)
|
||||||
|
addAll(GRID_OPTIONS)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val GRID_OPTIONS =
|
||||||
listOf(
|
listOf(
|
||||||
ViewOptionsTypePref,
|
ViewOptionsTypePref,
|
||||||
ViewOptionsImageType,
|
ViewOptionsImageType,
|
||||||
|
|
@ -269,7 +299,16 @@ data class ViewOptions(
|
||||||
ViewOptionsReset,
|
ViewOptionsReset,
|
||||||
)
|
)
|
||||||
|
|
||||||
val LIST_OPTIONS =
|
/**
|
||||||
|
* Get the options for a list [ViewOptions]
|
||||||
|
*/
|
||||||
|
fun buildListOptions(showArtistSource: Boolean) =
|
||||||
|
buildList {
|
||||||
|
if (showArtistSource) add(ViewOptionsArtistSource)
|
||||||
|
addAll(LIST_OPTIONS)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val LIST_OPTIONS =
|
||||||
listOf(
|
listOf(
|
||||||
ViewOptionsTypePref,
|
ViewOptionsTypePref,
|
||||||
ViewOptionsDetailHeader,
|
ViewOptionsDetailHeader,
|
||||||
|
|
@ -338,3 +377,22 @@ val BaseItemKind.defaultViewOptions: ViewOptions
|
||||||
|
|
||||||
else -> ViewOptionsPoster
|
else -> ViewOptionsPoster
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extras [ViewOptions] info that is context or library specific
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
sealed interface ViewOptionsExtras {
|
||||||
|
@Serializable
|
||||||
|
data object None : ViewOptionsExtras
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MusicLibrary(
|
||||||
|
val artistSource: ArtistSource = ArtistSource.ARTIST,
|
||||||
|
) : ViewOptionsExtras
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [ArtistSource] if the [ViewOptions.extras] is a [ViewOptionsExtras.MusicLibrary]
|
||||||
|
*/
|
||||||
|
val ViewOptions.artistSource: ArtistSource get() = (extras as? ViewOptionsExtras.MusicLibrary)?.artistSource ?: ArtistSource.ARTIST
|
||||||
|
|
|
||||||
|
|
@ -22,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.GetAlbumArtistsRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetArtistsRequest
|
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
|
||||||
|
|
@ -401,6 +402,25 @@ object GetArtistsHandler : RequestHandler<GetArtistsRequest> {
|
||||||
): Response<BaseItemDtoQueryResult> = api.artistsApi.getArtists((request))
|
): Response<BaseItemDtoQueryResult> = api.artistsApi.getArtists((request))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
object GetAlbumArtistsHandler : RequestHandler<GetAlbumArtistsRequest> {
|
||||||
|
override fun prepare(
|
||||||
|
request: GetAlbumArtistsRequest,
|
||||||
|
startIndex: Int,
|
||||||
|
limit: Int,
|
||||||
|
enableTotalRecordCount: Boolean,
|
||||||
|
): GetAlbumArtistsRequest =
|
||||||
|
request.copy(
|
||||||
|
startIndex = startIndex,
|
||||||
|
limit = limit,
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun execute(
|
||||||
|
api: ApiClient,
|
||||||
|
request: GetAlbumArtistsRequest,
|
||||||
|
): Response<BaseItemDtoQueryResult> = api.artistsApi.getAlbumArtists(request)
|
||||||
|
}
|
||||||
|
|
||||||
val requestSerializersModule =
|
val requestSerializersModule =
|
||||||
SerializersModule {
|
SerializersModule {
|
||||||
polymorphic(Any::class) {
|
polymorphic(Any::class) {
|
||||||
|
|
@ -417,5 +437,6 @@ val requestSerializersModule =
|
||||||
subclass(GetRecordingsRequest::class)
|
subclass(GetRecordingsRequest::class)
|
||||||
subclass(GetLiveTvChannelsRequest::class)
|
subclass(GetLiveTvChannelsRequest::class)
|
||||||
subclass(GetArtistsHandler::class)
|
subclass(GetArtistsHandler::class)
|
||||||
|
subclass(GetAlbumArtistsHandler::class)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -806,6 +806,11 @@
|
||||||
<string name="creator">Creator</string>
|
<string name="creator">Creator</string>
|
||||||
<string name="artist">Artist</string>
|
<string name="artist">Artist</string>
|
||||||
<string name="album_artist">Album artist</string>
|
<string name="album_artist">Album artist</string>
|
||||||
|
<string name="artist_source">Artist source</string>
|
||||||
|
<string-array name="artist_sources">
|
||||||
|
<item>@string/artist</item>
|
||||||
|
<item>@string/album_artist</item>
|
||||||
|
</string-array>
|
||||||
<string name="album">Album</string>
|
<string name="album">Album</string>
|
||||||
<string name="popular_songs">Popular songs</string>
|
<string name="popular_songs">Popular songs</string>
|
||||||
<string name="play_next">Play next</string>
|
<string name="play_next">Play next</string>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import com.github.damontecres.wholphin.services.DeletedItem
|
||||||
import com.github.damontecres.wholphin.services.MediaManagementService
|
import com.github.damontecres.wholphin.services.MediaManagementService
|
||||||
import com.github.damontecres.wholphin.ui.successQueryResult
|
import com.github.damontecres.wholphin.ui.successQueryResult
|
||||||
import com.github.damontecres.wholphin.ui.successResponse
|
import com.github.damontecres.wholphin.ui.successResponse
|
||||||
|
import com.github.damontecres.wholphin.util.GetAlbumArtistsHandler
|
||||||
import com.github.damontecres.wholphin.util.GetArtistsHandler
|
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
|
||||||
|
|
@ -34,6 +35,7 @@ import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
import org.jellyfin.sdk.model.api.ItemFields
|
import org.jellyfin.sdk.model.api.ItemFields
|
||||||
|
import org.jellyfin.sdk.model.api.request.GetAlbumArtistsRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetArtistsRequest
|
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.junit.After
|
import org.junit.After
|
||||||
|
|
@ -57,6 +59,7 @@ class CollectionFolderViewModelTest {
|
||||||
private val mockUserLibraryApi = mockk<UserLibraryApi>()
|
private val mockUserLibraryApi = mockk<UserLibraryApi>()
|
||||||
|
|
||||||
private val artistsRequest = slot<GetArtistsRequest>()
|
private val artistsRequest = slot<GetArtistsRequest>()
|
||||||
|
private val albumArtistsRequest = slot<GetAlbumArtistsRequest>()
|
||||||
private val itemsRequest = slot<GetItemsRequest>()
|
private val itemsRequest = slot<GetItemsRequest>()
|
||||||
|
|
||||||
private val libraryId = UUID.randomUUID()
|
private val libraryId = UUID.randomUUID()
|
||||||
|
|
@ -83,11 +86,14 @@ class CollectionFolderViewModelTest {
|
||||||
// against the relaxed ApiClient
|
// against the relaxed ApiClient
|
||||||
mockkObject(GetItemsRequestHandler)
|
mockkObject(GetItemsRequestHandler)
|
||||||
mockkObject(GetArtistsHandler)
|
mockkObject(GetArtistsHandler)
|
||||||
|
mockkObject(GetAlbumArtistsHandler)
|
||||||
mockkObject(GetPersonsHandler)
|
mockkObject(GetPersonsHandler)
|
||||||
coEvery { GetItemsRequestHandler.execute(mockApi, capture(itemsRequest)) } returns
|
coEvery { GetItemsRequestHandler.execute(mockApi, capture(itemsRequest)) } returns
|
||||||
successQueryResult(totalRecordCount = LIBRARY_ITEM_COUNT)
|
successQueryResult(totalRecordCount = LIBRARY_ITEM_COUNT)
|
||||||
coEvery { GetArtistsHandler.execute(mockApi, capture(artistsRequest)) } returns
|
coEvery { GetArtistsHandler.execute(mockApi, capture(artistsRequest)) } returns
|
||||||
successQueryResult(totalRecordCount = ARTIST_COUNT)
|
successQueryResult(totalRecordCount = ARTIST_COUNT)
|
||||||
|
coEvery { GetAlbumArtistsHandler.execute(mockApi, capture(albumArtistsRequest)) } returns
|
||||||
|
successQueryResult(totalRecordCount = ALBUM_ARTIST_COUNT)
|
||||||
coEvery { GetPersonsHandler.execute(mockApi, any()) } returns
|
coEvery { GetPersonsHandler.execute(mockApi, any()) } returns
|
||||||
successQueryResult(totalRecordCount = PERSON_COUNT)
|
successQueryResult(totalRecordCount = PERSON_COUNT)
|
||||||
}
|
}
|
||||||
|
|
@ -97,11 +103,14 @@ class CollectionFolderViewModelTest {
|
||||||
WholphinDispatchers.reset()
|
WholphinDispatchers.reset()
|
||||||
unmockkObject(GetItemsRequestHandler)
|
unmockkObject(GetItemsRequestHandler)
|
||||||
unmockkObject(GetArtistsHandler)
|
unmockkObject(GetArtistsHandler)
|
||||||
|
unmockkObject(GetAlbumArtistsHandler)
|
||||||
unmockkObject(GetPersonsHandler)
|
unmockkObject(GetPersonsHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createViewModel(filter: GetItemsFilter) =
|
private fun createViewModel(
|
||||||
CollectionFolderViewModel(
|
filter: GetItemsFilter,
|
||||||
|
viewOptions: ViewOptions = ViewOptionsSquare,
|
||||||
|
) = CollectionFolderViewModel(
|
||||||
savedStateHandle = SavedStateHandle(),
|
savedStateHandle = SavedStateHandle(),
|
||||||
api = mockApi,
|
api = mockApi,
|
||||||
context = mockk(relaxed = true),
|
context = mockk(relaxed = true),
|
||||||
|
|
@ -122,7 +131,7 @@ class CollectionFolderViewModelTest {
|
||||||
recursive = true,
|
recursive = true,
|
||||||
collectionFilter = CollectionFolderFilter(filter = filter),
|
collectionFilter = CollectionFolderFilter(filter = filter),
|
||||||
useSeriesForPrimary = false,
|
useSeriesForPrimary = false,
|
||||||
defaultViewOptions = ViewOptionsSquare,
|
defaultViewOptions = viewOptions,
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Counting via /Items would return every song and album, overshooting the artist grid */
|
/** Counting via /Items would return every song and album, overshooting the artist grid */
|
||||||
|
|
@ -143,6 +152,37 @@ class CollectionFolderViewModelTest {
|
||||||
coVerify(exactly = 0) { GetItemsRequestHandler.execute(any(), any<GetItemsRequest>()) }
|
coVerify(exactly = 0) { GetItemsRequestHandler.execute(any(), any<GetItemsRequest>()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `positionOfLetter counts album artists for the ALBUM_ARTIST source`() =
|
||||||
|
runTest(testDispatcher) {
|
||||||
|
val viewModel =
|
||||||
|
createViewModel(
|
||||||
|
GetItemsFilter(override = GetItemsFilterOverride.ARTIST),
|
||||||
|
ViewOptionsSquare.copy(extras = ViewOptionsExtras.MusicLibrary(ArtistSource.ALBUM_ARTIST)),
|
||||||
|
)
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
val position = viewModel.positionOfLetter('K')
|
||||||
|
|
||||||
|
assertEquals(ALBUM_ARTIST_COUNT, position)
|
||||||
|
assertEquals("K", albumArtistsRequest.captured.nameLessThan)
|
||||||
|
assertEquals(libraryId, albumArtistsRequest.captured.parentId)
|
||||||
|
coVerify(exactly = 0) { GetArtistsHandler.execute(any(), any<GetArtistsRequest>()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `changing the artist source reloads from the album artists endpoint`() =
|
||||||
|
runTest(testDispatcher) {
|
||||||
|
val viewModel = createViewModel(GetItemsFilter(override = GetItemsFilterOverride.ARTIST))
|
||||||
|
advanceUntilIdle()
|
||||||
|
coVerify(exactly = 0) { GetAlbumArtistsHandler.execute(any(), any<GetAlbumArtistsRequest>()) }
|
||||||
|
|
||||||
|
viewModel.saveViewOptions(ViewOptionsSquare.copy(extras = ViewOptionsExtras.MusicLibrary(ArtistSource.ALBUM_ARTIST)))
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
coVerify { GetAlbumArtistsHandler.execute(any(), any<GetAlbumArtistsRequest>()) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Without SortName, the alphabet highlight falls back to Name and highlights "T" for artists
|
* Without SortName, the alphabet highlight falls back to Name and highlights "T" for artists
|
||||||
* like "The Beatles" even though the grid sorts them under "B".
|
* like "The Beatles" even though the grid sorts them under "B".
|
||||||
|
|
@ -197,6 +237,7 @@ class CollectionFolderViewModelTest {
|
||||||
companion object {
|
companion object {
|
||||||
private const val LIBRARY_ITEM_COUNT = 7315
|
private const val LIBRARY_ITEM_COUNT = 7315
|
||||||
private const val ARTIST_COUNT = 730
|
private const val ARTIST_COUNT = 730
|
||||||
|
private const val ALBUM_ARTIST_COUNT = 215
|
||||||
private const val PERSON_COUNT = 12
|
private const val PERSON_COUNT = 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue