mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
Make media detail flows reactive
This commit is contained in:
@@ -11,11 +11,14 @@ import hu.bbara.purefin.core.navigation.Route
|
|||||||
import hu.bbara.purefin.core.navigation.SeriesDto
|
import hu.bbara.purefin.core.navigation.SeriesDto
|
||||||
import hu.bbara.purefin.model.Episode
|
import hu.bbara.purefin.model.Episode
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
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.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -30,19 +33,23 @@ class EpisodeScreenViewModel @Inject constructor(
|
|||||||
private val _episodeId = MutableStateFlow<UUID?>(null)
|
private val _episodeId = MutableStateFlow<UUID?>(null)
|
||||||
private val _seriesId = MutableStateFlow<UUID?>(null)
|
private val _seriesId = MutableStateFlow<UUID?>(null)
|
||||||
|
|
||||||
val episode: StateFlow<Episode?> = combine(
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
_episodeId,
|
val episode: StateFlow<Episode?> = _episodeId
|
||||||
mediaCatalogReader.episodes
|
.flatMapLatest { episodeId ->
|
||||||
) { id, episodesMap ->
|
if (episodeId == null) flowOf(null) else mediaCatalogReader.getEpisode(episodeId)
|
||||||
id?.let { episodesMap[it] }
|
}
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
||||||
|
|
||||||
val seriesTitle: StateFlow<String?> = combine(
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
_seriesId,
|
val seriesTitle: StateFlow<String?> = _seriesId
|
||||||
mediaCatalogReader.series
|
.flatMapLatest { seriesId ->
|
||||||
) { seriesId, seriesMap ->
|
if (seriesId == null) {
|
||||||
seriesId?.let { id -> seriesMap[id]?.name }
|
flowOf(null)
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
} else {
|
||||||
|
mediaCatalogReader.getSeries(seriesId).map { it?.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
||||||
|
|
||||||
private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
||||||
val downloadState: StateFlow<DownloadState> = _downloadState.asStateFlow()
|
val downloadState: StateFlow<DownloadState> = _downloadState.asStateFlow()
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import hu.bbara.purefin.core.navigation.NavigationManager
|
|||||||
import hu.bbara.purefin.core.navigation.Route
|
import hu.bbara.purefin.core.navigation.Route
|
||||||
import hu.bbara.purefin.model.Movie
|
import hu.bbara.purefin.model.Movie
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
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.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -28,12 +30,12 @@ class MovieScreenViewModel @Inject constructor(
|
|||||||
|
|
||||||
private val _movieId = MutableStateFlow<UUID?>(null)
|
private val _movieId = MutableStateFlow<UUID?>(null)
|
||||||
|
|
||||||
val movie: StateFlow<Movie?> = combine(
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
_movieId,
|
val movie: StateFlow<Movie?> = _movieId
|
||||||
mediaCatalogReader.movies
|
.flatMapLatest { movieId ->
|
||||||
) { movieId, movies ->
|
if (movieId == null) flowOf(null) else mediaCatalogReader.getMovie(movieId)
|
||||||
movieId?.let { movies[it] }
|
}
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
||||||
|
|
||||||
private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
||||||
val downloadState: StateFlow<DownloadState> = _downloadState.asStateFlow()
|
val downloadState: StateFlow<DownloadState> = _downloadState.asStateFlow()
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -33,12 +35,11 @@ class SeriesViewModel @Inject constructor(
|
|||||||
private val _seriesId = MutableStateFlow<UUID?>(null)
|
private val _seriesId = MutableStateFlow<UUID?>(null)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
val series: StateFlow<Series?> = combine(
|
val series: StateFlow<Series?> = _seriesId
|
||||||
_seriesId,
|
.flatMapLatest { seriesId ->
|
||||||
mediaCatalogReader.series
|
if (seriesId == null) flowOf(null) else mediaCatalogReader.getSeries(seriesId)
|
||||||
) { seriesId, seriesMap ->
|
}
|
||||||
seriesId?.let { seriesMap[it] }
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
|
||||||
|
|
||||||
private val _seriesDownloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
private val _seriesDownloadState = MutableStateFlow<DownloadState>(DownloadState.NotDownloaded)
|
||||||
val seriesDownloadState: StateFlow<DownloadState> = _seriesDownloadState
|
val seriesDownloadState: StateFlow<DownloadState> = _seriesDownloadState
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -58,7 +59,7 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
moviesState.update { current -> current + (movie.id to movie) }
|
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<Series?> {
|
override suspend fun getSeries(id: UUID): Flow<Series?> {
|
||||||
@@ -72,7 +73,7 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
seriesState.update { current -> current + (series.id to series) }
|
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<Episode?> {
|
override suspend fun getEpisode(id: UUID): Flow<Episode?> {
|
||||||
@@ -88,7 +89,7 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
val episode = episodesState.value[id] ?: return flowOf(null)
|
val episode = episodesState.value[id] ?: return flowOf(null)
|
||||||
loadSeasons(seriesId = episode.seriesId)
|
loadSeasons(seriesId = episode.seriesId)
|
||||||
return episodesState.map { it[id] }
|
return episodesState.map { it[id] }.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upsertMovies(movies: List<Movie>) {
|
fun upsertMovies(movies: List<Movie>) {
|
||||||
@@ -162,10 +163,14 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (episodesState.value.containsKey(mediaId)) {
|
if (episodesState.value.containsKey(mediaId)) {
|
||||||
|
var updatedEpisode: Episode? = null
|
||||||
episodesState.update { current ->
|
episodesState.update { current ->
|
||||||
val episode = current[mediaId] ?: return@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
|
return
|
||||||
}
|
}
|
||||||
if (episodesState.value.containsKey(mediaId)) {
|
if (episodesState.value.containsKey(mediaId)) {
|
||||||
|
var updatedEpisode: Episode? = null
|
||||||
episodesState.update { current ->
|
episodesState.update { current ->
|
||||||
val episode = current[mediaId] ?: return@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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user