diff --git a/app/build.gradle b/app/build.gradle
index fa04e160..f3f12b4a 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 {
@@ -16,8 +16,8 @@ android {
applicationId "dev.jdtech.jellyfin"
minSdkVersion 24
targetSdkVersion 31
- versionCode 1
- versionName "0.1.0"
+ versionCode 2
+ versionName "0.1.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -42,6 +42,7 @@ android {
buildFeatures {
dataBinding true
+ viewBinding true
}
}
@@ -96,11 +97,12 @@ 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 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/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..123391e0 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 ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
+ }
+
viewModel.loadEpisode(args.episodeId)
return binding.root
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 6e6ac673..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,12 +11,12 @@ 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
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
@@ -44,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
@@ -91,7 +93,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 +121,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 ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
+ }
+
binding.trailerButton.setOnClickListener {
val intent = Intent(
Intent.ACTION_VIEW,
@@ -155,10 +170,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/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/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/repository/JellyfinRepositoryImpl.kt b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt
index 297def39..1f393e98 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt
@@ -66,7 +66,10 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
val items: List
withContext(Dispatchers.IO) {
items =
- jellyfinApi.itemsApi.getResumeItems(jellyfinApi.userId!!).content.items ?: listOf()
+ jellyfinApi.itemsApi.getResumeItems(
+ jellyfinApi.userId!!,
+ includeItemTypes = listOf("Movie", "Episode"),
+ ).content.items ?: listOf()
}
return items
}
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/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 {
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/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..70807509 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 {
@@ -57,7 +57,8 @@ constructor(
Timber.d("Collection type: ${view.collectionType}")
if (view.collectionType == "homevideos" ||
view.collectionType == "music" ||
- view.collectionType == "playlists"
+ view.collectionType == "playlists" ||
+ view.collectionType == "books"
) continue
val latestItems = jellyfinRepository.getLatestMedia(view.id)
if (latestItems.isEmpty()) continue
@@ -87,7 +88,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 6df01cbb..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,16 +64,16 @@ 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()
- 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
+ _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
}
}
}
@@ -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/java/dev/jdtech/jellyfin/viewmodels/MediaViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaViewModel.kt
index a23f9607..60e1e1c6 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()
@@ -39,11 +39,12 @@ constructor(
it.collectionType != "homevideos" &&
it.collectionType != "music" &&
it.collectionType != "playlists" &&
- it.collectionType != "boxsets"
+ it.collectionType != "boxsets" &&
+ it.collectionType != "books"
}
} 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/collection_item.xml b/app/src/main/res/layout/collection_item.xml
index 6b78f8f1..2261215b 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="24dp"
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/layout/episode_bottom_sheet.xml b/app/src/main/res/layout/episode_bottom_sheet.xml
index 99044167..5bb2b42b 100644
--- a/app/src/main/res/layout/episode_bottom_sheet.xml
+++ b/app/src/main/res/layout/episode_bottom_sheet.xml
@@ -65,13 +65,14 @@
tools:text="1. To You, in 2000 Years: The Fall of Shiganshina, Part 1" />
+ app:layout_constraintStart_toStartOf="@id/episode_image"
+ app:layout_constraintTop_toBottomOf="@id/episode_image">
+ app:layout_constraintTop_toBottomOf="@id/episode_metadata">
@@ -163,21 +164,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">
+
+
+
+
+
+
+
\ No newline at end of file
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_favorite.xml b/app/src/main/res/layout/fragment_favorite.xml
index a4660e90..06d57999 100644
--- a/app/src/main/res/layout/fragment_favorite.xml
+++ b/app/src/main/res/layout/fragment_favorite.xml
@@ -9,12 +9,6 @@
type="dev.jdtech.jellyfin.viewmodels.FavoriteViewModel" />
-
-
@@ -30,6 +24,10 @@
app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" />
+
+
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index c231859c..63f600f6 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -10,43 +10,39 @@
type="dev.jdtech.jellyfin.viewmodels.HomeViewModel" />
-
+ android:layout_height="match_parent"
+ tools:context=".fragments.HomeFragment">
+
-
+
-
-
-
-
-
+
+
diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml
index 4e023031..51042b48 100644
--- a/app/src/main/res/layout/fragment_library.xml
+++ b/app/src/main/res/layout/fragment_library.xml
@@ -10,47 +10,43 @@
type="dev.jdtech.jellyfin.viewmodels.LibraryViewModel" />
-
+ android:layout_height="match_parent"
+ tools:context=".fragments.LibraryFragment">
-
+
-
+
-
+
-
-
+
diff --git a/app/src/main/res/layout/fragment_media.xml b/app/src/main/res/layout/fragment_media.xml
index a38dbffd..ae44afcc 100644
--- a/app/src/main/res/layout/fragment_media.xml
+++ b/app/src/main/res/layout/fragment_media.xml
@@ -10,46 +10,42 @@
type="dev.jdtech.jellyfin.viewmodels.MediaViewModel" />
-
+ android:layout_height="match_parent"
+ android:animateLayoutChanges="true"
+ tools:context=".fragments.MediaFragment">
-
+
-
+
-
+
-
-
+
diff --git a/app/src/main/res/layout/fragment_media_info.xml b/app/src/main/res/layout/fragment_media_info.xml
index 2d08d881..3ebd9fec 100644
--- a/app/src/main/res/layout/fragment_media_info.xml
+++ b/app/src/main/res/layout/fragment_media_info.xml
@@ -12,15 +12,22 @@
type="dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel" />
-
+
+
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent">
-
+ tools:visibility="visible">
+
+
+
+
+
+
-
-
+
diff --git a/app/src/main/res/layout/fragment_search_result.xml b/app/src/main/res/layout/fragment_search_result.xml
index d250d75e..da296d2d 100644
--- a/app/src/main/res/layout/fragment_search_result.xml
+++ b/app/src/main/res/layout/fragment_search_result.xml
@@ -4,60 +4,55 @@
xmlns:tools="http://schemas.android.com/tools">
+
-
+ android:layout_height="match_parent">
-
+
-
+
-
+
-
+
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_season.xml b/app/src/main/res/layout/fragment_season.xml
index bb1f63ae..490dd83e 100644
--- a/app/src/main/res/layout/fragment_season.xml
+++ b/app/src/main/res/layout/fragment_season.xml
@@ -12,12 +12,6 @@
-
-
-
@@ -33,6 +27,10 @@
app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" />
+
+
-
-
\ 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-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/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/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
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c1c8aca1..d268baba 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
@@ -49,9 +49,11 @@
Manage servers
Appearance
Theme
- Error preparing player items
- Version
- Open source licenses
+ Error preparing player items.
+ View details
+ @string/view_details
About
Privacy policy
+ App info
+ Unknown error
\ No newline at end of file
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
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..5f6ed4c6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,9 +4,12 @@ buildscript {
repositories {
google()
mavenCentral()
+ maven {
+ url "https://plugins.gradle.org/m2/"
+ }
}
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
@@ -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"
}
}