Provide better error messages with stacktrace (#119)

* Provide better error messages with stacktrace

+ clean up
+ fix error details popup in MediaInfoFragment

* Simplify exception passing by sending complete exception to the dialog

* Use viewLifecycleOwner with repeatOnLifecycle
This commit is contained in:
Jarne Demeulemeester 2022-06-06 14:41:37 +02:00 committed by GitHub
parent 741083da40
commit 751ee75c3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 74 additions and 99 deletions

View file

@ -8,18 +8,21 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import java.lang.IllegalStateException import java.lang.IllegalStateException
class ErrorDialogFragment(private val errorMessage: String) : DialogFragment() { class ErrorDialogFragment(
private val error: Exception
) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
val builder = MaterialAlertDialogBuilder(it, R.style.ErrorDialogStyle) val builder = MaterialAlertDialogBuilder(it, R.style.ErrorDialogStyle)
builder builder
.setMessage(errorMessage) .setTitle(error.message ?: getString(R.string.unknown_error))
.setMessage(error.stackTraceToString())
.setPositiveButton(getString(R.string.close)) { _, _ -> .setPositiveButton(getString(R.string.close)) { _, _ ->
} }
.setNeutralButton(getString(R.string.share)) { _, _ -> .setNeutralButton(getString(R.string.share)) { _, _ ->
val sendIntent: Intent = Intent().apply { val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, errorMessage) putExtra(Intent.EXTRA_TEXT, "${error.message}\n ${error.stackTraceToString()}")
type = "text/plain" type = "text/plain"
} }

View file

@ -12,7 +12,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.* import dev.jdtech.jellyfin.adapters.*
import dev.jdtech.jellyfin.databinding.FragmentDownloadBinding import dev.jdtech.jellyfin.databinding.FragmentDownloadBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
@ -86,12 +85,11 @@ class DownloadFragment : Fragment() {
} }
private fun bindUiStateError(uiState: DownloadViewModel.UiState.Error) { private fun bindUiStateError(uiState: DownloadViewModel.UiState.Error) {
val error = uiState.message ?: resources.getString(R.string.unknown_error) errorDialog = ErrorDialogFragment(uiState.error)
errorDialog = ErrorDialogFragment(error)
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.downloadsRecyclerView.isVisible = false binding.downloadsRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(error) checkIfLoginRequired(uiState.error.message)
} }
private fun navigateToMediaInfoFragment(item: PlayerItem) { private fun navigateToMediaInfoFragment(item: PlayerItem) {

View file

@ -207,7 +207,7 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
private fun bindUiStateError(uiState: EpisodeBottomSheetViewModel.UiState.Error) { private fun bindUiStateError(uiState: EpisodeBottomSheetViewModel.UiState.Error) {
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.overview.text = uiState.message binding.overview.text = uiState.error.message
} }
private fun bindPlayerItems(items: PlayerViewModel.PlayerItems) { private fun bindPlayerItems(items: PlayerViewModel.PlayerItems) {
@ -222,7 +222,7 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
} }
private fun bindPlayerItemsError(error: PlayerViewModel.PlayerItemError) { private fun bindPlayerItemsError(error: PlayerViewModel.PlayerItemError) {
Timber.e(error.message) Timber.e(error.error.message)
binding.playerItemsError.isVisible = true binding.playerItemsError.isVisible = true
binding.playButton.setImageDrawable( binding.playButton.setImageDrawable(
@ -233,7 +233,7 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
) )
binding.progressCircular.visibility = View.INVISIBLE binding.progressCircular.visibility = View.INVISIBLE
binding.playerItemsErrorDetails.setOnClickListener { binding.playerItemsErrorDetails.setOnClickListener {
ErrorDialogFragment(error.message).show(parentFragmentManager, "errordialog") ErrorDialogFragment(error.error).show(parentFragmentManager, "errordialog")
} }
} }

View file

@ -12,7 +12,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
@ -87,12 +86,11 @@ class FavoriteFragment : Fragment() {
} }
private fun bindUiStateError(uiState: FavoriteViewModel.UiState.Error) { private fun bindUiStateError(uiState: FavoriteViewModel.UiState.Error) {
val error = uiState.message ?: resources.getString(R.string.unknown_error) errorDialog = ErrorDialogFragment(uiState.error)
errorDialog = ErrorDialogFragment(error)
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.favoritesRecyclerView.isVisible = false binding.favoritesRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(error) checkIfLoginRequired(uiState.error.message)
} }
private fun navigateToMediaInfoFragment(item: BaseItemDto) { private fun navigateToMediaInfoFragment(item: BaseItemDto) {

View file

@ -139,13 +139,12 @@ class HomeFragment : Fragment() {
} }
private fun bindUiStateError(uiState: HomeViewModel.UiState.Error) { private fun bindUiStateError(uiState: HomeViewModel.UiState.Error) {
val error = uiState.message ?: getString(R.string.unknown_error) errorDialog = ErrorDialogFragment(uiState.error)
errorDialog = ErrorDialogFragment(error)
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.refreshLayout.isRefreshing = false binding.refreshLayout.isRefreshing = false
binding.viewsRecyclerView.isVisible = false binding.viewsRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(error) checkIfLoginRequired(uiState.error.message)
} }
private fun navigateToLibraryFragment(view: dev.jdtech.jellyfin.models.View) { private fun navigateToLibraryFragment(view: dev.jdtech.jellyfin.models.View) {

View file

@ -132,12 +132,11 @@ class LibraryFragment : Fragment() {
} }
private fun bindUiStateError(uiState: LibraryViewModel.UiState.Error) { private fun bindUiStateError(uiState: LibraryViewModel.UiState.Error) {
val error = uiState.message ?: getString(R.string.unknown_error) errorDialog = ErrorDialogFragment(uiState.error)
errorDialog = ErrorDialogFragment(error)
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.itemsRecyclerView.isVisible = false binding.itemsRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(error) checkIfLoginRequired(uiState.error.message)
} }
private fun navigateToMediaInfoFragment(item: BaseItemDto) { private fun navigateToMediaInfoFragment(item: BaseItemDto) {

View file

@ -45,7 +45,7 @@ class LoginFragment : Fragment() {
} }
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when(uiState) { when(uiState) {

View file

@ -120,12 +120,11 @@ class MediaFragment : Fragment() {
} }
private fun bindUiStateError(uiState: MediaViewModel.UiState.Error) { private fun bindUiStateError(uiState: MediaViewModel.UiState.Error) {
val error = uiState.message ?: resources.getString(R.string.unknown_error) errorDialog = ErrorDialogFragment(uiState.error)
errorDialog = ErrorDialogFragment(error)
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.viewsRecyclerView.isVisible = false binding.viewsRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(error) checkIfLoginRequired(uiState.error.message)
} }

View file

@ -145,6 +145,10 @@ class MediaInfoFragment : Fragment() {
viewModel.loadData(args.itemId, args.itemType) viewModel.loadData(args.itemId, args.itemType)
} }
binding.errorLayout.errorDetailsButton.setOnClickListener {
errorDialog.show(parentFragmentManager, "errordialog")
}
binding.checkButton.setOnClickListener { binding.checkButton.setOnClickListener {
when (viewModel.played) { when (viewModel.played) {
true -> { true -> {
@ -286,11 +290,11 @@ class MediaInfoFragment : Fragment() {
} }
private fun bindUiStateError(uiState: MediaInfoViewModel.UiState.Error) { private fun bindUiStateError(uiState: MediaInfoViewModel.UiState.Error) {
val error = uiState.message ?: getString(R.string.unknown_error) errorDialog = ErrorDialogFragment(uiState.error)
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.mediaInfoScrollview.isVisible = false binding.mediaInfoScrollview.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(error) checkIfLoginRequired(uiState.error.message)
} }
private fun bindPlayerItems(items: PlayerViewModel.PlayerItems) { private fun bindPlayerItems(items: PlayerViewModel.PlayerItems) {
@ -305,7 +309,7 @@ class MediaInfoFragment : Fragment() {
} }
private fun bindPlayerItemsError(error: PlayerViewModel.PlayerItemError) { private fun bindPlayerItemsError(error: PlayerViewModel.PlayerItemError) {
Timber.e(error.message) Timber.e(error.error.message)
binding.playerItemsError.visibility = View.VISIBLE binding.playerItemsError.visibility = View.VISIBLE
binding.playButton.setImageDrawable( binding.playButton.setImageDrawable(
ContextCompat.getDrawable( ContextCompat.getDrawable(
@ -315,7 +319,7 @@ class MediaInfoFragment : Fragment() {
) )
binding.progressCircular.visibility = View.INVISIBLE binding.progressCircular.visibility = View.INVISIBLE
binding.playerItemsErrorDetails.setOnClickListener { binding.playerItemsErrorDetails.setOnClickListener {
ErrorDialogFragment(error.message).show(parentFragmentManager, "errordialog") ErrorDialogFragment(error.error).show(parentFragmentManager, "errordialog")
} }
} }

View file

@ -104,12 +104,11 @@ internal class PersonDetailFragment : Fragment() {
} }
private fun bindUiStateError(uiState: PersonDetailViewModel.UiState.Error) { private fun bindUiStateError(uiState: PersonDetailViewModel.UiState.Error) {
val error = uiState.message ?: resources.getString(R.string.unknown_error) errorDialog = ErrorDialogFragment(uiState.error)
errorDialog = ErrorDialogFragment(error)
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.fragmentContent.isVisible = false binding.fragmentContent.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(error) checkIfLoginRequired(uiState.error.message)
} }
private fun adapter() = ViewItemListAdapter( private fun adapter() = ViewItemListAdapter(

View file

@ -13,7 +13,6 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
@ -91,12 +90,11 @@ class SearchResultFragment : Fragment() {
} }
private fun bindUiStateError(uiState: SearchResultViewModel.UiState.Error) { private fun bindUiStateError(uiState: SearchResultViewModel.UiState.Error) {
val error = uiState.message ?: getString(R.string.unknown_error) errorDialog = ErrorDialogFragment(uiState.error)
errorDialog = ErrorDialogFragment(error)
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.searchResultsRecyclerView.isVisible = false binding.searchResultsRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(error) checkIfLoginRequired(uiState.error.message)
} }
private fun navigateToMediaInfoFragment(item: BaseItemDto) { private fun navigateToMediaInfoFragment(item: BaseItemDto) {

View file

@ -13,7 +13,6 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.EpisodeListAdapter import dev.jdtech.jellyfin.adapters.EpisodeListAdapter
import dev.jdtech.jellyfin.databinding.FragmentSeasonBinding import dev.jdtech.jellyfin.databinding.FragmentSeasonBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
@ -88,12 +87,11 @@ class SeasonFragment : Fragment() {
} }
private fun bindUiStateError(uiState: SeasonViewModel.UiState.Error) { private fun bindUiStateError(uiState: SeasonViewModel.UiState.Error) {
val error = uiState.message ?: getString(R.string.unknown_error) errorDialog = ErrorDialogFragment(uiState.error)
errorDialog = ErrorDialogFragment(error)
binding.loadingIndicator.isVisible = false binding.loadingIndicator.isVisible = false
binding.episodesRecyclerView.isVisible = false binding.episodesRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(error) checkIfLoginRequired(uiState.error.message)
} }
private fun navigateToEpisodeBottomSheetFragment(episode: BaseItemDto) { private fun navigateToEpisodeBottomSheetFragment(episode: BaseItemDto) {

View file

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

View file

@ -248,7 +248,7 @@ internal class MediaDetailFragment : Fragment() {
} }
private fun bindPlayerItemsError(error: PlayerItemError) { private fun bindPlayerItemsError(error: PlayerItemError) {
Timber.e(error.message) Timber.e(error.error.message)
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
binding.playButton.setImageDrawable( binding.playButton.setImageDrawable(

View file

@ -40,7 +40,7 @@ class TvLoginFragment : Fragment() {
} }
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> viewModel.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
Timber.d("$uiState") Timber.d("$uiState")
when(uiState) { when(uiState) {

View file

@ -32,10 +32,12 @@ fun BaseItemDto.contentType() = when (type) {
else -> ContentType.UNKNOWN else -> ContentType.UNKNOWN
} }
fun Fragment.checkIfLoginRequired(error: String) { fun Fragment.checkIfLoginRequired(error: String?) {
if (error.contains("401")) { if (error != null) {
Timber.d("Login required!") if (error.contains("401")) {
findNavController().navigate(AppNavigationDirections.actionGlobalLoginFragment()) Timber.d("Login required!")
findNavController().navigate(AppNavigationDirections.actionGlobalLoginFragment())
}
} }
} }

View file

@ -95,8 +95,7 @@ constructor(
} }
okServers.isNotEmpty() -> { okServers.isNotEmpty() -> {
val okServer = okServers.first() val okServer = okServers.first()
val issuesString = createIssuesString(okServer) throw Exception(createIssuesString(okServer))
throw Exception(issuesString)
} }
else -> { else -> {
throw Exception(resources.getString(R.string.add_server_error_not_found)) throw Exception(resources.getString(R.string.add_server_error_not_found))

View file

@ -8,7 +8,6 @@ import dev.jdtech.jellyfin.models.DownloadSection
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.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -24,7 +23,7 @@ constructor(
sealed class UiState { sealed class UiState {
data class Normal(val downloadSections: List<DownloadSection>) : UiState() data class Normal(val downloadSections: List<DownloadSection>) : UiState()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -40,10 +39,6 @@ constructor(
uiState.emit(UiState.Loading) uiState.emit(UiState.Loading)
try { try {
val items = loadDownloadedEpisodes(downloadDatabase) val items = loadDownloadedEpisodes(downloadDatabase)
if (items.isEmpty()) {
uiState.emit(UiState.Normal(emptyList()))
//return@launch
}
val downloadSections = mutableListOf<DownloadSection>() val downloadSections = mutableListOf<DownloadSection>()
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
DownloadSection( DownloadSection(
@ -65,7 +60,7 @@ constructor(
} }
uiState.emit(UiState.Normal(downloadSections)) uiState.emit(UiState.Normal(downloadSections))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e.message)) uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -45,7 +45,7 @@ constructor(
) : UiState() ) : UiState()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -91,7 +91,7 @@ constructor(
) )
) )
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e.message)) uiState.emit(UiState.Error(e))
} }
} }
} }
@ -170,7 +170,12 @@ constructor(
val metadata = baseItemDtoToDownloadMetadata(episode) val metadata = baseItemDtoToDownloadMetadata(episode)
downloadRequestItem = DownloadRequestItem(uri, itemId, metadata) downloadRequestItem = DownloadRequestItem(uri, itemId, metadata)
downloadEpisode = true downloadEpisode = true
requestDownload(downloadDatabase, Uri.parse(downloadRequestItem.uri), downloadRequestItem, application) requestDownload(
downloadDatabase,
Uri.parse(downloadRequestItem.uri),
downloadRequestItem,
application
)
} }
} }

View file

@ -8,7 +8,6 @@ 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.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
@ -25,7 +24,7 @@ constructor(
sealed class UiState { sealed class UiState {
data class Normal(val favoriteSections: List<FavoriteSection>) : UiState() data class Normal(val favoriteSections: List<FavoriteSection>) : UiState()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -78,7 +77,7 @@ constructor(
uiState.emit(UiState.Normal(favoriteSections)) uiState.emit(UiState.Normal(favoriteSections))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e.message)) uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -17,7 +17,6 @@ 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.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
@ -34,7 +33,7 @@ class HomeViewModel @Inject internal constructor(
sealed class UiState { sealed class UiState {
data class Normal(val homeItems: List<HomeItem>) : UiState() data class Normal(val homeItems: List<HomeItem>) : UiState()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -60,7 +59,7 @@ class HomeViewModel @Inject internal constructor(
} }
uiState.emit(UiState.Normal(updated)) uiState.emit(UiState.Normal(updated))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e.message)) uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -5,7 +5,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.SortBy import dev.jdtech.jellyfin.utils.SortBy
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
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.SortOrder import org.jellyfin.sdk.model.api.SortOrder
@ -24,7 +23,7 @@ constructor(
sealed class UiState { sealed class UiState {
data class Normal(val items: List<BaseItemDto>) : UiState() data class Normal(val items: List<BaseItemDto>) : UiState()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -55,7 +54,7 @@ constructor(
) )
uiState.emit(UiState.Normal(items)) uiState.emit(UiState.Normal(items))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e.message)) uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -12,7 +12,6 @@ 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.collect
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

View file

@ -19,10 +19,6 @@ class MainViewModel
constructor( constructor(
private val database: ServerDatabaseDao, private val database: ServerDatabaseDao,
) : ViewModel() { ) : ViewModel() {
private val _doneLoading = MutableLiveData<Boolean>()
val doneLoading: LiveData<Boolean> = _doneLoading
private val _navigateToAddServer = MutableLiveData<Boolean>() private val _navigateToAddServer = MutableLiveData<Boolean>()
val navigateToAddServer: LiveData<Boolean> = _navigateToAddServer val navigateToAddServer: LiveData<Boolean> = _navigateToAddServer
@ -36,9 +32,7 @@ constructor(
if (servers.isEmpty()) { if (servers.isEmpty()) {
_navigateToAddServer.value = true _navigateToAddServer.value = true
} }
_doneLoading.value = true
} }
_doneLoading.value = true
} }
fun doneNavigateToAddServer() { fun doneNavigateToAddServer() {

View file

@ -52,7 +52,7 @@ constructor(
) : UiState() ) : UiState()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -121,9 +121,7 @@ constructor(
) )
) )
} catch (e: Exception) { } catch (e: Exception) {
Timber.d(e) uiState.emit(UiState.Error(e))
Timber.d(itemId.toString())
uiState.emit(UiState.Error(e.message))
} }
} }
} }

View file

@ -5,7 +5,6 @@ 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.collect
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
@ -22,7 +21,7 @@ constructor(
sealed class UiState { sealed class UiState {
data class Normal(val collections: List<BaseItemDto>) : UiState() data class Normal(val collections: List<BaseItemDto>) : UiState()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -43,7 +42,7 @@ constructor(
uiState.emit(UiState.Normal(collections)) uiState.emit(UiState.Normal(collections))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit( uiState.emit(
UiState.Error(e.message) UiState.Error(e)
) )
} }
} }

View file

@ -9,10 +9,8 @@ import dev.jdtech.jellyfin.models.ContentType.TVSHOW
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.contentType import dev.jdtech.jellyfin.utils.contentType
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import java.lang.Exception
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@ -26,7 +24,7 @@ internal class PersonDetailViewModel @Inject internal constructor(
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()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -58,7 +56,7 @@ internal class PersonDetailViewModel @Inject internal constructor(
uiState.emit(UiState.Normal(data, starredIn)) uiState.emit(UiState.Normal(data, starredIn))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e.message)) uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -59,7 +59,7 @@ class PlayerViewModel @Inject internal constructor(
createItems(item, playbackPosition, mediaSourceIndex).let(::PlayerItems) createItems(item, playbackPosition, mediaSourceIndex).let(::PlayerItems)
} catch (e: Exception) { } catch (e: Exception) {
Timber.d(e) Timber.d(e)
PlayerItemError(e.toString()) PlayerItemError(e)
} }
playerItems.tryEmit(items) playerItems.tryEmit(items)
@ -195,6 +195,6 @@ class PlayerViewModel @Inject internal constructor(
sealed class PlayerItemState sealed class PlayerItemState
data class PlayerItemError(val message: String) : PlayerItemState() data class PlayerItemError(val error: Exception) : PlayerItemState()
data class PlayerItems(val items: List<PlayerItem>) : PlayerItemState() data class PlayerItems(val items: List<PlayerItem>) : PlayerItemState()
} }

View file

@ -8,7 +8,6 @@ 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.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
@ -25,7 +24,7 @@ constructor(
sealed class UiState { sealed class UiState {
data class Normal(val sections: List<FavoriteSection>) : UiState() data class Normal(val sections: List<FavoriteSection>) : UiState()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -74,7 +73,7 @@ constructor(
uiState.emit(UiState.Normal(sections)) uiState.emit(UiState.Normal(sections))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e.message)) uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -5,7 +5,6 @@ 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.collect
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.*
@ -22,7 +21,7 @@ constructor(
sealed class UiState { sealed class UiState {
data class Normal(val episodes: List<EpisodeItem>) : UiState() data class Normal(val episodes: List<EpisodeItem>) : UiState()
object Loading : UiState() object Loading : UiState()
data class Error(val message: String?) : UiState() data class Error(val error: Exception) : UiState()
} }
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
@ -36,7 +35,7 @@ constructor(
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.message)) uiState.emit(UiState.Error(e))
} }
} }
} }

View file

@ -11,9 +11,7 @@ import dev.jdtech.jellyfin.database.ServerDatabaseDao
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -43,10 +41,8 @@ constructor(
* @param server The server * @param server The server
*/ */
fun deleteServer(server: Server) { fun deleteServer(server: Server) {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
withContext(Dispatchers.IO) { database.delete(server.id)
database.delete(server.id)
}
} }
} }