Add error handling to all fragments

This commit is contained in:
jarnedemeulemeester 2021-07-29 12:20:49 +02:00
parent 11793a423b
commit fe7775329a
No known key found for this signature in database
GPG key ID: 60884A0C1EBA43E5
14 changed files with 600 additions and 414 deletions

View file

@ -8,7 +8,9 @@ import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.databinding.FragmentLibraryBinding
@ -36,6 +38,27 @@ class LibraryFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewModel = viewModel
val snackbar =
Snackbar.make(
binding.mainLayout,
getString(R.string.error_loading_data),
Snackbar.LENGTH_INDEFINITE
)
snackbar.setAction(getString(R.string.retry)) {
viewModel.loadItems(args.libraryId)
}
viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) {
snackbar.show()
}
})
viewModel.finishedLoading.observe(viewLifecycleOwner, {
binding.loadingIndicator.visibility = if (it) View.GONE else View.VISIBLE
})
binding.itemsRecyclerView.adapter =
ViewItemListAdapter(ViewItemListAdapter.OnClickListener { item ->
navigateToMediaInfoFragment(item)

View file

@ -7,7 +7,9 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.CollectionListAdapter
import dev.jdtech.jellyfin.databinding.FragmentMediaBinding
import dev.jdtech.jellyfin.viewmodels.MediaViewModel
@ -26,6 +28,16 @@ class MediaFragment : Fragment() {
): View {
binding = FragmentMediaBinding.inflate(inflater, container, false)
val snackbar =
Snackbar.make(
binding.mainLayout,
getString(R.string.error_loading_data),
Snackbar.LENGTH_INDEFINITE
)
snackbar.setAction(getString(R.string.retry)) {
viewModel.loadData()
}
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.viewsRecyclerView.adapter =
@ -34,8 +46,12 @@ class MediaFragment : Fragment() {
})
viewModel.finishedLoading.observe(viewLifecycleOwner, {
if (it) {
binding.loadingIndicator.visibility = View.GONE
binding.loadingIndicator.visibility = if (it) View.GONE else View.VISIBLE
})
viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) {
snackbar.show()
}
})

View file

@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.PersonListAdapter
@ -42,8 +43,24 @@ class MediaInfoFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val snackbar =
Snackbar.make(
binding.mainLayout,
getString(R.string.error_loading_data),
Snackbar.LENGTH_INDEFINITE
)
snackbar.setAction(getString(R.string.retry)) {
viewModel.loadData(args.itemId, args.itemType)
}
binding.viewModel = viewModel
viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) {
snackbar.show()
}
})
viewModel.item.observe(viewLifecycleOwner, { item ->
if (item.originalTitle != item.name) {
binding.originalTitle.visibility = View.VISIBLE

View file

@ -8,7 +8,9 @@ import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.EpisodeListAdapter
import dev.jdtech.jellyfin.databinding.FragmentSeasonBinding
import dev.jdtech.jellyfin.viewmodels.SeasonViewModel
@ -34,6 +36,27 @@ class SeasonFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewModel = viewModel
val snackbar =
Snackbar.make(
binding.mainLayout,
getString(R.string.error_loading_data),
Snackbar.LENGTH_INDEFINITE
)
snackbar.setAction(getString(R.string.retry)) {
viewModel.loadEpisodes(args.seriesId, args.seasonId)
}
viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) {
snackbar.show()
}
})
viewModel.finishedLoading.observe(viewLifecycleOwner, {
binding.loadingIndicator.visibility = if (it) View.GONE else View.VISIBLE
})
binding.episodesRecyclerView.adapter =
EpisodeListAdapter(EpisodeListAdapter.OnClickListener { episode ->
navigateToEpisodeBottomSheetFragment(episode)

View file

@ -10,6 +10,7 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.MediaSourceInfo
import timber.log.Timber
import java.text.DateFormat
import java.time.ZoneOffset
import java.util.*
@ -42,12 +43,17 @@ constructor(
fun loadEpisode(episodeId: UUID) {
viewModelScope.launch {
_item.value = jellyfinRepository.getItem(episodeId)
_runTime.value = "${_item.value?.runTimeTicks?.div(600000000)} min"
_dateString.value = getDateString(_item.value!!)
try {
val item = jellyfinRepository.getItem(episodeId)
_item.value = item
_runTime.value = "${item.runTimeTicks?.div(600000000)} min"
_dateString.value = getDateString(item)
_mediaSources.value = jellyfinRepository.getMediaSources(episodeId)
_played.value = _item.value?.userData?.played
_favorite.value = _item.value?.userData?.isFavorite
_played.value = item.userData?.played
_favorite.value = item.userData?.isFavorite
} catch (e: Exception) {
Timber.e(e)
}
}
}

View file

@ -85,12 +85,11 @@ constructor(
_views.value = items + views.map { HomeItem.ViewItem(it) }
_finishedLoading.value = true
} catch (e: Exception) {
Timber.e(e)
_finishedLoading.value = true
_error.value = true
}
_finishedLoading.value = true
}
}
}

