list: unify viewmodels

Unify the disjoint selection and menu viewmodels into a single
ListViewModel.

This is technically not fully done, as MenuViewModel remains as a
backing element of the menu dialogs, but otherwise that's it.
This commit is contained in:
Alexander Capehart 2023-07-04 20:37:55 -06:00
parent b4ffffedfd
commit 7239256e27
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
18 changed files with 256 additions and 243 deletions

View file

@ -39,7 +39,7 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import org.oxycblt.auxio.databinding.FragmentMainBinding import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.OpenPanel import org.oxycblt.auxio.playback.OpenPanel
@ -67,7 +67,7 @@ class MainFragment :
ViewTreeObserver.OnPreDrawListener, ViewTreeObserver.OnPreDrawListener,
NavController.OnDestinationChangedListener { NavController.OnDestinationChangedListener {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val selectionModel: SelectionViewModel by activityViewModels() private val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private var sheetBackCallback: SheetBackPressedCallback? = null private var sheetBackCallback: SheetBackPressedCallback? = null
private var detailBackCallback: DetailBackPressedCallback? = null private var detailBackCallback: DetailBackPressedCallback? = null
@ -100,7 +100,7 @@ class MainFragment :
val detailBackCallback = val detailBackCallback =
DetailBackPressedCallback(detailModel).also { detailBackCallback = it } DetailBackPressedCallback(detailModel).also { detailBackCallback = it }
val selectionBackCallback = val selectionBackCallback =
SelectionBackPressedCallback(selectionModel).also { selectionBackCallback = it } SelectionBackPressedCallback(listModel).also { selectionBackCallback = it }
val exploreBackCallback = val exploreBackCallback =
ExploreBackPressedCallback(binding.exploreNavHost).also { exploreBackCallback = it } ExploreBackPressedCallback(binding.exploreNavHost).also { exploreBackCallback = it }
@ -152,7 +152,7 @@ class MainFragment :
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
collectImmediately(detailModel.editedPlaylist, detailBackCallback::invalidateEnabled) collectImmediately(detailModel.editedPlaylist, detailBackCallback::invalidateEnabled)
collectImmediately(selectionModel.selected, selectionBackCallback::invalidateEnabled) collectImmediately(listModel.selected, selectionBackCallback::invalidateEnabled)
collectImmediately(playbackModel.song, ::updateSong) collectImmediately(playbackModel.song, ::updateSong)
collectImmediately(playbackModel.openPanel.flow, ::handlePanel) collectImmediately(playbackModel.openPanel.flow, ::handlePanel)
} }
@ -289,7 +289,7 @@ class MainFragment :
initialNavDestinationChange = true initialNavDestinationChange = true
return return
} }
selectionModel.drop() listModel.dropSelection()
} }
private fun updateSong(song: Song?) { private fun updateSong(song: Song?) {
@ -450,11 +450,10 @@ class MainFragment :
} }
} }
private inner class SelectionBackPressedCallback( private inner class SelectionBackPressedCallback(private val listModel: ListViewModel) :
private val selectionModel: SelectionViewModel OnBackPressedCallback(false) {
) : OnBackPressedCallback(false) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
if (selectionModel.drop()) { if (listModel.dropSelection()) {
logD("Dropped selection") logD("Dropped selection")
} }
} }

View file

