From d6a20fedb37b45ed80fc9ed79a98ef607f593ffa Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Mon, 3 Jul 2023 16:52:42 -0600 Subject: [PATCH] all: use menu everywhere Use the new menu system in all applicable places. More consideration is needed right now on whether the toolbars should also have menu items, so they remain unchanged right now. --- .../auxio/detail/AlbumDetailFragment.kt | 22 ++++- .../auxio/detail/ArtistDetailFragment.kt | 26 +++++- .../auxio/detail/GenreDetailFragment.kt | 26 +++++- .../auxio/detail/PlaylistDetailFragment.kt | 22 ++++- .../org/oxycblt/auxio/home/HomeFragment.kt | 25 ++++++ .../auxio/home/list/AlbumListFragment.kt | 6 +- .../auxio/home/list/ArtistListFragment.kt | 8 +- .../auxio/home/list/GenreListFragment.kt | 8 +- .../auxio/home/list/PlaylistListFragment.kt | 8 +- .../auxio/home/list/SongListFragment.kt | 8 +- .../auxio/list/menu/MenuDialogFragment.kt | 2 +- .../auxio/list/menu/MenuDialogFragmentImpl.kt | 12 +-- .../oxycblt/auxio/list/menu/MenuViewModel.kt | 49 ++++++++++- .../oxycblt/auxio/search/SearchFragment.kt | 87 ++++++++++++------- app/src/main/res/layout/dialog_menu.xml | 40 ++++++--- app/src/main/res/navigation/main.xml | 54 +++++++++++- 16 files changed, 325 insertions(+), 78 deletions(-) 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 1197a5f78..c1e9db6ff 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -40,6 +40,8 @@ import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.menu.MenuViewModel +import org.oxycblt.auxio.list.menu.PendingMenu import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Music @@ -72,6 +74,7 @@ class AlbumDetailFragment : AlbumDetailHeaderAdapter.Listener, DetailListAdapter.Listener { override val detailModel: DetailViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels() @@ -124,6 +127,7 @@ class AlbumDetailFragment : collectImmediately(detailModel.currentAlbum, ::updateAlbum) collectImmediately(detailModel.albumList, ::updateList) collect(detailModel.toShow.flow, ::handleShow) + collect(menuModel.pendingMenu.flow, ::handleMenu) collectImmediately(selectionModel.selected, ::updateSelection) collect(musicModel.playlistDecision.flow, ::handleDecision) collectImmediately( @@ -183,7 +187,7 @@ class AlbumDetailFragment : } override fun onOpenMenu(item: Song, anchor: View) { - openMusicMenu(anchor, R.menu.item_album_song, item) + menuModel.openMenu(R.menu.item_album_song, item) } override fun onPlay() { @@ -300,6 +304,22 @@ class AlbumDetailFragment : } } + private fun handleMenu(pendingMenu: PendingMenu?) { + if (pendingMenu == null) return + val directions = + when (pendingMenu) { + is PendingMenu.ForSong -> + AlbumDetailFragmentDirections.openSongMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForAlbum, + is PendingMenu.ForArtist, + is PendingMenu.ForGenre, + is PendingMenu.ForPlaylist -> error("Unexpected menu $pendingMenu") + } + findNavController().navigateSafe(directions) + menuModel.pendingMenu.consume() + } + private fun updateSelection(selected: List) { albumListAdapter.setSelected(selected.toSet()) 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 b281ec397..61bc0fc4f 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -40,6 +40,8 @@ import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.menu.MenuViewModel +import org.oxycblt.auxio.list.menu.PendingMenu import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist @@ -70,6 +72,7 @@ class ArtistDetailFragment : DetailHeaderAdapter.Listener, DetailListAdapter.Listener { override val detailModel: DetailViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels() @@ -125,6 +128,7 @@ class ArtistDetailFragment : collectImmediately(detailModel.currentArtist, ::updateArtist) collectImmediately(detailModel.artistList, ::updateList) collect(detailModel.toShow.flow, ::handleShow) + collect(menuModel.pendingMenu.flow, ::handleMenu) collectImmediately(selectionModel.selected, ::updateSelection) collect(musicModel.playlistDecision.flow, ::handleDecision) collectImmediately( @@ -194,8 +198,8 @@ class ArtistDetailFragment : override fun onOpenMenu(item: Music, anchor: View) { when (item) { - is Song -> openMusicMenu(anchor, R.menu.item_artist_song, item) - is Album -> openMusicMenu(anchor, R.menu.item_artist_album, item) + is Song -> menuModel.openMenu(R.menu.item_artist_song, item) + is Album -> menuModel.openMenu(R.menu.item_artist_album, item) else -> error("Unexpected datatype: ${item::class.simpleName}") } } @@ -310,6 +314,24 @@ class ArtistDetailFragment : } } + private fun handleMenu(pendingMenu: PendingMenu?) { + if (pendingMenu == null) return + val directions = + when (pendingMenu) { + is PendingMenu.ForSong -> + ArtistDetailFragmentDirections.openSongMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForAlbum -> + ArtistDetailFragmentDirections.openAlbumMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForArtist, + is PendingMenu.ForGenre, + is PendingMenu.ForPlaylist -> error("Unexpected menu $pendingMenu") + } + findNavController().navigateSafe(directions) + menuModel.pendingMenu.consume() + } + private fun updateSelection(selected: List) { artistListAdapter.setSelected(selected.toSet()) 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 a8646fe24..2d393fd01 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -40,6 +40,8 @@ import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.menu.MenuViewModel +import org.oxycblt.auxio.list.menu.PendingMenu import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -70,6 +72,7 @@ class GenreDetailFragment : DetailHeaderAdapter.Listener, DetailListAdapter.Listener { override val detailModel: DetailViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels() @@ -123,6 +126,7 @@ class GenreDetailFragment : collectImmediately(detailModel.currentGenre, ::updatePlaylist) collectImmediately(detailModel.genreList, ::updateList) collect(detailModel.toShow.flow, ::handleShow) + collect(menuModel.pendingMenu.flow, ::handleMenu) collectImmediately(selectionModel.selected, ::updateSelection) collect(musicModel.playlistDecision.flow, ::handleDecision) collectImmediately( @@ -192,8 +196,8 @@ class GenreDetailFragment : override fun onOpenMenu(item: Music, anchor: View) { when (item) { - is Artist -> openMusicMenu(anchor, R.menu.item_parent, item) - is Song -> openMusicMenu(anchor, R.menu.item_song, item) + is Artist -> menuModel.openMenu(R.menu.item_parent, item) + is Song -> menuModel.openMenu(R.menu.item_song, item) else -> error("Unexpected datatype: ${item::class.simpleName}") } } @@ -298,6 +302,24 @@ class GenreDetailFragment : } } + private fun handleMenu(pendingMenu: PendingMenu?) { + if (pendingMenu == null) return + val directions = + when (pendingMenu) { + is PendingMenu.ForSong -> + GenreDetailFragmentDirections.openSongMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForArtist -> + GenreDetailFragmentDirections.openArtistMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForAlbum, + is PendingMenu.ForGenre, + is PendingMenu.ForPlaylist -> error("Unexpected menu $pendingMenu") + } + findNavController().navigateSafe(directions) + menuModel.pendingMenu.consume() + } + private fun updateSelection(selected: List) { genreListAdapter.setSelected(selected.toSet()) 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 b444abfdb..f97a745f9 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt @@ -43,6 +43,8 @@ import org.oxycblt.auxio.list.Divider import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment +import org.oxycblt.auxio.list.menu.MenuViewModel +import org.oxycblt.auxio.list.menu.PendingMenu import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent @@ -73,6 +75,7 @@ class PlaylistDetailFragment : PlaylistDetailListAdapter.Listener, NavController.OnDestinationChangedListener { override val detailModel: DetailViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels() @@ -138,6 +141,7 @@ class PlaylistDetailFragment : collectImmediately(detailModel.playlistList, ::updateList) collectImmediately(detailModel.editedPlaylist, ::updateEditedList) collect(detailModel.toShow.flow, ::handleShow) + collect(menuModel.pendingMenu.flow, ::handleMenu) collectImmediately(selectionModel.selected, ::updateSelection) collect(musicModel.playlistDecision.flow, ::handleDecision) collectImmediately( @@ -235,7 +239,7 @@ class PlaylistDetailFragment : } override fun onOpenMenu(item: Song, anchor: View) { - openMusicMenu(anchor, R.menu.item_playlist_song, item) + menuModel.openMenu(R.menu.item_playlist_song, item) } override fun onPlay() { @@ -345,6 +349,22 @@ class PlaylistDetailFragment : } } + private fun handleMenu(pendingMenu: PendingMenu?) { + if (pendingMenu == null) return + val directions = + when (pendingMenu) { + is PendingMenu.ForSong -> + PlaylistDetailFragmentDirections.openSongMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForArtist, + is PendingMenu.ForAlbum, + is PendingMenu.ForGenre, + is PendingMenu.ForPlaylist -> error("Unexpected menu $pendingMenu") + } + findNavController().navigateSafe(directions) + menuModel.pendingMenu.consume() + } + private fun updateSelection(selected: List) { playlistListAdapter.setSelected(selected.toSet()) 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 4a878826f..d0df07da6 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -56,6 +56,8 @@ import org.oxycblt.auxio.home.list.SongListFragment import org.oxycblt.auxio.home.tabs.AdaptiveTabStrategy import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.menu.MenuViewModel +import org.oxycblt.auxio.list.menu.PendingMenu import org.oxycblt.auxio.list.selection.SelectionFragment import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.IndexingProgress @@ -87,6 +89,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull @AndroidEntryPoint class HomeFragment : SelectionFragment(), AppBarLayout.OnOffsetChangedListener { + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels() @@ -175,6 +178,7 @@ class HomeFragment : collect(homeModel.recreateTabs.flow, ::handleRecreate) collectImmediately(homeModel.currentTabMode, ::updateCurrentTab) collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab) + collect(menuModel.pendingMenu.flow, ::handleMenu) collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(musicModel.indexingState, ::updateIndexerState) collect(musicModel.playlistDecision.flow, ::handleDecision) @@ -584,6 +588,27 @@ class HomeFragment : } } + private fun handleMenu(pendingMenu: PendingMenu?) { + if (pendingMenu == null) return + val directions = + when (pendingMenu) { + is PendingMenu.ForSong -> + HomeFragmentDirections.openSongMenu(pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForAlbum -> + HomeFragmentDirections.openAlbumMenu(pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForArtist -> + HomeFragmentDirections.openArtistMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForGenre -> + HomeFragmentDirections.openGenreMenu(pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForPlaylist -> + HomeFragmentDirections.openPlaylistMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + } + findNavController().navigateSafe(directions) + menuModel.pendingMenu.consume() + } + private fun updateSelection(selected: List) { val binding = requireBinding() if (selected.isNotEmpty()) { 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 f6ed98f88..9bf2bc05c 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 @@ -35,6 +35,7 @@ import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter +import org.oxycblt.auxio.list.menu.MenuViewModel import org.oxycblt.auxio.list.recycler.AlbumViewHolder import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Album @@ -60,9 +61,10 @@ class AlbumListFragment : FastScrollRecyclerView.PopupProvider { private val homeModel: HomeViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels() - override val playbackModel: PlaybackViewModel by activityViewModels() - override val musicModel: MusicViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() + override val musicModel: MusicViewModel by activityViewModels() + override val playbackModel: PlaybackViewModel by activityViewModels() private val albumAdapter = AlbumAdapter(this) // Save memory by re-using the same formatter and string builder when creating popup text private val formatterSb = StringBuilder(64) 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 86fdc3483..f994e4abe 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 @@ -33,6 +33,7 @@ import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter +import org.oxycblt.auxio.list.menu.MenuViewModel import org.oxycblt.auxio.list.recycler.ArtistViewHolder import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Artist @@ -58,9 +59,10 @@ class ArtistListFragment : FastScrollRecyclerView.Listener { private val homeModel: HomeViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels() - override val playbackModel: PlaybackViewModel by activityViewModels() - override val musicModel: MusicViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() + override val musicModel: MusicViewModel by activityViewModels() + override val playbackModel: PlaybackViewModel by activityViewModels() private val artistAdapter = ArtistAdapter(this) override fun onCreateBinding(inflater: LayoutInflater) = @@ -118,7 +120,7 @@ class ArtistListFragment : } override fun onOpenMenu(item: Artist, anchor: View) { - openMusicMenu(anchor, R.menu.item_parent, item) + menuModel.openMenu(R.menu.item_parent, item) } private fun updateArtists(artists: 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 46c26f689..f4d3bd31b 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 @@ -33,6 +33,7 @@ import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter +import org.oxycblt.auxio.list.menu.MenuViewModel import org.oxycblt.auxio.list.recycler.GenreViewHolder import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Genre @@ -57,9 +58,10 @@ class GenreListFragment : FastScrollRecyclerView.Listener { private val homeModel: HomeViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels() - override val playbackModel: PlaybackViewModel by activityViewModels() - override val musicModel: MusicViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() + override val musicModel: MusicViewModel by activityViewModels() + override val playbackModel: PlaybackViewModel by activityViewModels() private val genreAdapter = GenreAdapter(this) override fun onCreateBinding(inflater: LayoutInflater) = @@ -117,7 +119,7 @@ class GenreListFragment : } override fun onOpenMenu(item: Genre, anchor: View) { - openMusicMenu(anchor, R.menu.item_parent, item) + menuModel.openMenu(R.menu.item_parent, item) } private fun updateGenres(genres: 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 cef433bde..edf1c9780 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 @@ -32,6 +32,7 @@ import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter +import org.oxycblt.auxio.list.menu.MenuViewModel import org.oxycblt.auxio.list.recycler.PlaylistViewHolder import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Music @@ -55,9 +56,10 @@ class PlaylistListFragment : FastScrollRecyclerView.Listener { private val homeModel: HomeViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels() - override val playbackModel: PlaybackViewModel by activityViewModels() - override val musicModel: MusicViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() + override val musicModel: MusicViewModel by activityViewModels() + override val playbackModel: PlaybackViewModel by activityViewModels() private val playlistAdapter = PlaylistAdapter(this) override fun onCreateBinding(inflater: LayoutInflater) = @@ -115,7 +117,7 @@ class PlaylistListFragment : } override fun onOpenMenu(item: Playlist, anchor: View) { - openMusicMenu(anchor, R.menu.item_playlist, item) + menuModel.openMenu(R.menu.item_playlist, item) } private fun updatePlaylists(playlists: 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 b22366658..52f64f8bb 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 @@ -35,6 +35,7 @@ import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter +import org.oxycblt.auxio.list.menu.MenuViewModel import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Music @@ -59,9 +60,10 @@ class SongListFragment : FastScrollRecyclerView.Listener { private val homeModel: HomeViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels() - override val playbackModel: PlaybackViewModel by activityViewModels() - override val musicModel: MusicViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels() + override val musicModel: MusicViewModel by activityViewModels() + override val playbackModel: PlaybackViewModel by activityViewModels() private val songAdapter = SongAdapter(this) // Save memory by re-using the same formatter and string builder when creating popup text private val formatterSb = StringBuilder(64) @@ -143,7 +145,7 @@ class SongListFragment : } override fun onOpenMenu(item: Song, anchor: View) { - openMusicMenu(anchor, R.menu.item_song, item) + menuModel.openMenu(R.menu.item_song, item) } private fun updateSongs(songs: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt index 1f0317ae8..e9d20edff 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt @@ -71,7 +71,7 @@ abstract class MenuDialogFragment : menuAdapter.update(builder.children.toList(), UpdateInstructions.Diff) // --- VIEWMODEL SETUP --- - menuModel.setMusic(uid) + menuModel.setCurrentMenu(uid) collectImmediately(menuModel.currentMusic, this::updateMusic) } diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt index 9c1f8f7cc..e68af70ca 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt @@ -18,7 +18,7 @@ package org.oxycblt.auxio.list.menu -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.navArgs import dagger.hilt.android.AndroidEntryPoint import org.oxycblt.auxio.R @@ -34,7 +34,7 @@ import org.oxycblt.auxio.util.getPlural @AndroidEntryPoint class SongMenuDialogFragment : MenuDialogFragment() { - override val menuModel: MenuViewModel by viewModels() + override val menuModel: MenuViewModel by activityViewModels() private val args: SongMenuDialogFragmentArgs by navArgs() override val menuRes: Int @@ -55,7 +55,7 @@ class SongMenuDialogFragment : MenuDialogFragment() { @AndroidEntryPoint class AlbumMenuDialogFragment : MenuDialogFragment() { - override val menuModel: MenuViewModel by viewModels() + override val menuModel: MenuViewModel by activityViewModels() private val args: AlbumMenuDialogFragmentArgs by navArgs() override val menuRes: Int @@ -76,7 +76,7 @@ class AlbumMenuDialogFragment : MenuDialogFragment() { @AndroidEntryPoint class ArtistMenuDialogFragment : MenuDialogFragment() { - override val menuModel: MenuViewModel by viewModels() + override val menuModel: MenuViewModel by activityViewModels() private val args: ArtistMenuDialogFragmentArgs by navArgs() override val menuRes: Int @@ -105,7 +105,7 @@ class ArtistMenuDialogFragment : MenuDialogFragment() { @AndroidEntryPoint class GenreMenuDialogFragment : MenuDialogFragment() { - override val menuModel: MenuViewModel by viewModels() + override val menuModel: MenuViewModel by activityViewModels() private val args: GenreMenuDialogFragmentArgs by navArgs() override val menuRes: Int @@ -130,7 +130,7 @@ class GenreMenuDialogFragment : MenuDialogFragment() { @AndroidEntryPoint class PlaylistMenuDialogFragment : MenuDialogFragment() { - override val menuModel: MenuViewModel by viewModels() + override val menuModel: MenuViewModel by activityViewModels() private val args: PlaylistMenuDialogFragmentArgs by navArgs() override val menuRes: Int diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt index 91fccff25..af27a2e99 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt @@ -18,18 +18,29 @@ package org.oxycblt.auxio.list.menu +import androidx.annotation.MenuRes import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicRepository +import org.oxycblt.auxio.music.Playlist +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.util.Event +import org.oxycblt.auxio.util.MutableEvent import org.oxycblt.auxio.util.logW @HiltViewModel class MenuViewModel @Inject constructor(private val musicRepository: MusicRepository) : ViewModel(), MusicRepository.UpdateListener { + private val _pendingMenu = MutableEvent() + val pendingMenu: Event = _pendingMenu + private val _currentMusic = MutableStateFlow(null) val currentMusic: StateFlow = _currentMusic @@ -45,10 +56,46 @@ class MenuViewModel @Inject constructor(private val musicRepository: MusicReposi musicRepository.removeUpdateListener(this) } - fun setMusic(uid: Music.UID) { + fun openMenu(@MenuRes menuRes: Int, song: Song) = + openMenuImpl(PendingMenu.ForSong(menuRes, song)) + + fun openMenu(@MenuRes menuRes: Int, album: Album) = + openMenuImpl(PendingMenu.ForAlbum(menuRes, album)) + + fun openMenu(@MenuRes menuRes: Int, artist: Artist) = + openMenuImpl(PendingMenu.ForArtist(menuRes, artist)) + + fun openMenu(@MenuRes menuRes: Int, genre: Genre) = + openMenuImpl(PendingMenu.ForGenre(menuRes, genre)) + + fun openMenu(@MenuRes menuRes: Int, playlist: Playlist) = + openMenuImpl(PendingMenu.ForPlaylist(menuRes, playlist)) + + private fun openMenuImpl(pendingMenu: PendingMenu) { + val existing = _pendingMenu.flow.value + if (existing != null) { + logW("Already opening $existing, ignoring $pendingMenu") + return + } + _pendingMenu.put(pendingMenu) + } + + fun setCurrentMenu(uid: Music.UID) { _currentMusic.value = musicRepository.find(uid) if (_currentMusic.value == null) { logW("Given Music UID to show was invalid") } } } + +sealed interface PendingMenu { + val menuRes: Int + val music: Music + + class ForSong(@MenuRes override val menuRes: Int, override val music: Song) : PendingMenu + class ForAlbum(@MenuRes override val menuRes: Int, override val music: Album) : PendingMenu + class ForArtist(@MenuRes override val menuRes: Int, override val music: Artist) : PendingMenu + class ForGenre(@MenuRes override val menuRes: Int, override val music: Genre) : PendingMenu + class ForPlaylist(@MenuRes override val menuRes: Int, override val music: Playlist) : + PendingMenu +} 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 ff41e9910..7d0e8e4e8 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -36,11 +36,12 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentSearchBinding import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.Show -import org.oxycblt.auxio.home.HomeFragmentDirections import org.oxycblt.auxio.list.Divider import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment +import org.oxycblt.auxio.list.menu.MenuViewModel +import org.oxycblt.auxio.list.menu.PendingMenu import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist @@ -71,9 +72,10 @@ import org.oxycblt.auxio.util.setFullWidthLookup @AndroidEntryPoint class SearchFragment : ListFragment() { override val detailModel: DetailViewModel by activityViewModels() + private val menuModel: MenuViewModel by activityViewModels() + override val selectionModel: SelectionViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels() - override val selectionModel: SelectionViewModel by activityViewModels() private val searchModel: SearchViewModel by viewModels() private val searchAdapter = SearchAdapter(this) private var imm: InputMethodManager? = null @@ -138,6 +140,7 @@ class SearchFragment : ListFragment() { // --- VIEWMODEL SETUP --- collectImmediately(searchModel.searchResults, ::updateSearchResults) + collect(menuModel.pendingMenu.flow, ::handleMenu) collectImmediately(selectionModel.selected, ::updateSelection) collect(musicModel.playlistDecision.flow, ::handleDecision) collectImmediately( @@ -181,11 +184,11 @@ class SearchFragment : ListFragment() { override fun onOpenMenu(item: Music, anchor: View) { when (item) { - is Song -> openMusicMenu(anchor, R.menu.item_song, item) - is Album -> openMusicMenu(anchor, R.menu.item_album, item) - is Artist -> openMusicMenu(anchor, R.menu.item_parent, item) - is Genre -> openMusicMenu(anchor, R.menu.item_parent, item) - is Playlist -> openMusicMenu(anchor, R.menu.item_playlist, item) + is Song -> menuModel.openMenu(R.menu.item_song, item) + is Album -> menuModel.openMenu(R.menu.item_album, item) + is Artist -> menuModel.openMenu(R.menu.item_parent, item) + is Genre -> menuModel.openMenu(R.menu.item_parent, item) + is Playlist -> menuModel.openMenu(R.menu.item_playlist, item) } } @@ -247,7 +250,7 @@ class SearchFragment : ListFragment() { is Show.PlaylistDetails -> { logD("Navigating to ${show.playlist}") findNavController() - .navigateSafe(SearchFragmentDirections.showGenre(show.playlist.uid)) + .navigateSafe(SearchFragmentDirections.showPlaylist(show.playlist.uid)) } null -> {} } @@ -258,32 +261,26 @@ class SearchFragment : ListFragment() { private fun handleDecision(decision: PlaylistDecision?) { if (decision == null) return - when (decision) { - is PlaylistDecision.New -> { - logD("Creating new playlist") - findNavController() - .navigateSafe( - HomeFragmentDirections.newPlaylist( - decision.songs.map { it.uid }.toTypedArray())) + val directions = + when (decision) { + is PlaylistDecision.Rename -> { + logD("Renaming ${decision.playlist}") + SearchFragmentDirections.renamePlaylist(decision.playlist.uid) + } + is PlaylistDecision.Delete -> { + logD("Deleting ${decision.playlist}") + SearchFragmentDirections.deletePlaylist(decision.playlist.uid) + } + is PlaylistDecision.Add -> { + logD("Adding ${decision.songs.size} to a playlist") + SearchFragmentDirections.addToPlaylist( + decision.songs.map { it.uid }.toTypedArray()) + } + is PlaylistDecision.New -> { + error("Unexpected decision $decision") + } } - is PlaylistDecision.Rename -> { - logD("Renaming ${decision.playlist}") - findNavController() - .navigateSafe(HomeFragmentDirections.renamePlaylist(decision.playlist.uid)) - } - is PlaylistDecision.Delete -> { - logD("Deleting ${decision.playlist}") - findNavController() - .navigateSafe(SearchFragmentDirections.deletePlaylist(decision.playlist.uid)) - } - is PlaylistDecision.Add -> { - logD("Adding ${decision.songs.size} to a playlist") - findNavController() - .navigateSafe( - HomeFragmentDirections.addToPlaylist( - decision.songs.map { it.uid }.toTypedArray())) - } - } + findNavController().navigateSafe(directions) musicModel.playlistDecision.consume() } @@ -291,6 +288,30 @@ class SearchFragment : ListFragment() { searchAdapter.setPlaying(parent ?: song, isPlaying) } + private fun handleMenu(pendingMenu: PendingMenu?) { + if (pendingMenu == null) return + val directions = + when (pendingMenu) { + is PendingMenu.ForSong -> + SearchFragmentDirections.openSongMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForAlbum -> + SearchFragmentDirections.openAlbumMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForArtist -> + SearchFragmentDirections.openArtistMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForGenre -> + SearchFragmentDirections.openGenreMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + is PendingMenu.ForPlaylist -> + SearchFragmentDirections.openPlaylistMenu( + pendingMenu.menuRes, pendingMenu.music.uid) + } + findNavController().navigateSafe(directions) + menuModel.pendingMenu.consume() + } + private fun updateSelection(selected: List) { searchAdapter.setSelected(selected.toSet()) val binding = requireBinding() diff --git a/app/src/main/res/layout/dialog_menu.xml b/app/src/main/res/layout/dialog_menu.xml index f7dbc37af..c685bfc9a 100644 --- a/app/src/main/res/layout/dialog_menu.xml +++ b/app/src/main/res/layout/dialog_menu.xml @@ -2,26 +2,28 @@ - + + \ No newline at end of file diff --git a/app/src/main/res/navigation/main.xml b/app/src/main/res/navigation/main.xml index ea498b24f..cb0ddb39b 100644 --- a/app/src/main/res/navigation/main.xml +++ b/app/src/main/res/navigation/main.xml @@ -33,6 +33,21 @@ + + + + + @@ -86,6 +101,21 @@ + + + + + @@ -123,12 +153,15 @@ - + + @@ -154,6 +187,12 @@ + + @@ -182,6 +221,12 @@ + + @@ -210,6 +255,9 @@ +