feat: add smart download management with enable and delete options

This commit is contained in:
2026-05-14 14:05:51 +02:00
parent f60aba5a72
commit a0a51ef436
7 changed files with 66 additions and 7 deletions

View File

@@ -60,6 +60,7 @@ fun SeriesScreen(
series = seriesData,
seriesDownloadState = viewModel.seriesDownloadState.collectAsState().value,
seasonDownloadState = viewModel.seasonDownloadState.collectAsState().value,
isSmartDownloadEnabled = viewModel.isSmartDownloadEnabled.collectAsState().value,
onDownloadOptionSelected = { option, selectedSeason ->
when (option) {
SeriesDownloadOption.SEASON ->
@@ -70,6 +71,9 @@ fun SeriesScreen(
SeriesDownloadOption.SMART ->
viewModel.enableSmartDownload(seriesData.id)
SeriesDownloadOption.DELETE_SMART ->
viewModel.deleteSmartDownloads(seriesData.id)
}
},
onLoadSeasonEpisodes = viewModel::loadSeasonEpisodes,
@@ -88,6 +92,7 @@ private fun SeriesScreenInternal(
series: Series,
seriesDownloadState: DownloadState,
seasonDownloadState: DownloadState,
isSmartDownloadEnabled: Boolean,
onDownloadOptionSelected: (SeriesDownloadOption, Season) -> Unit,
onLoadSeasonEpisodes: (UUID, UUID) -> Unit,
onObserveSeasonDownloadState: (List<Episode>) -> Unit,
@@ -130,6 +135,7 @@ private fun SeriesScreenInternal(
seriesDownloadState = seriesDownloadState,
selectedSeason = selectedSeason,
seasonDownloadState = seasonDownloadState,
isSmartDownloadEnabled = isSmartDownloadEnabled,
offline = offline,
onDownloadOptionSelected = { option ->
onDownloadOptionSelected(option, selectedSeason)
@@ -188,6 +194,7 @@ private fun SeriesScreenPreview() {
series = previewSeries(),
seriesDownloadState = DownloadState.Downloading(progressPercent = 0.58f),
seasonDownloadState = DownloadState.NotDownloaded,
isSmartDownloadEnabled = true,
onDownloadOptionSelected = { _, _ -> },
onLoadSeasonEpisodes = { _, _ -> },
onObserveSeasonDownloadState = {},

View File

@@ -33,6 +33,7 @@ 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.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -112,6 +113,7 @@ internal fun SeriesActionButtons(
seriesDownloadState: DownloadState,
selectedSeason: Season,
seasonDownloadState: DownloadState,
isSmartDownloadEnabled: Boolean,
offline: Boolean,
onDownloadOptionSelected: (SeriesDownloadOption) -> Unit,
modifier: Modifier = Modifier,
@@ -171,6 +173,7 @@ internal fun SeriesActionButtons(
DownloadOptionsDialog(
selectedSeasonName = selectedSeason.name,
seasonDownloadState = seasonDownloadState,
isSmartDownloadEnabled = isSmartDownloadEnabled,
onDownloadOptionSelected = {
showDownloadDialog = false
onDownloadOptionSelected(it)
@@ -183,13 +186,15 @@ internal fun SeriesActionButtons(
internal enum class SeriesDownloadOption {
SEASON,
SERIES,
SMART
SMART,
DELETE_SMART
}
@Composable
private fun DownloadOptionsDialog(
selectedSeasonName: String,
seasonDownloadState: DownloadState,
isSmartDownloadEnabled: Boolean,
onDownloadOptionSelected: (SeriesDownloadOption) -> Unit,
onDismiss: () -> Unit,
) {
@@ -221,10 +226,21 @@ private fun DownloadOptionsDialog(
}
},
dismissButton = {
if (isSmartDownloadEnabled) {
TextButton(
onClick = { onDownloadOptionSelected(SeriesDownloadOption.DELETE_SMART) },
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.error
)
) {
Text("Delete Smart Downloads")
}
} else {
TextButton(onClick = { onDownloadOptionSelected(SeriesDownloadOption.SMART) }) {
Text("Smart Download")
}
}
}
)
}

View File

@@ -13,5 +13,7 @@ interface MediaDownloadController {
suspend fun downloadEpisodes(episodeIds: List<UUID>)
suspend fun cancelEpisodeDownload(episodeId: UUID)
suspend fun enableSmartDownload(seriesId: UUID)
suspend fun deleteSmartDownloads(seriesId: UUID)
fun isSmartDownloadEnabled(seriesId: UUID): Flow<Boolean>
suspend fun syncSmartDownloads()
}

View File

@@ -206,11 +206,16 @@ class MediaDownloadManager @Inject constructor(
syncSmartDownloadsForSeries(seriesId)
}
suspend fun disableSmartDownload(seriesId: UUID) {
override suspend fun deleteSmartDownloads(seriesId: UUID) {
withContext(Dispatchers.IO) {
smartDownloadStore.disable(seriesId)
offlineCatalogStore.getEpisodesBySeries(seriesId).forEach { episode ->
cancelEpisodeDownload(episode.id)
}
}
}
fun isSmartDownloadEnabled(seriesId: UUID): Flow<Boolean> = smartDownloadStore.observe(seriesId)
override fun isSmartDownloadEnabled(seriesId: UUID): Flow<Boolean> = smartDownloadStore.observe(seriesId)
override suspend fun syncSmartDownloads() {
withContext(Dispatchers.IO) {

View File

@@ -88,9 +88,15 @@ class EpisodeScreenViewModel @Inject constructor(
is DownloadState.NotDownloaded, is DownloadState.Failed -> {
mediaDownloadManager.downloadEpisode(episodeId)
}
is DownloadState.Downloading, is DownloadState.Downloaded -> {
is DownloadState.Downloading -> {
mediaDownloadManager.cancelEpisodeDownload(episodeId)
}
is DownloadState.Downloaded -> {
mediaDownloadManager.cancelEpisodeDownload(episodeId)
if (_episode.value?.offline == true) {
navigationManager.pop()
}
}
}
}
}

View File

@@ -80,6 +80,9 @@ class MovieScreenViewModel @Inject constructor(
}
is DownloadState.Downloaded -> {
mediaDownloadManager.cancelDownload(movieId)
if (_movie.value?.offline == true) {
navigationManager.pop()
}
}
}
}

View File

@@ -48,6 +48,17 @@ class SeriesViewModel @Inject constructor(
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
@OptIn(ExperimentalCoroutinesApi::class)
val isSmartDownloadEnabled: StateFlow<Boolean> = _series
.flatMapLatest { series ->
if (series == null) {
flowOf(false)
} else {
mediaDownloadManager.isSmartDownloadEnabled(series.id)
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
private val _seriesDownloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
val seriesDownloadState: StateFlow<DownloadState> = _seriesDownloadState
@@ -111,6 +122,15 @@ class SeriesViewModel @Inject constructor(
}
}
fun deleteSmartDownloads(seriesId: UUID) {
viewModelScope.launch {
mediaDownloadManager.deleteSmartDownloads(seriesId)
if (_series.value?.offline == true) {
navigationManager.pop()
}
}
}
fun downloadSeries(seriesData: Series) {
viewModelScope.launch {
val mediaCatalogReader = selectedMediaCatalogReader()