@ -39,10 +39,9 @@ import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.Sort 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.Album
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
@ -75,8 +74,7 @@ class AlbumDetailFragment :
AlbumDetailHeaderAdapter.Listener, AlbumDetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Song> { DetailListAdapter.Listener<Song> {
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what album to display is initially within the navigation arguments // Information about what album to display is initially within the navigation arguments
@ -128,8 +126,8 @@ class AlbumDetailFragment :
collectImmediately(detailModel.currentAlbum, ::updateAlbum) collectImmediately(detailModel.currentAlbum, ::updateAlbum)
collectImmediately(detailModel.albumList, ::updateList) collectImmediately(detailModel.albumList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collect(menuModel.pendingMenu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision) collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -187,7 +185,7 @@ class AlbumDetailFragment :
} }
override fun onOpenMenu(item: Song, anchor: View) { override fun onOpenMenu(item: Song, anchor: View) {
menuModel.open(R.menu.item_album_song, item) listModel.openMenu(R.menu.item_album_song, item)
} }
override fun onPlay() { override fun onPlay() {
@ -304,17 +302,16 @@ class AlbumDetailFragment :
} }
} }
private fun handleMenu(pendingMenu: PendingMenu?) { private fun handleMenu(menu: Menu?) {
if (pendingMenu == null) return if (menu == null) return
val directions = val directions =
when (pendingMenu) { when (menu) {
is PendingMenu.ForSong -> is Menu.ForSong ->
AlbumDetailFragmentDirections.openSongMenu( AlbumDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForAlbum,
is PendingMenu.ForAlbum, is Menu.ForArtist,
is PendingMenu.ForArtist, is Menu.ForGenre,
is PendingMenu.ForGenre, is Menu.ForPlaylist -> error("Unexpected menu $menu")
is PendingMenu.ForPlaylist -> error("Unexpected menu $pendingMenu")
} }
findNavController().navigateSafe(directions) findNavController().navigateSafe(directions)
} }

View file

@ -39,10 +39,9 @@ import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.Sort 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.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
@ -73,8 +72,7 @@ class ArtistDetailFragment :
DetailHeaderAdapter.Listener, DetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Music> { DetailListAdapter.Listener<Music> {
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what artist to display is initially within the navigation arguments // Information about what artist to display is initially within the navigation arguments
@ -129,8 +127,8 @@ class ArtistDetailFragment :
collectImmediately(detailModel.currentArtist, ::updateArtist) collectImmediately(detailModel.currentArtist, ::updateArtist)
collectImmediately(detailModel.artistList, ::updateList) collectImmediately(detailModel.artistList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collect(menuModel.pendingMenu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision) collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -198,8 +196,8 @@ class ArtistDetailFragment :
override fun onOpenMenu(item: Music, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
when (item) { when (item) {
is Song -> menuModel.open(R.menu.item_artist_song, item) is Song -> listModel.openMenu(R.menu.item_artist_song, item)
is Album -> menuModel.open(R.menu.item_artist_album, item) is Album -> listModel.openMenu(R.menu.item_artist_album, item)
else -> error("Unexpected datatype: ${item::class.simpleName}") else -> error("Unexpected datatype: ${item::class.simpleName}")
} }
} }
@ -314,19 +312,17 @@ class ArtistDetailFragment :
} }
} }
private fun handleMenu(pendingMenu: PendingMenu?) { private fun handleMenu(menu: Menu?) {
if (pendingMenu == null) return if (menu == null) return
val directions = val directions =
when (pendingMenu) { when (menu) {
is PendingMenu.ForSong -> is Menu.ForSong ->
ArtistDetailFragmentDirections.openSongMenu( ArtistDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForAlbum ->
is PendingMenu.ForAlbum -> ArtistDetailFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid)
ArtistDetailFragmentDirections.openAlbumMenu( is Menu.ForArtist,
pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForGenre,
is PendingMenu.ForArtist, is Menu.ForPlaylist -> error("Unexpected menu $menu")
is PendingMenu.ForGenre,
is PendingMenu.ForPlaylist -> error("Unexpected menu $pendingMenu")
} }
findNavController().navigateSafe(directions) findNavController().navigateSafe(directions)
} }

View file

@ -39,10 +39,9 @@ import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.Sort 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.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
@ -73,8 +72,7 @@ class GenreDetailFragment :
DetailHeaderAdapter.Listener, DetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Music> { DetailListAdapter.Listener<Music> {
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what genre to display is initially within the navigation arguments // Information about what genre to display is initially within the navigation arguments
@ -127,8 +125,8 @@ class GenreDetailFragment :
collectImmediately(detailModel.currentGenre, ::updatePlaylist) collectImmediately(detailModel.currentGenre, ::updatePlaylist)
collectImmediately(detailModel.genreList, ::updateList) collectImmediately(detailModel.genreList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collect(menuModel.pendingMenu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collect(musicModel.playlistDecision.flow, ::handleDecision) collect(musicModel.playlistDecision.flow, ::handleDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -196,8 +194,8 @@ class GenreDetailFragment :
override fun onOpenMenu(item: Music, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
when (item) { when (item) {
is Artist -> menuModel.open(R.menu.item_parent, item) is Artist -> listModel.openMenu(R.menu.item_parent, item)
is Song -> menuModel.open(R.menu.item_song, item) is Song -> listModel.openMenu(R.menu.item_song, item)
else -> error("Unexpected datatype: ${item::class.simpleName}") else -> error("Unexpected datatype: ${item::class.simpleName}")
} }
} }
@ -302,19 +300,17 @@ class GenreDetailFragment :
} }
} }
private fun handleMenu(pendingMenu: PendingMenu?) { private fun handleMenu(menu: Menu?) {
if (pendingMenu == null) return if (menu == null) return
val directions = val directions =
when (pendingMenu) { when (menu) {
is PendingMenu.ForSong -> is Menu.ForSong ->
GenreDetailFragmentDirections.openSongMenu( GenreDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForArtist ->
is PendingMenu.ForArtist -> GenreDetailFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid)
GenreDetailFragmentDirections.openArtistMenu( is Menu.ForAlbum,
pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForGenre,
is PendingMenu.ForAlbum, is Menu.ForPlaylist -> error("Unexpected menu $menu")
is PendingMenu.ForGenre,
is PendingMenu.ForPlaylist -> error("Unexpected menu $pendingMenu")
} }
findNavController().navigateSafe(directions) findNavController().navigateSafe(directions)
} }

View file

@ -43,9 +43,8 @@ import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.MenuViewModel import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.menu.PendingMenu import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
@ -76,8 +75,7 @@ class PlaylistDetailFragment :
PlaylistDetailListAdapter.Listener, PlaylistDetailListAdapter.Listener,
NavController.OnDestinationChangedListener { NavController.OnDestinationChangedListener {
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what playlist to display is initially within the navigation arguments // Information about what playlist to display is initially within the navigation arguments
@ -142,8 +140,8 @@ class PlaylistDetailFragment :
collectImmediately(detailModel.playlistList, ::updateList) collectImmediately(detailModel.playlistList, ::updateList)
collectImmediately(detailModel.editedPlaylist, ::updateEditedList) collectImmediately(detailModel.editedPlaylist, ::updateEditedList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collect(menuModel.pendingMenu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collect(musicModel.playlistDecision.flow, ::handleDecision) collect(musicModel.playlistDecision.flow, ::handleDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -239,7 +237,7 @@ class PlaylistDetailFragment :
} }
override fun onOpenMenu(item: Song, anchor: View) { override fun onOpenMenu(item: Song, anchor: View) {
menuModel.open(R.menu.item_playlist_song, item) listModel.openMenu(R.menu.item_playlist_song, item)
} }
override fun onPlay() { override fun onPlay() {
@ -286,7 +284,7 @@ class PlaylistDetailFragment :
private fun updateEditedList(editedPlaylist: List<Song>?) { private fun updateEditedList(editedPlaylist: List<Song>?) {
playlistListAdapter.setEditing(editedPlaylist != null) playlistListAdapter.setEditing(editedPlaylist != null)
playlistHeaderAdapter.setEditedPlaylist(editedPlaylist) playlistHeaderAdapter.setEditedPlaylist(editedPlaylist)
selectionModel.drop() listModel.dropSelection()
if (editedPlaylist != null) { if (editedPlaylist != null) {
logD("Updating save button state") logD("Updating save button state")
@ -349,17 +347,16 @@ class PlaylistDetailFragment :
} }
} }
private fun handleMenu(pendingMenu: PendingMenu?) { private fun handleMenu(menu: Menu?) {
if (pendingMenu == null) return if (menu == null) return
val directions = val directions =
when (pendingMenu) { when (menu) {
is PendingMenu.ForSong -> is Menu.ForSong ->
PlaylistDetailFragmentDirections.openSongMenu( PlaylistDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForArtist,
is PendingMenu.ForArtist, is Menu.ForAlbum,
is PendingMenu.ForAlbum, is Menu.ForGenre,
is PendingMenu.ForGenre, is Menu.ForPlaylist -> error("Unexpected menu $menu")
is PendingMenu.ForPlaylist -> error("Unexpected menu $pendingMenu")
} }
findNavController().navigateSafe(directions) findNavController().navigateSafe(directions)
} }
@ -421,7 +418,7 @@ class PlaylistDetailFragment :
logD("Currently editing playlist, showing edit toolbar") logD("Currently editing playlist, showing edit toolbar")
R.id.detail_edit_toolbar R.id.detail_edit_toolbar
} }
selectionModel.selected.value.isNotEmpty() -> { listModel.selected.value.isNotEmpty() -> {
logD("Currently selecting, showing selection toolbar") logD("Currently selecting, showing selection toolbar")
R.id.detail_selection_toolbar R.id.detail_selection_toolbar
} }

View file

@ -55,11 +55,10 @@ import org.oxycblt.auxio.home.list.PlaylistListFragment
import org.oxycblt.auxio.home.list.SongListFragment import org.oxycblt.auxio.home.list.SongListFragment
import org.oxycblt.auxio.home.tabs.AdaptiveTabStrategy import org.oxycblt.auxio.home.tabs.AdaptiveTabStrategy
import org.oxycblt.auxio.home.tabs.Tab 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.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 import org.oxycblt.auxio.music.IndexingProgress
import org.oxycblt.auxio.music.IndexingState import org.oxycblt.auxio.music.IndexingState
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
@ -89,8 +88,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
@AndroidEntryPoint @AndroidEntryPoint
class HomeFragment : class HomeFragment :
SelectionFragment<FragmentHomeBinding>(), AppBarLayout.OnOffsetChangedListener { SelectionFragment<FragmentHomeBinding>(), AppBarLayout.OnOffsetChangedListener {
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
@ -104,8 +102,7 @@ class HomeFragment :
// Orientation change will wipe whatever transition we were using prior, which will // Orientation change will wipe whatever transition we were using prior, which will
// result in no transition when the user navigates back. Make sure we re-initialize // result in no transition when the user navigates back. Make sure we re-initialize
// our transitions. // our transitions.
val id = savedInstanceState.getInt(KEY_LAST_TRANSITION_ID, -2) when (val id = savedInstanceState.getInt(KEY_LAST_TRANSITION_ID, -2)) {
when (id) {
-2 -> {} -2 -> {}
-1 -> applyFadeTransition() -1 -> applyFadeTransition()
else -> applyAxisTransition(id) else -> applyAxisTransition(id)
@ -178,8 +175,8 @@ class HomeFragment :
collect(homeModel.recreateTabs.flow, ::handleRecreate) collect(homeModel.recreateTabs.flow, ::handleRecreate)
collectImmediately(homeModel.currentTabMode, ::updateCurrentTab) collectImmediately(homeModel.currentTabMode, ::updateCurrentTab)
collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab) collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab)
collect(menuModel.pendingMenu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately(musicModel.indexingState, ::updateIndexerState) collectImmediately(musicModel.indexingState, ::updateIndexerState)
collect(musicModel.playlistDecision.flow, ::handleDecision) collect(musicModel.playlistDecision.flow, ::handleDecision)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
@ -582,22 +579,19 @@ class HomeFragment :
} }
} }
private fun handleMenu(pendingMenu: PendingMenu?) { private fun handleMenu(menu: Menu?) {
if (pendingMenu == null) return if (menu == null) return
val directions = val directions =
when (pendingMenu) { when (menu) {
is PendingMenu.ForSong -> is Menu.ForSong -> HomeFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
HomeFragmentDirections.openSongMenu(pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForAlbum ->
is PendingMenu.ForAlbum -> HomeFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid)
HomeFragmentDirections.openAlbumMenu(pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForArtist ->
is PendingMenu.ForArtist -> HomeFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid)
HomeFragmentDirections.openArtistMenu( is Menu.ForGenre ->
pendingMenu.menuRes, pendingMenu.music.uid) HomeFragmentDirections.openGenreMenu(menu.menuRes, menu.music.uid)
is PendingMenu.ForGenre -> is Menu.ForPlaylist ->
HomeFragmentDirections.openGenreMenu(pendingMenu.menuRes, pendingMenu.music.uid) HomeFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid)
is PendingMenu.ForPlaylist ->
HomeFragmentDirections.openPlaylistMenu(
pendingMenu.menuRes, pendingMenu.music.uid)
} }
findNavController().navigateSafe(directions) findNavController().navigateSafe(directions)
} }

View file

@ -32,12 +32,11 @@ import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter 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.recycler.AlbumViewHolder
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
@ -61,8 +60,7 @@ class AlbumListFragment :
FastScrollRecyclerView.PopupProvider { FastScrollRecyclerView.PopupProvider {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
private val albumAdapter = AlbumAdapter(this) private val albumAdapter = AlbumAdapter(this)
@ -84,7 +82,7 @@ class AlbumListFragment :
} }
collectImmediately(homeModel.albumsList, ::updateAlbums) collectImmediately(homeModel.albumsList, ::updateAlbums)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
} }
@ -144,7 +142,7 @@ class AlbumListFragment :
} }
override fun onOpenMenu(item: Album, anchor: View) { override fun onOpenMenu(item: Album, anchor: View) {
menuModel.open(R.menu.item_album, item) listModel.openMenu(R.menu.item_album, item)
} }
private fun updateAlbums(albums: List<Album>) { private fun updateAlbums(albums: List<Album>) {

View file

@ -30,12 +30,11 @@ import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter 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.recycler.ArtistViewHolder
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
@ -59,8 +58,7 @@ class ArtistListFragment :
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
private val artistAdapter = ArtistAdapter(this) private val artistAdapter = ArtistAdapter(this)
@ -79,7 +77,7 @@ class ArtistListFragment :
} }
collectImmediately(homeModel.artistsList, ::updateArtists) collectImmediately(homeModel.artistsList, ::updateArtists)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
} }
@ -120,7 +118,7 @@ class ArtistListFragment :
} }
override fun onOpenMenu(item: Artist, anchor: View) { override fun onOpenMenu(item: Artist, anchor: View) {
menuModel.open(R.menu.item_parent, item) listModel.openMenu(R.menu.item_parent, item)
} }
private fun updateArtists(artists: List<Artist>) { private fun updateArtists(artists: List<Artist>) {

View file

@ -30,12 +30,11 @@ import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter 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.recycler.GenreViewHolder
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
@ -58,8 +57,7 @@ class GenreListFragment :
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
private val genreAdapter = GenreAdapter(this) private val genreAdapter = GenreAdapter(this)
@ -78,7 +76,7 @@ class GenreListFragment :
} }
collectImmediately(homeModel.genresList, ::updateGenres) collectImmediately(homeModel.genresList, ::updateGenres)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
} }
@ -119,7 +117,7 @@ class GenreListFragment :
} }
override fun onOpenMenu(item: Genre, anchor: View) { override fun onOpenMenu(item: Genre, anchor: View) {
menuModel.open(R.menu.item_parent, item) listModel.openMenu(R.menu.item_parent, item)
} }
private fun updateGenres(genres: List<Genre>) { private fun updateGenres(genres: List<Genre>) {

View file

@ -29,12 +29,11 @@ import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter 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.recycler.PlaylistViewHolder
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
@ -56,8 +55,7 @@ class PlaylistListFragment :
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
private val playlistAdapter = PlaylistAdapter(this) private val playlistAdapter = PlaylistAdapter(this)
@ -76,7 +74,7 @@ class PlaylistListFragment :
} }
collectImmediately(homeModel.playlistsList, ::updatePlaylists) collectImmediately(homeModel.playlistsList, ::updatePlaylists)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
} }
@ -117,7 +115,7 @@ class PlaylistListFragment :
} }
override fun onOpenMenu(item: Playlist, anchor: View) { override fun onOpenMenu(item: Playlist, anchor: View) {
menuModel.open(R.menu.item_playlist, item) listModel.openMenu(R.menu.item_playlist, item)
} }
private fun updatePlaylists(playlists: List<Playlist>) { private fun updatePlaylists(playlists: List<Playlist>) {

View file

@ -31,12 +31,11 @@ import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter 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.recycler.SongViewHolder
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
@ -58,8 +57,7 @@ class SongListFragment :
FastScrollRecyclerView.PopupProvider, FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
private val songAdapter = SongAdapter(this) private val songAdapter = SongAdapter(this)
@ -81,7 +79,7 @@ class SongListFragment :
} }
collectImmediately(homeModel.songsList, ::updateSongs) collectImmediately(homeModel.songsList, ::updateSongs)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
} }
@ -143,7 +141,7 @@ class SongListFragment :
} }
override fun onOpenMenu(item: Song, anchor: View) { override fun onOpenMenu(item: Song, anchor: View) {
menuModel.open(R.menu.item_song, item) listModel.openMenu(R.menu.item_song, item)
} }
private fun updateSongs(songs: List<Song>) { private fun updateSongs(songs: List<Song>) {

View file

@ -24,7 +24,6 @@ import androidx.appcompat.widget.PopupMenu
import androidx.core.view.MenuCompat import androidx.core.view.MenuCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import org.oxycblt.auxio.list.selection.SelectionFragment
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -52,9 +51,9 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
abstract fun onRealClick(item: T) abstract fun onRealClick(item: T)
final override fun onClick(item: T, viewHolder: RecyclerView.ViewHolder) { final override fun onClick(item: T, viewHolder: RecyclerView.ViewHolder) {
if (selectionModel.selected.value.isNotEmpty()) { if (listModel.selected.value.isNotEmpty()) {
// Map clicking an item to selecting an item when items are already selected. // Map clicking an item to selecting an item when items are already selected.
selectionModel.select(item) listModel.select(item)
} else { } else {
// Delegate to the concrete implementation when we don't select the item. // Delegate to the concrete implementation when we don't select the item.
onRealClick(item) onRealClick(item)
@ -62,7 +61,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
} }
final override fun onSelect(item: T) { final override fun onSelect(item: T) {
selectionModel.select(item) listModel.select(item)
} }
/** /**

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2022 Auxio Project * Copyright (c) 2023 Auxio Project
* SelectionViewModel.kt is part of Auxio. * ListViewModel.kt is part of Auxio.
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.list.selection package org.oxycblt.auxio.list
import androidx.annotation.MenuRes
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@ -32,15 +33,18 @@ import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.Event
import org.oxycblt.auxio.util.MutableEvent
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
/** /**
* A [ViewModel] that manages the current selection. * A [ViewModel] that orchestrates menu dialogs and selection state.
* *
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@HiltViewModel @HiltViewModel
class SelectionViewModel class ListViewModel
@Inject @Inject
constructor( constructor(
private val musicRepository: MusicRepository, private val musicRepository: MusicRepository,
@ -51,6 +55,9 @@ constructor(
val selected: StateFlow<List<Music>> val selected: StateFlow<List<Music>>
get() = _selected get() = _selected
private val _Menu = MutableEvent<Menu>()
val menu: Event<Menu> = _Menu
init { init {
musicRepository.addUpdateListener(this) musicRepository.addUpdateListener(this)
} }
@ -105,7 +112,7 @@ constructor(
* *
* @return A list of [Song]s collated from each item selected. * @return A list of [Song]s collated from each item selected.
*/ */
fun take(): List<Song> { fun takeSelection(): List<Song> {
logD("Taking selection") logD("Taking selection")
return _selected.value return _selected.value
.flatMap { .flatMap {
@ -125,8 +132,88 @@ constructor(
* *
* @return true if the prior selection was non-empty, false otherwise. * @return true if the prior selection was non-empty, false otherwise.
*/ */
fun drop(): Boolean { fun dropSelection(): Boolean {
logD("Dropping selection [empty=${_selected.value.isEmpty()}]") logD("Dropping selection [empty=${_selected.value.isEmpty()}]")
return _selected.value.isNotEmpty().also { _selected.value = listOf() } return _selected.value.isNotEmpty().also { _selected.value = listOf() }
} }
/**
* Open a menu for a [Song]. This is not a popup menu, instead actually a dialog of menu options
* with additional information.
*
* @param menuRes The resource of the menu to use.
* @param song The [Song] to show.
*/
fun openMenu(@MenuRes menuRes: Int, song: Song) {
logD("Opening menu for $song")
openImpl(Menu.ForSong(menuRes, song))
}
/**
* Open a menu for a [Album]. This is not a popup menu, instead actually a dialog of menu
* options with additional information.
*
* @param menuRes The resource of the menu to use.
* @param album The [Album] to show.
*/
fun openMenu(@MenuRes menuRes: Int, album: Album) {
logD("Opening menu for $album")
openImpl(Menu.ForAlbum(menuRes, album))
}
/**
* Open a menu for a [Artist]. This is not a popup menu, instead actually a dialog of menu
* options with additional information.
*
* @param menuRes The resource of the menu to use.
* @param artist The [Artist] to show.
*/
fun openMenu(@MenuRes menuRes: Int, artist: Artist) {
logD("Opening menu for $artist")
openImpl(Menu.ForArtist(menuRes, artist))
}
/**
* Open a menu for a [Genre]. This is not a popup menu, instead actually a dialog of menu
* options with additional information.
*
* @param menuRes The resource of the menu to use.
* @param genre The [Genre] to show.
*/
fun openMenu(@MenuRes menuRes: Int, genre: Genre) {
logD("Opening menu for $genre")
openImpl(Menu.ForGenre(menuRes, genre))
}
/**
* Open a menu for a [Playlist]. This is not a popup menu, instead actually a dialog of menu
* options with additional information.
*
* @param menuRes The resource of the menu to use.
* @param playlist The [Playlist] to show.
*/
fun openMenu(@MenuRes menuRes: Int, playlist: Playlist) {
logD("Opening menu for $playlist")
openImpl(Menu.ForPlaylist(menuRes, playlist))
}
private fun openImpl(menu: Menu) {
val existing = _Menu.flow.value
if (existing != null) {
logW("Already opening $existing, ignoring $menu")
return
}
_Menu.put(menu)
}
}
sealed interface Menu {
val menuRes: Int
val music: Music
class ForSong(@MenuRes override val menuRes: Int, override val music: Song) : Menu
class ForAlbum(@MenuRes override val menuRes: Int, override val music: Album) : Menu
class ForArtist(@MenuRes override val menuRes: Int, override val music: Artist) : Menu
class ForGenre(@MenuRes override val menuRes: Int, override val music: Genre) : Menu
class ForPlaylist(@MenuRes override val menuRes: Int, override val music: Playlist) : Menu
} }

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.list.selection package org.oxycblt.auxio.list
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.showToast
*/ */
abstract class SelectionFragment<VB : ViewBinding> : abstract class SelectionFragment<VB : ViewBinding> :
ViewBindingFragment<VB>(), Toolbar.OnMenuItemClickListener { ViewBindingFragment<VB>(), Toolbar.OnMenuItemClickListener {
protected abstract val selectionModel: SelectionViewModel protected abstract val listModel: ListViewModel
protected abstract val musicModel: MusicViewModel protected abstract val musicModel: MusicViewModel
protected abstract val playbackModel: PlaybackViewModel protected abstract val playbackModel: PlaybackViewModel
@ -46,7 +46,7 @@ abstract class SelectionFragment<VB : ViewBinding> :
super.onBindingCreated(binding, savedInstanceState) super.onBindingCreated(binding, savedInstanceState)
getSelectionToolbar(binding)?.apply { getSelectionToolbar(binding)?.apply {
// Add cancel and menu item listeners to manage what occurs with the selection. // Add cancel and menu item listeners to manage what occurs with the selection.
setNavigationOnClickListener { selectionModel.drop() } setNavigationOnClickListener { listModel.dropSelection() }
setOnMenuItemClickListener(this@SelectionFragment) setOnMenuItemClickListener(this@SelectionFragment)
} }
} }
@ -59,29 +59,29 @@ abstract class SelectionFragment<VB : ViewBinding> :
override fun onMenuItemClick(item: MenuItem) = override fun onMenuItemClick(item: MenuItem) =
when (item.itemId) { when (item.itemId) {
R.id.action_selection_play_next -> { R.id.action_selection_play_next -> {
playbackModel.playNext(selectionModel.take()) playbackModel.playNext(listModel.takeSelection())
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
true true
} }
R.id.action_selection_playlist_add -> { R.id.action_selection_playlist_add -> {
musicModel.addToPlaylist(selectionModel.take()) musicModel.addToPlaylist(listModel.takeSelection())
true true
} }
R.id.action_selection_queue_add -> { R.id.action_selection_queue_add -> {
playbackModel.addToQueue(selectionModel.take()) playbackModel.addToQueue(listModel.takeSelection())
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
true true
} }
R.id.action_selection_play -> { R.id.action_selection_play -> {
playbackModel.play(selectionModel.take()) playbackModel.play(listModel.takeSelection())
true true
} }
R.id.action_selection_shuffle -> { R.id.action_selection_shuffle -> {
playbackModel.shuffle(selectionModel.take()) playbackModel.shuffle(listModel.takeSelection())
true true
} }
R.id.action_selection_share -> { R.id.action_selection_share -> {
requireContext().share(selectionModel.take()) requireContext().share(listModel.takeSelection())
true true
} }
else -> false else -> false

View file

@ -29,6 +29,7 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.DialogMenuBinding import org.oxycblt.auxio.databinding.DialogMenuBinding
import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment
@ -44,6 +45,7 @@ import org.oxycblt.auxio.util.logD
abstract class MenuDialogFragment<T : Music> : abstract class MenuDialogFragment<T : Music> :
ViewBindingBottomSheetDialogFragment<DialogMenuBinding>(), ClickableListListener<MenuItem> { ViewBindingBottomSheetDialogFragment<DialogMenuBinding>(), ClickableListListener<MenuItem> {
protected abstract val menuModel: MenuViewModel protected abstract val menuModel: MenuViewModel
protected abstract val listModel: ListViewModel
private val menuAdapter = MenuItemAdapter(@Suppress("LeakingThis") this) private val menuAdapter = MenuItemAdapter(@Suppress("LeakingThis") this)
abstract val menuRes: Int abstract val menuRes: Int
@ -66,7 +68,7 @@ abstract class MenuDialogFragment<T : Music> :
} }
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
menuModel.pendingMenu.consume() listModel.menu.consume()
menuModel.setCurrentMenu(uid) menuModel.setCurrentMenu(uid)
collectImmediately(menuModel.currentMusic, this::updateMusic) collectImmediately(menuModel.currentMusic, this::updateMusic)
} }

View file

@ -20,11 +20,13 @@ package org.oxycblt.auxio.list.menu
import android.view.MenuItem import android.view.MenuItem
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMenuBinding import org.oxycblt.auxio.databinding.DialogMenuBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
@ -41,6 +43,7 @@ import org.oxycblt.auxio.util.showToast
@AndroidEntryPoint @AndroidEntryPoint
class SongMenuDialogFragment : MenuDialogFragment<Song>() { class SongMenuDialogFragment : MenuDialogFragment<Song>() {
override val menuModel: MenuViewModel by activityViewModels() override val menuModel: MenuViewModel by activityViewModels()
override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels() private val musicModel: MusicViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
@ -83,7 +86,8 @@ class SongMenuDialogFragment : MenuDialogFragment<Song>() {
@AndroidEntryPoint @AndroidEntryPoint
class AlbumMenuDialogFragment : MenuDialogFragment<Album>() { class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
override val menuModel: MenuViewModel by activityViewModels() override val menuModel: MenuViewModel by viewModels()
override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels() private val musicModel: MusicViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
@ -127,7 +131,8 @@ class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
@AndroidEntryPoint @AndroidEntryPoint
class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() { class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
override val menuModel: MenuViewModel by activityViewModels() override val menuModel: MenuViewModel by viewModels()
override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels() private val musicModel: MusicViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
@ -189,7 +194,8 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
@AndroidEntryPoint @AndroidEntryPoint
class GenreMenuDialogFragment : MenuDialogFragment<Genre>() { class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
override val menuModel: MenuViewModel by activityViewModels() override val menuModel: MenuViewModel by viewModels()
override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels() private val musicModel: MusicViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
@ -236,7 +242,8 @@ class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
@AndroidEntryPoint @AndroidEntryPoint
class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() { class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
override val menuModel: MenuViewModel by activityViewModels() override val menuModel: MenuViewModel by viewModels()
override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels() private val musicModel: MusicViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()

View file

@ -18,29 +18,18 @@
package org.oxycblt.auxio.list.menu package org.oxycblt.auxio.list.menu
import androidx.annotation.MenuRes
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow 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.Music
import org.oxycblt.auxio.music.MusicRepository 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 import org.oxycblt.auxio.util.logW
@HiltViewModel @HiltViewModel
class MenuViewModel @Inject constructor(private val musicRepository: MusicRepository) : class MenuViewModel @Inject constructor(private val musicRepository: MusicRepository) :
ViewModel(), MusicRepository.UpdateListener { ViewModel(), MusicRepository.UpdateListener {
private val _pendingMenu = MutableEvent<PendingMenu>()
val pendingMenu: Event<PendingMenu> = _pendingMenu
private val _currentMusic = MutableStateFlow<Music?>(null) private val _currentMusic = MutableStateFlow<Music?>(null)
val currentMusic: StateFlow<Music?> = _currentMusic val currentMusic: StateFlow<Music?> = _currentMusic
@ -56,27 +45,6 @@ class MenuViewModel @Inject constructor(private val musicRepository: MusicReposi
musicRepository.removeUpdateListener(this) musicRepository.removeUpdateListener(this)
} }
fun open(@MenuRes menuRes: Int, song: Song) = openImpl(PendingMenu.ForSong(menuRes, song))
fun open(@MenuRes menuRes: Int, album: Album) = openImpl(PendingMenu.ForAlbum(menuRes, album))
fun open(@MenuRes menuRes: Int, artist: Artist) =
openImpl(PendingMenu.ForArtist(menuRes, artist))
fun open(@MenuRes menuRes: Int, genre: Genre) = openImpl(PendingMenu.ForGenre(menuRes, genre))
fun open(@MenuRes menuRes: Int, playlist: Playlist) =
openImpl(PendingMenu.ForPlaylist(menuRes, playlist))
private fun openImpl(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) { fun setCurrentMenu(uid: Music.UID) {
_currentMusic.value = musicRepository.find(uid) _currentMusic.value = musicRepository.find(uid)
if (_currentMusic.value == null) { if (_currentMusic.value == null) {
@ -84,15 +52,3 @@ class MenuViewModel @Inject constructor(private val musicRepository: MusicReposi
} }
} }
} }
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
}

View file

@ -40,9 +40,8 @@ import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.MenuViewModel import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.menu.PendingMenu import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
@ -74,8 +73,7 @@ import org.oxycblt.auxio.util.setFullWidthLookup
class SearchFragment : ListFragment<Music, FragmentSearchBinding>() { class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
private val searchModel: SearchViewModel by viewModels() private val searchModel: SearchViewModel by viewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
private val searchAdapter = SearchAdapter(this) private val searchAdapter = SearchAdapter(this)
@ -141,8 +139,8 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
collectImmediately(searchModel.searchResults, ::updateSearchResults) collectImmediately(searchModel.searchResults, ::updateSearchResults)
collect(menuModel.pendingMenu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately(selectionModel.selected, ::updateSelection) collect(listModel.menu.flow, ::handleMenu)
collect(musicModel.playlistDecision.flow, ::handleDecision) collect(musicModel.playlistDecision.flow, ::handleDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -186,11 +184,11 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
override fun onOpenMenu(item: Music, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
when (item) { when (item) {
is Song -> menuModel.open(R.menu.item_song, item) is Song -> listModel.openMenu(R.menu.item_song, item)
is Album -> menuModel.open(R.menu.item_album, item) is Album -> listModel.openMenu(R.menu.item_album, item)
is Artist -> menuModel.open(R.menu.item_parent, item) is Artist -> listModel.openMenu(R.menu.item_parent, item)
is Genre -> menuModel.open(R.menu.item_parent, item) is Genre -> listModel.openMenu(R.menu.item_parent, item)
is Playlist -> menuModel.open(R.menu.item_playlist, item) is Playlist -> listModel.openMenu(R.menu.item_playlist, item)
} }
} }
@ -262,25 +260,20 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
hideKeyboard() hideKeyboard()
} }
private fun handleMenu(pendingMenu: PendingMenu?) { private fun handleMenu(menu: Menu?) {
if (pendingMenu == null) return if (menu == null) return
val directions = val directions =
when (pendingMenu) { when (menu) {
is PendingMenu.ForSong -> is Menu.ForSong ->
SearchFragmentDirections.openSongMenu( SearchFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForAlbum ->
is PendingMenu.ForAlbum -> SearchFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid)
SearchFragmentDirections.openAlbumMenu( is Menu.ForArtist ->
pendingMenu.menuRes, pendingMenu.music.uid) SearchFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid)
is PendingMenu.ForArtist -> is Menu.ForGenre ->
SearchFragmentDirections.openArtistMenu( SearchFragmentDirections.openGenreMenu(menu.menuRes, menu.music.uid)
pendingMenu.menuRes, pendingMenu.music.uid) is Menu.ForPlaylist ->
is PendingMenu.ForGenre -> SearchFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid)
SearchFragmentDirections.openGenreMenu(
pendingMenu.menuRes, pendingMenu.music.uid)
is PendingMenu.ForPlaylist ->
SearchFragmentDirections.openPlaylistMenu(
pendingMenu.menuRes, pendingMenu.music.uid)
} }
findNavController().navigateSafe(directions) findNavController().navigateSafe(directions)
} }