Fix TV detail top bar focus navigation

This commit is contained in:
2026-03-28 16:58:37 +01:00
parent cfff7c6403
commit 6de42dc65b
7 changed files with 262 additions and 202 deletions

View File

@@ -2,15 +2,18 @@ package hu.bbara.purefin.app.content.episode
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import hu.bbara.purefin.common.ui.components.MediaDetailsTopBar import hu.bbara.purefin.common.ui.components.MediaDetailsTopBar
@Composable @Composable
internal fun EpisodeTopBar( internal fun EpisodeTopBar(
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
downFocusRequester: FocusRequester? = null
) { ) {
MediaDetailsTopBar( MediaDetailsTopBar(
onBack = onBack, onBack = onBack,
modifier = modifier modifier = modifier,
downFocusRequester = downFocusRequester
) )
} }

View File

@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
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.focus.focusRequester import androidx.compose.ui.focus.focusRequester
@@ -94,21 +95,21 @@ private fun EpisodeScreenInternal(
playFocusRequester.requestFocus() playFocusRequester.requestFocus()
} }
LazyColumn( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.background(scheme.background) .background(scheme.background)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) { ) {
item { item {
Box {
MediaHero( MediaHero(
imageUrl = episode.heroImageUrl, imageUrl = episode.heroImageUrl,
backgroundColor = scheme.background, backgroundColor = scheme.background,
heightFraction = 0.30f, heightFraction = 0.30f,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
EpisodeTopBar(onBack = onBack)
}
} }
item { item {
Column(modifier = hPad) { Column(modifier = hPad) {
@@ -188,4 +189,10 @@ private fun EpisodeScreenInternal(
} }
} }
} }
EpisodeTopBar(
onBack = onBack,
downFocusRequester = playFocusRequester,
modifier = Modifier.align(Alignment.TopStart)
)
}
} }

View File

@@ -2,15 +2,18 @@ package hu.bbara.purefin.app.content.movie
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import hu.bbara.purefin.common.ui.components.MediaDetailsTopBar import hu.bbara.purefin.common.ui.components.MediaDetailsTopBar
@Composable @Composable
internal fun MovieTopBar( internal fun MovieTopBar(
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
downFocusRequester: FocusRequester? = null
) { ) {
MediaDetailsTopBar( MediaDetailsTopBar(
onBack = onBack, onBack = onBack,
modifier = modifier modifier = modifier,
downFocusRequester = downFocusRequester
) )
} }

View File

@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
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.focus.focusRequester import androidx.compose.ui.focus.focusRequester
@@ -76,21 +77,21 @@ private fun MovieScreenInternal(
playFocusRequester.requestFocus() playFocusRequester.requestFocus()
} }
LazyColumn( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.background(scheme.background) .background(scheme.background)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) { ) {
item { item {
Box {
MediaHero( MediaHero(
imageUrl = movie.heroImageUrl, imageUrl = movie.heroImageUrl,
backgroundColor = scheme.background, backgroundColor = scheme.background,
heightFraction = 0.30f, heightFraction = 0.30f,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
MovieTopBar(onBack = onBack)
}
} }
item { item {
Column(modifier = hPad) { Column(modifier = hPad) {
@@ -163,4 +164,10 @@ private fun MovieScreenInternal(
} }
} }
} }
MovieTopBar(
onBack = onBack,
downFocusRequester = playFocusRequester,
modifier = Modifier.align(Alignment.TopStart)
)
}
} }

View File

