Move ID into display models & add documentation

This commit is contained in:
Damontecres 2026-01-25 11:08:45 -05:00
parent f58b371698
commit 15ce562957
No known key found for this signature in database
7 changed files with 123 additions and 110 deletions

View file

@ -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,

View file

@ -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<HomeRowConfigDisplay>,
) {

View file

@ -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 {

View file

@ -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,

View file

@ -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,

View file

@ -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