feat: add smart download feature for series

Automatically manages downloaded episodes per series — keeps 5 unwatched
episodes downloaded, removing watched ones and fetching new ones on
HomeScreen open or pull-to-refresh. A single download button on the
Series screen opens a dialog to choose between downloading all episodes
or enabling smart download.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 22:21:21 +01:00
parent 2d278bd348
commit 3941c67d8b
9 changed files with 230 additions and 12 deletions

View File

@@ -39,6 +39,13 @@ class SeriesViewModel @Inject constructor(
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
@OptIn(ExperimentalCoroutinesApi::class)
val isSmartDownloadEnabled: StateFlow<Boolean> = _seriesId
.flatMapLatest { id ->
if (id != null) mediaDownloadManager.isSmartDownloadEnabled(id) else flowOf(false)
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
private val _seriesDownloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
val seriesDownloadState: StateFlow<DownloadState> = _seriesDownloadState
@@ -76,6 +83,16 @@ class SeriesViewModel @Inject constructor(
}
}
fun toggleSmartDownload(seriesId: UUID) {
viewModelScope.launch {
if (isSmartDownloadEnabled.value) {
mediaDownloadManager.disableSmartDownload(seriesId)
} else {
mediaDownloadManager.enableSmartDownload(seriesId)
}
}
}
fun downloadSeries(series: Series) {
viewModelScope.launch {
val allEpisodeIds = series.seasons.flatMap { season ->

View File

@@ -12,6 +12,7 @@ import hu.bbara.purefin.core.data.navigation.NavigationManager
import hu.bbara.purefin.core.data.navigation.Route
import hu.bbara.purefin.core.data.navigation.SeriesDto
import hu.bbara.purefin.core.data.session.UserSessionRepository
import hu.bbara.purefin.feature.download.MediaDownloadManager
import hu.bbara.purefin.core.model.Media
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -31,7 +32,8 @@ class HomePageViewModel @Inject constructor(
private val appContentRepository: AppContentRepository,
private val userSessionRepository: UserSessionRepository,
private val navigationManager: NavigationManager,
private val refreshHomeDataUseCase: RefreshHomeDataUseCase
private val refreshHomeDataUseCase: RefreshHomeDataUseCase,
private val mediaDownloadManager: MediaDownloadManager,
) : ViewModel() {
private val _isRefreshing = MutableStateFlow(false)
@@ -199,6 +201,11 @@ class HomePageViewModel @Inject constructor(
// Refresh is best-effort; don't crash on failure
}
}
viewModelScope.launch {
try {
mediaDownloadManager.syncSmartDownloads()
} catch (_: Exception) { }
}
}
fun onRefresh() {
@@ -212,6 +219,11 @@ class HomePageViewModel @Inject constructor(
_isRefreshing.value = false
}
}
viewModelScope.launch {
try {
mediaDownloadManager.syncSmartDownloads()
} catch (_: Exception) { }
}
}
fun logout() {