diff --git a/app/src/main/java/com/nuvio/tv/ui/components/CatalogRowSection.kt b/app/src/main/java/com/nuvio/tv/ui/components/CatalogRowSection.kt index 63e0cb83..58bdddb7 100644 --- a/app/src/main/java/com/nuvio/tv/ui/components/CatalogRowSection.kt +++ b/app/src/main/java/com/nuvio/tv/ui/components/CatalogRowSection.kt @@ -16,8 +16,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.tv.material3.ExperimentalTvMaterial3Api @@ -33,7 +37,9 @@ fun CatalogRowSection( onItemClick: (String, String, String) -> Unit, onLoadMore: () -> Unit, modifier: Modifier = Modifier, - initialScrollIndex: Int = 0 + initialScrollIndex: Int = 0, + focusedItemIndex: Int = -1, + onItemFocused: (itemIndex: Int) -> Unit = {} ) { val listState = rememberLazyListState( initialFirstVisibleItemIndex = initialScrollIndex @@ -46,6 +52,22 @@ fun CatalogRowSection( } } + // Track which item has focus + var currentFocusedIndex by remember { mutableStateOf(-1) } + val itemFocusRequester = remember { FocusRequester() } + + // Restore focus to specific item if requested + LaunchedEffect(focusedItemIndex) { + if (focusedItemIndex >= 0 && focusedItemIndex < catalogRow.items.size) { + kotlinx.coroutines.delay(100) // Wait for composition + try { + itemFocusRequester.requestFocus() + } catch (e: IllegalStateException) { + // Item not yet composed, ignore + } + } + } + val shouldLoadMore by remember { derivedStateOf { val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 @@ -95,7 +117,13 @@ fun CatalogRowSection( ContentCard( item = item, onClick = { onItemClick(item.id, item.type.toApiString(), catalogRow.addonBaseUrl) }, - modifier = Modifier + modifier = Modifier.onFocusChanged { focusState -> + if (focusState.isFocused) { + currentFocusedIndex = index + onItemFocused(index) + } + }, + focusRequester = if (index == focusedItemIndex) itemFocusRequester else null ) } diff --git a/app/src/main/java/com/nuvio/tv/ui/components/ContentCard.kt b/app/src/main/java/com/nuvio/tv/ui/components/ContentCard.kt index 25e9c4b8..fde3bfd7 100644 --- a/app/src/main/java/com/nuvio/tv/ui/components/ContentCard.kt +++ b/app/src/main/java/com/nuvio/tv/ui/components/ContentCard.kt @@ -18,6 +18,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextOverflow @@ -40,6 +42,7 @@ import com.nuvio.tv.ui.theme.NuvioTheme fun ContentCard( item: MetaPreview, modifier: Modifier = Modifier, + focusRequester: FocusRequester? = null, onClick: () -> Unit = {} ) { var isFocused by remember { mutableStateOf(false) } @@ -62,6 +65,10 @@ fun ContentCard( onClick = onClick, modifier = Modifier .fillMaxWidth() + .then( + if (focusRequester != null) Modifier.focusRequester(focusRequester) + else Modifier + ) .onFocusChanged { isFocused = it.isFocused }, shape = CardDefaults.shape( shape = RoundedCornerShape(8.dp) diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeScreen.kt b/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeScreen.kt index 5b2976c5..fb098313 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeScreen.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeScreen.kt @@ -13,6 +13,9 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -48,15 +51,20 @@ fun HomeScreen( } } + // Track which row and item have focus + var currentFocusedRowIndex by remember { mutableStateOf(focusState.focusedRowIndex) } + var currentFocusedItemIndex by remember { mutableStateOf(focusState.focusedItemIndex) } + val catalogRowScrollStates = remember { mutableMapOf() } + // 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 + focusedRowIndex = currentFocusedRowIndex, + focusedItemIndex = currentFocusedItemIndex, + catalogRowScrollStates = catalogRowScrollStates.toMap() ) } } @@ -109,6 +117,9 @@ fun HomeScreen( key = { _, item -> "${item.addonId}_${item.type}_${item.catalogId}" } ) { index, catalogRow -> val catalogKey = "${catalogRow.addonId}_${catalogRow.type.toApiString()}_${catalogRow.catalogId}" + val shouldRestoreFocus = index == focusState.focusedRowIndex + val focusedItemIndex = if (shouldRestoreFocus) focusState.focusedItemIndex else -1 + CatalogRowSection( catalogRow = catalogRow, onItemClick = { id, type, addonBaseUrl -> @@ -123,7 +134,13 @@ fun HomeScreen( ) ) }, - initialScrollIndex = focusState.catalogRowScrollStates[catalogKey] ?: 0 + initialScrollIndex = focusState.catalogRowScrollStates[catalogKey] ?: 0, + focusedItemIndex = focusedItemIndex, + onItemFocused = { itemIndex -> + currentFocusedRowIndex = index + currentFocusedItemIndex = itemIndex + catalogRowScrollStates[catalogKey] = itemIndex + } ) } }