From 98b729568bf859dfec49fdd650efd64d5f79780b Mon Sep 17 00:00:00 2001 From: Nicolas Pomepuy Date: Tue, 25 Mar 2025 15:02:42 +0100 Subject: [PATCH] Implement the display settings in the HeaderMediaListActivity Fixes #3180 --- .../src/main/res/drawable/ic_sort_track.xml | 37 +++++++++ .../res/layout/dialog_display_settings.xml | 66 +++++++++++++-- .../vlc-android/res/menu/playlist_option.xml | 54 ++----------- .../vlc/gui/HeaderMediaListActivity.kt | 81 +++++++++---------- .../vlc/gui/dialogs/DisplaySettingsDialog.kt | 55 ++++++++++--- .../viewmodels/mobile/PlaylistViewModel.kt | 4 +- 6 files changed, 189 insertions(+), 108 deletions(-) create mode 100644 application/resources/src/main/res/drawable/ic_sort_track.xml diff --git a/application/resources/src/main/res/drawable/ic_sort_track.xml b/application/resources/src/main/res/drawable/ic_sort_track.xml new file mode 100644 index 000000000..90e160e0d --- /dev/null +++ b/application/resources/src/main/res/drawable/ic_sort_track.xml @@ -0,0 +1,37 @@ + + + + + + + diff --git a/application/vlc-android/res/layout/dialog_display_settings.xml b/application/vlc-android/res/layout/dialog_display_settings.xml index a4b4ca95e..6b1c670d2 100644 --- a/application/vlc-android/res/layout/dialog_display_settings.xml +++ b/application/vlc-android/res/layout/dialog_display_settings.xml @@ -102,7 +102,8 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/display_mode_group" /> + app:layout_constraintTop_toBottomOf="@+id/display_mode_group" + app:layout_goneMarginTop="16dp" /> + app:layout_constraintTop_toBottomOf="@+id/show_all_artist_group" + app:layout_goneMarginTop="16dp" /> + + + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/show_track_numbers_group" + app:layout_goneMarginTop="16dp" /> + app:layout_constraintTop_toBottomOf="@+id/show_hidden_files_group" + app:layout_goneMarginTop="16dp" /> + app:layout_constraintTop_toBottomOf="@+id/only_favs_group" + app:layout_goneMarginTop="16dp" /> + app:layout_constraintTop_toBottomOf="@+id/video_groups_group" + app:layout_goneMarginTop="24dp" /> + app:layout_constraintTop_toTopOf="@+id/default_actions_group" /> - - - - - - - - - - - + android:title="@string/display_settings" + android:visible="false" + app:showAsAction="ifRoom|collapseActionView" /> \ No newline at end of file diff --git a/application/vlc-android/src/org/videolan/vlc/gui/HeaderMediaListActivity.kt b/application/vlc-android/src/org/videolan/vlc/gui/HeaderMediaListActivity.kt index d802ec1cc..dde42b30d 100644 --- a/application/vlc-android/src/org/videolan/vlc/gui/HeaderMediaListActivity.kt +++ b/application/vlc-android/src/org/videolan/vlc/gui/HeaderMediaListActivity.kt @@ -76,11 +76,15 @@ import org.videolan.vlc.gui.audio.AudioBrowserFragment import org.videolan.vlc.gui.dialogs.CONFIRM_DELETE_DIALOG_MEDIALIST import org.videolan.vlc.gui.dialogs.CONFIRM_DELETE_DIALOG_RESULT import org.videolan.vlc.gui.dialogs.CONFIRM_RENAME_DIALOG_RESULT +import org.videolan.vlc.gui.dialogs.CURRENT_SORT import org.videolan.vlc.gui.dialogs.ConfirmDeleteDialog import org.videolan.vlc.gui.dialogs.CtxActionReceiver +import org.videolan.vlc.gui.dialogs.DEFAULT_ACTIONS +import org.videolan.vlc.gui.dialogs.DisplaySettingsDialog import org.videolan.vlc.gui.dialogs.RENAME_DIALOG_MEDIA import org.videolan.vlc.gui.dialogs.RENAME_DIALOG_NEW_NAME import org.videolan.vlc.gui.dialogs.RenameDialog +import org.videolan.vlc.gui.dialogs.SHOW_TRACK_NUMBER import org.videolan.vlc.gui.dialogs.SavePlaylistDialog import org.videolan.vlc.gui.dialogs.showContext import org.videolan.vlc.gui.helpers.AudioUtil @@ -332,17 +336,8 @@ open class HeaderMediaListActivity : AudioPlayerContainerActivity(), IEventsHand super.onCreateOptionsMenu(menu) menuInflater.inflate(R.menu.playlist_option, menu) if (!isPlaylist) { - menu.findItem(R.id.ml_menu_sortby).isVisible = true - val showTrackNumber = menu.findItem(R.id.ml_menu_albums_show_track_numbers) - showTrackNumber.isVisible = true - showTrackNumber.isChecked = Settings.showTrackNumber + menu.findItem(R.id.ml_menu_display_options).isVisible = true } - menu.findItem(R.id.ml_menu_sortby).isVisible = viewModel.canSortByName() - menu.findItem(R.id.ml_menu_sortby_filename).isVisible = viewModel.canSortByFileNameName() - menu.findItem(R.id.ml_menu_sortby_artist_name).isVisible = viewModel.canSortByArtist() - menu.findItem(R.id.ml_menu_sortby_length).isVisible = viewModel.canSortByDuration() - menu.findItem(R.id.ml_menu_sortby_date).isVisible = viewModel.canSortByReleaseDate() - menu.findItem(R.id.ml_menu_sortby_last_modified).isVisible = viewModel.canSortByLastModified() val searchItem = menu.findItem(R.id.ml_menu_filter) searchView = searchItem.actionView as SearchView searchView.queryHint = getString(R.string.search_in_list_hint) @@ -362,44 +357,46 @@ open class HeaderMediaListActivity : AudioPlayerContainerActivity(), IEventsHand override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.ml_menu_sortby_track -> { - viewModel.sort(Medialibrary.TrackId) - return true - } - R.id.ml_menu_sortby_filename -> { - viewModel.sort(Medialibrary.SORT_FILENAME) - return true - } - R.id.ml_menu_sortby_length -> { - viewModel.sort(Medialibrary.SORT_DURATION) - return true - } - R.id.ml_menu_sortby_date -> { - viewModel.sort(Medialibrary.SORT_RELEASEDATE) - return true - } - R.id.ml_menu_sortby_last_modified -> { - viewModel.sort(Medialibrary.SORT_LASTMODIFICATIONDATE) - return true - } - R.id.ml_menu_sortby_artist_name -> { - viewModel.sort(Medialibrary.SORT_ARTIST) + R.id.ml_menu_display_options -> { + //filter all sorts and keep only applicable ones + val sorts = arrayListOf(Medialibrary.TrackId, Medialibrary.SORT_ALPHA, Medialibrary.SORT_FILENAME, Medialibrary.SORT_ARTIST, Medialibrary.SORT_ALBUM, Medialibrary.SORT_DURATION, Medialibrary.SORT_RELEASEDATE, Medialibrary.SORT_LASTMODIFICATIONDATE, Medialibrary.SORT_FILESIZE, Medialibrary.NbMedia).filter { + viewModel.canSortBy(it) + } + //Open the display settings Bottom sheet + DisplaySettingsDialog.newInstance( + displayInCards = null, + onlyFavs = null, + sorts = sorts, + showTrackNumber = Settings.showTrackNumber, + currentSort = viewModel.tracksProvider.sort, + currentSortDesc = viewModel.tracksProvider.desc, + defaultPlaybackActions = DefaultPlaybackActionMediaType.TRACK.getDefaultPlaybackActions(Settings.getInstance(this)), + defaultActionType = getString(DefaultPlaybackActionMediaType.TRACK.title) + ) + .show(supportFragmentManager, "DisplaySettingsDialog") return true } - R.id.ml_menu_sortby_album_name -> { - viewModel.sort(Medialibrary.SORT_ALBUM) - return true + else -> return super.onOptionsItemSelected(item) + } + } + + override fun onDisplaySettingChanged(key: String, value: Any) { + when (key) { + CURRENT_SORT -> { + @Suppress("UNCHECKED_CAST") val sort = value as Pair + viewModel.desc = sort.second + viewModel.sort(sort.first) } - R.id.ml_menu_albums_show_track_numbers -> { - item.isChecked = !Settings.getInstance(this).getBoolean( - ALBUMS_SHOW_TRACK_NUMBER, true) - Settings.getInstance(this).putSingle(ALBUMS_SHOW_TRACK_NUMBER, item.isChecked) - Settings.showTrackNumber = item.isChecked + SHOW_TRACK_NUMBER -> { + val checked = value as Boolean + Settings.getInstance(this).putSingle(ALBUMS_SHOW_TRACK_NUMBER, checked) + Settings.showTrackNumber = checked audioBrowserAdapter.notifyDataSetChanged() viewModel.refresh() - return true } - else -> return super.onOptionsItemSelected(item) + DEFAULT_ACTIONS -> { + Settings.getInstance(this).putSingle(DefaultPlaybackActionMediaType.TRACK.defaultActionKey, (value as DefaultPlaybackAction).name) + } } } diff --git a/application/vlc-android/src/org/videolan/vlc/gui/dialogs/DisplaySettingsDialog.kt b/application/vlc-android/src/org/videolan/vlc/gui/dialogs/DisplaySettingsDialog.kt index a1e4aeab6..d837cec40 100644 --- a/application/vlc-android/src/org/videolan/vlc/gui/dialogs/DisplaySettingsDialog.kt +++ b/application/vlc-android/src/org/videolan/vlc/gui/dialogs/DisplaySettingsDialog.kt @@ -68,6 +68,7 @@ const val CURRENT_SORT = "current_sort" const val CURRENT_SORT_DESC = "current_sort_desc" const val SHOW_ONLY_MULTIMEDIA_FILES = "show_only_multimedia_files" const val SHOW_HIDDEN_FILES = "show_hidden_files" +const val SHOW_TRACK_NUMBER = "show_track_number" const val DEFAULT_ACTIONS = "default_actions" const val DEFAULT_ACTION_TYPE = "default_action_type" @@ -77,7 +78,7 @@ const val DEFAULT_ACTION_TYPE = "default_action_type" class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { //current values - private var displayInCards: Boolean = false + private var displayInCards: Boolean? = null private var onlyFavs: Boolean? = null private lateinit var sorts: ArrayList private var currentSort: Int = -1 @@ -85,6 +86,7 @@ class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { private var showAllArtists: Boolean? = null private var showOnlyMultimediaFiles: Boolean? = null private var showHiddenFiles: Boolean? = null + private var showTrackNumbers: Boolean? = null private var showVideoGroups: String? = null private var defaultPlaybackActions: List? = null private var defaultActionType: String? = null @@ -95,12 +97,14 @@ class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { companion object { - fun newInstance(displayInCards: Boolean, showAllArtists: Boolean? = null, onlyFavs: Boolean?, sorts: List, currentSort: Int, currentSortDesc: Boolean, videoGroup: String? = null, showOnlyMultimediaFiles:Boolean? = null, showHiddenFiles:Boolean? = null, defaultPlaybackActions: List? = null, defaultActionType: String? = null): DisplaySettingsDialog { + fun newInstance(displayInCards: Boolean?, showAllArtists: Boolean? = null, onlyFavs: Boolean?, sorts: List, currentSort: Int, currentSortDesc: Boolean, videoGroup: String? = null, showOnlyMultimediaFiles:Boolean? = null, showTrackNumber:Boolean? = null, showHiddenFiles:Boolean? = null, defaultPlaybackActions: List? = null, defaultActionType: String? = null): DisplaySettingsDialog { return DisplaySettingsDialog().apply { - arguments = bundleOf(DISPLAY_IN_CARDS to displayInCards, SORTS to sorts, CURRENT_SORT to currentSort, CURRENT_SORT_DESC to currentSortDesc, VIDEO_GROUPING to videoGroup) + arguments = bundleOf(SORTS to sorts, CURRENT_SORT to currentSort, CURRENT_SORT_DESC to currentSortDesc, VIDEO_GROUPING to videoGroup) + if (displayInCards != null) arguments!!.putBoolean(DISPLAY_IN_CARDS, displayInCards) if (onlyFavs != null) arguments!!.putBoolean(ONLY_FAVS, onlyFavs) if (showAllArtists != null) arguments!!.putBoolean(SHOW_ALL_ARTISTS, showAllArtists) if (showOnlyMultimediaFiles != null) arguments!!.putBoolean(SHOW_ONLY_MULTIMEDIA_FILES, showOnlyMultimediaFiles) + if (showTrackNumber != null) arguments!!.putBoolean(SHOW_TRACK_NUMBER, showTrackNumber) if (showHiddenFiles != null) arguments!!.putBoolean(SHOW_HIDDEN_FILES, showHiddenFiles) if (defaultPlaybackActions != null) arguments!!.putStringArrayList(DEFAULT_ACTIONS, ArrayList(defaultPlaybackActions.map { it.name })) if(defaultActionType != null) arguments!!.putString(DEFAULT_ACTION_TYPE, defaultActionType) @@ -121,8 +125,7 @@ class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { if (requireActivity().showPinIfNeeded()) dismiss() } super.onCreate(savedInstanceState) - displayInCards = arguments?.getBoolean(DISPLAY_IN_CARDS) - ?: throw IllegalStateException("Display in list should be provided") + displayInCards =if (arguments?.containsKey(DISPLAY_IN_CARDS) == true) arguments?.getBoolean(DISPLAY_IN_CARDS) else null onlyFavs = if (arguments?.containsKey(ONLY_FAVS) == true) arguments?.getBoolean(ONLY_FAVS) else null sorts = arguments?.getIntegerArrayList(SORTS) ?: throw IllegalStateException("Sorts should be provided") @@ -132,6 +135,7 @@ class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { ?: throw IllegalStateException("Current sort desc should be provided") showAllArtists = if (arguments?.containsKey(SHOW_ALL_ARTISTS) == true) arguments?.getBoolean(SHOW_ALL_ARTISTS) else null showOnlyMultimediaFiles = if (arguments?.containsKey(SHOW_ONLY_MULTIMEDIA_FILES) == true) arguments?.getBoolean(SHOW_ONLY_MULTIMEDIA_FILES) else null + showTrackNumbers = if (arguments?.containsKey(SHOW_TRACK_NUMBER) == true) arguments?.getBoolean(SHOW_TRACK_NUMBER) else null showHiddenFiles = if (arguments?.containsKey(SHOW_HIDDEN_FILES) == true) arguments?.getBoolean(SHOW_HIDDEN_FILES) else null showVideoGroups = arguments?.getString(VIDEO_GROUPING, null) val defaultActionsKeys = arguments?.getStringArrayList(DEFAULT_ACTIONS)?.toTypedArray() @@ -153,14 +157,15 @@ class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { updateShowAllArtists() updateShowOnlyFavs() updateShowAllFiles() + updateShowTrackNumbers() updateShowHiddenFiles() updateSorts() updateDefaultActions() binding.displayModeGroup.setOnClickListener { - displayInCards = !displayInCards + displayInCards = !displayInCards!! updateDisplayMode() - lifecycleScope.launch { displaySettingsViewModel.send(DISPLAY_IN_CARDS, displayInCards) } + lifecycleScope.launch { displaySettingsViewModel.send(DISPLAY_IN_CARDS, displayInCards!!) } } binding.showAllArtistGroup.setOnClickListener { binding.showAllArtistCheckbox.isChecked = !binding.showAllArtistCheckbox.isChecked @@ -191,6 +196,16 @@ class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { lifecycleScope.launch { displaySettingsViewModel.send(SHOW_ONLY_MULTIMEDIA_FILES, showOnlyMultimediaFiles!!) } } + binding.showTrackNumbersGroup.setOnClickListener { + binding.showTrackNumbersCheckbox.isChecked = !binding.showTrackNumbersCheckbox.isChecked + } + + binding.showTrackNumbersCheckbox.setOnCheckedChangeListener { _, isChecked -> + showTrackNumbers = isChecked + updateShowTrackNumbers() + lifecycleScope.launch { displaySettingsViewModel.send(SHOW_TRACK_NUMBER, showTrackNumbers!!) } + } + binding.onlyFavsGroup.setOnClickListener { binding.onlyFavsCheckbox.isChecked = !binding.onlyFavsCheckbox.isChecked } @@ -254,8 +269,14 @@ class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { * */ private fun updateDisplayMode() { - binding.displayInListText.text = getString(if (!displayInCards) R.string.display_in_grid else R.string.display_in_list) - binding.displayInListImage.setImageDrawable(ContextCompat.getDrawable(requireActivity(), if (!displayInCards) R.drawable.ic_view_grid else R.drawable.ic_view_list)) + if (displayInCards == null) { + binding.displayInListText.setGone() + binding.displayInListImage.setGone() + binding.displayModeGroup.setGone() + return + } + binding.displayInListText.text = getString(if (displayInCards == false) R.string.display_in_grid else R.string.display_in_list) + binding.displayInListImage.setImageDrawable(ContextCompat.getDrawable(requireActivity(), if (displayInCards == false) R.drawable.ic_view_grid else R.drawable.ic_view_list)) } /** @@ -288,6 +309,21 @@ class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { binding.showAllFilesCheckbox.isChecked = showOnlyMultimediaFiles!! } + /** + * Update the view for the "show track numbers" item + * + */ + private fun updateShowTrackNumbers() { + if (showTrackNumbers == null) { + binding.showTrackNumbersGroup.setGone() + binding.showTrackNumbersImage.setGone() + binding.showTrackNumbersCheckbox.setGone() + binding.showTrackNumbersCheckbox.setGone() + return + } + binding.showTrackNumbersCheckbox.isChecked = showTrackNumbers!! + } + /** * Update the view for the "show hidden files" item * @@ -358,6 +394,7 @@ class DisplaySettingsDialog : VLCBottomSheetDialogFragment() { } val isCurrentSort = (sort == currentSort || currentSort == Medialibrary.SORT_DEFAULT && sort == Medialibrary.SORT_ALPHA) when (sort) { + Medialibrary.TrackId -> setupSortViews(binding, isCurrentSort, R.string.sortby_track, R.string.ascending, R.string.descending, R.drawable.ic_sort_track) Medialibrary.SORT_ALPHA -> setupSortViews(binding, isCurrentSort, R.string.sortby_name, R.string.sort_alpha_asc, R.string.sort_alpha_desc, R.drawable.ic_sort_alpha) Medialibrary.SORT_FILENAME -> setupSortViews(binding, isCurrentSort, R.string.sortby_filename, R.string.sort_alpha_asc, R.string.sort_alpha_desc, R.drawable.ic_sort_filename) Medialibrary.SORT_ARTIST -> setupSortViews(binding, isCurrentSort, R.string.sortby_artist_name, R.string.sort_alpha_asc, R.string.sort_alpha_desc, R.drawable.ic_sort_artist) diff --git a/application/vlc-android/src/org/videolan/vlc/viewmodels/mobile/PlaylistViewModel.kt b/application/vlc-android/src/org/videolan/vlc/viewmodels/mobile/PlaylistViewModel.kt index 4b32260d6..6426535b3 100644 --- a/application/vlc-android/src/org/videolan/vlc/viewmodels/mobile/PlaylistViewModel.kt +++ b/application/vlc-android/src/org/videolan/vlc/viewmodels/mobile/PlaylistViewModel.kt @@ -36,7 +36,6 @@ import org.videolan.vlc.gui.HeaderMediaListActivity import org.videolan.vlc.providers.medialibrary.MedialibraryProvider import org.videolan.vlc.providers.medialibrary.TracksProvider import org.videolan.vlc.viewmodels.MedialibraryViewModel -import java.util.ArrayList class PlaylistViewModel(context: Context, private val initialPlaylist: MediaLibraryItem) : MedialibraryViewModel(context) { @@ -65,6 +64,9 @@ class PlaylistViewModel(context: Context, private val initialPlaylist: MediaLibr } } + override fun canSortByTrackId() = initialPlaylist is Album + override fun canSortByName() = false + override fun refresh() { viewModelScope.launch { refreshPlaylistItem()