Keep TV detail hero visible on initial focus

This commit is contained in:
2026-04-06 19:54:01 +02:00
parent 0870e8aee4
commit 60291dc78e
2 changed files with 80 additions and 7 deletions

View File

@@ -0,0 +1,49 @@
package hu.bbara.purefin.common.ui.components
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TvMediaDetailBringIntoViewSpecTest {
@Test
fun returnsZero_whenFocusedItemIsAlreadyFullyVisible() {
assertEquals(
0f,
TvMediaDetailBringIntoViewSpec.calculateScrollDistance(
offset = 320f,
size = 52f,
containerSize = 1080f
),
0.0001f
)
}
@Test
fun scrollsOnlyEnoughToRevealFocusedItemBelowViewport() {
assertEquals(
36f,
TvMediaDetailBringIntoViewSpec.calculateScrollDistance(
offset = 1064f,
size = 52f,
containerSize = 1080f
),
0.0001f
)
}
@Test
fun scrollsBackJustToTopEdge_whenFocusedItemMovesAboveViewport() {
assertEquals(
-24f,
TvMediaDetailBringIntoViewSpec.calculateScrollDistance(
offset = -24f,
size = 52f,
containerSize = 1080f
),
0.0001f
)
}
}

View File

@@ -1,6 +1,10 @@
@file:OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class)
package hu.bbara.purefin.common.ui.components package hu.bbara.purefin.common.ui.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.BringIntoViewSpec
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
@@ -17,6 +21,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -35,6 +40,23 @@ private val MediaDetailHeaderTopPadding = 104.dp
private val MediaDetailHeaderBottomPadding = 36.dp private val MediaDetailHeaderBottomPadding = 36.dp
private const val MediaDetailBodyImageWidthFraction = 0.66f private const val MediaDetailBodyImageWidthFraction = 0.66f
internal val TvMediaDetailBringIntoViewSpec: BringIntoViewSpec =
object : BringIntoViewSpec {
override fun calculateScrollDistance(
offset: Float,
size: Float,
containerSize: Float,
): Float {
val trailingEdge = offset + size
return when {
offset < 0f -> offset
trailingEdge > containerSize -> trailingEdge - containerSize
else -> 0f
}
}
}
@Composable @Composable
internal fun TvMediaDetailScaffold( internal fun TvMediaDetailScaffold(
resetScrollKey: Any, resetScrollKey: Any,
@@ -49,6 +71,7 @@ internal fun TvMediaDetailScaffold(
listState.scrollToItem(0) listState.scrollToItem(0)
} }
CompositionLocalProvider(LocalBringIntoViewSpec provides TvMediaDetailBringIntoViewSpec) {
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = modifier modifier = modifier
@@ -57,6 +80,7 @@ internal fun TvMediaDetailScaffold(
) { ) {
bodyContent(contentPadding) bodyContent(contentPadding)
} }
}
} }
@Composable @Composable