Merge pull request #19 from jarnedemeulemeester/develop

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

View file

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

View file

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

View file

@ -51,9 +51,10 @@ class MainActivity : AppCompatActivity() {
navController.addOnDestinationChangedListener { _, destination, _ ->
binding.navView.visibility = when (destination.id) {
R.id.settingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment -> View.GONE
R.id.settingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, R.id.about_libraries_dest -> View.GONE
else -> View.VISIBLE
}
if (destination.id == R.id.about_libraries_dest) binding.mainToolbar.title = getString(R.string.app_info)
}
viewModel.navigateToAddServer.observe(this, {

View file

@ -0,0 +1,33 @@
package dev.jdtech.jellyfin.dialogs
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.jdtech.jellyfin.R
import java.lang.IllegalStateException
class ErrorDialogFragment(private val errorMessage: String) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = MaterialAlertDialogBuilder(it, R.style.ErrorDialogStyle)
builder
.setMessage(errorMessage)
.setPositiveButton("close") { _, _ ->
}
.setNeutralButton("share") { _, _ ->
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, errorMessage)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
startActivity(shareIntent)
}
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}

View file

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

View file

@ -7,13 +7,13 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.databinding.FragmentFavoriteBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.viewmodels.FavoriteViewModel
import org.jellyfin.sdk.model.api.BaseItemDto
@ -29,16 +29,6 @@ class FavoriteFragment : Fragment() {
): View {
binding = FragmentFavoriteBinding.inflate(inflater, container, false)
val snackbar =
Snackbar.make(
binding.mainLayout,
getString(R.string.error_loading_data),
Snackbar.LENGTH_INDEFINITE
)
snackbar.setAction(getString(R.string.retry)) {
viewModel.loadData()
}
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
binding.favoritesRecyclerView.adapter = FavoritesListAdapter(
@ -53,11 +43,23 @@ class FavoriteFragment : Fragment() {
})
viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) {
snackbar.show()
if (error != null) {
binding.errorLayout.errorPanel.visibility = View.VISIBLE
binding.favoritesRecyclerView.visibility = View.GONE
} else {
binding.errorLayout.errorPanel.visibility = View.GONE
binding.favoritesRecyclerView.visibility = View.VISIBLE
}
})
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadData()
}
binding.errorLayout.errorDetailsButton.setOnClickListener {
ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
}
viewModel.favoriteSections.observe(viewLifecycleOwner, { sections ->
if (sections.isEmpty()) {
binding.noFavoritesText.visibility = View.VISIBLE

View file

@ -5,13 +5,13 @@ import android.view.*
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.adapters.ViewListAdapter
import dev.jdtech.jellyfin.databinding.FragmentHomeBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.viewmodels.HomeViewModel
import org.jellyfin.sdk.model.api.BaseItemDto
@ -49,12 +49,6 @@ class HomeFragment : Fragment() {
): View {
binding = FragmentHomeBinding.inflate(inflater, container, false)
val snackbar =
Snackbar.make(binding.mainLayout, getString(R.string.error_loading_data), Snackbar.LENGTH_INDEFINITE)
snackbar.setAction(getString(R.string.retry)) {
viewModel.loadData()
}
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
binding.viewsRecyclerView.adapter = ViewListAdapter(ViewListAdapter.OnClickListener {
@ -78,11 +72,23 @@ class HomeFragment : Fragment() {
})
viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) {
snackbar.show()
if (error != null) {
binding.errorLayout.errorPanel.visibility = View.VISIBLE
binding.viewsRecyclerView.visibility = View.GONE
} else {
binding.errorLayout.errorPanel.visibility = View.GONE
binding.viewsRecyclerView.visibility = View.VISIBLE
}
})
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadData()
}
binding.errorLayout.errorDetailsButton.setOnClickListener {
ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
}
return binding.root
}

View file

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

View file

@ -6,11 +6,11 @@ import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.CollectionListAdapter
import dev.jdtech.jellyfin.databinding.FragmentMediaBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.viewmodels.MediaViewModel
import org.jellyfin.sdk.model.api.BaseItemDto
@ -53,16 +53,6 @@ class MediaFragment : Fragment() {
): View {
binding = FragmentMediaBinding.inflate(inflater, container, false)
val snackbar =
Snackbar.make(
binding.mainLayout,
getString(R.string.error_loading_data),
Snackbar.LENGTH_INDEFINITE
)
snackbar.setAction(getString(R.string.retry)) {
viewModel.loadData()
}
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
binding.viewsRecyclerView.adapter =
@ -75,11 +65,23 @@ class MediaFragment : Fragment() {
})
viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) {
snackbar.show()
if (error != null) {
binding.errorLayout.errorPanel.visibility = View.VISIBLE
binding.viewsRecyclerView.visibility = View.GONE
} else {
binding.errorLayout.errorPanel.visibility = View.GONE
binding.viewsRecyclerView.visibility = View.VISIBLE
}
})
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadData()
}
binding.errorLayout.errorDetailsButton.setOnClickListener {
ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
}
return binding.root
}

View file

