From 5d1eaf72dd35e77c5ebbd2a2951df28c1912b600 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 14 Jun 2022 17:39:18 -0600 Subject: [PATCH] detail: add queue actions to artist/genre Add play next/add to queue actions to artists and genres, as the queue system now makes their UX reasonable. --- CHANGELOG.md | 1 + .../java/org/oxycblt/auxio/MainFragment.kt | 13 ++++++---- .../auxio/detail/AlbumDetailFragment.kt | 4 +++ .../auxio/detail/ArtistDetailFragment.kt | 20 ++++++++++++-- .../oxycblt/auxio/detail/DetailFragment.kt | 8 ++---- .../auxio/detail/GenreDetailFragment.kt | 20 ++++++++++++-- .../oxycblt/auxio/detail/SongDetailDialog.kt | 17 +++--------- .../org/oxycblt/auxio/home/HomeFragment.kt | 4 +-- .../auxio/playback/PlaybackBarFragment.kt | 2 +- .../auxio/playback/PlaybackPanelFragment.kt | 7 +++-- .../auxio/playback/PlaybackViewModel.kt | 20 ++++++++++++++ .../java/org/oxycblt/auxio/ui/ActionMenu.kt | 26 ++++++++++++++----- .../oxycblt/auxio/ui/NavigationViewModel.kt | 15 ++++++----- app/src/main/res/menu/menu_album_detail.xml | 3 +++ app/src/main/res/menu/menu_genre_actions.xml | 9 ------- ...ions.xml => menu_genre_artist_actions.xml} | 6 +++++ .../res/menu/menu_genre_artist_detail.xml | 9 +++++++ app/src/main/res/navigation/nav_main.xml | 12 +++++++++ 18 files changed, 139 insertions(+), 57 deletions(-) delete mode 100644 app/src/main/res/menu/menu_genre_actions.xml rename app/src/main/res/menu/{menu_artist_actions.xml => menu_genre_artist_actions.xml} (59%) create mode 100644 app/src/main/res/menu/menu_genre_artist_detail.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 852db0dad..96d9af42f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - "Rounded album covers" option is no longer dependent on "Show album covers" option - Added song actions to the playback panel - Playback controls are now easier to reach when gesture navigation is enabled +- Added Play Next/Add to Queue options to artists and genres #### What's Fixed - Playback bar now picks the larger inset in case that gesture inset is missing [#149] diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 49484b435..8a0c71c3e 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -88,14 +88,17 @@ class MainFragment : ViewBindingFragment() { val binding = requireBinding() when (action) { - MainNavigationAction.EXPAND -> binding.bottomSheetLayout.expand() - MainNavigationAction.COLLAPSE -> binding.bottomSheetLayout.collapse() - MainNavigationAction.SETTINGS -> + is MainNavigationAction.Expand -> binding.bottomSheetLayout.expand() + is MainNavigationAction.Collapse -> binding.bottomSheetLayout.collapse() + is MainNavigationAction.Settings -> findNavController().navigate(MainFragmentDirections.actionShowSettings()) - MainNavigationAction.ABOUT -> + is MainNavigationAction.About -> findNavController().navigate(MainFragmentDirections.actionShowAbout()) - MainNavigationAction.QUEUE -> + is MainNavigationAction.Queue -> findNavController().navigate(MainFragmentDirections.actionShowQueue()) + is MainNavigationAction.SongDetails -> + findNavController() + .navigate(MainFragmentDirections.actionShowDetails(action.song.id)) } navModel.finishMainNavigation() 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 ddbaf79ec..ee5eecc5c 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -87,6 +87,10 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { requireContext().showToast(R.string.lbl_queue_added) true } + R.id.action_go_artist -> { + navModel.exploreNavigateTo(unlikelyToBeNull(detailModel.currentAlbum.value)) + true + } else -> false } } 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 ad63576c3..4b1ac45e2 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -41,6 +41,7 @@ import org.oxycblt.auxio.util.collectWith import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW +import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -54,7 +55,8 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener { override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) { detailModel.setArtistId(args.artistId) - setupToolbar(unlikelyToBeNull(detailModel.currentArtist.value)) + setupToolbar( + unlikelyToBeNull(detailModel.currentArtist.value), R.menu.menu_genre_artist_detail) requireBinding().detailRecycler.apply { adapter = detailAdapter applySpans { pos -> @@ -72,7 +74,21 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener { launch { playbackModel.song.collectWith(playbackModel.parent, ::updatePlayback) } } - override fun onMenuItemClick(item: MenuItem): Boolean = false + override fun onMenuItemClick(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_play_next -> { + playbackModel.playNext(unlikelyToBeNull(detailModel.currentArtist.value)) + requireContext().showToast(R.string.lbl_queue_added) + true + } + R.id.action_queue_add -> { + playbackModel.addToQueue(unlikelyToBeNull(detailModel.currentArtist.value)) + requireContext().showToast(R.string.lbl_queue_added) + true + } + else -> false + } + } override fun onItemClick(item: Item) { when (item) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt index fad2ab790..6cd98cafe 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -61,14 +61,10 @@ abstract class DetailFragment : * @param data Parent data to use as the toolbar title * @param menuId Menu resource to use */ - protected fun setupToolbar(data: MusicParent, @MenuRes menuId: Int = -1) { + protected fun setupToolbar(data: MusicParent, @MenuRes menuId: Int) { requireBinding().detailToolbar.apply { title = data.resolveName(context) - - if (menuId != -1) { - inflateMenu(menuId) - } - + inflateMenu(menuId) setNavigationOnClickListener { findNavController().navigateUp() } setOnMenuItemClickListener(this@DetailFragment) } 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 cea5d44f0..9c8e0b7db 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -41,6 +41,7 @@ import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.collectWith import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -54,7 +55,8 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener { override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) { detailModel.setGenreId(args.genreId) - setupToolbar(unlikelyToBeNull(detailModel.currentGenre.value)) + setupToolbar( + unlikelyToBeNull(detailModel.currentArtist.value), R.menu.menu_genre_artist_detail) binding.detailRecycler.apply { adapter = detailAdapter applySpans { pos -> @@ -71,7 +73,21 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener { launch { playbackModel.song.collectWith(playbackModel.parent, ::updatePlayback) } } - override fun onMenuItemClick(item: MenuItem): Boolean = false + override fun onMenuItemClick(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_play_next -> { + playbackModel.playNext(unlikelyToBeNull(detailModel.currentGenre.value)) + requireContext().showToast(R.string.lbl_queue_added) + true + } + R.id.action_queue_add -> { + playbackModel.addToQueue(unlikelyToBeNull(detailModel.currentGenre.value)) + requireContext().showToast(R.string.lbl_queue_added) + true + } + else -> false + } + } override fun onItemClick(item: Item) { when (item) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt index d11b50d90..955e61364 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt @@ -22,10 +22,9 @@ import android.text.format.Formatter import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone -import org.oxycblt.auxio.BuildConfig +import androidx.navigation.fragment.navArgs import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogSongDetailBinding -import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.formatDuration @@ -33,6 +32,7 @@ import org.oxycblt.auxio.util.launch class SongDetailDialog : ViewBindingDialogFragment() { private val detailModel: DetailViewModel by androidActivityViewModels() + private val args: SongDetailDialogArgs by navArgs() override fun onCreateBinding(inflater: LayoutInflater) = DialogSongDetailBinding.inflate(inflater) @@ -44,7 +44,7 @@ class SongDetailDialog : ViewBindingDialogFragment() { override fun onBindingCreated(binding: DialogSongDetailBinding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) - detailModel.setSongId(requireNotNull(arguments).getLong(ARG_ID)) + detailModel.setSongId(args.songId) launch { detailModel.currentSong.collect(::updateSong) } } @@ -80,15 +80,4 @@ class SongDetailDialog : ViewBindingDialogFragment() { binding.detailContainer.isGone = true } } - - companion object { - fun from(song: Song): SongDetailDialog { - val instance = SongDetailDialog() - instance.arguments = Bundle().apply { putLong(ARG_ID, song.id) } - return instance - } - - const val TAG = BuildConfig.APPLICATION_ID + ".tag.SONG_DETAILS" - private const val ARG_ID = BuildConfig.APPLICATION_ID + ".arg.SONG_ID" - } } 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 9035c5d7c..d0912a568 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -161,11 +161,11 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI } R.id.action_settings -> { logD("Navigating to settings") - navModel.mainNavigateTo(MainNavigationAction.SETTINGS) + navModel.mainNavigateTo(MainNavigationAction.Settings) } R.id.action_about -> { logD("Navigating to about") - navModel.mainNavigateTo(MainNavigationAction.ABOUT) + navModel.mainNavigateTo(MainNavigationAction.About) } R.id.submenu_sorting -> { // Junk click event when opening the menu diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt index 296c6fbb2..15ab6f5db 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt @@ -51,7 +51,7 @@ class PlaybackBarFragment : ViewBindingFragment() { savedInstanceState: Bundle? ) { binding.root.apply { - setOnClickListener { navModel.mainNavigateTo(MainNavigationAction.EXPAND) } + setOnClickListener { navModel.mainNavigateTo(MainNavigationAction.Expand) } setOnLongClickListener { playbackModel.song.value?.let(navModel::exploreNavigateTo) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index 6344fcbd5..6ecec6c21 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -27,7 +27,6 @@ import androidx.fragment.app.activityViewModels import kotlin.math.max import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding -import org.oxycblt.auxio.detail.SongDetailDialog import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.RepeatMode @@ -80,7 +79,7 @@ class PlaybackPanelFragment : } binding.playbackToolbar.apply { - setNavigationOnClickListener { navModel.mainNavigateTo(MainNavigationAction.COLLAPSE) } + setNavigationOnClickListener { navModel.mainNavigateTo(MainNavigationAction.Collapse) } setOnMenuItemClickListener(this@PlaybackPanelFragment) queueItem = menu.findItem(R.id.action_queue) } @@ -136,7 +135,7 @@ class PlaybackPanelFragment : override fun onMenuItemClick(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_queue -> { - navModel.mainNavigateTo(MainNavigationAction.QUEUE) + navModel.mainNavigateTo(MainNavigationAction.Queue) true } R.id.action_go_artist -> { @@ -149,7 +148,7 @@ class PlaybackPanelFragment : } R.id.action_song_detail -> { playbackModel.song.value?.let { - SongDetailDialog.from(it).show(childFragmentManager, SongDetailDialog.TAG) + navModel.mainNavigateTo(MainNavigationAction.SongDetails(it)) } true } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index b2a3e8d70..43cfe4da2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -223,6 +223,16 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore playbackManager.playNext(settingsManager.detailAlbumSort.songs(album.songs)) } + /** Add an [Artist] to the top of the queue. */ + fun playNext(artist: Artist) { + playbackManager.playNext(settingsManager.detailArtistSort.songs(artist.songs)) + } + + /** Add a [Genre] to the top of the queue. */ + fun playNext(genre: Genre) { + playbackManager.playNext(settingsManager.detailGenreSort.songs(genre.songs)) + } + /** Add a [Song] to the end of the queue. */ fun addToQueue(song: Song) { playbackManager.addToQueue(song) @@ -233,6 +243,16 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore playbackManager.addToQueue(settingsManager.detailAlbumSort.songs(album.songs)) } + /** Add an [Artist] to the end of the queue. */ + fun addToQueue(artist: Artist) { + playbackManager.addToQueue(settingsManager.detailArtistSort.songs(artist.songs)) + } + + /** Add a [Genre] to the end of the queue. */ + fun addToQueue(genre: Genre) { + playbackManager.addToQueue(settingsManager.detailGenreSort.songs(genre.songs)) + } + // --- STATUS FUNCTIONS --- /** Flip the playing status, e.g from playing to paused */ diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt index 91bdceea8..32917a277 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt @@ -26,7 +26,6 @@ import androidx.core.view.children import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import org.oxycblt.auxio.R -import org.oxycblt.auxio.detail.SongDetailDialog import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -55,7 +54,7 @@ fun Fragment.newMenu(anchor: View, data: Item, flag: Int = ActionMenu.FLAG_NONE) * @throws IllegalStateException When there is no menu for this specific datatype/flag * @author OxygenCobalt * - * TODO: Prevent duplicate menus from showing up + * TODO: Prevent duplicate menus from showing up (merge into ViewBindingFragment) * * TODO: Add multi-select */ @@ -118,8 +117,8 @@ class ActionMenu( else -> -1 } } - is Artist -> R.menu.menu_artist_actions - is Genre -> R.menu.menu_genre_actions + is Artist, + is Genre -> R.menu.menu_genre_artist_actions else -> -1 } } @@ -153,6 +152,14 @@ class ActionMenu( playbackModel.playNext(data) context.showToast(R.string.lbl_queue_added) } + is Artist -> { + playbackModel.playNext(data) + context.showToast(R.string.lbl_queue_added) + } + is Genre -> { + playbackModel.playNext(data) + context.showToast(R.string.lbl_queue_added) + } else -> {} } } @@ -166,6 +173,14 @@ class ActionMenu( playbackModel.addToQueue(data) context.showToast(R.string.lbl_queue_added) } + is Artist -> { + playbackModel.addToQueue(data) + context.showToast(R.string.lbl_queue_added) + } + is Genre -> { + playbackModel.addToQueue(data) + context.showToast(R.string.lbl_queue_added) + } else -> {} } } @@ -183,8 +198,7 @@ class ActionMenu( } R.id.action_song_detail -> { if (data is Song) { - SongDetailDialog.from(data) - .show(activity.supportFragmentManager, SongDetailDialog.TAG) + navModel.mainNavigateTo(MainNavigationAction.SongDetails(data)) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt b/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt index ba919e3bc..018a75297 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.music.Song /** * A ViewModel that handles complicated navigation situations. @@ -68,15 +69,17 @@ class NavigationViewModel : ViewModel() { * normal fragments. This can be passed to [NavigationViewModel.mainNavigateTo] in order to * facilitate navigation without stupid fragment hacks. */ -enum class MainNavigationAction { +sealed class MainNavigationAction { /** Expand the playback panel. */ - EXPAND, + object Expand : MainNavigationAction() /** Collapse the playback panel. */ - COLLAPSE, + object Collapse : MainNavigationAction() /** Go to settings. */ - SETTINGS, + object Settings : MainNavigationAction() /** Go to the about page. */ - ABOUT, + object About : MainNavigationAction() /** Go to the queue. */ - QUEUE + object Queue : MainNavigationAction() + /** Show song details. */ + data class SongDetails(val song: Song) : MainNavigationAction() } diff --git a/app/src/main/res/menu/menu_album_detail.xml b/app/src/main/res/menu/menu_album_detail.xml index e480d9191..4c5d8d7d5 100644 --- a/app/src/main/res/menu/menu_album_detail.xml +++ b/app/src/main/res/menu/menu_album_detail.xml @@ -6,4 +6,7 @@ + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_genre_actions.xml b/app/src/main/res/menu/menu_genre_actions.xml deleted file mode 100644 index 03747adeb..000000000 --- a/app/src/main/res/menu/menu_genre_actions.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/menu_artist_actions.xml b/app/src/main/res/menu/menu_genre_artist_actions.xml similarity index 59% rename from app/src/main/res/menu/menu_artist_actions.xml rename to app/src/main/res/menu/menu_genre_artist_actions.xml index 03747adeb..df535f77d 100644 --- a/app/src/main/res/menu/menu_artist_actions.xml +++ b/app/src/main/res/menu/menu_genre_artist_actions.xml @@ -6,4 +6,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_genre_artist_detail.xml b/app/src/main/res/menu/menu_genre_artist_detail.xml new file mode 100644 index 000000000..e480d9191 --- /dev/null +++ b/app/src/main/res/menu/menu_genre_artist_detail.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_main.xml b/app/src/main/res/navigation/nav_main.xml index 04d9dd63c..6fe7d613f 100644 --- a/app/src/main/res/navigation/nav_main.xml +++ b/app/src/main/res/navigation/nav_main.xml @@ -29,6 +29,9 @@ app:exitAnim="@anim/nav_default_exit_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popExitAnim="@anim/nav_default_pop_exit_anim" /> + + + + \ No newline at end of file