Add sorting options to library (collection)
This commit is contained in:
parent
11ff3b4e16
commit
aa1ef5ca5b
8 changed files with 210 additions and 11 deletions
|
@ -0,0 +1,87 @@
|
||||||
|
package dev.jdtech.jellyfin.dialogs
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import dev.jdtech.jellyfin.R
|
||||||
|
import dev.jdtech.jellyfin.utils.SortBy
|
||||||
|
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
|
import java.lang.IllegalStateException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class SortDialogFragment(
|
||||||
|
private val parentId: UUID,
|
||||||
|
private val libraryType: String?,
|
||||||
|
private val viewModel: LibraryViewModel,
|
||||||
|
private val sortType: String
|
||||||
|
) : DialogFragment() {
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
return activity?.let {
|
||||||
|
val sp = PreferenceManager.getDefaultSharedPreferences(it.applicationContext)
|
||||||
|
val builder = MaterialAlertDialogBuilder(it)
|
||||||
|
|
||||||
|
// Current sort by
|
||||||
|
val currentSortByString = sp.getString("sortBy", SortBy.defaultValue.name)!!
|
||||||
|
val currentSortBy = SortBy.fromString(currentSortByString)
|
||||||
|
|
||||||
|
// Current sort order
|
||||||
|
val currentSortOrderString = sp.getString("sortOrder", SortOrder.ASCENDING.name)!!
|
||||||
|
val currentSortOrder = try {
|
||||||
|
SortOrder.valueOf(currentSortOrderString)
|
||||||
|
} catch (e: java.lang.IllegalArgumentException) {
|
||||||
|
SortOrder.ASCENDING
|
||||||
|
}
|
||||||
|
|
||||||
|
when (sortType) {
|
||||||
|
"sortBy" -> {
|
||||||
|
val sortByOptions = resources.getStringArray(R.array.sort_by_options)
|
||||||
|
val sortByValues = SortBy.values()
|
||||||
|
builder
|
||||||
|
.setTitle(resources.getString(R.string.sort_by))
|
||||||
|
.setSingleChoiceItems(
|
||||||
|
sortByOptions, currentSortBy.ordinal
|
||||||
|
) { dialog, which ->
|
||||||
|
sp.edit().putString("sortBy", sortByValues[which].name).apply()
|
||||||
|
viewModel.loadItems(
|
||||||
|
parentId,
|
||||||
|
libraryType,
|
||||||
|
sortBy = sortByValues[which],
|
||||||
|
sortOrder = currentSortOrder
|
||||||
|
)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sortOrder" -> {
|
||||||
|
val sortByOptions = resources.getStringArray(R.array.sort_order_options)
|
||||||
|
val sortOrderValues = SortOrder.values()
|
||||||
|
|
||||||
|
builder
|
||||||
|
.setTitle(resources.getString(R.string.sort_order))
|
||||||
|
.setSingleChoiceItems(
|
||||||
|
sortByOptions, currentSortOrder.ordinal
|
||||||
|
) { dialog, which ->
|
||||||
|
sp.edit().putString("sortOrder", sortOrderValues[which].name).apply()
|
||||||
|
|
||||||
|
val sortOrder = try {
|
||||||
|
sortOrderValues[which]
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
SortOrder.ASCENDING
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loadItems(
|
||||||
|
parentId,
|
||||||
|
libraryType,
|
||||||
|
sortBy = currentSortBy,
|
||||||
|
sortOrder = sortOrder
|
||||||
|
)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.create()
|
||||||
|
} ?: throw IllegalStateException("Activity cannot be null")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
package dev.jdtech.jellyfin.fragments
|
package dev.jdtech.jellyfin.fragments
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.*
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
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
|
||||||
|
@ -14,8 +13,13 @@ 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 dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
|
||||||
|
import dev.jdtech.jellyfin.dialogs.SortDialogFragment
|
||||||
|
import dev.jdtech.jellyfin.utils.SortBy
|
||||||
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
|
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class LibraryFragment : Fragment() {
|
class LibraryFragment : Fragment() {
|
||||||
|
@ -25,6 +29,39 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
private val args: LibraryFragmentArgs by navArgs()
|
private val args: LibraryFragmentArgs by navArgs()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var sp: SharedPreferences
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
|
inflater.inflate(R.menu.library_menu, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
return when (item.itemId) {
|
||||||
|
R.id.action_sort_by -> {
|
||||||
|
SortDialogFragment(args.libraryId, args.libraryType, viewModel, "sortBy").show(
|
||||||
|
parentFragmentManager,
|
||||||
|
"sortdialog"
|
||||||
|
)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.action_sort_order -> {
|
||||||
|
SortDialogFragment(args.libraryId, args.libraryType, viewModel, "sortOrder").show(
|
||||||
|
parentFragmentManager,
|
||||||
|
"sortdialog"
|
||||||
|
)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
|
@ -56,7 +93,10 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.errorLayout.errorDetailsButton.setOnClickListener {
|
binding.errorLayout.errorDetailsButton.setOnClickListener {
|
||||||
ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(parentFragmentManager, "errordialog")
|
ErrorDialogFragment(viewModel.error.value ?: getString(R.string.unknown_error)).show(
|
||||||
|
parentFragmentManager,
|
||||||
|
"errordialog"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.finishedLoading.observe(viewLifecycleOwner, {
|
viewModel.finishedLoading.observe(viewLifecycleOwner, {
|
||||||
|
@ -67,7 +107,16 @@ class LibraryFragment : Fragment() {
|
||||||
ViewItemListAdapter(ViewItemListAdapter.OnClickListener { item ->
|
ViewItemListAdapter(ViewItemListAdapter.OnClickListener { item ->
|
||||||
navigateToMediaInfoFragment(item)
|
navigateToMediaInfoFragment(item)
|
||||||
})
|
})
|
||||||
viewModel.loadItems(args.libraryId, args.libraryType)
|
|
||||||
|
// Sorting options
|
||||||
|
val sortBy = SortBy.fromString(sp.getString("sortBy", SortBy.defaultValue.name)!!)
|
||||||
|
val sortOrder = try {
|
||||||
|
SortOrder.valueOf(sp.getString("sortOrder", SortOrder.ASCENDING.name)!!)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
SortOrder.ASCENDING
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loadItems(args.libraryId, args.libraryType, sortBy = sortBy, sortOrder = sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToMediaInfoFragment(item: BaseItemDto) {
|
private fun navigateToMediaInfoFragment(item: BaseItemDto) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.jdtech.jellyfin.repository
|
package dev.jdtech.jellyfin.repository
|
||||||
|
|
||||||
|
import dev.jdtech.jellyfin.utils.SortBy
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
import org.jellyfin.sdk.model.api.ItemFields
|
import org.jellyfin.sdk.model.api.ItemFields
|
||||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||||
|
@ -15,7 +16,7 @@ interface JellyfinRepository {
|
||||||
parentId: UUID? = null,
|
parentId: UUID? = null,
|
||||||
includeTypes: List<String>? = null,
|
includeTypes: List<String>? = null,
|
||||||
recursive: Boolean = false,
|
recursive: Boolean = false,
|
||||||
sortBy: String = "SortName",
|
sortBy: SortBy = SortBy.defaultValue,
|
||||||
sortOrder: SortOrder = SortOrder.ASCENDING
|
sortOrder: SortOrder = SortOrder.ASCENDING
|
||||||
): List<BaseItemDto>
|
): List<BaseItemDto>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package dev.jdtech.jellyfin.repository
|
package dev.jdtech.jellyfin.repository
|
||||||
|
|
||||||
import dev.jdtech.jellyfin.api.JellyfinApi
|
import dev.jdtech.jellyfin.api.JellyfinApi
|
||||||
|
import dev.jdtech.jellyfin.utils.SortBy
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.jellyfin.sdk.model.api.*
|
import org.jellyfin.sdk.model.api.*
|
||||||
|
@ -29,17 +30,18 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
|
||||||
parentId: UUID?,
|
parentId: UUID?,
|
||||||
includeTypes: List<String>?,
|
includeTypes: List<String>?,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
sortBy: String,
|
sortBy: SortBy,
|
||||||
sortOrder: SortOrder
|
sortOrder: SortOrder
|
||||||
): List<BaseItemDto> {
|
): List<BaseItemDto> {
|
||||||
val items: List<BaseItemDto>
|
val items: List<BaseItemDto>
|
||||||
|
Timber.d("$sortBy $sortOrder")
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
items = jellyfinApi.itemsApi.getItems(
|
items = jellyfinApi.itemsApi.getItems(
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
parentId = parentId,
|
parentId = parentId,
|
||||||
includeItemTypes = includeTypes,
|
includeItemTypes = includeTypes,
|
||||||
recursive = recursive,
|
recursive = recursive,
|
||||||
sortBy = listOf(sortBy),
|
sortBy = listOf(sortBy.SortString),
|
||||||
sortOrder = listOf(sortOrder)
|
sortOrder = listOf(sortOrder)
|
||||||
).content.items ?: listOf()
|
).content.items ?: listOf()
|
||||||
}
|
}
|
||||||
|
|
22
app/src/main/java/dev/jdtech/jellyfin/utils/SortBy.kt
Normal file
22
app/src/main/java/dev/jdtech/jellyfin/utils/SortBy.kt
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package dev.jdtech.jellyfin.utils
|
||||||
|
|
||||||
|
enum class SortBy(val SortString: String) {
|
||||||
|
NAME("SortName"),
|
||||||
|
IMDB_RATING("CommunityRating"),
|
||||||
|
PARENTAL_RATING("CriticRating"),
|
||||||
|
DATE_ADDED("DateCreated"),
|
||||||
|
DATE_PLAYED("DatePlayed"),
|
||||||
|
RELEASE_DATE("PremiereDate");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val defaultValue = NAME
|
||||||
|
|
||||||
|
fun fromString(string: String): SortBy {
|
||||||
|
return try {
|
||||||
|
valueOf(string)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,10 @@ package dev.jdtech.jellyfin.viewmodels
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||||
|
import dev.jdtech.jellyfin.utils.SortBy
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -23,7 +25,12 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
|
||||||
private val _error = MutableLiveData<String>()
|
private val _error = MutableLiveData<String>()
|
||||||
val error: LiveData<String> = _error
|
val error: LiveData<String> = _error
|
||||||
|
|
||||||
fun loadItems(parentId: UUID, libraryType: String?) {
|
fun loadItems(
|
||||||
|
parentId: UUID,
|
||||||
|
libraryType: String?,
|
||||||
|
sortBy: SortBy = SortBy.defaultValue,
|
||||||
|
sortOrder: SortOrder = SortOrder.ASCENDING
|
||||||
|
) {
|
||||||
_error.value = null
|
_error.value = null
|
||||||
_finishedLoading.value = false
|
_finishedLoading.value = false
|
||||||
Timber.d("$libraryType")
|
Timber.d("$libraryType")
|
||||||
|
@ -37,7 +44,9 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
|
||||||
_items.value = jellyfinRepository.getItems(
|
_items.value = jellyfinRepository.getItems(
|
||||||
parentId,
|
parentId,
|
||||||
includeTypes = if (itemType != null) listOf(itemType) else null,
|
includeTypes = if (itemType != null) listOf(itemType) else null,
|
||||||
recursive = true
|
recursive = true,
|
||||||
|
sortBy = sortBy,
|
||||||
|
sortOrder = sortOrder
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
|
|
15
app/src/main/res/menu/library_menu.xml
Normal file
15
app/src/main/res/menu/library_menu.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<!--Icon is currently not used but maybe in the future?-->
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_sort_by"
|
||||||
|
android:title="@string/sort_by"
|
||||||
|
app:showAsAction="collapseActionView" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_sort_order"
|
||||||
|
android:title="@string/sort_order"
|
||||||
|
app:showAsAction="collapseActionView" />
|
||||||
|
</menu>
|
|
@ -56,11 +56,25 @@
|
||||||
<string name="privacy_policy">Privacy policy</string>
|
<string name="privacy_policy">Privacy policy</string>
|
||||||
<string name="app_info">App info</string>
|
<string name="app_info">App info</string>
|
||||||
<string name="unknown_error">Unknown error</string>
|
<string name="unknown_error">Unknown error</string>
|
||||||
<string name="search_hint">Search movies, shows, episodes...</string>
|
<string name="search_hint">Search movies, shows, episodes…</string>
|
||||||
<string name="select_audio_track">Select audio track</string>
|
<string name="select_audio_track">Select audio track</string>
|
||||||
<string name="select_subtile_track">Select subtitle track</string>
|
<string name="select_subtile_track">Select subtitle track</string>
|
||||||
<string name="mpv_player">MPV Player</string>
|
<string name="mpv_player">MPV Player</string>
|
||||||
<string name="mpv_player_summary">Use the experimental MPV Player to play videos. MPV has support for more video, audio and subtitle codecs.</string>
|
<string name="mpv_player_summary">Use the experimental MPV Player to play videos. MPV has support for more video, audio and subtitle codecs.</string>
|
||||||
<string name="force_software_decoding">Force software decoding</string>
|
<string name="force_software_decoding">Force software decoding</string>
|
||||||
<string name="force_software_decoding_summary">Disable hardware decoding and use software decoding. Can be useful if hardware decoding gives weird artifacts.</string>
|
<string name="force_software_decoding_summary">Disable hardware decoding and use software decoding. Can be useful if hardware decoding gives weird artifacts.</string>
|
||||||
|
<string name="sort_by">Sort by</string>
|
||||||
|
<string name="sort_order">Sort order</string>
|
||||||
|
<string-array name="sort_by_options">
|
||||||
|
<item>Name</item>
|
||||||
|
<item>IMDB Rating</item>
|
||||||
|
<item>Parental Rating</item>
|
||||||
|
<item>Date Added</item>
|
||||||
|
<item>Date Played</item>
|
||||||
|
<item>Release Date</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="sort_order_options">
|
||||||
|
<item>Ascending</item>
|
||||||
|
<item>Descending</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue