From d5c0bbded6d8c7c9d7df002df1f7dc0f89285df9 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Sun, 22 Feb 2026 13:41:47 +0100 Subject: [PATCH] feat: add DownloadsViewModel for managing offline media downloads --- .../purefin/app/home/ui/DownloadsContent.kt | 66 ++++++++++++++----- .../data/room/offline/OfflineMediaDatabase.kt | 2 +- .../shared/download/DownloadsViewModel.kt | 60 +++++++++++++++++ 3 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/DownloadsViewModel.kt diff --git a/app/src/main/java/hu/bbara/purefin/app/home/ui/DownloadsContent.kt b/app/src/main/java/hu/bbara/purefin/app/home/ui/DownloadsContent.kt index 5ed31ed..d6d70be 100644 --- a/app/src/main/java/hu/bbara/purefin/app/home/ui/DownloadsContent.kt +++ b/app/src/main/java/hu/bbara/purefin/app/home/ui/DownloadsContent.kt @@ -1,39 +1,71 @@ package hu.bbara.purefin.app.home.ui +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Download import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import hu.bbara.purefin.common.ui.PosterCard +import hu.bbara.purefin.feature.shared.download.DownloadsViewModel @Composable fun DownloadsContent( modifier: Modifier = Modifier, + viewModel: DownloadsViewModel = hiltViewModel(), ) { - Column( - modifier = modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = Icons.Outlined.Download, - contentDescription = null, - modifier = Modifier.size(64.dp), - tint = MaterialTheme.colorScheme.onSurfaceVariant - ) - Text( - text = "No downloads yet", - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier - ) + val downloads = viewModel.downloads.collectAsState(emptyList()) + + if (downloads.value.isEmpty()) { + Column( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = Icons.Outlined.Download, + contentDescription = null, + modifier = Modifier.size(64.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = "No downloads yet", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier + ) + } } + + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = 120.dp), + contentPadding = PaddingValues(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier.background(MaterialTheme.colorScheme.background) + ) { + items(downloads.value) { item -> + PosterCard( + item = item, + onMovieSelected = viewModel::onMovieSelected, + onSeriesSelected = viewModel::onSeriesSelected, + onEpisodeSelected = { _, _, _ -> }, + ) + } + } + } diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineMediaDatabase.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineMediaDatabase.kt index bdf5b59..dabd083 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineMediaDatabase.kt +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineMediaDatabase.kt @@ -20,7 +20,7 @@ import hu.bbara.purefin.core.data.room.entity.SeriesEntity SeasonEntity::class, EpisodeEntity::class, ], - version = 4, + version = 5, exportSchema = false ) @TypeConverters(UuidConverters::class) diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/DownloadsViewModel.kt b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/DownloadsViewModel.kt new file mode 100644 index 0000000..6bc820b --- /dev/null +++ b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/DownloadsViewModel.kt @@ -0,0 +1,60 @@ +package hu.bbara.purefin.feature.shared.download + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import hu.bbara.purefin.core.data.OfflineMediaRepository +import hu.bbara.purefin.core.data.navigation.MovieDto +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.feature.download.MediaDownloadManager +import hu.bbara.purefin.feature.shared.home.PosterItem +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch +import org.jellyfin.sdk.model.UUID +import org.jellyfin.sdk.model.api.BaseItemKind +import javax.inject.Inject + +@HiltViewModel +class DownloadsViewModel @Inject constructor( + private val offlineMediaRepository: OfflineMediaRepository, + private val navigationManager: NavigationManager, + private val downloadManager: MediaDownloadManager +) : ViewModel() { + + fun onMovieSelected(movieId: UUID) { + navigationManager.navigate(Route.MovieRoute( + MovieDto( + id = movieId, + ) + )) + } + + fun onSeriesSelected(seriesId: UUID) { + viewModelScope.launch { + navigationManager.navigate(Route.SeriesRoute( + SeriesDto( + id = seriesId, + ) + )) + } + } + + val downloads = combine( + offlineMediaRepository.movies, + offlineMediaRepository.series + ) { movies, series -> + movies.values.map { + PosterItem( + type = BaseItemKind.MOVIE, + movie = it + ) + } + series.values.map { + PosterItem( + type = BaseItemKind.SERIES, + series = it + ) + } + } +} \ No newline at end of file