View file

@ -5,6 +5,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@ -19,9 +20,19 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading
private val _error = MutableLiveData<Boolean>()
val error: LiveData<Boolean> = _error
fun loadItems(parentId: UUID) {
_error.value = false
_finishedLoading.value = false
viewModelScope.launch {
try {
_items.value = jellyfinRepository.getItems(parentId)
} catch (e: Exception) {
Timber.e(e)
_error.value = true
}
_finishedLoading.value = true
}
}

View file

@ -13,6 +13,7 @@ import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemPerson
import org.jellyfin.sdk.model.api.MediaSourceInfo
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@ -62,8 +63,13 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
private val _favorite = MutableLiveData<Boolean>()
val favorite: LiveData<Boolean> = _favorite
private val _error = MutableLiveData<Boolean>()
val error: LiveData<Boolean> = _error
fun loadData(itemId: UUID, itemType: String) {
_error.value = false
viewModelScope.launch {
try {
_item.value = jellyfinRepository.getItem(itemId)
_actors.value = getActors(_item.value!!)
_director.value = getDirector(_item.value!!)
@ -82,6 +88,10 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
if (itemType == "Movie") {
_mediaSources.value = jellyfinRepository.getMediaSources(itemId)
}
} catch (e: Exception) {
Timber.e(e)
_error.value = true
}
}
}

View file

@ -5,6 +5,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
@ -20,8 +21,18 @@ constructor(
private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading
private val _error = MutableLiveData<Boolean>()
val error: LiveData<Boolean> = _error
init {
loadData()
}
fun loadData() {
_finishedLoading.value = false
_error.value = false
viewModelScope.launch {
try {
val items = jellyfinRepository.getItems()
_collections.value =
items.filter {
@ -30,6 +41,10 @@ constructor(
it.collectionType != "playlists" &&
it.collectionType != "boxsets"
}
} catch (e: Exception) {
Timber.e(e)
_error.value = true
}
_finishedLoading.value = true
}
}

View file

@ -9,6 +9,7 @@ import dev.jdtech.jellyfin.adapters.EpisodeItem
import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.ItemFields
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@ -20,9 +21,23 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
private val _episodes = MutableLiveData<List<EpisodeItem>>()
val episodes: LiveData<List<EpisodeItem>> = _episodes
private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading
private val _error = MutableLiveData<Boolean>()
val error: LiveData<Boolean> = _error
fun loadEpisodes(seriesId: UUID, seasonId: UUID) {
_error.value = false
_finishedLoading.value = false
viewModelScope.launch {
try {
_episodes.value = getEpisodes(seriesId, seasonId)
} catch (e: Exception) {
Timber.e(e)
_error.value = true
}
_finishedLoading.value = true
}
}

View file

@ -2,17 +2,35 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="dev.jdtech.jellyfin.viewmodels.LibraryViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.LibraryFragment">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/loading_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/items_recycler_view"
android:layout_width="0dp"
@ -31,5 +49,7 @@
tools:listitem="@layout/base_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View file

@ -10,6 +10,11 @@
type="dev.jdtech.jellyfin.viewmodels.MediaViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -45,4 +50,5 @@
tools:listitem="@layout/collection_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View file

@ -12,6 +12,11 @@
type="dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -374,4 +379,6 @@
</LinearLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View file

@ -12,10 +12,27 @@
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/loading_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/episodes_recycler_view"
android:layout_width="0dp"
@ -30,5 +47,6 @@
tools:itemCount="4"
tools:listitem="@layout/episode_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>