From 52a51b1b28d201544a1167563afc11a540c05bb3 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Sun, 19 Apr 2026 20:07:08 +0200 Subject: [PATCH] Tighten TV home hero and bring-in-view offset --- .../purefin/ui/screen/home/TvHomeScreen.kt | 2 +- .../home/components/TvHomeBringIntoView.kt | 2 +- .../screen/home/components/TvHomeContent.kt | 107 ++++++++++-------- .../components/TvHomeBringIntoViewSpecTest.kt | 49 ++++++++ 4 files changed, 108 insertions(+), 52 deletions(-) create mode 100644 app-tv/src/test/java/hu/bbara/purefin/ui/screen/home/components/TvHomeBringIntoViewSpecTest.kt diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/TvHomeScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/TvHomeScreen.kt index 1d3e16d0..e4104f16 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/TvHomeScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/TvHomeScreen.kt @@ -43,7 +43,7 @@ fun TvHomeScreen( ) { TvFocusedItemHero( item = focusedMediaUiModel.value, - height = 250.dp + height = 220.dp ) TvHomeContent( libraries = libraries, diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/components/TvHomeBringIntoView.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/components/TvHomeBringIntoView.kt index d9366313..891adae9 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/components/TvHomeBringIntoView.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/components/TvHomeBringIntoView.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.gestures.BringIntoViewSpec import androidx.compose.ui.unit.dp import kotlin.math.abs -internal val TvHomeFocusedItemTopOffset = 56.dp +internal val TvHomeFocusedItemTopOffset = 42.dp internal val TvHomeBringIntoViewTrailingSpace = 120.dp private const val TvHomeRowPivotParentFraction = 0.3f diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/components/TvHomeContent.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/components/TvHomeContent.kt index 43e01061..d6995869 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/components/TvHomeContent.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/home/components/TvHomeContent.kt @@ -3,6 +3,7 @@ package hu.bbara.purefin.ui.screen.home.components import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.LocalBringIntoViewSpec import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -11,9 +12,11 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import hu.bbara.purefin.core.ui.model.MediaUiModel @@ -37,67 +40,71 @@ fun TvHomeContent( modifier: Modifier = Modifier, ) { val scheme = MaterialTheme.colorScheme + val topOffsetPx = with(LocalDensity.current) { TvHomeFocusedItemTopOffset.toPx() } val hasContinueWatching = continueWatching.isNotEmpty() val hasNextUp = nextUp.isNotEmpty() val hasVisibleContent = hasContinueWatching || hasNextUp val initialFocusRequester = remember { FocusRequester() } - - LazyColumn( - modifier = modifier - .fillMaxSize() - .background(scheme.background) - .testTag(TvHomeContentViewportTag), - contentPadding = contentPadding + CompositionLocalProvider( + LocalBringIntoViewSpec provides tvHomeColumnBringIntoViewSpec(topOffsetPx) ) { - item(key = "tv-home-top-spacer") { - Spacer(modifier = Modifier.height(8.dp)) - } - if (hasContinueWatching) { - item(key = "tv-home-continue-watching") { - TvContinueWatchingSection( - items = continueWatching, + LazyColumn( + modifier = modifier + .fillMaxSize() + .background(scheme.background) + .testTag(TvHomeContentViewportTag), + contentPadding = contentPadding + ) { + item(key = "tv-home-top-spacer") { + Spacer(modifier = Modifier.height(8.dp)) + } + if (hasContinueWatching) { + item(key = "tv-home-continue-watching") { + TvContinueWatchingSection( + items = continueWatching, + onFocusedItem = onMediaFocused, + onMovieSelected = onMovieSelected, + onEpisodeSelected = onEpisodeSelected, + firstItemFocusRequester = initialFocusRequester, + firstItemTestTag = TvHomeInitialFocusTag, + rowTestTag = TvHomeContinueWatchingRowTag + ) + } + } + + if (hasNextUp) { + item(key = "tv-home-next-up") { + TvNextUpSection( + items = nextUp, + onFocusedItem = onMediaFocused, + onEpisodeSelected = onEpisodeSelected, + firstItemFocusRequester = initialFocusRequester.takeIf { !hasContinueWatching }, + firstItemTestTag = TvHomeInitialFocusTag.takeIf { !hasContinueWatching }, + rowTestTag = TvHomeNextUpRowTag + ) + } + } + + itemsIndexed( + items = libraries, + key = { _, library -> library.id } + ) { _, library -> + TvLibraryPosterSection( + title = library.name, + items = libraryContent[library.id].orEmpty(), onFocusedItem = onMediaFocused, - onMovieSelected = onMovieSelected, - onEpisodeSelected = onEpisodeSelected, firstItemFocusRequester = initialFocusRequester, - firstItemTestTag = TvHomeInitialFocusTag, - rowTestTag = TvHomeContinueWatchingRowTag + onMovieSelected = onMovieSelected, + onSeriesSelected = onSeriesSelected, + onEpisodeSelected = onEpisodeSelected ) } - } - if (hasNextUp) { - item(key = "tv-home-next-up") { - TvNextUpSection( - items = nextUp, - onFocusedItem = onMediaFocused, - onEpisodeSelected = onEpisodeSelected, - firstItemFocusRequester = initialFocusRequester.takeIf { !hasContinueWatching }, - firstItemTestTag = TvHomeInitialFocusTag.takeIf { !hasContinueWatching }, - rowTestTag = TvHomeNextUpRowTag - ) - } - } - - itemsIndexed( - items = libraries, - key = { _, library -> library.id } - ) { index, library -> - TvLibraryPosterSection( - title = library.name, - items = libraryContent[library.id].orEmpty(), - onFocusedItem = onMediaFocused, - firstItemFocusRequester = initialFocusRequester, - onMovieSelected = onMovieSelected, - onSeriesSelected = onSeriesSelected, - onEpisodeSelected = onEpisodeSelected - ) - } - - if (hasVisibleContent) { - item(key = "tv-home-trailing-space") { - Spacer(modifier = Modifier.height(TvHomeBringIntoViewTrailingSpace)) + if (hasVisibleContent) { + item(key = "tv-home-trailing-space") { + Spacer(modifier = Modifier.height(TvHomeBringIntoViewTrailingSpace)) + } } } } diff --git a/app-tv/src/test/java/hu/bbara/purefin/ui/screen/home/components/TvHomeBringIntoViewSpecTest.kt b/app-tv/src/test/java/hu/bbara/purefin/ui/screen/home/components/TvHomeBringIntoViewSpecTest.kt new file mode 100644 index 00000000..e517d2ba --- /dev/null +++ b/app-tv/src/test/java/hu/bbara/purefin/ui/screen/home/components/TvHomeBringIntoViewSpecTest.kt @@ -0,0 +1,49 @@ +package hu.bbara.purefin.ui.screen.home.components + +import org.junit.Assert.assertEquals +import org.junit.Test + +class TvHomeBringIntoViewSpecTest { + + private val topOffsetPx = 56f + private val spec = tvHomeColumnBringIntoViewSpec(topOffsetPx) + + @Test + fun returnsZero_whenFocusedItemAlreadyAtTopOffset() { + assertEquals( + 0f, + spec.calculateScrollDistance( + offset = topOffsetPx, + size = 52f, + containerSize = 240f + ), + 0.0001f + ) + } + + @Test + fun scrollsDownToTopOffset_whenFocusedItemIsBelowTarget() { + assertEquals( + 64f, + spec.calculateScrollDistance( + offset = 120f, + size = 52f, + containerSize = 240f + ), + 0.0001f + ) + } + + @Test + fun scrollsUpToTopOffset_whenFocusedItemIsAboveTarget() { + assertEquals( + -32f, + spec.calculateScrollDistance( + offset = 24f, + size = 52f, + containerSize = 240f + ), + 0.0001f + ) + } +}