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:
@@ -7,14 +7,19 @@ import hu.bbara.purefin.core.data.MediaRepository
|
||||
import hu.bbara.purefin.core.data.navigation.EpisodeDto
|
||||
import hu.bbara.purefin.core.data.navigation.NavigationManager
|
||||
import hu.bbara.purefin.core.data.navigation.Route
|
||||
import hu.bbara.purefin.core.model.Episode
|
||||
import hu.bbara.purefin.core.model.Series
|
||||
import hu.bbara.purefin.feature.download.DownloadState
|
||||
import hu.bbara.purefin.feature.download.MediaDownloadManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -22,6 +27,7 @@ import javax.inject.Inject
|
||||
class SeriesViewModel @Inject constructor(
|
||||
private val mediaRepository: MediaRepository,
|
||||
private val navigationManager: NavigationManager,
|
||||
private val mediaDownloadManager: MediaDownloadManager,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _seriesId = MutableStateFlow<UUID?>(null)
|
||||
@@ -33,6 +39,65 @@ class SeriesViewModel @Inject constructor(
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
||||
|
||||
private val _seriesDownloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
||||
val seriesDownloadState: StateFlow<DownloadState> = _seriesDownloadState
|
||||
|
||||
private val _seasonDownloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
||||
val seasonDownloadState: StateFlow<DownloadState> = _seasonDownloadState
|
||||
|
||||
fun observeSeasonDownloadState(episodes: List<Episode>) {
|
||||
viewModelScope.launch {
|
||||
if (episodes.isEmpty()) {
|
||||
_seasonDownloadState.value = DownloadState.NotDownloaded
|
||||
return@launch
|
||||
}
|
||||
val flows = episodes.map { mediaDownloadManager.observeDownloadState(it.id.toString()) }
|
||||
combine(flows) { states -> aggregateDownloadStates(states.toList()) }
|
||||
.collect { _seasonDownloadState.value = it }
|
||||
}
|
||||
}
|
||||
|
||||
fun observeSeriesDownloadState(series: Series) {
|
||||
viewModelScope.launch {
|
||||
val allEpisodes = series.seasons.flatMap { it.episodes }
|
||||
if (allEpisodes.isEmpty()) {
|
||||
_seriesDownloadState.value = DownloadState.NotDownloaded
|
||||
return@launch
|
||||
}
|
||||
val flows = allEpisodes.map { mediaDownloadManager.observeDownloadState(it.id.toString()) }
|
||||
combine(flows) { states -> aggregateDownloadStates(states.toList()) }
|
||||
.collect { _seriesDownloadState.value = it }
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadSeason(episodes: List<Episode>) {
|
||||
viewModelScope.launch {
|
||||
mediaDownloadManager.downloadEpisodes(episodes.map { it.id })
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadSeries(series: Series) {
|
||||
viewModelScope.launch {
|
||||
val allEpisodeIds = series.seasons.flatMap { season ->
|
||||
season.episodes.map { it.id }
|
||||
}
|
||||
mediaDownloadManager.downloadEpisodes(allEpisodeIds)
|
||||
}
|
||||
}
|
||||
|
||||
private fun aggregateDownloadStates(states: List<DownloadState>): DownloadState {
|
||||
if (states.isEmpty()) return DownloadState.NotDownloaded
|
||||
if (states.all { it is DownloadState.Downloaded }) return DownloadState.Downloaded
|
||||
if (states.any { it is DownloadState.Downloading }) {
|
||||
val avg = states.filterIsInstance<DownloadState.Downloading>()
|
||||
.map { it.progressPercent }
|
||||
.average()
|
||||
.toFloat()
|
||||
return DownloadState.Downloading(avg)
|
||||
}
|
||||
return DownloadState.NotDownloaded
|
||||
}
|
||||
|
||||
fun onSelectEpisode(seriesId: UUID, seasonId: UUID, episodeId: UUID) {
|
||||
navigationManager.navigate(Route.EpisodeRoute(
|
||||
EpisodeDto(
|
||||
|
||||
Reference in New Issue
Block a user