feat: update media selection logic to handle offline state in view models and DTOs

This commit is contained in:
2026-05-14 12:56:48 +02:00
parent 83730188eb
commit f60aba5a72
19 changed files with 111 additions and 72 deletions

View File

@@ -3,14 +3,15 @@ package hu.bbara.purefin.core.feature.content.episode
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.core.Offline
import hu.bbara.purefin.core.data.LocalMediaRepository
import hu.bbara.purefin.core.download.DownloadState
import hu.bbara.purefin.core.download.MediaDownloadController
import hu.bbara.purefin.core.navigation.EpisodeDto
import hu.bbara.purefin.core.navigation.NavigationManager
import hu.bbara.purefin.core.navigation.Route
import hu.bbara.purefin.core.navigation.SeriesDto
import hu.bbara.purefin.model.Episode
import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -25,28 +26,32 @@ import javax.inject.Inject
@HiltViewModel
class EpisodeScreenViewModel @Inject constructor(
private val mediaCatalogReader: LocalMediaRepository,
private val defaultMediaCatalogReader: LocalMediaRepository,
@param:Offline private val offlineMediaCatalogReader: LocalMediaRepository,
private val navigationManager: NavigationManager,
private val mediaDownloadManager: MediaDownloadController,
): ViewModel() {
private val _episodeId = MutableStateFlow<UUID?>(null)
private val _seriesId = MutableStateFlow<UUID?>(null)
private val _episode = MutableStateFlow<EpisodeDto?>(null)
@OptIn(ExperimentalCoroutinesApi::class)
val episode: StateFlow<Episode?> = _episodeId
.flatMapLatest { episodeId ->
if (episodeId == null) flowOf(null) else mediaCatalogReader.getEpisode(episodeId)
val episode: StateFlow<Episode?> = _episode
.flatMapLatest { episode ->
if (episode == null) {
flowOf(null)
} else {
mediaCatalogReader(episode.offline).getEpisode(episode.id)
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
@OptIn(ExperimentalCoroutinesApi::class)
val seriesTitle: StateFlow<String?> = _seriesId
.flatMapLatest { seriesId ->
if (seriesId == null) {
val seriesTitle: StateFlow<String?> = _episode
.flatMapLatest { episode ->
if (episode == null) {
flowOf(null)
} else {
mediaCatalogReader.getSeries(seriesId).map { it?.name }
mediaCatalogReader(episode.offline).getSeries(episode.seriesId).map { it?.name }
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
@@ -59,22 +64,25 @@ class EpisodeScreenViewModel @Inject constructor(
}
fun onSeriesClick() {
val seriesId = _seriesId.value ?: return
navigationManager.navigate(Route.SeriesRoute(SeriesDto(id = seriesId)))
val episode = _episode.value ?: return
navigationManager.navigate(
Route.SeriesRoute(
SeriesDto(id = episode.seriesId, offline = episode.offline)
)
)
}
fun selectEpisode(seriesId: UUID, seasonId: UUID, episodeId: UUID) {
_episodeId.value = episodeId
_seriesId.value = seriesId
fun selectEpisode(episode: EpisodeDto) {
_episode.value = episode
viewModelScope.launch {
mediaDownloadManager.observeDownloadState(episodeId.toString()).collect {
mediaDownloadManager.observeDownloadState(episode.id.toString()).collect {
_downloadState.value = it
}
}
}
fun onDownloadClick() {
val episodeId = _episodeId.value ?: return
val episodeId = _episode.value?.id ?: return
viewModelScope.launch {
when (_downloadState.value) {
is DownloadState.NotDownloaded, is DownloadState.Failed -> {
@@ -87,4 +95,7 @@ class EpisodeScreenViewModel @Inject constructor(
}
}
private fun mediaCatalogReader(offline: Boolean): LocalMediaRepository {
return if (offline) offlineMediaCatalogReader else defaultMediaCatalogReader
}
}

View File

@@ -3,13 +3,14 @@ package hu.bbara.purefin.core.feature.content.movie
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.core.Offline
import hu.bbara.purefin.core.data.LocalMediaRepository
import hu.bbara.purefin.core.download.DownloadState
import hu.bbara.purefin.core.download.MediaDownloadController
import hu.bbara.purefin.core.navigation.MovieDto
import hu.bbara.purefin.core.navigation.NavigationManager
import hu.bbara.purefin.core.navigation.Route
import hu.bbara.purefin.model.Movie
import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -23,17 +24,22 @@ import javax.inject.Inject
@HiltViewModel
class MovieScreenViewModel @Inject constructor(
private val mediaCatalogReader: LocalMediaRepository,
private val defaultMediaCatalogReader: LocalMediaRepository,
@param:Offline private val offlineMediaCatalogReader: LocalMediaRepository,
private val navigationManager: NavigationManager,
private val mediaDownloadManager: MediaDownloadController,
): ViewModel() {
private val _movieId = MutableStateFlow<UUID?>(null)
private val _movie = MutableStateFlow<MovieDto?>(null)
@OptIn(ExperimentalCoroutinesApi::class)
val movie: StateFlow<Movie?> = _movieId
.flatMapLatest { movieId ->
if (movieId == null) flowOf(null) else mediaCatalogReader.getMovie(movieId)
val movie: StateFlow<Movie?> = _movie
.flatMapLatest { movie ->
if (movie == null) {
flowOf(null)
} else {
mediaCatalogReader(movie.offline).getMovie(movie.id)
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
@@ -53,10 +59,10 @@ class MovieScreenViewModel @Inject constructor(
navigationManager.replaceAll(Route.Home)
}
fun selectMovie(movieId: UUID) {
_movieId.value = movieId
fun selectMovie(movie: MovieDto) {
_movie.value = movie
viewModelScope.launch {
mediaDownloadManager.observeDownloadState(movieId.toString()).collect {
mediaDownloadManager.observeDownloadState(movie.id.toString()).collect {
_downloadState.value = it
}
}
@@ -79,4 +85,7 @@ class MovieScreenViewModel @Inject constructor(
}
}
private fun mediaCatalogReader(offline: Boolean): LocalMediaRepository {
return if (offline) offlineMediaCatalogReader else defaultMediaCatalogReader
}
}

View File

@@ -3,12 +3,14 @@ package hu.bbara.purefin.core.feature.content.series
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.core.Offline
import hu.bbara.purefin.core.data.LocalMediaRepository
import hu.bbara.purefin.core.download.DownloadState
import hu.bbara.purefin.core.download.MediaDownloadController
import hu.bbara.purefin.core.navigation.EpisodeDto
import hu.bbara.purefin.core.navigation.NavigationManager
import hu.bbara.purefin.core.navigation.Route
import hu.bbara.purefin.core.navigation.SeriesDto
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Series
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -27,17 +29,22 @@ import javax.inject.Inject
@HiltViewModel
class SeriesViewModel @Inject constructor(
private val mediaCatalogReader: LocalMediaRepository,
private val defaultMediaCatalogReader: LocalMediaRepository,
@param:Offline private val offlineMediaCatalogReader: LocalMediaRepository,
private val navigationManager: NavigationManager,
private val mediaDownloadManager: MediaDownloadController,
) : ViewModel() {
private val _seriesId = MutableStateFlow<UUID?>(null)
private val _series = MutableStateFlow<SeriesDto?>(null)
@OptIn(ExperimentalCoroutinesApi::class)
val series: StateFlow<Series?> = _seriesId
.flatMapLatest { seriesId ->
if (seriesId == null) flowOf(null) else mediaCatalogReader.getSeries(seriesId)
val series: StateFlow<Series?> = _series
.flatMapLatest { series ->
if (series == null) {
flowOf(null)
} else {
mediaCatalogReader(series.offline).getSeries(series.id)
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
@@ -80,12 +87,13 @@ class SeriesViewModel @Inject constructor(
fun loadSeasonEpisodes(seriesId: UUID, seasonId: UUID) {
viewModelScope.launch {
mediaCatalogReader.loadSeasonEpisodes(seriesId, seasonId)
selectedMediaCatalogReader().loadSeasonEpisodes(seriesId, seasonId)
}
}
fun downloadSeason(seriesId: UUID, seasonId: UUID) {
viewModelScope.launch {
val mediaCatalogReader = selectedMediaCatalogReader()
mediaCatalogReader.loadSeasonEpisodes(seriesId, seasonId)
val episodes = mediaCatalogReader.getSeries(seriesId)
.first()
@@ -105,6 +113,7 @@ class SeriesViewModel @Inject constructor(
fun downloadSeries(seriesData: Series) {
viewModelScope.launch {
val mediaCatalogReader = selectedMediaCatalogReader()
seriesData.seasons.forEach { season ->
mediaCatalogReader.loadSeasonEpisodes(seriesData.id, season.id)
}
@@ -136,7 +145,8 @@ class SeriesViewModel @Inject constructor(
EpisodeDto(
id = episodeId,
seasonId = seasonId,
seriesId = seriesId
seriesId = seriesId,
offline = _series.value?.offline == true,
)
))
}
@@ -153,10 +163,18 @@ class SeriesViewModel @Inject constructor(
navigationManager.replaceAll(Route.Home)
}
fun selectSeries(seriesId: UUID) {
_seriesId.value = seriesId
fun selectSeries(series: SeriesDto) {
_series.value = series
viewModelScope.launch {
mediaCatalogReader.loadSeasons(seriesId)
mediaCatalogReader(series.offline).loadSeasons(series.id)
}
}
private fun selectedMediaCatalogReader(): LocalMediaRepository {
return mediaCatalogReader(_series.value?.offline == true)
}
private fun mediaCatalogReader(offline: Boolean): LocalMediaRepository {
return if (offline) offlineMediaCatalogReader else defaultMediaCatalogReader
}
}

View File

@@ -23,7 +23,7 @@ import javax.inject.Inject
@HiltViewModel
class DownloadsViewModel @Inject constructor(
@Offline private val offlineCatalogReader: LocalMediaRepository,
@param:Offline private val offlineCatalogReader: LocalMediaRepository,
private val navigationManager: NavigationManager,
private val downloadManager: MediaDownloadController,
) : ViewModel() {
@@ -31,7 +31,7 @@ class DownloadsViewModel @Inject constructor(
fun onMovieSelected(movieId: UUID) {
navigationManager.navigate(
Route.MovieRoute(
MovieDto(id = movieId)
MovieDto(id = movieId, offline = true)
))
}
@@ -39,7 +39,7 @@ class DownloadsViewModel @Inject constructor(
viewModelScope.launch {
navigationManager.navigate(
Route.SeriesRoute(
SeriesDto(id = seriesId)
SeriesDto(id = seriesId, offline = true)
))
}
}

View File

@@ -11,4 +11,5 @@ data class EpisodeDto(
val seasonId: UUID,
@Serializable(with = UuidSerializer::class)
val seriesId: UUID,
val offline: Boolean = false,
)

View File

@@ -7,4 +7,5 @@ import kotlinx.serialization.Serializable
data class MovieDto(
@Serializable(with = UuidSerializer::class)
val id: UUID,
val offline: Boolean = false,
)

View File

@@ -7,4 +7,5 @@ import kotlinx.serialization.Serializable
data class SeriesDto(
@Serializable(with = UuidSerializer::class)
val id: UUID,
val offline: Boolean = false,
)