refactor: Do not use JellyfinApiClient in viewModels. Use MediaRepository for consistency

This commit is contained in:
2026-02-18 18:37:57 +01:00
parent 1a46247da0
commit 9a9cb9c2e7
3 changed files with 27 additions and 88 deletions

View File

@@ -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"
}
}
}

View File

@@ -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<List<LibraryItem>>(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<BaseItemDto> = 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,

View File

@@ -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(