From fbb19e6970a14b575f17710adc9b9ed1e3586336 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Wed, 13 May 2026 17:33:46 +0200 Subject: [PATCH] refactor: extract common MediaComponents screen layout skeleton into its own component. --- .../ui/common/media/MediaDetailScaffold.kt | 97 ++++++++++ .../purefin/ui/common/media/MediaHero.kt | 51 ----- .../ui/screen/episode/EpisodeScreen.kt | 142 ++++---------- .../episode/components/EpisodeComponents.kt | 6 - .../purefin/ui/screen/movie/MovieScreen.kt | 130 ++++--------- .../movie/components/MovieComponents.kt | 98 +++++----- .../purefin/ui/screen/series/SeriesScreen.kt | 178 ++++++------------ 7 files changed, 268 insertions(+), 434 deletions(-) create mode 100644 app/src/main/java/hu/bbara/purefin/ui/common/media/MediaDetailScaffold.kt delete mode 100644 app/src/main/java/hu/bbara/purefin/ui/common/media/MediaHero.kt diff --git a/app/src/main/java/hu/bbara/purefin/ui/common/media/MediaDetailScaffold.kt b/app/src/main/java/hu/bbara/purefin/ui/common/media/MediaDetailScaffold.kt new file mode 100644 index 00000000..8b646338 --- /dev/null +++ b/app/src/main/java/hu/bbara/purefin/ui/common/media/MediaDetailScaffold.kt @@ -0,0 +1,97 @@ +package hu.bbara.purefin.ui.common.media + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import hu.bbara.purefin.ui.common.image.PurefinAsyncImage + +@Composable +fun MediaDetailScaffold( + imageUrl: String, + modifier: Modifier = Modifier, + imageHeight: Dp = 320.dp, + contentOverlap: Dp = 24.dp, + contentSpacing: Dp = 16.dp, + topBar: @Composable () -> Unit = {}, + heroContent: @Composable ColumnScope.() -> Unit, + content: @Composable ColumnScope.() -> Unit +) { + val scheme = MaterialTheme.colorScheme + val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive() + + Box( + modifier = modifier + .fillMaxSize() + .background(scheme.background) + ) { + Box( + modifier = Modifier + .homeMediaSharedBoundsDestination() + .fillMaxWidth() + .height(imageHeight) + ) { + PurefinAsyncImage( + model = imageUrl, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + Box( + modifier = Modifier + .matchParentSize() + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Transparent, + scheme.background.copy(alpha = 0.5f), + scheme.background + ) + ) + ) + ) + } + + 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() + } + } + } + } +} diff --git a/app/src/main/java/hu/bbara/purefin/ui/common/media/MediaHero.kt b/app/src/main/java/hu/bbara/purefin/ui/common/media/MediaHero.kt deleted file mode 100644 index b566bab7..00000000 --- a/app/src/main/java/hu/bbara/purefin/ui/common/media/MediaHero.kt +++ /dev/null @@ -1,51 +0,0 @@ -package hu.bbara.purefin.ui.common.media - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.unit.dp -import hu.bbara.purefin.ui.common.image.PurefinAsyncImage - -@Composable -fun MediaHero( - imageUrl: String, - backgroundColor: Color, - heightFraction: Float = 0.4f, - modifier: Modifier = Modifier, -) { - val screenHeight = LocalConfiguration.current.screenHeightDp.dp - val heroHeight = screenHeight * heightFraction - - Box( - modifier = modifier - .height(heroHeight) - .background(backgroundColor) - ) { - PurefinAsyncImage( - model = imageUrl, - contentDescription = null, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop - ) - Box( - modifier = Modifier - .matchParentSize() - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - backgroundColor.copy(alpha = 0.5f), - backgroundColor - ) - ) - ) - ) - } -} diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt index c5db6385..68287810 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt @@ -4,51 +4,36 @@ import android.Manifest import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text 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.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel -import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen -import hu.bbara.purefin.ui.common.media.MediaHero -import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow -import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsDestination -import hu.bbara.purefin.ui.common.media.isHomeMediaSharedBoundsTransitionActive import hu.bbara.purefin.core.download.DownloadState +import hu.bbara.purefin.core.feature.content.episode.EpisodeScreenViewModel +import hu.bbara.purefin.core.image.ArtworkKind import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.navigation.EpisodeDto -import hu.bbara.purefin.navigation.LocalNavigationBackStack import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.model.CastMember import hu.bbara.purefin.model.Episode -import hu.bbara.purefin.core.feature.content.episode.EpisodeScreenViewModel +import hu.bbara.purefin.navigation.LocalNavigationBackStack +import hu.bbara.purefin.ui.common.media.MediaDetailScaffold +import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow import hu.bbara.purefin.ui.screen.episode.components.EpisodeDetails import hu.bbara.purefin.ui.screen.episode.components.EpisodeTopBar import hu.bbara.purefin.ui.screen.episode.components.EpisodeTopBarShortcut +import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen import hu.bbara.purefin.ui.theme.AppTheme -import hu.bbara.purefin.core.image.ArtworkKind import java.util.UUID @Composable @@ -119,101 +104,52 @@ private fun EpisodeScreenInternal( onDownloadClick: () -> Unit, modifier: Modifier = Modifier, ) { - val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive() - - Scaffold( + MediaDetailScaffold( + imageUrl = ImageUrlBuilder.finishImageUrl(episode.imageUrlPrefix, ArtworkKind.PRIMARY), modifier = modifier, - containerColor = MaterialTheme.colorScheme.background, topBar = { - if (!isHomeMediaTransitionActive) { - EpisodeTopBar( - shortcut = topBarShortcut, - onBack = onBack, - onSeriesClick = onSeriesClick, - modifier = Modifier - ) - } + EpisodeTopBar( + shortcut = topBarShortcut, + onBack = onBack, + onSeriesClick = onSeriesClick + ) + }, + heroContent = { + EpisodeHeroContent(episode = episode) } - ) { innerPadding -> - Column( + ) { + EpisodeDetails( + episode = episode, + downloadState = downloadState, + onDownloadClick = onDownloadClick, modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - ) { - EpisodeHeroSection(episode = episode) - if (!isHomeMediaTransitionActive) { - EpisodeDetails( - episode = episode, - downloadState = downloadState, - onDownloadClick = onDownloadClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(bottom = innerPadding.calculateBottomPadding()) - ) - } - } + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) } } @Composable -private fun EpisodeHeroSection( +private fun EpisodeHeroContent( episode: Episode, modifier: Modifier = Modifier, ) { val scheme = MaterialTheme.colorScheme - val screenHeight = LocalConfiguration.current.screenHeightDp.dp - val sectionHeight = screenHeight * 0.4f - Box( - modifier = modifier - .homeMediaSharedBoundsDestination() - .fillMaxWidth() - .height(sectionHeight) - ) { - MediaHero( - imageUrl = ImageUrlBuilder.finishImageUrl(episode.imageUrlPrefix, ArtworkKind.PRIMARY), - backgroundColor = scheme.background, - modifier = Modifier.fillMaxSize() - ) - Box( - modifier = Modifier - .matchParentSize() - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Transparent, - scheme.background.copy(alpha = 0.5f), - scheme.background - ) - ) - ) - ) - Column( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomStart) - .padding(16.dp) - ) { - 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)) - EpisodeMetaChips(episode = episode) - } - } + Text( + text = episode.title, + color = scheme.onBackground, + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + lineHeight = 38.sp + ) + Text( + text = "Episode ${episode.index}", + color = scheme.onBackground, + fontSize = 14.sp, + fontWeight = FontWeight.Medium + ) + EpisodeMetaChips(episode = episode) } @Composable diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt index 7c5ee576..8cfa624c 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt @@ -131,8 +131,6 @@ internal fun EpisodeDetails( MediaSynopsis( synopsis = episode.synopsis ) - Spacer(modifier = Modifier.height(24.dp)) - Row() { MediaResumeButton( text = mediaPlayButtonText(episode.progress, episode.watched), @@ -162,8 +160,6 @@ internal fun EpisodeDetails( ) } } - Spacer(modifier = Modifier.height(24.dp)) - MediaPlaybackSettings( backgroundColor = MaterialTheme.colorScheme.surface, foregroundColor = MaterialTheme.colorScheme.onSurface, @@ -171,8 +167,6 @@ internal fun EpisodeDetails( audioTrack = "ENG", subtitles = "ENG" ) - Spacer(modifier = Modifier.height(24.dp)) - if (episode.cast.isNotEmpty()) { Text( text = "Cast", diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt index a6476192..ad42dd85 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt @@ -4,43 +4,25 @@ import android.Manifest import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.core.download.DownloadState import hu.bbara.purefin.core.feature.content.movie.MovieScreenViewModel import hu.bbara.purefin.core.image.ArtworkKind import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.core.navigation.MovieDto import hu.bbara.purefin.model.CastMember import hu.bbara.purefin.model.Movie -import hu.bbara.purefin.core.navigation.MovieDto -import hu.bbara.purefin.ui.common.media.MediaHero +import hu.bbara.purefin.ui.common.media.MediaDetailScaffold import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow -import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsDestination -import hu.bbara.purefin.ui.common.media.isHomeMediaSharedBoundsTransitionActive import hu.bbara.purefin.ui.screen.movie.components.MovieDetails import hu.bbara.purefin.ui.screen.movie.components.MovieTopBar import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen @@ -49,7 +31,9 @@ import java.util.UUID @Composable fun MovieScreen( - movie: MovieDto, viewModel: MovieScreenViewModel = hiltViewModel(), modifier: Modifier = Modifier + movie: MovieDto, + viewModel: MovieScreenViewModel = hiltViewModel(), + modifier: Modifier = Modifier ) { LaunchedEffect(movie.id) { viewModel.selectMovie(movie.id) @@ -96,93 +80,43 @@ private fun MovieScreenInternal( onBack: () -> Unit, modifier: Modifier = Modifier, ) { - val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive() - - Scaffold( + MediaDetailScaffold( + imageUrl = ImageUrlBuilder.finishImageUrl(movie.imageUrlPrefix, ArtworkKind.PRIMARY), modifier = modifier, - containerColor = MaterialTheme.colorScheme.background, topBar = { - if (!isHomeMediaTransitionActive) { - MovieTopBar( - onBack = onBack, - modifier = Modifier - ) - } - } - ) { innerPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - ) { - MediaHeroSection(movie = movie) - if (!isHomeMediaTransitionActive) { - MovieDetails( - movie = movie, - downloadState = downloadState, - onDownloadClick = onDownloadClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(bottom = innerPadding.calculateBottomPadding()) - ) - } + MovieTopBar(onBack = onBack) + }, + heroContent = { + MovieHeroContent(movie = movie) } + ) { + MovieDetails( + movie = movie, + downloadState = downloadState, + onDownloadClick = onDownloadClick, + ) } } @Composable -fun MediaHeroSection( +private fun MovieHeroContent( movie: Movie, - modifier: Modifier = Modifier) { - + modifier: Modifier = Modifier +) { val scheme = MaterialTheme.colorScheme - val screenHeight = LocalConfiguration.current.screenHeightDp.dp - val sectionHeight = screenHeight * 0.4f - Box ( - modifier = modifier - .homeMediaSharedBoundsDestination() - .height(sectionHeight) - ) { - MediaHero( - imageUrl = ImageUrlBuilder.finishImageUrl(movie.imageUrlPrefix, ArtworkKind.PRIMARY), - backgroundColor = MaterialTheme.colorScheme.background, - modifier = Modifier.fillMaxSize() - ) - Box( - modifier = Modifier - .matchParentSize() - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Transparent, - scheme.background.copy(alpha = 0.5f), - scheme.background - ) - ) - ) - ) - Column ( - modifier = Modifier.fillMaxWidth() - .align(Alignment.BottomStart) - .padding(16.dp) - ) { - Text( - text = movie.title, - color = scheme.onBackground, - fontSize = 32.sp, - fontWeight = FontWeight.Bold, - lineHeight = 38.sp - ) - Spacer(modifier = Modifier.height(16.dp)) - MediaMetadataFlowRow( - items = listOf(movie.year, movie.rating, movie.runtime, movie.format), - highlightedItem = movie.format - ) - } - } + Text( + text = movie.title, + color = scheme.onBackground, + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + lineHeight = 38.sp + ) +// Spacer(modifier = Modifier.height(8.dp)) + MediaMetadataFlowRow( + items = listOf(movie.year, movie.rating, movie.runtime, movie.format), + highlightedItem = movie.format + ) } @Preview(showBackground = true) diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt index 9948467c..d3aa7c8b 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt @@ -2,7 +2,6 @@ package hu.bbara.purefin.ui.screen.movie.components import android.content.Intent import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -60,7 +59,10 @@ internal fun MovieTopBar( ) Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { GhostIconButton(icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = { }) - GhostIconButton(icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = { }) + GhostIconButton( + icon = Icons.Outlined.MoreVert, + contentDescription = "More", + onClick = { }) } } } @@ -71,7 +73,6 @@ internal fun MovieDetails( movie: Movie, downloadState: DownloadState, onDownloadClick: () -> Unit, - modifier: Modifier = Modifier ) { val scheme = MaterialTheme.colorScheme @@ -84,63 +85,56 @@ internal fun MovieDetails( } } - Column(modifier = modifier) { - MediaSynopsis( - synopsis = movie.synopsis + MediaSynopsis( + synopsis = movie.synopsis + ) + Row() { + MediaResumeButton( + text = mediaPlayButtonText(movie.progress, movie.watched), + progress = mediaPlaybackProgress(movie.progress), + onClick = playAction, + modifier = Modifier.sizeIn(maxWidth = 200.dp) + ) + VerticalDivider( + color = MaterialTheme.colorScheme.secondary, + thickness = 4.dp, + modifier = Modifier + .height(48.dp) + .padding(horizontal = 16.dp, vertical = 8.dp) ) - Spacer(modifier = Modifier.height(24.dp)) - Row() { - MediaResumeButton( - text = mediaPlayButtonText(movie.progress, movie.watched), - progress = mediaPlaybackProgress(movie.progress), - onClick = playAction, - modifier = Modifier.sizeIn(maxWidth = 200.dp) + MediaActionButton( + backgroundColor = MaterialTheme.colorScheme.surface, + iconColor = MaterialTheme.colorScheme.onSurface, + icon = when (downloadState) { + is DownloadState.NotDownloaded -> Icons.Outlined.Download + is DownloadState.Downloading -> Icons.Outlined.Close + is DownloadState.Downloaded -> Icons.Outlined.DownloadDone + is DownloadState.Failed -> Icons.Outlined.Download + }, + height = 48.dp, + onClick = onDownloadClick ) - VerticalDivider( - color = MaterialTheme.colorScheme.secondary, - thickness = 4.dp, - modifier = Modifier - .height(48.dp) - .padding(horizontal = 16.dp, vertical = 8.dp) - ) - Row() { - MediaActionButton( - backgroundColor = MaterialTheme.colorScheme.surface, - iconColor = MaterialTheme.colorScheme.onSurface, - icon = when (downloadState) { - is DownloadState.NotDownloaded -> Icons.Outlined.Download - is DownloadState.Downloading -> Icons.Outlined.Close - is DownloadState.Downloaded -> Icons.Outlined.DownloadDone - is DownloadState.Failed -> Icons.Outlined.Download - }, - height = 48.dp, - onClick = onDownloadClick - ) - } } + } + MediaPlaybackSettings( + backgroundColor = MaterialTheme.colorScheme.surface, + foregroundColor = MaterialTheme.colorScheme.onSurface, + audioTrack = "ENG", + subtitles = "ENG" + ) + if (movie.cast.isNotEmpty()) { Spacer(modifier = Modifier.height(24.dp)) - - MediaPlaybackSettings( - backgroundColor = MaterialTheme.colorScheme.surface, - foregroundColor = MaterialTheme.colorScheme.onSurface, - audioTrack = "ENG", - subtitles = "ENG" + Text( + text = "Cast", + color = scheme.onBackground, + fontSize = 18.sp, + fontWeight = FontWeight.Bold ) - - if (movie.cast.isNotEmpty()) { - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = "Cast", - color = scheme.onBackground, - fontSize = 18.sp, - fontWeight = FontWeight.Bold - ) - Spacer(modifier = Modifier.height(12.dp)) - //TODO fix + Spacer(modifier = Modifier.height(12.dp)) + //TODO fix // MediaCastRow( // cast = movie.cast, // ) - } } } diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt index b130d7de..5ecfab8b 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt @@ -1,17 +1,8 @@ package hu.bbara.purefin.ui.screen.series -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -20,11 +11,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -34,15 +21,13 @@ import hu.bbara.purefin.core.download.DownloadState import hu.bbara.purefin.core.feature.content.series.SeriesViewModel import hu.bbara.purefin.core.image.ArtworkKind import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.core.navigation.SeriesDto import hu.bbara.purefin.model.CastMember import hu.bbara.purefin.model.Episode import hu.bbara.purefin.model.Season import hu.bbara.purefin.model.Series -import hu.bbara.purefin.core.navigation.SeriesDto -import hu.bbara.purefin.ui.common.media.MediaHero +import hu.bbara.purefin.ui.common.media.MediaDetailScaffold import hu.bbara.purefin.ui.common.media.MediaSynopsis -import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsDestination -import hu.bbara.purefin.ui.common.media.isHomeMediaSharedBoundsTransitionActive import hu.bbara.purefin.ui.screen.series.components.CastRow import hu.bbara.purefin.ui.screen.series.components.EpisodeCarousel import hu.bbara.purefin.ui.screen.series.components.SeasonTabs @@ -79,8 +64,10 @@ fun SeriesScreen( when (option) { SeriesDownloadOption.SEASON -> viewModel.downloadSeason(seriesData.id, selectedSeason.id) + SeriesDownloadOption.SERIES -> viewModel.downloadSeries(seriesData) + SeriesDownloadOption.SMART -> viewModel.enableSmartDownload(seriesData.id) } @@ -107,14 +94,14 @@ private fun SeriesScreenInternal( modifier: Modifier = Modifier, ) { val scheme = MaterialTheme.colorScheme - val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive() fun getDefaultSeason(): Season { return series.seasons.firstOrNull { it.unwatchedEpisodeCount > 0 } ?: series.seasons.first() } var selectedSeasonId by remember(series.id) { mutableStateOf(getDefaultSeason().id) } - val selectedSeason = series.seasons.firstOrNull { it.id == selectedSeasonId } ?: getDefaultSeason() + val selectedSeason = + series.seasons.firstOrNull { it.id == selectedSeasonId } ?: getDefaultSeason() val nextUpEpisode = selectedSeason.episodes.firstOrNull { !it.watched } ?: selectedSeason.episodes.firstOrNull() @@ -126,125 +113,68 @@ private fun SeriesScreenInternal( onObserveSeasonDownloadState(selectedSeason.episodes) } - Scaffold( + MediaDetailScaffold( + imageUrl = ImageUrlBuilder.finishImageUrl(series.imageUrlPrefix, ArtworkKind.PRIMARY), modifier = modifier, - containerColor = MaterialTheme.colorScheme.background, topBar = { - if (!isHomeMediaTransitionActive) { - SeriesTopBar( - onBack = onBack, - modifier = Modifier - ) - } + SeriesTopBar(onBack = onBack) + }, + heroContent = { + SeriesHeroContent(series = series) } - ) { innerPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - ) { - SeriesHeroSection(series = series) - if (!isHomeMediaTransitionActive) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(bottom = innerPadding.calculateBottomPadding()) - ) { - SeriesActionButtons( - nextUpEpisode = nextUpEpisode, - seriesDownloadState = seriesDownloadState, - selectedSeason = selectedSeason, - seasonDownloadState = seasonDownloadState, - onDownloadOptionSelected = { option -> - onDownloadOptionSelected(option, selectedSeason) - } - ) - Spacer(modifier = Modifier.height(24.dp)) - MediaSynopsis( - synopsis = series.synopsis, - bodyColor = scheme.onSurface, - bodyFontSize = 13.sp, - bodyLineHeight = null, - titleSpacing = 8.dp - ) - Spacer(modifier = Modifier.height(24.dp)) - SeasonTabs( - seasons = series.seasons, - selectedSeason = selectedSeason, - onSelect = { selectedSeasonId = it.id } - ) - EpisodeCarousel( - episodes = selectedSeason.episodes, - ) - Spacer(modifier = Modifier.height(16.dp)) - if(series.cast.isNotEmpty()) { - Text( - text = "Cast", - color = scheme.onBackground, - fontSize = 18.sp, - fontWeight = FontWeight.Bold - ) - Spacer(modifier = Modifier.height(12.dp)) - CastRow(cast = series.cast) - } - } + ) { + SeriesActionButtons( + nextUpEpisode = nextUpEpisode, + seriesDownloadState = seriesDownloadState, + selectedSeason = selectedSeason, + seasonDownloadState = seasonDownloadState, + onDownloadOptionSelected = { option -> + onDownloadOptionSelected(option, selectedSeason) } + ) + MediaSynopsis( + synopsis = series.synopsis, + bodyColor = scheme.onSurface, + bodyFontSize = 13.sp, + bodyLineHeight = null, + titleSpacing = 8.dp + ) + SeasonTabs( + seasons = series.seasons, + selectedSeason = selectedSeason, + onSelect = { selectedSeasonId = it.id } + ) + EpisodeCarousel( + episodes = selectedSeason.episodes, + ) + if (series.cast.isNotEmpty()) { + Text( + text = "Cast", + color = scheme.onBackground, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(12.dp)) + CastRow(cast = series.cast) } } } @Composable -private fun SeriesHeroSection( +private fun SeriesHeroContent( series: Series, modifier: Modifier = Modifier, ) { val scheme = MaterialTheme.colorScheme - val screenHeight = LocalConfiguration.current.screenHeightDp.dp - val sectionHeight = screenHeight * 0.4f - Box( - modifier = modifier - .homeMediaSharedBoundsDestination() - .fillMaxWidth() - .height(sectionHeight) - ) { - MediaHero( - imageUrl = ImageUrlBuilder.finishImageUrl(series.imageUrlPrefix, ArtworkKind.PRIMARY), - backgroundColor = scheme.background, - modifier = Modifier.fillMaxSize() - ) - Box( - modifier = Modifier - .matchParentSize() - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Transparent, - scheme.background.copy(alpha = 0.5f), - scheme.background - ) - ) - ) - ) - Column( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomStart) - .padding(16.dp) - ) { - 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) - } - } + Text( + text = series.name, + color = scheme.onBackground, + fontSize = 30.sp, + fontWeight = FontWeight.Bold, + lineHeight = 36.sp + ) + SeriesMetaChips(series = series) } @Preview(showBackground = true)