refactor(app/ui): refactor MediaDetailScaffold

Move everything into the Scaffold for simpler scrolling behavior

pass Modifier to heroContent and content composables
This commit is contained in:
2026-06-06 11:30:03 +02:00
parent ed418a8e44
commit 25b65b9048
8 changed files with 130 additions and 91 deletions

View File

@@ -30,16 +30,27 @@ fun MediaDetailScaffold(
contentOverlap: Dp = 24.dp, contentOverlap: Dp = 24.dp,
contentSpacing: Dp = 16.dp, contentSpacing: Dp = 16.dp,
topBar: @Composable () -> Unit = {}, topBar: @Composable () -> Unit = {},
heroContent: @Composable ColumnScope.() -> Unit, heroContent: @Composable ColumnScope.(Modifier) -> Unit,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.(Modifier) -> Unit
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive() val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive()
Box( Scaffold(
modifier = modifier modifier = Modifier.fillMaxSize(),
containerColor = scheme.background,
topBar = {
if (!isHomeMediaTransitionActive) {
topBar()
}
}
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(scheme.background) .verticalScroll(rememberScrollState())
.padding(bottom = innerPadding.calculateBottomPadding()),
verticalArrangement = Arrangement.spacedBy(contentSpacing)
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -68,30 +79,11 @@ fun MediaDetailScaffold(
) )
) )
} }
if (!isHomeMediaTransitionActive) {
heroContent(Modifier.padding(horizontal = 16.dp))
content(Modifier.padding(horizontal = 16.dp))
}
}
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
containerColor = Color.Transparent,
topBar = {
if (!isHomeMediaTransitionActive) {
topBar()
}
}
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(bottom = innerPadding.calculateBottomPadding())
.padding(horizontal = 16.dp)
.padding(top = imageHeight - contentOverlap),
verticalArrangement = Arrangement.spacedBy(contentSpacing)
) {
if (!isHomeMediaTransitionActive) {
heroContent()
content()
}
}
}
}
}

View File

@@ -95,14 +95,18 @@ private fun EpisodeScreenInternal(
onBack = onBack, onBack = onBack,
) )
}, },
heroContent = { heroContent = { _modifier ->
EpisodeHeroContent(episode = episode) EpisodeHeroContent(
episode = episode,
modifier = _modifier
)
} }
) { ) { _modifier ->
EpisodeDetails( EpisodeDetails(
episode = episode, episode = episode,
downloadState = downloadState, downloadState = downloadState,
onDownloadClick = onDownloadClick, onDownloadClick = onDownloadClick,
modifier = _modifier
) )
} }
} }
@@ -119,22 +123,31 @@ private fun EpisodeHeroContent(
color = scheme.onBackground, color = scheme.onBackground,
fontSize = 32.sp, fontSize = 32.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
lineHeight = 38.sp lineHeight = 38.sp,
modifier = modifier
) )
Text( Text(
text = "Episode ${episode.index}", text = "Episode ${episode.index}",
color = scheme.onBackground, color = scheme.onBackground,
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.Medium fontWeight = FontWeight.Medium,
modifier = modifier
)
EpisodeMetaChips(
episode = episode,
modifier = modifier
) )
EpisodeMetaChips(episode = episode)
} }
@Composable @Composable
private fun EpisodeMetaChips(episode: Episode) { private fun EpisodeMetaChips(
episode: Episode,
modifier: Modifier
) {
MediaMetadataFlowRow( MediaMetadataFlowRow(
items = listOf(episode.releaseDate, episode.rating, episode.runtime, episode.format), items = listOf(episode.releaseDate, episode.rating, episode.runtime, episode.format),
highlightedItem = episode.format highlightedItem = episode.format,
modifier = modifier
) )
} }

View File

@@ -102,6 +102,7 @@ internal fun EpisodeDetails(
episode: Episode, episode: Episode,
downloadState: DownloadState, downloadState: DownloadState,
onDownloadClick: () -> Unit, onDownloadClick: () -> Unit,
modifier: Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
@@ -115,9 +116,10 @@ internal fun EpisodeDetails(
} }
MediaSynopsis( MediaSynopsis(
synopsis = episode.synopsis synopsis = episode.synopsis,
modifier = modifier
) )
Row() { Row(modifier = modifier) {
MediaResumeButton( MediaResumeButton(
text = mediaPlayButtonText(episode.progress, episode.watched), text = mediaPlayButtonText(episode.progress, episode.watched),
progress = mediaPlaybackProgress(episode.progress), progress = mediaPlaybackProgress(episode.progress),
@@ -146,16 +148,18 @@ internal fun EpisodeDetails(
foregroundColor = MaterialTheme.colorScheme.onSurface, foregroundColor = MaterialTheme.colorScheme.onSurface,
//TODO fix it //TODO fix it
audioTrack = "ENG", audioTrack = "ENG",
subtitles = "ENG" subtitles = "ENG",
modifier = modifier
) )
if (episode.cast.isNotEmpty()) { if (episode.cast.isNotEmpty()) {
Text( Text(
text = "Cast", text = "Cast",
color = scheme.onBackground, color = scheme.onBackground,
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold,
modifier = modifier
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = modifier.height(12.dp))
//TODO use it //TODO use it
// MediaCastRow( // MediaCastRow(
// cast = episode.cast // cast = episode.cast

View File

@@ -77,14 +77,18 @@ private fun MovieScreenInternal(
topBar = { topBar = {
MovieTopBar(onBack = onBack) MovieTopBar(onBack = onBack)
}, },
heroContent = { heroContent = { _modifier ->
MovieHeroContent(movie = movie) MovieHeroContent(
movie = movie,
modifier = _modifier
)
} }
) { ) { _modifier ->
MovieDetails( MovieDetails(
movie = movie, movie = movie,
downloadState = downloadState, downloadState = downloadState,
onDownloadClick = onDownloadClick, onDownloadClick = onDownloadClick,
modifier = _modifier
) )
} }
} }
@@ -101,12 +105,14 @@ private fun MovieHeroContent(
color = scheme.onBackground, color = scheme.onBackground,
fontSize = 32.sp, fontSize = 32.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
lineHeight = 38.sp lineHeight = 38.sp,
modifier = modifier
) )
// Spacer(modifier = Modifier.height(8.dp)) // Spacer(modifier = Modifier.height(8.dp))
MediaMetadataFlowRow( MediaMetadataFlowRow(
items = listOf(movie.year, movie.rating, movie.runtime, movie.format), items = listOf(movie.year, movie.rating, movie.runtime, movie.format),
highlightedItem = movie.format highlightedItem = movie.format,
modifier = modifier
) )
} }

View File

@@ -63,6 +63,7 @@ internal fun MovieDetails(
movie: Movie, movie: Movie,
downloadState: DownloadState, downloadState: DownloadState,
onDownloadClick: () -> Unit, onDownloadClick: () -> Unit,
modifier: Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
@@ -76,9 +77,10 @@ internal fun MovieDetails(
} }
MediaSynopsis( MediaSynopsis(
synopsis = movie.synopsis synopsis = movie.synopsis,
modifier = modifier
) )
Row() { Row(modifier = modifier) {
MediaResumeButton( MediaResumeButton(
text = mediaPlayButtonText(movie.progress, movie.watched), text = mediaPlayButtonText(movie.progress, movie.watched),
progress = mediaPlaybackProgress(movie.progress), progress = mediaPlaybackProgress(movie.progress),
@@ -106,7 +108,8 @@ internal fun MovieDetails(
backgroundColor = MaterialTheme.colorScheme.surface, backgroundColor = MaterialTheme.colorScheme.surface,
foregroundColor = MaterialTheme.colorScheme.onSurface, foregroundColor = MaterialTheme.colorScheme.onSurface,
audioTrack = "ENG", audioTrack = "ENG",
subtitles = "ENG" subtitles = "ENG",
modifier = modifier
) )
if (movie.cast.isNotEmpty()) { if (movie.cast.isNotEmpty()) {
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
@@ -114,9 +117,10 @@ internal fun MovieDetails(
text = "Cast", text = "Cast",
color = scheme.onBackground, color = scheme.onBackground,
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold,
modifier = modifier
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = modifier.height(12.dp))
//TODO fix //TODO fix
// MediaCastRow( // MediaCastRow(
// cast = movie.cast, // cast = movie.cast,

View File

@@ -7,7 +7,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -17,6 +16,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import hu.bbara.purefin.core.download.DownloadState import hu.bbara.purefin.core.download.DownloadState
import hu.bbara.purefin.core.feature.content.series.SeriesViewModel import hu.bbara.purefin.core.feature.content.series.SeriesViewModel
import hu.bbara.purefin.core.image.ArtworkKind import hu.bbara.purefin.core.image.ArtworkKind
@@ -151,10 +151,13 @@ private fun SeriesScreenInternal(
topBar = { topBar = {
SeriesTopBar(onBack = onBack) SeriesTopBar(onBack = onBack)
}, },
heroContent = { heroContent = { _modifier ->
SeriesHeroContent(series = series) SeriesHeroContent(
series = series,
modifier = _modifier
)
} }
) { ) { _modifier ->
if (selectedSeason != null) { if (selectedSeason != null) {
SeriesActionButtons( SeriesActionButtons(
nextUpEpisode = nextUpEpisode, nextUpEpisode = nextUpEpisode,
@@ -165,7 +168,8 @@ private fun SeriesScreenInternal(
offline = offline, offline = offline,
onDownloadOptionSelected = { option -> onDownloadOptionSelected = { option ->
onDownloadOptionSelected(option, selectedSeason) onDownloadOptionSelected(option, selectedSeason)
} },
modifier = _modifier
) )
} }
MediaSynopsis( MediaSynopsis(
@@ -173,22 +177,26 @@ private fun SeriesScreenInternal(
bodyColor = scheme.onSurface, bodyColor = scheme.onSurface,
bodyFontSize = 13.sp, bodyFontSize = 13.sp,
bodyLineHeight = null, bodyLineHeight = null,
titleSpacing = 8.dp titleSpacing = 8.dp,
modifier = _modifier
) )
if (selectedSeason != null) { if (selectedSeason != null) {
SeasonTabs( SeasonTabs(
seasons = series.seasons, seasons = series.seasons,
selectedSeason = selectedSeason, selectedSeason = selectedSeason,
onSelect = { selectedSeasonId = it.id } onSelect = { selectedSeasonId = it.id },
modifier = _modifier
) )
EpisodeCarousel( EpisodeCarousel(
episodes = selectedSeason.episodes, episodes = selectedSeason.episodes,
modifier = _modifier
) )
} else { } else {
Text( Text(
text = "Loading seasons...", text = "Loading seasons...",
color = scheme.onSurfaceVariant, color = scheme.onSurfaceVariant,
fontSize = 13.sp fontSize = 13.sp,
modifier = _modifier
) )
} }
if (series.cast.isNotEmpty()) { if (series.cast.isNotEmpty()) {
@@ -196,9 +204,10 @@ private fun SeriesScreenInternal(
text = "Cast", text = "Cast",
color = scheme.onBackground, color = scheme.onBackground,
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold,
modifier = _modifier
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = _modifier.height(12.dp))
CastRow(cast = series.cast) CastRow(cast = series.cast)
} }
} }
@@ -216,9 +225,13 @@ private fun SeriesHeroContent(
color = scheme.onBackground, color = scheme.onBackground,
fontSize = 30.sp, fontSize = 30.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
lineHeight = 36.sp lineHeight = 36.sp,
modifier = modifier
)
SeriesMetaChips(
series = series,
modifier = modifier
) )
SeriesMetaChips(series = series)
} }
private fun SeriesDownloadOption.canPerform( private fun SeriesDownloadOption.canPerform(

View File

@@ -103,9 +103,13 @@ internal fun SeriesTopBar(
} }
@Composable @Composable
internal fun SeriesMetaChips(series: Series) { internal fun SeriesMetaChips(
series: Series,
modifier: Modifier = Modifier
) {
MediaMetadataFlowRow( MediaMetadataFlowRow(
items = listOf(series.year, "${series.seasonCount} Seasons") items = listOf(series.year, "${series.seasonCount} Seasons"),
modifier = modifier
) )
} }
@@ -142,7 +146,10 @@ internal fun SeriesActionButtons(
} }
} }
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
) {
MediaResumeButton( MediaResumeButton(
text = mediaPlayButtonText(nextUpEpisode?.progress, nextUpEpisode?.watched), text = mediaPlayButtonText(nextUpEpisode?.progress, nextUpEpisode?.watched),
progress = mediaPlaybackProgress(nextUpEpisode?.progress), progress = mediaPlaybackProgress(nextUpEpisode?.progress),

View File

@@ -3,21 +3,21 @@ package hu.bbara.purefin.ui.common.media
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.material3.MaterialTheme
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun MediaMetadataFlowRow( fun MediaMetadataFlowRow(
items: List<String>, items: List<String>,
modifier: Modifier = Modifier,
highlightedItem: String? = null, highlightedItem: String? = null,
highlightedBackground: Color = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f), highlightedBackground: Color = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
highlightedBorder: Color = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f), highlightedBorder: Color = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),
highlightedTextColor: Color = MaterialTheme.colorScheme.primary highlightedTextColor: Color = MaterialTheme.colorScheme.primary,
modifier: Modifier = Modifier
) { ) {
FlowRow( FlowRow(
modifier = modifier, modifier = modifier,