Add play button to TV series screen

This commit is contained in:
2026-03-29 17:00:15 +02:00
parent 4b92af26ba
commit 0afa7334be
2 changed files with 49 additions and 2 deletions

View File

@@ -3,11 +3,13 @@ package hu.bbara.purefin.app.content.series
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -19,6 +21,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -26,10 +30,13 @@ import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.common.ui.MediaSynopsis import hu.bbara.purefin.common.ui.MediaSynopsis
import hu.bbara.purefin.common.ui.PurefinWaitingScreen import hu.bbara.purefin.common.ui.PurefinWaitingScreen
import hu.bbara.purefin.common.ui.components.MediaHero import hu.bbara.purefin.common.ui.components.MediaHero
import hu.bbara.purefin.common.ui.components.MediaResumeButton
import hu.bbara.purefin.core.data.navigation.SeriesDto import hu.bbara.purefin.core.data.navigation.SeriesDto
import hu.bbara.purefin.core.model.Episode
import hu.bbara.purefin.core.model.Season import hu.bbara.purefin.core.model.Season
import hu.bbara.purefin.core.model.Series import hu.bbara.purefin.core.model.Series
import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel
import org.jellyfin.sdk.model.UUID
@Composable @Composable
fun SeriesScreen( fun SeriesScreen(
@@ -48,6 +55,7 @@ fun SeriesScreen(
SeriesScreenInternal( SeriesScreenInternal(
series = seriesData, series = seriesData,
onBack = viewModel::onBack, onBack = viewModel::onBack,
onPlayEpisode = viewModel::onPlayEpisode,
modifier = modifier modifier = modifier
) )
} else { } else {
@@ -59,6 +67,7 @@ fun SeriesScreen(
private fun SeriesScreenInternal( private fun SeriesScreenInternal(
series: Series, series: Series,
onBack: () -> Unit, onBack: () -> Unit,
onPlayEpisode: (UUID) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
@@ -72,9 +81,23 @@ private fun SeriesScreenInternal(
} }
return series.seasons.first() return series.seasons.first()
} }
val selectedSeason = remember { mutableStateOf<Season>(getDefaultSeason()) } val selectedSeason = remember(series.id) { mutableStateOf(getDefaultSeason()) }
val nextUpEpisode = remember(series.id) {
series.seasons.firstNotNullOfOrNull { season ->
season.episodes.firstOrNull { !it.watched }
} ?: series.seasons.firstOrNull()?.episodes?.firstOrNull()
}
val playFocusRequester = remember { FocusRequester() }
val firstContentFocusRequester = remember { FocusRequester() } val firstContentFocusRequester = remember { FocusRequester() }
LaunchedEffect(series.id, nextUpEpisode?.id) {
if (nextUpEpisode != null) {
playFocusRequester.requestFocus()
} else {
firstContentFocusRequester.requestFocus()
}
}
Box( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@@ -104,6 +127,22 @@ private fun SeriesScreenInternal(
SeriesMetaChips(series = series) SeriesMetaChips(series = series)
} }
} }
if (nextUpEpisode != null) {
item {
Spacer(modifier = Modifier.height(24.dp))
Row(modifier = hPad) {
MediaResumeButton(
text = nextUpEpisode.playButtonText(),
progress = nextUpEpisode.progress?.div(100)?.toFloat() ?: 0f,
onClick = { onPlayEpisode(nextUpEpisode.id) },
modifier = Modifier
.sizeIn(maxWidth = 200.dp)
.focusRequester(playFocusRequester)
.focusProperties { down = firstContentFocusRequester }
)
}
}
}
item { item {
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
MediaSynopsis( MediaSynopsis(
@@ -150,8 +189,12 @@ private fun SeriesScreenInternal(
} }
SeriesTopBar( SeriesTopBar(
onBack = onBack, onBack = onBack,
downFocusRequester = firstContentFocusRequester, downFocusRequester = nextUpEpisode?.let { playFocusRequester } ?: firstContentFocusRequester,
modifier = Modifier.align(Alignment.TopStart) modifier = Modifier.align(Alignment.TopStart)
) )
} }
} }
private fun Episode.playButtonText(): String {
return if ((progress ?: 0.0) > 0.0 && !watched) "Resume" else "Play"
}

View File

@@ -114,6 +114,10 @@ class SeriesViewModel @Inject constructor(
)) ))
} }
fun onPlayEpisode(episodeId: UUID) {
navigationManager.navigate(Route.PlayerRoute(mediaId = episodeId.toString()))
}
fun onBack() { fun onBack() {
navigationManager.pop() navigationManager.pop()
} }