mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
feat: refactor media retrieval to use combined flows for episodes and continue watching
This commit is contained in:
@@ -7,7 +7,10 @@ import hu.bbara.purefin.data.InMemoryMediaRepository
|
|||||||
import hu.bbara.purefin.data.model.Episode
|
import hu.bbara.purefin.data.model.Episode
|
||||||
import hu.bbara.purefin.navigation.NavigationManager
|
import hu.bbara.purefin.navigation.NavigationManager
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -18,8 +21,14 @@ class EpisodeScreenViewModel @Inject constructor(
|
|||||||
private val navigationManager: NavigationManager,
|
private val navigationManager: NavigationManager,
|
||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
|
|
||||||
private val _episode = MutableStateFlow<Episode?>(null)
|
private val _episodeId = MutableStateFlow<UUID?>(null)
|
||||||
val episode = _episode.asStateFlow()
|
|
||||||
|
val episode: StateFlow<Episode?> = combine(
|
||||||
|
_episodeId,
|
||||||
|
mediaRepository.episodes
|
||||||
|
) { id, episodesMap ->
|
||||||
|
id?.let { episodesMap[it] }
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch { mediaRepository.ensureReady() }
|
viewModelScope.launch { mediaRepository.ensureReady() }
|
||||||
@@ -30,13 +39,7 @@ class EpisodeScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun selectEpisode(seriesId: UUID, seasonId: UUID, episodeId: UUID) {
|
fun selectEpisode(seriesId: UUID, seasonId: UUID, episodeId: UUID) {
|
||||||
viewModelScope.launch {
|
_episodeId.value = episodeId
|
||||||
_episode.value = mediaRepository.getEpisode(
|
|
||||||
seriesId = seriesId,
|
|
||||||
seasonId = seasonId,
|
|
||||||
episodeId = episodeId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,10 @@ fun SeriesScreen(
|
|||||||
|
|
||||||
val series = viewModel.series.collectAsState()
|
val series = viewModel.series.collectAsState()
|
||||||
|
|
||||||
if (series.value != null) {
|
val seriesData = series.value
|
||||||
|
if (seriesData != null && seriesData.seasons.isNotEmpty()) {
|
||||||
SeriesScreenInternal(
|
SeriesScreenInternal(
|
||||||
series = series.value!!,
|
series = seriesData,
|
||||||
onBack = viewModel::onBack,
|
onBack = viewModel::onBack,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -60,9 +60,5 @@ class SeriesViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun selectSeries(seriesId: UUID) {
|
fun selectSeries(seriesId: UUID) {
|
||||||
_seriesId.value = seriesId
|
_seriesId.value = seriesId
|
||||||
// Ensure content is loaded from API if not cached
|
|
||||||
viewModelScope.launch {
|
|
||||||
mediaRepository.getSeriesWithContent(seriesId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,16 +19,12 @@ import hu.bbara.purefin.navigation.NavigationManager
|
|||||||
import hu.bbara.purefin.navigation.Route
|
import hu.bbara.purefin.navigation.Route
|
||||||
import hu.bbara.purefin.navigation.SeriesDto
|
import hu.bbara.purefin.navigation.SeriesDto
|
||||||
import hu.bbara.purefin.session.UserSessionRepository
|
import hu.bbara.purefin.session.UserSessionRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.flowOn
|
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
@@ -59,97 +55,58 @@ class HomePageViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val continueWatching = mediaRepository.continueWatching
|
val continueWatching = combine(
|
||||||
.mapLatest { list ->
|
mediaRepository.continueWatching,
|
||||||
withContext(Dispatchers.IO) {
|
mediaRepository.movies,
|
||||||
list.map { media ->
|
mediaRepository.episodes
|
||||||
when (media) {
|
) { list, moviesMap, episodesMap ->
|
||||||
is Media.MovieMedia -> {
|
list.mapNotNull { media ->
|
||||||
val movie = mediaRepository.getMovie(media.movieId)
|
when (media) {
|
||||||
ContinueWatchingItem(
|
is Media.MovieMedia -> moviesMap[media.movieId]?.let {
|
||||||
type = BaseItemKind.MOVIE,
|
ContinueWatchingItem(type = BaseItemKind.MOVIE, movie = it)
|
||||||
movie = movie
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Media.EpisodeMedia -> {
|
|
||||||
val episode = mediaRepository.getEpisode(
|
|
||||||
seriesId = media.seriesId,
|
|
||||||
episodeId = media.episodeId
|
|
||||||
)
|
|
||||||
ContinueWatchingItem(
|
|
||||||
type = BaseItemKind.EPISODE,
|
|
||||||
episode = episode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw UnsupportedOperationException("Unsupported item type: $media")
|
|
||||||
}
|
|
||||||
}.distinctBy { it.id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.flowOn(Dispatchers.IO)
|
|
||||||
.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.WhileSubscribed(5_000),
|
|
||||||
initialValue = emptyList()
|
|
||||||
)
|
|
||||||
|
|
||||||
val latestLibraryContent = mediaRepository.latestLibraryContent
|
|
||||||
.mapLatest { libraryMap ->
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
libraryMap.mapValues { (_, items) ->
|
|
||||||
items.map { media ->
|
|
||||||
when (media) {
|
|
||||||
is Media.MovieMedia -> {
|
|
||||||
val movie = mediaRepository.getMovie(media.movieId)
|
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.MOVIE,
|
|
||||||
movie = movie
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Media.EpisodeMedia -> {
|
|
||||||
val episode = mediaRepository.getEpisode(
|
|
||||||
seriesId = media.seriesId,
|
|
||||||
episodeId = media.episodeId
|
|
||||||
)
|
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.EPISODE,
|
|
||||||
episode = episode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Media.SeriesMedia -> {
|
|
||||||
val series = mediaRepository.getSeries(media.id)
|
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.SERIES,
|
|
||||||
series = series
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Media.SeasonMedia -> {
|
|
||||||
val series = mediaRepository.getSeries(media.seriesId)
|
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.SERIES,
|
|
||||||
series = series
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw UnsupportedOperationException("Unsupported item type: $media")
|
|
||||||
}
|
|
||||||
}.distinctBy { it.id }
|
|
||||||
}
|
}
|
||||||
|
is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let {
|
||||||
|
ContinueWatchingItem(type = BaseItemKind.EPISODE, episode = it)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
|
}.distinctBy { it.id }
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val latestLibraryContent = combine(
|
||||||
|
mediaRepository.latestLibraryContent,
|
||||||
|
mediaRepository.movies,
|
||||||
|
mediaRepository.series,
|
||||||
|
mediaRepository.episodes
|
||||||
|
) { libraryMap, moviesMap, seriesMap, episodesMap ->
|
||||||
|
libraryMap.mapValues { (_, items) ->
|
||||||
|
items.mapNotNull { media ->
|
||||||
|
when (media) {
|
||||||
|
is Media.MovieMedia -> moviesMap[media.movieId]?.let {
|
||||||
|
PosterItem(type = BaseItemKind.MOVIE, movie = it)
|
||||||
|
}
|
||||||
|
is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let {
|
||||||
|
PosterItem(type = BaseItemKind.EPISODE, episode = it)
|
||||||
|
}
|
||||||
|
is Media.SeriesMedia -> seriesMap[media.seriesId]?.let {
|
||||||
|
PosterItem(type = BaseItemKind.SERIES, series = it)
|
||||||
|
}
|
||||||
|
is Media.SeasonMedia -> seriesMap[media.seriesId]?.let {
|
||||||
|
PosterItem(type = BaseItemKind.SERIES, series = it)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}.distinctBy { it.id }
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
}.stateIn(
|
||||||
.flowOn(Dispatchers.IO)
|
scope = viewModelScope,
|
||||||
.stateIn(
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
scope = viewModelScope,
|
initialValue = emptyMap()
|
||||||
started = SharingStarted.WhileSubscribed(5_000),
|
)
|
||||||
initialValue = emptyMap()
|
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch { mediaRepository.ensureReady() }
|
viewModelScope.launch { mediaRepository.ensureReady() }
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||||||
import hu.bbara.purefin.app.home.ui.PosterItem
|
import hu.bbara.purefin.app.home.ui.PosterItem
|
||||||
import hu.bbara.purefin.client.JellyfinApiClient
|
import hu.bbara.purefin.client.JellyfinApiClient
|
||||||
import hu.bbara.purefin.data.InMemoryMediaRepository
|
import hu.bbara.purefin.data.InMemoryMediaRepository
|
||||||
|
import hu.bbara.purefin.data.model.Media
|
||||||
import hu.bbara.purefin.image.JellyfinImageHelper
|
import hu.bbara.purefin.image.JellyfinImageHelper
|
||||||
import hu.bbara.purefin.navigation.MovieDto
|
import hu.bbara.purefin.navigation.MovieDto
|
||||||
import hu.bbara.purefin.navigation.NavigationManager
|
import hu.bbara.purefin.navigation.NavigationManager
|
||||||
@@ -14,7 +15,8 @@ import hu.bbara.purefin.navigation.SeriesDto
|
|||||||
import hu.bbara.purefin.session.UserSessionRepository
|
import hu.bbara.purefin.session.UserSessionRepository
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
@@ -35,8 +37,26 @@ class LibraryViewModel @Inject constructor(
|
|||||||
started = SharingStarted.Eagerly,
|
started = SharingStarted.Eagerly,
|
||||||
initialValue = ""
|
initialValue = ""
|
||||||
)
|
)
|
||||||
private val _contents = MutableStateFlow<List<PosterItem>>(emptyList())
|
|
||||||
val contents = _contents.asStateFlow()
|
private val _libraryItems = MutableStateFlow<List<Media>>(emptyList())
|
||||||
|
|
||||||
|
val contents: StateFlow<List<PosterItem>> = combine(
|
||||||
|
_libraryItems,
|
||||||
|
mediaRepository.movies,
|
||||||
|
mediaRepository.series
|
||||||
|
) { items, moviesMap, seriesMap ->
|
||||||
|
items.mapNotNull { media ->
|
||||||
|
when (media) {
|
||||||
|
is Media.MovieMedia -> moviesMap[media.movieId]?.let {
|
||||||
|
PosterItem(type = BaseItemKind.MOVIE, movie = it)
|
||||||
|
}
|
||||||
|
is Media.SeriesMedia -> seriesMap[media.seriesId]?.let {
|
||||||
|
PosterItem(type = BaseItemKind.SERIES, series = it)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch { mediaRepository.ensureReady() }
|
viewModelScope.launch { mediaRepository.ensureReady() }
|
||||||
@@ -67,22 +87,10 @@ class LibraryViewModel @Inject constructor(
|
|||||||
fun selectLibrary(libraryId: UUID) {
|
fun selectLibrary(libraryId: UUID) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val libraryItems = jellyfinApiClient.getLibraryContent(libraryId)
|
val libraryItems = jellyfinApiClient.getLibraryContent(libraryId)
|
||||||
_contents.value = libraryItems.map {
|
_libraryItems.value = libraryItems.map {
|
||||||
when (it.type) {
|
when (it.type) {
|
||||||
BaseItemKind.MOVIE -> {
|
BaseItemKind.MOVIE -> Media.MovieMedia(movieId = it.id)
|
||||||
val movie = mediaRepository.getMovie(it.id)
|
BaseItemKind.SERIES -> Media.SeriesMedia(seriesId = it.id)
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.MOVIE,
|
|
||||||
movie = movie
|
|
||||||
)
|
|
||||||
}
|
|
||||||
BaseItemKind.SERIES -> {
|
|
||||||
val series = mediaRepository.getSeries(it.id)
|
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.SERIES,
|
|
||||||
series = series
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> throw UnsupportedOperationException("Unsupported item type: ${it.type}")
|
else -> throw UnsupportedOperationException("Unsupported item type: ${it.type}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,8 +53,16 @@ class InMemoryMediaRepository @Inject constructor(
|
|||||||
override val series: StateFlow<Map<UUID, Series>> = localDataSource.seriesFlow
|
override val series: StateFlow<Map<UUID, Series>> = localDataSource.seriesFlow
|
||||||
.stateIn(scope, SharingStarted.Eagerly, emptyMap())
|
.stateIn(scope, SharingStarted.Eagerly, emptyMap())
|
||||||
|
|
||||||
override fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> =
|
override val episodes: StateFlow<Map<UUID, Episode>> = localDataSource.episodesFlow
|
||||||
localDataSource.observeSeriesWithContent(seriesId)
|
.stateIn(scope, SharingStarted.Eagerly, emptyMap())
|
||||||
|
|
||||||
|
override fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> {
|
||||||
|
scope.launch {
|
||||||
|
awaitReady()
|
||||||
|
ensureSeriesContentLoaded(seriesId)
|
||||||
|
}
|
||||||
|
return localDataSource.observeSeriesWithContent(seriesId)
|
||||||
|
}
|
||||||
|
|
||||||
private val _continueWatching: MutableStateFlow<List<Media>> = MutableStateFlow(emptyList())
|
private val _continueWatching: MutableStateFlow<List<Media>> = MutableStateFlow(emptyList())
|
||||||
val continueWatching: StateFlow<List<Media>> = _continueWatching.asStateFlow()
|
val continueWatching: StateFlow<List<Media>> = _continueWatching.asStateFlow()
|
||||||
@@ -203,23 +211,11 @@ class InMemoryMediaRepository @Inject constructor(
|
|||||||
//TODO Load seasons and episodes, other types are already loaded at this point.
|
//TODO Load seasons and episodes, other types are already loaded at this point.
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getMovie(movieId: UUID): Movie {
|
private suspend fun ensureSeriesContentLoaded(seriesId: UUID) {
|
||||||
awaitReady()
|
awaitReady()
|
||||||
return localDataSource.getMovie(movieId)
|
// Skip if content is already cached in Room
|
||||||
?: throw RuntimeException("Movie not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSeries(seriesId: UUID): Series {
|
|
||||||
awaitReady()
|
|
||||||
return localDataSource.getSeriesBasic(seriesId)
|
|
||||||
?: throw RuntimeException("Series not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSeriesWithContent(seriesId: UUID): Series {
|
|
||||||
awaitReady()
|
|
||||||
// Use cached content if available
|
|
||||||
localDataSource.getSeriesWithContent(seriesId)?.takeIf { it.seasons.isNotEmpty() }?.let {
|
localDataSource.getSeriesWithContent(seriesId)?.takeIf { it.seasons.isNotEmpty() }?.let {
|
||||||
return it
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val series = this.series.value[seriesId] ?: throw RuntimeException("Series not found")
|
val series = this.series.value[seriesId] ?: throw RuntimeException("Series not found")
|
||||||
@@ -234,68 +230,6 @@ class InMemoryMediaRepository @Inject constructor(
|
|||||||
val updatedSeries = series.copy(seasons = filledSeasons)
|
val updatedSeries = series.copy(seasons = filledSeasons)
|
||||||
localDataSource.saveSeries(listOf(updatedSeries))
|
localDataSource.saveSeries(listOf(updatedSeries))
|
||||||
localDataSource.saveSeriesContent(updatedSeries)
|
localDataSource.saveSeriesContent(updatedSeries)
|
||||||
return updatedSeries
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSeason(
|
|
||||||
seriesId: UUID,
|
|
||||||
seasonId: UUID,
|
|
||||||
): Season {
|
|
||||||
awaitReady()
|
|
||||||
localDataSource.getSeason(seriesId, seasonId)?.let { return it }
|
|
||||||
// Fallback: ensure series content is loaded, then retry
|
|
||||||
val series = getSeriesWithContent(seriesId)
|
|
||||||
return series.seasons.find { it.id == seasonId }?: throw RuntimeException("Season not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSeasons(
|
|
||||||
seriesId: UUID,
|
|
||||||
): List<Season> {
|
|
||||||
awaitReady()
|
|
||||||
val seasons = localDataSource.getSeasons(seriesId)
|
|
||||||
if (seasons.isNotEmpty()) return seasons
|
|
||||||
val series = getSeriesWithContent(seriesId)
|
|
||||||
return series.seasons
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getEpisode(
|
|
||||||
seriesId: UUID,
|
|
||||||
episodeId: UUID
|
|
||||||
) : Episode {
|
|
||||||
awaitReady()
|
|
||||||
localDataSource.getEpisodeById(episodeId)?.let { return it }
|
|
||||||
val series = getSeriesWithContent(seriesId)
|
|
||||||
return series.seasons.flatMap { it.episodes }.find { it.id == episodeId }?: throw RuntimeException("Episode not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getEpisode(
|
|
||||||
seriesId: UUID,
|
|
||||||
seasonId: UUID,
|
|
||||||
episodeId: UUID
|
|
||||||
): Episode {
|
|
||||||
awaitReady()
|
|
||||||
localDataSource.getEpisode(seriesId, seasonId, episodeId)?.let { return it }
|
|
||||||
val series = getSeriesWithContent(seriesId)
|
|
||||||
return series.seasons.find { it.id == seasonId }?.episodes?.find { it.id == episodeId } ?: throw RuntimeException("Episode not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getEpisodes(
|
|
||||||
seriesId: UUID,
|
|
||||||
seasonId: UUID
|
|
||||||
): List<Episode> {
|
|
||||||
awaitReady()
|
|
||||||
val episodes = localDataSource.getSeason(seriesId, seasonId)?.episodes
|
|
||||||
if (episodes != null && episodes.isNotEmpty()) return episodes
|
|
||||||
val series = getSeriesWithContent(seriesId)
|
|
||||||
return series.seasons.find { it.id == seasonId }?.episodes ?: throw RuntimeException("Season not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getEpisodes(seriesId: UUID): List<Episode> {
|
|
||||||
awaitReady()
|
|
||||||
val episodes = localDataSource.getEpisodesBySeries(seriesId)
|
|
||||||
if (episodes.isNotEmpty()) return episodes
|
|
||||||
val series = getSeriesWithContent(seriesId)
|
|
||||||
return series.seasons.flatMap { it.episodes }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateWatchProgress(mediaId: UUID, positionMs: Long, durationMs: Long) {
|
override suspend fun updateWatchProgress(mediaId: UUID, positionMs: Long, durationMs: Long) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package hu.bbara.purefin.data
|
|||||||
|
|
||||||
import hu.bbara.purefin.data.model.Episode
|
import hu.bbara.purefin.data.model.Episode
|
||||||
import hu.bbara.purefin.data.model.Movie
|
import hu.bbara.purefin.data.model.Movie
|
||||||
import hu.bbara.purefin.data.model.Season
|
|
||||||
import hu.bbara.purefin.data.model.Series
|
import hu.bbara.purefin.data.model.Series
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -12,22 +11,13 @@ interface MediaRepository {
|
|||||||
|
|
||||||
val movies: StateFlow<Map<UUID, Movie>>
|
val movies: StateFlow<Map<UUID, Movie>>
|
||||||
val series: StateFlow<Map<UUID, Series>>
|
val series: StateFlow<Map<UUID, Series>>
|
||||||
|
val episodes: StateFlow<Map<UUID, Episode>>
|
||||||
val state: StateFlow<MediaRepositoryState>
|
val state: StateFlow<MediaRepositoryState>
|
||||||
|
|
||||||
fun observeSeriesWithContent(seriesId: UUID): Flow<Series?>
|
fun observeSeriesWithContent(seriesId: UUID): Flow<Series?>
|
||||||
|
|
||||||
suspend fun ensureReady()
|
suspend fun ensureReady()
|
||||||
|
|
||||||
suspend fun getMovie(movieId: UUID) : Movie
|
|
||||||
suspend fun getSeries(seriesId: UUID) : Series
|
|
||||||
suspend fun getSeriesWithContent(seriesId: UUID) : Series
|
|
||||||
suspend fun getSeasons(seriesId: UUID) : List<Season>
|
|
||||||
suspend fun getSeason(seriesId: UUID, seasonId: UUID) : Season
|
|
||||||
suspend fun getEpisodes(seriesId: UUID) : List<Episode>
|
|
||||||
suspend fun getEpisodes(seriesId: UUID, seasonId: UUID) : List<Episode>
|
|
||||||
suspend fun getEpisode(seriesId: UUID, seasonId: UUID, episodeId: UUID) : Episode
|
|
||||||
suspend fun getEpisode(seriesId: UUID, episodeId: UUID) : Episode
|
|
||||||
|
|
||||||
suspend fun updateWatchProgress(mediaId: UUID, positionMs: Long, durationMs: Long)
|
suspend fun updateWatchProgress(mediaId: UUID, positionMs: Long, durationMs: Long)
|
||||||
suspend fun refreshHomeData()
|
suspend fun refreshHomeData()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ class RoomMediaLocalDataSource @Inject constructor(
|
|||||||
entities.associate { it.id to it.toDomain(seasons = emptyList(), cast = emptyList()) }
|
entities.associate { it.id to it.toDomain(seasons = emptyList(), cast = emptyList()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val episodesFlow: Flow<Map<UUID, Episode>> = episodeDao.observeAll()
|
||||||
|
.map { entities -> entities.associate { it.id to it.toDomain(cast = emptyList()) } }
|
||||||
|
|
||||||
// Full content Flow for series detail screen (scoped to one series)
|
// Full content Flow for series detail screen (scoped to one series)
|
||||||
fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> =
|
fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> =
|
||||||
seriesDao.observeWithContent(seriesId).map { relation ->
|
seriesDao.observeWithContent(seriesId).map { relation ->
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import androidx.room.Dao
|
|||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.Upsert
|
import androidx.room.Upsert
|
||||||
import hu.bbara.purefin.data.local.room.EpisodeEntity
|
import hu.bbara.purefin.data.local.room.EpisodeEntity
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@@ -20,6 +21,9 @@ interface EpisodeDao {
|
|||||||
@Query("SELECT * FROM episodes WHERE seasonId = :seasonId")
|
@Query("SELECT * FROM episodes WHERE seasonId = :seasonId")
|
||||||
suspend fun getBySeasonId(seasonId: UUID): List<EpisodeEntity>
|
suspend fun getBySeasonId(seasonId: UUID): List<EpisodeEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM episodes")
|
||||||
|
fun observeAll(): Flow<List<EpisodeEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM episodes WHERE id = :id")
|
@Query("SELECT * FROM episodes WHERE id = :id")
|
||||||
suspend fun getById(id: UUID): EpisodeEntity?
|
suspend fun getById(id: UUID): EpisodeEntity?
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user