From afb3a0c87171b2442ad4f764a76599bd137dcfd6 Mon Sep 17 00:00:00 2001 From: Jeena Date: Mon, 19 Oct 2026 13:08:00 +0000 Subject: [PATCH] channel page: park the grid unconditionally, tighter header band MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old park ran only while `firstVisibleItemIndex > 0`, but the focus pull that hands the first card its focus leaves the header row at index 0 while slid up by ~96 dp — so the guard never fired and the top of the channel header stayed clipped off screen (#31 review). scrollToItem(0) re-anchors both index and offset. The banner band drops to 116 dp with a 40 dp description strip, so the whole header is under one card row: the grid never reflows around it and the header stays fully on screen when the first card takes focus. Addresses #27, #31. Co-Authored-By: Claude --- .../ui/screens/list/VideoListScreen.kt | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/net/jeena/jtubetv/ui/screens/list/VideoListScreen.kt b/app/src/main/java/net/jeena/jtubetv/ui/screens/list/VideoListScreen.kt index ff7a4e7..dcd57d3 100644 --- a/app/src/main/java/net/jeena/jtubetv/ui/screens/list/VideoListScreen.kt +++ b/app/src/main/java/net/jeena/jtubetv/ui/screens/list/VideoListScreen.kt @@ -92,10 +92,14 @@ fun VideoListScreen( // handing the first card the focus animates the grid up (a focused card // that sits partly off-screen is pulled into view), and this effect then // snapped it back down — the jump that was still reported on the channel - // page when the first video got focus (#27 review). Doing the park first, - // and only while the grid is actually displaced, leaves nothing to undo. + // page when the first video got focus (#27 review). + // The park has to be unconditional, and has to reset the row's offset, + // not just its index: the focus pull leaves the header row at index 0 but + // slid up by ~96 dp, so a guard on firstVisibleItemIndex > 0 never fires + // in that state and the top of the channel header stays clipped off + // screen (#31 review). scrollToItem(0) re-anchors both. LaunchedEffect(items.isNotEmpty(), source) { - if (items.isNotEmpty() && gridState.firstVisibleItemIndex > 0) { + if (items.isNotEmpty()) { runCatching { gridState.scrollToItem(0) } } } @@ -436,12 +440,14 @@ private fun ChannelHeader( .clip(androidx.compose.foundation.shape.RoundedCornerShape(12.dp)) .background(Color.Black), ) { - // Banner area: a fixed height with the identity + stats anchored to its - // bottom; the description sits in the black strip below it. + // Banner area: 116 dp, with the identity + stats anchored to its bottom; the + // description sits in the black strip below it. The whole band is 160 dp + // (4 + 116 + 40) — under one card row — so the grid never reflows around it + // and the band stays fully on screen when the first card takes focus (#31). Box( Modifier .fillMaxWidth() - .height(178.dp) + .height(116.dp) .background(androidx.compose.ui.graphics.Brush.linearGradient(accent)), ) { if (banner != null) { @@ -563,7 +569,7 @@ private fun ChannelHeader( Box( Modifier .fillMaxWidth() - .height(42.dp) + .height(40.dp) .padding(start = 16.dp, end = 16.dp, top = 6.dp), ) { if (description != null) {