Save/load from server

This commit is contained in:
Damontecres 2026-01-29 15:49:25 -05:00
parent e11b9ecc72
commit f315531db7
No known key found for this signature in database
7 changed files with 192 additions and 33 deletions

View file

@ -399,7 +399,7 @@ class HomeSettingsService
/**
* Converts a [HomeRowConfig] into [HomeRowConfigDisplay] for UI purposes
*/
private suspend fun resolve(
suspend fun resolve(
id: Int,
config: HomeRowConfig,
): HomeRowConfigDisplay =

View file

@ -0,0 +1,56 @@
package com.github.damontecres.wholphin.ui.main.settings
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.tv.material3.ListItem
import androidx.tv.material3.ListItemBorder
import androidx.tv.material3.ListItemColors
import androidx.tv.material3.ListItemDefaults
import androidx.tv.material3.ListItemGlow
import androidx.tv.material3.ListItemScale
import androidx.tv.material3.ListItemShape
@Composable
@NonRestartableComposable
fun HomeSettingsListItem(
selected: Boolean,
onClick: () -> Unit,
headlineContent: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
onLongClick: (() -> Unit)? = null,
overlineContent: (@Composable () -> Unit)? = null,
supportingContent: (@Composable () -> Unit)? = null,
leadingContent: (@Composable BoxScope.() -> Unit)? = null,
trailingContent: (@Composable () -> Unit)? = null,
tonalElevation: Dp = 3.dp,
shape: ListItemShape = ListItemDefaults.shape(),
colors: ListItemColors = ListItemDefaults.colors(),
scale: ListItemScale = ListItemDefaults.scale(),
border: ListItemBorder = ListItemDefaults.border(),
glow: ListItemGlow = ListItemDefaults.glow(),
interactionSource: MutableInteractionSource? = null,
) = ListItem(
selected = selected,
onClick = onClick,
headlineContent = headlineContent,
modifier = modifier,
enabled = enabled,
onLongClick = onLongClick,
overlineContent = overlineContent,
supportingContent = supportingContent,
leadingContent = leadingContent,
trailingContent = trailingContent,
tonalElevation = tonalElevation,
shape = shape,
colors = colors,
scale = scale,
border = border,
glow = glow,
interactionSource = interactionSource,
)

View file

@ -12,8 +12,12 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
@ -23,6 +27,8 @@ import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.surfaceColorAtElevation
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.components.ConfirmDialog
import com.github.damontecres.wholphin.ui.main.HomePageContent
import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsDestination.ChooseRowType
import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsDestination.RowSettings
@ -41,6 +47,7 @@ fun HomeSettingsPage(
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
val backStack = rememberNavBackStack(HomeSettingsDestination.RowList)
var showConfirmDialog by remember { mutableStateOf<(() -> Unit)?>(null) }
val state by viewModel.state.collectAsState()
val discoverEnabled by viewModel.discoverEnabled.collectAsState(false)
@ -86,7 +93,16 @@ fun HomeSettingsPage(
HomeSettingsRowList(
state = state,
onClickAdd = { backStack.add(HomeSettingsDestination.AddRow) },
onClickSaveLocal = { viewModel.saveToLocal() },
onClickSave = {
showConfirmDialog = {
viewModel.saveToRemote()
}
},
onClickLoad = {
showConfirmDialog = {
viewModel.loadFromRemote()
}
},
onClickMove = viewModel::moveRow,
onClickDelete = viewModel::deleteRow,
onClick = { index, row ->
@ -189,4 +205,15 @@ fun HomeSettingsPage(
.weight(1f),
)
}
showConfirmDialog?.let { onConfirm ->
ConfirmDialog(
title = stringResource(R.string.confirm),
body = "Overwrite?",
onCancel = { showConfirmDialog = null },
onConfirm = {
onConfirm.invoke()
showConfirmDialog = null
},
)
}
}

View file

@ -12,7 +12,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
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.Edit
import androidx.compose.material3.HorizontalDivider
@ -27,9 +26,11 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.tv.material3.Icon
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.services.HomeRowConfigDisplay
@ -47,7 +48,8 @@ fun HomeSettingsRowList(
state: HomePageSettingsState,
onClick: (Int, HomeRowConfigDisplay) -> Unit,
onClickResize: (Int) -> Unit,
onClickSaveLocal: () -> Unit,
onClickSave: () -> Unit,
onClickLoad: () -> Unit,
onClickAdd: () -> Unit,
onClickMove: (MoveDirection, Int) -> Unit,
onClickDelete: (Int) -> Unit,
@ -82,21 +84,57 @@ fun HomeSettingsRowList(
modifier = Modifier.focusRequester(firstFocus),
)
}
item {
Row {
HomeSettingsListItem(
selected = false,
headlineContent = {
Text(
text = "Increase card sizes",
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = null,
)
},
onClick = { onClickResize.invoke(1) },
modifier = Modifier.weight(1f),
)
HomeSettingsListItem(
selected = false,
headlineContent = {
Text(
text = "Decrease card sizes",
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = null,
)
},
onClick = { onClickResize.invoke(-1) },
modifier = Modifier.weight(1f),
)
}
}
item {
HomeSettingsListItem(
selected = false,
headlineContent = {
Text(
text = "Increase card sizes",
text = stringResource(R.string.save_to_server),
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = null,
Text(
text = stringResource(R.string.fa_cloud_arrow_up),
fontFamily = FontAwesome,
)
},
onClick = { onClickResize.invoke(1) },
onClick = onClickSave,
modifier = Modifier,
)
}
@ -105,38 +143,26 @@ fun HomeSettingsRowList(
selected = false,
headlineContent = {
Text(
text = "Decrease card sizes",
text = stringResource(R.string.load_from_server),
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = null,
)
},
onClick = { onClickResize.invoke(-1) },
modifier = Modifier,
)
}
item {
HomeSettingsListItem(
selected = false,
headlineContent = {
Text(
text = stringResource(R.string.save),
text = stringResource(R.string.fa_cloud_arrow_down),
fontFamily = FontAwesome,
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Create,
contentDescription = null,
)
},
onClick = onClickSaveLocal,
onClick = onClickLoad,
modifier = Modifier,
)
}
item {
Text(
text = stringResource(R.string.home_rows),
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
HorizontalDivider()
}
itemsIndexed(state.rows, key = { _, row -> row.id }) { index, row ->
@ -192,8 +218,7 @@ fun HomeRowConfigContent(
Text(
text = config.title,
overflow = TextOverflow.Ellipsis,
modifier =
Modifier,
modifier = Modifier,
)
},
onClick = onClick,

View file

@ -339,6 +339,52 @@ class HomeSettingsViewModel
}
}
fun saveToRemote() {
viewModelScope.launchIO {
serverRepository.currentUser.value?.let { user ->
Timber.d("Saving home settings to remote")
val rows = state.value.rows.map { it.config }
val settings = HomePageSettings(rows = rows)
try {
Timber.d("saveToRemote")
homeSettingsService.saveToServer(user.id, settings)
} catch (ex: Exception) {
Timber.e(ex)
showToast(context, "Error saving: ${ex.localizedMessage}")
}
}
}
}
fun loadFromRemote() {
viewModelScope.launchIO {
serverRepository.currentUser.value?.let { user ->
Timber.d("Loading home settings from remote")
try {
_state.update { it.copy(loading = LoadingState.Loading) }
val result = homeSettingsService.loadFromServer(user.id)
if (result != null) {
Timber.v("Got remote settings")
val newRows =
result.rows.mapIndexed { index, config ->
homeSettingsService.resolve(index, config)
}
_state.update {
it.copy(rows = newRows)
}
} else {
Timber.v("No remote settings")
showToast(context, "No server-side settings found")
}
fetchRowData()
} catch (ex: Exception) {
Timber.e(ex)
showToast(context, "Error: ${ex.localizedMessage}")
}
}
}
}
fun saveToLocal() {
// This uses injected ioScope so that it will still run when the page is closing
ioScope.launchIO {

View file

@ -50,4 +50,6 @@
<string name="fa_clock" translatable="false">&#xf017;</string>
<string name="fa_bell" translatable="false">&#xf0f3;</string>
<string name="fa_check" translatable="false">&#xf00c;</string>
<string name="fa_cloud_arrow_up" translatable="false">&#xf0ee;</string>
<string name="fa_cloud_arrow_down" translatable="false">&#xf0ed;</string>
</resources>

View file

@ -468,6 +468,9 @@
<string name="height">Height</string>
<string name="apply_all_rows">Apply to all rows</string>
<string name="customize_home">Customize home page</string>
<string name="home_rows">Home rows</string>
<string name="load_from_server">Load from server</string>
<string name="save_to_server">Save to server</string>
<string-array name="theme_song_volume">
<item>Disabled</item>