diff --git a/app/src/main/java/dev/jdtech/jellyfin/dialogs/SortDialogFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/dialogs/SortDialogFragment.kt new file mode 100644 index 00000000..95d3a4b2 --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/dialogs/SortDialogFragment.kt @@ -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") + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt index 739f00f9..f72442fe 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt @@ -1,10 +1,9 @@ package dev.jdtech.jellyfin.fragments +import android.content.SharedPreferences import android.os.Bundle +import android.view.* import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController 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.databinding.FragmentLibraryBinding 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 org.jellyfin.sdk.model.api.BaseItemDto +import org.jellyfin.sdk.model.api.SortOrder +import java.lang.IllegalArgumentException +import javax.inject.Inject @AndroidEntryPoint class LibraryFragment : Fragment() { @@ -25,6 +29,39 @@ class LibraryFragment : Fragment() { 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( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -56,7 +93,10 @@ class LibraryFragment : Fragment() { } 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, { @@ -67,7 +107,16 @@ class LibraryFragment : Fragment() { ViewItemListAdapter(ViewItemListAdapter.OnClickListener { 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) { diff --git a/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt index 4232dfa9..88a7ec1b 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt @@ -1,5 +1,6 @@ package dev.jdtech.jellyfin.repository +import dev.jdtech.jellyfin.utils.SortBy import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.ItemFields import org.jellyfin.sdk.model.api.MediaSourceInfo @@ -15,7 +16,7 @@ interface JellyfinRepository { parentId: UUID? = null, includeTypes: List? = null, recursive: Boolean = false, - sortBy: String = "SortName", + sortBy: SortBy = SortBy.defaultValue, sortOrder: SortOrder = SortOrder.ASCENDING ): List diff --git a/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt index 0c64b99a..90bd7e77 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt @@ -1,6 +1,7 @@ package dev.jdtech.jellyfin.repository import dev.jdtech.jellyfin.api.JellyfinApi +import dev.jdtech.jellyfin.utils.SortBy import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.jellyfin.sdk.model.api.* @@ -29,17 +30,18 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep parentId: UUID?, includeTypes: List?, recursive: Boolean, - sortBy: String, + sortBy: SortBy, sortOrder: SortOrder ): List { val items: List + Timber.d("$sortBy $sortOrder") withContext(Dispatchers.IO) { items = jellyfinApi.itemsApi.getItems( jellyfinApi.userId!!, parentId = parentId, includeItemTypes = includeTypes, recursive = recursive, - sortBy = listOf(sortBy), + sortBy = listOf(sortBy.SortString), sortOrder = listOf(sortOrder) ).content.items ?: listOf() } diff --git a/app/src/main/java/dev/jdtech/jellyfin/utils/SortBy.kt b/app/src/main/java/dev/jdtech/jellyfin/utils/SortBy.kt new file mode 100644 index 00000000..09164b7d --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/utils/SortBy.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/LibraryViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/LibraryViewModel.kt index 3f8ee64f..707e812c 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/LibraryViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/LibraryViewModel.kt @@ -3,8 +3,10 @@ package dev.jdtech.jellyfin.viewmodels import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel import dev.jdtech.jellyfin.repository.JellyfinRepository +import dev.jdtech.jellyfin.utils.SortBy import kotlinx.coroutines.launch import org.jellyfin.sdk.model.api.BaseItemDto +import org.jellyfin.sdk.model.api.SortOrder import timber.log.Timber import java.util.* import javax.inject.Inject @@ -23,7 +25,12 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { private val _error = MutableLiveData() val error: LiveData = _error - fun loadItems(parentId: UUID, libraryType: String?) { + fun loadItems( + parentId: UUID, + libraryType: String?, + sortBy: SortBy = SortBy.defaultValue, + sortOrder: SortOrder = SortOrder.ASCENDING + ) { _error.value = null _finishedLoading.value = false Timber.d("$libraryType") @@ -37,7 +44,9 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() { _items.value = jellyfinRepository.getItems( parentId, includeTypes = if (itemType != null) listOf(itemType) else null, - recursive = true + recursive = true, + sortBy = sortBy, + sortOrder = sortOrder ) } catch (e: Exception) { Timber.e(e) diff --git a/app/src/main/res/menu/library_menu.xml b/app/src/main/res/menu/library_menu.xml new file mode 100644 index 00000000..b425ffee --- /dev/null +++ b/app/src/main/res/menu/library_menu.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9c49f4db..5b381297 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -56,11 +56,25 @@ Privacy policy App info Unknown error - Search movies, shows, episodes... + Search movies, shows, episodes… Select audio track Select subtitle track MPV Player Use the experimental MPV Player to play videos. MPV has support for more video, audio and subtitle codecs. Force software decoding Disable hardware decoding and use software decoding. Can be useful if hardware decoding gives weird artifacts. + Sort by + Sort order + + Name + IMDB Rating + Parental Rating + Date Added + Date Played + Release Date + + + Ascending + Descending + \ No newline at end of file