feat: refactor SeriesScreen and ViewModel for improved season episode loading and download handling

This commit is contained in:
2026-05-08 18:42:52 +02:00
parent dddf6e4cfa
commit 9d7c166bc8
9 changed files with 131 additions and 88 deletions

View File

@@ -12,6 +12,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
@@ -58,8 +59,12 @@ class CompositeLocalMediaRepository @Inject constructor(
.flatMapLatest { it.getEpisode(id) }
}
override fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> {
return activeRepository.flatMapLatest { it.observeSeriesWithContent(seriesId) }
override suspend fun loadSeasons(seriesId: UUID) {
activeRepository.first().loadSeasons(seriesId)
}
override suspend fun loadSeasonEpisodes(seriesId: UUID, seasonId: UUID) {
activeRepository.first().loadSeasonEpisodes(seriesId, seasonId)
}
override suspend fun updateWatchProgress(mediaId: UUID, positionMs: Long, durationMs: Long) {

View File

@@ -14,5 +14,6 @@ interface LocalMediaRepository : LocalMediaUpdater {
suspend fun getMovie(id: UUID): Flow<Movie?>
suspend fun getSeries(id: UUID): Flow<Series?>
suspend fun getEpisode(id: UUID): Flow<Episode?>
fun observeSeriesWithContent(seriesId: UUID): Flow<Series?>
suspend fun loadSeasons(seriesId: UUID)
suspend fun loadSeasonEpisodes(seriesId: UUID, seasonId: UUID)
}

View File

@@ -11,16 +11,16 @@ import hu.bbara.purefin.core.navigation.NavigationManager
import hu.bbara.purefin.core.navigation.Route
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Series
import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
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.first
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject
@HiltViewModel
@@ -33,11 +33,12 @@ class SeriesViewModel @Inject constructor(
private val _seriesId = MutableStateFlow<UUID?>(null)
@OptIn(ExperimentalCoroutinesApi::class)
val series: StateFlow<Series?> = _seriesId
.flatMapLatest { id ->
if (id != null) mediaCatalogReader.observeSeriesWithContent(id) else flowOf(null)
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
val series: StateFlow<Series?> = combine(
_seriesId,
mediaCatalogReader.series
) { seriesId, seriesMap ->
seriesId?.let { seriesMap[it] }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
private val _seriesDownloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
val seriesDownloadState: StateFlow<DownloadState> = _seriesDownloadState
@@ -45,8 +46,12 @@ class SeriesViewModel @Inject constructor(
private val _seasonDownloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
val seasonDownloadState: StateFlow<DownloadState> = _seasonDownloadState
private var seasonDownloadStateJob: Job? = null
private var seriesDownloadStateJob: Job? = null
fun observeSeasonDownloadState(episodes: List<Episode>) {
viewModelScope.launch {
seasonDownloadStateJob?.cancel()
seasonDownloadStateJob = viewModelScope.launch {
if (episodes.isEmpty()) {
_seasonDownloadState.value = DownloadState.NotDownloaded
return@launch
@@ -58,9 +63,11 @@ class SeriesViewModel @Inject constructor(
}
fun observeSeriesDownloadState(series: Series) {
viewModelScope.launch {
seriesDownloadStateJob?.cancel()
seriesDownloadStateJob = viewModelScope.launch {
val allEpisodes = series.seasons.flatMap { it.episodes }
if (allEpisodes.isEmpty()) {
val hasUnloadedSeasons = series.seasons.any { it.episodes.isEmpty() && it.episodeCount > 0 }
if (allEpisodes.isEmpty() || hasUnloadedSeasons) {
_seriesDownloadState.value = DownloadState.NotDownloaded
return@launch
}
@@ -70,8 +77,21 @@ class SeriesViewModel @Inject constructor(
}
}
fun downloadSeason(episodes: List<Episode>) {
fun loadSeasonEpisodes(seriesId: UUID, seasonId: UUID) {
viewModelScope.launch {
mediaCatalogReader.loadSeasonEpisodes(seriesId, seasonId)
}
}
fun downloadSeason(seriesId: UUID, seasonId: UUID) {
viewModelScope.launch {
mediaCatalogReader.loadSeasonEpisodes(seriesId, seasonId)
val episodes = mediaCatalogReader.getSeries(seriesId)
.first()
?.seasons
?.firstOrNull { it.id == seasonId }
?.episodes
.orEmpty()
mediaDownloadManager.downloadEpisodes(episodes.map { it.id })
}
}
@@ -82,11 +102,16 @@ class SeriesViewModel @Inject constructor(
}
}
fun downloadSeries(series: Series) {
fun downloadSeries(seriesData: Series) {
viewModelScope.launch {
val allEpisodeIds = series.seasons.flatMap { season ->
season.episodes.map { it.id }
seriesData.seasons.forEach { season ->
mediaCatalogReader.loadSeasonEpisodes(seriesData.id, season.id)
}
val allEpisodeIds = mediaCatalogReader.getSeries(seriesData.id)
.first()
?.seasons
.orEmpty()
.flatMap { season -> season.episodes.map { it.id } }
mediaDownloadManager.downloadEpisodes(allEpisodeIds)
}
}
@@ -129,5 +154,8 @@ class SeriesViewModel @Inject constructor(
fun selectSeries(seriesId: UUID) {
_seriesId.value = seriesId
viewModelScope.launch {
mediaCatalogReader.loadSeasons(seriesId)
}
}
}