Fix TV tab selection sticking, scroll restore, and browse header clip.
Keep a single top-nav host so Films/Series stay selected, hoist list/grid state above the detail routes, and add focus padding under browse titles.
This commit is contained in:
parent
056078d6bc
commit
94c04e0381
4 changed files with 63 additions and 25 deletions
|
|
@ -20,8 +20,8 @@ android {
|
|||
applicationId = "nl.vonas.mediacluster.tv"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 90
|
||||
versionName = "0.14.27"
|
||||
versionCode = 91
|
||||
versionName = "0.14.28"
|
||||
buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"")
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ fun BrowseScreen(
|
|||
api: ApiClient,
|
||||
catalogId: String,
|
||||
titleHint: String? = null,
|
||||
gridState: androidx.compose.foundation.lazy.grid.LazyGridState = rememberLazyGridState(),
|
||||
onBack: (() -> Unit)? = null,
|
||||
onAtTopBack: (() -> Unit)? = null,
|
||||
onOpenMovie: (String) -> Unit,
|
||||
|
|
@ -92,9 +93,9 @@ fun BrowseScreen(
|
|||
var continueMenu by remember { mutableStateOf<MediaItem?>(null) }
|
||||
var menuReady by remember { mutableStateOf(false) }
|
||||
val menuFocus = remember { FocusRequester() }
|
||||
val gridState = rememberLazyGridState()
|
||||
val scope = rememberCoroutineScope()
|
||||
val showBrowseTools = !isContinue
|
||||
var lastLoadKey by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
|
||||
suspend fun load(reset: Boolean, jumpLetter: String? = null) {
|
||||
if (reset) {
|
||||
|
|
@ -133,9 +134,14 @@ fun BrowseScreen(
|
|||
}
|
||||
|
||||
LaunchedEffect(catalogId, sort) {
|
||||
val key = "$catalogId|$sort"
|
||||
val catalogOrSortChanged = lastLoadKey != null && lastLoadKey != key
|
||||
load(reset = true, jumpLetter = null)
|
||||
if (catalogOrSortChanged) {
|
||||
gridState.scrollToItem(0)
|
||||
}
|
||||
lastLoadKey = key
|
||||
}
|
||||
|
||||
LaunchedEffect(gridState, items.size, total, listOffset) {
|
||||
snapshotFlow {
|
||||
|
|
@ -259,7 +265,7 @@ fun BrowseScreen(
|
|||
// ~124dp: dichter dan 140 (meer rijen), nog herkenbaar van de bank
|
||||
columns = GridCells.Adaptive(minSize = 124.dp),
|
||||
state = gridState,
|
||||
contentPadding = PaddingValues(start = 4.dp, top = 4.dp, end = 4.dp, bottom = 28.dp),
|
||||
contentPadding = PaddingValues(start = 4.dp, top = 28.dp, end = 4.dp, bottom = 28.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import androidx.compose.ui.graphics.Brush
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
|
|
@ -128,6 +129,11 @@ fun McNav() {
|
|||
val hubTab = remember(hubTabName) {
|
||||
MainTab.entries.firstOrNull { it.name == hubTabName } ?: MainTab.Home
|
||||
}
|
||||
// Scroll-state boven NavHost: blijft behouden bij film/serie/player
|
||||
val homeListState = rememberLazyListState()
|
||||
val moviesGridState = androidx.compose.foundation.lazy.grid.rememberLazyGridState()
|
||||
val seriesGridState = androidx.compose.foundation.lazy.grid.rememberLazyGridState()
|
||||
val watchlistGridState = androidx.compose.foundation.lazy.grid.rememberLazyGridState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
val base = store.apiBase.first()
|
||||
|
|
@ -265,6 +271,10 @@ fun McNav() {
|
|||
api = api,
|
||||
tab = hubTab,
|
||||
onTabChange = { hubTabName = it.name },
|
||||
homeListState = homeListState,
|
||||
moviesGridState = moviesGridState,
|
||||
seriesGridState = seriesGridState,
|
||||
watchlistGridState = watchlistGridState,
|
||||
tvCategoryId = tvCategoryId,
|
||||
onTvCategoryChange = { tvCategoryId = it },
|
||||
tvLastStreamId = tvLastStreamId,
|
||||
|
|
@ -419,6 +429,10 @@ private fun MainHub(
|
|||
api: ApiClient,
|
||||
tab: MainTab,
|
||||
onTabChange: (MainTab) -> Unit,
|
||||
homeListState: androidx.compose.foundation.lazy.LazyListState,
|
||||
moviesGridState: androidx.compose.foundation.lazy.grid.LazyGridState,
|
||||
seriesGridState: androidx.compose.foundation.lazy.grid.LazyGridState,
|
||||
watchlistGridState: androidx.compose.foundation.lazy.grid.LazyGridState,
|
||||
tvCategoryId: String?,
|
||||
onTvCategoryChange: (String?) -> Unit,
|
||||
tvLastStreamId: Int?,
|
||||
|
|
@ -447,7 +461,6 @@ private fun MainHub(
|
|||
val homeContentFocus = remember { FocusRequester() }
|
||||
val exitFocus = remember { FocusRequester() }
|
||||
var exitReady by remember { mutableStateOf(false) }
|
||||
val homeListState = rememberLazyListState()
|
||||
var prevTabName by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val hubScope = rememberCoroutineScope()
|
||||
val activity = LocalContext.current as? Activity
|
||||
|
|
@ -541,8 +554,16 @@ private fun MainHub(
|
|||
focusTopNav()
|
||||
}
|
||||
}
|
||||
// Films/Series/Zoeken: BrowseScreen handelt scroll→top af; hier alleen top-nav → Home
|
||||
BackHandler(enabled = navFocused && tab != MainTab.Home && !exitConfirm) {
|
||||
// Films/Series/…: Back in top-nav → Home (kort uitgeschakeld na tab-wissel tegen race)
|
||||
var navBackToHomeArmed by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(tab, navFocused) {
|
||||
navBackToHomeArmed = false
|
||||
if (navFocused && tab != MainTab.Home && tab != MainTab.Settings) {
|
||||
delay(400)
|
||||
navBackToHomeArmed = true
|
||||
}
|
||||
}
|
||||
BackHandler(enabled = navBackToHomeArmed && navFocused && tab != MainTab.Home && !exitConfirm) {
|
||||
onTabChange(MainTab.Home)
|
||||
focusTopNav()
|
||||
}
|
||||
|
|
@ -581,6 +602,12 @@ private fun MainHub(
|
|||
}
|
||||
|
||||
val homeOverlay = tab == MainTab.Home
|
||||
val density = LocalDensity.current
|
||||
var topNavHeightPx by remember { mutableIntStateOf(0) }
|
||||
val topNavBlockHeight = with(density) {
|
||||
if (topNavHeightPx > 0) topNavHeightPx.toDp()
|
||||
else nl.vonas.mediacluster.tv.ui.components.HomeChrome.TopNavHeight
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HubTopNav(overlay: Boolean) {
|
||||
|
|
@ -660,10 +687,9 @@ private fun MainHub(
|
|||
|
||||
Box(Modifier.fillMaxSize().background(McColors.Bg)) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
// Vaste ruimte voor topnav (niet op Home: daar ligt nav als overlay)
|
||||
if (!homeOverlay) {
|
||||
Box(Modifier.focusRequester(navFocus)) {
|
||||
HubTopNav(overlay = false)
|
||||
}
|
||||
Spacer(Modifier.height(topNavBlockHeight))
|
||||
}
|
||||
AnimatedContent(
|
||||
targetState = tab,
|
||||
|
|
@ -688,6 +714,7 @@ private fun MainHub(
|
|||
)
|
||||
MainTab.Watchlist -> WatchlistScreen(
|
||||
api = api,
|
||||
gridState = watchlistGridState,
|
||||
onAtTopBack = { focusTopNav() },
|
||||
onOpenMovie = onOpenMovie,
|
||||
onOpenSeries = onOpenSeries,
|
||||
|
|
@ -696,6 +723,7 @@ private fun MainHub(
|
|||
api = api,
|
||||
catalogId = movieCatalog,
|
||||
titleHint = movieSubs.firstOrNull { it.id == movieSubId }?.label ?: "Films",
|
||||
gridState = moviesGridState,
|
||||
onAtTopBack = { focusTopNav() },
|
||||
onOpenMovie = onOpenMovie,
|
||||
onOpenSeries = onOpenSeries,
|
||||
|
|
@ -704,6 +732,7 @@ private fun MainHub(
|
|||
api = api,
|
||||
catalogId = seriesCatalog,
|
||||
titleHint = seriesSubs.firstOrNull { it.id == seriesSubId }?.label ?: "Series",
|
||||
gridState = seriesGridState,
|
||||
onAtTopBack = { focusTopNav() },
|
||||
onOpenMovie = onOpenMovie,
|
||||
onOpenSeries = onOpenSeries,
|
||||
|
|
@ -744,15 +773,15 @@ private fun MainHub(
|
|||
}
|
||||
}
|
||||
|
||||
if (homeOverlay) {
|
||||
// Eén vaste TopNav-host: blijft bestaan bij Home↔Films (voorkomt tab-reset)
|
||||
Box(
|
||||
Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.fillMaxWidth()
|
||||
.focusRequester(navFocus),
|
||||
.focusRequester(navFocus)
|
||||
.onSizeChanged { topNavHeightPx = it.height },
|
||||
) {
|
||||
HubTopNav(overlay = true)
|
||||
}
|
||||
HubTopNav(overlay = homeOverlay)
|
||||
}
|
||||
|
||||
if (exitConfirm) {
|
||||
|
|
@ -1824,6 +1853,8 @@ private fun SettingsScreen(
|
|||
@Composable
|
||||
private fun WatchlistScreen(
|
||||
api: ApiClient,
|
||||
gridState: androidx.compose.foundation.lazy.grid.LazyGridState =
|
||||
androidx.compose.foundation.lazy.grid.rememberLazyGridState(),
|
||||
onAtTopBack: () -> Unit,
|
||||
onOpenMovie: (String) -> Unit,
|
||||
onOpenSeries: (String) -> Unit,
|
||||
|
|
@ -1871,6 +1902,7 @@ private fun WatchlistScreen(
|
|||
else -> {
|
||||
androidx.compose.foundation.lazy.grid.LazyVerticalGrid(
|
||||
columns = androidx.compose.foundation.lazy.grid.GridCells.Adaptive(minSize = 124.dp),
|
||||
state = gridState,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp),
|
||||
contentPadding = PaddingValues(top = 28.dp, bottom = 32.dp),
|
||||
|
|
|
|||
|
|
@ -106,12 +106,12 @@ fun TopNavBar(
|
|||
Modifier
|
||||
},
|
||||
)
|
||||
// Semi-transparant of vast: home overlay vs vaste tabs
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.then(
|
||||
if (overlay) {
|
||||
// Semi-transparant: hero zichtbaar, menu blijft leesbaar
|
||||
Modifier.background(
|
||||
Brush.verticalGradient(
|
||||
0f to Color(0xB8000000),
|
||||
|
|
@ -120,7 +120,7 @@ fun TopNavBar(
|
|||
),
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
Modifier.background(McColors.Bg)
|
||||
},
|
||||
)
|
||||
.graphicsLayer { this.alpha = alpha },
|
||||
|
|
@ -232,9 +232,9 @@ fun TopNavBar(
|
|||
if (!profileLabel.isNullOrBlank()) {
|
||||
Text(
|
||||
profileLabel,
|
||||
color = McColors.Brand,
|
||||
color = Color.White,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue