mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
Tighten TV home hero and bring-in-view offset
This commit is contained in:
@@ -43,7 +43,7 @@ fun TvHomeScreen(
|
|||||||
) {
|
) {
|
||||||
TvFocusedItemHero(
|
TvFocusedItemHero(
|
||||||
item = focusedMediaUiModel.value,
|
item = focusedMediaUiModel.value,
|
||||||
height = 250.dp
|
height = 220.dp
|
||||||
)
|
)
|
||||||
TvHomeContent(
|
TvHomeContent(
|
||||||
libraries = libraries,
|
libraries = libraries,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import androidx.compose.foundation.gestures.BringIntoViewSpec
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
internal val TvHomeFocusedItemTopOffset = 56.dp
|
internal val TvHomeFocusedItemTopOffset = 42.dp
|
||||||
internal val TvHomeBringIntoViewTrailingSpace = 120.dp
|
internal val TvHomeBringIntoViewTrailingSpace = 120.dp
|
||||||
|
|
||||||
private const val TvHomeRowPivotParentFraction = 0.3f
|
private const val TvHomeRowPivotParentFraction = 0.3f
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package hu.bbara.purefin.ui.screen.home.components
|
package hu.bbara.purefin.ui.screen.home.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
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.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import hu.bbara.purefin.core.ui.model.MediaUiModel
|
import hu.bbara.purefin.core.ui.model.MediaUiModel
|
||||||
@@ -37,67 +40,71 @@ fun TvHomeContent(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
val topOffsetPx = with(LocalDensity.current) { TvHomeFocusedItemTopOffset.toPx() }
|
||||||
val hasContinueWatching = continueWatching.isNotEmpty()
|
val hasContinueWatching = continueWatching.isNotEmpty()
|
||||||
val hasNextUp = nextUp.isNotEmpty()
|
val hasNextUp = nextUp.isNotEmpty()
|
||||||
val hasVisibleContent = hasContinueWatching || hasNextUp
|
val hasVisibleContent = hasContinueWatching || hasNextUp
|
||||||
val initialFocusRequester = remember { FocusRequester() }
|
val initialFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
CompositionLocalProvider(
|
||||||
LazyColumn(
|
LocalBringIntoViewSpec provides tvHomeColumnBringIntoViewSpec(topOffsetPx)
|
||||||
modifier = modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(scheme.background)
|
|
||||||
.testTag(TvHomeContentViewportTag),
|
|
||||||
contentPadding = contentPadding
|
|
||||||
) {
|
) {
|
||||||
item(key = "tv-home-top-spacer") {
|
LazyColumn(
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
modifier = modifier
|
||||||
}
|
.fillMaxSize()
|
||||||
if (hasContinueWatching) {
|
.background(scheme.background)
|
||||||
item(key = "tv-home-continue-watching") {
|
.testTag(TvHomeContentViewportTag),
|
||||||
TvContinueWatchingSection(
|
contentPadding = contentPadding
|
||||||
items = continueWatching,
|
) {
|
||||||
|
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,
|
onFocusedItem = onMediaFocused,
|
||||||
onMovieSelected = onMovieSelected,
|
|
||||||
onEpisodeSelected = onEpisodeSelected,
|
|
||||||
firstItemFocusRequester = initialFocusRequester,
|
firstItemFocusRequester = initialFocusRequester,
|
||||||
firstItemTestTag = TvHomeInitialFocusTag,
|
onMovieSelected = onMovieSelected,
|
||||||
rowTestTag = TvHomeContinueWatchingRowTag
|
onSeriesSelected = onSeriesSelected,
|
||||||
|
onEpisodeSelected = onEpisodeSelected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNextUp) {
|
if (hasVisibleContent) {
|
||||||
item(key = "tv-home-next-up") {
|
item(key = "tv-home-trailing-space") {
|
||||||
TvNextUpSection(
|
Spacer(modifier = Modifier.height(TvHomeBringIntoViewTrailingSpace))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user