From 6ab22428d8ced2d4027aef7422edab74d0d1c4ee Mon Sep 17 00:00:00 2001 From: jarnedemeulemeester Date: Sat, 21 Aug 2021 15:25:36 +0200 Subject: [PATCH 01/12] Switch to AboutLibraries for licenses screen --- app/build.gradle | 7 ++++--- app/src/main/AndroidManifest.xml | 5 ----- .../main/java/dev/jdtech/jellyfin/MainActivity.kt | 3 ++- .../jdtech/jellyfin/fragments/SettingsFragment.kt | 5 ++--- .../dev/jdtech/jellyfin/utils/VersionPreference.kt | 14 -------------- app/src/main/res/navigation/main_navigation.xml | 12 +++++++++--- app/src/main/res/values/about_libraries.xml | 8 ++++++++ app/src/main/res/values/strings.xml | 5 ++--- app/src/main/res/xml/fragment_settings.xml | 6 ++---- build.gradle | 7 +++++-- 10 files changed, 34 insertions(+), 38 deletions(-) delete mode 100644 app/src/main/java/dev/jdtech/jellyfin/utils/VersionPreference.kt create mode 100644 app/src/main/res/values/about_libraries.xml diff --git a/app/build.gradle b/app/build.gradle index fa04e160..821a4f60 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { id 'kotlin-kapt' id 'androidx.navigation.safeargs.kotlin' id 'dagger.hilt.android.plugin' - id 'com.google.android.gms.oss-licenses-plugin' + id "com.mikepenz.aboutlibraries.plugin" } android { @@ -99,8 +99,9 @@ dependencies { def timber_version = "5.0.0" implementation "com.jakewharton.timber:timber:$timber_version" - def oss_licenses_version = "17.0.0" - implementation "com.google.android.gms:play-services-oss-licenses:$oss_licenses_version" + def about_libraries_version = "8.9.1" + implementation "com.mikepenz:aboutlibraries-core:$about_libraries_version" + implementation "com.mikepenz:aboutlibraries:$about_libraries_version" // Testing testImplementation 'junit:junit:4.13.2' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 75b71ba4..8afba6f1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,11 +27,6 @@ - - - diff --git a/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt b/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt index 1d74d115..ca3969bd 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt @@ -51,9 +51,10 @@ class MainActivity : AppCompatActivity() { navController.addOnDestinationChangedListener { _, destination, _ -> binding.navView.visibility = when (destination.id) { - R.id.settingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment -> View.GONE + R.id.settingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, R.id.about_libraries_dest -> View.GONE else -> View.VISIBLE } + if (destination.id == R.id.about_libraries_dest) binding.mainToolbar.title = getString(R.string.app_info) } viewModel.navigateToAddServer.observe(this, { diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/SettingsFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/SettingsFragment.kt index 9fd43144..79be8b10 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/SettingsFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/SettingsFragment.kt @@ -8,7 +8,6 @@ import androidx.navigation.fragment.findNavController import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import com.google.android.gms.oss.licenses.OssLicensesMenuActivity import dev.jdtech.jellyfin.R class SettingsFragment : PreferenceFragmentCompat() { @@ -38,8 +37,8 @@ class SettingsFragment : PreferenceFragmentCompat() { true } - findPreference("ossLicenses")?.setOnPreferenceClickListener { - startActivity(Intent(requireContext(), OssLicensesMenuActivity::class.java)) + findPreference("appInfo")?.setOnPreferenceClickListener { + findNavController().navigate(SettingsFragmentDirections.actionSettingsFragmentToAboutLibraries()) true } } diff --git a/app/src/main/java/dev/jdtech/jellyfin/utils/VersionPreference.kt b/app/src/main/java/dev/jdtech/jellyfin/utils/VersionPreference.kt deleted file mode 100644 index 7ce9e9fc..00000000 --- a/app/src/main/java/dev/jdtech/jellyfin/utils/VersionPreference.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.jdtech.jellyfin.utils - -import android.content.Context -import android.util.AttributeSet -import androidx.preference.Preference - -class VersionPreference(context: Context, attrs: AttributeSet): Preference(context, attrs) { - init { - val packageManager = context.packageManager - val packageInfo = packageManager.getPackageInfo(context.packageName, 0) - val versionName = packageInfo.versionName - summary = versionName - } -} \ No newline at end of file diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 3f64d1d2..2807ccd7 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -56,10 +56,13 @@ + android:label="@string/title_settings"> + + tools:layout="@layout/fragment_search_result"> @@ -222,7 +225,7 @@ android:id="@+id/initializingFragment" android:name="dev.jdtech.jellyfin.fragments.InitializingFragment" android:label="@string/initializing" - tools:layout="@layout/fragment_initializing" > + tools:layout="@layout/fragment_initializing"> + + + \ No newline at end of file diff --git a/app/src/main/res/values/about_libraries.xml b/app/src/main/res/values/about_libraries.xml new file mode 100644 index 00000000..c34ab3d8 --- /dev/null +++ b/app/src/main/res/values/about_libraries.xml @@ -0,0 +1,8 @@ + + + true + true + @string/app_name + @string/app_description + true + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1c8aca1..d6fb7adc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ Findroid + Third-party native Jellyfin app Jellyfin banner Add server Login @@ -14,7 +15,6 @@ Are you sure you want to remove the server %1$s Remove Cancel - MainActivity Home My media Favorites @@ -50,8 +50,7 @@ Appearance Theme Error preparing player items - Version - Open source licenses About Privacy policy + App info \ No newline at end of file diff --git a/app/src/main/res/xml/fragment_settings.xml b/app/src/main/res/xml/fragment_settings.xml index dfa140bc..669c3e45 100644 --- a/app/src/main/res/xml/fragment_settings.xml +++ b/app/src/main/res/xml/fragment_settings.xml @@ -46,10 +46,8 @@ app:title="@string/privacy_policy" /> - - + app:key="appInfo" + app:title="@string/app_info" /> diff --git a/build.gradle b/build.gradle index 29d39b5e..3c49a726 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,9 @@ buildscript { repositories { google() mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } } dependencies { classpath 'com.android.tools.build:gradle:7.0.0' @@ -17,8 +20,8 @@ buildscript { def hilt_version = "2.38.1" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - def oss_licenses_version = "0.10.4" - classpath "com.google.android.gms:oss-licenses-plugin:$oss_licenses_version" + def about_libraries_version = "8.9.1" + classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries_version" } } From eaa49596af60bf78bb8ae8e8ef9ac2702da522ab Mon Sep 17 00:00:00 2001 From: jarnedemeulemeester Date: Sat, 21 Aug 2021 15:41:45 +0200 Subject: [PATCH 02/12] Update gradle plugin (7.0.1) & timber library (5.0.1) --- app/build.gradle | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 821a4f60..8a623112 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -96,7 +96,7 @@ dependencies { implementation files('libs/extension-ffmpeg-release.aar') // Timber - def timber_version = "5.0.0" + def timber_version = "5.0.1" implementation "com.jakewharton.timber:timber:$timber_version" def about_libraries_version = "8.9.1" diff --git a/build.gradle b/build.gradle index 3c49a726..5f6ed4c6 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:7.0.0' + classpath 'com.android.tools.build:gradle:7.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong From 53d6fdb340b6efd6dd60bcebcc608078baceba5a Mon Sep 17 00:00:00 2001 From: jarnedemeulemeester Date: Sat, 21 Aug 2021 16:10:18 +0200 Subject: [PATCH 03/12] Make library images smaller --- app/src/main/res/layout/collection_item.xml | 3 +-- app/src/main/res/values-sw600dp/dimens.xml | 2 +- app/src/main/res/values-sw720dp/dimens.xml | 1 - app/src/main/res/values/dimens.xml | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/layout/collection_item.xml b/app/src/main/res/layout/collection_item.xml index 6b78f8f1..33784dd5 100644 --- a/app/src/main/res/layout/collection_item.xml +++ b/app/src/main/res/layout/collection_item.xml @@ -14,7 +14,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="12dp" - android:layout_marginBottom="32dp" + android:layout_marginBottom="12dp" android:foreground="?android:attr/selectableItemBackground" android:orientation="vertical"> @@ -37,7 +37,6 @@ android:layout_marginTop="4dp" android:text="@{collection.name}" android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1" - android:textSize="18sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/collection_image" diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index c75a4667..bb31a398 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -2,7 +2,7 @@ 400dp 6 - 2 + 3 4 450 350dp diff --git a/app/src/main/res/values-sw720dp/dimens.xml b/app/src/main/res/values-sw720dp/dimens.xml index 1d98770c..43e88e2a 100644 --- a/app/src/main/res/values-sw720dp/dimens.xml +++ b/app/src/main/res/values-sw720dp/dimens.xml @@ -1,6 +1,5 @@ 8 - 3 6 \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 6ad4fb71..1f9090ec 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -5,7 +5,7 @@ @dimen/match_parent 150dp 3 - 1 + 2 2 250 @dimen/match_parent From 2094ec987027cb7aec70235e9192659b2d65018c Mon Sep 17 00:00:00 2001 From: jarnedemeulemeester Date: Sat, 21 Aug 2021 16:43:50 +0200 Subject: [PATCH 04/12] Add gradient to episode description --- app/src/main/res/layout/episode_item.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/res/layout/episode_item.xml b/app/src/main/res/layout/episode_item.xml index 7a94f37a..428b9fac 100644 --- a/app/src/main/res/layout/episode_item.xml +++ b/app/src/main/res/layout/episode_item.xml @@ -82,5 +82,13 @@ app:layout_constraintStart_toStartOf="@id/episode_title" app:layout_constraintTop_toBottomOf="@id/episode_title" tools:text="After one hundred years of peace, humanity is suddenly reminded of the terror of being at the Titans' mercy. After one hundred years of peace, humanity is suddenly reminded of the terror of being at the Titans' mercy." /> + + \ No newline at end of file From 306c3b02c263c1ff8c524517f34b796d53651fef Mon Sep 17 00:00:00 2001 From: jarnedemeulemeester Date: Sat, 21 Aug 2021 17:52:39 +0200 Subject: [PATCH 05/12] Improve server discovery (no longer need to type http:// or https:// or ports) --- .../jellyfin/viewmodels/AddServerViewModel.kt | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt index 829d6dc5..3f86da36 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt @@ -9,8 +9,11 @@ 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.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.jellyfin.sdk.discovery.RecommendedServerInfo +import org.jellyfin.sdk.discovery.RecommendedServerInfoScore import timber.log.Timber import javax.inject.Inject @@ -34,19 +37,32 @@ constructor( * - Connect to server and check if it is a Jellyfin server * - Check if server is not already in Database */ - fun checkServer(baseUrl: String) { + fun checkServer(inputValue: String) { _error.value = null viewModelScope.launch { - jellyfinApi.apply { - api.baseUrl = baseUrl - api.accessToken = null - } try { - val publicSystemInfo by jellyfinApi.systemApi.getPublicSystemInfo() - Timber.d("Remote server: ${publicSystemInfo.id}") + val candidates = jellyfinApi.jellyfin.discovery.getAddressCandidates(inputValue) + val recommended = jellyfinApi.jellyfin.discovery.getRecommendedServers( + candidates, + RecommendedServerInfoScore.GOOD + ) + val recommendedServer: RecommendedServerInfo - if (serverAlreadyInDatabase(publicSystemInfo.id)) { + try { + recommendedServer = recommended.first() + } catch (e: NoSuchElementException) { + throw Exception("Server not found") + } + + jellyfinApi.apply { + api.baseUrl = recommendedServer.address + api.accessToken = null + } + + Timber.d("Remote server: ${recommendedServer.systemInfo?.id}") + + if (serverAlreadyInDatabase(recommendedServer.systemInfo?.id)) { _error.value = "Server already added" _navigateToLogin.value = false } else { From f2ce0308564e2f528dc4fd3c896595b68dfd4c56 Mon Sep 17 00:00:00 2001 From: jarnedemeulemeester Date: Sun, 22 Aug 2021 11:47:25 +0200 Subject: [PATCH 06/12] View detailed error message for playerItemsError --- .../jellyfin/dialogs/ErrorDialogFragment.kt | 33 +++++++++++++ .../fragments/EpisodeBottomSheetFragment.kt | 20 ++++---- .../jellyfin/fragments/MediaInfoFragment.kt | 46 ++++++++++++++----- .../viewmodels/EpisodeBottomSheetViewModel.kt | 8 ++-- .../jellyfin/viewmodels/MediaInfoViewModel.kt | 8 ++-- .../main/res/layout/episode_bottom_sheet.xml | 36 +++++++++++---- .../main/res/layout/fragment_media_info.xml | 27 +++++++++-- app/src/main/res/values/strings.xml | 3 +- app/src/main/res/values/styles.xml | 10 ++++ 9 files changed, 148 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/dev/jdtech/jellyfin/dialogs/ErrorDialogFragment.kt diff --git a/app/src/main/java/dev/jdtech/jellyfin/dialogs/ErrorDialogFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/dialogs/ErrorDialogFragment.kt new file mode 100644 index 00000000..6f3ead62 --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/dialogs/ErrorDialogFragment.kt @@ -0,0 +1,33 @@ +package dev.jdtech.jellyfin.dialogs + +import android.app.Dialog +import android.content.Intent +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dev.jdtech.jellyfin.R +import java.lang.IllegalStateException + +class ErrorDialogFragment(private val errorMessage: String) : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return activity?.let { + val builder = MaterialAlertDialogBuilder(it, R.style.ErrorDialogStyle) + builder + .setMessage(errorMessage) + .setPositiveButton("close") { _, _ -> + } + .setNeutralButton("share") { _, _ -> + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, errorMessage) + type = "text/plain" + } + + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) + + } + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt index eb4a83a7..a81beae0 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt @@ -13,6 +13,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import dagger.hilt.android.AndroidEntryPoint import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.databinding.EpisodeBottomSheetBinding +import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel @@ -98,17 +99,20 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() { } }) - viewModel.playerItemsError.observe(viewLifecycleOwner, { - when (it) { - true -> { - binding.playerItemsError.visibility = View.VISIBLE - binding.playButton.setImageDrawable(ContextCompat.getDrawable(requireActivity(), R.drawable.ic_play)) - binding.progressCircular.visibility = View.INVISIBLE - } - false -> binding.playerItemsError.visibility = View.GONE + viewModel.playerItemsError.observe(viewLifecycleOwner, { errorMessage -> + if (errorMessage != null) { + binding.playerItemsError.visibility = View.VISIBLE + binding.playButton.setImageDrawable(ContextCompat.getDrawable(requireActivity(), R.drawable.ic_play)) + binding.progressCircular.visibility = View.INVISIBLE + } else { + binding.playerItemsError.visibility = View.GONE } }) + binding.playerItemsErrorDetails.setOnClickListener { + ErrorDialogFragment(viewModel.playerItemsError.value ?: "Unknown error").show(parentFragmentManager, "errordialog") + } + viewModel.loadEpisode(args.episodeId) return binding.root diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt index 6e6ac673..b9d7bb13 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt @@ -17,6 +17,7 @@ import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.adapters.PersonListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.databinding.FragmentMediaInfoBinding +import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.dialogs.VideoVersionDialogFragment import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel @@ -91,7 +92,12 @@ class MediaInfoFragment : Fragment() { viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000) ) viewModel.doneNavigatingToPlayer() - binding.playButton.setImageDrawable(ContextCompat.getDrawable(requireActivity(), R.drawable.ic_play)) + binding.playButton.setImageDrawable( + ContextCompat.getDrawable( + requireActivity(), + R.drawable.ic_play + ) + ) binding.progressCircular.visibility = View.INVISIBLE } }) @@ -114,17 +120,25 @@ class MediaInfoFragment : Fragment() { binding.favoriteButton.setImageResource(drawable) }) - viewModel.playerItemsError.observe(viewLifecycleOwner, { - when (it) { - true -> { - binding.playerItemsError.visibility = View.VISIBLE - binding.playButton.setImageDrawable(ContextCompat.getDrawable(requireActivity(), R.drawable.ic_play)) - binding.progressCircular.visibility = View.INVISIBLE - } - false -> binding.playerItemsError.visibility = View.GONE + viewModel.playerItemsError.observe(viewLifecycleOwner, { errorMessage -> + if (errorMessage != null) { + binding.playerItemsError.visibility = View.VISIBLE + binding.playButton.setImageDrawable( + ContextCompat.getDrawable( + requireActivity(), + R.drawable.ic_play + ) + ) + binding.progressCircular.visibility = View.INVISIBLE + } else { + binding.playerItemsError.visibility = View.GONE } }) + binding.playerItemsErrorDetails.setOnClickListener { + ErrorDialogFragment(viewModel.playerItemsError.value ?: "Unknown error").show(parentFragmentManager, "errordialog") + } + binding.trailerButton.setOnClickListener { val intent = Intent( Intent.ACTION_VIEW, @@ -155,10 +169,20 @@ class MediaInfoFragment : Fragment() { ) } else { navigateToPlayerActivity( - arrayOf(PlayerItem(args.itemId, viewModel.mediaSources.value!![0].id!!)), + arrayOf( + PlayerItem( + args.itemId, + viewModel.mediaSources.value!![0].id!! + ) + ), viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000), ) - binding.playButton.setImageDrawable(ContextCompat.getDrawable(requireActivity(), R.drawable.ic_play)) + binding.playButton.setImageDrawable( + ContextCompat.getDrawable( + requireActivity(), + R.drawable.ic_play + ) + ) binding.progressCircular.visibility = View.INVISIBLE } } diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt index 51422e83..baa784df 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt @@ -43,8 +43,8 @@ constructor( var playerItems: MutableList = mutableListOf() - private val _playerItemsError = MutableLiveData() - val playerItemsError: LiveData = _playerItemsError + private val _playerItemsError = MutableLiveData() + val playerItemsError: LiveData = _playerItemsError fun loadEpisode(episodeId: UUID) { viewModelScope.launch { @@ -62,13 +62,13 @@ constructor( } fun preparePlayer() { - _playerItemsError.value = false + _playerItemsError.value = null viewModelScope.launch { try { createPlayerItems(_item.value!!) _navigateToPlayer.value = true } catch (e: Exception) { - _playerItemsError.value = true + _playerItemsError.value = e.message } } } diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt index 6df01cbb..0e77c612 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt @@ -69,8 +69,8 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { var playerItems: MutableList = mutableListOf() - private val _playerItemsError = MutableLiveData() - val playerItemsError: LiveData = _playerItemsError + private val _playerItemsError = MutableLiveData() + val playerItemsError: LiveData = _playerItemsError fun loadData(itemId: UUID, itemType: String) { _error.value = false @@ -184,13 +184,13 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { } fun preparePlayer() { - _playerItemsError.value = false + _playerItemsError.value = null viewModelScope.launch { try { createPlayerItems(_item.value!!) _navigateToPlayer.value = playerItems.toTypedArray() } catch (e: Exception) { - _playerItemsError.value = true + _playerItemsError.value = e.message } } } diff --git a/app/src/main/res/layout/episode_bottom_sheet.xml b/app/src/main/res/layout/episode_bottom_sheet.xml index 99044167..501acc53 100644 --- a/app/src/main/res/layout/episode_bottom_sheet.xml +++ b/app/src/main/res/layout/episode_bottom_sheet.xml @@ -136,10 +136,10 @@ android:id="@+id/progress_circular" android:layout_width="48dp" android:layout_height="48dp" - android:elevation="8dp" - android:padding="8dp" android:layout_centerHorizontal="true" + android:elevation="8dp" android:indeterminateTint="@color/white" + android:padding="8dp" android:visibility="invisible" /> @@ -163,21 +163,37 @@ android:src="@drawable/ic_heart" /> - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/buttons" + tools:visibility="visible"> + + + + + - + tools:visibility="visible"> + + + + + + Manage servers Appearance Theme - Error preparing player items + Error preparing player items. + View details About Privacy policy App info diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 4c7729be..e88792ac 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -10,4 +10,14 @@ rounded 10dp + + + + + + \ No newline at end of file From a067b6b13d6df748e26cba618456f40715200565 Mon Sep 17 00:00:00 2001 From: jarnedemeulemeester Date: Sun, 22 Aug 2021 18:47:25 +0200 Subject: [PATCH 07/12] New error panel with details dialog --- app/build.gradle | 1 + .../fragments/EpisodeBottomSheetFragment.kt | 2 +- .../jellyfin/fragments/FavoriteFragment.kt | 28 ++++--- .../jdtech/jellyfin/fragments/HomeFragment.kt | 24 ++++-- .../jellyfin/fragments/LibraryFragment.kt | 28 ++++--- .../jellyfin/fragments/MediaFragment.kt | 28 ++++--- .../jellyfin/fragments/MediaInfoFragment.kt | 29 +++---- .../fragments/SearchResultFragment.kt | 28 ++++--- .../jellyfin/fragments/SeasonFragment.kt | 28 ++++--- .../fragments/ServerSelectFragment.kt | 1 - .../jellyfin/viewmodels/FavoriteViewModel.kt | 8 +- .../jellyfin/viewmodels/HomeViewModel.kt | 8 +- .../jellyfin/viewmodels/LibraryViewModel.kt | 8 +- .../jellyfin/viewmodels/MainViewModel.kt | 2 - .../jellyfin/viewmodels/MediaInfoViewModel.kt | 8 +- .../jellyfin/viewmodels/MediaViewModel.kt | 8 +- .../viewmodels/SearchResultViewModel.kt | 8 +- .../jellyfin/viewmodels/SeasonViewModel.kt | 8 +- app/src/main/res/drawable/ic_alert_circle.xml | 28 +++++++ app/src/main/res/layout/activity_player.xml | 4 +- .../main/res/layout/episode_bottom_sheet.xml | 2 +- app/src/main/res/layout/error_panel.xml | 53 ++++++++++++ app/src/main/res/layout/fragment_favorite.xml | 12 +-- app/src/main/res/layout/fragment_home.xml | 64 +++++++------- app/src/main/res/layout/fragment_library.xml | 68 +++++++-------- app/src/main/res/layout/fragment_media.xml | 70 ++++++++-------- .../main/res/layout/fragment_media_info.xml | 20 +++-- .../res/layout/fragment_search_result.xml | 83 +++++++++---------- app/src/main/res/layout/fragment_season.xml | 12 +-- app/src/main/res/values/strings.xml | 4 +- 30 files changed, 377 insertions(+), 298 deletions(-) create mode 100644 app/src/main/res/drawable/ic_alert_circle.xml create mode 100644 app/src/main/res/layout/error_panel.xml diff --git a/app/build.gradle b/app/build.gradle index 8a623112..14099906 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,6 +42,7 @@ android { buildFeatures { dataBinding true + viewBinding true } } diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt index a81beae0..123391e0 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt @@ -110,7 +110,7 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() { }) binding.playerItemsErrorDetails.setOnClickListener { - ErrorDialogFragment(viewModel.playerItemsError.value ?: "Unknown error").show(parentFragmentManager, "errordialog") + ErrorDialogFragment(viewModel.playerItemsError.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog") } viewModel.loadEpisode(args.episodeId) diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/FavoriteFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/FavoriteFragment.kt index d66f7011..d92e5a04 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/FavoriteFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/FavoriteFragment.kt @@ -7,13 +7,13 @@ import android.view.View import android.view.ViewGroup 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.FavoritesListAdapter import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.databinding.FragmentFavoriteBinding +import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.viewmodels.FavoriteViewModel import org.jellyfin.sdk.model.api.BaseItemDto @@ -29,16 +29,6 @@ class FavoriteFragment : Fragment() { ): View { binding = FragmentFavoriteBinding.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 = viewLifecycleOwner binding.viewModel = viewModel binding.favoritesRecyclerView.adapter = FavoritesListAdapter( @@ -53,11 +43,23 @@ class FavoriteFragment : Fragment() { }) viewModel.error.observe(viewLifecycleOwner, { error -> - if (error) { - snackbar.show() + if (error != null) { + binding.errorLayout.errorPanel.visibility = View.VISIBLE + binding.favoritesRecyclerView.visibility = View.GONE + } else { + binding.errorLayout.errorPanel.visibility = View.GONE + binding.favoritesRecyclerView.visibility = View.VISIBLE } }) + binding.errorLayout.errorRetryButton.setOnClickListener { + viewModel.loadData() + } + + binding.errorLayout.errorDetailsButton.setOnClickListener { + ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog") + } + viewModel.favoriteSections.observe(viewLifecycleOwner, { sections -> if (sections.isEmpty()) { binding.noFavoritesText.visibility = View.VISIBLE diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt index f2bc3b74..78891d63 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt @@ -5,13 +5,13 @@ import android.view.* 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.HomeEpisodeListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewListAdapter import dev.jdtech.jellyfin.databinding.FragmentHomeBinding +import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.viewmodels.HomeViewModel import org.jellyfin.sdk.model.api.BaseItemDto @@ -49,12 +49,6 @@ class HomeFragment : Fragment() { ): View { binding = FragmentHomeBinding.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 = viewLifecycleOwner binding.viewModel = viewModel binding.viewsRecyclerView.adapter = ViewListAdapter(ViewListAdapter.OnClickListener { @@ -78,11 +72,23 @@ class HomeFragment : Fragment() { }) viewModel.error.observe(viewLifecycleOwner, { error -> - if (error) { - snackbar.show() + if (error != null) { + binding.errorLayout.errorPanel.visibility = View.VISIBLE + binding.viewsRecyclerView.visibility = View.GONE + } else { + binding.errorLayout.errorPanel.visibility = View.GONE + binding.viewsRecyclerView.visibility = View.VISIBLE } }) + binding.errorLayout.errorRetryButton.setOnClickListener { + viewModel.loadData() + } + + binding.errorLayout.errorDetailsButton.setOnClickListener { + ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog") + } + return binding.root } diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt index 5858ca06..3eba789f 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt @@ -8,12 +8,12 @@ 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 +import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import org.jellyfin.sdk.model.api.BaseItemDto @AndroidEntryPoint @@ -39,21 +39,23 @@ class LibraryFragment : Fragment() { 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.error.observe(viewLifecycleOwner, { error -> + if (error != null) { + binding.errorLayout.errorPanel.visibility = View.VISIBLE + binding.itemsRecyclerView.visibility = View.GONE + } else { + binding.errorLayout.errorPanel.visibility = View.GONE + binding.itemsRecyclerView.visibility = View.VISIBLE + } + }) + + binding.errorLayout.errorRetryButton.setOnClickListener { viewModel.loadItems(args.libraryId) } - viewModel.error.observe(viewLifecycleOwner, { error -> - if (error) { - snackbar.show() - } - }) + binding.errorLayout.errorDetailsButton.setOnClickListener { + ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog") + } viewModel.finishedLoading.observe(viewLifecycleOwner, { binding.loadingIndicator.visibility = if (it) View.GONE else View.VISIBLE diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaFragment.kt index 94a185ee..cb6b35d0 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaFragment.kt @@ -6,11 +6,11 @@ import androidx.appcompat.widget.SearchView 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.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.viewmodels.MediaViewModel import org.jellyfin.sdk.model.api.BaseItemDto @@ -53,16 +53,6 @@ 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 = viewLifecycleOwner binding.viewModel = viewModel binding.viewsRecyclerView.adapter = @@ -75,11 +65,23 @@ class MediaFragment : Fragment() { }) viewModel.error.observe(viewLifecycleOwner, { error -> - if (error) { - snackbar.show() + if (error != null) { + binding.errorLayout.errorPanel.visibility = View.VISIBLE + binding.viewsRecyclerView.visibility = View.GONE + } else { + binding.errorLayout.errorPanel.visibility = View.GONE + binding.viewsRecyclerView.visibility = View.VISIBLE } }) + binding.errorLayout.errorRetryButton.setOnClickListener { + viewModel.loadData() + } + + binding.errorLayout.errorDetailsButton.setOnClickListener { + ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog") + } + return binding.root } diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt index b9d7bb13..a94aa985 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt @@ -11,7 +11,6 @@ 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 @@ -45,24 +44,26 @@ 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() + if (error != null) { + binding.errorLayout.errorPanel.visibility = View.VISIBLE + binding.mediaInfoScrollview.visibility = View.GONE + } else { + binding.errorLayout.errorPanel.visibility = View.GONE + binding.mediaInfoScrollview.visibility = View.VISIBLE } }) + binding.errorLayout.errorRetryButton.setOnClickListener { + viewModel.loadData(args.itemId, args.itemType) + } + + binding.errorLayout.errorDetailsButton.setOnClickListener { + ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog") + } + viewModel.item.observe(viewLifecycleOwner, { item -> if (item.originalTitle != item.name) { binding.originalTitle.visibility = View.VISIBLE @@ -136,7 +137,7 @@ class MediaInfoFragment : Fragment() { }) binding.playerItemsErrorDetails.setOnClickListener { - ErrorDialogFragment(viewModel.playerItemsError.value ?: "Unknown error").show(parentFragmentManager, "errordialog") + ErrorDialogFragment(viewModel.playerItemsError.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog") } binding.trailerButton.setOnClickListener { diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/SearchResultFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/SearchResultFragment.kt index ca1b6374..f13de478 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/SearchResultFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/SearchResultFragment.kt @@ -8,13 +8,13 @@ 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.FavoritesListAdapter import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.databinding.FragmentSearchResultBinding +import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.viewmodels.SearchResultViewModel import org.jellyfin.sdk.model.api.BaseItemDto @@ -32,16 +32,6 @@ class SearchResultFragment : Fragment() { ): View { binding = FragmentSearchResultBinding.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(args.query) - } - binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = viewModel binding.searchResultsRecyclerView.adapter = FavoritesListAdapter( @@ -56,11 +46,23 @@ class SearchResultFragment : Fragment() { }) viewModel.error.observe(viewLifecycleOwner, { error -> - if (error) { - snackbar.show() + if (error != null) { + binding.errorLayout.errorPanel.visibility = View.VISIBLE + binding.searchResultsRecyclerView.visibility = View.GONE + } else { + binding.errorLayout.errorPanel.visibility = View.GONE + binding.searchResultsRecyclerView.visibility = View.VISIBLE } }) + binding.errorLayout.errorRetryButton.setOnClickListener { + viewModel.loadData(args.query) + } + + binding.errorLayout.errorDetailsButton.setOnClickListener { + ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog") + } + viewModel.sections.observe(viewLifecycleOwner, { sections -> if (sections.isEmpty()) { binding.noSearchResultsText.visibility = View.VISIBLE diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt index 32500867..7ae52879 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt @@ -8,11 +8,11 @@ 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.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.viewmodels.SeasonViewModel import org.jellyfin.sdk.model.api.BaseItemDto @@ -37,21 +37,23 @@ class SeasonFragment : Fragment() { 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.error.observe(viewLifecycleOwner, { error -> + if (error != null) { + binding.errorLayout.errorPanel.visibility = View.VISIBLE + binding.episodesRecyclerView.visibility = View.GONE + } else { + binding.errorLayout.errorPanel.visibility = View.GONE + binding.episodesRecyclerView.visibility = View.VISIBLE + } + }) + + binding.errorLayout.errorRetryButton.setOnClickListener { viewModel.loadEpisodes(args.seriesId, args.seasonId) } - viewModel.error.observe(viewLifecycleOwner, { error -> - if (error) { - snackbar.show() - } - }) + binding.errorLayout.errorDetailsButton.setOnClickListener { + ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog") + } viewModel.finishedLoading.observe(viewLifecycleOwner, { binding.loadingIndicator.visibility = if (it) View.GONE else View.VISIBLE diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/ServerSelectFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/ServerSelectFragment.kt index d082b145..886db38a 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/ServerSelectFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/ServerSelectFragment.kt @@ -8,7 +8,6 @@ import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.databinding.FragmentServerSelectBinding import dev.jdtech.jellyfin.dialogs.DeleteServerDialogFragment import dev.jdtech.jellyfin.adapters.ServerGridAdapter diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/FavoriteViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/FavoriteViewModel.kt index e9138d09..eb339dc7 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/FavoriteViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/FavoriteViewModel.kt @@ -26,15 +26,15 @@ constructor( private val _finishedLoading = MutableLiveData() val finishedLoading: LiveData = _finishedLoading - private val _error = MutableLiveData() - val error: LiveData = _error + private val _error = MutableLiveData() + val error: LiveData = _error init { loadData() } fun loadData() { - _error.value = false + _error.value = null _finishedLoading.value = false viewModelScope.launch { try { @@ -78,7 +78,7 @@ constructor( _favoriteSections.value = tempFavoriteSections } catch (e: Exception) { Timber.e(e) - _error.value = true + _error.value = e.message } _finishedLoading.value = true } diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt index cfba9866..73efc889 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt @@ -38,15 +38,15 @@ constructor( private val _finishedLoading = MutableLiveData() val finishedLoading: LiveData = _finishedLoading - private val _error = MutableLiveData() - val error: LiveData = _error + private val _error = MutableLiveData() + val error: LiveData = _error init { loadData() } fun loadData() { - _error.value = false + _error.value = null _finishedLoading.value = false viewModelScope.launch { try { @@ -87,7 +87,7 @@ constructor( } catch (e: Exception) { Timber.e(e) - _error.value = true + _error.value = e.message } _finishedLoading.value = true } diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/LibraryViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/LibraryViewModel.kt index 04c48f53..ac0d75e9 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/LibraryViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/LibraryViewModel.kt @@ -20,18 +20,18 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { private val _finishedLoading = MutableLiveData() val finishedLoading: LiveData = _finishedLoading - private val _error = MutableLiveData() - val error: LiveData = _error + private val _error = MutableLiveData() + val error: LiveData = _error fun loadItems(parentId: UUID) { - _error.value = false + _error.value = null _finishedLoading.value = false viewModelScope.launch { try { _items.value = jellyfinRepository.getItems(parentId) } catch (e: Exception) { Timber.e(e) - _error.value = true + _error.value = e.message } _finishedLoading.value = true } diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MainViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MainViewModel.kt index 92cab80b..f319617a 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MainViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MainViewModel.kt @@ -1,12 +1,10 @@ package dev.jdtech.jellyfin.viewmodels -import android.app.Application import android.content.SharedPreferences import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.preference.PreferenceManager import dagger.hilt.android.lifecycle.HiltViewModel import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.database.Server diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt index 0e77c612..ea564c2d 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt @@ -64,8 +64,8 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { private val _favorite = MutableLiveData() val favorite: LiveData = _favorite - private val _error = MutableLiveData() - val error: LiveData = _error + private val _error = MutableLiveData() + val error: LiveData = _error var playerItems: MutableList = mutableListOf() @@ -73,7 +73,7 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { val playerItemsError: LiveData = _playerItemsError fun loadData(itemId: UUID, itemType: String) { - _error.value = false + _error.value = null viewModelScope.launch { try { _item.value = jellyfinRepository.getItem(itemId) @@ -96,7 +96,7 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { } } catch (e: Exception) { Timber.e(e) - _error.value = true + _error.value = e.message } } } diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaViewModel.kt index a23f9607..412ea745 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaViewModel.kt @@ -21,8 +21,8 @@ constructor( private val _finishedLoading = MutableLiveData() val finishedLoading: LiveData = _finishedLoading - private val _error = MutableLiveData() - val error: LiveData = _error + private val _error = MutableLiveData() + val error: LiveData = _error init { loadData() @@ -30,7 +30,7 @@ constructor( fun loadData() { _finishedLoading.value = false - _error.value = false + _error.value = null viewModelScope.launch { try { val items = jellyfinRepository.getItems() @@ -43,7 +43,7 @@ constructor( } } catch (e: Exception) { Timber.e(e) - _error.value = true + _error.value = e.message } _finishedLoading.value = true } diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SearchResultViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SearchResultViewModel.kt index f5f8f925..b9fc1e47 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SearchResultViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SearchResultViewModel.kt @@ -26,11 +26,11 @@ constructor( private val _finishedLoading = MutableLiveData() val finishedLoading: LiveData = _finishedLoading - private val _error = MutableLiveData() - val error: LiveData = _error + private val _error = MutableLiveData() + val error: LiveData = _error fun loadData(query: String) { - _error.value = false + _error.value = null _finishedLoading.value = false viewModelScope.launch { try { @@ -74,7 +74,7 @@ constructor( _sections.value = tempSections } catch (e: Exception) { Timber.e(e) - _error.value = true + _error.value = e.message } _finishedLoading.value = true } diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt index cf15b182..39f7525c 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt @@ -24,18 +24,18 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { private val _finishedLoading = MutableLiveData() val finishedLoading: LiveData = _finishedLoading - private val _error = MutableLiveData() - val error: LiveData = _error + private val _error = MutableLiveData() + val error: LiveData = _error fun loadEpisodes(seriesId: UUID, seasonId: UUID) { - _error.value = false + _error.value = null _finishedLoading.value = false viewModelScope.launch { try { _episodes.value = getEpisodes(seriesId, seasonId) } catch (e: Exception) { Timber.e(e) - _error.value = true + _error.value = e.message } _finishedLoading.value = true } diff --git a/app/src/main/res/drawable/ic_alert_circle.xml b/app/src/main/res/drawable/ic_alert_circle.xml new file mode 100644 index 00000000..961d2ac4 --- /dev/null +++ b/app/src/main/res/drawable/ic_alert_circle.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/layout/activity_player.xml b/app/src/main/res/layout/activity_player.xml index 49020401..19680665 100644 --- a/app/src/main/res/layout/activity_player.xml +++ b/app/src/main/res/layout/activity_player.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/episode_bottom_sheet.xml b/app/src/main/res/layout/episode_bottom_sheet.xml index 501acc53..f7168564 100644 --- a/app/src/main/res/layout/episode_bottom_sheet.xml +++ b/app/src/main/res/layout/episode_bottom_sheet.xml @@ -190,7 +190,7 @@ android:id="@+id/player_items_error_details" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/view_details" + android:text="@string/view_details_underlined" android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textColor="@color/red" /> diff --git a/app/src/main/res/layout/error_panel.xml b/app/src/main/res/layout/error_panel.xml new file mode 100644 index 00000000..33ea0c78 --- /dev/null +++ b/app/src/main/res/layout/error_panel.xml @@ -0,0 +1,53 @@ + + + + + + + + + +