Basic save/load locally

This commit is contained in:
Damontecres 2026-01-24 16:05:33 -05:00
parent 84dc56289c
commit ee8a4ac0ac
No known key found for this signature in database
8 changed files with 392 additions and 123 deletions

View file

@ -99,7 +99,7 @@ sealed class HomeRowConfig {
val parentId: UUID, val parentId: UUID,
override val viewOptions: HomeRowViewOptions = override val viewOptions: HomeRowViewOptions =
HomeRowViewOptions( HomeRowViewOptions(
heightDp = (Cards.HEIGHT_2X3_DP * .75f).toInt(), heightDp = (Cards.HEIGHT_2X3_DP * .75f).toInt().let { it - it % 4 },
aspectRatio = AspectRatio.WIDE, aspectRatio = AspectRatio.WIDE,
), ),
) : HomeRowConfig() { ) : HomeRowConfig() {
@ -128,7 +128,7 @@ sealed class HomeRowConfig {
@SerialName("GetItems") @SerialName("GetItems")
data class GetItems( data class GetItems(
override val id: Int, override val id: Int,
val name: String?, val name: String,
val getItems: GetItemsRequest, val getItems: GetItemsRequest,
override val viewOptions: HomeRowViewOptions, override val viewOptions: HomeRowViewOptions,
) : HomeRowConfig() { ) : HomeRowConfig() {
@ -142,8 +142,8 @@ data class HomeRowConfigDisplay(
) )
@Serializable @Serializable
@SerialName("HomePageConfiguration") @SerialName("HomePageSettings")
data class HomePageConfiguration( data class HomePageSettings(
val version: Int = 1, val version: Int = 1,
val rows: List<HomeRowConfig>, val rows: List<HomeRowConfig>,
) )

View file

@ -0,0 +1,102 @@
package com.github.damontecres.wholphin.services
import android.content.Context
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.HomePageSettings
import com.github.damontecres.wholphin.ui.toServerString
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.displayPreferencesApi
import org.jellyfin.sdk.model.UUID
import java.io.File
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class HomeSettingsService
@Inject
constructor(
@param:ApplicationContext private val context: Context,
private val api: ApiClient,
private val serverRepository: ServerRepository,
) {
val jsonParser =
Json {
isLenient = true
ignoreUnknownKeys = true
}
suspend fun saveToServer(
settings: HomePageSettings,
displayPreferencesId: String = DISPLAY_PREF_ID,
) {
serverRepository.currentUser.value?.let { user ->
val current = getDisplayPreferences(user.id, DISPLAY_PREF_ID)
val customPrefs =
current.customPrefs.toMutableMap().apply {
put(CUSTOM_PREF_ID, jsonParser.encodeToString(settings))
}
api.displayPreferencesApi.updateDisplayPreferences(
displayPreferencesId = displayPreferencesId,
userId = user.id,
client = context.getString(R.string.app_name),
data = current.copy(customPrefs = customPrefs),
)
}
}
suspend fun loadFromServer(displayPreferencesId: String = DISPLAY_PREF_ID): HomePageSettings? =
serverRepository.currentUser.value?.let { user ->
val current = getDisplayPreferences(user.id, displayPreferencesId)
current.customPrefs[DISPLAY_PREF_ID]?.let {
Json.decodeFromString<HomePageSettings>(it)
}
}
private suspend fun getDisplayPreferences(
userId: UUID,
displayPreferencesId: String,
) = api.displayPreferencesApi
.getDisplayPreferences(
userId = userId,
displayPreferencesId = displayPreferencesId,
client = context.getString(R.string.app_name),
).content
private fun filename(userId: UUID) = "${CUSTOM_PREF_ID}_${userId.toServerString()}.json"
@OptIn(ExperimentalSerializationApi::class)
suspend fun saveToLocal(settings: HomePageSettings) {
serverRepository.currentUser.value?.let { user ->
val dir = File(context.filesDir, CUSTOM_PREF_ID)
dir.mkdirs()
File(dir, filename(user.id)).outputStream().use {
jsonParser.encodeToStream(settings, it)
}
}
}
@OptIn(ExperimentalSerializationApi::class)
suspend fun loadFromLocal(): HomePageSettings? =
serverRepository.currentUser.value?.let { user ->
val dir = File(context.filesDir, CUSTOM_PREF_ID)
val file = File(dir, filename(user.id))
if (file.exists()) {
file.inputStream().use {
jsonParser.decodeFromStream<HomePageSettings>(it)
}
} else {
null
}
}
companion object {
const val DISPLAY_PREF_ID = "default"
const val CUSTOM_PREF_ID = "home_settings"
}
}

View file

@ -195,6 +195,7 @@ fun HomePageContent(
onFocusPosition: ((RowColumn) -> Unit)? = null, onFocusPosition: ((RowColumn) -> Unit)? = null,
loadingState: LoadingState? = null, loadingState: LoadingState? = null,
listState: LazyListState = rememberLazyListState(), listState: LazyListState = rememberLazyListState(),
takeFocus: Boolean = true,
) { ) {
var position by rememberPosition() var position by rememberPosition()
val focusedItem = val focusedItem =
@ -204,22 +205,24 @@ fun HomePageContent(
val rowFocusRequesters = remember(homeRows) { List(homeRows.size) { FocusRequester() } } val rowFocusRequesters = remember(homeRows) { List(homeRows.size) { FocusRequester() } }
var firstFocused by remember { mutableStateOf(false) } var firstFocused by remember { mutableStateOf(false) }
LaunchedEffect(homeRows) { if (takeFocus) {
if (!firstFocused) { LaunchedEffect(homeRows) {
if (position.row >= 0) { if (!firstFocused) {
rowFocusRequesters[position.row].tryRequestFocus() if (position.row >= 0) {
firstFocused = true rowFocusRequesters[position.row].tryRequestFocus()
} else { firstFocused = true
// Waiting for the first home row to load, then focus on it } else {
homeRows // Waiting for the first home row to load, then focus on it
.indexOfFirst { it is HomeRowLoadingState.Success && it.items.isNotEmpty() } homeRows
.takeIf { it >= 0 } .indexOfFirst { it is HomeRowLoadingState.Success && it.items.isNotEmpty() }
?.let { .takeIf { it >= 0 }
rowFocusRequesters[it].tryRequestFocus() ?.let {
firstFocused = true rowFocusRequesters[it].tryRequestFocus()
delay(50) firstFocused = true
listState.scrollToItem(it) delay(50)
} listState.scrollToItem(it)
}
}
} }
} }
} }
@ -421,19 +424,17 @@ fun HomePageHeader(
item: BaseItem?, item: BaseItem?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
item?.let { val isEpisode = item?.type == BaseItemKind.EPISODE
val isEpisode = item.type == BaseItemKind.EPISODE val dto = item?.data
val dto = item.data HomePageHeader(
HomePageHeader( title = item?.title,
title = item.title, subtitle = if (isEpisode) dto?.name else null,
subtitle = if (isEpisode) dto.name else null, overview = dto?.overview,
overview = dto.overview, overviewTwoLines = isEpisode,
overviewTwoLines = isEpisode, quickDetails = item?.ui?.quickDetails ?: AnnotatedString(""),
quickDetails = item.ui.quickDetails, timeRemaining = item?.timeRemainingOrRuntime,
timeRemaining = item.timeRemainingOrRuntime, modifier = modifier,
modifier = modifier, )
)
}
} }
@Composable @Composable

View file

@ -4,17 +4,22 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import androidx.tv.material3.surfaceColorAtElevation import androidx.tv.material3.surfaceColorAtElevation
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.preferences.ComposablePreference import com.github.damontecres.wholphin.ui.preferences.ComposablePreference
import com.github.damontecres.wholphin.ui.tryRequestFocus
@Composable @Composable
fun HomeRowSettings( fun HomeRowSettings(
@ -25,6 +30,8 @@ fun HomeRowSettings(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
defaultViewOptions: HomeRowViewOptions = HomeRowViewOptions(), defaultViewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) { ) {
val firstFocus = remember { FocusRequester() }
LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
Column(modifier = modifier) { Column(modifier = modifier) {
Text( Text(
text = title, text = title,
@ -32,7 +39,7 @@ fun HomeRowSettings(
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
) )
LazyColumn { LazyColumn {
items(HomeRowViewOptions.OPTIONS) { pref -> itemsIndexed(HomeRowViewOptions.OPTIONS) { index, pref ->
pref as AppPreference<HomeRowViewOptions, Any> pref as AppPreference<HomeRowViewOptions, Any>
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val value = pref.getter.invoke(viewOptions) val value = pref.getter.invoke(viewOptions)
@ -51,7 +58,13 @@ fun HomeRowSettings(
onApplyApplyAll.invoke() onApplyApplyAll.invoke()
} }
}, },
modifier = Modifier.background(MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp)), modifier =
Modifier
.background(
MaterialTheme.colorScheme.surfaceColorAtElevation(
5.dp,
),
).ifElse(index == 0, Modifier.focusRequester(firstFocus)),
) )
} }
} }

View file

@ -1,9 +1,11 @@
package com.github.damontecres.wholphin.ui.main.settings package com.github.damontecres.wholphin.ui.main.settings
import androidx.navigation3.runtime.NavKey
/** /**
* Tracking the pages for selecting and configuring rows * Tracking the pages for selecting and configuring rows
*/ */
sealed interface HomeSettingsDestination { sealed interface HomeSettingsDestination : NavKey {
data object RowList : HomeSettingsDestination data object RowList : HomeSettingsDestination
data object ChooseLibrary : HomeSettingsDestination data object ChooseLibrary : HomeSettingsDestination

View file

@ -15,12 +15,16 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import com.github.damontecres.wholphin.ui.main.HomePageContent import com.github.damontecres.wholphin.ui.main.HomePageContent
import com.github.damontecres.wholphin.util.ExceptionHandler
import kotlinx.coroutines.launch
import timber.log.Timber
val settingsWidth = 300.dp val settingsWidth = 300.dp
@ -29,10 +33,12 @@ fun HomeSettingsPage(
modifier: Modifier, modifier: Modifier,
viewModel: HomeSettingsViewModel = hiltViewModel(), viewModel: HomeSettingsViewModel = hiltViewModel(),
) { ) {
val state by viewModel.state.collectAsState() val scope = rememberCoroutineScope()
val listState = rememberLazyListState() val listState = rememberLazyListState()
var destination by remember { mutableStateOf<HomeSettingsDestination>(HomeSettingsDestination.RowList) } var destination by remember { mutableStateOf<HomeSettingsDestination>(HomeSettingsDestination.RowList) }
val state by viewModel.state.collectAsState()
BackHandler(destination is HomeSettingsDestination.ChooseRowType) { BackHandler(destination is HomeSettingsDestination.ChooseRowType) {
destination = HomeSettingsDestination.ChooseLibrary destination = HomeSettingsDestination.ChooseLibrary
} }
@ -62,10 +68,15 @@ fun HomeSettingsPage(
HomeSettingsRowList( HomeSettingsRowList(
state = state, state = state,
onClickAdd = { destination = HomeSettingsDestination.ChooseLibrary }, onClickAdd = { destination = HomeSettingsDestination.ChooseLibrary },
onClickSaveLocal = { viewModel.saveToLocal() },
onClickMove = viewModel::moveRow, onClickMove = viewModel::moveRow,
onClickDelete = viewModel::deleteRow, onClickDelete = viewModel::deleteRow,
onClick = { index, row -> onClick = { index, row ->
destination = HomeSettingsDestination.RowSettings(row.config.id) destination = HomeSettingsDestination.RowSettings(row.config.id)
scope.launch(ExceptionHandler()) {
Timber.v("Scroll to $index")
listState.scrollToItem(index)
}
}, },
modifier = destModifier, modifier = destModifier,
) )
@ -121,6 +132,7 @@ fun HomeSettingsPage(
showClock = false, showClock = false,
onUpdateBackdrop = viewModel::updateBackdrop, onUpdateBackdrop = viewModel::updateBackdrop,
listState = listState, listState = listState,
takeFocus = false,
modifier = modifier =
Modifier Modifier
.fillMaxHeight() .fillMaxHeight()

View file

@ -13,8 +13,12 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -33,7 +37,7 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.HomeRowConfigDisplay import com.github.damontecres.wholphin.data.model.HomeRowConfigDisplay
import com.github.damontecres.wholphin.ui.FontAwesome import com.github.damontecres.wholphin.ui.FontAwesome
import com.github.damontecres.wholphin.ui.components.Button import com.github.damontecres.wholphin.ui.components.Button
import com.github.damontecres.wholphin.ui.ifElse import com.github.damontecres.wholphin.ui.tryRequestFocus
enum class MoveDirection { enum class MoveDirection {
UP, UP,
@ -44,12 +48,14 @@ enum class MoveDirection {
fun HomeSettingsRowList( fun HomeSettingsRowList(
state: HomePageSettingsState, state: HomePageSettingsState,
onClick: (Int, HomeRowConfigDisplay) -> Unit, onClick: (Int, HomeRowConfigDisplay) -> Unit,
onClickSaveLocal: () -> Unit,
onClickAdd: () -> Unit, onClickAdd: () -> Unit,
onClickMove: (MoveDirection, Int) -> Unit, onClickMove: (MoveDirection, Int) -> Unit,
onClickDelete: (Int) -> Unit, onClickDelete: (Int) -> Unit,
modifier: Modifier, modifier: Modifier,
firstFocus: FocusRequester = remember { FocusRequester() }, firstFocus: FocusRequester = remember { FocusRequester() },
) { ) {
LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
Column(modifier = modifier) { Column(modifier = modifier) {
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
@ -58,6 +64,45 @@ fun HomeSettingsRowList(
.fillMaxHeight() .fillMaxHeight()
.focusRestorer(firstFocus), .focusRestorer(firstFocus),
) { ) {
item {
ListItem(
selected = false,
headlineContent = {
Text(
text = stringResource(R.string.add_row),
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Add,
contentDescription = null,
)
},
onClick = onClickAdd,
modifier = Modifier.focusRequester(firstFocus),
)
}
item {
ListItem(
selected = false,
headlineContent = {
Text(
text = stringResource(R.string.save),
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Create,
contentDescription = null,
)
},
onClick = onClickSaveLocal,
modifier = Modifier,
)
}
item {
HorizontalDivider()
}
itemsIndexed(state.rows, key = { _, row -> row.config.id }) { index, row -> itemsIndexed(state.rows, key = { _, row -> row.config.id }) { index, row ->
HomeRowConfigContent( HomeRowConfigContent(
config = row, config = row,
@ -69,20 +114,9 @@ fun HomeSettingsRowList(
onClick = { onClick.invoke(index, row) }, onClick = { onClick.invoke(index, row) },
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth(),
.ifElse(index == 0, Modifier.focusRequester(firstFocus)),
) )
} }
item {
Button(
onClick = onClickAdd,
modifier = Modifier.ifElse(state.rows.isEmpty(), Modifier.focusRequester(firstFocus)),
) {
Text(
text = stringResource(R.string.add_row),
)
}
}
} }
} }
} }

View file

@ -1,6 +1,7 @@
package com.github.damontecres.wholphin.ui.main.settings package com.github.damontecres.wholphin.ui.main.settings
import android.content.Context import android.content.Context
import android.widget.Toast
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -8,11 +9,13 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.NavDrawerItemRepository import com.github.damontecres.wholphin.data.NavDrawerItemRepository
import com.github.damontecres.wholphin.data.ServerRepository 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.HomePageSettings
import com.github.damontecres.wholphin.data.model.HomeRowConfig import com.github.damontecres.wholphin.data.model.HomeRowConfig
import com.github.damontecres.wholphin.data.model.HomeRowConfigDisplay import com.github.damontecres.wholphin.data.model.HomeRowConfigDisplay
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
import com.github.damontecres.wholphin.preferences.HomePagePreferences import com.github.damontecres.wholphin.preferences.HomePagePreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.HomeSettingsService
import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.ImageUrlService
import com.github.damontecres.wholphin.services.LatestNextUpService import com.github.damontecres.wholphin.services.LatestNextUpService
import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.services.UserPreferencesService
@ -21,6 +24,7 @@ import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.components.getGenreImageMap import com.github.damontecres.wholphin.ui.components.getGenreImageMap
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.GetGenresRequestHandler import com.github.damontecres.wholphin.util.GetGenresRequestHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.HomeRowLoadingState import com.github.damontecres.wholphin.util.HomeRowLoadingState
@ -40,6 +44,7 @@ import org.jellyfin.sdk.model.api.request.GetGenresRequest
import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.api.request.GetItemsRequest
import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest
import org.jellyfin.sdk.model.serializer.toUUID import org.jellyfin.sdk.model.serializer.toUUID
import timber.log.Timber
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@ -49,6 +54,7 @@ class HomeSettingsViewModel
constructor( constructor(
@param:ApplicationContext private val context: Context, @param:ApplicationContext private val context: Context,
private val api: ApiClient, private val api: ApiClient,
private val homeSettingsService: HomeSettingsService,
private val serverRepository: ServerRepository, private val serverRepository: ServerRepository,
private val userPreferencesService: UserPreferencesService, private val userPreferencesService: UserPreferencesService,
private val navDrawerItemRepository: NavDrawerItemRepository, private val navDrawerItemRepository: NavDrawerItemRepository,
@ -73,91 +79,176 @@ class HomeSettingsViewModel
} }
_state.update { it.copy(libraries = libraries) } _state.update { it.copy(libraries = libraries) }
// TODO get config val localSettings =
try {
homeSettingsService.loadFromLocal()
} catch (ex: Exception) {
Timber.e(ex)
showToast(context, "Error loading settings: ${ex.localizedMessage}")
null
}
if (localSettings != null) {
val displays = localSettings.rows.map { convert(it) }
_state.update {
it.copy(rows = displays)
}
} else {
// Or create default // Or create default
val prefs = userPreferencesService.getCurrent().appPreferences.homePagePreferences val prefs =
val includedIds = userPreferencesService.getCurrent().appPreferences.homePagePreferences
navDrawerItemRepository val includedIds =
.getFilteredNavDrawerItems(navDrawerItems) navDrawerItemRepository
.filter { it is ServerNavDrawerItem } .getFilteredNavDrawerItems(navDrawerItems)
.mapIndexed { index, it -> .filter { it is ServerNavDrawerItem }
val id = (it as ServerNavDrawerItem).itemId .mapIndexed { index, it ->
val name = libraries.firstOrNull { it.itemId == id }?.name val id = (it as ServerNavDrawerItem).itemId
val title = val name = libraries.firstOrNull { it.itemId == id }?.name
name?.let { context.getString(R.string.recently_added_in, it) } val title =
?: context.getString(R.string.recently_added) name?.let { context.getString(R.string.recently_added_in, it) }
HomeRowConfigDisplay( ?: context.getString(R.string.recently_added)
title, HomeRowConfigDisplay(
HomeRowConfig.RecentlyAdded( title,
index, HomeRowConfig.RecentlyAdded(
id, index,
HomeRowViewOptions(), id,
HomeRowViewOptions(),
),
)
}
val continueWatchingRows =
if (prefs.combineContinueNext) {
listOf(
HomeRowConfigDisplay(
context.getString(R.string.combine_continue_next),
HomeRowConfig.ContinueWatchingCombined(
includedIds.size + 1,
HomeRowViewOptions(),
),
),
)
} else {
listOf(
HomeRowConfigDisplay(
context.getString(R.string.continue_watching),
HomeRowConfig.ContinueWatching(
includedIds.size + 1,
HomeRowViewOptions(),
),
),
HomeRowConfigDisplay(
context.getString(R.string.next_up),
HomeRowConfig.NextUp(
includedIds.size + 2,
HomeRowViewOptions(),
),
), ),
) )
} }
val continueWatchingRows = val rowConfig =
if (prefs.combineContinueNext) { continueWatchingRows + includedIds +
listOf( // TODO remove after testing
HomeRowConfigDisplay( listOf(
context.getString(R.string.combine_continue_next), HomeRowConfigDisplay(
HomeRowConfig.ContinueWatchingCombined( "Collection",
includedIds.size + 1, HomeRowConfig.ByParent(
HomeRowViewOptions(), id = 100,
parentId = "34ab6fd1f51c41bb014981f2e334f465".toUUID(),
recursive = true,
viewOptions = HomeRowViewOptions(),
),
), ),
), HomeRowConfigDisplay(
) "Playlist",
} else { HomeRowConfig.ByParent(
listOf( id = 101,
HomeRowConfigDisplay( parentId = "f94be36e9836127a0bccfc7843b19e5b".toUUID(),
context.getString(R.string.continue_watching), recursive = true,
HomeRowConfig.ContinueWatching( viewOptions = HomeRowViewOptions(),
includedIds.size + 1, ),
HomeRowViewOptions(),
), ),
), )
HomeRowConfigDisplay( _state.update {
context.getString(R.string.next_up), it.copy(rows = rowConfig)
HomeRowConfig.NextUp(
includedIds.size + 2,
HomeRowViewOptions(),
),
),
)
} }
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(),
),
),
)
_state.update {
it.copy(rows = rowConfig)
} }
// val json = Json.encodeToString(rowConfig)
// Timber.v("json=$json")
fetchRowData() fetchRowData()
} }
} }
private suspend fun convert(config: HomeRowConfig): HomeRowConfigDisplay =
when (config) {
is HomeRowConfig.ByParent -> {
val name =
api.userLibraryApi
.getItem(itemId = config.parentId)
.content.name ?: ""
HomeRowConfigDisplay(
name,
config,
)
}
is HomeRowConfig.ContinueWatching -> {
HomeRowConfigDisplay(
context.getString(R.string.continue_watching),
config,
)
}
is HomeRowConfig.ContinueWatchingCombined -> {
HomeRowConfigDisplay(
context.getString(R.string.combine_continue_next),
config,
)
}
is HomeRowConfig.Genres -> {
val name =
api.userLibraryApi
.getItem(itemId = config.parentId)
.content.name ?: ""
HomeRowConfigDisplay(
context.getString(R.string.genres_in, name),
config,
)
}
is HomeRowConfig.GetItems -> {
HomeRowConfigDisplay(config.name, config)
}
is HomeRowConfig.NextUp -> {
HomeRowConfigDisplay(
context.getString(R.string.next_up),
config,
)
}
is HomeRowConfig.RecentlyAdded -> {
val name =
api.userLibraryApi
.getItem(itemId = config.parentId)
.content.name ?: ""
HomeRowConfigDisplay(
context.getString(R.string.recently_added_in, name),
config,
)
}
is HomeRowConfig.RecentlyReleased -> {
val name =
api.userLibraryApi
.getItem(itemId = config.parentId)
.content.name ?: ""
HomeRowConfigDisplay(
context.getString(R.string.recently_released_in, name),
config,
)
}
}
fun updateBackdrop(item: BaseItem) { fun updateBackdrop(item: BaseItem) {
viewModelScope.launchIO { viewModelScope.launchIO {
backdropService.submit(item) backdropService.submit(item)
@ -615,6 +706,20 @@ class HomeSettingsViewModel
) )
} }
} }
fun saveToLocal() {
viewModelScope.launchIO {
val rows = state.value.rows.map { it.config }
val settings = HomePageSettings(rows = rows)
try {
homeSettingsService.saveToLocal(settings)
showToast(context, context.getString(R.string.save), Toast.LENGTH_SHORT)
} catch (ex: Exception) {
Timber.e(ex)
showToast(context, "Error saving: ${ex.localizedMessage}")
}
}
}
} }
data class HomePageSettingsState( data class HomePageSettingsState(