Fix focus restoration when navigating back to home screen

- Created HomeScreenFocusState data class to store scroll and focus state
- Added focus state management to HomeViewModel
- Implemented scroll position saving in HomeScreen using DisposableEffect
- Implemented scroll position restoration using LaunchedEffect
- Added horizontal scroll restoration support to CatalogRowSection
- Fixes issue where users lost their place when returning from detail screen

This ensures that when users navigate from HomeScreen -> MetaDetailsScreen
and back, their vertical scroll position and horizontal scroll positions in
catalog rows are properly restored.
This commit is contained in:
CrissZollo 2026-02-01 23:27:12 +01:00
parent e7ff9185fe
commit f5ead7c729
4 changed files with 107 additions and 4 deletions

View file

@ -32,9 +32,19 @@ fun CatalogRowSection(
catalogRow: CatalogRow,
onItemClick: (String, String, String) -> Unit,
onLoadMore: () -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
initialScrollIndex: Int = 0
) {
val listState = rememberLazyListState()
val listState = rememberLazyListState(
initialFirstVisibleItemIndex = initialScrollIndex
)
// Restore scroll position if needed
LaunchedEffect(initialScrollIndex) {
if (initialScrollIndex > 0) {
listState.scrollToItem(initialScrollIndex)
}
}
val shouldLoadMore by remember {
derivedStateOf {

View file

@ -9,6 +9,8 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@ -29,8 +31,35 @@ fun HomeScreen(
onNavigateToDetail: (String, String, String) -> Unit
) {
val uiState by viewModel.uiState.collectAsState()
val focusState by viewModel.focusState.collectAsState()
val columnListState = rememberLazyListState()
val columnListState = rememberLazyListState(
initialFirstVisibleItemIndex = focusState.verticalScrollIndex,
initialFirstVisibleItemScrollOffset = focusState.verticalScrollOffset
)
// Restore scroll position when state is available
LaunchedEffect(focusState.verticalScrollIndex, focusState.verticalScrollOffset) {
if (focusState.verticalScrollIndex > 0 || focusState.verticalScrollOffset > 0) {
columnListState.scrollToItem(
focusState.verticalScrollIndex,
focusState.verticalScrollOffset
)
}
}
// Save scroll position when leaving screen
DisposableEffect(Unit) {
onDispose {
viewModel.saveFocusState(
verticalScrollIndex = columnListState.firstVisibleItemIndex,
verticalScrollOffset = columnListState.firstVisibleItemScrollOffset,
focusedRowIndex = 0, // Basic implementation
focusedItemIndex = 0, // Basic implementation
catalogRowScrollStates = emptyMap() // Will be enhanced with horizontal scroll tracking
)
}
}
Box(
modifier = Modifier
@ -79,6 +108,7 @@ fun HomeScreen(
items = uiState.catalogRows,
key = { _, item -> "${item.addonId}_${item.type}_${item.catalogId}" }
) { index, catalogRow ->
val catalogKey = "${catalogRow.addonId}_${catalogRow.type.toApiString()}_${catalogRow.catalogId}"
CatalogRowSection(
catalogRow = catalogRow,
onItemClick = { id, type, addonBaseUrl ->
@ -92,7 +122,8 @@ fun HomeScreen(
type = catalogRow.type.toApiString()
)
)
}
},
initialScrollIndex = focusState.catalogRowScrollStates[catalogKey] ?: 0
)
}
}

View file

@ -0,0 +1,33 @@
package com.nuvio.tv.ui.screens.home
/**
* Stores focus and scroll state for the HomeScreen to enable proper state restoration
* when navigating back from detail screens.
*/
data class HomeScreenFocusState(
/**
* The index of the first visible item in the main vertical LazyColumn.
*/
val verticalScrollIndex: Int = 0,
/**
* The pixel offset of the first visible item in the vertical scroll.
*/
val verticalScrollOffset: Int = 0,
/**
* Index of the catalog row that had focus when navigating away.
*/
val focusedRowIndex: Int = 0,
/**
* Index of the item within the focused catalog row.
*/
val focusedItemIndex: Int = 0,
/**
* Map of catalog row keys to their horizontal scroll positions.
* Key format: "${addonId}_${type}_${catalogId}"
*/
val catalogRowScrollStates: Map<String, Int> = emptyMap()
)

View file

@ -31,6 +31,9 @@ class HomeViewModel @Inject constructor(
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
private val _focusState = MutableStateFlow(HomeScreenFocusState())
val focusState: StateFlow<HomeScreenFocusState> = _focusState.asStateFlow()
private val catalogsMap = linkedMapOf<String, CatalogRow>()
private val catalogOrder = mutableListOf<String>()
@ -190,4 +193,30 @@ class HomeViewModel @Inject constructor(
private fun CatalogDescriptor.isSearchOnlyCatalog(): Boolean {
return extra.any { extra -> extra.name == "search" && extra.isRequired }
}
/**
* Saves the current focus and scroll state for restoration when returning to this screen.
*/
fun saveFocusState(
verticalScrollIndex: Int,
verticalScrollOffset: Int,
focusedRowIndex: Int,
focusedItemIndex: Int,
catalogRowScrollStates: Map<String, Int>
) {
_focusState.value = HomeScreenFocusState(
verticalScrollIndex = verticalScrollIndex,
verticalScrollOffset = verticalScrollOffset,
focusedRowIndex = focusedRowIndex,
focusedItemIndex = focusedItemIndex,
catalogRowScrollStates = catalogRowScrollStates
)
}
/**
* Clears the saved focus state.
*/
fun clearFocusState() {
_focusState.value = HomeScreenFocusState()
}
}