diff --git a/core/src/main/java/hu/bbara/purefin/core/feature/content/episode/EpisodeScreenViewModel.kt b/core/src/main/java/hu/bbara/purefin/core/feature/content/episode/EpisodeScreenViewModel.kt index 89d95e54..b193ad45 100644 --- a/core/src/main/java/hu/bbara/purefin/core/feature/content/episode/EpisodeScreenViewModel.kt +++ b/core/src/main/java/hu/bbara/purefin/core/feature/content/episode/EpisodeScreenViewModel.kt @@ -11,11 +11,14 @@ 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 import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @@ -30,19 +33,23 @@ class EpisodeScreenViewModel @Inject constructor( private val _episodeId = MutableStateFlow(null) private val _seriesId = MutableStateFlow(null) - val episode: StateFlow = combine( - _episodeId, - mediaCatalogReader.episodes - ) { id, episodesMap -> - id?.let { episodesMap[it] } - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) + @OptIn(ExperimentalCoroutinesApi::class) + val episode: StateFlow = _episodeId + .flatMapLatest { episodeId -> + if (episodeId == null) flowOf(null) else mediaCatalogReader.getEpisode(episodeId) + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) - val seriesTitle: StateFlow = combine( - _seriesId, - mediaCatalogReader.series - ) { seriesId, seriesMap -> - seriesId?.let { id -> seriesMap[id]?.name } - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) + @OptIn(ExperimentalCoroutinesApi::class) + val seriesTitle: StateFlow = _seriesId + .flatMapLatest { seriesId -> + if (seriesId == null) { + flowOf(null) + } else { + mediaCatalogReader.getSeries(seriesId).map { it?.name } + } + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) private val _downloadState = MutableStateFlow(DownloadState.NotDownloaded) val downloadState: StateFlow = _downloadState.asStateFlow() diff --git a/core/src/main/java/hu/bbara/purefin/core/feature/content/movie/MovieScreenViewModel.kt b/core/src/main/java/hu/bbara/purefin/core/feature/content/movie/MovieScreenViewModel.kt index c544be56..13f3c516 100644 --- a/core/src/main/java/hu/bbara/purefin/core/feature/content/movie/MovieScreenViewModel.kt +++ b/core/src/main/java/hu/bbara/purefin/core/feature/content/movie/MovieScreenViewModel.kt @@ -10,11 +10,13 @@ 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 import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @@ -28,12 +30,12 @@ class MovieScreenViewModel @Inject constructor( private val _movieId = MutableStateFlow(null) - val movie: StateFlow = combine( - _movieId, - mediaCatalogReader.movies - ) { movieId, movies -> - movieId?.let { movies[it] } - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) + @OptIn(ExperimentalCoroutinesApi::class) + val movie: StateFlow = _movieId + .flatMapLatest { movieId -> + if (movieId == null) flowOf(null) else mediaCatalogReader.getMovie(movieId) + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) private val _downloadState = MutableStateFlow(DownloadState.NotDownloaded) val downloadState: StateFlow = _downloadState.asStateFlow() diff --git a/core/src/main/java/hu/bbara/purefin/core/feature/content/series/SeriesViewModel.kt b/core/src/main/java/hu/bbara/purefin/core/feature/content/series/SeriesViewModel.kt index 685b79cf..ec2e81c1 100644 --- a/core/src/main/java/hu/bbara/purefin/core/feature/content/series/SeriesViewModel.kt +++ b/core/src/main/java/hu/bbara/purefin/core/feature/content/series/SeriesViewModel.kt @@ -18,6 +18,8 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import java.util.UUID @@ -33,12 +35,11 @@ class SeriesViewModel @Inject constructor( private val _seriesId = MutableStateFlow(null) @OptIn(ExperimentalCoroutinesApi::class) - val series: StateFlow = combine( - _seriesId, - mediaCatalogReader.series - ) { seriesId, seriesMap -> - seriesId?.let { seriesMap[it] } - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) + val series: StateFlow = _seriesId + .flatMapLatest { seriesId -> + if (seriesId == null) flowOf(null) else mediaCatalogReader.getSeries(seriesId) + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) private val _seriesDownloadState = MutableStateFlow(DownloadState.NotDownloaded) val seriesDownloadState: StateFlow = _seriesDownloadState diff --git a/data/src/main/java/hu/bbara/purefin/data/catalog/InMemoryLocalMediaRepository.kt b/data/src/main/java/hu/bbara/purefin/data/catalog/InMemoryLocalMediaRepository.kt index 3aa663f6..7be7dd92 100644 --- a/data/src/main/java/hu/bbara/purefin/data/catalog/InMemoryLocalMediaRepository.kt +++ b/data/src/main/java/hu/bbara/purefin/data/catalog/InMemoryLocalMediaRepository.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -58,7 +59,7 @@ class InMemoryLocalMediaRepository @Inject constructor( moviesState.update { current -> current + (movie.id to movie) } } } - return moviesState.map { it[id] } + return moviesState.map { it[id] }.distinctUntilChanged() } override suspend fun getSeries(id: UUID): Flow { @@ -72,7 +73,7 @@ class InMemoryLocalMediaRepository @Inject constructor( seriesState.update { current -> current + (series.id to series) } } } - return seriesState.map { it[id] } + return seriesState.map { it[id] }.distinctUntilChanged() } override suspend fun getEpisode(id: UUID): Flow { @@ -88,7 +89,7 @@ class InMemoryLocalMediaRepository @Inject constructor( } val episode = episodesState.value[id] ?: return flowOf(null) loadSeasons(seriesId = episode.seriesId) - return episodesState.map { it[id] } + return episodesState.map { it[id] }.distinctUntilChanged() } fun upsertMovies(movies: List) { @@ -162,10 +163,14 @@ class InMemoryLocalMediaRepository @Inject constructor( return } if (episodesState.value.containsKey(mediaId)) { + var updatedEpisode: Episode? = null episodesState.update { current -> val episode = current[mediaId] ?: return@update current - current + (mediaId to episode.copy(progress = progressPercent, watched = watched)) + val updated = episode.copy(progress = progressPercent, watched = watched) + updatedEpisode = updated + current + (mediaId to updated) } + updatedEpisode?.let(::updateLoadedSeriesEpisode) } } @@ -178,9 +183,37 @@ class InMemoryLocalMediaRepository @Inject constructor( return } if (episodesState.value.containsKey(mediaId)) { + var updatedEpisode: Episode? = null episodesState.update { current -> val episode = current[mediaId] ?: return@update current - current + (mediaId to episode.copy(watched = watched)) + val updated = episode.copy(watched = watched) + updatedEpisode = updated + current + (mediaId to updated) + } + updatedEpisode?.let(::updateLoadedSeriesEpisode) + } + } + + private fun updateLoadedSeriesEpisode(updatedEpisode: Episode) { + seriesState.update { current -> + val series = current[updatedEpisode.seriesId] ?: return@update current + var changed = false + val seasons = series.seasons.map { season -> + if (season.episodes.none { it.id == updatedEpisode.id }) { + season + } else { + changed = true + season.copy( + episodes = season.episodes.map { episode -> + if (episode.id == updatedEpisode.id) updatedEpisode else episode + } + ) + } + } + if (changed) { + current + (series.id to series.copy(seasons = seasons)) + } else { + current } } }