commit
27265098ba
48 changed files with 605 additions and 397 deletions
|
@ -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'
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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, {
|
||||||
|
|
|
@ -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 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 {
|
||||||
}
|
binding.playerItemsError.visibility = View.GONE
|
||||||
false -> 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
binding.playButton.setImageDrawable(ContextCompat.getDrawable(requireActivity(), R.drawable.ic_play))
|
ContextCompat.getDrawable(
|
||||||
binding.progressCircular.visibility = View.INVISIBLE
|
requireActivity(),
|
||||||
}
|
R.drawable.ic_play
|
||||||
false -> binding.playerItemsError.visibility = View.GONE
|
)
|
||||||
|
)
|
||||||
|
binding.progressCircular.visibility = View.INVISIBLE
|
||||||
|
} else {
|
||||||
|
binding.playerItemsError.visibility = View.GONE
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
binding.playerItemsErrorDetails.setOnClickListener {
|
||||||
|
ErrorDialogFragment(viewModel.playerItemsError.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
|
||||||
|
}
|
||||||
|
|
||||||
binding.trailerButton.setOnClickListener {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.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 {
|
||||||
jellyfinApi.apply {
|
|
||||||
api.baseUrl = baseUrl
|
|
||||||
api.accessToken = null
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
val publicSystemInfo by jellyfinApi.systemApi.getPublicSystemInfo()
|
val candidates = jellyfinApi.jellyfin.discovery.getAddressCandidates(inputValue)
|
||||||
Timber.d("Remote server: ${publicSystemInfo.id}")
|
val recommended = jellyfinApi.jellyfin.discovery.getRecommendedServers(
|
||||||
|
candidates,
|
||||||
|
RecommendedServerInfoScore.GOOD
|
||||||
|
)
|
||||||
|
val recommendedServer: RecommendedServerInfo
|
||||||
|
|
||||||
if (serverAlreadyInDatabase(publicSystemInfo.id)) {
|
try {
|
||||||
|
recommendedServer = recommended.first()
|
||||||
|
} catch (e: NoSuchElementException) {
|
||||||
|
throw Exception("Server not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
jellyfinApi.apply {
|
||||||
|
api.baseUrl = recommendedServer.address
|
||||||
|
api.accessToken = null
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.d("Remote server: ${recommendedServer.systemInfo?.id}")
|
||||||
|
|
||||||
|
if (serverAlreadyInDatabase(recommendedServer.systemInfo?.id)) {
|
||||||
_error.value = "Server already added"
|
_error.value = "Server already added"
|
||||||
_navigateToLogin.value = false
|
_navigateToLogin.value = false
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
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"?>
|
<?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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
app:layout_constraintTop_toBottomOf="@id/buttons"
|
android:orientation="horizontal"
|
||||||
android:text="@string/error_preparing_player_items"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
|
||||||
android:textColor="@color/red"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
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" />
|
||||||
|
|
||||||
|
<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"
|
||||||
|
|
|
@ -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>
|
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" />
|
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>
|
|
@ -10,43 +10,39 @@
|
||||||
type="dev.jdtech.jellyfin.viewmodels.HomeViewModel" />
|
type="dev.jdtech.jellyfin.viewmodels.HomeViewModel" />
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
android:id="@+id/main_layout"
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".fragments.HomeFragment">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/loading_indicator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:trackCornerRadius="10dp" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".fragments.HomeFragment">
|
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/loading_indicator"
|
android:id="@+id/views_recycler_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:indeterminate="true"
|
android:clipToPadding="false"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:paddingTop="16dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:trackCornerRadius="10dp" />
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
app:views="@{viewModel.views}"
|
||||||
android:id="@+id/views_recycler_view"
|
tools:itemCount="4"
|
||||||
android:layout_width="0dp"
|
tools:listitem="@layout/view_item" />
|
||||||
android:layout_height="0dp"
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
android:clipToPadding="false"
|
|
||||||
android:paddingTop="16dp"
|
|
||||||
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:views="@{viewModel.views}"
|
|
||||||
tools:itemCount="4"
|
|
||||||
tools:listitem="@layout/view_item" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -10,47 +10,43 @@
|
||||||
type="dev.jdtech.jellyfin.viewmodels.LibraryViewModel" />
|
type="dev.jdtech.jellyfin.viewmodels.LibraryViewModel" />
|
||||||
</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"
|
||||||
|
tools:context=".fragments.LibraryFragment">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/loading_indicator"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="wrap_content"
|
||||||
tools:context=".fragments.LibraryFragment">
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:trackCornerRadius="10dp" />
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
|
||||||
android:id="@+id/loading_indicator"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:trackCornerRadius="10dp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/items_recycler_view"
|
android:id="@+id/items_recycler_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingHorizontal="12dp"
|
android:paddingHorizontal="12dp"
|
||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
app:items="@{viewModel.items}"
|
app:items="@{viewModel.items}"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
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:spanCount="@integer/library_columns"
|
app:spanCount="@integer/library_columns"
|
||||||
tools:itemCount="6"
|
tools:itemCount="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>
|
||||||
|
|
||||||
|
|
|
@ -10,46 +10,42 @@
|
||||||
type="dev.jdtech.jellyfin.viewmodels.MediaViewModel" />
|
type="dev.jdtech.jellyfin.viewmodels.MediaViewModel" />
|
||||||
</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"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
tools:context=".fragments.MediaFragment">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/loading_indicator"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:animateLayoutChanges="true"
|
android:layout_height="wrap_content"
|
||||||
tools:context=".fragments.MediaFragment">
|
android:indeterminate="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:trackCornerRadius="10dp" />
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
|
||||||
android:id="@+id/loading_indicator"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:trackCornerRadius="10dp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/views_recycler_view"
|
android:id="@+id/views_recycler_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingHorizontal="12dp"
|
android:paddingHorizontal="12dp"
|
||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
app:collections="@{viewModel.collections}"
|
app:collections="@{viewModel.collections}"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
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:spanCount="@integer/collection_columns"
|
app:spanCount="@integer/collection_columns"
|
||||||
tools:itemCount="4"
|
tools:itemCount="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>
|
||||||
|
|
|
@ -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:text="@string/error_preparing_player_items"
|
android:orientation="horizontal"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
|
||||||
android:textColor="@color/red"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
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" />
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
|
@ -4,60 +4,55 @@
|
||||||
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
|
<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">
|
||||||
tools:context=".fragments.FavoriteFragment">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/loading_indicator"
|
||||||
android:layout_height="match_parent">
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:trackCornerRadius="10dp" />
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
<include android:id="@+id/error_layout" layout="@layout/error_panel" />
|
||||||
android:id="@+id/loading_indicator"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:trackCornerRadius="10dp" />
|
|
||||||
|
|
||||||
<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"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:visibility="gone"/>
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/search_results_recycler_view"
|
android:id="@+id/search_results_recycler_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:favoriteSections="@{viewModel.sections}"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:favoriteSections="@{viewModel.sections}"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
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="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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue