refactor: add CompositeMediaRepository to switch between offline/online based on user session

Extract AppContentRepository interface from MediaRepository for home/library-specific features.
CompositeMediaRepository delegates to OfflineMediaRepository or AppContentRepository based on
UserSessionRepository.isOfflineMode, allowing content ViewModels to use a single MediaRepository
interface that automatically switches data source.
This commit is contained in:
2026-02-22 14:17:58 +01:00
parent d5c0bbded6
commit 69fda1fef2
11 changed files with 126 additions and 66 deletions

View File

@@ -12,7 +12,6 @@ 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 org.jellyfin.sdk.model.UUID
import javax.inject.Inject
@@ -31,10 +30,6 @@ class EpisodeScreenViewModel @Inject constructor(
id?.let { episodesMap[it] }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
init {
viewModelScope.launch { mediaRepository.ensureReady() }
}
fun onBack() {
navigationManager.pop()
}

View File

@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.UUID
import javax.inject.Inject
@@ -34,20 +33,14 @@ class SeriesViewModel @Inject constructor(
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
init {
viewModelScope.launch { mediaRepository.ensureReady() }
}
fun onSelectEpisode(seriesId: UUID, seasonId:UUID, episodeId: UUID) {
viewModelScope.launch {
navigationManager.navigate(Route.EpisodeRoute(
EpisodeDto(
id = episodeId,
seasonId = seasonId,
seriesId = seriesId
)
))
}
fun onSelectEpisode(seriesId: UUID, seasonId: UUID, episodeId: UUID) {
navigationManager.navigate(Route.EpisodeRoute(
EpisodeDto(
id = episodeId,
seasonId = seasonId,
seriesId = seriesId
)
))
}
fun onBack() {

View File

@@ -3,7 +3,7 @@ package hu.bbara.purefin.feature.shared.home
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.core.data.MediaRepository
import hu.bbara.purefin.core.data.AppContentRepository
import hu.bbara.purefin.core.data.domain.usecase.RefreshHomeDataUseCase
import hu.bbara.purefin.core.data.navigation.EpisodeDto
import hu.bbara.purefin.core.data.navigation.LibraryDto
@@ -25,7 +25,7 @@ import javax.inject.Inject
@HiltViewModel
class HomePageViewModel @Inject constructor(
private val mediaRepository: MediaRepository,
private val appContentRepository: AppContentRepository,
private val userSessionRepository: UserSessionRepository,
private val navigationManager: NavigationManager,
private val refreshHomeDataUseCase: RefreshHomeDataUseCase
@@ -37,7 +37,7 @@ class HomePageViewModel @Inject constructor(
initialValue = ""
)
val libraries = mediaRepository.libraries.map { libraries ->
val libraries = appContentRepository.libraries.map { libraries ->
libraries.map {
LibraryItem(
id = it.id,
@@ -45,8 +45,8 @@ class HomePageViewModel @Inject constructor(
type = it.type,
posterUrl = it.posterUrl,
isEmpty = when(it.type) {
CollectionType.MOVIES -> mediaRepository.movies.value.isEmpty()
CollectionType.TVSHOWS -> mediaRepository.series.value.isEmpty()
CollectionType.MOVIES -> appContentRepository.movies.value.isEmpty()
CollectionType.TVSHOWS -> appContentRepository.series.value.isEmpty()
else -> true
}
)
@@ -60,9 +60,9 @@ class HomePageViewModel @Inject constructor(
)
val continueWatching = combine(
mediaRepository.continueWatching,
mediaRepository.movies,
mediaRepository.episodes
appContentRepository.continueWatching,
appContentRepository.movies,
appContentRepository.episodes
) { list, moviesMap, episodesMap ->
list.mapNotNull { media ->
when (media) {
@@ -82,8 +82,8 @@ class HomePageViewModel @Inject constructor(
)
val nextUp = combine(
mediaRepository.nextUp,
mediaRepository.episodes
appContentRepository.nextUp,
appContentRepository.episodes
) { list, episodesMap ->
list.mapNotNull { media ->
when (media) {
@@ -100,10 +100,10 @@ class HomePageViewModel @Inject constructor(
)
val latestLibraryContent = combine(
mediaRepository.latestLibraryContent,
mediaRepository.movies,
mediaRepository.series,
mediaRepository.episodes
appContentRepository.latestLibraryContent,
appContentRepository.movies,
appContentRepository.series,
appContentRepository.episodes
) { libraryMap, moviesMap, seriesMap, episodesMap ->
libraryMap.mapValues { (_, items) ->
items.mapNotNull { media ->
@@ -131,7 +131,7 @@ class HomePageViewModel @Inject constructor(
)
init {
viewModelScope.launch { mediaRepository.ensureReady() }
viewModelScope.launch { appContentRepository.ensureReady() }
}
fun onLibrarySelected(id: UUID, name: String) {

View File

@@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.feature.shared.home.PosterItem
import hu.bbara.purefin.core.data.MediaRepository
import hu.bbara.purefin.core.data.AppContentRepository
import hu.bbara.purefin.core.data.navigation.MovieDto
import hu.bbara.purefin.core.data.navigation.NavigationManager
import hu.bbara.purefin.core.data.navigation.Route
@@ -22,13 +22,13 @@ import javax.inject.Inject
@HiltViewModel
class LibraryViewModel @Inject constructor(
private val mediaRepository: MediaRepository,
private val appContentRepository: AppContentRepository,
private val navigationManager: NavigationManager
) : ViewModel() {
private val selectedLibrary = MutableStateFlow<UUID?>(null)
val contents: StateFlow<List<PosterItem>> = combine(selectedLibrary, mediaRepository.libraries) {
val contents: StateFlow<List<PosterItem>> = combine(selectedLibrary, appContentRepository.libraries) {
libraryId, libraries ->
if (libraryId == null) {
return@combine emptyList()
@@ -46,7 +46,7 @@ class LibraryViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
init {
viewModelScope.launch { mediaRepository.ensureReady() }
viewModelScope.launch { appContentRepository.ensureReady() }
}
fun onMovieSelected(movieId: UUID) {