@ -11,12 +11,12 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.PersonListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.databinding.FragmentMediaInfoBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.dialogs.VideoVersionDialogFragment
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel
@ -44,24 +44,26 @@ class MediaInfoFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val snackbar =
Snackbar.make(
binding.mainLayout,
getString(R.string.error_loading_data),
Snackbar.LENGTH_INDEFINITE
)
snackbar.setAction(getString(R.string.retry)) {
viewModel.loadData(args.itemId, args.itemType)
}
binding.viewModel = viewModel
viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) {
snackbar.show()
if (error != null) {
binding.errorLayout.errorPanel.visibility = View.VISIBLE
binding.mediaInfoScrollview.visibility = View.GONE
} else {
binding.errorLayout.errorPanel.visibility = View.GONE
binding.mediaInfoScrollview.visibility = View.VISIBLE
}
})
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadData(args.itemId, args.itemType)
}
binding.errorLayout.errorDetailsButton.setOnClickListener {
ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
}
viewModel.item.observe(viewLifecycleOwner, { item ->
if (item.originalTitle != item.name) {
binding.originalTitle.visibility = View.VISIBLE
@ -91,7 +93,12 @@ class MediaInfoFragment : Fragment() {
viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000)
)
viewModel.doneNavigatingToPlayer()
binding.playButton.setImageDrawable(ContextCompat.getDrawable(requireActivity(), R.drawable.ic_play))
binding.playButton.setImageDrawable(
ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_play
)
)
binding.progressCircular.visibility = View.INVISIBLE
}
})
@ -114,17 +121,25 @@ class MediaInfoFragment : Fragment() {
binding.favoriteButton.setImageResource(drawable)
})
viewModel.playerItemsError.observe(viewLifecycleOwner, {
when (it) {
true -> {
viewModel.playerItemsError.observe(viewLifecycleOwner, { errorMessage ->
if (errorMessage != null) {
binding.playerItemsError.visibility = View.VISIBLE
binding.playButton.setImageDrawable(ContextCompat.getDrawable(requireActivity(), R.drawable.ic_play))
binding.playButton.setImageDrawable(
ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_play
)
)
binding.progressCircular.visibility = View.INVISIBLE
}
false -> binding.playerItemsError.visibility = View.GONE
} else {
binding.playerItemsError.visibility = View.GONE
}
})
binding.playerItemsErrorDetails.setOnClickListener {
ErrorDialogFragment(viewModel.playerItemsError.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
}
binding.trailerButton.setOnClickListener {
val intent = Intent(
Intent.ACTION_VIEW,
@ -155,10 +170,20 @@ class MediaInfoFragment : Fragment() {
)
} else {
navigateToPlayerActivity(
arrayOf(PlayerItem(args.itemId, viewModel.mediaSources.value!![0].id!!)),
arrayOf(
PlayerItem(
args.itemId,
viewModel.mediaSources.value!![0].id!!
)
),
viewModel.item.value!!.userData!!.playbackPositionTicks.div(10000),
)
binding.playButton.setImageDrawable(ContextCompat.getDrawable(requireActivity(), R.drawable.ic_play))
binding.playButton.setImageDrawable(
ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_play
)
)
binding.progressCircular.visibility = View.INVISIBLE
}
}

View file

@ -8,13 +8,13 @@ import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.databinding.FragmentSearchResultBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.viewmodels.SearchResultViewModel
import org.jellyfin.sdk.model.api.BaseItemDto
@ -32,16 +32,6 @@ class SearchResultFragment : Fragment() {
): View {
binding = FragmentSearchResultBinding.inflate(inflater, container, false)
val snackbar =
Snackbar.make(
binding.mainLayout,
getString(R.string.error_loading_data),
Snackbar.LENGTH_INDEFINITE
)
snackbar.setAction(getString(R.string.retry)) {
viewModel.loadData(args.query)
}
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
binding.searchResultsRecyclerView.adapter = FavoritesListAdapter(
@ -56,11 +46,23 @@ class SearchResultFragment : Fragment() {
})
viewModel.error.observe(viewLifecycleOwner, { error ->
if (error) {
snackbar.show()
if (error != null) {
binding.errorLayout.errorPanel.visibility = View.VISIBLE
binding.searchResultsRecyclerView.visibility = View.GONE
} else {
binding.errorLayout.errorPanel.visibility = View.GONE
binding.searchResultsRecyclerView.visibility = View.VISIBLE
}
})
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadData(args.query)
}
binding.errorLayout.errorDetailsButton.setOnClickListener {
ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
}
viewModel.sections.observe(viewLifecycleOwner, { sections ->
if (sections.isEmpty()) {
binding.noSearchResultsText.visibility = View.VISIBLE

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +0,0 @@
package dev.jdtech.jellyfin.utils
import android.content.Context
import android.util.AttributeSet
import androidx.preference.Preference
class VersionPreference(context: Context, attrs: AttributeSet): Preference(context, attrs) {
init {
val packageManager = context.packageManager
val packageInfo = packageManager.getPackageInfo(context.packageName, 0)
val versionName = packageInfo.versionName
summary = versionName
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -82,5 +82,13 @@
app:layout_constraintStart_toStartOf="@id/episode_title"
app:layout_constraintTop_toBottomOf="@id/episode_title"
tools:text="After one hundred years of peace, humanity is suddenly reminded of the terror of being at the Titans' mercy. After one hundred years of peace, humanity is suddenly reminded of the terror of being at the Titans' mercy." />
<FrameLayout
android:layout_width="0dp"
android:layout_height="24dp"
android:background="@drawable/header_gradient"
app:layout_constraintBottom_toBottomOf="@id/episode_desc"
app:layout_constraintEnd_toEndOf="@id/episode_desc"
app:layout_constraintStart_toStartOf="@id/episode_desc" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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