From c81b228bb247ed137c63d08e548ea4f08e847c68 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Sat, 6 Jun 2026 12:17:34 +0000 Subject: [PATCH] refactor(media): centralize detail scaffold content Move duplicated media detail title, metadata, actions, synopsis, playback, and cast rendering behind MediaDetailScaffoldUiModel so screens only provide model data and optional trailing content. --- .../ui/common/media/MediaDetailScaffold.kt | 284 +++++++++++++++++- .../ui/screen/episode/EpisodeScreen.kt | 127 ++++---- .../episode/components/EpisodeComponents.kt | 87 ------ .../purefin/ui/screen/movie/MovieScreen.kt | 111 ++++--- .../movie/components/MovieComponents.kt | 97 ------ .../purefin/ui/screen/series/SeriesScreen.kt | 183 +++++++---- .../series/components/SeriesComponents.kt | 107 +------ 7 files changed, 534 insertions(+), 462 deletions(-) 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 index cd9dc08e..7cca2d23 100644 --- 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 @@ -5,44 +5,137 @@ 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.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio 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.layout.sizeIn +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Person import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import hu.bbara.purefin.core.download.DownloadState +import hu.bbara.purefin.model.CastMember +import hu.bbara.purefin.ui.common.button.DownloadActionButton +import hu.bbara.purefin.ui.common.button.MediaActionButton +import hu.bbara.purefin.ui.common.button.MediaResumeButton import hu.bbara.purefin.ui.common.image.PurefinAsyncImage +data class MediaDetailScaffoldUiModel( + val imageUrl: String, + val title: String, + val imageContentDescription: String? = null, + val titleFontSize: TextUnit = 32.sp, + val titleLineHeight: TextUnit = 38.sp, + val subtitle: String? = null, + val subtitleFontSize: TextUnit = 14.sp, + val subtitleFontWeight: FontWeight = FontWeight.Medium, + val metadataItems: List = emptyList(), + val highlightedMetadataItem: String? = null, + val actions: MediaDetailActionsUiModel? = null, + val synopsis: MediaDetailSynopsisUiModel? = null, + val playbackSettings: MediaDetailPlaybackSettingsUiModel? = null, + val cast: MediaDetailCastUiModel? = null, +) + +data class MediaDetailActionsUiModel( + val primaryAction: MediaDetailPrimaryActionUiModel, + val secondaryActions: List = emptyList(), + val dividerThickness: Dp? = null, +) + +data class MediaDetailPrimaryActionUiModel( + val text: String, + val progress: Float, + val onClick: () -> Unit, + val testTag: String? = null, +) + +sealed interface MediaDetailSecondaryActionUiModel { + val testTag: String? + + data class Download( + val downloadState: DownloadState, + val onClick: () -> Unit, + override val testTag: String? = null, + ) : MediaDetailSecondaryActionUiModel + + data class Icon( + val icon: ImageVector, + val onClick: () -> Unit = {}, + override val testTag: String? = null, + ) : MediaDetailSecondaryActionUiModel +} + +data class MediaDetailSynopsisUiModel( + val text: String, + val bodyColor: Color? = null, + val bodyFontSize: TextUnit = 15.sp, + val bodyLineHeight: TextUnit? = 22.sp, + val titleSpacing: Dp = 12.dp, +) + +data class MediaDetailPlaybackSettingsUiModel( + val audioTrack: String, + val subtitles: String, +) + +data class MediaDetailCastUiModel( + val members: List, + val title: String = "Cast", + val topSpacing: Dp = 0.dp, + val cardWidth: Dp = 96.dp, + val nameFontSize: TextUnit = 12.sp, + val roleFontSize: TextUnit = 10.sp, +) + @OptIn(ExperimentalMaterial3Api::class) @Composable fun MediaDetailScaffold( - imageUrl: String, + uiModel: MediaDetailScaffoldUiModel, modifier: Modifier = Modifier, imageHeight: Dp = 320.dp, contentOverlap: Dp = 24.dp, contentSpacing: Dp = 16.dp, topBar: @Composable (scrollBehavior: TopAppBarScrollBehavior) -> Unit = {}, - content: @Composable ColumnScope.(Modifier) -> Unit + content: @Composable ColumnScope.(Modifier) -> Unit = {} ) { val scheme = MaterialTheme.colorScheme val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive() val topBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() Scaffold( - modifier = Modifier + modifier = modifier .fillMaxSize() .nestedScroll(topBarScrollBehavior.nestedScrollConnection), containerColor = scheme.background, @@ -66,8 +159,8 @@ fun MediaDetailScaffold( .height(imageHeight) ) { PurefinAsyncImage( - model = imageUrl, - contentDescription = null, + model = uiModel.imageUrl, + contentDescription = uiModel.imageContentDescription, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop ) @@ -87,8 +180,187 @@ fun MediaDetailScaffold( ) } if (!isHomeMediaTransitionActive) { - content(Modifier.padding(horizontal = 16.dp)) + val contentModifier = Modifier.padding(horizontal = 16.dp) + MediaDetailScaffoldContent( + uiModel = uiModel, + modifier = contentModifier + ) + content(contentModifier) } } } } + +@Composable +private fun ColumnScope.MediaDetailScaffoldContent( + uiModel: MediaDetailScaffoldUiModel, + modifier: Modifier = Modifier, +) { + val scheme = MaterialTheme.colorScheme + + Text( + text = uiModel.title, + color = scheme.onBackground, + fontSize = uiModel.titleFontSize, + fontWeight = FontWeight.Bold, + lineHeight = uiModel.titleLineHeight, + modifier = modifier + ) + uiModel.subtitle?.takeIf { it.isNotBlank() }?.let { subtitle -> + Text( + text = subtitle, + color = scheme.onBackground, + fontSize = uiModel.subtitleFontSize, + fontWeight = uiModel.subtitleFontWeight, + modifier = modifier + ) + } + if (uiModel.metadataItems.any { it.isNotBlank() }) { + MediaMetadataFlowRow( + items = uiModel.metadataItems, + highlightedItem = uiModel.highlightedMetadataItem, + modifier = modifier + ) + } + uiModel.actions?.let { actions -> + MediaDetailActions( + uiModel = actions, + modifier = modifier + ) + } + uiModel.synopsis?.takeIf { it.text.isNotBlank() }?.let { synopsis -> + MediaSynopsis( + synopsis = synopsis.text, + bodyColor = synopsis.bodyColor ?: scheme.onSurfaceVariant, + bodyFontSize = synopsis.bodyFontSize, + bodyLineHeight = synopsis.bodyLineHeight, + titleSpacing = synopsis.titleSpacing, + modifier = modifier + ) + } + uiModel.playbackSettings?.let { playbackSettings -> + MediaPlaybackSettings( + backgroundColor = scheme.surface, + foregroundColor = scheme.onSurface, + audioTrack = playbackSettings.audioTrack, + subtitles = playbackSettings.subtitles, + modifier = modifier + ) + } + uiModel.cast?.takeIf { it.members.isNotEmpty() }?.let { cast -> + if (cast.topSpacing > 0.dp) { + Spacer(modifier = Modifier.height(cast.topSpacing)) + } + Text( + text = cast.title, + color = scheme.onBackground, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + modifier = modifier + ) + Spacer(modifier = modifier.height(12.dp)) + MediaDetailCastRow( + cast = cast, + modifier = modifier + ) + } +} + +@Composable +private fun MediaDetailCastRow( + cast: MediaDetailCastUiModel, + modifier: Modifier = Modifier, +) { + val scheme = MaterialTheme.colorScheme + val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f) + + LazyRow( + modifier = modifier, + contentPadding = PaddingValues(horizontal = 4.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(cast.members) { member -> + Column(modifier = Modifier.width(cast.cardWidth)) { + PurefinAsyncImage( + model = member.imageUrl, + contentDescription = null, + modifier = Modifier + .aspectRatio(4f / 5f) + .clip(RoundedCornerShape(12.dp)) + .background(scheme.surfaceVariant), + contentScale = ContentScale.Crop, + fallbackIcon = Icons.Outlined.Person + ) + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = member.name, + color = scheme.onBackground, + fontSize = cast.nameFontSize, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = member.role, + color = mutedStrong, + fontSize = cast.roleFontSize, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } +} + +@Composable +private fun MediaDetailActions( + uiModel: MediaDetailActionsUiModel, + modifier: Modifier = Modifier, +) { + Row(modifier = modifier) { + MediaResumeButton( + text = uiModel.primaryAction.text, + progress = uiModel.primaryAction.progress, + onClick = uiModel.primaryAction.onClick, + modifier = Modifier + .sizeIn(maxWidth = 200.dp) + .optionalTestTag(uiModel.primaryAction.testTag) + ) + uiModel.dividerThickness?.let { dividerThickness -> + VerticalDivider( + color = MaterialTheme.colorScheme.secondary, + thickness = dividerThickness, + modifier = Modifier + .height(48.dp) + .padding(horizontal = 16.dp, vertical = 8.dp) + ) + } ?: Spacer(modifier = Modifier.width(12.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + uiModel.secondaryActions.forEach { action -> + when (action) { + is MediaDetailSecondaryActionUiModel.Download -> { + DownloadActionButton( + downloadState = action.downloadState, + onClick = action.onClick, + modifier = Modifier.optionalTestTag(action.testTag) + ) + } + + is MediaDetailSecondaryActionUiModel.Icon -> { + MediaActionButton( + backgroundColor = MaterialTheme.colorScheme.surface, + iconColor = MaterialTheme.colorScheme.onSurface, + icon = action.icon, + height = 48.dp, + onClick = action.onClick, + modifier = Modifier.optionalTestTag(action.testTag) + ) + } + } + } + } + } +} + +private fun Modifier.optionalTestTag(testTag: String?): Modifier = + if (testTag == null) this else then(Modifier.testTag(testTag)) 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 1e9ca121..14f03cfc 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 @@ -1,15 +1,14 @@ package hu.bbara.purefin.ui.screen.episode +import android.content.Intent import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import hu.bbara.purefin.core.download.DownloadState @@ -21,10 +20,20 @@ import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.model.CastMember import hu.bbara.purefin.model.Episode import hu.bbara.purefin.navigation.LocalNavigationBackStack +import hu.bbara.purefin.player.PlayerActivity +import hu.bbara.purefin.ui.common.media.MediaDetailActionsUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailCastUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailPlaybackSettingsUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailPrimaryActionUiModel import hu.bbara.purefin.ui.common.media.MediaDetailScaffold -import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow +import hu.bbara.purefin.ui.common.media.MediaDetailScaffoldUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailSecondaryActionUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailSynopsisUiModel +import hu.bbara.purefin.ui.common.media.mediaPlayButtonText +import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress import hu.bbara.purefin.ui.common.permission.rememberNotificationPermissionGate -import hu.bbara.purefin.ui.screen.episode.components.EpisodeDetails +import hu.bbara.purefin.ui.screen.episode.components.EpisodeDownloadButtonTag +import hu.bbara.purefin.ui.screen.episode.components.EpisodePlayButtonTag 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 @@ -88,8 +97,21 @@ private fun EpisodeScreenInternal( onDownloadClick: () -> Unit, modifier: Modifier = Modifier, ) { + val context = LocalContext.current + val playAction = remember(episode.id) { + { + val intent = Intent(context, PlayerActivity::class.java) + intent.putExtra("MEDIA_ID", episode.id.toString()) + context.startActivity(intent) + } + } + MediaDetailScaffold( - imageUrl = ImageUrlBuilder.finishImageUrl(episode.imageUrlPrefix, ArtworkKind.PRIMARY), + uiModel = episode.toMediaDetailScaffoldUiModel( + onPlayClick = playAction, + downloadState = downloadState, + onDownloadClick = onDownloadClick + ), modifier = modifier, topBar = { scrollBehavior -> EpisodeTopBar( @@ -98,59 +120,46 @@ private fun EpisodeScreenInternal( scrollBehavior = scrollBehavior ) }, - ) { _modifier -> - EpisodeHeroContent( - episode = episode, - modifier = _modifier - ) - EpisodeDetails( - episode = episode, - downloadState = downloadState, - onDownloadClick = onDownloadClick, - modifier = _modifier - ) + ) +} + +private fun Episode.toMediaDetailScaffoldUiModel( + onPlayClick: () -> Unit, + downloadState: DownloadState, + onDownloadClick: () -> Unit, +): MediaDetailScaffoldUiModel = MediaDetailScaffoldUiModel( + imageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY), + title = title, + subtitle = "Episode $index", + metadataItems = listOf(releaseDate, rating, runtime, format), + highlightedMetadataItem = format, + actions = MediaDetailActionsUiModel( + primaryAction = MediaDetailPrimaryActionUiModel( + text = mediaPlayButtonText(progress, watched), + progress = mediaPlaybackProgress(progress), + onClick = onPlayClick, + testTag = EpisodePlayButtonTag + ), + secondaryActions = listOf( + MediaDetailSecondaryActionUiModel.Download( + downloadState = downloadState, + onClick = onDownloadClick, + testTag = EpisodeDownloadButtonTag + ) + ), + dividerThickness = 2.dp + ), + synopsis = MediaDetailSynopsisUiModel(text = synopsis), + playbackSettings = MediaDetailPlaybackSettingsUiModel( + audioTrack = "ENG", + subtitles = "ENG" + ), + cast = if (cast.isNotEmpty()) { + MediaDetailCastUiModel(members = cast) + } else { + null } -} - -@Composable -private fun EpisodeHeroContent( - episode: Episode, - modifier: Modifier = Modifier, -) { - val scheme = MaterialTheme.colorScheme - - Text( - text = episode.title, - color = scheme.onBackground, - fontSize = 32.sp, - fontWeight = FontWeight.Bold, - lineHeight = 38.sp, - modifier = modifier - ) - Text( - text = "Episode ${episode.index}", - color = scheme.onBackground, - fontSize = 14.sp, - fontWeight = FontWeight.Medium, - modifier = modifier - ) - EpisodeMetaChips( - episode = episode, - modifier = modifier - ) -} - -@Composable -private fun EpisodeMetaChips( - episode: Episode, - modifier: Modifier -) { - MediaMetadataFlowRow( - items = listOf(episode.releaseDate, episode.rating, episode.runtime, episode.format), - highlightedItem = episode.format, - modifier = modifier - ) -} +) @Preview(showBackground = true) @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 5643c503..dead44cd 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 @@ -1,11 +1,6 @@ package hu.bbara.purefin.ui.screen.episode.components -import android.content.Intent -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowBack @@ -17,27 +12,15 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import hu.bbara.purefin.core.download.DownloadState -import hu.bbara.purefin.model.Episode -import hu.bbara.purefin.player.PlayerActivity -import hu.bbara.purefin.ui.common.button.DownloadActionButton import hu.bbara.purefin.ui.common.button.GhostIconButton -import hu.bbara.purefin.ui.common.button.MediaResumeButton -import hu.bbara.purefin.ui.common.media.MediaPlaybackSettings -import hu.bbara.purefin.ui.common.media.MediaSynopsis -import hu.bbara.purefin.ui.common.media.mediaPlayButtonText -import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar internal sealed interface EpisodeTopBarShortcut { @@ -102,76 +85,6 @@ internal fun EpisodeTopBar( ) } -@Composable -internal fun EpisodeDetails( - episode: Episode, - downloadState: DownloadState, - onDownloadClick: () -> Unit, - modifier: Modifier -) { - val scheme = MaterialTheme.colorScheme - - val context = LocalContext.current - val playAction = remember(episode.id) { - { - val intent = Intent(context, PlayerActivity::class.java) - intent.putExtra("MEDIA_ID", episode.id.toString()) - context.startActivity(intent) - } - } - - MediaSynopsis( - synopsis = episode.synopsis, - modifier = modifier - ) - Row(modifier = modifier) { - MediaResumeButton( - text = mediaPlayButtonText(episode.progress, episode.watched), - progress = mediaPlaybackProgress(episode.progress), - onClick = playAction, - modifier = Modifier - .sizeIn(maxWidth = 200.dp) - .testTag(EpisodePlayButtonTag) - ) - VerticalDivider( - color = MaterialTheme.colorScheme.secondary, - thickness = 2.dp, - modifier = Modifier - .height(48.dp) - .padding(horizontal = 16.dp, vertical = 8.dp) - ) - Row() { - DownloadActionButton( - downloadState = downloadState, - modifier = Modifier.testTag(EpisodeDownloadButtonTag), - onClick = onDownloadClick - ) - } - } - MediaPlaybackSettings( - backgroundColor = MaterialTheme.colorScheme.surface, - foregroundColor = MaterialTheme.colorScheme.onSurface, - //TODO fix it - audioTrack = "ENG", - subtitles = "ENG", - modifier = modifier - ) - if (episode.cast.isNotEmpty()) { - Text( - text = "Cast", - color = scheme.onBackground, - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - modifier = modifier - ) - Spacer(modifier = modifier.height(12.dp)) - //TODO use it -// MediaCastRow( -// cast = episode.cast -// ) - } -} - internal const val EpisodeSeriesButtonTag = "episode-series-button" internal const val EpisodePlayButtonTag = "episode-play-button" internal const val EpisodeDownloadButtonTag = "episode-download-button" 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 58464be0..8d847ed7 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 @@ -1,14 +1,14 @@ package hu.bbara.purefin.ui.screen.movie +import android.content.Intent import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import hu.bbara.purefin.core.download.DownloadState @@ -18,10 +18,20 @@ 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.player.PlayerActivity +import hu.bbara.purefin.ui.common.media.MediaDetailActionsUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailCastUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailPlaybackSettingsUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailPrimaryActionUiModel import hu.bbara.purefin.ui.common.media.MediaDetailScaffold -import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow +import hu.bbara.purefin.ui.common.media.MediaDetailScaffoldUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailSecondaryActionUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailSynopsisUiModel +import hu.bbara.purefin.ui.common.media.mediaPlayButtonText +import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress import hu.bbara.purefin.ui.common.permission.rememberNotificationPermissionGate -import hu.bbara.purefin.ui.screen.movie.components.MovieDetails +import hu.bbara.purefin.ui.screen.movie.components.MovieDownloadButtonTag +import hu.bbara.purefin.ui.screen.movie.components.MoviePlayButtonTag import hu.bbara.purefin.ui.screen.movie.components.MovieTopBar import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen import hu.bbara.purefin.ui.theme.AppTheme @@ -73,8 +83,21 @@ private fun MovieScreenInternal( onBack: () -> Unit, modifier: Modifier = Modifier, ) { + val context = LocalContext.current + val playAction = remember(movie.id) { + { + val intent = Intent(context, PlayerActivity::class.java) + intent.putExtra("MEDIA_ID", movie.id.toString()) + context.startActivity(intent) + } + } + MediaDetailScaffold( - imageUrl = ImageUrlBuilder.finishImageUrl(movie.imageUrlPrefix, ArtworkKind.PRIMARY), + uiModel = movie.toMediaDetailScaffoldUiModel( + onPlayClick = playAction, + downloadState = downloadState, + onDownloadClick = onDownloadClick + ), modifier = modifier, topBar = { scrollBehavior -> MovieTopBar( @@ -82,42 +105,48 @@ private fun MovieScreenInternal( scrollBehavior = scrollBehavior ) }, - ) { _modifier -> - MovieHeroContent( - movie = movie, - modifier = _modifier - ) - MovieDetails( - movie = movie, - downloadState = downloadState, - onDownloadClick = onDownloadClick, - modifier = _modifier + ) +} + +private fun Movie.toMediaDetailScaffoldUiModel( + onPlayClick: () -> Unit, + downloadState: DownloadState, + onDownloadClick: () -> Unit, +): MediaDetailScaffoldUiModel = MediaDetailScaffoldUiModel( + imageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY), + title = title, + metadataItems = listOf(year, rating, runtime, format), + highlightedMetadataItem = format, + actions = MediaDetailActionsUiModel( + primaryAction = MediaDetailPrimaryActionUiModel( + text = mediaPlayButtonText(progress, watched), + progress = mediaPlaybackProgress(progress), + onClick = onPlayClick, + testTag = MoviePlayButtonTag + ), + secondaryActions = listOf( + MediaDetailSecondaryActionUiModel.Download( + downloadState = downloadState, + onClick = onDownloadClick, + testTag = MovieDownloadButtonTag + ) + ), + dividerThickness = 4.dp + ), + synopsis = MediaDetailSynopsisUiModel(text = synopsis), + playbackSettings = MediaDetailPlaybackSettingsUiModel( + audioTrack = "ENG", + subtitles = "ENG" + ), + cast = if (cast.isNotEmpty()) { + MediaDetailCastUiModel( + members = cast, + topSpacing = 24.dp ) + } else { + null } -} - -@Composable -private fun MovieHeroContent( - movie: Movie, - modifier: Modifier = Modifier -) { - val scheme = MaterialTheme.colorScheme - - Text( - text = movie.title, - color = scheme.onBackground, - fontSize = 32.sp, - fontWeight = FontWeight.Bold, - lineHeight = 38.sp, - modifier = modifier - ) -// Spacer(modifier = Modifier.height(8.dp)) - MediaMetadataFlowRow( - items = listOf(movie.year, movie.rating, movie.runtime, movie.format), - highlightedItem = movie.format, - modifier = modifier - ) -} +) @Preview(showBackground = true) @Composable 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 072ff393..f04fd0cb 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 @@ -1,39 +1,13 @@ package hu.bbara.purefin.ui.screen.movie.components -import android.content.Intent -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.sizeIn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.Cast import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import hu.bbara.purefin.core.download.DownloadState -import hu.bbara.purefin.model.Movie -import hu.bbara.purefin.player.PlayerActivity -import hu.bbara.purefin.ui.common.button.DownloadActionButton import hu.bbara.purefin.ui.common.button.GhostIconButton -import hu.bbara.purefin.ui.common.button.MediaResumeButton -import hu.bbara.purefin.ui.common.media.MediaPlaybackSettings -import hu.bbara.purefin.ui.common.media.MediaSynopsis -import hu.bbara.purefin.ui.common.media.mediaPlayButtonText -import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar @OptIn(ExperimentalMaterial3Api::class) @@ -62,76 +36,5 @@ internal fun MovieTopBar( ) } -@OptIn(ExperimentalLayoutApi::class) -@Composable -internal fun MovieDetails( - movie: Movie, - downloadState: DownloadState, - onDownloadClick: () -> Unit, - modifier: Modifier -) { - val scheme = MaterialTheme.colorScheme - - val context = LocalContext.current - val playAction = remember(movie.id) { - { - val intent = Intent(context, PlayerActivity::class.java) - intent.putExtra("MEDIA_ID", movie.id.toString()) - context.startActivity(intent) - } - } - - MediaSynopsis( - synopsis = movie.synopsis, - modifier = modifier - ) - Row(modifier = modifier) { - MediaResumeButton( - text = mediaPlayButtonText(movie.progress, movie.watched), - progress = mediaPlaybackProgress(movie.progress), - onClick = playAction, - modifier = Modifier - .sizeIn(maxWidth = 200.dp) - .testTag(MoviePlayButtonTag) - ) - VerticalDivider( - color = MaterialTheme.colorScheme.secondary, - thickness = 4.dp, - modifier = Modifier - .height(48.dp) - .padding(horizontal = 16.dp, vertical = 8.dp) - ) - Row() { - DownloadActionButton( - downloadState = downloadState, - modifier = Modifier.testTag(MovieDownloadButtonTag), - onClick = onDownloadClick - ) - } - } - MediaPlaybackSettings( - backgroundColor = MaterialTheme.colorScheme.surface, - foregroundColor = MaterialTheme.colorScheme.onSurface, - audioTrack = "ENG", - subtitles = "ENG", - modifier = modifier - ) - if (movie.cast.isNotEmpty()) { - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = "Cast", - color = scheme.onBackground, - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - modifier = modifier - ) - Spacer(modifier = modifier.height(12.dp)) - //TODO fix -// MediaCastRow( -// cast = movie.cast, -// ) - } -} - internal const val MoviePlayButtonTag = "movie-play-button" internal const val MovieDownloadButtonTag = "movie-download-button" 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 0451ede3..8a1dee75 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,7 +1,9 @@ package hu.bbara.purefin.ui.screen.series -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height +import android.content.Intent +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.Download import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -12,7 +14,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel @@ -21,19 +24,31 @@ 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.EpisodeDto +import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.core.navigation.SeriesDto import hu.bbara.purefin.model.Episode import hu.bbara.purefin.model.Season import hu.bbara.purefin.model.Series +import hu.bbara.purefin.navigation.LocalNavigationManager +import hu.bbara.purefin.player.PlayerActivity +import hu.bbara.purefin.ui.common.media.MediaDetailActionsUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailCastUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailPrimaryActionUiModel import hu.bbara.purefin.ui.common.media.MediaDetailScaffold -import hu.bbara.purefin.ui.common.media.MediaSynopsis +import hu.bbara.purefin.ui.common.media.MediaDetailScaffoldUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailSecondaryActionUiModel +import hu.bbara.purefin.ui.common.media.MediaDetailSynopsisUiModel +import hu.bbara.purefin.ui.common.media.mediaPlayButtonText +import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress import hu.bbara.purefin.ui.common.permission.rememberNotificationPermissionGate -import hu.bbara.purefin.ui.screen.series.components.CastRow +import hu.bbara.purefin.ui.screen.series.components.DownloadOptionsBottomSheet import hu.bbara.purefin.ui.screen.series.components.EpisodeCarousel import hu.bbara.purefin.ui.screen.series.components.SeasonTabs -import hu.bbara.purefin.ui.screen.series.components.SeriesActionButtons +import hu.bbara.purefin.ui.screen.series.components.SeriesAddButtonTag +import hu.bbara.purefin.ui.screen.series.components.SeriesDownloadButtonTag import hu.bbara.purefin.ui.screen.series.components.SeriesDownloadOption -import hu.bbara.purefin.ui.screen.series.components.SeriesMetaChips +import hu.bbara.purefin.ui.screen.series.components.SeriesPlayButtonTag import hu.bbara.purefin.ui.screen.series.components.SeriesTopBar import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen import java.util.UUID @@ -125,6 +140,9 @@ private fun SeriesScreenInternal( modifier: Modifier = Modifier, ) { val scheme = MaterialTheme.colorScheme + val context = LocalContext.current + val navigationManager = LocalNavigationManager.current + var showDownloadDialog by remember { mutableStateOf(false) } fun getDefaultSeason(): Season? { return series.seasons.firstOrNull { it.unwatchedEpisodeCount > 0 } ?: series.seasons.firstOrNull() @@ -135,6 +153,25 @@ private fun SeriesScreenInternal( series.seasons.firstOrNull { it.id == selectedSeasonId } ?: getDefaultSeason() val nextUpEpisode = selectedSeason?.episodes?.firstOrNull { !it.watched } ?: selectedSeason?.episodes?.firstOrNull() + val playAction = remember(nextUpEpisode, offline) { + nextUpEpisode?.let { episode -> + { + navigationManager.navigate( + Route.EpisodeRoute( + EpisodeDto( + id = episode.id, + seasonId = episode.seasonId, + seriesId = episode.seriesId, + offline = offline, + ) + ) + ) + val intent = Intent(context, PlayerActivity::class.java) + intent.putExtra("MEDIA_ID", episode.id.toString()) + context.startActivity(intent) + } + } + } LaunchedEffect(series.id, selectedSeason?.id) { selectedSeason?.let { onLoadSeasonEpisodes(series.id, it.id) } @@ -145,7 +182,13 @@ private fun SeriesScreenInternal( } MediaDetailScaffold( - imageUrl = ImageUrlBuilder.finishImageUrl(series.imageUrlPrefix, ArtworkKind.PRIMARY), + uiModel = series.toMediaDetailScaffoldUiModel( + selectedSeason = selectedSeason, + nextUpEpisode = nextUpEpisode, + onPlayClick = playAction ?: {}, + onDownloadClick = { showDownloadDialog = true }, + bodyColor = scheme.onSurface + ), modifier = modifier, topBar = { scrollBehavior -> SeriesTopBar( @@ -154,32 +197,6 @@ private fun SeriesScreenInternal( ) }, ) { _modifier -> - SeriesHeroContent( - series = series, - modifier = _modifier - ) - if (selectedSeason != null) { - SeriesActionButtons( - nextUpEpisode = nextUpEpisode, - selectedSeason = selectedSeason, - seriesDownloadState = seriesDownloadState, - seasonDownloadState = seasonDownloadState, - isSmartDownloadEnabled = isSmartDownloadEnabled, - offline = offline, - onDownloadOptionSelected = { option -> - onDownloadOptionSelected(option, selectedSeason) - }, - modifier = _modifier - ) - } - MediaSynopsis( - synopsis = series.synopsis, - bodyColor = scheme.onSurface, - bodyFontSize = 13.sp, - bodyLineHeight = null, - titleSpacing = 8.dp, - modifier = _modifier - ) if (selectedSeason != null) { SeasonTabs( seasons = series.seasons, @@ -199,40 +216,74 @@ private fun SeriesScreenInternal( modifier = _modifier ) } - if (series.cast.isNotEmpty()) { - Text( - text = "Cast", - color = scheme.onBackground, - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - modifier = _modifier - ) - Spacer(modifier = _modifier.height(12.dp)) - CastRow(cast = series.cast) - } + } + + if (showDownloadDialog && selectedSeason != null) { + DownloadOptionsBottomSheet( + selectedSeasonName = selectedSeason.name, + seriesDownloadState = seriesDownloadState, + seasonDownloadState = seasonDownloadState, + isSmartDownloadEnabled = isSmartDownloadEnabled, + onDownloadOptionSelected = { + showDownloadDialog = false + onDownloadOptionSelected(it, selectedSeason) + }, + onDismiss = { showDownloadDialog = false } + ) } } -@Composable -private fun SeriesHeroContent( - series: Series, - modifier: Modifier = Modifier, -) { - val scheme = MaterialTheme.colorScheme - - Text( - text = series.name, - color = scheme.onBackground, - fontSize = 30.sp, - fontWeight = FontWeight.Bold, - lineHeight = 36.sp, - modifier = modifier - ) - SeriesMetaChips( - series = series, - modifier = modifier - ) -} +private fun Series.toMediaDetailScaffoldUiModel( + selectedSeason: Season?, + nextUpEpisode: Episode?, + onPlayClick: () -> Unit, + onDownloadClick: () -> Unit, + bodyColor: Color, +): MediaDetailScaffoldUiModel = MediaDetailScaffoldUiModel( + imageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY), + title = name, + titleFontSize = 30.sp, + titleLineHeight = 36.sp, + metadataItems = listOf(year, "$seasonCount Seasons"), + actions = selectedSeason?.let { + MediaDetailActionsUiModel( + primaryAction = MediaDetailPrimaryActionUiModel( + text = mediaPlayButtonText(nextUpEpisode?.progress, nextUpEpisode?.watched), + progress = mediaPlaybackProgress(nextUpEpisode?.progress), + onClick = onPlayClick, + testTag = SeriesPlayButtonTag + ), + secondaryActions = listOf( + MediaDetailSecondaryActionUiModel.Icon( + icon = Icons.Outlined.Add, + testTag = SeriesAddButtonTag + ), + MediaDetailSecondaryActionUiModel.Icon( + icon = Icons.Outlined.Download, + onClick = onDownloadClick, + testTag = SeriesDownloadButtonTag + ) + ) + ) + }, + synopsis = MediaDetailSynopsisUiModel( + text = synopsis, + bodyColor = bodyColor, + bodyFontSize = 13.sp, + bodyLineHeight = null, + titleSpacing = 8.dp + ), + cast = if (cast.isNotEmpty()) { + MediaDetailCastUiModel( + members = cast, + cardWidth = 84.dp, + nameFontSize = 11.sp, + roleFontSize = 10.sp + ) + } else { + null + } +) private fun SeriesDownloadOption.canPerform( seriesDownloadState: DownloadState, @@ -250,4 +301,4 @@ private fun SeriesDownloadOption.requiresNotificationPermission(): Boolean = whe SeriesDownloadOption.SERIES, SeriesDownloadOption.SMART -> true SeriesDownloadOption.DELETE_SMART -> false -} \ No newline at end of file +} diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt index dc2b833c..f4f20a1e 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt @@ -1,6 +1,5 @@ package hu.bbara.purefin.ui.screen.series.components -import android.content.Intent import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -8,16 +7,13 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.ArrowDropDown import androidx.compose.material.icons.outlined.AutoAwesome @@ -45,7 +41,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -56,23 +51,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.EpisodeDto -import hu.bbara.purefin.core.navigation.Route 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.navigation.LocalNavigationManager -import hu.bbara.purefin.player.PlayerActivity import hu.bbara.purefin.ui.common.badge.WatchStateBadge import hu.bbara.purefin.ui.common.bar.MediaProgressBar import hu.bbara.purefin.ui.common.button.GhostIconButton -import hu.bbara.purefin.ui.common.button.MediaActionButton -import hu.bbara.purefin.ui.common.button.MediaResumeButton import hu.bbara.purefin.ui.common.image.PurefinAsyncImage -import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow -import hu.bbara.purefin.ui.common.media.mediaPlayButtonText -import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar @OptIn(ExperimentalMaterial3Api::class) @@ -100,96 +85,6 @@ internal fun SeriesTopBar( ) } -@Composable -internal fun SeriesMetaChips( - series: Series, - modifier: Modifier = Modifier -) { - MediaMetadataFlowRow( - items = listOf(series.year, "${series.seasonCount} Seasons"), - modifier = modifier - ) -} - -@Composable -internal fun SeriesActionButtons( - nextUpEpisode: Episode?, - selectedSeason: Season, - seriesDownloadState: DownloadState, - seasonDownloadState: DownloadState, - isSmartDownloadEnabled: Boolean, - offline: Boolean, - onDownloadOptionSelected: (SeriesDownloadOption) -> Unit, - modifier: Modifier = Modifier, -) { - val context = LocalContext.current - val navigationManager = LocalNavigationManager.current - var showDownloadDialog by remember { mutableStateOf(false) } - val playAction = remember(nextUpEpisode, offline) { - nextUpEpisode?.let { episode -> - { - navigationManager.navigate( - Route.EpisodeRoute( - EpisodeDto( - id = episode.id, - seasonId = episode.seasonId, - seriesId = episode.seriesId, - offline = offline, - ) - ) - ) - val intent = Intent(context, PlayerActivity::class.java) - intent.putExtra("MEDIA_ID", episode.id.toString()) - context.startActivity(intent) - } - } - } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - ) { - MediaResumeButton( - text = mediaPlayButtonText(nextUpEpisode?.progress, nextUpEpisode?.watched), - progress = mediaPlaybackProgress(nextUpEpisode?.progress), - onClick = playAction ?: {}, - modifier = Modifier - .sizeIn(maxWidth = 200.dp) - .testTag(SeriesPlayButtonTag) - ) - Spacer(modifier = Modifier.width(12.dp)) - MediaActionButton( - backgroundColor = MaterialTheme.colorScheme.surface, - iconColor = MaterialTheme.colorScheme.onSurface, - icon = Icons.Outlined.Add, - height = 48.dp, - modifier = Modifier.testTag(SeriesAddButtonTag) - ) - Spacer(modifier = Modifier.width(12.dp)) - MediaActionButton( - backgroundColor = MaterialTheme.colorScheme.surface, - iconColor = MaterialTheme.colorScheme.onSurface, - icon = Icons.Outlined.Download, - height = 48.dp, - modifier = Modifier.testTag(SeriesDownloadButtonTag), - onClick = { showDownloadDialog = true } - ) - } - - if (showDownloadDialog) { - DownloadOptionsBottomSheet( - selectedSeasonName = selectedSeason.name, - seriesDownloadState = seriesDownloadState, - seasonDownloadState = seasonDownloadState, - isSmartDownloadEnabled = isSmartDownloadEnabled, - onDownloadOptionSelected = { - showDownloadDialog = false - onDownloadOptionSelected(it) - }, - onDismiss = { showDownloadDialog = false } - ) - } -} - internal enum class SeriesDownloadOption { SEASON, SERIES, @@ -199,7 +94,7 @@ internal enum class SeriesDownloadOption { @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun DownloadOptionsBottomSheet( +internal fun DownloadOptionsBottomSheet( selectedSeasonName: String, seriesDownloadState: DownloadState, seasonDownloadState: DownloadState,