From 9a9cb9c2e7a32f92b5510faa70251e8e15b87b88 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Wed, 18 Feb 2026 18:37:57 +0100 Subject: [PATCH] refactor: Do not use JellyfinApiClient in viewModels. Use MediaRepository for consistency --- .../app/content/movie/MovieScreenViewModel.kt | 66 ++++--------------- .../purefin/app/home/HomePageViewModel.kt | 42 +++++------- .../purefin/app/library/LibraryViewModel.kt | 7 -- 3 files changed, 27 insertions(+), 88 deletions(-) diff --git a/app/src/main/java/hu/bbara/purefin/app/content/movie/MovieScreenViewModel.kt b/app/src/main/java/hu/bbara/purefin/app/content/movie/MovieScreenViewModel.kt index a754408..2c906c1 100644 --- a/app/src/main/java/hu/bbara/purefin/app/content/movie/MovieScreenViewModel.kt +++ b/app/src/main/java/hu/bbara/purefin/app/content/movie/MovieScreenViewModel.kt @@ -3,30 +3,23 @@ package hu.bbara.purefin.app.content.movie import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import hu.bbara.purefin.client.JellyfinApiClient +import hu.bbara.purefin.data.MediaRepository +import hu.bbara.purefin.data.model.Movie import hu.bbara.purefin.download.DownloadState import hu.bbara.purefin.download.MediaDownloadManager -import hu.bbara.purefin.image.JellyfinImageHelper import hu.bbara.purefin.navigation.NavigationManager import hu.bbara.purefin.navigation.Route -import hu.bbara.purefin.session.UserSessionRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.jellyfin.sdk.model.UUID -import org.jellyfin.sdk.model.api.BaseItemDto -import org.jellyfin.sdk.model.api.BaseItemPerson -import org.jellyfin.sdk.model.api.ImageType -import java.util.concurrent.TimeUnit import javax.inject.Inject @HiltViewModel class MovieScreenViewModel @Inject constructor( - private val jellyfinApiClient: JellyfinApiClient, + private val mediaRepository: MediaRepository, private val navigationManager: NavigationManager, - private val userSessionRepository: UserSessionRepository, private val mediaDownloadManager: MediaDownloadManager ): ViewModel() { @@ -47,15 +40,12 @@ class MovieScreenViewModel @Inject constructor( fun selectMovie(movieId: UUID) { viewModelScope.launch { - val movieInfo = jellyfinApiClient.getItemInfo(movieId) - if (movieInfo == null) { + val movieData = mediaRepository.movies.value[movieId] + if (movieData == null) { _movie.value = null return@launch } - val serverUrl = userSessionRepository.serverUrl.first().trim().ifBlank { - "https://jellyfin.bbara.hu" - } - _movie.value = movieInfo.toUiModel(serverUrl) + _movie.value = movieData.toUiModel() launch { mediaDownloadManager.observeDownloadState(movieId.toString()).collect { @@ -82,54 +72,20 @@ class MovieScreenViewModel @Inject constructor( } } - private fun BaseItemDto.toUiModel(serverUrl: String): MovieUiModel { - val year = productionYear?.toString() ?: premiereDate?.year?.toString().orEmpty() - val rating = officialRating ?: "NR" - val runtime = formatRuntime(runTimeTicks) - val format = container?.uppercase() ?: "VIDEO" - val synopsis = overview ?: "No synopsis available." - val heroImageUrl = id?.let { itemId -> - JellyfinImageHelper.toImageUrl( - url = serverUrl, - itemId = itemId, - type = ImageType.BACKDROP - ) - } ?: "" - val cast = people.orEmpty().map { it.toCastMember() } + private fun Movie.toUiModel(): MovieUiModel { return MovieUiModel( id = id, - - title = name ?: "Unknown title", + title = title, year = year, rating = rating, runtime = runtime, format = format, synopsis = synopsis, heroImageUrl = heroImageUrl, - audioTrack = "Default", - subtitles = "Unknown", - cast = cast + audioTrack = audioTrack, + subtitles = subtitles, + cast = cast.map { CastMember(name = it.name, role = it.role, imageUrl = it.imageUrl) } ) } - private fun BaseItemPerson.toCastMember(): CastMember { - return CastMember( - name = name ?: "Unknown", - role = role ?: "", - imageUrl = null - ) - } - - private fun formatRuntime(ticks: Long?): String { - if (ticks == null || ticks <= 0) return "—" - val totalSeconds = ticks / 10_000_000 - val hours = TimeUnit.SECONDS.toHours(totalSeconds) - val minutes = TimeUnit.SECONDS.toMinutes(totalSeconds) % 60 - return if (hours > 0) { - "${hours}h ${minutes}m" - } else { - "${minutes}m" - } - } - } diff --git a/app/src/main/java/hu/bbara/purefin/app/home/HomePageViewModel.kt b/app/src/main/java/hu/bbara/purefin/app/home/HomePageViewModel.kt index a387009..9ec07ac 100644 --- a/app/src/main/java/hu/bbara/purefin/app/home/HomePageViewModel.kt +++ b/app/src/main/java/hu/bbara/purefin/app/home/HomePageViewModel.kt @@ -8,7 +8,6 @@ import hu.bbara.purefin.app.home.ui.HomeNavItem import hu.bbara.purefin.app.home.ui.LibraryItem import hu.bbara.purefin.app.home.ui.NextUpItem import hu.bbara.purefin.app.home.ui.PosterItem -import hu.bbara.purefin.client.JellyfinApiClient import hu.bbara.purefin.data.MediaRepository import hu.bbara.purefin.data.model.Media import hu.bbara.purefin.domain.usecase.RefreshHomeDataUseCase @@ -20,15 +19,14 @@ import hu.bbara.purefin.navigation.NavigationManager import hu.bbara.purefin.navigation.Route import hu.bbara.purefin.navigation.SeriesDto import hu.bbara.purefin.session.UserSessionRepository -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.jellyfin.sdk.model.UUID -import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.CollectionType import org.jellyfin.sdk.model.api.ImageType import javax.inject.Inject @@ -37,7 +35,6 @@ class HomePageViewModel @Inject constructor( private val mediaRepository: MediaRepository, private val userSessionRepository: UserSessionRepository, private val navigationManager: NavigationManager, - private val jellyfinApiClient: JellyfinApiClient, private val refreshHomeDataUseCase: RefreshHomeDataUseCase ) : ViewModel() { @@ -47,8 +44,20 @@ class HomePageViewModel @Inject constructor( initialValue = "" ) - private val _libraries = MutableStateFlow>(emptyList()) - val libraries = _libraries.asStateFlow() + val libraries = mediaRepository.libraries.map { libraries -> + libraries.map { + LibraryItem( + id = it.id, + name = it.name, + type = it.type, + isEmpty = when(it.type) { + CollectionType.MOVIES -> mediaRepository.movies.value.isEmpty() + CollectionType.TVSHOWS -> mediaRepository.series.value.isEmpty() + else -> true + } + ) + } + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) val isOfflineMode = userSessionRepository.isOfflineMode.stateIn( scope = viewModelScope, @@ -56,12 +65,6 @@ class HomePageViewModel @Inject constructor( initialValue = false ) - init { - viewModelScope.launch { - loadLibraries() - } - } - val continueWatching = combine( mediaRepository.continueWatching, mediaRepository.movies, @@ -182,19 +185,6 @@ class HomePageViewModel @Inject constructor( navigationManager.replaceAll(Route.Home) } - private suspend fun loadLibraries() { - val libraries: List = jellyfinApiClient.getLibraries() - val mappedLibraries = libraries.map { - LibraryItem( - name = it.name!!, - id = it.id, - isEmpty = it.childCount!! == 0, - type = it.collectionType!! - ) - } - _libraries.value = mappedLibraries - } - fun getImageUrl(itemId: UUID, type: ImageType): String { return JellyfinImageHelper.toImageUrl( url = _url.value, diff --git a/app/src/main/java/hu/bbara/purefin/app/library/LibraryViewModel.kt b/app/src/main/java/hu/bbara/purefin/app/library/LibraryViewModel.kt index cb6d362..e6b59dc 100644 --- a/app/src/main/java/hu/bbara/purefin/app/library/LibraryViewModel.kt +++ b/app/src/main/java/hu/bbara/purefin/app/library/LibraryViewModel.kt @@ -4,28 +4,21 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import hu.bbara.purefin.app.home.ui.PosterItem -import hu.bbara.purefin.client.JellyfinApiClient import hu.bbara.purefin.data.MediaRepository -import hu.bbara.purefin.data.model.Media -import hu.bbara.purefin.image.JellyfinImageHelper import hu.bbara.purefin.navigation.MovieDto import hu.bbara.purefin.navigation.NavigationManager import hu.bbara.purefin.navigation.Route import hu.bbara.purefin.navigation.SeriesDto -import hu.bbara.purefin.session.UserSessionRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.jellyfin.sdk.model.UUID import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.CollectionType -import org.jellyfin.sdk.model.api.ImageType import javax.inject.Inject -import kotlin.collections.emptyList @HiltViewModel class LibraryViewModel @Inject constructor(