commit
27265098ba
48 changed files with 605 additions and 397 deletions
|
@ -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'
|
||||
|
|
|
@ -27,11 +27,6 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</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>
|
||||
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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 -> {
|
||||
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
|
||||
}
|
||||
false -> binding.playerItemsError.visibility = View.GONE
|
||||
} 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 -> {
|
||||
viewModel.playerItemsError.observe(viewLifecycleOwner, { errorMessage ->
|
||||
if (errorMessage != null) {
|
||||
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
|
||||
}
|
||||
false -> binding.playerItemsError.visibility = View.GONE
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Preference>("ossLicenses")?.setOnPreferenceClickListener {
|
||||
startActivity(Intent(requireContext(), OssLicensesMenuActivity::class.java))
|
||||
findPreference<Preference>("appInfo")?.setOnPreferenceClickListener {
|
||||
findNavController().navigate(SettingsFragmentDirections.actionSettingsFragmentToAboutLibraries())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,10 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
|
|||
val items: List<BaseItemDto>
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
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 {
|
||||
api.baseUrl = baseUrl
|
||||
api.baseUrl = recommendedServer.address
|
||||
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"
|
||||
_navigateToLogin.value = false
|
||||
} else {
|
||||
|
|
|
@ -43,8 +43,8 @@ constructor(
|
|||
|
||||
var playerItems: MutableList<PlayerItem> = mutableListOf()
|
||||
|
||||
private val _playerItemsError = MutableLiveData<Boolean>()
|
||||
val playerItemsError: LiveData<Boolean> = _playerItemsError
|
||||
private val _playerItemsError = MutableLiveData<String>()
|
||||
val playerItemsError: LiveData<String> = _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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,15 +26,15 @@ constructor(
|
|||
private val _finishedLoading = MutableLiveData<Boolean>()
|
||||
val finishedLoading: LiveData<Boolean> = _finishedLoading
|
||||
|
||||
private val _error = MutableLiveData<Boolean>()
|
||||
val error: LiveData<Boolean> = _error
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> = _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
|
||||
}
|
||||
|
|
|
@ -38,15 +38,15 @@ constructor(
|
|||
private val _finishedLoading = MutableLiveData<Boolean>()
|
||||
val finishedLoading: LiveData<Boolean> = _finishedLoading
|
||||
|
||||
private val _error = MutableLiveData<Boolean>()
|
||||
val error: LiveData<Boolean> = _error
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> = _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
|
||||
}
|
||||
|
|
|
@ -20,18 +20,18 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
|
|||
private val _finishedLoading = MutableLiveData<Boolean>()
|
||||
val finishedLoading: LiveData<Boolean> = _finishedLoading
|
||||
|
||||
private val _error = MutableLiveData<Boolean>()
|
||||
val error: LiveData<Boolean> = _error
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> = _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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -64,16 +64,16 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
|
|||
private val _favorite = MutableLiveData<Boolean>()
|
||||
val favorite: LiveData<Boolean> = _favorite
|
||||
|
||||
private val _error = MutableLiveData<Boolean>()
|
||||
val error: LiveData<Boolean> = _error
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> = _error
|
||||
|
||||
var playerItems: MutableList<PlayerItem> = mutableListOf()
|
||||
|
||||
private val _playerItemsError = MutableLiveData<Boolean>()
|
||||
val playerItemsError: LiveData<Boolean> = _playerItemsError
|
||||
private val _playerItemsError = MutableLiveData<String>()
|
||||
val playerItemsError: LiveData<String> = _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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ constructor(
|
|||
private val _finishedLoading = MutableLiveData<Boolean>()
|
||||
val finishedLoading: LiveData<Boolean> = _finishedLoading
|
||||
|
||||
private val _error = MutableLiveData<Boolean>()
|
||||
val error: LiveData<Boolean> = _error
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> = _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
|
||||
}
|
||||
|
|
|
@ -26,11 +26,11 @@ constructor(
|
|||
private val _finishedLoading = MutableLiveData<Boolean>()
|
||||
val finishedLoading: LiveData<Boolean> = _finishedLoading
|
||||
|
||||
private val _error = MutableLiveData<Boolean>()
|
||||
val error: LiveData<Boolean> = _error
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> = _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
|
||||
}
|
||||
|
|
|
@ -24,18 +24,18 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
|
|||
private val _finishedLoading = MutableLiveData<Boolean>()
|
||||
val finishedLoading: LiveData<Boolean> = _finishedLoading
|
||||
|
||||
private val _error = MutableLiveData<Boolean>()
|
||||
val error: LiveData<Boolean> = _error
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> = _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
|
||||
}
|
||||
|
|
28
app/src/main/res/drawable/ic_alert_circle.xml
Normal file
28
app/src/main/res/drawable/ic_alert_circle.xml
Normal 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>
|
|
@ -1,5 +1,5 @@
|
|||
<?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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -13,4 +13,4 @@
|
|||
android:background="@color/black"
|
||||
app:show_subtitle_button="true" />
|
||||
|
||||
</FrameLayout>
|
||||
</merge>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -65,13 +65,14 @@
|
|||
tools:text="1. To You, in 2000 Years: The Fall of Shiganshina, Part 1" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/episode_metadata"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/episode_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/episode_name">
|
||||
app:layout_constraintStart_toStartOf="@id/episode_image"
|
||||
app:layout_constraintTop_toBottomOf="@id/episode_image">
|
||||
|
||||
|
||||
<TextView
|
||||
|
@ -115,7 +116,7 @@
|
|||
android:layout_marginBottom="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/episode_image">
|
||||
app:layout_constraintTop_toBottomOf="@id/episode_metadata">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -136,10 +137,10 @@
|
|||
android:id="@+id/progress_circular"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="8dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:elevation="8dp"
|
||||
android:indeterminateTint="@color/white"
|
||||
android:padding="8dp"
|
||||
android:visibility="invisible" />
|
||||
</RelativeLayout>
|
||||
|
||||
|
@ -163,21 +164,37 @@
|
|||
android:src="@drawable/ic_heart" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:id="@+id/player_items_error"
|
||||
android:layout_width="0dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="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"
|
||||
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:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textColor="@color/red"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
android:textColor="@color/red" />
|
||||
|
||||
<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
|
||||
android:layout_width="0dp"
|
||||
|
|
|
@ -82,5 +82,13 @@
|
|||
app:layout_constraintStart_toStartOf="@id/episode_title"
|
||||
app:layout_constraintTop_toBottomOf="@id/episode_title"
|
||||
tools:text="After one hundred years of peace, humanity is suddenly reminded of the terror of being at the Titans' mercy. After one hundred years of peace, humanity is suddenly reminded of the terror of being at the Titans' mercy." />
|
||||
|
||||
<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>
|
||||
</layout>
|
53
app/src/main/res/layout/error_panel.xml
Normal file
53
app/src/main/res/layout/error_panel.xml
Normal 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>
|
|
@ -9,12 +9,6 @@
|
|||
type="dev.jdtech.jellyfin.viewmodels.FavoriteViewModel" />
|
||||
</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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
@ -30,6 +24,10 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:trackCornerRadius="10dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/error_layout"
|
||||
layout="@layout/error_panel" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_favorites_text"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -58,6 +56,4 @@
|
|||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
|
@ -10,11 +10,6 @@
|
|||
type="dev.jdtech.jellyfin.viewmodels.HomeViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/main_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -32,6 +27,8 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:trackCornerRadius="10dp" />
|
||||
|
||||
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/views_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
|
@ -47,6 +44,5 @@
|
|||
tools:itemCount="4"
|
||||
tools:listitem="@layout/view_item" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
|
|
|
@ -10,11 +10,6 @@
|
|||
type="dev.jdtech.jellyfin.viewmodels.LibraryViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/main_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -31,6 +26,8 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:trackCornerRadius="10dp" />
|
||||
|
||||
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/items_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
|
@ -50,7 +47,6 @@
|
|||
tools:listitem="@layout/base_item" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
|
||||
|
|
|
@ -10,11 +10,6 @@
|
|||
type="dev.jdtech.jellyfin.viewmodels.MediaViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/main_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -32,6 +27,8 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:trackCornerRadius="10dp" />
|
||||
|
||||
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/views_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
|
@ -51,5 +48,4 @@
|
|||
tools:listitem="@layout/collection_item" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</layout>
|
||||
|
|
|
@ -12,15 +12,22 @@
|
|||
type="dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/main_layout"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
android:id="@+id/error_layout"
|
||||
layout="@layout/error_panel" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/media_info_scrollview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
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">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -185,18 +192,35 @@
|
|||
android:src="@drawable/ic_heart" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:id="@+id/player_items_error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="-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:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textColor="@color/red"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
android:textColor="@color/red" />
|
||||
|
||||
<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
|
||||
android:id="@+id/info"
|
||||
|
@ -415,7 +439,6 @@
|
|||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
|
@ -4,17 +4,12 @@
|
|||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="dev.jdtech.jellyfin.viewmodels.SearchResultViewModel" />
|
||||
</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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
@ -30,16 +25,18 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:trackCornerRadius="10dp" />
|
||||
|
||||
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_search_results_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/no_search_results"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:visibility="gone"/>
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/search_results_recycler_view"
|
||||
|
@ -47,17 +44,15 @@
|
|||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="16dp"
|
||||
app:favoriteSections="@{viewModel.sections}"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:favoriteSections="@{viewModel.sections}"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/favorite_section" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
|
@ -12,12 +12,6 @@
|
|||
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/main_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
@ -33,6 +27,10 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:trackCornerRadius="10dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/error_layout"
|
||||
layout="@layout/error_panel" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/episodes_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
|
@ -47,6 +45,4 @@
|
|||
tools:itemCount="4"
|
||||
tools:listitem="@layout/episode_item" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
|
@ -60,6 +60,9 @@
|
|||
<action
|
||||
android:id="@+id/action_navigation_settings_to_serverSelectFragment2"
|
||||
app:destination="@id/serverSelectFragment" />
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_about_libraries"
|
||||
app:destination="@id/about_libraries" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/libraryFragment"
|
||||
|
@ -234,4 +237,7 @@
|
|||
app:popUpTo="@id/initializingFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
</fragment>
|
||||
|
||||
<include app:graph="@navigation/aboutlibs_navigation" />
|
||||
|
||||
</navigation>
|
|
@ -2,7 +2,7 @@
|
|||
<resources>
|
||||
<dimen name="setup_container_width">400dp</dimen>
|
||||
<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="recyclerview_animation_duration" type="integer">450</item>
|
||||
<dimen name="nextup_media_width">350dp</dimen>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="server_columns" type="integer">8</item>
|
||||
<item name="collection_columns" type="integer">3</item>
|
||||
<item name="library_columns" type="integer">6</item>
|
||||
</resources>
|
8
app/src/main/res/values/about_libraries.xml
Normal file
8
app/src/main/res/values/about_libraries.xml
Normal 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>
|
|
@ -5,7 +5,7 @@
|
|||
<dimen name="setup_container_width">@dimen/match_parent</dimen>
|
||||
<dimen name="overview_media_width">150dp</dimen>
|
||||
<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="recyclerview_animation_duration" type="integer">250</item>
|
||||
<dimen name="nextup_media_width">@dimen/match_parent</dimen>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<resources>
|
||||
<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="add_server">Add server</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">Remove</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="title_activity_main">MainActivity</string>
|
||||
<string name="title_home">Home</string>
|
||||
<string name="title_media">My media</string>
|
||||
<string name="title_favorite">Favorites</string>
|
||||
|
@ -49,9 +49,11 @@
|
|||
<string name="manage_servers">Manage servers</string>
|
||||
<string name="settings_category_appearance">Appearance</string>
|
||||
<string name="theme">Theme</string>
|
||||
<string name="error_preparing_player_items">Error preparing player items</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="open_source_licenses">Open source licenses</string>
|
||||
<string name="error_preparing_player_items">Error preparing player items.</string>
|
||||
<string name="view_details">View details</string>
|
||||
<string name="view_details_underlined"><u>@string/view_details</u></string>
|
||||
<string name="about">About</string>
|
||||
<string name="privacy_policy">Privacy policy</string>
|
||||
<string name="app_info">App info</string>
|
||||
<string name="unknown_error">Unknown error</string>
|
||||
</resources>
|
|
@ -10,4 +10,14 @@
|
|||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSize">10dp</item>
|
||||
</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>
|
|
@ -46,10 +46,8 @@
|
|||
app:title="@string/privacy_policy" />
|
||||
|
||||
<Preference
|
||||
app:key="ossLicenses"
|
||||
app:title="@string/open_source_licenses" />
|
||||
|
||||
<dev.jdtech.jellyfin.utils.VersionPreference app:title="@string/version" />
|
||||
app:key="appInfo"
|
||||
app:title="@string/app_info" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue