mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
Fix TV detail top bar focus navigation
This commit is contained in:
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,98 +95,104 @@ private fun EpisodeScreenInternal(
|
|||||||
playFocusRequester.requestFocus()
|
playFocusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(scheme.background)
|
.background(scheme.background)
|
||||||
) {
|
) {
|
||||||
item {
|
LazyColumn(
|
||||||
Box {
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
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 {
|
|
||||||
Column(modifier = hPad) {
|
|
||||||
Text(
|
|
||||||
text = episode.title,
|
|
||||||
color = scheme.onBackground,
|
|
||||||
fontSize = 32.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
lineHeight = 38.sp
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = "Episode ${episode.index}",
|
|
||||||
color = scheme.onBackground,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
FlowRow(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
MediaMetaChip(text = episode.releaseDate)
|
|
||||||
MediaMetaChip(text = episode.rating)
|
|
||||||
MediaMetaChip(text = episode.runtime)
|
|
||||||
MediaMetaChip(
|
|
||||||
text = episode.format,
|
|
||||||
background = scheme.primary.copy(alpha = 0.2f),
|
|
||||||
border = scheme.primary.copy(alpha = 0.3f),
|
|
||||||
textColor = scheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
MediaSynopsis(
|
|
||||||
synopsis = episode.synopsis,
|
|
||||||
modifier = hPad
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Row(modifier = hPad) {
|
|
||||||
MediaResumeButton(
|
|
||||||
text = if (episode.progress == null) "Play" else "Resume",
|
|
||||||
progress = episode.progress?.div(100)?.toFloat() ?: 0f,
|
|
||||||
onClick = onPlay,
|
|
||||||
modifier = Modifier.sizeIn(maxWidth = 200.dp).focusRequester(playFocusRequester)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
MediaPlaybackSettings(
|
|
||||||
backgroundColor = scheme.surface,
|
|
||||||
foregroundColor = scheme.onSurface,
|
|
||||||
audioTrack = "ENG",
|
|
||||||
subtitles = "ENG",
|
|
||||||
modifier = hPad
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (episode.cast.isNotEmpty()) {
|
|
||||||
item {
|
item {
|
||||||
Column(modifier = hPad) {
|
Column(modifier = hPad) {
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = "Cast",
|
text = episode.title,
|
||||||
color = scheme.onBackground,
|
color = scheme.onBackground,
|
||||||
fontSize = 18.sp,
|
fontSize = 32.sp,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold,
|
||||||
|
lineHeight = 38.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "Episode ${episode.index}",
|
||||||
|
color = scheme.onBackground,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
MediaCastRow(cast = episode.cast)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
FlowRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
MediaMetaChip(text = episode.releaseDate)
|
||||||
|
MediaMetaChip(text = episode.rating)
|
||||||
|
MediaMetaChip(text = episode.runtime)
|
||||||
|
MediaMetaChip(
|
||||||
|
text = episode.format,
|
||||||
|
background = scheme.primary.copy(alpha = 0.2f),
|
||||||
|
border = scheme.primary.copy(alpha = 0.3f),
|
||||||
|
textColor = scheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
MediaSynopsis(
|
||||||
|
synopsis = episode.synopsis,
|
||||||
|
modifier = hPad
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Row(modifier = hPad) {
|
||||||
|
MediaResumeButton(
|
||||||
|
text = if (episode.progress == null) "Play" else "Resume",
|
||||||
|
progress = episode.progress?.div(100)?.toFloat() ?: 0f,
|
||||||
|
onClick = onPlay,
|
||||||
|
modifier = Modifier.sizeIn(maxWidth = 200.dp).focusRequester(playFocusRequester)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
MediaPlaybackSettings(
|
||||||
|
backgroundColor = scheme.surface,
|
||||||
|
foregroundColor = scheme.onSurface,
|
||||||
|
audioTrack = "ENG",
|
||||||
|
subtitles = "ENG",
|
||||||
|
modifier = hPad
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (episode.cast.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Column(modifier = hPad) {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Text(
|
||||||
|
text = "Cast",
|
||||||
|
color = scheme.onBackground,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
MediaCastRow(cast = episode.cast)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EpisodeTopBar(
|
||||||
|
onBack = onBack,
|
||||||
|
downFocusRequester = playFocusRequester,
|
||||||
|
modifier = Modifier.align(Alignment.TopStart)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,91 +77,97 @@ private fun MovieScreenInternal(
|
|||||||
playFocusRequester.requestFocus()
|
playFocusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(scheme.background)
|
.background(scheme.background)
|
||||||
) {
|
) {
|
||||||
item {
|
LazyColumn(
|
||||||
Box {
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
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 {
|
|
||||||
Column(modifier = hPad) {
|
|
||||||
Text(
|
|
||||||
text = movie.title,
|
|
||||||
color = scheme.onBackground,
|
|
||||||
fontSize = 32.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
lineHeight = 38.sp
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
FlowRow(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
MediaMetaChip(text = movie.year)
|
|
||||||
MediaMetaChip(text = movie.rating)
|
|
||||||
MediaMetaChip(text = movie.runtime)
|
|
||||||
MediaMetaChip(
|
|
||||||
text = movie.format,
|
|
||||||
background = scheme.primary.copy(alpha = 0.2f),
|
|
||||||
border = scheme.primary.copy(alpha = 0.3f),
|
|
||||||
textColor = scheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
MediaSynopsis(
|
|
||||||
synopsis = movie.synopsis,
|
|
||||||
modifier = hPad
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Row(modifier = hPad) {
|
|
||||||
MediaResumeButton(
|
|
||||||
text = if (movie.progress == null) "Play" else "Resume",
|
|
||||||
progress = movie.progress?.div(100)?.toFloat() ?: 0f,
|
|
||||||
onClick = onPlay,
|
|
||||||
modifier = Modifier.sizeIn(maxWidth = 200.dp).focusRequester(playFocusRequester)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
MediaPlaybackSettings(
|
|
||||||
backgroundColor = scheme.surface,
|
|
||||||
foregroundColor = scheme.onSurface,
|
|
||||||
audioTrack = movie.audioTrack,
|
|
||||||
subtitles = movie.subtitles,
|
|
||||||
modifier = hPad
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (movie.cast.isNotEmpty()) {
|
|
||||||
item {
|
item {
|
||||||
Column(modifier = hPad) {
|
Column(modifier = hPad) {
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = "Cast",
|
text = movie.title,
|
||||||
color = scheme.onBackground,
|
color = scheme.onBackground,
|
||||||
fontSize = 18.sp,
|
fontSize = 32.sp,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold,
|
||||||
|
lineHeight = 38.sp
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
MediaCastRow(cast = movie.cast)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
FlowRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
MediaMetaChip(text = movie.year)
|
||||||
|
MediaMetaChip(text = movie.rating)
|
||||||
|
MediaMetaChip(text = movie.runtime)
|
||||||
|
MediaMetaChip(
|
||||||
|
text = movie.format,
|
||||||
|
background = scheme.primary.copy(alpha = 0.2f),
|
||||||
|
border = scheme.primary.copy(alpha = 0.3f),
|
||||||
|
textColor = scheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
MediaSynopsis(
|
||||||
|
synopsis = movie.synopsis,
|
||||||
|
modifier = hPad
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Row(modifier = hPad) {
|
||||||
|
MediaResumeButton(
|
||||||
|
text = if (movie.progress == null) "Play" else "Resume",
|
||||||
|
progress = movie.progress?.div(100)?.toFloat() ?: 0f,
|
||||||
|
onClick = onPlay,
|
||||||
|
modifier = Modifier.sizeIn(maxWidth = 200.dp).focusRequester(playFocusRequester)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
MediaPlaybackSettings(
|
||||||
|
backgroundColor = scheme.surface,
|
||||||
|
foregroundColor = scheme.onSurface,
|
||||||
|
audioTrack = movie.audioTrack,
|
||||||
|
subtitles = movie.subtitles,
|
||||||
|
modifier = hPad
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (movie.cast.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Column(modifier = hPad) {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Text(
|
||||||
|
text = "Cast",
|
||||||
|
color = scheme.onBackground,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
MediaCastRow(cast = movie.cast)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MovieTopBar(
|
||||||
|
onBack = onBack,
|
||||||
|
downFocusRequester = playFocusRequester,
|
||||||
|
modifier = Modifier.align(Alignment.TopStart)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,77 +73,85 @@ 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)
|
||||||
) {
|
) {
|
||||||
item {
|
LazyColumn(
|
||||||
Box {
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
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 {
|
|
||||||
Column(modifier = hPad) {
|
|
||||||
Text(
|
|
||||||
text = series.name,
|
|
||||||
color = scheme.onBackground,
|
|
||||||
fontSize = 30.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
lineHeight = 36.sp
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
SeriesMetaChips(series = series)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
MediaSynopsis(
|
|
||||||
synopsis = series.synopsis,
|
|
||||||
bodyColor = textMutedStrong,
|
|
||||||
bodyFontSize = 13.sp,
|
|
||||||
bodyLineHeight = null,
|
|
||||||
titleSpacing = 8.dp,
|
|
||||||
modifier = hPad
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
SeasonTabs(
|
|
||||||
seasons = series.seasons,
|
|
||||||
selectedSeason = selectedSeason.value,
|
|
||||||
onSelect = { selectedSeason.value = it },
|
|
||||||
modifier = hPad
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
EpisodeCarousel(
|
|
||||||
episodes = selectedSeason.value.episodes,
|
|
||||||
modifier = hPad
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (series.cast.isNotEmpty()) {
|
|
||||||
item {
|
item {
|
||||||
Column(modifier = hPad) {
|
Column(modifier = hPad) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = "Cast",
|
text = series.name,
|
||||||
color = scheme.onBackground,
|
color = scheme.onBackground,
|
||||||
fontSize = 18.sp,
|
fontSize = 30.sp,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold,
|
||||||
|
lineHeight = 36.sp
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
CastRow(cast = series.cast)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
SeriesMetaChips(series = series)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
MediaSynopsis(
|
||||||
|
synopsis = series.synopsis,
|
||||||
|
bodyColor = textMutedStrong,
|
||||||
|
bodyFontSize = 13.sp,
|
||||||
|
bodyLineHeight = null,
|
||||||
|
titleSpacing = 8.dp,
|
||||||
|
modifier = hPad
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
SeasonTabs(
|
||||||
|
seasons = series.seasons,
|
||||||
|
selectedSeason = selectedSeason.value,
|
||||||
|
firstItemFocusRequester = firstContentFocusRequester,
|
||||||
|
onSelect = { selectedSeason.value = it },
|
||||||
|
modifier = hPad
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
EpisodeCarousel(
|
||||||
|
episodes = selectedSeason.value.episodes,
|
||||||
|
modifier = hPad
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (series.cast.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Column(modifier = hPad) {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(
|
||||||
|
text = "Cast",
|
||||||
|
color = scheme.onBackground,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
CastRow(cast = series.cast)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SeriesTopBar(
|
||||||
|
onBack = onBack,
|
||||||
|
downFocusRequester = firstContentFocusRequester,
|
||||||
|
modifier = Modifier.align(Alignment.TopStart)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user