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 cef6561b..8e98d947 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 @@ -22,7 +22,6 @@ import java.util.UUID @Serializable sealed interface HomeRowConfig { - val id: Int val viewOptions: HomeRowViewOptions fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig @@ -33,7 +32,6 @@ sealed interface HomeRowConfig { @Serializable @SerialName("ContinueWatching") data class ContinueWatching( - override val id: Int, override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(), ) : HomeRowConfig { override fun updateViewOptions(viewOptions: HomeRowViewOptions): ContinueWatching = this.copy(viewOptions = viewOptions) @@ -45,7 +43,6 @@ sealed interface HomeRowConfig { @Serializable @SerialName("NextUp") data class NextUp( - override val id: Int, override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(), ) : HomeRowConfig { override fun updateViewOptions(viewOptions: HomeRowViewOptions): NextUp = this.copy(viewOptions = viewOptions) @@ -57,7 +54,6 @@ sealed interface HomeRowConfig { @Serializable @SerialName("ContinueWatchingCombined") data class ContinueWatchingCombined( - override val id: Int, override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(), ) : HomeRowConfig { override fun updateViewOptions(viewOptions: HomeRowViewOptions): ContinueWatchingCombined = this.copy(viewOptions = viewOptions) @@ -69,7 +65,6 @@ sealed interface HomeRowConfig { @Serializable @SerialName("RecentlyAdded") data class RecentlyAdded( - override val id: Int, val parentId: UUID, override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(), ) : HomeRowConfig { @@ -82,7 +77,6 @@ sealed interface HomeRowConfig { @Serializable @SerialName("RecentlyReleased") data class RecentlyReleased( - override val id: Int, val parentId: UUID, override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(), ) : HomeRowConfig { @@ -95,7 +89,6 @@ sealed interface HomeRowConfig { @Serializable @SerialName("Genres") data class Genres( - override val id: Int, val parentId: UUID, override val viewOptions: HomeRowViewOptions = HomeRowViewOptions( @@ -112,7 +105,6 @@ sealed interface HomeRowConfig { @Serializable @SerialName("ByParent") data class ByParent( - override val id: Int, val parentId: UUID, val recursive: Boolean, val sort: SortAndDirection? = null, @@ -127,7 +119,6 @@ sealed interface HomeRowConfig { @Serializable @SerialName("GetItems") data class GetItems( - override val id: Int, val name: String, val getItems: GetItemsRequest, override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(), @@ -136,6 +127,11 @@ sealed interface HomeRowConfig { } } +/** + * Root class for home page settings + * + * Contains the list of rows and a version + */ @Serializable @SerialName("HomePageSettings") data class HomePageSettings( @@ -152,6 +148,11 @@ data class HomePageSettings( */ const val SUPPORTED_HOME_PAGE_SETTINGS_VERSION = 1 +/** + * View options for displaying a row + * + * Allows for changing things like height or aspect ratio + */ @Serializable data class HomeRowViewOptions( val heightDp: Int = Cards.HEIGHT_2X3_DP, diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt index a213707e..c1c4d730 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt @@ -6,7 +6,6 @@ import com.github.damontecres.wholphin.data.NavDrawerItemRepository import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.HomePageSettings import com.github.damontecres.wholphin.data.model.HomeRowConfig -import com.github.damontecres.wholphin.data.model.HomeRowViewOptions import com.github.damontecres.wholphin.preferences.HomePagePreferences import com.github.damontecres.wholphin.ui.DefaultItemFields import com.github.damontecres.wholphin.ui.SlimItemFields @@ -38,7 +37,6 @@ import org.jellyfin.sdk.model.api.UserDto import org.jellyfin.sdk.model.api.request.GetGenresRequest import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest -import org.jellyfin.sdk.model.serializer.toUUID import timber.log.Timber import java.io.File import javax.inject.Inject @@ -63,6 +61,11 @@ class HomeSettingsService val currentSettings = MutableStateFlow(HomePageResolvedSettings.EMPTY) + /** + * Saves a [HomePageSettings] to the server for the user under the display preference ID + * + * @see loadFromServer + */ suspend fun saveToServer( userId: UUID, settings: HomePageSettings, @@ -81,6 +84,13 @@ class HomeSettingsService ) } + /** + * Reads a [HomePageSettings] from the server for the user and display preference ID + * + * Returns null if there is none saved + * + * @see saveToServer + */ suspend fun loadFromServer( userId: UUID, displayPreferencesId: String = DISPLAY_PREF_ID, @@ -102,8 +112,16 @@ class HomeSettingsService client = context.getString(R.string.app_name), ).content + /** + * Computes the filename for locally saved [HomePageSettings] + */ private fun filename(userId: UUID) = "${CUSTOM_PREF_ID}_${userId.toServerString()}.json" + /** + * Save the [HomePageSettings] for the user locally on the device + * + * @see loadFromLocal + */ @OptIn(ExperimentalSerializationApi::class) suspend fun saveToLocal( userId: UUID, @@ -116,6 +134,11 @@ class HomeSettingsService } } + /** + * Reads [HomePageSettings] for the user if it exists + * + * @see saveToLocal + */ @OptIn(ExperimentalSerializationApi::class) suspend fun loadFromLocal(userId: UUID): HomePageSettings? { val dir = File(context.filesDir, CUSTOM_PREF_ID) @@ -129,6 +152,11 @@ class HomeSettingsService } } + /** + * Decodes [HomePageSettings] from a [JsonElement] skipping any unknown/unparsable rows + * + * This is public only for testing + */ fun decode(element: JsonElement): HomePageSettings { val rowsElement = element.jsonObject["rows"]?.jsonArray val rows = @@ -145,7 +173,14 @@ class HomeSettingsService return HomePageSettings(rows) } - suspend fun updateCurrent(userId: UUID) { + /** + * Loads [HomePageSettings] into [currentSettings] + * + * First checks locally, then on the server, and finally creates a default if needed + * + * Does not persist either the server nor default + */ + suspend fun loadCurrentSettings(userId: UUID) { Timber.v("Getting setting for %s", userId) // User local then server/remote otherwise create a default val settings = loadFromLocal(userId) ?: loadFromServer(userId) @@ -153,7 +188,10 @@ class HomeSettingsService if (settings != null) { Timber.v("Found settings") // Resolve - val resolvedRows = settings.rows.map { convert(it) } + val resolvedRows = + settings.rows.mapIndexed { index, config -> + resolve(index, config) + } HomePageResolvedSettings(resolvedRows) } else { createDefault(userId) @@ -162,7 +200,10 @@ class HomeSettingsService currentSettings.update { resolvedSettings } } - suspend fun createDefault(userId: UUID): HomePageResolvedSettings { + /** + * Create a default [HomePageResolvedSettings] using the available libraries + */ + private suspend fun createDefault(userId: UUID): HomePageResolvedSettings { Timber.v("Creating default settings") val navDrawerItems = navDrawerItemRepository.getNavDrawerItems() val libraries = @@ -179,76 +220,51 @@ class HomeSettingsService .getFilteredNavDrawerItems(navDrawerItems) .filter { it is ServerNavDrawerItem } .mapIndexed { index, it -> - val id = (it as ServerNavDrawerItem).itemId - val name = libraries.firstOrNull { it.itemId == id }?.name + val parentId = (it as ServerNavDrawerItem).itemId + val name = libraries.firstOrNull { it.itemId == parentId }?.name val title = name?.let { context.getString(R.string.recently_added_in, it) } ?: context.getString(R.string.recently_added) HomeRowConfigDisplay( - title, - HomeRowConfig.RecentlyAdded( - index, - id, - HomeRowViewOptions(), - ), + id = index, + title = title, + config = HomeRowConfig.RecentlyAdded(parentId), ) } val continueWatchingRows = - if (prefs.combineContinueNext) { + if (prefs.combineContinueNext) { // TODO listOf( HomeRowConfigDisplay( - context.getString(R.string.combine_continue_next), - HomeRowConfig.ContinueWatchingCombined( - includedIds.size + 1, - HomeRowViewOptions(), - ), + id = includedIds.size + 1, + title = context.getString(R.string.combine_continue_next), + config = HomeRowConfig.ContinueWatchingCombined(), ), ) } else { listOf( HomeRowConfigDisplay( - context.getString(R.string.continue_watching), - HomeRowConfig.ContinueWatching( - includedIds.size + 1, - HomeRowViewOptions(), - ), + id = includedIds.size + 1, + title = context.getString(R.string.continue_watching), + config = HomeRowConfig.ContinueWatching(), ), HomeRowConfigDisplay( - context.getString(R.string.next_up), - HomeRowConfig.NextUp( - includedIds.size + 2, - HomeRowViewOptions(), - ), + id = includedIds.size + 2, + title = context.getString(R.string.next_up), + config = HomeRowConfig.NextUp(), ), ) } - val rowConfig = - continueWatchingRows + includedIds + - // TODO remove after testing - listOf( - HomeRowConfigDisplay( - "Collection", - HomeRowConfig.ByParent( - id = 100, - parentId = "34ab6fd1f51c41bb014981f2e334f465".toUUID(), - recursive = true, - viewOptions = HomeRowViewOptions(), - ), - ), - HomeRowConfigDisplay( - "Playlist", - HomeRowConfig.ByParent( - id = 101, - parentId = "f94be36e9836127a0bccfc7843b19e5b".toUUID(), - recursive = true, - viewOptions = HomeRowViewOptions(), - ), - ), - ) + val rowConfig = continueWatchingRows + includedIds return HomePageResolvedSettings(rowConfig) } - suspend fun convert(config: HomeRowConfig): HomeRowConfigDisplay = + /** + * Converts a [HomeRowConfig] into [HomeRowConfigDisplay] for UI purposes + */ + private suspend fun resolve( + id: Int, + config: HomeRowConfig, + ): HomeRowConfigDisplay = when (config) { is HomeRowConfig.ByParent -> { val name = @@ -256,6 +272,7 @@ class HomeSettingsService .getItem(itemId = config.parentId) .content.name ?: "" HomeRowConfigDisplay( + id, name, config, ) @@ -263,6 +280,7 @@ class HomeSettingsService is HomeRowConfig.ContinueWatching -> { HomeRowConfigDisplay( + id, context.getString(R.string.continue_watching), config, ) @@ -270,6 +288,7 @@ class HomeSettingsService is HomeRowConfig.ContinueWatchingCombined -> { HomeRowConfigDisplay( + id, context.getString(R.string.combine_continue_next), config, ) @@ -281,17 +300,19 @@ class HomeSettingsService .getItem(itemId = config.parentId) .content.name ?: "" HomeRowConfigDisplay( + id, context.getString(R.string.genres_in, name), config, ) } is HomeRowConfig.GetItems -> { - HomeRowConfigDisplay(config.name, config) + HomeRowConfigDisplay(id, config.name, config) } is HomeRowConfig.NextUp -> { HomeRowConfigDisplay( + id, context.getString(R.string.next_up), config, ) @@ -303,6 +324,7 @@ class HomeSettingsService .getItem(itemId = config.parentId) .content.name ?: "" HomeRowConfigDisplay( + id, context.getString(R.string.recently_added_in, name), config, ) @@ -314,12 +336,16 @@ class HomeSettingsService .getItem(itemId = config.parentId) .content.name ?: "" HomeRowConfigDisplay( + id, context.getString(R.string.recently_released_in, name), config, ) } } + /** + * Fetch the data from the server for a given [HomeRowConfig] + */ suspend fun fetchDataForRow( row: HomeRowConfig, scope: CoroutineScope, @@ -553,11 +579,20 @@ class HomeSettingsService } } +/** + * A [HomeRowConfig] with a resolved ID and title so it is usable in the UI + */ data class HomeRowConfigDisplay( + val id: Int, val title: String, val config: HomeRowConfig, ) +/** + * List of resolved [HomeRowConfig]s as [HomeRowConfigDisplay]s + * + * @see HomePageSettings + */ data class HomePageResolvedSettings( val rows: List, ) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt index 41b37520..b28b682b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt @@ -225,7 +225,7 @@ class UserSwitchListener if (user != null) { // Check for home settings launchIO { - homeSettingsService.updateCurrent(user.id) + homeSettingsService.loadCurrentSettings(user.id) } // Check for seerr server launchIO { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsPage.kt index 72cfa7a7..56aee366 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsPage.kt @@ -72,7 +72,7 @@ fun HomeSettingsPage( onClickMove = viewModel::moveRow, onClickDelete = viewModel::deleteRow, onClick = { index, row -> - destination = HomeSettingsDestination.RowSettings(row.config.id) + destination = HomeSettingsDestination.RowSettings(row.id) scope.launch(ExceptionHandler()) { Timber.v("Scroll to $index") listState.scrollToItem(index) @@ -108,7 +108,7 @@ fun HomeSettingsPage( is HomeSettingsDestination.RowSettings -> { val row = state.rows - .first { it.config.id == dest.rowId } + .first { it.id == dest.rowId } HomeRowSettings( title = row.title, viewOptions = row.config.viewOptions, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsRowList.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsRowList.kt index 7740065b..52cd0f83 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsRowList.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsRowList.kt @@ -103,7 +103,7 @@ fun HomeSettingsRowList( item { HorizontalDivider() } - itemsIndexed(state.rows, key = { _, row -> row.config.id }) { index, row -> + itemsIndexed(state.rows, key = { _, row -> row.id }) { index, row -> HomeRowConfigContent( config = row, moveUpAllowed = index > 0, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt index 8a3d3984..0881dc72 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt @@ -162,31 +162,25 @@ class HomeSettingsViewModel when (type) { MetaRowType.CONTINUE_WATCHING -> { HomeRowConfigDisplay( - context.getString(R.string.continue_watching), - HomeRowConfig.ContinueWatching( - id, - HomeRowViewOptions(), - ), + id = id, + title = context.getString(R.string.continue_watching), + config = HomeRowConfig.ContinueWatching(), ) } MetaRowType.NEXT_UP -> { HomeRowConfigDisplay( - context.getString(R.string.continue_watching), - HomeRowConfig.NextUp( - id, - HomeRowViewOptions(), - ), + id = id, + title = context.getString(R.string.continue_watching), + config = HomeRowConfig.NextUp(), ) } MetaRowType.COMBINED_CONTINUE_WATCHING -> { HomeRowConfigDisplay( - context.getString(R.string.combine_continue_next), - HomeRowConfig.ContinueWatchingCombined( - id, - HomeRowViewOptions(), - ), + id = id, + title = context.getString(R.string.combine_continue_next), + config = HomeRowConfig.ContinueWatchingCombined(), ) } } @@ -212,12 +206,9 @@ class HomeSettingsViewModel val title = library.name.let { context.getString(R.string.recently_added_in, it) } HomeRowConfigDisplay( - title, - HomeRowConfig.RecentlyAdded( - id, - library.itemId, - HomeRowViewOptions(), - ), + id = id, + title = title, + config = HomeRowConfig.RecentlyAdded(library.itemId), ) } @@ -230,23 +221,18 @@ class HomeSettingsViewModel ) } HomeRowConfigDisplay( - title, - HomeRowConfig.RecentlyReleased( - id, - library.itemId, - HomeRowViewOptions(), - ), + id = id, + title = title, + config = HomeRowConfig.RecentlyReleased(library.itemId), ) } LibraryRowType.GENRES -> { val title = library.name.let { context.getString(R.string.genres_in, it) } HomeRowConfigDisplay( - title, - HomeRowConfig.Genres( - id, - library.itemId, - ), + id = id, + title = title, + config = HomeRowConfig.Genres(library.itemId), ) } } @@ -266,7 +252,7 @@ class HomeSettingsViewModel ) { viewModelScope.launchIO { updateState { - val index = it.rows.indexOfFirst { it.config.id == rowId } + val index = it.rows.indexOfFirst { it.id == rowId } val newRowConfig = it.rows[index] .config 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 d0289348..ab4d170a 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 @@ -23,7 +23,6 @@ class TestHomeRowSamples { val SAMPLES = listOf( HomeRowConfig.RecentlyAdded( - id = 0, parentId = UUID.randomUUID(), viewOptions = HomeRowViewOptions( @@ -37,36 +36,29 @@ class TestHomeRowSamples { ), ), 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( @@ -116,7 +108,7 @@ class TestHomeRowSamples { } @Test - fun test() { + fun `Parse list of rows with an unknown type`() { val service = HomeSettingsService( context = mockk(), @@ -133,7 +125,6 @@ class TestHomeRowSamples { "rows": [ { "type": "RecentlyAdded", - "id": 0, "parentId": "1dd1c2fd-2e1b-48e4-ba94-17a2350fe9cf" }, {