Refactor the lifecycle state (#135)

This commit is contained in:
Jarne Demeulemeester 2022-07-25 12:57:09 +02:00 committed by GitHub
parent 932ea56335
commit 8552f0c469
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 180 additions and 177 deletions

View file

@ -45,8 +45,8 @@ class AddServerFragment : Fragment() {
} }
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is AddServerViewModel.UiState.Normal -> bindUiStateNormal() is AddServerViewModel.UiState.Normal -> bindUiStateNormal()
@ -54,8 +54,11 @@ class AddServerFragment : Fragment() {
is AddServerViewModel.UiState.Loading -> bindUiStateLoading() is AddServerViewModel.UiState.Loading -> bindUiStateLoading()
} }
} }
viewModel.onNavigateToLogin(viewLifecycleOwner.lifecycleScope) { }
Timber.d("Navigate to login: $it") }
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToLogin.collect {
if (it) { if (it) {
navigateToLoginFragment() navigateToLoginFragment()
} }

View file

@ -47,7 +47,7 @@ class DownloadFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is DownloadViewModel.UiState.Normal -> bindUiStateNormal(uiState) is DownloadViewModel.UiState.Normal -> bindUiStateNormal(uiState)

View file

@ -51,7 +51,7 @@ class DownloadSeriesFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
when (uiState) { when (uiState) {
is DownloadSeriesViewModel.UiState.Normal -> bindUiStateNormal(uiState) is DownloadSeriesViewModel.UiState.Normal -> bindUiStateNormal(uiState)
is DownloadSeriesViewModel.UiState.Loading -> bindUiStateLoading(uiState) is DownloadSeriesViewModel.UiState.Loading -> bindUiStateLoading(uiState)

View file

@ -60,7 +60,7 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is EpisodeBottomSheetViewModel.UiState.Normal -> bindUiStateNormal(uiState) is EpisodeBottomSheetViewModel.UiState.Normal -> bindUiStateNormal(uiState)

View file

@ -46,7 +46,7 @@ class FavoriteFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is FavoriteViewModel.UiState.Normal -> bindUiStateNormal(uiState) is FavoriteViewModel.UiState.Normal -> bindUiStateNormal(uiState)

View file

@ -111,7 +111,7 @@ class HomeFragment : Fragment() {
private fun bindState() { private fun bindState() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is HomeViewModel.UiState.Normal -> bindUiStateNormal(uiState) is HomeViewModel.UiState.Normal -> bindUiStateNormal(uiState)

View file

@ -124,14 +124,18 @@ class LibraryFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
when (uiState) { when (uiState) {
is LibraryViewModel.UiState.Normal -> bindUiStateNormal(uiState) is LibraryViewModel.UiState.Normal -> bindUiStateNormal(uiState)
is LibraryViewModel.UiState.Loading -> bindUiStateLoading() is LibraryViewModel.UiState.Loading -> bindUiStateLoading()
is LibraryViewModel.UiState.Error -> bindUiStateError(uiState) is LibraryViewModel.UiState.Error -> bindUiStateError(uiState)
} }
} }
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Sorting options // Sorting options
val sortBy = SortBy.fromString(sp.getString("sortBy", SortBy.defaultValue.name)!!) val sortBy = SortBy.fromString(sp.getString("sortBy", SortBy.defaultValue.name)!!)
val sortOrder = try { val sortOrder = try {

View file

@ -46,7 +46,7 @@ class LoginFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when(uiState) { when(uiState) {
is LoginViewModel.UiState.Normal -> bindUiStateNormal() is LoginViewModel.UiState.Normal -> bindUiStateNormal()
@ -54,8 +54,12 @@ class LoginFragment : Fragment() {
is LoginViewModel.UiState.Loading -> bindUiStateLoading() is LoginViewModel.UiState.Loading -> bindUiStateLoading()
} }
} }
viewModel.onNavigateToMain(viewLifecycleOwner.lifecycleScope) { }
Timber.d("Navigate to MainActivity: $it") }
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToMain.collect {
if (it) { if (it) {
navigateToMainActivity() navigateToMainActivity()
} }

View file

@ -47,7 +47,7 @@ class MediaFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is MediaViewModel.UiState.Normal -> bindUiStateNormal(uiState) is MediaViewModel.UiState.Normal -> bindUiStateNormal(uiState)

View file

@ -61,7 +61,7 @@ class MediaInfoFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is MediaInfoViewModel.UiState.Normal -> bindUiStateNormal(uiState) is MediaInfoViewModel.UiState.Normal -> bindUiStateNormal(uiState)
@ -69,6 +69,11 @@ class MediaInfoFragment : Fragment() {
is MediaInfoViewModel.UiState.Error -> bindUiStateError(uiState) is MediaInfoViewModel.UiState.Error -> bindUiStateError(uiState)
} }
} }
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
if (!args.isOffline) { if (!args.isOffline) {
viewModel.loadData(args.itemId, args.itemType) viewModel.loadData(args.itemId, args.itemType)
} else { } else {

View file

@ -53,7 +53,7 @@ internal class PersonDetailFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is PersonDetailViewModel.UiState.Normal -> bindUiStateNormal(uiState) is PersonDetailViewModel.UiState.Normal -> bindUiStateNormal(uiState)
@ -61,6 +61,11 @@ internal class PersonDetailFragment : Fragment() {
is PersonDetailViewModel.UiState.Error -> bindUiStateError(uiState) is PersonDetailViewModel.UiState.Error -> bindUiStateError(uiState)
} }
} }
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.loadData(args.personId) viewModel.loadData(args.personId)
} }
} }

