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.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
Timber.d("$uiState")
when (uiState) {
is AddServerViewModel.UiState.Normal -> bindUiStateNormal()
@ -54,8 +54,11 @@ class AddServerFragment : Fragment() {
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) {
navigateToLoginFragment()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -46,7 +46,7 @@ class LoginFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
viewModel.uiState.collect { uiState ->
Timber.d("$uiState")
when(uiState) {
is LoginViewModel.UiState.Normal -> bindUiStateNormal()
@ -54,8 +54,12 @@ class LoginFragment : Fragment() {
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) {
navigateToMainActivity()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -47,7 +47,7 @@ internal class TvAddServerFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
viewModel.uiState.collect { uiState ->
Timber.d("$uiState")
when (uiState) {
is AddServerViewModel.UiState.Normal -> bindUiStateNormal()
@ -55,8 +55,12 @@ internal class TvAddServerFragment : Fragment() {
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) {
navigateToLoginFragment()
}

View file

@ -46,7 +46,7 @@ class TvLoginFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
viewModel.uiState.collect { uiState ->
Timber.d("$uiState")
when(uiState) {
is LoginViewModel.UiState.Normal -> bindUiStateNormal()
@ -54,8 +54,12 @@ class TvLoginFragment : Fragment() {
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) {
navigateToMainActivity()
}

View file

@ -2,7 +2,6 @@ package dev.jdtech.jellyfin.viewmodels
import android.content.res.Resources
import android.widget.Toast
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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.ServerDatabaseDao
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -31,8 +31,12 @@ constructor(
) : ViewModel() {
private val resources: Resources = application.resources
private val uiState = MutableStateFlow<UiState>(UiState.Normal)
private val navigateToLogin = MutableSharedFlow<Boolean>()
private val _uiState = MutableStateFlow<UiState>(UiState.Normal)
val uiState = _uiState.asStateFlow()
private val _navigateToLogin = MutableSharedFlow<Boolean>()
val navigateToLogin = _navigateToLogin.asSharedFlow()
private var serverFound = false
sealed class UiState {
object Normal : UiState()
@ -40,14 +44,6 @@ constructor(
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:
*
@ -58,7 +54,7 @@ constructor(
*/
fun checkServer(inputValue: String) {
viewModelScope.launch {
uiState.emit(UiState.Loading)
_uiState.emit(UiState.Loading)
try {
// Check if input value is not empty
@ -78,6 +74,7 @@ constructor(
recommended
.onCompletion {
if (serverFound) return@onCompletion
when {
greatServers.isNotEmpty() -> {
connectToServer(greatServers.first())
@ -102,14 +99,18 @@ constructor(
}
.collect { recommendedServerInfo ->
when (recommendedServerInfo.score) {
RecommendedServerInfoScore.GREAT -> greatServers.add(recommendedServerInfo)
RecommendedServerInfoScore.GREAT -> {
serverFound = true
connectToServer(recommendedServerInfo)
this.cancel()
}
RecommendedServerInfoScore.GOOD -> goodServers.add(recommendedServerInfo)
RecommendedServerInfoScore.OK -> okServers.add(recommendedServerInfo)
RecommendedServerInfoScore.BAD -> Unit
}
}
} catch (e: Exception) {
uiState.emit(
_uiState.emit(
UiState.Error(
e.message ?: resources.getString(R.string.unknown_error)
)
@ -133,8 +134,8 @@ constructor(
api.accessToken = null
}
uiState.emit(UiState.Normal)
navigateToLogin.emit(true)
_uiState.emit(UiState.Normal)
_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.models.DownloadSeriesMetadata
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -14,7 +15,8 @@ import javax.inject.Inject
class DownloadSeriesViewModel
@Inject
constructor() : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading)
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState {
data class Normal(val downloadEpisodes: List<DownloadEpisodeItem>) : UiState()
@ -22,17 +24,13 @@ constructor() : ViewModel() {
data class Error(val error: Exception) : UiState()
}
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
fun loadEpisodes(seriesMetadata: DownloadSeriesMetadata) {
viewModelScope.launch {
uiState.emit(UiState.Loading)
_uiState.emit(UiState.Loading)
try {
uiState.emit(UiState.Normal(getEpisodes((seriesMetadata))))
_uiState.emit(UiState.Normal(getEpisodes((seriesMetadata))))
} 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 kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemKind
import java.util.*
@ -20,7 +21,8 @@ class DownloadViewModel
constructor(
private val downloadDatabase: DownloadDatabaseDao,
) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading)
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState {
data class Normal(val downloadSections: List<DownloadSection>) : UiState()
@ -28,17 +30,13 @@ constructor(
data class Error(val error: Exception) : UiState()
}
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
init {
loadData()
}
fun loadData() {
viewModelScope.launch {
uiState.emit(UiState.Loading)
_uiState.emit(UiState.Loading)
try {
val items = loadDownloadedEpisodes(downloadDatabase)
@ -70,9 +68,9 @@ constructor(
)
}
}
uiState.emit(UiState.Normal(downloadSections))
_uiState.emit(UiState.Normal(downloadSections))
} 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.utils.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.model.DateTime
@ -29,7 +30,8 @@ constructor(
private val jellyfinRepository: JellyfinRepository,
private val downloadDatabase: DownloadDatabaseDao
) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading)
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState {
data class Normal(
@ -48,10 +50,6 @@ constructor(
data class Error(val error: Exception) : UiState()
}
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
var item: BaseItemDto? = null
private var runTime: String = ""
private var dateString: String = ""
@ -67,7 +65,7 @@ constructor(
fun loadEpisode(episodeId: UUID) {
viewModelScope.launch {
uiState.emit(UiState.Loading)
_uiState.emit(UiState.Loading)
try {
val tempItem = jellyfinRepository.getItem(episodeId)
item = tempItem
@ -77,7 +75,7 @@ constructor(
favorite = tempItem.userData?.isFavorite == true
canDownload = tempItem.canDownload == true
downloaded = isItemDownloaded(downloadDatabase, episodeId)
uiState.emit(
_uiState.emit(
UiState.Normal(
tempItem,
runTime,
@ -91,19 +89,19 @@ constructor(
)
)
} catch (e: Exception) {
uiState.emit(UiState.Error(e))
_uiState.emit(UiState.Error(e))
}
}
}
fun loadEpisode(playerItem: PlayerItem) {
viewModelScope.launch {
uiState.emit(UiState.Loading)
_uiState.emit(UiState.Loading)
playerItems.add(playerItem)
item = downloadMetadataToBaseItemDto(playerItem.item!!)
available = isItemAvailable(playerItem.itemId)
Timber.d("Available: $available")
uiState.emit(
_uiState.emit(
UiState.Normal(
item!!,
runTime,

View file

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

View file

@ -1,7 +1,6 @@
package dev.jdtech.jellyfin.viewmodels
import android.app.Application
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
@ -17,6 +16,7 @@ import dev.jdtech.jellyfin.utils.syncPlaybackProgress
import dev.jdtech.jellyfin.utils.toView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
@ -28,7 +28,8 @@ class HomeViewModel @Inject internal constructor(
private val repository: JellyfinRepository,
private val downloadDatabase: DownloadDatabaseDao,
) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading)
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class 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()
}
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
init {
loadData(updateCapabilities = true)
}
@ -48,7 +45,7 @@ class HomeViewModel @Inject internal constructor(
private fun loadData(updateCapabilities: Boolean) {
viewModelScope.launch {
uiState.emit(UiState.Loading)
_uiState.emit(UiState.Loading)
try {
if (updateCapabilities) repository.postCapabilities()
@ -57,9 +54,9 @@ class HomeViewModel @Inject internal constructor(
withContext(Dispatchers.Default) {
syncPlaybackProgress(downloadDatabase, repository)
}
uiState.emit(UiState.Normal(updated))
_uiState.emit(UiState.Normal(updated))
} 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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
@ -21,7 +22,8 @@ class LibraryViewModel
constructor(
private val jellyfinRepository: JellyfinRepository
) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading)
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState {
data class Normal(val items: Flow<PagingData<BaseItemDto>>) : UiState()
@ -29,10 +31,6 @@ constructor(
data class Error(val error: Exception) : UiState()
}
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
fun loadItems(
parentId: UUID,
libraryType: String?,
@ -46,7 +44,7 @@ constructor(
else -> null
}
viewModelScope.launch {
uiState.emit(UiState.Loading)
_uiState.emit(UiState.Loading)
try {
val items = jellyfinRepository.getItemsPaging(
@ -56,9 +54,9 @@ constructor(
sortBy = sortBy,
sortOrder = sortOrder
)
uiState.emit(UiState.Normal(items))
_uiState.emit(UiState.Normal(items))
} 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.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.AuthenticateUserByName
@ -30,8 +32,10 @@ constructor(
) : ViewModel() {
private val resources: Resources = application.resources
private val uiState = MutableStateFlow<UiState>(UiState.Normal)
private val navigateToMain = MutableSharedFlow<Boolean>()
private val _uiState = MutableStateFlow<UiState>(UiState.Normal)
val uiState = _uiState.asStateFlow()
private val _navigateToMain = MutableSharedFlow<Boolean>()
val navigateToMain = _navigateToMain.asSharedFlow()
sealed class UiState {
object Normal : UiState()
@ -39,14 +43,6 @@ constructor(
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
*
@ -55,7 +51,7 @@ constructor(
*/
fun login(username: String, password: String) {
viewModelScope.launch {
uiState.emit(UiState.Loading)
_uiState.emit(UiState.Loading)
try {
val authenticationResult by jellyfinApi.userApi.authenticateUserByName(
@ -87,15 +83,15 @@ constructor(
userId = authenticationResult.user?.id
}
uiState.emit(UiState.Normal)
navigateToMain.emit(true)
_uiState.emit(UiState.Normal)
_navigateToMain.emit(true)
} catch (e: Exception) {
Timber.e(e)
val message =
if (e.cause?.message?.contains("401") == true) resources.getString(R.string.login_error_wrong_username_password) else resources.getString(
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.net.Uri
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
@ -13,6 +12,7 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.exception.ApiClientException
@ -31,7 +31,8 @@ constructor(
private val jellyfinRepository: JellyfinRepository,
private val downloadDatabase: DownloadDatabaseDao,
) : ViewModel() {
private val uiState = MutableStateFlow<UiState>(UiState.Loading)
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
sealed class UiState {
data class Normal(
@ -56,10 +57,6 @@ constructor(
data class Error(val error: Exception) : UiState()
}
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
var item: BaseItemDto? = null
private var actors: List<BaseItemPerson> = emptyList()
private var director: BaseItemPerson? = null
@ -83,7 +80,7 @@ constructor(
fun loadData(itemId: UUID, itemType: BaseItemKind) {
viewModelScope.launch {
uiState.emit(UiState.Loading)
_uiState.emit(UiState.Loading)
try {
val tempItem = jellyfinRepository.getItem(itemId)
item = tempItem
@ -102,7 +99,7 @@ constructor(
nextUp = getNextUp(itemId)
seasons = jellyfinRepository.getSeasons(itemId)
}
uiState.emit(
_uiState.emit(
UiState.Normal(
tempItem,
actors,
@ -122,7 +119,7 @@ constructor(
)
)
} catch (e: Exception) {
uiState.emit(UiState.Error(e))
_uiState.emit(UiState.Error(e))
}
}
}
@ -142,7 +139,7 @@ constructor(
played = tempItem.userData?.played ?: false
favorite = tempItem.userData?.isFavorite ?: false
available = isItemAvailable(tempItem.id)
uiState.emit(
_uiState.emit(
UiState.Normal(
tempItem,
actors,

View file

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

View file

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

View file

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

View file

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