mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
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:
@@ -33,14 +33,20 @@ import androidx.compose.material.icons.outlined.Cast
|
|||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material.icons.outlined.Download
|
import androidx.compose.material.icons.outlined.Download
|
||||||
import androidx.compose.material.icons.outlined.DownloadDone
|
import androidx.compose.material.icons.outlined.DownloadDone
|
||||||
|
import androidx.compose.material.icons.outlined.Autorenew
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
import androidx.compose.material.icons.outlined.PlayCircle
|
import androidx.compose.material.icons.outlined.PlayCircle
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -116,10 +122,14 @@ internal fun SeriesMetaChips(series: Series) {
|
|||||||
internal fun SeriesActionButtons(
|
internal fun SeriesActionButtons(
|
||||||
nextUpEpisode: Episode?,
|
nextUpEpisode: Episode?,
|
||||||
downloadState: DownloadState,
|
downloadState: DownloadState,
|
||||||
onDownloadClick: () -> Unit,
|
isSmartDownloadEnabled: Boolean,
|
||||||
|
onDownloadAllClick: () -> Unit,
|
||||||
|
onSmartDownloadToggle: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
var showDownloadDialog by remember { mutableStateOf(false) }
|
||||||
val episodeId = nextUpEpisode?.id
|
val episodeId = nextUpEpisode?.id
|
||||||
val playAction = remember(episodeId) {
|
val playAction = remember(episodeId) {
|
||||||
episodeId?.let { id ->
|
episodeId?.let { id ->
|
||||||
@@ -148,18 +158,72 @@ internal fun SeriesActionButtons(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
MediaActionButton(
|
MediaActionButton(
|
||||||
backgroundColor = MaterialTheme.colorScheme.secondary,
|
backgroundColor = if (isSmartDownloadEnabled) scheme.primary else scheme.secondary,
|
||||||
iconColor = MaterialTheme.colorScheme.onSecondary,
|
iconColor = if (isSmartDownloadEnabled) scheme.onPrimary else scheme.onSecondary,
|
||||||
icon = when (downloadState) {
|
icon = when {
|
||||||
is DownloadState.NotDownloaded -> Icons.Outlined.Download
|
isSmartDownloadEnabled -> Icons.Outlined.Autorenew
|
||||||
is DownloadState.Downloading -> Icons.Outlined.Close
|
downloadState is DownloadState.Downloading -> Icons.Outlined.Close
|
||||||
is DownloadState.Downloaded -> Icons.Outlined.DownloadDone
|
downloadState is DownloadState.Downloaded -> Icons.Outlined.DownloadDone
|
||||||
is DownloadState.Failed -> Icons.Outlined.Download
|
else -> Icons.Outlined.Download
|
||||||
},
|
},
|
||||||
height = 32.dp,
|
height = 32.dp,
|
||||||
onClick = onDownloadClick
|
onClick = { showDownloadDialog = true }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showDownloadDialog) {
|
||||||
|
DownloadOptionsDialog(
|
||||||
|
isSmartDownloadEnabled = isSmartDownloadEnabled,
|
||||||
|
onDownloadAll = {
|
||||||
|
showDownloadDialog = false
|
||||||
|
onDownloadAllClick()
|
||||||
|
},
|
||||||
|
onSmartDownload = {
|
||||||
|
showDownloadDialog = false
|
||||||
|
onSmartDownloadToggle()
|
||||||
|
},
|
||||||
|
onDismiss = { showDownloadDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DownloadOptionsDialog(
|
||||||
|
isSmartDownloadEnabled: Boolean,
|
||||||
|
onDownloadAll: () -> Unit,
|
||||||
|
onSmartDownload: () -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text("Download") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
Text(
|
||||||
|
text = "Choose how to download this series.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
if (isSmartDownloadEnabled) {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "Smart download is currently enabled.",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDownloadAll) {
|
||||||
|
Text("Download All")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onSmartDownload) {
|
||||||
|
Text(if (isSmartDownloadEnabled) "Disable Smart Download" else "Smart Download")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ private fun SeriesScreenInternal(
|
|||||||
|
|
||||||
val seriesDownloadState by viewModel.seriesDownloadState.collectAsState()
|
val seriesDownloadState by viewModel.seriesDownloadState.collectAsState()
|
||||||
val seasonDownloadState by viewModel.seasonDownloadState.collectAsState()
|
val seasonDownloadState by viewModel.seasonDownloadState.collectAsState()
|
||||||
|
val isSmartDownloadEnabled by viewModel.isSmartDownloadEnabled.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(selectedSeason.value) {
|
LaunchedEffect(selectedSeason.value) {
|
||||||
viewModel.observeSeasonDownloadState(selectedSeason.value.episodes)
|
viewModel.observeSeasonDownloadState(selectedSeason.value.episodes)
|
||||||
@@ -134,7 +135,9 @@ private fun SeriesScreenInternal(
|
|||||||
SeriesActionButtons(
|
SeriesActionButtons(
|
||||||
nextUpEpisode = nextUpEpisode,
|
nextUpEpisode = nextUpEpisode,
|
||||||
downloadState = seriesDownloadState,
|
downloadState = seriesDownloadState,
|
||||||
onDownloadClick = { viewModel.downloadSeries(series) }
|
isSmartDownloadEnabled = isSmartDownloadEnabled,
|
||||||
|
onDownloadAllClick = { viewModel.downloadSeries(series) },
|
||||||
|
onSmartDownloadToggle = { viewModel.toggleSmartDownload(series.id) }
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
MediaSynopsis(
|
MediaSynopsis(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import hu.bbara.purefin.core.data.room.dao.EpisodeDao
|
|||||||
import hu.bbara.purefin.core.data.room.dao.MovieDao
|
import hu.bbara.purefin.core.data.room.dao.MovieDao
|
||||||
import hu.bbara.purefin.core.data.room.dao.SeasonDao
|
import hu.bbara.purefin.core.data.room.dao.SeasonDao
|
||||||
import hu.bbara.purefin.core.data.room.dao.SeriesDao
|
import hu.bbara.purefin.core.data.room.dao.SeriesDao
|
||||||
|
import hu.bbara.purefin.core.data.room.dao.SmartDownloadDao
|
||||||
import hu.bbara.purefin.core.data.room.offline.OfflineMediaDatabase
|
import hu.bbara.purefin.core.data.room.offline.OfflineMediaDatabase
|
||||||
import hu.bbara.purefin.core.data.room.offline.OfflineRoomMediaLocalDataSource
|
import hu.bbara.purefin.core.data.room.offline.OfflineRoomMediaLocalDataSource
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -39,6 +40,9 @@ object MediaDatabaseModule {
|
|||||||
@Provides
|
@Provides
|
||||||
fun provideOfflineEpisodeDao(db: OfflineMediaDatabase) = db.episodeDao()
|
fun provideOfflineEpisodeDao(db: OfflineMediaDatabase) = db.episodeDao()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideSmartDownloadDao(db: OfflineMediaDatabase): SmartDownloadDao = db.smartDownloadDao()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideOfflineDataSource(
|
fun provideOfflineDataSource(
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package hu.bbara.purefin.core.data.room.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import hu.bbara.purefin.core.data.room.entity.SmartDownloadEntity
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface SmartDownloadDao {
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insert(entity: SmartDownloadEntity)
|
||||||
|
|
||||||
|
@Query("DELETE FROM smart_downloads WHERE seriesId = :seriesId")
|
||||||
|
suspend fun delete(seriesId: UUID)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM smart_downloads")
|
||||||
|
suspend fun getAll(): List<SmartDownloadEntity>
|
||||||
|
|
||||||
|
@Query("SELECT EXISTS(SELECT 1 FROM smart_downloads WHERE seriesId = :seriesId)")
|
||||||
|
suspend fun exists(seriesId: UUID): Boolean
|
||||||
|
|
||||||
|
@Query("SELECT EXISTS(SELECT 1 FROM smart_downloads WHERE seriesId = :seriesId)")
|
||||||
|
fun observe(seriesId: UUID): Flow<Boolean>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM smart_downloads")
|
||||||
|
fun observeAll(): Flow<List<SmartDownloadEntity>>
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package hu.bbara.purefin.core.data.room.entity
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Entity(tableName = "smart_downloads")
|
||||||
|
data class SmartDownloadEntity(
|
||||||
|
@PrimaryKey val seriesId: UUID
|
||||||
|
)
|
||||||
@@ -8,10 +8,12 @@ import hu.bbara.purefin.core.data.room.dao.EpisodeDao
|
|||||||
import hu.bbara.purefin.core.data.room.dao.MovieDao
|
import hu.bbara.purefin.core.data.room.dao.MovieDao
|
||||||
import hu.bbara.purefin.core.data.room.dao.SeasonDao
|
import hu.bbara.purefin.core.data.room.dao.SeasonDao
|
||||||
import hu.bbara.purefin.core.data.room.dao.SeriesDao
|
import hu.bbara.purefin.core.data.room.dao.SeriesDao
|
||||||
|
import hu.bbara.purefin.core.data.room.dao.SmartDownloadDao
|
||||||
import hu.bbara.purefin.core.data.room.entity.EpisodeEntity
|
import hu.bbara.purefin.core.data.room.entity.EpisodeEntity
|
||||||
import hu.bbara.purefin.core.data.room.entity.MovieEntity
|
import hu.bbara.purefin.core.data.room.entity.MovieEntity
|
||||||
import hu.bbara.purefin.core.data.room.entity.SeasonEntity
|
import hu.bbara.purefin.core.data.room.entity.SeasonEntity
|
||||||
import hu.bbara.purefin.core.data.room.entity.SeriesEntity
|
import hu.bbara.purefin.core.data.room.entity.SeriesEntity
|
||||||
|
import hu.bbara.purefin.core.data.room.entity.SmartDownloadEntity
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
@@ -19,8 +21,9 @@ import hu.bbara.purefin.core.data.room.entity.SeriesEntity
|
|||||||
SeriesEntity::class,
|
SeriesEntity::class,
|
||||||
SeasonEntity::class,
|
SeasonEntity::class,
|
||||||
EpisodeEntity::class,
|
EpisodeEntity::class,
|
||||||
|
SmartDownloadEntity::class,
|
||||||
],
|
],
|
||||||
version = 5,
|
version = 6,
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
@TypeConverters(UuidConverters::class)
|
@TypeConverters(UuidConverters::class)
|
||||||
@@ -29,4 +32,5 @@ abstract class OfflineMediaDatabase : RoomDatabase() {
|
|||||||
abstract fun seriesDao(): SeriesDao
|
abstract fun seriesDao(): SeriesDao
|
||||||
abstract fun seasonDao(): SeasonDao
|
abstract fun seasonDao(): SeasonDao
|
||||||
abstract fun episodeDao(): EpisodeDao
|
abstract fun episodeDao(): EpisodeDao
|
||||||
|
abstract fun smartDownloadDao(): SmartDownloadDao
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,8 @@ import hu.bbara.purefin.core.data.InMemoryMediaRepository
|
|||||||
import hu.bbara.purefin.core.data.client.JellyfinApiClient
|
import hu.bbara.purefin.core.data.client.JellyfinApiClient
|
||||||
import hu.bbara.purefin.core.data.image.JellyfinImageHelper
|
import hu.bbara.purefin.core.data.image.JellyfinImageHelper
|
||||||
import hu.bbara.purefin.core.data.room.dao.MovieDao
|
import hu.bbara.purefin.core.data.room.dao.MovieDao
|
||||||
|
import hu.bbara.purefin.core.data.room.dao.SmartDownloadDao
|
||||||
|
import hu.bbara.purefin.core.data.room.entity.SmartDownloadEntity
|
||||||
import hu.bbara.purefin.core.data.room.offline.OfflineRoomMediaLocalDataSource
|
import hu.bbara.purefin.core.data.room.offline.OfflineRoomMediaLocalDataSource
|
||||||
import hu.bbara.purefin.core.data.session.UserSessionRepository
|
import hu.bbara.purefin.core.data.session.UserSessionRepository
|
||||||
import hu.bbara.purefin.core.model.Episode
|
import hu.bbara.purefin.core.model.Episode
|
||||||
@@ -46,6 +48,7 @@ class MediaDownloadManager @Inject constructor(
|
|||||||
private val jellyfinApiClient: JellyfinApiClient,
|
private val jellyfinApiClient: JellyfinApiClient,
|
||||||
private val offlineDataSource: OfflineRoomMediaLocalDataSource,
|
private val offlineDataSource: OfflineRoomMediaLocalDataSource,
|
||||||
private val movieDao: MovieDao,
|
private val movieDao: MovieDao,
|
||||||
|
private val smartDownloadDao: SmartDownloadDao,
|
||||||
private val userSessionRepository: UserSessionRepository,
|
private val userSessionRepository: UserSessionRepository,
|
||||||
private val inMemoryMediaRepository: InMemoryMediaRepository,
|
private val inMemoryMediaRepository: InMemoryMediaRepository,
|
||||||
) {
|
) {
|
||||||
@@ -261,6 +264,76 @@ class MediaDownloadManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Smart Download ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
suspend fun enableSmartDownload(seriesId: UUID) {
|
||||||
|
smartDownloadDao.insert(SmartDownloadEntity(seriesId))
|
||||||
|
syncSmartDownloadsForSeries(seriesId)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun disableSmartDownload(seriesId: UUID) {
|
||||||
|
smartDownloadDao.delete(seriesId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSmartDownloadEnabled(seriesId: UUID): Flow<Boolean> = smartDownloadDao.observe(seriesId)
|
||||||
|
|
||||||
|
suspend fun syncSmartDownloads() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val enabled = smartDownloadDao.getAll()
|
||||||
|
for (entry in enabled) {
|
||||||
|
try {
|
||||||
|
syncSmartDownloadsForSeries(entry.seriesId)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Smart download sync failed for series ${entry.seriesId}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun syncSmartDownloadsForSeries(seriesId: UUID) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val serverUrl = userSessionRepository.serverUrl.first().trim()
|
||||||
|
|
||||||
|
// 1. Get currently downloaded episodes for this series
|
||||||
|
val downloadedEpisodes = offlineDataSource.getEpisodesBySeries(seriesId)
|
||||||
|
|
||||||
|
// 2. Check watched status from server and delete watched downloads
|
||||||
|
val unwatchedDownloaded = mutableListOf<UUID>()
|
||||||
|
for (episode in downloadedEpisodes) {
|
||||||
|
val itemInfo = jellyfinApiClient.getItemInfo(episode.id)
|
||||||
|
val isWatched = itemInfo?.userData?.played ?: false
|
||||||
|
if (isWatched) {
|
||||||
|
Log.d(TAG, "Smart download: removing watched episode ${episode.title}")
|
||||||
|
cancelEpisodeDownload(episode.id)
|
||||||
|
} else {
|
||||||
|
unwatchedDownloaded.add(episode.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Get all episodes of the series from the server in order
|
||||||
|
val seasons = jellyfinApiClient.getSeasons(seriesId)
|
||||||
|
val allEpisodes = mutableListOf<BaseItemDto>()
|
||||||
|
for (season in seasons) {
|
||||||
|
val episodes = jellyfinApiClient.getEpisodesInSeason(seriesId, season.id)
|
||||||
|
allEpisodes.addAll(episodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Find unwatched episodes not already downloaded
|
||||||
|
val needed = SMART_DOWNLOAD_COUNT - unwatchedDownloaded.size
|
||||||
|
if (needed <= 0) return@withContext
|
||||||
|
|
||||||
|
val toDownload = allEpisodes
|
||||||
|
.filter { ep -> ep.userData?.played != true && ep.id !in unwatchedDownloaded }
|
||||||
|
.take(needed)
|
||||||
|
.map { it.id }
|
||||||
|
|
||||||
|
if (toDownload.isNotEmpty()) {
|
||||||
|
Log.d(TAG, "Smart download: queuing ${toDownload.size} episodes for series $seriesId")
|
||||||
|
downloadEpisodes(toDownload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getOrCreateStateFlow(contentId: String): MutableStateFlow<DownloadState> {
|
private fun getOrCreateStateFlow(contentId: String): MutableStateFlow<DownloadState> {
|
||||||
return stateFlows.getOrPut(contentId) { MutableStateFlow(DownloadState.NotDownloaded) }
|
return stateFlows.getOrPut(contentId) { MutableStateFlow(DownloadState.NotDownloaded) }
|
||||||
}
|
}
|
||||||
@@ -339,5 +412,6 @@ class MediaDownloadManager @Inject constructor(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "MediaDownloadManager"
|
private const val TAG = "MediaDownloadManager"
|
||||||
|
private const val SMART_DOWNLOAD_COUNT = 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ class SeriesViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
.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)
|
private val _seriesDownloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
||||||
val seriesDownloadState: StateFlow<DownloadState> = _seriesDownloadState
|
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) {
|
fun downloadSeries(series: Series) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val allEpisodeIds = series.seasons.flatMap { season ->
|
val allEpisodeIds = series.seasons.flatMap { season ->
|
||||||
|
|||||||
@@ -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.Route
|
||||||
import hu.bbara.purefin.core.data.navigation.SeriesDto
|
import hu.bbara.purefin.core.data.navigation.SeriesDto
|
||||||
import hu.bbara.purefin.core.data.session.UserSessionRepository
|
import hu.bbara.purefin.core.data.session.UserSessionRepository
|
||||||
|
import hu.bbara.purefin.feature.download.MediaDownloadManager
|
||||||
import hu.bbara.purefin.core.model.Media
|
import hu.bbara.purefin.core.model.Media
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
@@ -31,7 +32,8 @@ class HomePageViewModel @Inject constructor(
|
|||||||
private val appContentRepository: AppContentRepository,
|
private val appContentRepository: AppContentRepository,
|
||||||
private val userSessionRepository: UserSessionRepository,
|
private val userSessionRepository: UserSessionRepository,
|
||||||
private val navigationManager: NavigationManager,
|
private val navigationManager: NavigationManager,
|
||||||
private val refreshHomeDataUseCase: RefreshHomeDataUseCase
|
private val refreshHomeDataUseCase: RefreshHomeDataUseCase,
|
||||||
|
private val mediaDownloadManager: MediaDownloadManager,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _isRefreshing = MutableStateFlow(false)
|
private val _isRefreshing = MutableStateFlow(false)
|
||||||
@@ -199,6 +201,11 @@ class HomePageViewModel @Inject constructor(
|
|||||||
// Refresh is best-effort; don't crash on failure
|
// Refresh is best-effort; don't crash on failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
mediaDownloadManager.syncSmartDownloads()
|
||||||
|
} catch (_: Exception) { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRefresh() {
|
fun onRefresh() {
|
||||||
@@ -212,6 +219,11 @@ class HomePageViewModel @Inject constructor(
|
|||||||
_isRefreshing.value = false
|
_isRefreshing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
mediaDownloadManager.syncSmartDownloads()
|
||||||
|
} catch (_: Exception) { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logout() {
|
fun logout() {
|
||||||
|
|||||||
Reference in New Issue
Block a user