View file

@ -48,7 +48,7 @@ class SearchResultFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is SearchResultViewModel.UiState.Normal -> bindUiStateNormal(uiState) is SearchResultViewModel.UiState.Normal -> bindUiStateNormal(uiState)
@ -56,6 +56,11 @@ class SearchResultFragment : Fragment() {
is SearchResultViewModel.UiState.Error -> bindUiStateError(uiState) is SearchResultViewModel.UiState.Error -> bindUiStateError(uiState)
} }
} }
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.loadData(args.query) viewModel.loadData(args.query)
} }
} }

View file

@ -44,7 +44,7 @@ class SeasonFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is SeasonViewModel.UiState.Normal -> bindUiStateNormal(uiState) is SeasonViewModel.UiState.Normal -> bindUiStateNormal(uiState)
@ -52,6 +52,11 @@ class SeasonFragment : Fragment() {
is SeasonViewModel.UiState.Error -> bindUiStateError(uiState) is SeasonViewModel.UiState.Error -> bindUiStateError(uiState)
} }
} }
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.loadEpisodes(args.seriesId, args.seasonId) viewModel.loadEpisodes(args.seriesId, args.seasonId)
} }
} }

View file

@ -50,7 +50,7 @@ class ServerSelectFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onNavigateToMain(viewLifecycleOwner.lifecycleScope) { viewModel.navigateToMain.collect {
if (it) { if (it) {
navigateToMainActivity() navigateToMainActivity()
} }

View file

@ -55,7 +55,7 @@ internal class HomeFragment : BrowseSupportFragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is HomeViewModel.UiState.Normal -> bindUiStateNormal(uiState) is HomeViewModel.UiState.Normal -> bindUiStateNormal(uiState)

View file

@ -63,7 +63,7 @@ internal class MediaDetailFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is MediaInfoViewModel.UiState.Normal -> bindUiStateNormal(uiState) is MediaInfoViewModel.UiState.Normal -> bindUiStateNormal(uiState)
@ -194,12 +194,12 @@ internal class MediaDetailFragment : Fragment() {
false -> { false -> {
val typedValue = TypedValue() val typedValue = TypedValue()
requireActivity().theme.resolveAttribute(R.attr.colorOnSecondaryContainer, typedValue, true) requireActivity().theme.resolveAttribute(R.attr.colorOnSecondaryContainer, typedValue, true)
binding.checkButton.imageTintList = ColorStateList.valueOf( /*binding.checkButton.imageTintList = ColorStateList.valueOf(
resources.getColor( resources.getColor(
typedValue.resourceId, typedValue.resourceId,
requireActivity().theme requireActivity().theme
) )
) )*/
} }
} }

View file

@ -47,7 +47,7 @@ internal class TvAddServerFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when (uiState) { when (uiState) {
is AddServerViewModel.UiState.Normal -> bindUiStateNormal() is AddServerViewModel.UiState.Normal -> bindUiStateNormal()
@ -55,8 +55,12 @@ internal class TvAddServerFragment : Fragment() {
is AddServerViewModel.UiState.Loading -> bindUiStateLoading() is AddServerViewModel.UiState.Loading -> bindUiStateLoading()
} }
} }
viewModel.onNavigateToLogin(viewLifecycleOwner.lifecycleScope) { }
Timber.d("Navigate to login: $it") }
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToLogin.collect {
if (it) { if (it) {
navigateToLoginFragment() navigateToLoginFragment()
} }

View file

@ -46,7 +46,7 @@ class TvLoginFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.uiState.collect { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when(uiState) { when(uiState) {
is LoginViewModel.UiState.Normal -> bindUiStateNormal() is LoginViewModel.UiState.Normal -> bindUiStateNormal()
@ -54,8 +54,12 @@ class TvLoginFragment : Fragment() {
is LoginViewModel.UiState.Loading -> bindUiStateLoading() is LoginViewModel.UiState.Loading -> bindUiStateLoading()
} }
} }
viewModel.onNavigateToMain(viewLifecycleOwner.lifecycleScope) { }
Timber.d("Navigate to MainActivity: $it") }
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToMain.collect {
if (it) { if (it) {
navigateToMainActivity() navigateToMainActivity()
} }

View file

@ -2,7 +2,6 @@ package dev.jdtech.jellyfin.viewmodels
import android.content.res.Resources import android.content.res.Resources
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.LifecycleCoroutineScope
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
@ -12,6 +11,7 @@ import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -31,8 +31,12 @@ constructor(
) : ViewModel() { ) : ViewModel() {
private val resources: Resources = application.resources private val resources: Resources = application.resources
private val uiState = MutableStateFlow<UiState>(UiState.Normal) private val _uiState = MutableStateFlow<UiState>(UiState.Normal)
private val navigateToLogin = MutableSharedFlow<Boolean>() val uiState = _uiState.asStateFlow()
private val _navigateToLogin = MutableSharedFlow<Boolean>()
val navigateToLogin = _navigateToLogin.asSharedFlow()
private var serverFound = false
sealed class UiState { sealed class UiState {
object Normal : UiState() object Normal : UiState()
@ -40,14 +44,6 @@ constructor(
data class Error(val message: String) : UiState() data class Error(val message: String) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
fun onNavigateToLogin(scope: LifecycleCoroutineScope, collector: (Boolean) -> Unit) {
scope.launch { navigateToLogin.collect { collector(it) } }
}
/** /**
* Run multiple check on the server before continuing: * Run multiple check on the server before continuing:
* *
@ -58,7 +54,7 @@ constructor(
*/ */
fun checkServer(inputValue: String) { fun checkServer(inputValue: String) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
// Check if input value is not empty // Check if input value is not empty
@ -78,6 +74,7 @@ constructor(
recommended recommended
.onCompletion { .onCompletion {
if (serverFound) return@onCompletion
when { when {
greatServers.isNotEmpty() -> { greatServers.isNotEmpty() -> {
connectToServer(greatServers.first()) connectToServer(greatServers.first())
@ -102,14 +99,18 @@ constructor(
} }
.collect { recommendedServerInfo -> .collect { recommendedServerInfo ->
when (recommendedServerInfo.score) { when (recommendedServerInfo.score) {
RecommendedServerInfoScore.GREAT -> greatServers.add(recommendedServerInfo) RecommendedServerInfoScore.GREAT -> {
serverFound = true
connectToServer(recommendedServerInfo)
this.cancel()
}
RecommendedServerInfoScore.GOOD -> goodServers.add(recommendedServerInfo) RecommendedServerInfoScore.GOOD -> goodServers.add(recommendedServerInfo)
RecommendedServerInfoScore.OK -> okServers.add(recommendedServerInfo) RecommendedServerInfoScore.OK -> okServers.add(recommendedServerInfo)
RecommendedServerInfoScore.BAD -> Unit RecommendedServerInfoScore.BAD -> Unit
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit( _uiState.emit(
UiState.Error( UiState.Error(
e.message ?: resources.getString(R.string.unknown_error) e.message ?: resources.getString(R.string.unknown_error)
) )
@ -133,8 +134,8 @@ constructor(
api.accessToken = null api.accessToken = null
} }
uiState.emit(UiState.Normal) _uiState.emit(UiState.Normal)
navigateToLogin.emit(true) _navigateToLogin.emit(true)
} }
/** /**

View file

@ -7,6 +7,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.adapters.DownloadEpisodeItem import dev.jdtech.jellyfin.adapters.DownloadEpisodeItem
import dev.jdtech.jellyfin.models.DownloadSeriesMetadata import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -14,7 +15,8 @@ import javax.inject.Inject
class DownloadSeriesViewModel class DownloadSeriesViewModel
@Inject @Inject
constructor() : ViewModel() { constructor() : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal(val downloadEpisodes: List<DownloadEpisodeItem>) : UiState() data class Normal(val downloadEpisodes: List<DownloadEpisodeItem>) : UiState()
@ -22,17 +24,13 @@ constructor() : ViewModel() {
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
fun loadEpisodes(seriesMetadata: DownloadSeriesMetadata) { fun loadEpisodes(seriesMetadata: DownloadSeriesMetadata) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
uiState.emit(UiState.Normal(getEpisodes((seriesMetadata)))) _uiState.emit(UiState.Normal(getEpisodes((seriesMetadata))))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -9,6 +9,7 @@ import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.utils.loadDownloadedEpisodes import dev.jdtech.jellyfin.utils.loadDownloadedEpisodes
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import java.util.* import java.util.*
@ -20,7 +21,8 @@ class DownloadViewModel
constructor( constructor(
private val downloadDatabase: DownloadDatabaseDao, private val downloadDatabase: DownloadDatabaseDao,
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal(val downloadSections: List<DownloadSection>) : UiState() data class Normal(val downloadSections: List<DownloadSection>) : UiState()
@ -28,17 +30,13 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
init { init {
loadData() loadData()
} }
fun loadData() { fun loadData() {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val items = loadDownloadedEpisodes(downloadDatabase) val items = loadDownloadedEpisodes(downloadDatabase)
@ -70,9 +68,9 @@ constructor(
) )
} }
} }
uiState.emit(UiState.Normal(downloadSections)) _uiState.emit(UiState.Normal(downloadSections))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -10,6 +10,7 @@ import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.* import dev.jdtech.jellyfin.utils.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.api.client.exception.ApiClientException import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.model.DateTime import org.jellyfin.sdk.model.DateTime
@ -29,7 +30,8 @@ constructor(
private val jellyfinRepository: JellyfinRepository, private val jellyfinRepository: JellyfinRepository,
private val downloadDatabase: DownloadDatabaseDao private val downloadDatabase: DownloadDatabaseDao
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal( data class Normal(
@ -48,10 +50,6 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
var item: BaseItemDto? = null var item: BaseItemDto? = null
private var runTime: String = "" private var runTime: String = ""
private var dateString: String = "" private var dateString: String = ""
@ -67,7 +65,7 @@ constructor(
fun loadEpisode(episodeId: UUID) { fun loadEpisode(episodeId: UUID) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val tempItem = jellyfinRepository.getItem(episodeId) val tempItem = jellyfinRepository.getItem(episodeId)
item = tempItem item = tempItem
@ -77,7 +75,7 @@ constructor(
favorite = tempItem.userData?.isFavorite == true favorite = tempItem.userData?.isFavorite == true
canDownload = tempItem.canDownload == true canDownload = tempItem.canDownload == true
downloaded = isItemDownloaded(downloadDatabase, episodeId) downloaded = isItemDownloaded(downloadDatabase, episodeId)
uiState.emit( _uiState.emit(
UiState.Normal( UiState.Normal(
tempItem, tempItem,
runTime, runTime,
@ -91,19 +89,19 @@ constructor(
) )
) )
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }
fun loadEpisode(playerItem: PlayerItem) { fun loadEpisode(playerItem: PlayerItem) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
playerItems.add(playerItem) playerItems.add(playerItem)
item = downloadMetadataToBaseItemDto(playerItem.item!!) item = downloadMetadataToBaseItemDto(playerItem.item!!)
available = isItemAvailable(playerItem.itemId) available = isItemAvailable(playerItem.itemId)
Timber.d("Available: $available") Timber.d("Available: $available")
uiState.emit( _uiState.emit(
UiState.Normal( UiState.Normal(
item!!, item!!,
runTime, runTime,

View file

@ -1,6 +1,5 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import androidx.lifecycle.LifecycleCoroutineScope
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
@ -8,6 +7,7 @@ import dev.jdtech.jellyfin.models.FavoriteSection
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
@ -20,7 +20,8 @@ class FavoriteViewModel
constructor( constructor(
private val jellyfinRepository: JellyfinRepository private val jellyfinRepository: JellyfinRepository
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal(val favoriteSections: List<FavoriteSection>) : UiState() data class Normal(val favoriteSections: List<FavoriteSection>) : UiState()
@ -28,22 +29,18 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
init { init {
loadData() loadData()
} }
fun loadData() { fun loadData() {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val items = jellyfinRepository.getFavoriteItems() val items = jellyfinRepository.getFavoriteItems()
if (items.isEmpty()) { if (items.isEmpty()) {
uiState.emit(UiState.Normal(emptyList())) _uiState.emit(UiState.Normal(emptyList()))
return@launch return@launch
} }
@ -76,9 +73,9 @@ constructor(
} }
} }
uiState.emit(UiState.Normal(favoriteSections)) _uiState.emit(UiState.Normal(favoriteSections))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -1,7 +1,6 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import android.app.Application import android.app.Application
import androidx.lifecycle.LifecycleCoroutineScope
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
@ -17,6 +16,7 @@ import dev.jdtech.jellyfin.utils.syncPlaybackProgress
import dev.jdtech.jellyfin.utils.toView import dev.jdtech.jellyfin.utils.toView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
@ -28,7 +28,8 @@ class HomeViewModel @Inject internal constructor(
private val repository: JellyfinRepository, private val repository: JellyfinRepository,
private val downloadDatabase: DownloadDatabaseDao, private val downloadDatabase: DownloadDatabaseDao,
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal(val homeItems: List<HomeItem>) : UiState() data class Normal(val homeItems: List<HomeItem>) : UiState()
@ -36,10 +37,6 @@ class HomeViewModel @Inject internal constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
init { init {
loadData(updateCapabilities = true) loadData(updateCapabilities = true)
} }
@ -48,7 +45,7 @@ class HomeViewModel @Inject internal constructor(
private fun loadData(updateCapabilities: Boolean) { private fun loadData(updateCapabilities: Boolean) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
if (updateCapabilities) repository.postCapabilities() if (updateCapabilities) repository.postCapabilities()
@ -57,9 +54,9 @@ class HomeViewModel @Inject internal constructor(
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
syncPlaybackProgress(downloadDatabase, repository) syncPlaybackProgress(downloadDatabase, repository)
} }
uiState.emit(UiState.Normal(updated)) _uiState.emit(UiState.Normal(updated))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -7,6 +7,7 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.SortBy import dev.jdtech.jellyfin.utils.SortBy
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
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
@ -21,7 +22,8 @@ class LibraryViewModel
constructor( constructor(
private val jellyfinRepository: JellyfinRepository private val jellyfinRepository: JellyfinRepository
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal(val items: Flow<PagingData<BaseItemDto>>) : UiState() data class Normal(val items: Flow<PagingData<BaseItemDto>>) : UiState()
@ -29,10 +31,6 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
fun loadItems( fun loadItems(
parentId: UUID, parentId: UUID,
libraryType: String?, libraryType: String?,
@ -46,7 +44,7 @@ constructor(
else -> null else -> null
} }
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val items = jellyfinRepository.getItemsPaging( val items = jellyfinRepository.getItemsPaging(
@ -56,9 +54,9 @@ constructor(
sortBy = sortBy, sortBy = sortBy,
sortOrder = sortOrder sortOrder = sortOrder
) )
uiState.emit(UiState.Normal(items)) _uiState.emit(UiState.Normal(items))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -12,6 +12,8 @@ import dev.jdtech.jellyfin.database.ServerDatabaseDao
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.AuthenticateUserByName import org.jellyfin.sdk.model.api.AuthenticateUserByName
@ -30,8 +32,10 @@ constructor(
) : ViewModel() { ) : ViewModel() {
private val resources: Resources = application.resources private val resources: Resources = application.resources
private val uiState = MutableStateFlow<UiState>(UiState.Normal) private val _uiState = MutableStateFlow<UiState>(UiState.Normal)
private val navigateToMain = MutableSharedFlow<Boolean>() val uiState = _uiState.asStateFlow()
private val _navigateToMain = MutableSharedFlow<Boolean>()
val navigateToMain = _navigateToMain.asSharedFlow()
sealed class UiState { sealed class UiState {
object Normal : UiState() object Normal : UiState()
@ -39,14 +43,6 @@ constructor(
data class Error(val message: String) : UiState() data class Error(val message: String) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
fun onNavigateToMain(scope: LifecycleCoroutineScope, collector: (Boolean) -> Unit) {
scope.launch { navigateToMain.collect { collector(it) } }
}
/** /**
* Send a authentication request to the Jellyfin server * Send a authentication request to the Jellyfin server
* *
@ -55,7 +51,7 @@ constructor(
*/ */
fun login(username: String, password: String) { fun login(username: String, password: String) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val authenticationResult by jellyfinApi.userApi.authenticateUserByName( val authenticationResult by jellyfinApi.userApi.authenticateUserByName(
@ -87,15 +83,15 @@ constructor(
userId = authenticationResult.user?.id userId = authenticationResult.user?.id
} }
uiState.emit(UiState.Normal) _uiState.emit(UiState.Normal)
navigateToMain.emit(true) _navigateToMain.emit(true)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
val message = val message =
if (e.cause?.message?.contains("401") == true) resources.getString(R.string.login_error_wrong_username_password) else resources.getString( if (e.cause?.message?.contains("401") == true) resources.getString(R.string.login_error_wrong_username_password) else resources.getString(
R.string.unknown_error R.string.unknown_error
) )
uiState.emit(UiState.Error(message)) _uiState.emit(UiState.Error(message))
} }
} }
} }

View file

@ -2,7 +2,6 @@ package dev.jdtech.jellyfin.viewmodels
import android.app.Application import android.app.Application
import android.net.Uri import android.net.Uri
import androidx.lifecycle.LifecycleCoroutineScope
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
@ -13,6 +12,7 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.* import dev.jdtech.jellyfin.utils.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.exception.ApiClientException import org.jellyfin.sdk.api.client.exception.ApiClientException
@ -31,7 +31,8 @@ constructor(
private val jellyfinRepository: JellyfinRepository, private val jellyfinRepository: JellyfinRepository,
private val downloadDatabase: DownloadDatabaseDao, private val downloadDatabase: DownloadDatabaseDao,
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal( data class Normal(
@ -56,10 +57,6 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
var item: BaseItemDto? = null var item: BaseItemDto? = null
private var actors: List<BaseItemPerson> = emptyList() private var actors: List<BaseItemPerson> = emptyList()
private var director: BaseItemPerson? = null private var director: BaseItemPerson? = null
@ -83,7 +80,7 @@ constructor(
fun loadData(itemId: UUID, itemType: BaseItemKind) { fun loadData(itemId: UUID, itemType: BaseItemKind) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val tempItem = jellyfinRepository.getItem(itemId) val tempItem = jellyfinRepository.getItem(itemId)
item = tempItem item = tempItem
@ -102,7 +99,7 @@ constructor(
nextUp = getNextUp(itemId) nextUp = getNextUp(itemId)
seasons = jellyfinRepository.getSeasons(itemId) seasons = jellyfinRepository.getSeasons(itemId)
} }
uiState.emit( _uiState.emit(
UiState.Normal( UiState.Normal(
tempItem, tempItem,
actors, actors,
@ -122,7 +119,7 @@ constructor(
) )
) )
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }
@ -142,7 +139,7 @@ constructor(
played = tempItem.userData?.played ?: false played = tempItem.userData?.played ?: false
favorite = tempItem.userData?.isFavorite ?: false favorite = tempItem.userData?.isFavorite ?: false
available = isItemAvailable(tempItem.id) available = isItemAvailable(tempItem.id)
uiState.emit( _uiState.emit(
UiState.Normal( UiState.Normal(
tempItem, tempItem,
actors, actors,

View file

@ -5,6 +5,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.models.CollectionType import dev.jdtech.jellyfin.models.CollectionType
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import javax.inject.Inject import javax.inject.Inject
@ -16,7 +17,8 @@ constructor(
private val jellyfinRepository: JellyfinRepository private val jellyfinRepository: JellyfinRepository
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal(val collections: List<BaseItemDto>) : UiState() data class Normal(val collections: List<BaseItemDto>) : UiState()
@ -24,24 +26,20 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
init { init {
loadData() loadData()
} }
fun loadData() { fun loadData() {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val items = jellyfinRepository.getItems() val items = jellyfinRepository.getItems()
val collections = val collections =
items.filter { collection -> CollectionType.unsupportedCollections.none { it.type == collection.collectionType } } items.filter { collection -> CollectionType.unsupportedCollections.none { it.type == collection.collectionType } }
uiState.emit(UiState.Normal(collections)) _uiState.emit(UiState.Normal(collections))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit( _uiState.emit(
UiState.Error(e) UiState.Error(e)
) )
} }

View file

@ -1,11 +1,11 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import androidx.lifecycle.LifecycleCoroutineScope
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 dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
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
@ -17,7 +17,8 @@ internal class PersonDetailViewModel @Inject internal constructor(
private val jellyfinRepository: JellyfinRepository private val jellyfinRepository: JellyfinRepository
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal(val data: PersonOverview, val starredIn: StarredIn) : UiState() data class Normal(val data: PersonOverview, val starredIn: StarredIn) : UiState()
@ -25,13 +26,9 @@ internal class PersonDetailViewModel @Inject internal constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
fun loadData(personId: UUID) { fun loadData(personId: UUID) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val personDetail = jellyfinRepository.getItem(personId) val personDetail = jellyfinRepository.getItem(personId)
@ -52,9 +49,9 @@ internal class PersonDetailViewModel @Inject internal constructor(
val starredIn = StarredIn(movies, shows) val starredIn = StarredIn(movies, shows)
uiState.emit(UiState.Normal(data, starredIn)) _uiState.emit(UiState.Normal(data, starredIn))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -1,6 +1,5 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import androidx.lifecycle.LifecycleCoroutineScope
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
@ -8,6 +7,7 @@ import dev.jdtech.jellyfin.models.FavoriteSection
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
@ -20,7 +20,8 @@ class SearchResultViewModel
constructor( constructor(
private val jellyfinRepository: JellyfinRepository private val jellyfinRepository: JellyfinRepository
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal(val sections: List<FavoriteSection>) : UiState() data class Normal(val sections: List<FavoriteSection>) : UiState()
@ -28,18 +29,14 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
fun loadData(query: String) { fun loadData(query: String) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val items = jellyfinRepository.getSearchItems(query) val items = jellyfinRepository.getSearchItems(query)
if (items.isEmpty()) { if (items.isEmpty()) {
uiState.emit(UiState.Normal(emptyList())) _uiState.emit(UiState.Normal(emptyList()))
return@launch return@launch
} }
@ -72,9 +69,9 @@ constructor(
} }
} }
uiState.emit(UiState.Normal(sections)) _uiState.emit(UiState.Normal(sections))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -5,6 +5,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.adapters.EpisodeItem import dev.jdtech.jellyfin.adapters.EpisodeItem
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.ItemFields import org.jellyfin.sdk.model.api.ItemFields
import java.util.* import java.util.*
@ -16,7 +17,8 @@ class SeasonViewModel
constructor( constructor(
private val jellyfinRepository: JellyfinRepository private val jellyfinRepository: JellyfinRepository
) : ViewModel() { ) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState { sealed class UiState {
data class Normal(val episodes: List<EpisodeItem>) : UiState() data class Normal(val episodes: List<EpisodeItem>) : UiState()
@ -24,18 +26,14 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
fun loadEpisodes(seriesId: UUID, seasonId: UUID) { fun loadEpisodes(seriesId: UUID, seasonId: UUID) {
viewModelScope.launch { viewModelScope.launch {
uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val episodes = getEpisodes(seriesId, seasonId) val episodes = getEpisodes(seriesId, seasonId)
uiState.emit(UiState.Normal(episodes)) _uiState.emit(UiState.Normal(episodes))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -1,7 +1,6 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.lifecycle.LifecycleCoroutineScope
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
@ -9,8 +8,8 @@ import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -25,15 +24,8 @@ constructor(
) : ViewModel() { ) : ViewModel() {
val servers = database.getAllServers() val servers = database.getAllServers()
private val navigateToMain = MutableSharedFlow<Boolean>( private val _navigateToMain = MutableSharedFlow<Boolean>()
replay = 0, val navigateToMain = _navigateToMain.asSharedFlow()
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
fun onNavigateToMain(scope: LifecycleCoroutineScope, collector: (Boolean) -> Unit) {
scope.launch { navigateToMain.collect { collector(it) } }
}
/** /**
* Delete server from database * Delete server from database
@ -47,16 +39,18 @@ constructor(
} }
fun connectToServer(server: Server) { fun connectToServer(server: Server) {
val spEdit = sharedPreferences.edit() viewModelScope.launch {
spEdit.putString("selectedServer", server.id) val spEdit = sharedPreferences.edit()
spEdit.apply() spEdit.putString("selectedServer", server.id)
spEdit.apply()
jellyfinApi.apply { jellyfinApi.apply {
api.baseUrl = server.address api.baseUrl = server.address
api.accessToken = server.accessToken api.accessToken = server.accessToken
userId = UUID.fromString(server.userId) userId = UUID.fromString(server.userId)
}
_navigateToMain.emit(true)
} }
navigateToMain.tryEmit(true)
} }
} }