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.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import hu.bbara.purefin.common.ui.components.MediaDetailsTopBar
@Composable
internal fun EpisodeTopBar(
onBack: () -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
downFocusRequester: FocusRequester? = null
) {
MediaDetailsTopBar(
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.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@@ -94,21 +95,21 @@ private fun EpisodeScreenInternal(
playFocusRequester.requestFocus()
}
LazyColumn(
Box(
modifier = modifier
.fillMaxSize()
.background(scheme.background)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Box {
MediaHero(
imageUrl = episode.heroImageUrl,
backgroundColor = scheme.background,
heightFraction = 0.30f,
modifier = Modifier.fillMaxWidth()
)
EpisodeTopBar(onBack = onBack)
}
}
item {
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.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import hu.bbara.purefin.common.ui.components.MediaDetailsTopBar
@Composable
internal fun MovieTopBar(
onBack: () -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
downFocusRequester: FocusRequester? = null
) {
MediaDetailsTopBar(
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.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@@ -76,21 +77,21 @@ private fun MovieScreenInternal(
playFocusRequester.requestFocus()
}
LazyColumn(
Box(
modifier = modifier
.fillMaxSize()
.background(scheme.background)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Box {
MediaHero(
imageUrl = movie.heroImageUrl,
backgroundColor = scheme.background,
heightFraction = 0.30f,
modifier = Modifier.fillMaxWidth()
)
MovieTopBar(onBack = onBack)
}
}
item {
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.Modifier
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.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
@@ -62,11 +64,13 @@ import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel
@Composable
internal fun SeriesTopBar(
onBack: () -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
downFocusRequester: FocusRequester? = null
) {
MediaDetailsTopBar(
onBack = onBack,
modifier = modifier
modifier = modifier,
downFocusRequester = downFocusRequester
)
}
@@ -88,6 +92,7 @@ internal fun SeasonTabs(
seasons: List<Season>,
selectedSeason: Season?,
modifier: Modifier = Modifier,
firstItemFocusRequester: FocusRequester? = null,
onSelect: (Season) -> Unit
) {
Row(
@@ -96,11 +101,16 @@ internal fun SeasonTabs(
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
seasons.forEach { season ->
seasons.forEachIndexed { index, season ->
SeasonTab(
name = season.name,
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.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -71,22 +73,23 @@ private fun SeriesScreenInternal(
return series.seasons.first()
}
val selectedSeason = remember { mutableStateOf<Season>(getDefaultSeason()) }
val firstContentFocusRequester = remember { FocusRequester() }
LazyColumn(
Box(
modifier = modifier
.fillMaxSize()
.background(scheme.background)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Box {
MediaHero(
imageUrl = series.heroImageUrl,
heightFraction = 0.30f,
backgroundColor = scheme.background,
modifier = Modifier.fillMaxWidth()
)
SeriesTopBar(onBack = onBack)
}
}
item {
Column(modifier = hPad) {
@@ -117,6 +120,7 @@ private fun SeriesScreenInternal(
SeasonTabs(
seasons = series.seasons,
selectedSeason = selectedSeason.value,
firstItemFocusRequester = firstContentFocusRequester,
onSelect = { selectedSeason.value = it },
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.unit.dp
@Composable
@@ -19,8 +21,15 @@ fun MediaDetailsTopBar(
onBack: () -> Unit,
modifier: Modifier = Modifier,
onCastClick: () -> Unit = {},
onMoreClick: () -> Unit = {}
onMoreClick: () -> Unit = {},
downFocusRequester: FocusRequester? = null
) {
val downModifier = if (downFocusRequester != null) {
Modifier.focusProperties { down = downFocusRequester }
} else {
Modifier
}
Row(
modifier = modifier
.fillMaxWidth()
@@ -32,11 +41,22 @@ fun MediaDetailsTopBar(
GhostIconButton(
icon = Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = "Back",
onClick = onBack
onClick = onBack,
modifier = downModifier
)
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
GhostIconButton(icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = onCastClick)
GhostIconButton(icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = onMoreClick)
GhostIconButton(
icon = Icons.Outlined.Cast,
contentDescription = "Cast",
onClick = onCastClick,
modifier = downModifier
)
GhostIconButton(
icon = Icons.Outlined.MoreVert,
contentDescription = "More",
onClick = onMoreClick,
modifier = downModifier
)
}
}
}