Some fixes to customize homes (#893)

## Description
- Update header when editing a row
- Exclude non-empty rows to prevent scroll lock
- Only save settings locally if they are different from the source

### Related issues
Related to #803 

### Testing
Emulator, tested remote & local settings changes

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Ray 2026-02-13 16:30:03 -05:00 committed by GitHub
parent aa69646b2d
commit 1e810c1157
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 67 additions and 25 deletions

View file

@ -34,6 +34,7 @@ import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.main.HomePageContent
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.rememberPosition
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState
@ -149,8 +150,10 @@ fun RecommendedContent(
}
LoadingState.Success -> {
var position by rememberPosition()
HomePageContent(
homeRows = rows,
position = position,
onClickItem = { _, item ->
viewModel.navigationManager.navigateTo(item.destination())
},
@ -160,7 +163,21 @@ fun RecommendedContent(
onClickPlay = { _, item ->
viewModel.navigationManager.navigateTo(Destination.Playback(item))
},
onFocusPosition = onFocusPosition,
onFocusPosition = {
position = it
val nonEmptyRowBefore =
rows
.subList(0, it.row)
.count {
it is HomeRowLoadingState.Success && it.items.isEmpty()
}
onFocusPosition?.invoke(
RowColumn(
it.row - nonEmptyRowBefore,
it.column,
),
)
},
showClock = preferences.appPreferences.interfacePreferences.showClock,
onUpdateBackdrop = viewModel::updateBackdrop,
modifier = modifier,

View file

@ -117,12 +117,17 @@ fun HomePage(
var dialog by remember { mutableStateOf<DialogParams?>(null) }
var showPlaylistDialog by remember { mutableStateOf<UUID?>(null) }
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
var position by rememberPosition()
HomePageContent(
homeRows = homeRows,
onClickItem = { position, item ->
position = position,
onFocusPosition = { position = it },
onClickItem = { clickedPosition, item ->
position = clickedPosition
viewModel.navigationManager.navigateTo(item.destination())
},
onLongClickItem = { position, item ->
onLongClickItem = { clickedPosition, item ->
position = clickedPosition
val dialogItems =
buildMoreDialogItemsForHome(
context = context,
@ -193,19 +198,19 @@ fun HomePage(
@Composable
fun HomePageContent(
homeRows: List<HomeRowLoadingState>,
position: RowColumn,
onFocusPosition: (RowColumn) -> Unit,
onClickItem: (RowColumn, BaseItem) -> Unit,
onLongClickItem: (RowColumn, BaseItem) -> Unit,
onClickPlay: (RowColumn, BaseItem) -> Unit,
showClock: Boolean,
onUpdateBackdrop: (BaseItem) -> Unit,
modifier: Modifier = Modifier,
onFocusPosition: ((RowColumn) -> Unit)? = null,
loadingState: LoadingState? = null,
listState: LazyListState = rememberLazyListState(),
takeFocus: Boolean = true,
showEmptyRows: Boolean = false,
) {
var position by rememberPosition()
val focusedItem =
position.let {
(homeRows.getOrNull(it.row) as? HomeRowLoadingState.Success)?.items?.getOrNull(it.column)
@ -329,21 +334,8 @@ fun HomePageContent(
cardModifier
.onFocusChanged {
if (it.isFocused) {
position =
RowColumn(rowIndex, index)
}
if (it.isFocused && onFocusPosition != null) {
val nonEmptyRowBefore =
homeRows
.subList(0, rowIndex)
.count {
it is HomeRowLoadingState.Success && it.items.isEmpty()
}
onFocusPosition.invoke(
RowColumn(
rowIndex - nonEmptyRowBefore,
index,
),
onFocusPosition?.invoke(
RowColumn(rowIndex, index),
)
}
}.onKeyEvent {

View file

@ -131,7 +131,14 @@ class HomeViewModel
)
}
}
val rows = deferred.awaitAll()
val rows =
deferred
.awaitAll()
.filter {
// Include only errors & non-empty successes
it is HomeRowLoadingState.Error ||
(it is HomeRowLoadingState.Success && it.items.isNotEmpty())
}
Timber.v("Got all rows")
_state.update {
it.copy(

View file

@ -33,11 +33,14 @@ import com.github.damontecres.wholphin.data.model.HomeRowConfig
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.ui.components.ConfirmDialog
import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.launchIO
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
import com.github.damontecres.wholphin.ui.rememberPosition
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.HomeRowLoadingState
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import timber.log.Timber
@ -55,6 +58,7 @@ fun HomeSettingsPage(
var showConfirmDialog by remember { mutableStateOf<ShowConfirm?>(null) }
val state by viewModel.state.collectAsState()
var position by rememberPosition(0, 0)
// TODO discover rows
val discoverEnabled = false // by viewModel.discoverEnabled.collectAsState(false)
@ -109,7 +113,15 @@ fun HomeSettingsPage(
backStack.add(RowSettings(row.id))
scope.launch(ExceptionHandler()) {
Timber.v("Scroll to $index")
listState.scrollToItem(index)
listState.animateScrollToItem(index)
// Update backdrop to first item in the row
(state.rowData.getOrNull(index) as? HomeRowLoadingState.Success)
?.items
?.firstOrNull()
?.let {
viewModel.updateBackdrop(it)
}
position = RowColumn(index, 0)
}
},
modifier = destModifier,
@ -259,6 +271,8 @@ fun HomeSettingsPage(
HomePageContent(
loadingState = state.loading,
homeRows = state.rowData,
position = position,
onFocusPosition = { position = it },
onClickItem = { _, _ -> },
onLongClickItem = { _, _ -> },
onClickPlay = { _, _ -> },

View file

@ -81,6 +81,9 @@ class HomeSettingsViewModel
val discoverEnabled = seerrServerRepository.active
private var originalLocalSettings: HomePageSettings? = null
private var originalRemoteSettings: HomePageSettings? = null
init {
addCloseable { saveToLocal() }
viewModelScope.launchIO {
@ -88,6 +91,8 @@ class HomeSettingsViewModel
val libraries = navDrawerService.getAllUserLibraries(userDto.id, userDto.tvAccess)
val currentSettings =
homeSettingsService.currentSettings.first { it != HomePageResolvedSettings.EMPTY }
originalLocalSettings = homeSettingsService.loadFromLocal(userDto.id)
originalRemoteSettings = homeSettingsService.loadFromServer(userDto.id)
Timber.v("currentSettings=%s", currentSettings)
idCounter = currentSettings.rows.maxOfOrNull { it.id }?.plus(1) ?: 0
_state.update {
@ -493,9 +498,16 @@ class HomeSettingsViewModel
HomePageSettings(rows = rows, SUPPORTED_HOME_PAGE_SETTINGS_VERSION)
try {
Timber.d("saveToLocal")
val local = homeSettingsService.loadFromLocal(user.id)
// Only save if there are changes
if (local != settings) {
// Only save if there are changes based on original source
val shouldSave =
if (originalLocalSettings != null) {
originalLocalSettings != settings
} else if (originalRemoteSettings != null) {
originalRemoteSettings != settings
} else {
true
}
if (shouldSave) {
homeSettingsService.saveToLocal(user.id, settings)
homeSettingsService.updateCurrent(settings)
showSaveToast()