mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
refactor: Do not use JellyfinApiClient in viewModels. Use MediaRepository for consistency
This commit is contained in:
@@ -3,30 +3,23 @@ package hu.bbara.purefin.app.content.movie
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
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.DownloadState
|
||||||
import hu.bbara.purefin.download.MediaDownloadManager
|
import hu.bbara.purefin.download.MediaDownloadManager
|
||||||
import hu.bbara.purefin.image.JellyfinImageHelper
|
|
||||||
import hu.bbara.purefin.navigation.NavigationManager
|
import hu.bbara.purefin.navigation.NavigationManager
|
||||||
import hu.bbara.purefin.navigation.Route
|
import hu.bbara.purefin.navigation.Route
|
||||||
import hu.bbara.purefin.session.UserSessionRepository
|
|
||||||
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.first
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
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.BaseItemPerson
|
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MovieScreenViewModel @Inject constructor(
|
class MovieScreenViewModel @Inject constructor(
|
||||||
private val jellyfinApiClient: JellyfinApiClient,
|
private val mediaRepository: MediaRepository,
|
||||||
private val navigationManager: NavigationManager,
|
private val navigationManager: NavigationManager,
|
||||||
private val userSessionRepository: UserSessionRepository,
|
|
||||||
private val mediaDownloadManager: MediaDownloadManager
|
private val mediaDownloadManager: MediaDownloadManager
|
||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
|
|
||||||
@@ -47,15 +40,12 @@ class MovieScreenViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun selectMovie(movieId: UUID) {
|
fun selectMovie(movieId: UUID) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val movieInfo = jellyfinApiClient.getItemInfo(movieId)
|
val movieData = mediaRepository.movies.value[movieId]
|
||||||
if (movieInfo == null) {
|
if (movieData == null) {
|
||||||
_movie.value = null
|
_movie.value = null
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val serverUrl = userSessionRepository.serverUrl.first().trim().ifBlank {
|
_movie.value = movieData.toUiModel()
|
||||||
"https://jellyfin.bbara.hu"
|
|
||||||
}
|
|
||||||
_movie.value = movieInfo.toUiModel(serverUrl)
|
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
mediaDownloadManager.observeDownloadState(movieId.toString()).collect {
|
mediaDownloadManager.observeDownloadState(movieId.toString()).collect {
|
||||||
@@ -82,54 +72,20 @@ class MovieScreenViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun BaseItemDto.toUiModel(serverUrl: String): MovieUiModel {
|
private fun Movie.toUiModel(): 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() }
|
|
||||||
return MovieUiModel(
|
return MovieUiModel(
|
||||||
id = id,
|
id = id,
|
||||||
|
title = title,
|
||||||
title = name ?: "Unknown title",
|
|
||||||
year = year,
|
year = year,
|
||||||
rating = rating,
|
rating = rating,
|
||||||
runtime = runtime,
|
runtime = runtime,
|
||||||
format = format,
|
format = format,
|
||||||
synopsis = synopsis,
|
synopsis = synopsis,
|
||||||
heroImageUrl = heroImageUrl,
|
heroImageUrl = heroImageUrl,
|
||||||
audioTrack = "Default",
|
audioTrack = audioTrack,
|
||||||
subtitles = "Unknown",
|
subtitles = subtitles,
|
||||||
cast = cast
|
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.LibraryItem
|
||||||
import hu.bbara.purefin.app.home.ui.NextUpItem
|
import hu.bbara.purefin.app.home.ui.NextUpItem
|
||||||
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.data.MediaRepository
|
import hu.bbara.purefin.data.MediaRepository
|
||||||
import hu.bbara.purefin.data.model.Media
|
import hu.bbara.purefin.data.model.Media
|
||||||
import hu.bbara.purefin.domain.usecase.RefreshHomeDataUseCase
|
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.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.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
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
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -37,7 +35,6 @@ class HomePageViewModel @Inject constructor(
|
|||||||
private val mediaRepository: MediaRepository,
|
private val mediaRepository: MediaRepository,
|
||||||
private val userSessionRepository: UserSessionRepository,
|
private val userSessionRepository: UserSessionRepository,
|
||||||
private val navigationManager: NavigationManager,
|
private val navigationManager: NavigationManager,
|
||||||
private val jellyfinApiClient: JellyfinApiClient,
|
|
||||||
private val refreshHomeDataUseCase: RefreshHomeDataUseCase
|
private val refreshHomeDataUseCase: RefreshHomeDataUseCase
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@@ -47,8 +44,20 @@ class HomePageViewModel @Inject constructor(
|
|||||||
initialValue = ""
|
initialValue = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
private val _libraries = MutableStateFlow<List<LibraryItem>>(emptyList())
|
val libraries = mediaRepository.libraries.map { libraries ->
|
||||||
val libraries = _libraries.asStateFlow()
|
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(
|
val isOfflineMode = userSessionRepository.isOfflineMode.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
@@ -56,12 +65,6 @@ class HomePageViewModel @Inject constructor(
|
|||||||
initialValue = false
|
initialValue = false
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
|
||||||
viewModelScope.launch {
|
|
||||||
loadLibraries()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val continueWatching = combine(
|
val continueWatching = combine(
|
||||||
mediaRepository.continueWatching,
|
mediaRepository.continueWatching,
|
||||||
mediaRepository.movies,
|
mediaRepository.movies,
|
||||||
@@ -182,19 +185,6 @@ class HomePageViewModel @Inject constructor(
|
|||||||
navigationManager.replaceAll(Route.Home)
|
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 {
|
fun getImageUrl(itemId: UUID, type: ImageType): String {
|
||||||
return JellyfinImageHelper.toImageUrl(
|
return JellyfinImageHelper.toImageUrl(
|
||||||
url = _url.value,
|
url = _url.value,
|
||||||
|
|||||||
@@ -4,28 +4,21 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
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.data.MediaRepository
|
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.MovieDto
|
||||||
import hu.bbara.purefin.navigation.NavigationManager
|
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 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.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
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
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.collections.emptyList
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class LibraryViewModel @Inject constructor(
|
class LibraryViewModel @Inject constructor(
|
||||||
|
|||||||
Reference in New Issue
Block a user