From 3c16817442dbefd58da7060aa0f9151aa594fdb4 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Thu, 30 Apr 2026 21:18:43 +0200 Subject: [PATCH] fix(tv): focus next up episode in series details --- .../screen/series/SeriesScreenContentTest.kt | 10 +- .../purefin/ui/screen/movie/TvMovieScreen.kt | 53 ++---- .../movie/components/TvMovieComponents.kt | 44 ++++- .../ui/screen/series/TvSeriesScreen.kt | 91 +++++----- .../series/components/TvSeriesComponents.kt | 161 ++++++++---------- 5 files changed, 172 insertions(+), 187 deletions(-) diff --git a/app-tv/src/androidTest/java/hu/bbara/purefin/ui/screen/series/SeriesScreenContentTest.kt b/app-tv/src/androidTest/java/hu/bbara/purefin/ui/screen/series/SeriesScreenContentTest.kt index 7876a957..0a1d681f 100644 --- a/app-tv/src/androidTest/java/hu/bbara/purefin/ui/screen/series/SeriesScreenContentTest.kt +++ b/app-tv/src/androidTest/java/hu/bbara/purefin/ui/screen/series/SeriesScreenContentTest.kt @@ -17,7 +17,7 @@ import hu.bbara.purefin.model.Season import hu.bbara.purefin.model.Series import hu.bbara.purefin.ui.theme.AppTheme import hu.bbara.purefin.ui.screen.series.components.SeriesFirstSeasonTabTag -import hu.bbara.purefin.ui.screen.series.components.SeriesPlayButtonTag +import hu.bbara.purefin.ui.screen.series.components.SeriesNextUpEpisodeCardTag import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test @@ -30,7 +30,7 @@ class SeriesScreenContentTest { val composeRule = createAndroidComposeRule() @Test - fun seriesScreenContent_focusesPrimaryAction_whenNextUpExists() { + fun seriesScreenContent_focusesNextUpEpisode_whenNextUpExists() { composeRule.setContent { AppTheme { TvSeriesScreenContent( @@ -44,8 +44,7 @@ class SeriesScreenContentTest { composeRule.onNodeWithText("Severance").assertIsDisplayed() composeRule.onNodeWithText("Overview").assertIsDisplayed() - composeRule.onNodeWithText("Continue Watching").assertIsDisplayed() - composeRule.onNodeWithTag(SeriesPlayButtonTag).assertIsDisplayed() + composeRule.onNodeWithTag(SeriesNextUpEpisodeCardTag).assertIsDisplayed() .assertIsFocused() composeRule.onNodeWithText("Season 1").assertIsDisplayed() composeRule.onNodeWithText("Good News About Hell").assertIsDisplayed() @@ -66,7 +65,6 @@ class SeriesScreenContentTest { composeRule.waitForIdle() composeRule.onNodeWithText("Overview").assertIsDisplayed() - composeRule.onNodeWithText("Choose a season below to start watching.").assertIsDisplayed() composeRule.onNodeWithTag(SeriesFirstSeasonTabTag) .assertIsDisplayed() .assertIsFocused() @@ -87,7 +85,7 @@ class SeriesScreenContentTest { composeRule.waitForIdle() - composeRule.onNodeWithTag(SeriesPlayButtonTag).assertIsFocused() + composeRule.onNodeWithTag(SeriesNextUpEpisodeCardTag).assertIsFocused() composeRule.onRoot().performKeyInput { pressKey(Key.DirectionCenter) } diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/TvMovieScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/TvMovieScreen.kt index 28c344c5..8098e262 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/TvMovieScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/TvMovieScreen.kt @@ -1,9 +1,9 @@ package hu.bbara.purefin.ui.screen.movie 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.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -11,14 +11,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.feature.content.movie.MovieScreenViewModel import hu.bbara.purefin.model.Movie import hu.bbara.purefin.navigation.MovieDto -import hu.bbara.purefin.ui.common.media.MediaDetailOverviewSection -import hu.bbara.purefin.ui.common.media.MediaDetailPlaybackSection -import hu.bbara.purefin.ui.common.media.MediaDetailSectionTitle +import hu.bbara.purefin.ui.common.media.MediaDetailHorizontalPadding import hu.bbara.purefin.ui.common.media.TvMediaDetailBodyBox import hu.bbara.purefin.ui.common.media.TvMediaDetailScaffold import hu.bbara.purefin.ui.common.media.tvMediaDetailBackgroundImageUrl @@ -67,38 +64,20 @@ internal fun TvMovieScreenContent( ) { TvMediaDetailBodyBox( backgroundImageUrl = tvMediaDetailBackgroundImageUrl(movie.imageUrlPrefix), - modifier = it + modifier = Modifier.fillMaxSize(), + heightFraction = 1f ) { - TvMovieHeroSection( - movie = movie, - onPlay = onPlay, - playFocusRequester = playFocusRequester, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(2.dp)) - } - Column(modifier = it.fillMaxWidth()) { - Spacer(modifier = Modifier.height(4.dp)) - MediaDetailOverviewSection( - synopsis = movie.synopsis, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(8.dp)) - } - Column(modifier = it.fillMaxWidth()) { - MediaDetailPlaybackSection( - audioTrack = "ENG", - subtitles = "ENG", - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(8.dp)) - } - if (movie.cast.isNotEmpty()) { - Column(modifier = it.fillMaxWidth()) { - MediaDetailSectionTitle(text = "Cast") - Spacer(modifier = Modifier.height(14.dp)) -// MediaCastRow(cast = movie.cast) - Spacer(modifier = Modifier.height(24.dp)) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = MediaDetailHorizontalPadding) + ) { + TvMovieHeroSection( + movie = movie, + onPlay = onPlay, + playFocusRequester = playFocusRequester, + modifier = Modifier.fillMaxWidth() + ) } } } diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/components/TvMovieComponents.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/components/TvMovieComponents.kt index 47906a1e..3c158700 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/components/TvMovieComponents.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/components/TvMovieComponents.kt @@ -4,8 +4,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer 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.widthIn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -17,11 +17,11 @@ 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.model.Movie import hu.bbara.purefin.ui.common.button.MediaResumeButton import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow -import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress import hu.bbara.purefin.ui.common.media.mediaPlayButtonText -import hu.bbara.purefin.model.Movie +import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress internal const val MoviePlayButtonTag = "movie-play-button" @@ -33,11 +33,12 @@ internal fun TvMovieHeroSection( modifier: Modifier = Modifier ) { val scheme = MaterialTheme.colorScheme + val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.85f) Column( modifier = modifier + .padding(top = 24.dp) .fillMaxWidth() - .widthIn(max = 560.dp) ) { Text( text = movie.title, @@ -53,7 +54,40 @@ internal fun TvMovieHeroSection( items = listOf(movie.year, movie.rating, movie.runtime, movie.format), highlightedItem = movie.format ) - Spacer(modifier = Modifier.height(6.dp)) + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = "Overview", + color = scheme.onBackground, + fontSize = 13.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = movie.synopsis, + color = mutedStrong, + fontSize = 12.sp, + lineHeight = 16.sp, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(0.4f) + ) + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = "Playback", + color = scheme.onBackground, + fontSize = 13.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = "ENG • Subtitles ENG", + color = mutedStrong, + fontSize = 11.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(12.dp)) MediaResumeButton( text = mediaPlayButtonText(movie.progress, movie.watched), progress = mediaPlaybackProgress(movie.progress), diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/TvSeriesScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/TvSeriesScreen.kt index 2e054e40..5c91f0b5 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/TvSeriesScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/TvSeriesScreen.kt @@ -2,8 +2,10 @@ package hu.bbara.purefin.ui.screen.series 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.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -17,15 +19,14 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.feature.content.series.SeriesViewModel +import hu.bbara.purefin.model.Episode import hu.bbara.purefin.model.Season import hu.bbara.purefin.model.Series import hu.bbara.purefin.navigation.SeriesDto -import hu.bbara.purefin.ui.common.media.MediaDetailOverviewSection -import hu.bbara.purefin.ui.common.media.MediaDetailSectionTitle +import hu.bbara.purefin.ui.common.media.MediaDetailHorizontalPadding import hu.bbara.purefin.ui.common.media.TvMediaDetailBodyBox import hu.bbara.purefin.ui.common.media.TvMediaDetailScaffold import hu.bbara.purefin.ui.common.media.tvMediaDetailBackgroundImageUrl -import hu.bbara.purefin.ui.screen.series.components.CastRow import hu.bbara.purefin.ui.screen.series.components.SeriesFirstSeasonTabTag import hu.bbara.purefin.ui.screen.series.components.TvEpisodeCarousel import hu.bbara.purefin.ui.screen.series.components.TvSeasonTabs @@ -63,18 +64,16 @@ internal fun TvSeriesScreenContent( onPlayEpisode: (UUID) -> Unit, modifier: Modifier = Modifier, ) { - var selectedSeason by remember(series.id) { mutableStateOf(series.defaultSeason()) } val nextUpEpisode = remember(series.id) { series.nextUpEpisode() } - val playFocusRequester = remember { FocusRequester() } + var selectedSeason by remember(series.id, nextUpEpisode?.seasonId) { + mutableStateOf(series.defaultSeason(nextUpEpisode)) + } val firstContentFocusRequester = remember { FocusRequester() } LaunchedEffect(series.id, nextUpEpisode?.id) { + if (nextUpEpisode != null) return@LaunchedEffect withFrameNanos { } - if (nextUpEpisode != null) { - playFocusRequester.requestFocus() - } else { - firstContentFocusRequester.requestFocus() - } + firstContentFocusRequester.requestFocus() } TvMediaDetailScaffold( @@ -83,52 +82,44 @@ internal fun TvSeriesScreenContent( ) { TvMediaDetailBodyBox( backgroundImageUrl = tvMediaDetailBackgroundImageUrl(series.imageUrlPrefix), - modifier = it + modifier = Modifier.fillMaxSize(), + heightFraction = 1f ) { - TvSeriesHeroSection( - series = series, - nextUpEpisode = nextUpEpisode, - onPlayEpisode = { onPlayEpisode(it.id) }, - playFocusRequester = playFocusRequester, - firstContentFocusRequester = firstContentFocusRequester, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(2.dp)) - } - Column(modifier = it.fillMaxWidth()) { - Spacer(modifier = Modifier.height(4.dp)) - MediaDetailOverviewSection( - synopsis = series.synopsis, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(8.dp)) - } - TvSeasonTabs( - seasons = series.seasons, - selectedSeason = selectedSeason, - firstItemFocusRequester = firstContentFocusRequester, - firstItemTestTag = SeriesFirstSeasonTabTag, - onSelect = { selectedSeason = it }, - modifier = it - ) - Spacer(modifier = Modifier.height(8.dp)) - TvEpisodeCarousel( - episodes = selectedSeason.episodes, - modifier = it - ) - if (series.cast.isNotEmpty()) { - Column(modifier = it) { - Spacer(modifier = Modifier.height(20.dp)) - MediaDetailSectionTitle(text = "Cast") - Spacer(modifier = Modifier.height(14.dp)) - CastRow(cast = series.cast) - Spacer(modifier = Modifier.height(24.dp)) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = MediaDetailHorizontalPadding) + ) { + TvSeriesHeroSection( + series = series, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(18.dp)) + TvSeasonTabs( + seasons = series.seasons, + selectedSeason = selectedSeason, + firstItemFocusRequester = firstContentFocusRequester, + firstItemTestTag = SeriesFirstSeasonTabTag, + onSelect = { selectedSeason = it }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(4.dp)) + TvEpisodeCarousel( + episodes = selectedSeason.episodes, + onPlayEpisode = { onPlayEpisode(it.id) }, + focusedEpisodeId = nextUpEpisode?.id, + modifier = Modifier.fillMaxWidth() + ) } } } } -private fun Series.defaultSeason(): Season { +private fun Series.defaultSeason(nextUpEpisode: Episode?): Season { + if (nextUpEpisode != null) { + seasons.firstOrNull { it.id == nextUpEpisode.seasonId }?.let { return it } + } + for (season in seasons) { if (season.episodes.any { !it.watched }) { return season diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/components/TvSeriesComponents.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/components/TvSeriesComponents.kt index 2b9a6f99..12197b11 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/components/TvSeriesComponents.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/components/TvSeriesComponents.kt @@ -1,13 +1,16 @@ package hu.bbara.purefin.ui.screen.series.components import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.LocalBringIntoViewSpec import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio @@ -16,9 +19,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -30,16 +31,17 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color @@ -50,8 +52,6 @@ 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 androidx.hilt.navigation.compose.hiltViewModel -import hu.bbara.purefin.feature.content.series.SeriesViewModel import hu.bbara.purefin.image.ArtworkKind import hu.bbara.purefin.image.ImageUrlBuilder import hu.bbara.purefin.model.CastMember @@ -60,14 +60,13 @@ import hu.bbara.purefin.model.Season import hu.bbara.purefin.model.Series import hu.bbara.purefin.ui.common.badge.WatchStateBadge import hu.bbara.purefin.ui.common.bar.MediaProgressBar -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.TvHomeRowBringIntoViewSpec +import java.util.UUID -internal const val SeriesPlayButtonTag = "series-play-button" internal const val SeriesFirstSeasonTabTag = "series-first-season-tab" +internal const val SeriesNextUpEpisodeCardTag = "series-next-up-episode-card" @Composable internal fun TvSeriesMetaChips(series: Series) { @@ -152,26 +151,56 @@ private fun TvSeasonTab( } } +@OptIn(ExperimentalFoundationApi::class) @Composable -internal fun TvEpisodeCarousel(episodes: List, modifier: Modifier = Modifier) { +internal fun TvEpisodeCarousel( + episodes: List, + onPlayEpisode: (Episode) -> Unit, + modifier: Modifier = Modifier, + focusedEpisodeId: UUID? = null +) { val listState = rememberLazyListState() + val focusedEpisodeFocusRequester = remember { FocusRequester() } - LaunchedEffect(episodes) { + LaunchedEffect(episodes, focusedEpisodeId) { + val focusedEpisodeIndex = focusedEpisodeId?.let { id -> + episodes.indexOfFirst { it.id == id } + } ?: -1 val firstUnwatchedIndex = episodes.indexOfFirst { !it.watched }.let { if (it == -1) 0 else it } - if (firstUnwatchedIndex != 0) { - listState.animateScrollToItem(firstUnwatchedIndex) + val targetIndex = focusedEpisodeIndex.takeIf { it >= 0 } ?: firstUnwatchedIndex + + if (targetIndex != 0) { + listState.scrollToItem(targetIndex) } else { listState.scrollToItem(0) } + + if (focusedEpisodeIndex >= 0) { + withFrameNanos { } + focusedEpisodeFocusRequester.requestFocus() + } } - LazyRow( - state = listState, - modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - items(episodes, key = { episode -> episode.id }) { episode -> - TvEpisodeCard(episode = episode) + CompositionLocalProvider(LocalBringIntoViewSpec provides TvHomeRowBringIntoViewSpec) { + LazyRow( + state = listState, + modifier = modifier, + contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(episodes, key = { episode -> episode.id }) { episode -> + TvEpisodeCard( + episode = episode, + onPlayEpisode = { onPlayEpisode(episode) }, + modifier = if (episode.id == focusedEpisodeId) { + Modifier + .focusRequester(focusedEpisodeFocusRequester) + .testTag(SeriesNextUpEpisodeCardTag) + } else { + Modifier + } + ) + } } } } @@ -179,10 +208,6 @@ internal fun TvEpisodeCarousel(episodes: List, modifier: Modifier = Mod @Composable internal fun TvSeriesHeroSection( series: Series, - nextUpEpisode: Episode?, - onPlayEpisode: (Episode) -> Unit, - playFocusRequester: FocusRequester, - firstContentFocusRequester: FocusRequester, modifier: Modifier = Modifier ) { val scheme = MaterialTheme.colorScheme @@ -190,8 +215,8 @@ internal fun TvSeriesHeroSection( Column( modifier = modifier + .padding(top = 24.dp) .fillMaxWidth() - .widthIn(max = 560.dp) ) { Text( text = series.name, @@ -204,69 +229,31 @@ internal fun TvSeriesHeroSection( ) Spacer(modifier = Modifier.height(4.dp)) TvSeriesMetaChips(series = series) - Spacer(modifier = Modifier.height(6.dp)) - if (nextUpEpisode != null) { - Text( - text = nextUpEpisode.heroStatusText(), - color = scheme.primary, - fontSize = 12.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = nextUpEpisode.title, - color = scheme.onBackground, - fontSize = 14.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = "Episode ${nextUpEpisode.index} • ${nextUpEpisode.runtime}", - color = mutedStrong, - fontSize = 10.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Spacer(modifier = Modifier.height(6.dp)) - MediaResumeButton( - text = mediaPlayButtonText(nextUpEpisode.progress, nextUpEpisode.watched), - progress = mediaPlaybackProgress(nextUpEpisode.progress), - onClick = { onPlayEpisode(nextUpEpisode) }, - modifier = Modifier - .sizeIn(minWidth = 160.dp, maxWidth = 192.dp) - .focusRequester(playFocusRequester) - .focusProperties { down = firstContentFocusRequester } - .testTag(SeriesPlayButtonTag), - focusedScale = 1.08f, - focusHaloColor = scheme.primary.copy(alpha = 0.22f), - focusBorderWidth = 3.dp, - focusBorderColor = scheme.onBackground, - overlayBorderWidth = 2.dp, - overlayBorderColor = scheme.primary.copy(alpha = 0.95f), - focusable = true, - height = 40.dp, - textSize = 13.sp, - iconSize = 20.dp - ) - } else { - Text( - text = "Choose a season below to start watching.", - color = mutedStrong, - fontSize = 12.sp, - fontWeight = FontWeight.Medium - ) - } + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = "Overview", + color = scheme.onBackground, + fontSize = 13.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = series.synopsis, + color = mutedStrong, + fontSize = 12.sp, + lineHeight = 16.sp, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(0.4f) + ) } } @Composable private fun TvEpisodeCard( - viewModel: SeriesViewModel = hiltViewModel(), - episode: Episode + episode: Episode, + onPlayEpisode: () -> Unit, + modifier: Modifier = Modifier ) { val scheme = MaterialTheme.colorScheme val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f) @@ -274,11 +261,11 @@ private fun TvEpisodeCard( val scale by animateFloatAsState(targetValue = if (isFocused) 1.07f else 1.0f, label = "scale") Column( - modifier = Modifier + modifier = modifier .width(260.dp) .graphicsLayer { scaleX = scale; scaleY = scale } .onFocusChanged { isFocused = it.isFocused } - .clickable { viewModel.onPlayEpisode(episode.id) }, + .clickable { onPlayEpisode() }, verticalArrangement = Arrangement.spacedBy(12.dp) ) { Box( @@ -373,7 +360,3 @@ internal fun CastRow(cast: List, modifier: Modifier = Modifier) { // roleSize = 10.sp // ) } - -private fun Episode.heroStatusText(): String { - return if ((progress ?: 0.0) > 0.0 && !watched) "Continue Watching" else "Up Next" -}