From a1947c4102491aa2b431edd5f18b5eed8baaec23 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Tue, 25 Jul 2023 17:32:03 -0600 Subject: [PATCH] home: switch to sort dialogs Switch the home view to sort dialogs, and simplify away any listeners that expected popup menus. --- .../auxio/detail/AlbumDetailFragment.kt | 15 +- .../auxio/detail/ArtistDetailFragment.kt | 13 +- .../oxycblt/auxio/detail/DetailViewModel.kt | 96 +++++--- .../auxio/detail/GenreDetailFragment.kt | 13 +- .../auxio/detail/PlaylistDetailFragment.kt | 13 +- .../auxio/detail/list/DetailListAdapter.kt | 4 +- .../auxio/detail/sort/AlbumSongSortDialog.kt | 5 + .../auxio/detail/sort/ArtistSongSortDialog.kt | 5 + .../auxio/detail/sort/GenreSongSortDialog.kt | 5 + .../org/oxycblt/auxio/home/HomeFragment.kt | 103 +------- .../org/oxycblt/auxio/home/HomeViewModel.kt | 231 ++++++++++-------- .../auxio/home/list/AlbumListFragment.kt | 12 +- .../auxio/home/list/ArtistListFragment.kt | 12 +- .../auxio/home/list/GenreListFragment.kt | 12 +- .../auxio/home/list/PlaylistListFragment.kt | 12 +- .../auxio/home/list/SongListFragment.kt | 12 +- .../auxio/home/sort/AlbumSortDialog.kt | 50 ++++ .../auxio/home/sort/ArtistSortDialog.kt | 44 ++++ .../auxio/home/sort/GenreSortDialog.kt | 44 ++++ .../auxio/home/sort/PlaylistSortDialog.kt | 44 ++++ .../oxycblt/auxio/home/sort/SongSortDialog.kt | 50 ++++ .../java/org/oxycblt/auxio/list/Listeners.kt | 5 +- .../main/java/org/oxycblt/auxio/list/Sort.kt | 52 ---- .../oxycblt/auxio/search/SearchFragment.kt | 2 +- app/src/main/res/menu/toolbar_home.xml | 40 +-- app/src/main/res/navigation/inner.xml | 45 ++++ 26 files changed, 544 insertions(+), 395 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/home/sort/AlbumSortDialog.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/home/sort/ArtistSortDialog.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/home/sort/GenreSortDialog.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/home/sort/PlaylistSortDialog.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/home/sort/SongSortDialog.kt diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 40f5d5796..d1b7199f8 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.View import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -111,7 +110,7 @@ class AlbumDetailFragment : adapter = ConcatAdapter(albumHeaderAdapter, albumListAdapter) (layoutManager as GridLayoutManager).setFullWidthLookup { if (it != 0) { - val item = detailModel.albumList.value[it - 1] + val item = detailModel.albumSongList.value[it - 1] item is Divider || item is Header || item is Disc } else { true @@ -123,7 +122,7 @@ class AlbumDetailFragment : // DetailViewModel handles most initialization from the navigation argument. detailModel.setAlbum(args.albumUid) collectImmediately(detailModel.currentAlbum, ::updateAlbum) - collectImmediately(detailModel.albumList, ::updateList) + collectImmediately(detailModel.albumSongList, ::updateList) collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) @@ -139,7 +138,7 @@ class AlbumDetailFragment : binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. - detailModel.albumInstructions.consume() + detailModel.albumSongInstructions.consume() } override fun onMenuItemClick(item: MenuItem): Boolean { @@ -182,7 +181,7 @@ class AlbumDetailFragment : playbackModel.play(item, detailModel.playInAlbumWith) } - override fun onOpenMenu(item: Song, anchor: View) { + override fun onOpenMenu(item: Song) { listModel.openMenu(R.menu.item_album_song, item, detailModel.playInAlbumWith) } @@ -194,7 +193,7 @@ class AlbumDetailFragment : playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value)) } - override fun onOpenSortMenu(anchor: View) { + override fun onOpenSortMenu() { findNavController().navigateSafe(AlbumDetailFragmentDirections.sort()) } @@ -213,7 +212,7 @@ class AlbumDetailFragment : } private fun updateList(list: List) { - albumListAdapter.update(list, detailModel.albumInstructions.consume()) + albumListAdapter.update(list, detailModel.albumSongInstructions.consume()) } private fun handleShow(show: Show?) { @@ -339,7 +338,7 @@ class AlbumDetailFragment : private fun scrollToAlbumSong(song: Song) { // Calculate where the item for the currently played song is - val pos = detailModel.albumList.value.indexOf(song) + val pos = detailModel.albumSongList.value.indexOf(song) if (pos != -1) { // Only scroll if the song is within this album. diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 04b3b8c2a..c50fb16b4 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.View import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -110,7 +109,7 @@ class ArtistDetailFragment : (layoutManager as GridLayoutManager).setFullWidthLookup { if (it != 0) { val item = - detailModel.artistList.value.getOrElse(it - 1) { + detailModel.artistSongList.value.getOrElse(it - 1) { return@setFullWidthLookup false } item is Divider || item is Header @@ -124,7 +123,7 @@ class ArtistDetailFragment : // DetailViewModel handles most initialization from the navigation argument. detailModel.setArtist(args.artistUid) collectImmediately(detailModel.currentArtist, ::updateArtist) - collectImmediately(detailModel.artistList, ::updateList) + collectImmediately(detailModel.artistSongList, ::updateList) collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) @@ -140,7 +139,7 @@ class ArtistDetailFragment : binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. - detailModel.artistInstructions.consume() + detailModel.artistSongInstructions.consume() } override fun onMenuItemClick(item: MenuItem): Boolean { @@ -183,7 +182,7 @@ class ArtistDetailFragment : } } - override fun onOpenMenu(item: Music, anchor: View) { + override fun onOpenMenu(item: Music) { when (item) { is Song -> listModel.openMenu(R.menu.item_artist_song, item, detailModel.playInArtistWith) @@ -200,7 +199,7 @@ class ArtistDetailFragment : playbackModel.shuffle(unlikelyToBeNull(detailModel.currentArtist.value)) } - override fun onOpenSortMenu(anchor: View) { + override fun onOpenSortMenu() { findNavController().navigateSafe(ArtistDetailFragmentDirections.sort()) } @@ -227,7 +226,7 @@ class ArtistDetailFragment : } private fun updateList(list: List) { - artistListAdapter.update(list, detailModel.artistInstructions.consume()) + artistListAdapter.update(list, detailModel.artistSongInstructions.consume()) } private fun handleShow(show: Show?) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index 735c15019..09d0cb01d 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -98,17 +98,17 @@ constructor( val currentAlbum: StateFlow get() = _currentAlbum - private val _albumList = MutableStateFlow(listOf()) + private val _albumSongList = MutableStateFlow(listOf()) /** The current list data derived from [currentAlbum]. */ - val albumList: StateFlow> - get() = _albumList + val albumSongList: StateFlow> + get() = _albumSongList - private val _albumInstructions = MutableEvent() - /** Instructions for updating [albumList] in the UI. */ - val albumInstructions: Event - get() = _albumInstructions + private val _albumSongInstructions = MutableEvent() + /** Instructions for updating [albumSongList] in the UI. */ + val albumSongInstructions: Event + get() = _albumSongInstructions - /** The current [Sort] used for [Song]s in [albumList]. */ + /** The current [Sort] used for [Song]s in [albumSongList]. */ val albumSongSort: Sort get() = musicSettings.albumSongSort @@ -123,15 +123,16 @@ constructor( val currentArtist: StateFlow get() = _currentArtist - private val _artistList = MutableStateFlow(listOf()) + private val _artistSongList = MutableStateFlow(listOf()) /** The current list derived from [currentArtist]. */ - val artistList: StateFlow> = _artistList - private val _artistInstructions = MutableEvent() - /** Instructions for updating [artistList] in the UI. */ - val artistInstructions: Event - get() = _artistInstructions + val artistSongList: StateFlow> = _artistSongList - /** The current [Sort] used for [Song]s in [artistList]. */ + private val _artistSongInstructions = MutableEvent() + /** Instructions for updating [artistSongList] in the UI. */ + val artistSongInstructions: Event + get() = _artistSongInstructions + + /** The current [Sort] used for [Song]s in [artistSongList]. */ var artistSongSort: Sort get() = musicSettings.artistSongSort set(value) { @@ -151,15 +152,16 @@ constructor( val currentGenre: StateFlow get() = _currentGenre - private val _genreList = MutableStateFlow(listOf()) + private val _genreSongList = MutableStateFlow(listOf()) /** The current list data derived from [currentGenre]. */ - val genreList: StateFlow> = _genreList - private val _genreInstructions = MutableEvent() - /** Instructions for updating [artistList] in the UI. */ - val genreInstructions: Event - get() = _genreInstructions + val genreSongList: StateFlow> = _genreSongList - /** The current [Sort] used for [Song]s in [genreList]. */ + private val _genreSongInstructions = MutableEvent() + /** Instructions for updating [artistSongList] in the UI. */ + val genreSongInstructions: Event + get() = _genreSongInstructions + + /** The current [Sort] used for [Song]s in [genreSongList]. */ var genreSongSort: Sort get() = musicSettings.genreSongSort set(value) { @@ -179,13 +181,14 @@ constructor( val currentPlaylist: StateFlow get() = _currentPlaylist - private val _playlistList = MutableStateFlow(listOf()) + private val _playlistSongList = MutableStateFlow(listOf()) /** The current list data derived from [currentPlaylist] */ - val playlistList: StateFlow> = _playlistList - private val _playlistInstructions = MutableEvent() - /** Instructions for updating [playlistList] in the UI. */ - val playlistInstructions: Event - get() = _playlistInstructions + val playlistSongList: StateFlow> = _playlistSongList + + private val _playlistSongInstructions = MutableEvent() + /** Instructions for updating [playlistSongList] in the UI. */ + val playlistSongInstructions: Event + get() = _playlistSongInstructions private val _editedPlaylist = MutableStateFlow?>(null) /** @@ -346,7 +349,7 @@ constructor( } /** - * Set a new [currentAlbum] from it's [Music.UID]. [currentAlbum] and [albumList] will be + * Set a new [currentAlbum] from it's [Music.UID]. [currentAlbum] and [albumSongList] will be * updated to align with the new [Album]. * * @param uid The [Music.UID] of the [Album] to update [currentAlbum] to. Must be valid. @@ -360,13 +363,18 @@ constructor( } } + /** + * Apply a new [Sort] to [albumSongList]. + * + * @param sort The [Sort] to apply. + */ fun applyAlbumSongSort(sort: Sort) { musicSettings.albumSongSort = sort _currentAlbum.value?.let { refreshAlbumList(it, true) } } /** - * Set a new [currentArtist] from it's [Music.UID]. [currentArtist] and [artistList] will be + * Set a new [currentArtist] from it's [Music.UID]. [currentArtist] and [artistSongList] will be * updated to align with the new [Artist]. * * @param uid The [Music.UID] of the [Artist] to update [currentArtist] to. Must be valid. @@ -380,13 +388,18 @@ constructor( } } + /** + * Apply a new [Sort] to [artistSongList]. + * + * @param sort The [Sort] to apply. + */ fun applyArtistSongSort(sort: Sort) { musicSettings.artistSongSort = sort _currentArtist.value?.let { refreshArtistList(it, true) } } /** - * Set a new [currentGenre] from it's [Music.UID]. [currentGenre] and [genreList] will be + * Set a new [currentGenre] from it's [Music.UID]. [currentGenre] and [genreSongList] will be * updated to align with the new album. * * @param uid The [Music.UID] of the [Genre] to update [currentGenre] to. Must be valid. @@ -400,6 +413,11 @@ constructor( } } + /** + * Apply a new [Sort] to [genreSongList]. + * + * @param sort The [Sort] to apply. + */ fun applyGenreSongSort(sort: Sort) { musicSettings.genreSongSort = sort _currentGenre.value?.let { refreshGenreList(it, true) } @@ -554,8 +572,8 @@ constructor( } logD("Update album list to ${list.size} items with $instructions") - _albumInstructions.put(instructions) - _albumList.value = list + _albumSongInstructions.put(instructions) + _albumSongList.value = list } private fun refreshArtistList(artist: Artist, replace: Boolean = false) { @@ -617,8 +635,8 @@ constructor( } logD("Updating artist list to ${list.size} items with $instructions") - _artistInstructions.put(instructions) - _artistList.value = list.toList() + _artistSongInstructions.put(instructions) + _artistSongList.value = list.toList() } private fun refreshGenreList(genre: Genre, replace: Boolean = false) { @@ -643,8 +661,8 @@ constructor( list.addAll(genreSongSort.songs(genre.songs)) logD("Updating genre list to ${list.size} items with $instructions") - _genreInstructions.put(instructions) - _genreList.value = list + _genreSongInstructions.put(instructions) + _genreSongList.value = list } private fun refreshPlaylistList( @@ -663,8 +681,8 @@ constructor( } logD("Updating playlist list to ${list.size} items with $instructions") - _playlistInstructions.put(instructions) - _playlistList.value = list + _playlistSongInstructions.put(instructions) + _playlistSongList.value = list } /** diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 49b6f451e..190ae4c81 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.View import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -108,7 +107,7 @@ class GenreDetailFragment : (layoutManager as GridLayoutManager).setFullWidthLookup { if (it != 0) { val item = - detailModel.genreList.value.getOrElse(it - 1) { + detailModel.genreSongList.value.getOrElse(it - 1) { return@setFullWidthLookup false } item is Divider || item is Header @@ -122,7 +121,7 @@ class GenreDetailFragment : // DetailViewModel handles most initialization from the navigation argument. detailModel.setGenre(args.genreUid) collectImmediately(detailModel.currentGenre, ::updatePlaylist) - collectImmediately(detailModel.genreList, ::updateList) + collectImmediately(detailModel.genreSongList, ::updateList) collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) @@ -138,7 +137,7 @@ class GenreDetailFragment : binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. - detailModel.genreInstructions.consume() + detailModel.genreSongInstructions.consume() } override fun onMenuItemClick(item: MenuItem): Boolean { @@ -181,7 +180,7 @@ class GenreDetailFragment : } } - override fun onOpenMenu(item: Music, anchor: View) { + override fun onOpenMenu(item: Music) { when (item) { is Artist -> listModel.openMenu(R.menu.item_parent, item) is Song -> listModel.openMenu(R.menu.item_song, item, detailModel.playInGenreWith) @@ -197,7 +196,7 @@ class GenreDetailFragment : playbackModel.shuffle(unlikelyToBeNull(detailModel.currentGenre.value)) } - override fun onOpenSortMenu(anchor: View) { + override fun onOpenSortMenu() { findNavController().navigateSafe(GenreDetailFragmentDirections.sort()) } @@ -212,7 +211,7 @@ class GenreDetailFragment : } private fun updateList(list: List) { - genreListAdapter.update(list, detailModel.genreInstructions.consume()) + genreListAdapter.update(list, detailModel.genreSongInstructions.consume()) } private fun handleShow(show: Show?) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt index 4f25b96f8..9c0088c11 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.View import androidx.fragment.app.activityViewModels import androidx.navigation.NavController import androidx.navigation.NavDestination @@ -123,7 +122,7 @@ class PlaylistDetailFragment : (layoutManager as GridLayoutManager).setFullWidthLookup { if (it != 0) { val item = - detailModel.playlistList.value.getOrElse(it - 1) { + detailModel.playlistSongList.value.getOrElse(it - 1) { return@setFullWidthLookup false } item is Divider || item is Header @@ -137,7 +136,7 @@ class PlaylistDetailFragment : // DetailViewModel handles most initialization from the navigation argument. detailModel.setPlaylist(args.playlistUid) collectImmediately(detailModel.currentPlaylist, ::updatePlaylist) - collectImmediately(detailModel.playlistList, ::updateList) + collectImmediately(detailModel.playlistSongList, ::updateList) collectImmediately(detailModel.editedPlaylist, ::updateEditedList) collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) @@ -168,7 +167,7 @@ class PlaylistDetailFragment : binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. - detailModel.playlistInstructions.consume() + detailModel.playlistSongInstructions.consume() } override fun onDestinationChanged( @@ -236,7 +235,7 @@ class PlaylistDetailFragment : requireNotNull(touchHelper) { "ItemTouchHelper was not available" }.startDrag(viewHolder) } - override fun onOpenMenu(item: Song, anchor: View) { + override fun onOpenMenu(item: Song) { listModel.openMenu(R.menu.item_playlist_song, item, detailModel.playInPlaylistWith) } @@ -252,7 +251,7 @@ class PlaylistDetailFragment : detailModel.startPlaylistEdit() } - override fun onOpenSortMenu(anchor: View) {} + override fun onOpenSortMenu() {} private fun updatePlaylist(playlist: Playlist?) { if (playlist == null) { @@ -278,7 +277,7 @@ class PlaylistDetailFragment : } private fun updateList(list: List) { - playlistListAdapter.update(list, detailModel.playlistInstructions.consume()) + playlistListAdapter.update(list, detailModel.playlistSongInstructions.consume()) } private fun updateEditedList(editedPlaylist: List?) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/list/DetailListAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/list/DetailListAdapter.kt index ba350e7b3..08293199f 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/list/DetailListAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/list/DetailListAdapter.kt @@ -82,7 +82,7 @@ abstract class DetailListAdapter( * Called when the button in a [SortHeader] item is pressed, requesting that the sort menu * should be opened. */ - fun onOpenSortMenu(anchor: View) + fun onOpenSortMenu() } protected companion object { @@ -132,7 +132,7 @@ private class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) : // Add a Tooltip based on the content description so that the purpose of this // button can be clear. TooltipCompat.setTooltipText(this, contentDescription) - setOnClickListener(listener::onOpenSortMenu) + setOnClickListener { listener.onOpenSortMenu() } } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/sort/AlbumSongSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/sort/AlbumSongSortDialog.kt index 8c7290e98..ea1fd15bd 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/sort/AlbumSongSortDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/sort/AlbumSongSortDialog.kt @@ -30,6 +30,11 @@ import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD +/** + * A [SortDialog] that controls the [Sort] of [DetailViewModel.albumSongSort]. + * + * @author Alexander Capehart (OxygenCobalt) + */ @AndroidEntryPoint class AlbumSongSortDialog : SortDialog() { private val detailModel: DetailViewModel by activityViewModels() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/sort/ArtistSongSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/sort/ArtistSongSortDialog.kt index a745bd11a..15daaff72 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/sort/ArtistSongSortDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/sort/ArtistSongSortDialog.kt @@ -30,6 +30,11 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD +/** + * A [SortDialog] that controls the [Sort] of [DetailViewModel.artistSongSort]. + * + * @author Alexander Capehart (OxygenCobalt) + */ @AndroidEntryPoint class ArtistSongSortDialog : SortDialog() { private val detailModel: DetailViewModel by activityViewModels() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/sort/GenreSongSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/sort/GenreSongSortDialog.kt index d491310c8..3191557e4 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/sort/GenreSongSortDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/sort/GenreSongSortDialog.kt @@ -30,6 +30,11 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD +/** + * A [SortDialog] that controls the [Sort] of [DetailViewModel.genreSongSort]. + * + * @author Alexander Capehart (OxygenCobalt) + */ @AndroidEntryPoint class GenreSongSortDialog : SortDialog() { private val detailModel: DetailViewModel by activityViewModels() diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 834b25fe6..51e25378a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -26,7 +26,6 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.MenuCompat import androidx.core.view.isVisible -import androidx.core.view.iterator import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -57,7 +56,6 @@ import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.list.ListViewModel import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.list.SelectionFragment -import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.music.IndexingProgress import org.oxycblt.auxio.music.IndexingState import org.oxycblt.auxio.music.Music @@ -76,7 +74,6 @@ import org.oxycblt.auxio.util.lazyReflectedField import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.navigateSafe -import org.oxycblt.auxio.util.unlikelyToBeNull /** * The starting [SelectionFragment] of Auxio. Shows the user's music library and enables navigation @@ -172,7 +169,7 @@ class HomeFragment : // --- VIEWMODEL SETUP --- collect(homeModel.recreateTabs.flow, ::handleRecreate) collectImmediately(homeModel.currentTabType, ::updateCurrentTab) - collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab) + collectImmediately(homeModel.songList, homeModel.isFastScrolling, ::updateFab) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) collectImmediately(musicModel.indexingState, ::updateIndexerState) @@ -232,41 +229,22 @@ class HomeFragment : } // Handle sort menu - R.id.submenu_sorting -> { + R.id.action_sort -> { // Junk click event when opening the menu - true - } - R.id.option_sort_asc -> { - logD("Switching to ascending sorting") - item.isChecked = true - homeModel.setSortForCurrentTab( - homeModel - .getSortForTab(homeModel.currentTabType.value) - .withDirection(Sort.Direction.ASCENDING)) - true - } - R.id.option_sort_dec -> { - logD("Switching to descending sorting") - item.isChecked = true - homeModel.setSortForCurrentTab( - homeModel - .getSortForTab(homeModel.currentTabType.value) - .withDirection(Sort.Direction.DESCENDING)) + val directions = + when (homeModel.currentTabType.value) { + MusicType.SONGS -> HomeFragmentDirections.sortSongs() + MusicType.ALBUMS -> HomeFragmentDirections.sortAlbums() + MusicType.ARTISTS -> HomeFragmentDirections.sortArtists() + MusicType.GENRES -> HomeFragmentDirections.sortGenres() + MusicType.PLAYLISTS -> HomeFragmentDirections.sortPlaylists() + } + findNavController().navigateSafe(directions) true } else -> { - val newMode = Sort.Mode.fromItemId(item.itemId) - if (newMode != null) { - // Sorting option was selected, mark it as selected and update the mode - logD("Updating sort mode") - item.isChecked = true - homeModel.setSortForCurrentTab( - homeModel.getSortForTab(homeModel.currentTabType.value).withMode(newMode)) - true - } else { - logW("Unexpected menu item selected") - false - } + logW("Unexpected menu item selected") + false } } } @@ -300,61 +278,6 @@ class HomeFragment : private fun updateCurrentTab(tabType: MusicType) { val binding = requireBinding() - // Update the sort options to align with those allowed by the tab - val isVisible: (Int) -> Boolean = - when (tabType) { - // Disallow sorting by count for songs - MusicType.SONGS -> { - logD("Using song-specific menu options") - ({ id -> id != R.id.option_sort_count }) - } - // Disallow sorting by album for albums - MusicType.ALBUMS -> { - logD("Using album-specific menu options") - ({ id -> id != R.id.option_sort_album }) - } - // Only allow sorting by name, count, and duration for parents - else -> { - logD("Using parent-specific menu options") - ({ id -> - id == R.id.option_sort_asc || - id == R.id.option_sort_dec || - id == R.id.option_sort_name || - id == R.id.option_sort_count || - id == R.id.option_sort_duration - }) - } - } - - val sortMenu = - unlikelyToBeNull(binding.homeNormalToolbar.menu.findItem(R.id.submenu_sorting).subMenu) - val toHighlight = homeModel.getSortForTab(tabType) - - for (option in sortMenu) { - val isCurrentMode = option.itemId == toHighlight.mode.itemId - val isCurrentlyAscending = - option.itemId == R.id.option_sort_asc && - toHighlight.direction == Sort.Direction.ASCENDING - val isCurrentlyDescending = - option.itemId == R.id.option_sort_dec && - toHighlight.direction == Sort.Direction.DESCENDING - // Check the corresponding direction and mode sort options to align with - // the current sort of the tab. - if (isCurrentMode || isCurrentlyAscending || isCurrentlyDescending) { - logD( - "Checking $option option [mode: $isCurrentMode asc: $isCurrentlyAscending dec: $isCurrentlyDescending]") - // Note: We cannot inline this boolean assignment since it unchecks all other radio - // buttons (even when setting it to false), which would result in nothing being - // selected. - option.isChecked = true - } - - // Disable options that are not allowed by the isVisible lambda - option.isVisible = isVisible(option.itemId) - if (!option.isVisible) { - logD("Hiding $option option") - } - } // Update the scrolling view in AppBarLayout to align with the current tab's // scrolling state. This prevents the lift state from being confused as one diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index a89b73b3d..0d7e1e4d0 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -55,63 +55,83 @@ constructor( private val musicSettings: MusicSettings ) : ViewModel(), MusicRepository.UpdateListener, HomeSettings.Listener { - private val _songsList = MutableStateFlow(listOf()) + private val _songList = MutableStateFlow(listOf()) /** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */ - val songsList: StateFlow> - get() = _songsList + val songList: StateFlow> + get() = _songList - private val _songsInstructions = MutableEvent() - /** Instructions for how to update [songsList] in the UI. */ - val songsInstructions: Event - get() = _songsInstructions + private val _songInstructions = MutableEvent() + /** Instructions for how to update [songList] in the UI. */ + val songInstructions: Event + get() = _songInstructions - private val _albumsLists = MutableStateFlow(listOf()) - /** A list of [Album]s, sorted by the preferred [Sort], to be shown in the home view. */ - val albumsList: StateFlow> - get() = _albumsLists - - private val _albumsInstructions = MutableEvent() - /** Instructions for how to update [albumsList] in the UI. */ - val albumsInstructions: Event - get() = _albumsInstructions - - private val _artistsList = MutableStateFlow(listOf()) - /** - * A list of [Artist]s, sorted by the preferred [Sort], to be shown in the home view. Note that - * if "Hide collaborators" is on, this list will not include collaborator [Artist]s. - */ - val artistsList: MutableStateFlow> - get() = _artistsList - - private val _artistsInstructions = MutableEvent() - /** Instructions for how to update [artistsList] in the UI. */ - val artistsInstructions: Event - get() = _artistsInstructions - - private val _genresList = MutableStateFlow(listOf()) - /** A list of [Genre]s, sorted by the preferred [Sort], to be shown in the home view. */ - val genresList: StateFlow> - get() = _genresList - - private val _genresInstructions = MutableEvent() - /** Instructions for how to update [genresList] in the UI. */ - val genresInstructions: Event - get() = _genresInstructions - - private val _playlistsList = MutableStateFlow(listOf()) - /** A list of [Playlist]s, sorted by the preferred [Sort], to be shown in the home view. */ - val playlistsList: StateFlow> - get() = _playlistsList - - private val _playlistsInstructions = MutableEvent() - /** Instructions for how to update [genresList] in the UI. */ - val playlistsInstructions: Event - get() = _playlistsInstructions + /** The current [Sort] used for [songList]. */ + val songSort: Sort + get() = musicSettings.songSort /** The [PlaySong] instructions to use when playing a [Song]. */ val playWith get() = playbackSettings.playInListWith + private val _albumList = MutableStateFlow(listOf()) + /** A list of [Album]s, sorted by the preferred [Sort], to be shown in the home view. */ + val albumList: StateFlow> + get() = _albumList + + private val _albumInstructions = MutableEvent() + /** Instructions for how to update [albumList] in the UI. */ + val albumInstructions: Event + get() = _albumInstructions + + /** The current [Sort] used for [albumList]. */ + val albumSort: Sort + get() = musicSettings.albumSort + + private val _artistList = MutableStateFlow(listOf()) + /** + * A list of [Artist]s, sorted by the preferred [Sort], to be shown in the home view. Note that + * if "Hide collaborators" is on, this list will not include collaborator [Artist]s. + */ + val artistList: MutableStateFlow> + get() = _artistList + + private val _artistInstructions = MutableEvent() + /** Instructions for how to update [artistList] in the UI. */ + val artistInstructions: Event + get() = _artistInstructions + + /** The current [Sort] used for [artistList]. */ + val artistSort: Sort + get() = musicSettings.artistSort + + private val _genreList = MutableStateFlow(listOf()) + /** A list of [Genre]s, sorted by the preferred [Sort], to be shown in the home view. */ + val genreList: StateFlow> + get() = _genreList + + private val _genreInstructions = MutableEvent() + /** Instructions for how to update [genreList] in the UI. */ + val genreInstructions: Event + get() = _genreInstructions + + /** The current [Sort] used for [genreList]. */ + val genreSort: Sort + get() = musicSettings.genreSort + + private val _playlistList = MutableStateFlow(listOf()) + /** A list of [Playlist]s, sorted by the preferred [Sort], to be shown in the home view. */ + val playlistList: StateFlow> + get() = _playlistList + + private val _playlistInstructions = MutableEvent() + /** Instructions for how to update [genreList] in the UI. */ + val playlistInstructions: Event + get() = _playlistInstructions + + /** The current [Sort] used for [genreList]. */ + val playlistSort: Sort + get() = musicSettings.playlistSort + /** * A list of [MusicType] corresponding to the current [Tab] configuration, excluding invisible * [Tab]s. @@ -157,12 +177,12 @@ constructor( logD("Refreshing library") // Get the each list of items in the library to use as our list data. // Applying the preferred sorting to them. - _songsInstructions.put(UpdateInstructions.Diff) - _songsList.value = musicSettings.songSort.songs(deviceLibrary.songs) - _albumsInstructions.put(UpdateInstructions.Diff) - _albumsLists.value = musicSettings.albumSort.albums(deviceLibrary.albums) - _artistsInstructions.put(UpdateInstructions.Diff) - _artistsList.value = + _songInstructions.put(UpdateInstructions.Diff) + _songList.value = musicSettings.songSort.songs(deviceLibrary.songs) + _albumInstructions.put(UpdateInstructions.Diff) + _albumList.value = musicSettings.albumSort.albums(deviceLibrary.albums) + _artistInstructions.put(UpdateInstructions.Diff) + _artistList.value = musicSettings.artistSort.artists( if (homeSettings.shouldHideCollaborators) { logD("Filtering collaborator artists") @@ -172,15 +192,15 @@ constructor( logD("Using all artists") deviceLibrary.artists }) - _genresInstructions.put(UpdateInstructions.Diff) - _genresList.value = musicSettings.genreSort.genres(deviceLibrary.genres) + _genreInstructions.put(UpdateInstructions.Diff) + _genreList.value = musicSettings.genreSort.genres(deviceLibrary.genres) } val userLibrary = musicRepository.userLibrary if (changes.userLibrary && userLibrary != null) { logD("Refreshing playlists") - _playlistsInstructions.put(UpdateInstructions.Diff) - _playlistsList.value = musicSettings.playlistSort.playlists(userLibrary.playlists) + _playlistInstructions.put(UpdateInstructions.Diff) + _playlistList.value = musicSettings.playlistSort.playlists(userLibrary.playlists) } } @@ -199,59 +219,58 @@ constructor( } /** - * Get the preferred [Sort] for a given [Tab]. + * Apply a new [Sort] to [songList]. * - * @param tabType The [MusicType] of the [Tab] desired. - * @return The [Sort] preferred for that [Tab] + * @param sort The [Sort] to apply. */ - fun getSortForTab(tabType: MusicType) = - when (tabType) { - MusicType.SONGS -> musicSettings.songSort - MusicType.ALBUMS -> musicSettings.albumSort - MusicType.ARTISTS -> musicSettings.artistSort - MusicType.GENRES -> musicSettings.genreSort - MusicType.PLAYLISTS -> musicSettings.playlistSort - } + fun applySongSort(sort: Sort) { + musicSettings.songSort = sort + _songInstructions.put(UpdateInstructions.Replace(0)) + _songList.value = musicSettings.songSort.songs(_songList.value) + } /** - * Update the preferred [Sort] for the current [Tab]. Will update corresponding list. + * Apply a new [Sort] to [albumList]. * - * @param sort The new [Sort] to apply. Assumed to be an allowed sort for the current [Tab]. + * @param sort The [Sort] to apply. */ - fun setSortForCurrentTab(sort: Sort) { - // Can simply re-sort the current list of items without having to access the library. - when (val type = _currentTabType.value) { - MusicType.SONGS -> { - logD("Updating song [$type] sort mode to $sort") - musicSettings.songSort = sort - _songsInstructions.put(UpdateInstructions.Replace(0)) - _songsList.value = sort.songs(_songsList.value) - } - MusicType.ALBUMS -> { - logD("Updating album [$type] sort mode to $sort") - musicSettings.albumSort = sort - _albumsInstructions.put(UpdateInstructions.Replace(0)) - _albumsLists.value = sort.albums(_albumsLists.value) - } - MusicType.ARTISTS -> { - logD("Updating artist [$type] sort mode to $sort") - musicSettings.artistSort = sort - _artistsInstructions.put(UpdateInstructions.Replace(0)) - _artistsList.value = sort.artists(_artistsList.value) - } - MusicType.GENRES -> { - logD("Updating genre [$type] sort mode to $sort") - musicSettings.genreSort = sort - _genresInstructions.put(UpdateInstructions.Replace(0)) - _genresList.value = sort.genres(_genresList.value) - } - MusicType.PLAYLISTS -> { - logD("Updating playlist [$type] sort mode to $sort") - musicSettings.playlistSort = sort - _playlistsInstructions.put(UpdateInstructions.Replace(0)) - _playlistsList.value = sort.playlists(_playlistsList.value) - } - } + fun applyAlbumSort(sort: Sort) { + musicSettings.albumSort = sort + _albumInstructions.put(UpdateInstructions.Replace(0)) + _albumList.value = musicSettings.albumSort.albums(_albumList.value) + } + + /** + * Apply a new [Sort] to [artistList]. + * + * @param sort The [Sort] to apply. + */ + fun applyArtistSort(sort: Sort) { + musicSettings.artistSort = sort + _artistInstructions.put(UpdateInstructions.Replace(0)) + _artistList.value = musicSettings.artistSort.artists(_artistList.value) + } + + /** + * Apply a new [Sort] to [genreList]. + * + * @param sort The [Sort] to apply. + */ + fun applyGenreSort(sort: Sort) { + musicSettings.genreSort = sort + _genreInstructions.put(UpdateInstructions.Replace(0)) + _genreList.value = musicSettings.genreSort.genres(_genreList.value) + } + + /** + * Apply a new [Sort] to [playlistList]. + * + * @param sort The [Sort] to apply. + */ + fun applyPlaylistSort(sort: Sort) { + musicSettings.playlistSort = sort + _playlistInstructions.put(UpdateInstructions.Replace(0)) + _playlistList.value = musicSettings.playlistSort.playlists(_playlistList.value) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt index ce8fcfb20..3e38a6e0d 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.text.format.DateUtils import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import dagger.hilt.android.AndroidEntryPoint @@ -40,7 +39,6 @@ import org.oxycblt.auxio.list.recycler.AlbumViewHolder import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -81,7 +79,7 @@ class AlbumListFragment : listener = this@AlbumListFragment } - collectImmediately(homeModel.albumsList, ::updateAlbums) + collectImmediately(homeModel.albumList, ::updateAlbums) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -97,9 +95,9 @@ class AlbumListFragment : } override fun getPopup(pos: Int): String? { - val album = homeModel.albumsList.value[pos] + val album = homeModel.albumList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicType.ALBUMS).mode) { + return when (homeModel.albumSort.mode) { // By Name -> Use Name is Sort.Mode.ByName -> album.name.thumb @@ -141,12 +139,12 @@ class AlbumListFragment : detailModel.showAlbum(item) } - override fun onOpenMenu(item: Album, anchor: View) { + override fun onOpenMenu(item: Album) { listModel.openMenu(R.menu.item_album, item) } private fun updateAlbums(albums: List) { - albumAdapter.update(albums, homeModel.albumsInstructions.consume()) + albumAdapter.update(albums, homeModel.albumInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt index 8cda450e7..920231d2a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import dagger.hilt.android.AndroidEntryPoint @@ -38,7 +37,6 @@ import org.oxycblt.auxio.list.recycler.ArtistViewHolder import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -76,7 +74,7 @@ class ArtistListFragment : listener = this@ArtistListFragment } - collectImmediately(homeModel.artistsList, ::updateArtists) + collectImmediately(homeModel.artistList, ::updateArtists) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -92,9 +90,9 @@ class ArtistListFragment : } override fun getPopup(pos: Int): String? { - val artist = homeModel.artistsList.value[pos] + val artist = homeModel.artistList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicType.ARTISTS).mode) { + return when (homeModel.artistSort.mode) { // By Name -> Use Name is Sort.Mode.ByName -> artist.name.thumb @@ -117,12 +115,12 @@ class ArtistListFragment : detailModel.showArtist(item) } - override fun onOpenMenu(item: Artist, anchor: View) { + override fun onOpenMenu(item: Artist) { listModel.openMenu(R.menu.item_parent, item) } private fun updateArtists(artists: List) { - artistAdapter.update(artists, homeModel.artistsInstructions.consume()) + artistAdapter.update(artists, homeModel.artistInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt index bba02ff20..381422748 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import dagger.hilt.android.AndroidEntryPoint @@ -38,7 +37,6 @@ import org.oxycblt.auxio.list.recycler.GenreViewHolder import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -75,7 +73,7 @@ class GenreListFragment : listener = this@GenreListFragment } - collectImmediately(homeModel.genresList, ::updateGenres) + collectImmediately(homeModel.genreList, ::updateGenres) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -91,9 +89,9 @@ class GenreListFragment : } override fun getPopup(pos: Int): String? { - val genre = homeModel.genresList.value[pos] + val genre = homeModel.genreList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicType.GENRES).mode) { + return when (homeModel.genreSort.mode) { // By Name -> Use Name is Sort.Mode.ByName -> genre.name.thumb @@ -116,12 +114,12 @@ class GenreListFragment : detailModel.showGenre(item) } - override fun onOpenMenu(item: Genre, anchor: View) { + override fun onOpenMenu(item: Genre) { listModel.openMenu(R.menu.item_parent, item) } private fun updateGenres(genres: List) { - genreAdapter.update(genres, homeModel.genresInstructions.consume()) + genreAdapter.update(genres, homeModel.genreInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt index 164b0da92..a36db3873 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt @@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import org.oxycblt.auxio.R @@ -36,7 +35,6 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.PlaylistViewHolder import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song @@ -73,7 +71,7 @@ class PlaylistListFragment : listener = this@PlaylistListFragment } - collectImmediately(homeModel.playlistsList, ::updatePlaylists) + collectImmediately(homeModel.playlistList, ::updatePlaylists) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -89,9 +87,9 @@ class PlaylistListFragment : } override fun getPopup(pos: Int): String? { - val playlist = homeModel.playlistsList.value[pos] + val playlist = homeModel.playlistList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicType.GENRES).mode) { + return when (homeModel.playlistSort.mode) { // By Name -> Use Name is Sort.Mode.ByName -> playlist.name.thumb @@ -114,12 +112,12 @@ class PlaylistListFragment : detailModel.showPlaylist(item) } - override fun onOpenMenu(item: Playlist, anchor: View) { + override fun onOpenMenu(item: Playlist) { listModel.openMenu(R.menu.item_playlist, item) } private fun updatePlaylists(playlists: List) { - playlistAdapter.update(playlists, homeModel.playlistsInstructions.consume()) + playlistAdapter.update(playlists, homeModel.playlistInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index 26b9282ea..38e63a5bd 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.text.format.DateUtils import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import dagger.hilt.android.AndroidEntryPoint @@ -38,7 +37,6 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -78,7 +76,7 @@ class SongListFragment : listener = this@SongListFragment } - collectImmediately(homeModel.songsList, ::updateSongs) + collectImmediately(homeModel.songList, ::updateSongs) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -94,11 +92,11 @@ class SongListFragment : } override fun getPopup(pos: Int): String? { - val song = homeModel.songsList.value[pos] + val song = homeModel.songList.value[pos] // Change how we display the popup depending on the current sort mode. // Note: We don't use the more correct individual artist name here, as sorts are largely // based off the names of the parent objects and not the child objects. - return when (homeModel.getSortForTab(MusicType.SONGS).mode) { + return when (homeModel.songSort.mode) { // Name -> Use name is Sort.Mode.ByName -> song.name.thumb @@ -140,12 +138,12 @@ class SongListFragment : playbackModel.play(item, homeModel.playWith) } - override fun onOpenMenu(item: Song, anchor: View) { + override fun onOpenMenu(item: Song) { listModel.openMenu(R.menu.item_song, item, homeModel.playWith) } private fun updateSongs(songs: List) { - songAdapter.update(songs, homeModel.songsInstructions.consume()) + songAdapter.update(songs, homeModel.songInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/AlbumSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/AlbumSortDialog.kt new file mode 100644 index 000000000..82426eb71 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/AlbumSortDialog.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Auxio Project + * AlbumSortDialog.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.albumList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class AlbumSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.albumSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applyAlbumSort(sort) + } + + override fun getModeChoices() = + listOf( + Sort.Mode.ByName, + Sort.Mode.ByArtist, + Sort.Mode.ByDate, + Sort.Mode.ByDuration, + Sort.Mode.ByCount, + Sort.Mode.ByDateAdded) +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/ArtistSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/ArtistSortDialog.kt new file mode 100644 index 000000000..5c02f777b --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/ArtistSortDialog.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Auxio Project + * ArtistSortDialog.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.artistList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class ArtistSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.artistSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applyArtistSort(sort) + } + + override fun getModeChoices() = + listOf(Sort.Mode.ByName, Sort.Mode.ByDuration, Sort.Mode.ByCount) +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/GenreSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/GenreSortDialog.kt new file mode 100644 index 000000000..96093c9a9 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/GenreSortDialog.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Auxio Project + * GenreSortDialog.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.genreList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class GenreSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.genreSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applyGenreSort(sort) + } + + override fun getModeChoices() = + listOf(Sort.Mode.ByName, Sort.Mode.ByDuration, Sort.Mode.ByCount) +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/PlaylistSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/PlaylistSortDialog.kt new file mode 100644 index 000000000..eb89c4bdd --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/PlaylistSortDialog.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Auxio Project + * PlaylistSortDialog.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.playlistList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class PlaylistSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.playlistSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applyPlaylistSort(sort) + } + + override fun getModeChoices() = + listOf(Sort.Mode.ByName, Sort.Mode.ByDuration, Sort.Mode.ByCount) +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/SongSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/SongSortDialog.kt new file mode 100644 index 000000000..bec18719a --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/SongSortDialog.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Auxio Project + * SongSortDialog.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.songList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class SongSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.songSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applySongSort(sort) + } + + override fun getModeChoices() = + listOf( + Sort.Mode.ByName, + Sort.Mode.ByArtist, + Sort.Mode.ByAlbum, + Sort.Mode.ByDate, + Sort.Mode.ByDuration, + Sort.Mode.ByDateAdded) +} diff --git a/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt b/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt index d728a6142..c7704e503 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt @@ -115,9 +115,8 @@ interface SelectableListListener : ClickableListListener { * Called when an item in the list requests that a menu related to it should be opened. * * @param item The [T] item to open a menu for. - * @param anchor The [View] to anchor the menu to. */ - fun onOpenMenu(item: T, anchor: View) + fun onOpenMenu(item: T) /** * Called when an item in the list requests that it be selected. @@ -148,6 +147,6 @@ interface SelectableListListener : ClickableListListener { true } // Map the menu button to the menu opening listener. - menuButton.setOnClickListener { onOpenMenu(item, it) } + menuButton.setOnClickListener { onOpenMenu(item) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/list/Sort.kt b/app/src/main/java/org/oxycblt/auxio/list/Sort.kt index 9b453e774..555fd6b9a 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/Sort.kt @@ -18,8 +18,6 @@ package org.oxycblt.auxio.list -import androidx.annotation.IdRes -import java.lang.IllegalStateException import kotlin.math.max import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R @@ -164,8 +162,6 @@ data class Sort(val mode: Mode, val direction: Direction) { sealed interface Mode { /** The integer representation of this sort mode. */ val intCode: Int - /** The item ID of this sort mode in menu resources. */ - val itemId: Int /** The string resource of the human-readable name of this sort mode. */ val stringRes: Int @@ -223,9 +219,6 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_NAME - override val itemId: Int - get() = R.id.option_sort_name - override val stringRes: Int get() = R.string.lbl_name @@ -254,9 +247,6 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_ALBUM - override val itemId: Int - get() = R.id.option_sort_album - override val stringRes: Int get() = R.string.lbl_album @@ -277,9 +267,6 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_ARTIST - override val itemId: Int - get() = R.id.option_sort_artist - override val stringRes: Int get() = R.string.lbl_artist @@ -309,9 +296,6 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_YEAR - override val itemId: Int - get() = R.id.option_sort_year - override val stringRes: Int get() = R.string.lbl_date @@ -334,9 +318,6 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_DURATION - override val itemId: Int - get() = R.id.option_sort_duration - override val stringRes: Int get() = R.string.lbl_duration @@ -372,9 +353,6 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_COUNT - override val itemId: Int - get() = R.id.option_sort_count - override val stringRes: Int get() = R.string.lbl_song_count @@ -406,9 +384,6 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_DISC - override val itemId: Int - get() = throw IllegalStateException() - override val stringRes: Int get() = R.string.lbl_disc @@ -428,9 +403,6 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_TRACK - override val itemId: Int - get() = throw IllegalStateException() - override val stringRes: Int get() = R.string.lbl_track @@ -451,9 +423,6 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_DATE_ADDED - override val itemId: Int - get() = R.id.option_sort_date_added - override val stringRes: Int get() = R.string.lbl_date_added @@ -488,27 +457,6 @@ data class Sort(val mode: Mode, val direction: Direction) { ByDateAdded.intCode -> ByDateAdded else -> null } - - /** - * Convert a menu item ID into a [Mode]. - * - * @param itemId The menu resource ID to convert - * @return A [Mode] corresponding to the given ID, or null if the ID is invalid. - * @see itemId - */ - fun fromItemId(@IdRes itemId: Int) = - when (itemId) { - ByName.itemId -> ByName - ByAlbum.itemId -> ByAlbum - ByArtist.itemId -> ByArtist - ByDate.itemId -> ByDate - ByDuration.itemId -> ByDuration - ByCount.itemId -> ByCount - ByDisc.itemId -> ByDisc - ByTrack.itemId -> ByTrack - ByDateAdded.itemId -> ByDateAdded - else -> null - } } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index 27abdeab1..1a3a29d5f 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -182,7 +182,7 @@ class SearchFragment : ListFragment() { } } - override fun onOpenMenu(item: Music, anchor: View) { + override fun onOpenMenu(item: Music) { when (item) { is Song -> listModel.openMenu(R.menu.item_song, item, searchModel.playWith) is Album -> listModel.openMenu(R.menu.item_album, item) diff --git a/app/src/main/res/menu/toolbar_home.xml b/app/src/main/res/menu/toolbar_home.xml index 7e27b3a49..9aa0360de 100644 --- a/app/src/main/res/menu/toolbar_home.xml +++ b/app/src/main/res/menu/toolbar_home.xml @@ -9,46 +9,10 @@ app:showAsAction="ifRoom" /> - - - - - - - - - - - - - - - - + app:showAsAction="ifRoom" /> + + + + + @@ -65,6 +80,36 @@ app:destination="@id/play_from_genre_dialog" /> + + + + + + + + + +