diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/HomeRowConfig.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/HomeRowConfig.kt index 27be82ca..cc60e436 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/HomeRowConfig.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/HomeRowConfig.kt @@ -12,9 +12,11 @@ import com.github.damontecres.wholphin.ui.AspectRatio import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.components.ViewOptionImageType import com.github.damontecres.wholphin.ui.components.ViewOptions +import com.github.damontecres.wholphin.ui.data.SortAndDirection import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers +import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.serializer.UUIDSerializer import java.util.UUID @@ -25,6 +27,9 @@ sealed class HomeRowConfig { abstract fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig + /** + * Continue watching media that the user has started but not finished + */ @Serializable @SerialName("ContinueWatching") data class ContinueWatching( @@ -34,6 +39,9 @@ sealed class HomeRowConfig { override fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig = this.copy(viewOptions = viewOptions) } + /** + * Next up row for next episodes in a series the user has started + */ @Serializable @SerialName("NextUp") data class NextUp( @@ -43,6 +51,9 @@ sealed class HomeRowConfig { override fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig = this.copy(viewOptions = viewOptions) } + /** + * Combined [ContinueWatching] and [NextUp] + */ @Serializable @SerialName("ContinueWatchingCombined") data class ContinueWatchingCombined( @@ -52,6 +63,9 @@ sealed class HomeRowConfig { override fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig = this.copy(viewOptions = viewOptions) } + /** + * Media recently added to a library + */ @Serializable @SerialName("RecentlyAdded") data class RecentlyAdded( @@ -62,6 +76,9 @@ sealed class HomeRowConfig { override fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig = this.copy(viewOptions = viewOptions) } + /** + * Media recently released (premiere date) in a library + */ @Serializable @SerialName("RecentlyReleased") data class RecentlyReleased( @@ -72,33 +89,65 @@ sealed class HomeRowConfig { override fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig = this.copy(viewOptions = viewOptions) } + /** + * Row of a genres in a library + */ @Serializable @SerialName("Genres") data class Genres( override val id: Int, val parentId: UUID, + override val viewOptions: HomeRowViewOptions = + HomeRowViewOptions( + heightDp = (Cards.HEIGHT_2X3_DP * .75f).toInt(), + aspectRatio = AspectRatio.WIDE, + ), + ) : HomeRowConfig() { + override fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig = this.copy(viewOptions = viewOptions) + } + + /** + * Fetch by parent ID such as a library, collection, or playlist with optional simple sorting + */ + @Serializable + @SerialName("ByParent") + data class ByParent( + override val id: Int, + val parentId: UUID, + val recursive: Boolean, + val sort: SortAndDirection? = null, override val viewOptions: HomeRowViewOptions, ) : HomeRowConfig() { override fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig = this.copy(viewOptions = viewOptions) } + /** + * An arbitrary [GetItemsRequest] allowing to query for anything + */ @Serializable - @SerialName("Collection") - data class Collection( + @SerialName("GetItems") + data class GetItems( override val id: Int, - val collectionId: UUID, + val name: String?, + val getItems: GetItemsRequest, override val viewOptions: HomeRowViewOptions, ) : HomeRowConfig() { override fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig = this.copy(viewOptions = viewOptions) } } -@Serializable data class HomeRowConfigDisplay( val title: String, val config: HomeRowConfig, ) +@Serializable +@SerialName("HomePageConfiguration") +data class HomePageConfiguration( + val version: Int = 1, + val rows: List, +) + @Serializable data class HomeRowViewOptions( val heightDp: Int = Cards.HEIGHT_2X3_DP, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomePageSettings.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomePageSettings.kt index a94d56ea..4fae6901 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomePageSettings.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomePageSettings.kt @@ -36,8 +36,6 @@ import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.LatestNextUpService import com.github.damontecres.wholphin.services.UserPreferencesService -import com.github.damontecres.wholphin.ui.AspectRatio -import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.DefaultItemFields import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.components.getGenreImageMap @@ -155,10 +153,20 @@ class HomePageSettingsViewModel listOf( HomeRowConfigDisplay( "Collection", - HomeRowConfig.Collection( - 100, - "34ab6fd1f51c41bb014981f2e334f465".toUUID(), - HomeRowViewOptions(), + HomeRowConfig.ByParent( + id = 100, + parentId = "34ab6fd1f51c41bb014981f2e334f465".toUUID(), + recursive = true, + viewOptions = HomeRowViewOptions(), + ), + ), + HomeRowConfigDisplay( + "Playlist", + HomeRowConfig.ByParent( + id = 101, + parentId = "f94be36e9836127a0bccfc7843b19e5b".toUUID(), + recursive = true, + viewOptions = HomeRowViewOptions(), ), ), ) @@ -363,15 +371,59 @@ class HomePageSettingsViewModel } } - is HomeRowConfig.Collection -> { - val collection by api.userLibraryApi.getItem(itemId = row.collectionId) + is HomeRowConfig.ByParent -> { val request = GetItemsRequest( - parentId = row.collectionId, + userId = userDto.id, + parentId = row.parentId, + recursive = row.recursive, + sortBy = row.sort?.let { listOf(it.sort) }, + sortOrder = row.sort?.let { listOf(it.direction) }, limit = limit, fields = DefaultItemFields, - recursive = true, ) + val name = + api.userLibraryApi + .getItem(itemId = row.parentId) + .content.name + GetItemsRequestHandler + .execute(api, request) + .content.items + .map { BaseItem(it, true) } + .let { + listOf( + Success( + name ?: context.getString(R.string.collection), + it, + row.viewOptions, + ), + ) + } + } + + is HomeRowConfig.GetItems -> { + val request = + row.getItems.let { + if (it.limit == null) { + it.copy( + userId = userDto.id, + limit = limit, + ) + } else { + it.copy( + userId = userDto.id, + ) + } + } + val name = + if (row.name == null && request.parentId != null) { + // If a name was not provided, use the parent's name if available + api.userLibraryApi + .getItem(itemId = request.parentId!!) + .content.name + } else { + row.name + } GetItemsRequestHandler .execute(api, request) .content.items @@ -379,7 +431,7 @@ class HomePageSettingsViewModel .let { listOf( Success( - collection.name ?: "", + name ?: context.getString(R.string.collection), it, row.viewOptions, ), @@ -524,10 +576,6 @@ class HomePageSettingsViewModel HomeRowConfig.Genres( id, library.itemId, - HomeRowViewOptions( - heightDp = (Cards.HEIGHT_2X3_DP * .75f).toInt(), - aspectRatio = AspectRatio.WIDE, - ), ), ) } diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/TestHomeRowSamples.kt b/app/src/test/java/com/github/damontecres/wholphin/test/TestHomeRowSamples.kt index ec3bf4eb..5ab69cad 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/test/TestHomeRowSamples.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/test/TestHomeRowSamples.kt @@ -1,13 +1,17 @@ package com.github.damontecres.wholphin.test import com.github.damontecres.wholphin.data.model.HomeRowConfig -import com.github.damontecres.wholphin.data.model.HomeRowConfigDisplay import com.github.damontecres.wholphin.data.model.HomeRowViewOptions import com.github.damontecres.wholphin.preferences.PrefContentScale import com.github.damontecres.wholphin.ui.AspectRatio import com.github.damontecres.wholphin.ui.components.ViewOptionImageType +import com.github.damontecres.wholphin.ui.data.SortAndDirection import kotlinx.serialization.json.Json import org.jellyfin.sdk.model.UUID +import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.ItemSortBy +import org.jellyfin.sdk.model.api.SortOrder +import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.junit.Assert import org.junit.Test import kotlin.reflect.KClass @@ -16,65 +20,62 @@ class TestHomeRowSamples { companion object { val SAMPLES = listOf( - HomeRowConfigDisplay( - title = "Recently added", - config = - HomeRowConfig.RecentlyAdded( - id = 0, + HomeRowConfig.RecentlyAdded( + id = 0, + parentId = UUID.randomUUID(), + viewOptions = + HomeRowViewOptions( + heightDp = 100, + spacing = 8, + contentScale = PrefContentScale.CROP, + aspectRatio = AspectRatio.FOUR_THREE, + imageType = ViewOptionImageType.THUMB, + showTitles = false, + useSeries = false, + ), + ), + HomeRowConfig.RecentlyReleased( + id = 0, + parentId = UUID.randomUUID(), + viewOptions = HomeRowViewOptions(), + ), + HomeRowConfig.Genres( + id = 0, + parentId = UUID.randomUUID(), + viewOptions = HomeRowViewOptions(), + ), + HomeRowConfig.ContinueWatching( + id = 0, + viewOptions = HomeRowViewOptions(), + ), + HomeRowConfig.NextUp( + id = 0, + viewOptions = HomeRowViewOptions(), + ), + HomeRowConfig.ContinueWatchingCombined( + id = 0, + viewOptions = HomeRowViewOptions(), + ), + HomeRowConfig.ByParent( + id = 0, + parentId = UUID.randomUUID(), + recursive = true, + sort = SortAndDirection(ItemSortBy.CRITIC_RATING, SortOrder.ASCENDING), + viewOptions = HomeRowViewOptions(), + ), + HomeRowConfig.GetItems( + id = 0, + name = "Episodes by date created", + getItems = + GetItemsRequest( parentId = UUID.randomUUID(), - viewOptions = - HomeRowViewOptions( - heightDp = 100, - spacing = 8, - contentScale = PrefContentScale.CROP, - aspectRatio = AspectRatio.FOUR_THREE, - imageType = ViewOptionImageType.THUMB, - showTitles = false, - useSeries = false, - ), - ), - ), - HomeRowConfigDisplay( - title = "Recently released", - config = - HomeRowConfig.RecentlyReleased( - id = 0, - parentId = UUID.randomUUID(), - viewOptions = HomeRowViewOptions(), - ), - ), - HomeRowConfigDisplay( - title = "Genres", - config = - HomeRowConfig.Genres( - id = 0, - parentId = UUID.randomUUID(), - viewOptions = HomeRowViewOptions(), - ), - ), - HomeRowConfigDisplay( - title = "Continue watching", - config = - HomeRowConfig.ContinueWatching( - id = 0, - viewOptions = HomeRowViewOptions(), - ), - ), - HomeRowConfigDisplay( - title = "Next up", - config = - HomeRowConfig.NextUp( - id = 0, - viewOptions = HomeRowViewOptions(), - ), - ), - HomeRowConfigDisplay( - title = "Combined", - config = - HomeRowConfig.ContinueWatchingCombined( - id = 0, - viewOptions = HomeRowViewOptions(), + recursive = true, + isFavorite = true, + includeItemTypes = listOf(BaseItemKind.EPISODE), + sortBy = listOf(ItemSortBy.DATE_CREATED), + sortOrder = listOf(SortOrder.DESCENDING), ), + viewOptions = HomeRowViewOptions(), ), ) } @@ -84,13 +85,15 @@ class TestHomeRowSamples { // This ensures there is a sample for each possible HomeRowConfig type val foundTypes = mutableSetOf>() SAMPLES.forEach { - when (it.config) { - is HomeRowConfig.ContinueWatching -> foundTypes.add(HomeRowConfig.ContinueWatching::class) - is HomeRowConfig.ContinueWatchingCombined -> foundTypes.add(HomeRowConfig.ContinueWatchingCombined::class) - is HomeRowConfig.Genres -> foundTypes.add(HomeRowConfig.Genres::class) - is HomeRowConfig.NextUp -> foundTypes.add(HomeRowConfig.NextUp::class) - is HomeRowConfig.RecentlyAdded -> foundTypes.add(HomeRowConfig.RecentlyAdded::class) - is HomeRowConfig.RecentlyReleased -> foundTypes.add(HomeRowConfig.RecentlyReleased::class) + when (it) { + is HomeRowConfig.ContinueWatching -> foundTypes.add(it::class) + is HomeRowConfig.ContinueWatchingCombined -> foundTypes.add(it::class) + is HomeRowConfig.Genres -> foundTypes.add(it::class) + is HomeRowConfig.NextUp -> foundTypes.add(it::class) + is HomeRowConfig.RecentlyAdded -> foundTypes.add(it::class) + is HomeRowConfig.RecentlyReleased -> foundTypes.add(it::class) + is HomeRowConfig.ByParent -> foundTypes.add(it::class) + is HomeRowConfig.GetItems -> foundTypes.add(it::class) } } Assert.assertEquals(HomeRowConfig::class.sealedSubclasses.size, foundTypes.size) @@ -107,6 +110,6 @@ class TestHomeRowSamples { } val string = json.encodeToString(SAMPLES) println(string) - json.decodeFromString>(string) + json.decodeFromString>(string) } }