mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
feat: add batch download for seasons and entire series
Wire up the existing download button on the Series screen to download all episodes, and add a per-season download button next to the season tabs. Episode metadata is fetched in parallel for faster queuing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,9 @@ 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.Cast
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.DownloadDone
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material.icons.outlined.PlayCircle
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -59,6 +61,7 @@ import hu.bbara.purefin.common.ui.components.MediaResumeButton
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
||||
import hu.bbara.purefin.common.ui.components.WatchStateIndicator
|
||||
import hu.bbara.purefin.feature.download.DownloadState
|
||||
import hu.bbara.purefin.core.model.CastMember
|
||||
import hu.bbara.purefin.core.model.Episode
|
||||
import hu.bbara.purefin.core.model.Season
|
||||
@@ -110,7 +113,12 @@ internal fun SeriesMetaChips(series: Series) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SeriesActionButtons(nextUpEpisode: Episode?, modifier: Modifier = Modifier) {
|
||||
internal fun SeriesActionButtons(
|
||||
nextUpEpisode: Episode?,
|
||||
downloadState: DownloadState,
|
||||
onDownloadClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val episodeId = nextUpEpisode?.id
|
||||
val playAction = remember(episodeId) {
|
||||
@@ -142,8 +150,14 @@ internal fun SeriesActionButtons(nextUpEpisode: Episode?, modifier: Modifier = M
|
||||
MediaActionButton(
|
||||
backgroundColor = MaterialTheme.colorScheme.secondary,
|
||||
iconColor = MaterialTheme.colorScheme.onSecondary,
|
||||
icon = Icons.Outlined.Download,
|
||||
height = 32.dp
|
||||
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 = 32.dp,
|
||||
onClick = onDownloadClick
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -152,6 +166,8 @@ internal fun SeriesActionButtons(nextUpEpisode: Episode?, modifier: Modifier = M
|
||||
internal fun SeasonTabs(
|
||||
seasons: List<Season>,
|
||||
selectedSeason: Season?,
|
||||
seasonDownloadState: DownloadState,
|
||||
onSeasonDownloadClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onSelect: (Season) -> Unit
|
||||
) {
|
||||
@@ -159,7 +175,8 @@ internal fun SeasonTabs(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(20.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
seasons.forEach { season ->
|
||||
SeasonTab(
|
||||
@@ -168,6 +185,18 @@ internal fun SeasonTabs(
|
||||
modifier = Modifier.clickable { onSelect(season) }
|
||||
)
|
||||
}
|
||||
MediaActionButton(
|
||||
backgroundColor = MaterialTheme.colorScheme.secondary,
|
||||
iconColor = MaterialTheme.colorScheme.onSecondary,
|
||||
icon = when (seasonDownloadState) {
|
||||
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 = 28.dp,
|
||||
onClick = onSeasonDownloadClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -27,6 +28,7 @@ import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.core.data.navigation.SeriesDto
|
||||
import hu.bbara.purefin.core.model.Season
|
||||
import hu.bbara.purefin.core.model.Series
|
||||
import hu.bbara.purefin.feature.download.DownloadState
|
||||
import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel
|
||||
|
||||
@Composable
|
||||
@@ -43,8 +45,12 @@ fun SeriesScreen(
|
||||
|
||||
val seriesData = series.value
|
||||
if (seriesData != null && seriesData.seasons.isNotEmpty()) {
|
||||
LaunchedEffect(seriesData) {
|
||||
viewModel.observeSeriesDownloadState(seriesData)
|
||||
}
|
||||
SeriesScreenInternal(
|
||||
series = seriesData,
|
||||
viewModel = viewModel,
|
||||
onBack = viewModel::onBack,
|
||||
modifier = modifier
|
||||
)
|
||||
@@ -56,6 +62,7 @@ fun SeriesScreen(
|
||||
@Composable
|
||||
private fun SeriesScreenInternal(
|
||||
series: Series,
|
||||
viewModel: SeriesViewModel,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -80,6 +87,13 @@ private fun SeriesScreenInternal(
|
||||
} ?: series.seasons.firstOrNull()?.episodes?.firstOrNull()
|
||||
}
|
||||
|
||||
val seriesDownloadState by viewModel.seriesDownloadState.collectAsState()
|
||||
val seasonDownloadState by viewModel.seasonDownloadState.collectAsState()
|
||||
|
||||
LaunchedEffect(selectedSeason.value) {
|
||||
viewModel.observeSeasonDownloadState(selectedSeason.value.episodes)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
@@ -117,7 +131,11 @@ private fun SeriesScreenInternal(
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
SeriesMetaChips(series = series)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
SeriesActionButtons(nextUpEpisode = nextUpEpisode)
|
||||
SeriesActionButtons(
|
||||
nextUpEpisode = nextUpEpisode,
|
||||
downloadState = seriesDownloadState,
|
||||
onDownloadClick = { viewModel.downloadSeries(series) }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
MediaSynopsis(
|
||||
synopsis = series.synopsis,
|
||||
@@ -130,6 +148,8 @@ private fun SeriesScreenInternal(
|
||||
SeasonTabs(
|
||||
seasons = series.seasons,
|
||||
selectedSeason = selectedSeason.value,
|
||||
seasonDownloadState = seasonDownloadState,
|
||||
onSeasonDownloadClick = { viewModel.downloadSeason(selectedSeason.value.episodes) },
|
||||
onSelect = { selectedSeason.value = it }
|
||||
)
|
||||
EpisodeCarousel(
|
||||
|
||||
Reference in New Issue
Block a user