Merge pull request #19 from jarnedemeulemeester/develop

Version 0.1.1
This commit is contained in:
Jarne Demeulemeester 2021-08-22 20:37:57 +02:00 committed by GitHub
commit 27265098ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 605 additions and 397 deletions

View file

@ -5,7 +5,7 @@ plugins {
id 'kotlin-kapt' id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin' id 'androidx.navigation.safeargs.kotlin'
id 'dagger.hilt.android.plugin' id 'dagger.hilt.android.plugin'
id 'com.google.android.gms.oss-licenses-plugin' id "com.mikepenz.aboutlibraries.plugin"
} }
android { android {
@ -16,8 +16,8 @@ android {
applicationId "dev.jdtech.jellyfin" applicationId "dev.jdtech.jellyfin"
minSdkVersion 24 minSdkVersion 24
targetSdkVersion 31 targetSdkVersion 31
versionCode 1 versionCode 2
versionName "0.1.0" versionName "0.1.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -42,6 +42,7 @@ android {
buildFeatures { buildFeatures {
dataBinding true dataBinding true
viewBinding true
} }
} }
@ -96,11 +97,12 @@ dependencies {
implementation files('libs/extension-ffmpeg-release.aar') implementation files('libs/extension-ffmpeg-release.aar')
// Timber // Timber
def timber_version = "5.0.0" def timber_version = "5.0.1"
implementation "com.jakewharton.timber:timber:$timber_version" implementation "com.jakewharton.timber:timber:$timber_version"
def oss_licenses_version = "17.0.0" def about_libraries_version = "8.9.1"
implementation "com.google.android.gms:play-services-oss-licenses:$oss_licenses_version" implementation "com.mikepenz:aboutlibraries-core:$about_libraries_version"
implementation "com.mikepenz:aboutlibraries:$about_libraries_version"
// Testing // Testing
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'

View file

@ -27,11 +27,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/>
<activity android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/>
</application> </application>

View file

@ -51,9 +51,10 @@ class MainActivity : AppCompatActivity() {
navController.addOnDestinationChangedListener { _, destination, _ -> navController.addOnDestinationChangedListener { _, destination, _ ->
binding.navView.visibility = when (destination.id) { 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 else -> View.VISIBLE
} }
if (destination.id == R.id.about_libraries_dest) binding.mainToolbar.title = getString(R.string.app_info)
} }
viewModel.navigateToAddServer.observe(this, { viewModel.navigateToAddServer.observe(this, {

View file

@ -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")
}
}

View file

@ -13,6 +13,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.databinding.EpisodeBottomSheetBinding import dev.jdtech.jellyfin.databinding.EpisodeBottomSheetBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel
@ -98,17 +99,20 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
} }
}) })
viewModel.playerItemsError.observe(viewLifecycleOwner, { viewModel.playerItemsError.observe(viewLifecycleOwner, { errorMessage ->
when (it) { if (errorMessage != null) {
true -> {
binding.playerItemsError.visibility = View.VISIBLE binding.playerItemsError.visibility = View.VISIBLE
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 binding.progressCircular.visibility = View.INVISIBLE
} } else {
false -> binding.playerItemsError.visibility = View.GONE binding.playerItemsError.visibility = View.GONE
} }
}) })
binding.playerItemsErrorDetails.setOnClickListener {
ErrorDialogFragment(viewModel.playerItemsError.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
}
viewModel.loadEpisode(args.episodeId) viewModel.loadEpisode(args.episodeId)
return binding.root return binding.root

View file

@ -7,13 +7,13 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.databinding.FragmentFavoriteBinding import dev.jdtech.jellyfin.databinding.FragmentFavoriteBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.viewmodels.FavoriteViewModel import dev.jdtech.jellyfin.viewmodels.FavoriteViewModel
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
@ -29,16 +29,6 @@ class FavoriteFragment : Fragment() {
): View { ): View {
binding = FragmentFavoriteBinding.inflate(inflater, container, false) 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.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel binding.viewModel = viewModel
binding.favoritesRecyclerView.adapter = FavoritesListAdapter( binding.favoritesRecyclerView.adapter = FavoritesListAdapter(
@ -53,11 +43,23 @@ class FavoriteFragment : Fragment() {
}) })
viewModel.error.observe(viewLifecycleOwner, { error -> viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) { if (error != null) {
snackbar.show() 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 -> viewModel.favoriteSections.observe(viewLifecycleOwner, { sections ->
if (sections.isEmpty()) { if (sections.isEmpty()) {
binding.noFavoritesText.visibility = View.VISIBLE binding.noFavoritesText.visibility = View.VISIBLE

View file

@ -5,13 +5,13 @@ import android.view.*
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.adapters.ViewListAdapter import dev.jdtech.jellyfin.adapters.ViewListAdapter
import dev.jdtech.jellyfin.databinding.FragmentHomeBinding import dev.jdtech.jellyfin.databinding.FragmentHomeBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.viewmodels.HomeViewModel import dev.jdtech.jellyfin.viewmodels.HomeViewModel
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
@ -49,12 +49,6 @@ class HomeFragment : Fragment() {
): View { ): View {
binding = FragmentHomeBinding.inflate(inflater, container, false) 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.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel binding.viewModel = viewModel
binding.viewsRecyclerView.adapter = ViewListAdapter(ViewListAdapter.OnClickListener { binding.viewsRecyclerView.adapter = ViewListAdapter(ViewListAdapter.OnClickListener {
@ -78,11 +72,23 @@ class HomeFragment : Fragment() {
}) })
viewModel.error.observe(viewLifecycleOwner, { error -> viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) { if (error != null) {
snackbar.show() 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 return binding.root
} }

View file

@ -8,12 +8,12 @@ import android.view.ViewGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.databinding.FragmentLibraryBinding import dev.jdtech.jellyfin.databinding.FragmentLibraryBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
@AndroidEntryPoint @AndroidEntryPoint
@ -39,21 +39,23 @@ class LibraryFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.viewModel = viewModel binding.viewModel = viewModel
val snackbar = viewModel.error.observe(viewLifecycleOwner, { error ->
Snackbar.make( if (error != null) {
binding.mainLayout, binding.errorLayout.errorPanel.visibility = View.VISIBLE
getString(R.string.error_loading_data), binding.itemsRecyclerView.visibility = View.GONE
Snackbar.LENGTH_INDEFINITE } else {
) binding.errorLayout.errorPanel.visibility = View.GONE
snackbar.setAction(getString(R.string.retry)) { binding.itemsRecyclerView.visibility = View.VISIBLE
}
})
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadItems(args.libraryId) viewModel.loadItems(args.libraryId)
} }
viewModel.error.observe(viewLifecycleOwner, { error -> binding.errorLayout.errorDetailsButton.setOnClickListener {
if (error) { ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
snackbar.show()
} }
})
viewModel.finishedLoading.observe(viewLifecycleOwner, { viewModel.finishedLoading.observe(viewLifecycleOwner, {
binding.loadingIndicator.visibility = if (it) View.GONE else View.VISIBLE binding.loadingIndicator.visibility = if (it) View.GONE else View.VISIBLE

View file

@ -6,11 +6,11 @@ import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.CollectionListAdapter import dev.jdtech.jellyfin.adapters.CollectionListAdapter
import dev.jdtech.jellyfin.databinding.FragmentMediaBinding import dev.jdtech.jellyfin.databinding.FragmentMediaBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.viewmodels.MediaViewModel import dev.jdtech.jellyfin.viewmodels.MediaViewModel
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
@ -53,16 +53,6 @@ class MediaFragment : Fragment() {
): View { ): View {
binding = FragmentMediaBinding.inflate(inflater, container, false) 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.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel binding.viewModel = viewModel
binding.viewsRecyclerView.adapter = binding.viewsRecyclerView.adapter =
@ -75,11 +65,23 @@ class MediaFragment : Fragment() {
}) })
viewModel.error.observe(viewLifecycleOwner, { error -> viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) { if (error != null) {
snackbar.show() 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 return binding.root
} }

View file

@ -11,12 +11,12 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.PersonListAdapter import dev.jdtech.jellyfin.adapters.PersonListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.databinding.FragmentMediaInfoBinding import dev.jdtech.jellyfin.databinding.FragmentMediaInfoBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.dialogs.VideoVersionDialogFragment import dev.jdtech.jellyfin.dialogs.VideoVersionDialogFragment
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel
@ -44,24 +44,26 @@ class MediaInfoFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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 binding.viewModel = viewModel
viewModel.error.observe(viewLifecycleOwner, { error -> viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) { if (error != null) {
snackbar.show() 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 -> viewModel.item.observe(viewLifecycleOwner, { item ->
if (item.originalTitle != item.name) { if (item.originalTitle != item.name) {
binding.originalTitle.visibility = View.VISIBLE binding.originalTitle.visibility = View.VISIBLE
@ -91,7 +93,12 @@ class MediaInfoFragment : Fragment() {
viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000) viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000)
) )
viewModel.doneNavigatingToPlayer() 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 binding.progressCircular.visibility = View.INVISIBLE
} }
}) })
@ -114,17 +121,25 @@ class MediaInfoFragment : Fragment() {
binding.favoriteButton.setImageResource(drawable) binding.favoriteButton.setImageResource(drawable)
}) })
viewModel.playerItemsError.observe(viewLifecycleOwner, { viewModel.playerItemsError.observe(viewLifecycleOwner, { errorMessage ->
when (it) { if (errorMessage != null) {
true -> {
binding.playerItemsError.visibility = View.VISIBLE binding.playerItemsError.visibility = View.VISIBLE
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 binding.progressCircular.visibility = View.INVISIBLE
} } else {
false -> binding.playerItemsError.visibility = View.GONE binding.playerItemsError.visibility = View.GONE
} }
}) })
binding.playerItemsErrorDetails.setOnClickListener {
ErrorDialogFragment(viewModel.playerItemsError.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
}
binding.trailerButton.setOnClickListener { binding.trailerButton.setOnClickListener {
val intent = Intent( val intent = Intent(
Intent.ACTION_VIEW, Intent.ACTION_VIEW,
@ -155,10 +170,20 @@ class MediaInfoFragment : Fragment() {
) )
} else { } else {
navigateToPlayerActivity( 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), 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 binding.progressCircular.visibility = View.INVISIBLE
} }
} }

View file

@ -8,13 +8,13 @@ import android.view.ViewGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.databinding.FragmentSearchResultBinding import dev.jdtech.jellyfin.databinding.FragmentSearchResultBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.viewmodels.SearchResultViewModel import dev.jdtech.jellyfin.viewmodels.SearchResultViewModel
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
@ -32,16 +32,6 @@ class SearchResultFragment : Fragment() {
): View { ): View {
binding = FragmentSearchResultBinding.inflate(inflater, container, false) 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.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel binding.viewModel = viewModel
binding.searchResultsRecyclerView.adapter = FavoritesListAdapter( binding.searchResultsRecyclerView.adapter = FavoritesListAdapter(
@ -56,11 +46,23 @@ class SearchResultFragment : Fragment() {
}) })
viewModel.error.observe(viewLifecycleOwner, { error -> viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) { if (error != null) {
snackbar.show() 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 -> viewModel.sections.observe(viewLifecycleOwner, { sections ->
if (sections.isEmpty()) { if (sections.isEmpty()) {
binding.noSearchResultsText.visibility = View.VISIBLE binding.noSearchResultsText.visibility = View.VISIBLE

View file

@ -8,11 +8,11 @@ import android.view.ViewGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.EpisodeListAdapter import dev.jdtech.jellyfin.adapters.EpisodeListAdapter
import dev.jdtech.jellyfin.databinding.FragmentSeasonBinding import dev.jdtech.jellyfin.databinding.FragmentSeasonBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.viewmodels.SeasonViewModel import dev.jdtech.jellyfin.viewmodels.SeasonViewModel
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
@ -37,21 +37,23 @@ class SeasonFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.viewModel = viewModel binding.viewModel = viewModel
val snackbar = viewModel.error.observe(viewLifecycleOwner, { error ->
Snackbar.make( if (error != null) {
binding.mainLayout, binding.errorLayout.errorPanel.visibility = View.VISIBLE
getString(R.string.error_loading_data), binding.episodesRecyclerView.visibility = View.GONE
Snackbar.LENGTH_INDEFINITE } else {
) binding.errorLayout.errorPanel.visibility = View.GONE
snackbar.setAction(getString(R.string.retry)) { binding.episodesRecyclerView.visibility = View.VISIBLE
}
})
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadEpisodes(args.seriesId, args.seasonId) viewModel.loadEpisodes(args.seriesId, args.seasonId)
} }
viewModel.error.observe(viewLifecycleOwner, { error -> binding.errorLayout.errorDetailsButton.setOnClickListener {
if (error) { ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
snackbar.show()
} }
})
viewModel.finishedLoading.observe(viewLifecycleOwner, { viewModel.finishedLoading.observe(viewLifecycleOwner, {
binding.loadingIndicator.visibility = if (it) View.GONE else View.VISIBLE binding.loadingIndicator.visibility = if (it) View.GONE else View.VISIBLE

View file

@ -8,7 +8,6 @@ import android.view.ViewGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.databinding.FragmentServerSelectBinding import dev.jdtech.jellyfin.databinding.FragmentServerSelectBinding
import dev.jdtech.jellyfin.dialogs.DeleteServerDialogFragment import dev.jdtech.jellyfin.dialogs.DeleteServerDialogFragment
import dev.jdtech.jellyfin.adapters.ServerGridAdapter import dev.jdtech.jellyfin.adapters.ServerGridAdapter

View file

@ -8,7 +8,6 @@ import androidx.navigation.fragment.findNavController
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
@ -38,8 +37,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
true true
} }
findPreference<Preference>("ossLicenses")?.setOnPreferenceClickListener { findPreference<Preference>("appInfo")?.setOnPreferenceClickListener {
startActivity(Intent(requireContext(), OssLicensesMenuActivity::class.java)) findNavController().navigate(SettingsFragmentDirections.actionSettingsFragmentToAboutLibraries())
true true
} }
} }

View file

@ -66,7 +66,10 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
val items: List<BaseItemDto> val items: List<BaseItemDto>
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
items = items =
jellyfinApi.itemsApi.getResumeItems(jellyfinApi.userId!!).content.items ?: listOf() jellyfinApi.itemsApi.getResumeItems(
jellyfinApi.userId!!,
includeItemTypes = listOf("Movie", "Episode"),
).content.items ?: listOf()
} }
return items return items
} }

View file

@ -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
}
}

View file

@ -9,8 +9,11 @@ import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.discovery.RecommendedServerInfo
import org.jellyfin.sdk.discovery.RecommendedServerInfoScore
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -34,19 +37,32 @@ constructor(
* - Connect to server and check if it is a Jellyfin server * - Connect to server and check if it is a Jellyfin server
* - Check if server is not already in Database * - Check if server is not already in Database
*/ */
fun checkServer(baseUrl: String) { fun checkServer(inputValue: String) {
_error.value = null _error.value = null
viewModelScope.launch { viewModelScope.launch {
try {
val candidates = jellyfinApi.jellyfin.discovery.getAddressCandidates(inputValue)
val recommended = jellyfinApi.jellyfin.discovery.getRecommendedServers(
candidates,
RecommendedServerInfoScore.GOOD
)
val recommendedServer: RecommendedServerInfo
try {
recommendedServer = recommended.first()
} catch (e: NoSuchElementException) {
throw Exception("Server not found")
}
jellyfinApi.apply { jellyfinApi.apply {
api.baseUrl = baseUrl api.baseUrl = recommendedServer.address
api.accessToken = null api.accessToken = null
} }
try {
val publicSystemInfo by jellyfinApi.systemApi.getPublicSystemInfo()
Timber.d("Remote server: ${publicSystemInfo.id}")
if (serverAlreadyInDatabase(publicSystemInfo.id)) { Timber.d("Remote server: ${recommendedServer.systemInfo?.id}")
if (serverAlreadyInDatabase(recommendedServer.systemInfo?.id)) {
_error.value = "Server already added" _error.value = "Server already added"
_navigateToLogin.value = false _navigateToLogin.value = false
} else { } else {

View file

@ -43,8 +43,8 @@ constructor(
var playerItems: MutableList<PlayerItem> = mutableListOf() var playerItems: MutableList<PlayerItem> = mutableListOf()
private val _playerItemsError = MutableLiveData<Boolean>() private val _playerItemsError = MutableLiveData<String>()
val playerItemsError: LiveData<Boolean> = _playerItemsError val playerItemsError: LiveData<String> = _playerItemsError
fun loadEpisode(episodeId: UUID) { fun loadEpisode(episodeId: UUID) {
viewModelScope.launch { viewModelScope.launch {
@ -62,13 +62,13 @@ constructor(
} }
fun preparePlayer() { fun preparePlayer() {
_playerItemsError.value = false _playerItemsError.value = null
viewModelScope.launch { viewModelScope.launch {
try { try {
createPlayerItems(_item.value!!) createPlayerItems(_item.value!!)
_navigateToPlayer.value = true _navigateToPlayer.value = true
} catch (e: Exception) { } catch (e: Exception) {
_playerItemsError.value = true _playerItemsError.value = e.message
} }
} }
} }

View file

@ -26,15 +26,15 @@ constructor(
private val _finishedLoading = MutableLiveData<Boolean>() private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading val finishedLoading: LiveData<Boolean> = _finishedLoading
private val _error = MutableLiveData<Boolean>() private val _error = MutableLiveData<String>()
val error: LiveData<Boolean> = _error val error: LiveData<String> = _error
init { init {
loadData() loadData()
} }
fun loadData() { fun loadData() {
_error.value = false _error.value = null
_finishedLoading.value = false _finishedLoading.value = false
viewModelScope.launch { viewModelScope.launch {
try { try {
@ -78,7 +78,7 @@ constructor(
_favoriteSections.value = tempFavoriteSections _favoriteSections.value = tempFavoriteSections
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
_error.value = true _error.value = e.message
} }
_finishedLoading.value = true _finishedLoading.value = true
} }

View file

@ -38,15 +38,15 @@ constructor(
private val _finishedLoading = MutableLiveData<Boolean>() private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading val finishedLoading: LiveData<Boolean> = _finishedLoading
private val _error = MutableLiveData<Boolean>() private val _error = MutableLiveData<String>()
val error: LiveData<Boolean> = _error val error: LiveData<String> = _error
init { init {
loadData() loadData()
} }
fun loadData() { fun loadData() {
_error.value = false _error.value = null
_finishedLoading.value = false _finishedLoading.value = false
viewModelScope.launch { viewModelScope.launch {
try { try {
@ -57,7 +57,8 @@ constructor(
Timber.d("Collection type: ${view.collectionType}") Timber.d("Collection type: ${view.collectionType}")
if (view.collectionType == "homevideos" || if (view.collectionType == "homevideos" ||
view.collectionType == "music" || view.collectionType == "music" ||
view.collectionType == "playlists" view.collectionType == "playlists" ||
view.collectionType == "books"
) continue ) continue
val latestItems = jellyfinRepository.getLatestMedia(view.id) val latestItems = jellyfinRepository.getLatestMedia(view.id)
if (latestItems.isEmpty()) continue if (latestItems.isEmpty()) continue
@ -87,7 +88,7 @@ constructor(
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
_error.value = true _error.value = e.message
} }
_finishedLoading.value = true _finishedLoading.value = true
} }

View file

@ -20,18 +20,18 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
private val _finishedLoading = MutableLiveData<Boolean>() private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading val finishedLoading: LiveData<Boolean> = _finishedLoading
private val _error = MutableLiveData<Boolean>() private val _error = MutableLiveData<String>()
val error: LiveData<Boolean> = _error val error: LiveData<String> = _error
fun loadItems(parentId: UUID) { fun loadItems(parentId: UUID) {
_error.value = false _error.value = null
_finishedLoading.value = false _finishedLoading.value = false
viewModelScope.launch { viewModelScope.launch {
try { try {
_items.value = jellyfinRepository.getItems(parentId) _items.value = jellyfinRepository.getItems(parentId)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
_error.value = true _error.value = e.message
} }
_finishedLoading.value = true _finishedLoading.value = true
} }

View file

@ -1,12 +1,10 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server import dev.jdtech.jellyfin.database.Server

View file

@ -64,16 +64,16 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
private val _favorite = MutableLiveData<Boolean>() private val _favorite = MutableLiveData<Boolean>()
val favorite: LiveData<Boolean> = _favorite val favorite: LiveData<Boolean> = _favorite
private val _error = MutableLiveData<Boolean>() private val _error = MutableLiveData<String>()
val error: LiveData<Boolean> = _error val error: LiveData<String> = _error
var playerItems: MutableList<PlayerItem> = mutableListOf() var playerItems: MutableList<PlayerItem> = mutableListOf()
private val _playerItemsError = MutableLiveData<Boolean>() private val _playerItemsError = MutableLiveData<String>()
val playerItemsError: LiveData<Boolean> = _playerItemsError val playerItemsError: LiveData<String> = _playerItemsError
fun loadData(itemId: UUID, itemType: String) { fun loadData(itemId: UUID, itemType: String) {
_error.value = false _error.value = null
viewModelScope.launch { viewModelScope.launch {
try { try {
_item.value = jellyfinRepository.getItem(itemId) _item.value = jellyfinRepository.getItem(itemId)
@ -96,7 +96,7 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
_error.value = true _error.value = e.message
} }
} }
} }
@ -184,13 +184,13 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
} }
fun preparePlayer() { fun preparePlayer() {
_playerItemsError.value = false _playerItemsError.value = null
viewModelScope.launch { viewModelScope.launch {
try { try {
createPlayerItems(_item.value!!) createPlayerItems(_item.value!!)
_navigateToPlayer.value = playerItems.toTypedArray() _navigateToPlayer.value = playerItems.toTypedArray()
} catch (e: Exception) { } catch (e: Exception) {
_playerItemsError.value = true _playerItemsError.value = e.message
} }
} }
} }

View file

@ -21,8 +21,8 @@ constructor(
private val _finishedLoading = MutableLiveData<Boolean>() private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading val finishedLoading: LiveData<Boolean> = _finishedLoading
private val _error = MutableLiveData<Boolean>() private val _error = MutableLiveData<String>()
val error: LiveData<Boolean> = _error val error: LiveData<String> = _error
init { init {
loadData() loadData()
@ -30,7 +30,7 @@ constructor(
fun loadData() { fun loadData() {
_finishedLoading.value = false _finishedLoading.value = false
_error.value = false _error.value = null
viewModelScope.launch { viewModelScope.launch {
try { try {
val items = jellyfinRepository.getItems() val items = jellyfinRepository.getItems()
@ -39,11 +39,12 @@ constructor(
it.collectionType != "homevideos" && it.collectionType != "homevideos" &&
it.collectionType != "music" && it.collectionType != "music" &&
it.collectionType != "playlists" && it.collectionType != "playlists" &&
it.collectionType != "boxsets" it.collectionType != "boxsets" &&
it.collectionType != "books"
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
_error.value = true _error.value = e.message
} }
_finishedLoading.value = true _finishedLoading.value = true
} }

View file

@ -26,11 +26,11 @@ constructor(
private val _finishedLoading = MutableLiveData<Boolean>() private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading val finishedLoading: LiveData<Boolean> = _finishedLoading
private val _error = MutableLiveData<Boolean>() private val _error = MutableLiveData<String>()
val error: LiveData<Boolean> = _error val error: LiveData<String> = _error
fun loadData(query: String) { fun loadData(query: String) {
_error.value = false _error.value = null
_finishedLoading.value = false _finishedLoading.value = false
viewModelScope.launch { viewModelScope.launch {
try { try {
@ -74,7 +74,7 @@ constructor(
_sections.value = tempSections _sections.value = tempSections
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
_error.value = true _error.value = e.message
} }
_finishedLoading.value = true _finishedLoading.value = true
} }

View file

@ -24,18 +24,18 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
private val _finishedLoading = MutableLiveData<Boolean>() private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading val finishedLoading: LiveData<Boolean> = _finishedLoading
private val _error = MutableLiveData<Boolean>() private val _error = MutableLiveData<String>()
val error: LiveData<Boolean> = _error val error: LiveData<String> = _error
fun loadEpisodes(seriesId: UUID, seasonId: UUID) { fun loadEpisodes(seriesId: UUID, seasonId: UUID) {
_error.value = false _error.value = null
_finishedLoading.value = false _finishedLoading.value = false
viewModelScope.launch { viewModelScope.launch {
try { try {
_episodes.value = getEpisodes(seriesId, seasonId) _episodes.value = getEpisodes(seriesId, seasonId)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
_error.value = true _error.value = e.message
} }
_finishedLoading.value = true _finishedLoading.value = true
} }

View file

@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorOnBackground">
<path
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@android:color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M12,8L12,12"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@android:color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M12,16L12.01,16"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@android:color/white"
android:strokeLineCap="round"/>
</vector>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -13,4 +13,4 @@
android:background="@color/black" android:background="@color/black"
app:show_subtitle_button="true" /> app:show_subtitle_button="true" />
</FrameLayout> </merge>

View file

@ -14,7 +14,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp" android:layout_marginHorizontal="12dp"
android:layout_marginBottom="32dp" android:layout_marginBottom="24dp"
android:foreground="?android:attr/selectableItemBackground" android:foreground="?android:attr/selectableItemBackground"
android:orientation="vertical"> android:orientation="vertical">
@ -37,7 +37,6 @@
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="@{collection.name}" android:text="@{collection.name}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1" android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/collection_image" app:layout_constraintTop_toBottomOf="@id/collection_image"

View file

@ -65,13 +65,14 @@
tools:text="1. To You, in 2000 Years: The Fall of Shiganshina, Part 1" /> tools:text="1. To You, in 2000 Years: The Fall of Shiganshina, Part 1" />
<LinearLayout <LinearLayout
android:id="@+id/episode_metadata"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="24dp" android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/episode_name" app:layout_constraintStart_toStartOf="@id/episode_image"
app:layout_constraintTop_toBottomOf="@id/episode_name"> app:layout_constraintTop_toBottomOf="@id/episode_image">
<TextView <TextView
@ -115,7 +116,7 @@
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/episode_image"> app:layout_constraintTop_toBottomOf="@id/episode_metadata">
<RelativeLayout <RelativeLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -136,10 +137,10 @@
android:id="@+id/progress_circular" android:id="@+id/progress_circular"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:elevation="8dp"
android:padding="8dp"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:elevation="8dp"
android:indeterminateTint="@color/white" android:indeterminateTint="@color/white"
android:padding="8dp"
android:visibility="invisible" /> android:visibility="invisible" />
</RelativeLayout> </RelativeLayout>
@ -163,21 +164,37 @@
android:src="@drawable/ic_heart" /> android:src="@drawable/ic_heart" />
</LinearLayout> </LinearLayout>
<TextView <LinearLayout
android:id="@+id/player_items_error" android:id="@+id/player_items_error"
android:layout_width="0dp" android:layout_width="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="24dp"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/buttons" app:layout_constraintTop_toBottomOf="@id/buttons"
tools:visibility="visible">
<TextView
android:id="@+id/player_items_error_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/error_preparing_player_items" android:text="@string/error_preparing_player_items"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="@color/red" android:textColor="@color/red" />
android:visibility="gone"
tools:visibility="visible" /> <TextView
android:id="@+id/player_items_error_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/view_details_underlined"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="@color/red" />
</LinearLayout>
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"

View file

@ -82,5 +82,13 @@
app:layout_constraintStart_toStartOf="@id/episode_title" app:layout_constraintStart_toStartOf="@id/episode_title"
app:layout_constraintTop_toBottomOf="@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." /> 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." />
<FrameLayout
android:layout_width="0dp"
android:layout_height="24dp"
android:background="@drawable/header_gradient"
app:layout_constraintBottom_toBottomOf="@id/episode_desc"
app:layout_constraintEnd_toEndOf="@id/episode_desc"
app:layout_constraintStart_toStartOf="@id/episode_desc" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/error_panel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="4dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_alert_circle" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/error_loading_data"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/error_details_button"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/view_details" />
<Button
android:id="@+id/error_retry_button"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/retry" />
</LinearLayout>
</LinearLayout>

View file

@ -9,12 +9,6 @@
type="dev.jdtech.jellyfin.viewmodels.FavoriteViewModel" /> type="dev.jdtech.jellyfin.viewmodels.FavoriteViewModel" />
</data> </data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.FavoriteFragment">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -30,6 +24,10 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" /> app:trackCornerRadius="10dp" />
<include
android:id="@+id/error_layout"
layout="@layout/error_panel" />
<TextView <TextView
android:id="@+id/no_favorites_text" android:id="@+id/no_favorites_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -58,6 +56,4 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View file

@ -10,11 +10,6 @@
type="dev.jdtech.jellyfin.viewmodels.HomeViewModel" /> type="dev.jdtech.jellyfin.viewmodels.HomeViewModel" />
</data> </data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -32,6 +27,8 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" /> app:trackCornerRadius="10dp" />
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/views_recycler_view" android:id="@+id/views_recycler_view"
android:layout_width="0dp" android:layout_width="0dp"
@ -47,6 +44,5 @@
tools:itemCount="4" tools:itemCount="4"
tools:listitem="@layout/view_item" /> tools:listitem="@layout/view_item" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View file

@ -10,11 +10,6 @@
type="dev.jdtech.jellyfin.viewmodels.LibraryViewModel" /> type="dev.jdtech.jellyfin.viewmodels.LibraryViewModel" />
</data> </data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -31,6 +26,8 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" /> app:trackCornerRadius="10dp" />
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/items_recycler_view" android:id="@+id/items_recycler_view"
android:layout_width="0dp" android:layout_width="0dp"
@ -50,7 +47,6 @@
tools:listitem="@layout/base_item" /> tools:listitem="@layout/base_item" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View file

@ -10,11 +10,6 @@
type="dev.jdtech.jellyfin.viewmodels.MediaViewModel" /> type="dev.jdtech.jellyfin.viewmodels.MediaViewModel" />
</data> </data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -32,6 +27,8 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" /> app:trackCornerRadius="10dp" />
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/views_recycler_view" android:id="@+id/views_recycler_view"
android:layout_width="0dp" android:layout_width="0dp"
@ -51,5 +48,4 @@
tools:listitem="@layout/collection_item" /> tools:listitem="@layout/collection_item" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View file

@ -12,15 +12,22 @@
type="dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel" /> type="dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel" />
</data> </data>
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<include
android:id="@+id/error_layout"
layout="@layout/error_panel" />
<ScrollView <ScrollView
android:id="@+id/media_info_scrollview" android:id="@+id/media_info_scrollview"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent"> android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -185,18 +192,35 @@
android:src="@drawable/ic_heart" /> android:src="@drawable/ic_heart" />
</LinearLayout> </LinearLayout>
<TextView <LinearLayout
android:id="@+id/player_items_error" android:id="@+id/player_items_error"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="24dp"
android:layout_marginTop="-12dp" android:layout_marginTop="-12dp"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/player_items_error_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/error_preparing_player_items" android:text="@string/error_preparing_player_items"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="@color/red" android:textColor="@color/red" />
android:visibility="gone"
tools:visibility="visible" /> <TextView
android:id="@+id/player_items_error_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/view_details_underlined"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="@color/red" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/info" android:id="@+id/info"
@ -415,7 +439,6 @@
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View file

@ -4,17 +4,12 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <data>
<variable <variable
name="viewModel" name="viewModel"
type="dev.jdtech.jellyfin.viewmodels.SearchResultViewModel" /> type="dev.jdtech.jellyfin.viewmodels.SearchResultViewModel" />
</data> </data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.FavoriteFragment">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -30,16 +25,18 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" /> app:trackCornerRadius="10dp" />
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
<TextView <TextView
android:id="@+id/no_search_results_text" android:id="@+id/no_search_results_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/no_search_results" android:text="@string/no_search_results"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
android:visibility="gone"/>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_results_recycler_view" android:id="@+id/search_results_recycler_view"
@ -47,17 +44,15 @@
android:layout_height="0dp" android:layout_height="0dp"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingTop="16dp" android:paddingTop="16dp"
app:favoriteSections="@{viewModel.sections}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:favoriteSections="@{viewModel.sections}"
tools:itemCount="4" tools:itemCount="4"
tools:listitem="@layout/favorite_section" /> tools:listitem="@layout/favorite_section" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View file

@ -12,12 +12,6 @@
</data> </data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -33,6 +27,10 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" /> app:trackCornerRadius="10dp" />
<include
android:id="@+id/error_layout"
layout="@layout/error_panel" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/episodes_recycler_view" android:id="@+id/episodes_recycler_view"
android:layout_width="0dp" android:layout_width="0dp"
@ -47,6 +45,4 @@
tools:itemCount="4" tools:itemCount="4"
tools:listitem="@layout/episode_item" /> tools:listitem="@layout/episode_item" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View file

@ -56,10 +56,13 @@
<fragment <fragment
android:id="@+id/settingsFragment" android:id="@+id/settingsFragment"
android:name="dev.jdtech.jellyfin.fragments.SettingsFragment" android:name="dev.jdtech.jellyfin.fragments.SettingsFragment"
android:label="@string/title_settings" > android:label="@string/title_settings">
<action <action
android:id="@+id/action_navigation_settings_to_serverSelectFragment2" android:id="@+id/action_navigation_settings_to_serverSelectFragment2"
app:destination="@id/serverSelectFragment" /> app:destination="@id/serverSelectFragment" />
<action
android:id="@+id/action_settingsFragment_to_about_libraries"
app:destination="@id/about_libraries" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/libraryFragment" android:id="@+id/libraryFragment"
@ -173,7 +176,7 @@
android:id="@+id/searchResultFragment" android:id="@+id/searchResultFragment"
android:name="dev.jdtech.jellyfin.fragments.SearchResultFragment" android:name="dev.jdtech.jellyfin.fragments.SearchResultFragment"
android:label="{query}" android:label="{query}"
tools:layout="@layout/fragment_search_result" > tools:layout="@layout/fragment_search_result">
<action <action
android:id="@+id/action_favoriteFragment_to_episodeBottomSheetFragment" android:id="@+id/action_favoriteFragment_to_episodeBottomSheetFragment"
app:destination="@id/episodeBottomSheetFragment" /> app:destination="@id/episodeBottomSheetFragment" />
@ -222,7 +225,7 @@
android:id="@+id/initializingFragment" android:id="@+id/initializingFragment"
android:name="dev.jdtech.jellyfin.fragments.InitializingFragment" android:name="dev.jdtech.jellyfin.fragments.InitializingFragment"
android:label="@string/initializing" android:label="@string/initializing"
tools:layout="@layout/fragment_initializing" > tools:layout="@layout/fragment_initializing">
<action <action
android:id="@+id/action_initializingFragment_to_navigation_home" android:id="@+id/action_initializingFragment_to_navigation_home"
app:destination="@id/homeFragment" app:destination="@id/homeFragment"
@ -234,4 +237,7 @@
app:popUpTo="@id/initializingFragment" app:popUpTo="@id/initializingFragment"
app:popUpToInclusive="true" /> app:popUpToInclusive="true" />
</fragment> </fragment>
<include app:graph="@navigation/aboutlibs_navigation" />
</navigation> </navigation>

View file

@ -2,7 +2,7 @@
<resources> <resources>
<dimen name="setup_container_width">400dp</dimen> <dimen name="setup_container_width">400dp</dimen>
<item name="server_columns" type="integer">6</item> <item name="server_columns" type="integer">6</item>
<item name="collection_columns" type="integer">2</item> <item name="collection_columns" type="integer">3</item>
<item name="library_columns" type="integer">4</item> <item name="library_columns" type="integer">4</item>
<item name="recyclerview_animation_duration" type="integer">450</item> <item name="recyclerview_animation_duration" type="integer">450</item>
<dimen name="nextup_media_width">350dp</dimen> <dimen name="nextup_media_width">350dp</dimen>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<item name="server_columns" type="integer">8</item> <item name="server_columns" type="integer">8</item>
<item name="collection_columns" type="integer">3</item>
<item name="library_columns" type="integer">6</item> <item name="library_columns" type="integer">6</item>
</resources> </resources>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="aboutLibraries_description_showIcon">true</string>
<string name="aboutLibraries_description_showVersion">true</string>
<string name="aboutLibraries_description_name">@string/app_name</string>
<string name="aboutLibraries_description_text">@string/app_description</string>
<string name="aboutLibraries_showLicense">true</string>
</resources>

View file

@ -5,7 +5,7 @@
<dimen name="setup_container_width">@dimen/match_parent</dimen> <dimen name="setup_container_width">@dimen/match_parent</dimen>
<dimen name="overview_media_width">150dp</dimen> <dimen name="overview_media_width">150dp</dimen>
<item name="server_columns" type="integer">3</item> <item name="server_columns" type="integer">3</item>
<item name="collection_columns" type="integer">1</item> <item name="collection_columns" type="integer">2</item>
<item name="library_columns" type="integer">2</item> <item name="library_columns" type="integer">2</item>
<item name="recyclerview_animation_duration" type="integer">250</item> <item name="recyclerview_animation_duration" type="integer">250</item>
<dimen name="nextup_media_width">@dimen/match_parent</dimen> <dimen name="nextup_media_width">@dimen/match_parent</dimen>

View file

@ -1,5 +1,6 @@
<resources> <resources>
<string name="app_name">Findroid</string> <string name="app_name">Findroid</string>
<string name="app_description">Third-party native Jellyfin app</string>
<string name="jellyfin_banner">Jellyfin banner</string> <string name="jellyfin_banner">Jellyfin banner</string>
<string name="add_server">Add server</string> <string name="add_server">Add server</string>
<string name="login">Login</string> <string name="login">Login</string>
@ -14,7 +15,6 @@
<string name="remove_server_dialog_text">Are you sure you want to remove the server %1$s</string> <string name="remove_server_dialog_text">Are you sure you want to remove the server %1$s</string>
<string name="remove">Remove</string> <string name="remove">Remove</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="title_activity_main">MainActivity</string>
<string name="title_home">Home</string> <string name="title_home">Home</string>
<string name="title_media">My media</string> <string name="title_media">My media</string>
<string name="title_favorite">Favorites</string> <string name="title_favorite">Favorites</string>
@ -49,9 +49,11 @@
<string name="manage_servers">Manage servers</string> <string name="manage_servers">Manage servers</string>
<string name="settings_category_appearance">Appearance</string> <string name="settings_category_appearance">Appearance</string>
<string name="theme">Theme</string> <string name="theme">Theme</string>
<string name="error_preparing_player_items">Error preparing player items</string> <string name="error_preparing_player_items">Error preparing player items.</string>
<string name="version">Version</string> <string name="view_details">View details</string>
<string name="open_source_licenses">Open source licenses</string> <string name="view_details_underlined"><u>@string/view_details</u></string>
<string name="about">About</string> <string name="about">About</string>
<string name="privacy_policy">Privacy policy</string> <string name="privacy_policy">Privacy policy</string>
<string name="app_info">App info</string>
<string name="unknown_error">Unknown error</string>
</resources> </resources>

View file

@ -10,4 +10,14 @@
<item name="cornerFamily">rounded</item> <item name="cornerFamily">rounded</item>
<item name="cornerSize">10dp</item> <item name="cornerSize">10dp</item>
</style> </style>
<style name="ErrorDialogStyle" parent="MaterialAlertDialog.MaterialComponents">
<item name="materialAlertDialogBodyTextStyle">@style/AlertDialogBodyText</item>
</style>
<style name="AlertDialogBodyText" parent="MaterialAlertDialog.MaterialComponents.Body.Text">
<item name="android:fontFamily">monospace</item>
</style>
</resources> </resources>

View file

@ -46,10 +46,8 @@
app:title="@string/privacy_policy" /> app:title="@string/privacy_policy" />
<Preference <Preference
app:key="ossLicenses" app:key="appInfo"
app:title="@string/open_source_licenses" /> app:title="@string/app_info" />
<dev.jdtech.jellyfin.utils.VersionPreference app:title="@string/version" />
</PreferenceCategory> </PreferenceCategory>

View file

@ -4,9 +4,12 @@ buildscript {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
} }
dependencies { 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" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@ -17,8 +20,8 @@ buildscript {
def hilt_version = "2.38.1" def hilt_version = "2.38.1"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
def oss_licenses_version = "0.10.4" def about_libraries_version = "8.9.1"
classpath "com.google.android.gms:oss-licenses-plugin:$oss_licenses_version" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries_version"
} }
} }