@@ -38,6 +38,8 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
@@ -62,11 +64,13 @@ import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel
@Composable @Composable
internal fun SeriesTopBar( internal fun SeriesTopBar(
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
downFocusRequester: FocusRequester? = null
) { ) {
MediaDetailsTopBar( MediaDetailsTopBar(
onBack = onBack, onBack = onBack,
modifier = modifier modifier = modifier,
downFocusRequester = downFocusRequester
) )
} }
@@ -88,6 +92,7 @@ internal fun SeasonTabs(
seasons: List<Season>, seasons: List<Season>,
selectedSeason: Season?, selectedSeason: Season?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
firstItemFocusRequester: FocusRequester? = null,
onSelect: (Season) -> Unit onSelect: (Season) -> Unit
) { ) {
Row( Row(
@@ -96,11 +101,16 @@ internal fun SeasonTabs(
.horizontalScroll(rememberScrollState()), .horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(20.dp) horizontalArrangement = Arrangement.spacedBy(20.dp)
) { ) {
seasons.forEach { season -> seasons.forEachIndexed { index, season ->
SeasonTab( SeasonTab(
name = season.name, name = season.name,
isSelected = season == selectedSeason, isSelected = season == selectedSeason,
onSelect = { onSelect(season) } onSelect = { onSelect(season) },
modifier = if (index == 0 && firstItemFocusRequester != null) {
Modifier.focusRequester(firstItemFocusRequester)
} else {
Modifier
}
) )
} }
} }

View File

@@ -16,7 +16,9 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -71,22 +73,23 @@ private fun SeriesScreenInternal(
return series.seasons.first() return series.seasons.first()
} }
val selectedSeason = remember { mutableStateOf<Season>(getDefaultSeason()) } val selectedSeason = remember { mutableStateOf<Season>(getDefaultSeason()) }
val firstContentFocusRequester = remember { FocusRequester() }
LazyColumn( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.background(scheme.background) .background(scheme.background)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) { ) {
item { item {
Box {
MediaHero( MediaHero(
imageUrl = series.heroImageUrl, imageUrl = series.heroImageUrl,
heightFraction = 0.30f, heightFraction = 0.30f,
backgroundColor = scheme.background, backgroundColor = scheme.background,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
SeriesTopBar(onBack = onBack)
}
} }
item { item {
Column(modifier = hPad) { Column(modifier = hPad) {
@@ -117,6 +120,7 @@ private fun SeriesScreenInternal(
SeasonTabs( SeasonTabs(
seasons = series.seasons, seasons = series.seasons,
selectedSeason = selectedSeason.value, selectedSeason = selectedSeason.value,
firstItemFocusRequester = firstContentFocusRequester,
onSelect = { selectedSeason.value = it }, onSelect = { selectedSeason.value = it },
modifier = hPad modifier = hPad
) )
@@ -144,4 +148,10 @@ private fun SeriesScreenInternal(
} }
} }
} }
SeriesTopBar(
onBack = onBack,
downFocusRequester = firstContentFocusRequester,
modifier = Modifier.align(Alignment.TopStart)
)
}
} }

View File

@@ -12,6 +12,8 @@ import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
@@ -19,8 +21,15 @@ fun MediaDetailsTopBar(
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onCastClick: () -> Unit = {}, onCastClick: () -> Unit = {},
onMoreClick: () -> Unit = {} onMoreClick: () -> Unit = {},
downFocusRequester: FocusRequester? = null
) { ) {
val downModifier = if (downFocusRequester != null) {
Modifier.focusProperties { down = downFocusRequester }
} else {
Modifier
}
Row( Row(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
@@ -32,11 +41,22 @@ fun MediaDetailsTopBar(
GhostIconButton( GhostIconButton(
icon = Icons.AutoMirrored.Outlined.ArrowBack, icon = Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = "Back", contentDescription = "Back",
onClick = onBack onClick = onBack,
modifier = downModifier
) )
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
GhostIconButton(icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = onCastClick) GhostIconButton(
GhostIconButton(icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = onMoreClick) icon = Icons.Outlined.Cast,
contentDescription = "Cast",
onClick = onCastClick,
modifier = downModifier
)
GhostIconButton(
icon = Icons.Outlined.MoreVert,
contentDescription = "More",
onClick = onMoreClick,
modifier = downModifier
)
} }
} }
} }