diff --git a/app/build.gradle b/app/build.gradle index c38c26e0d..aebae4fd5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -133,7 +133,7 @@ dependencies { // Material // TODO: Exactly figure out the conditions that the 1.7.0 ripple bug occurred so you can just // PR a fix. - implementation "com.google.android.material:material:1.10.0-alpha04" + implementation "com.google.android.material:material:1.10.0-alpha05" // Dependency Injection implementation "com.google.dagger:dagger:$hilt_version" 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 6dae706e3..b654902da 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -277,22 +277,20 @@ class AlbumDetailFragment : .navigateSafe(AlbumDetailFragmentDirections.showAlbum(show.album.uid)) } } - - // Always launch a new ArtistDetailFragment. is Show.ArtistDetails -> { logD("Navigating to ${show.artist}") findNavController() .navigateSafe(AlbumDetailFragmentDirections.showArtist(show.artist.uid)) } - is Show.SongArtistDetails -> { + is Show.SongArtistDecision -> { logD("Navigating to artist choices for ${show.song}") findNavController() - .navigateSafe(AlbumDetailFragmentDirections.showArtist(show.song.uid)) + .navigateSafe(AlbumDetailFragmentDirections.showArtistChoices(show.song.uid)) } - is Show.AlbumArtistDetails -> { + is Show.AlbumArtistDecision -> { logD("Navigating to artist choices for ${show.album}") findNavController() - .navigateSafe(AlbumDetailFragmentDirections.showArtist(show.album.uid)) + .navigateSafe(AlbumDetailFragmentDirections.showArtistChoices(show.album.uid)) } is Show.GenreDetails, is Show.PlaylistDetails -> { 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 35e72d7cd..6eebd76dd 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -302,8 +302,8 @@ class ArtistDetailFragment : .navigateSafe(ArtistDetailFragmentDirections.showArtist(show.artist.uid)) } } - is Show.SongArtistDetails, - is Show.AlbumArtistDetails, + is Show.SongArtistDecision, + is Show.AlbumArtistDecision, is Show.GenreDetails, is Show.PlaylistDetails -> { error("Unexpected show command $show") diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index de285ffa9..ee1fe4df0 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -71,6 +71,9 @@ constructor( private val playbackSettings: PlaybackSettings ) : ViewModel(), MusicRepository.UpdateListener { private val _toShow = MutableEvent() + /** + * A [Show] command that is awaiting a view capable of responding to it. Null if none currently. + */ val toShow: Event get() = _toShow @@ -241,32 +244,74 @@ constructor( } } + /** + * Navigate to the details (properties) of a [Song]. + * + * @param song The [Song] to navigate with. + */ fun showSong(song: Song) = showImpl(Show.SongDetails(song)) + /** + * Navigate to the [Album] details of the given [Song], scrolling to the given [Song] as well. + * + * @param song The [Song] to navigate with. + */ fun showAlbum(song: Song) = showImpl(Show.SongAlbumDetails(song)) + /** + * Navigate to the details of an [Album]. + * + * @param album The [Album] to navigate with. + */ fun showAlbum(album: Album) = showImpl(Show.AlbumDetails(album)) + /** + * Navigate to the details of one of the [Artist]s of a [Song] using the corresponding choice + * dialog. If there is only one artist, this call is identical to [showArtist]. + * + * @param song The [Song] to navigate with. + */ fun showArtist(song: Song) = showImpl( if (song.artists.size > 1) { - Show.SongArtistDetails(song) + Show.SongArtistDecision(song) } else { Show.ArtistDetails(song.artists.first()) }) + /** + * Navigate to the details of one of the [Artist]s of an [Album] using the corresponding choice + * dialog. If there is only one artist, this call is identical to [showArtist]. + * + * @param album The [Album] to navigate with. + */ fun showArtist(album: Album) = showImpl( if (album.artists.size > 1) { - Show.AlbumArtistDetails(album) + Show.AlbumArtistDecision(album) } else { Show.ArtistDetails(album.artists.first()) }) + /** + * Navigate to the details of an [Artist]. + * + * @param artist The [Artist] to navigate with. + */ fun showArtist(artist: Artist) = showImpl(Show.ArtistDetails(artist)) + /** + * Navigate to the details of a [Genre]. + * + * @param genre The [Genre] to navigate with. + */ fun showGenre(genre: Genre) = showImpl(Show.GenreDetails(genre)) + /** + * Navigate to the details of a [Playlist]. + * + * @param playlist The [Playlist] to navigate with. + */ fun showPlaylist(playlist: Playlist) = showImpl(Show.PlaylistDetails(playlist)) private fun showImpl(show: Show) { @@ -624,13 +669,68 @@ constructor( } } +/** + * A command for navigation to detail views. These can be handled partially if a certain command + * cannot occur in a specific view. + * + * @author Alexander Capehart (OxygenCobalt) + */ sealed interface Show { + /** + * Navigate to the details (properties) of a [Song]. + * + * @param song The [Song] to navigate with. + */ data class SongDetails(val song: Song) : Show + + /** + * Navigate to the details of an [Album]. + * + * @param album The [Album] to navigate with. + */ data class AlbumDetails(val album: Album) : Show + + /** + * Navigate to the [Album] details of the given [Song], scrolling to the given [Song] as well. + * + * @param song The [Song] to navigate with. + */ data class SongAlbumDetails(val song: Song) : Show + + /** + * Navigate to the details of an [Artist]. + * + * @param artist The [Artist] to navigate with. + */ data class ArtistDetails(val artist: Artist) : Show - data class SongArtistDetails(val song: Song) : Show - data class AlbumArtistDetails(val album: Album) : Show + + /** + * Navigate to the details of one of the [Artist]s of a [Song] using the corresponding choice + * dialog. + * + * @param song The [Song] to navigate with. + */ + data class SongArtistDecision(val song: Song) : Show + + /** + * Navigate to the details of one of the [Artist]s of an [Album] using the corresponding + * decision dialog. + * + * @param album The [Album] to navigate with. + */ + data class AlbumArtistDecision(val album: Album) : Show + + /** + * Navigate to the details of a [Genre]. + * + * @param genre The [Genre] to navigate with. + */ data class GenreDetails(val genre: Genre) : Show + + /** + * Navigate to the details of a [Playlist]. + * + * @param playlist The [Playlist] to navigate with. + */ data class PlaylistDetails(val playlist: Playlist) : Show } 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 7ba2cc6c9..8d26371e7 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -279,15 +279,15 @@ class GenreDetailFragment : findNavController() .navigateSafe(GenreDetailFragmentDirections.showArtist(show.artist.uid)) } - is Show.SongArtistDetails -> { + is Show.SongArtistDecision -> { logD("Navigating to artist choices for ${show.song}") findNavController() - .navigateSafe(GenreDetailFragmentDirections.showArtist(show.song.uid)) + .navigateSafe(GenreDetailFragmentDirections.showArtistChoices(show.song.uid)) } - is Show.AlbumArtistDetails -> { + is Show.AlbumArtistDecision -> { logD("Navigating to artist choices for ${show.album}") findNavController() - .navigateSafe(GenreDetailFragmentDirections.showArtist(show.album.uid)) + .navigateSafe(GenreDetailFragmentDirections.showArtistChoices(show.album.uid)) } is Show.GenreDetails -> { logD("Navigated to this genre") 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 a41e31f8b..632352075 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt @@ -303,38 +303,31 @@ class PlaylistDetailFragment : findNavController() .navigateSafe(PlaylistDetailFragmentDirections.showSong(show.song.uid)) } - - // Songs should be scrolled to if the album matches, or a new detail - // fragment should be launched otherwise. is Show.SongAlbumDetails -> { logD("Navigating to the album of ${show.song}") findNavController() .navigateSafe(PlaylistDetailFragmentDirections.showAlbum(show.song.album.uid)) } - - // If the album matches, no need to do anything. Otherwise launch a new - // detail fragment. is Show.AlbumDetails -> { logD("Navigating to ${show.album}") findNavController() .navigateSafe(PlaylistDetailFragmentDirections.showAlbum(show.album.uid)) } - - // Always launch a new ArtistDetailFragment. is Show.ArtistDetails -> { logD("Navigating to ${show.artist}") findNavController() .navigateSafe(PlaylistDetailFragmentDirections.showArtist(show.artist.uid)) } - is Show.SongArtistDetails -> { + is Show.SongArtistDecision -> { logD("Navigating to artist choices for ${show.song}") findNavController() - .navigateSafe(PlaylistDetailFragmentDirections.showArtist(show.song.uid)) + .navigateSafe(PlaylistDetailFragmentDirections.showArtistChoices(show.song.uid)) } - is Show.AlbumArtistDetails -> { + is Show.AlbumArtistDecision -> { logD("Navigating to artist choices for ${show.album}") findNavController() - .navigateSafe(PlaylistDetailFragmentDirections.showArtist(show.album.uid)) + .navigateSafe( + PlaylistDetailFragmentDirections.showArtistChoices(show.album.uid)) } is Show.PlaylistDetails -> { logD("Navigated to this playlist") diff --git a/app/src/main/java/org/oxycblt/auxio/detail/picker/ArtistShowChoice.kt b/app/src/main/java/org/oxycblt/auxio/detail/decision/ArtistShowChoice.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/detail/picker/ArtistShowChoice.kt rename to app/src/main/java/org/oxycblt/auxio/detail/decision/ArtistShowChoice.kt index f28b9d765..98a411a04 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/picker/ArtistShowChoice.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/decision/ArtistShowChoice.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.detail.picker +package org.oxycblt.auxio.detail.decision import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/org/oxycblt/auxio/detail/picker/DetailPickerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/decision/DetailDecisionViewModel.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/detail/picker/DetailPickerViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/detail/decision/DetailDecisionViewModel.kt index 3594c3c41..f4654ace4 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/picker/DetailPickerViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/decision/DetailDecisionViewModel.kt @@ -1,6 +1,6 @@ /* * Copyright (c) 2023 Auxio Project - * DetailPickerViewModel.kt is part of Auxio. + * DetailDecisionViewModel.kt is part of Auxio. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.detail.picker +package org.oxycblt.auxio.detail.decision import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/app/src/main/java/org/oxycblt/auxio/detail/picker/ShowArtistDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/decision/ShowArtistDialog.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/detail/picker/ShowArtistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/detail/decision/ShowArtistDialog.kt index 3f17ed888..1f82ddfe2 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/picker/ShowArtistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/decision/ShowArtistDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.detail.picker +package org.oxycblt.auxio.detail.decision import android.os.Bundle import android.view.LayoutInflater 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 9026057de..43dded9c6 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -532,37 +532,31 @@ class HomeFragment : logD("Navigating to ${show.song}") findNavController().navigateSafe(HomeFragmentDirections.showSong(show.song.uid)) } - - // Songs should be scrolled to if the album matches, or a new detail - // fragment should be launched otherwise. is Show.SongAlbumDetails -> { logD("Navigating to the album of ${show.song}") applyAxisTransition(MaterialSharedAxis.X) findNavController() .navigateSafe(HomeFragmentDirections.showAlbum(show.song.album.uid)) } - - // If the album matches, no need to do anything. Otherwise launch a new - // detail fragment. is Show.AlbumDetails -> { logD("Navigating to ${show.album}") applyAxisTransition(MaterialSharedAxis.X) findNavController().navigateSafe(HomeFragmentDirections.showAlbum(show.album.uid)) } - - // Always launch a new ArtistDetailFragment. is Show.ArtistDetails -> { logD("Navigating to ${show.artist}") applyAxisTransition(MaterialSharedAxis.X) findNavController().navigateSafe(HomeFragmentDirections.showArtist(show.artist.uid)) } - is Show.SongArtistDetails -> { + is Show.SongArtistDecision -> { logD("Navigating to artist choices for ${show.song}") - findNavController().navigateSafe(HomeFragmentDirections.showArtists(show.song.uid)) + findNavController() + .navigateSafe(HomeFragmentDirections.showArtistChoices(show.song.uid)) } - is Show.AlbumArtistDetails -> { + is Show.AlbumArtistDecision -> { logD("Navigating to artist choices for ${show.album}") - findNavController().navigateSafe(HomeFragmentDirections.showArtists(show.album.uid)) + findNavController() + .navigateSafe(HomeFragmentDirections.showArtistChoices(show.album.uid)) } is Show.GenreDetails -> { logD("Navigating to ${show.genre}") diff --git a/app/src/main/java/org/oxycblt/auxio/list/ListViewModel.kt b/app/src/main/java/org/oxycblt/auxio/list/ListViewModel.kt index 71a361d3a..217865df7 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/ListViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/ListViewModel.kt @@ -51,12 +51,15 @@ constructor( private val musicSettings: MusicSettings ) : ViewModel(), MusicRepository.UpdateListener { private val _selected = MutableStateFlow(listOf()) - /** the currently selected items. These are ordered in earliest selected and latest selected. */ + /** The currently selected items. These are ordered in earliest selected and latest selected. */ val selected: StateFlow> get() = _selected - private val _Menu = MutableEvent() - val menu: Event = _Menu + private val _menu = MutableEvent() + /** + * A [Menu] command that is awaiting a view capable of responding to it. Null if none currently. + */ + val menu: Event = _menu init { musicRepository.addUpdateListener(this) @@ -198,22 +201,34 @@ constructor( } private fun openImpl(menu: Menu) { - val existing = _Menu.flow.value + val existing = _menu.flow.value if (existing != null) { logW("Already opening $existing, ignoring $menu") return } - _Menu.put(menu) + _menu.put(menu) } } +/** + * Command to navigate to a specific menu dialog configuration. + * + * @author Alexander Capehart (OxygenCobalt) + */ sealed interface Menu { + /** The android resource ID of the menu options to display in the dialog. */ val menuRes: Int + /** The [Music] that the menu should act on. */ val music: Music + /** Navigate to a [Song] menu dialog. */ class ForSong(@MenuRes override val menuRes: Int, override val music: Song) : Menu + /** Navigate to a [Album] menu dialog. */ class ForAlbum(@MenuRes override val menuRes: Int, override val music: Album) : Menu + /** Navigate to a [Artist] menu dialog. */ class ForArtist(@MenuRes override val menuRes: Int, override val music: Artist) : Menu + /** Navigate to a [Genre] menu dialog. */ class ForGenre(@MenuRes override val menuRes: Int, override val music: Genre) : Menu + /** Navigate to a [Playlist] menu dialog. */ class ForPlaylist(@MenuRes override val menuRes: Int, override val music: Playlist) : Menu } diff --git a/app/src/main/java/org/oxycblt/auxio/list/SelectionFragment.kt b/app/src/main/java/org/oxycblt/auxio/list/SelectionFragment.kt index 2f810da8a..fd461d222 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/SelectionFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/SelectionFragment.kt @@ -86,4 +86,6 @@ abstract class SelectionFragment : } else -> false } + + // TODO: Re-add the automatic selection handling } 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 183908fb1..305234449 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 @@ -41,6 +41,8 @@ import org.oxycblt.auxio.util.logD * options. * * @author Alexander Capehart (OxygenCobalt) + * + * TODO: Extend the amount of music info shown in the dialog */ abstract class MenuDialogFragment : ViewBindingBottomSheetDialogFragment(), ClickableListListener { @@ -48,10 +50,33 @@ abstract class MenuDialogFragment : protected abstract val listModel: ListViewModel private val menuAdapter = MenuItemAdapter(@Suppress("LeakingThis") this) + /** The android resource ID of the menu options to display in the dialog. */ abstract val menuRes: Int + + /** The [Music.UID] of the [T] to display menu options for. */ abstract val uid: Music.UID + + /** + * Get the options to disable in the context of the currently shown [T]. + * + * @param music The currently-shown music [T]. + */ abstract fun getDisabledItemIds(music: T): Set + + /** + * Update the displayed information about the currently shown [T]. + * + * @param binding The [DialogMenuBinding] to bind information to. + * @param music The currently-shown music [T]. + */ abstract fun updateMusic(binding: DialogMenuBinding, music: T) + + /** + * Forward the clicked [MenuItem] to it's corresponding handler in another module. + * + * @param item The [MenuItem] that was clicked. + * @param music The currently-shown music [T]. + */ abstract fun onClick(item: MenuItem, music: T) override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater) @@ -69,7 +94,7 @@ abstract class MenuDialogFragment : // --- VIEWMODEL SETUP --- listModel.menu.consume() - menuModel.setCurrentMenu(uid) + menuModel.setMusic(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 a1f26d448..4aad05948 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 @@ -40,6 +40,11 @@ import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.share import org.oxycblt.auxio.util.showToast +/** + * [MenuDialogFragment] implementation for a [Song]. + * + * @author Alexander Capehart (OxygenCobalt) + */ @AndroidEntryPoint class SongMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by activityViewModels() @@ -54,6 +59,7 @@ class SongMenuDialogFragment : MenuDialogFragment() { override val uid: Music.UID get() = args.songUid + // Nothing to disable in song menus. override fun getDisabledItemIds(music: Song) = setOf() override fun updateMusic(binding: DialogMenuBinding, music: Song) { @@ -66,6 +72,7 @@ class SongMenuDialogFragment : MenuDialogFragment() { override fun onClick(item: MenuItem, music: Song) { when (item.itemId) { + // TODO: Song play and shuffle as soon as PlaybackMode is refactored R.id.action_play_next -> { playbackModel.playNext(music) requireContext().showToast(R.string.lng_queue_added) @@ -84,6 +91,11 @@ class SongMenuDialogFragment : MenuDialogFragment() { } } +/** + * [MenuDialogFragment] implementation for a [AlbumMenuDialogFragment]. + * + * @author Alexander Capehart (OxygenCobalt) + */ @AndroidEntryPoint class AlbumMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by viewModels() @@ -98,6 +110,7 @@ class AlbumMenuDialogFragment : MenuDialogFragment() { override val uid: Music.UID get() = args.albumUid + // Nothing to disable in album menus. override fun getDisabledItemIds(music: Album) = setOf() override fun updateMusic(binding: DialogMenuBinding, music: Album) { @@ -129,6 +142,11 @@ class AlbumMenuDialogFragment : MenuDialogFragment() { } } +/** + * [MenuDialogFragment] implementation for a [Artist]. + * + * @author Alexander Capehart (OxygenCobalt) + */ @AndroidEntryPoint class ArtistMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by viewModels() @@ -145,6 +163,8 @@ class ArtistMenuDialogFragment : MenuDialogFragment() { override fun getDisabledItemIds(music: Artist) = if (music.songs.isEmpty()) { + // Disable any operations that require some kind of songs to work with, as there won't + // be any in an empty artist. setOf( R.id.action_play, R.id.action_shuffle, @@ -192,6 +212,11 @@ class ArtistMenuDialogFragment : MenuDialogFragment() { } } +/** + * [MenuDialogFragment] implementation for a [Genre]. + * + * @author Alexander Capehart (OxygenCobalt) + */ @AndroidEntryPoint class GenreMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by viewModels() @@ -240,6 +265,11 @@ class GenreMenuDialogFragment : MenuDialogFragment() { } } +/** + * [MenuDialogFragment] implementation for a [Playlist]. + * + * @author Alexander Capehart (OxygenCobalt) + */ @AndroidEntryPoint class PlaylistMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by viewModels() @@ -256,6 +286,8 @@ class PlaylistMenuDialogFragment : MenuDialogFragment() { override fun getDisabledItemIds(music: Playlist) = if (music.songs.isEmpty()) { + // Disable any operations that require some kind of songs to work with, as there won't + // be any in an empty playlist. setOf( R.id.action_play, R.id.action_shuffle, 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 67c87a5d6..44af342bd 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 @@ -27,10 +27,15 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.util.logW +/** + * Manages the state information for [MenuDialogFragment] implementations. + * @author Alexander Capehart (OxygenCobalt) + */ @HiltViewModel class MenuViewModel @Inject constructor(private val musicRepository: MusicRepository) : ViewModel(), MusicRepository.UpdateListener { private val _currentMusic = MutableStateFlow(null) + /** The current [Music] information being shown in a menu dialog. */ val currentMusic: StateFlow = _currentMusic init { @@ -45,7 +50,13 @@ class MenuViewModel @Inject constructor(private val musicRepository: MusicReposi musicRepository.removeUpdateListener(this) } - fun setCurrentMenu(uid: Music.UID) { + /** + * Set a new [currentMusic] from it's [Music.UID]. [currentMusic] will be updated to align with + * the new album. + * + * @param uid The [Music.UID] of the [Music] to update [currentMusic] to. Must be valid. + */ + fun setMusic(uid: Music.UID) { _currentMusic.value = musicRepository.find(uid) if (_currentMusic.value == null) { logW("Given Music UID to show was invalid") diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt index 5c2b2b86a..6e314a4f1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt @@ -53,6 +53,10 @@ constructor( get() = _statistics private val _playlistDecision = MutableEvent() + /** + * A [PlaylistDecision] command that is awaiting a view capable of responding to it. Null if + * none currently. + */ val playlistDecision: Event get() = _playlistDecision @@ -222,9 +226,37 @@ constructor( ) } +/** + * Navigation command for when a [Playlist] must have some operation performed on it by the user. + * + * @author Alexander Capehart (OxygenCobalt) + */ sealed interface PlaylistDecision { + /** + * Navigate to a dialog that allows a user to pick a name for a new [Playlist]. + * + * @param songs The [Song]s to contain in the new [Playlist]. + */ data class New(val songs: List) : PlaylistDecision + + /** + * Navigate to a dialog that allows a user to rename an existing [Playlist]. + * + * @param playlist The playlist to act on. + */ data class Rename(val playlist: Playlist) : PlaylistDecision + + /** + * Navigate to a dialog that confirms the deletion of an existing [Playlist]. + * + * @param playlist The playlist to act on. + */ data class Delete(val playlist: Playlist) : PlaylistDecision + + /** + * Navigate to a dialog that allows the user to add [Song]s to a [Playlist]. + * + * @param songs The [Song]s to add to the chosen [Playlist]. + */ data class Add(val songs: List) : PlaylistDecision } diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/AddToPlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/AddToPlaylistDialog.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/picker/AddToPlaylistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/music/decision/AddToPlaylistDialog.kt index 72ee2de7e..57e2bdf7c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/AddToPlaylistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/AddToPlaylistDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.picker +package org.oxycblt.auxio.music.decision import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/DeletePlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/music/picker/DeletePlaylistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt index a36e1c3d4..a1683c6b0 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/DeletePlaylistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.picker +package org.oxycblt.auxio.music.decision import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/NewPlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistDialog.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/picker/NewPlaylistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistDialog.kt index 281196fde..46a2b3d0a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/NewPlaylistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.picker +package org.oxycblt.auxio.music.decision import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/NewPlaylistFooterAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistFooterAdapter.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/music/picker/NewPlaylistFooterAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistFooterAdapter.kt index fb7f1a965..2eee93bda 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/NewPlaylistFooterAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistFooterAdapter.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.picker +package org.oxycblt.auxio.music.decision import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/PlaylistChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistChoiceAdapter.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/music/picker/PlaylistChoiceAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistChoiceAdapter.kt index 02a5424e9..b45df2cdf 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/PlaylistChoiceAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistChoiceAdapter.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.picker +package org.oxycblt.auxio.music.decision import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/PlaylistPickerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistPickerViewModel.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/picker/PlaylistPickerViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistPickerViewModel.kt index 51a9895cb..def23d977 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/PlaylistPickerViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistPickerViewModel.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.picker +package org.oxycblt.auxio.music.decision import android.content.Context import androidx.lifecycle.ViewModel diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/RenamePlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/RenamePlaylistDialog.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/picker/RenamePlaylistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/music/decision/RenamePlaylistDialog.kt index 6608a9c54..4e287dc87 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/RenamePlaylistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/RenamePlaylistDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.picker +package org.oxycblt.auxio.music.decision import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index d273189bf..af1efd658 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -242,8 +242,8 @@ class PlaybackPanelFragment : is Show.ArtistDetails, is Show.AlbumDetails -> playbackModel.openMain() is Show.SongDetails, - is Show.SongArtistDetails, - is Show.AlbumArtistDetails, + is Show.SongArtistDecision, + is Show.AlbumArtistDecision, is Show.GenreDetails, is Show.PlaylistDetails, null -> {} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 29f4e2f18..660e371d2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -90,14 +90,23 @@ constructor( get() = _isShuffled private val _currentBarAction = MutableStateFlow(playbackSettings.barAction) + /** The current secondary action to show alongside the play button in the playback bar. */ val currentBarAction: StateFlow get() = _currentBarAction private val _openPanel = MutableEvent() + /** + * A [OpenPanel] command that is awaiting a view capable of responding to it. Null if none + * currently. + */ val openPanel: Event get() = _openPanel private val _playbackDecision = MutableEvent() + /** + * A [PlaybackDecision] command that is awaiting a view capable of responding to it. Null if + * none currently. + */ val playbackDecision: Event get() = _playbackDecision @@ -561,8 +570,17 @@ constructor( } // --- UI CONTROL --- + + /** Open the main panel, closing all other panels. */ fun openMain() = openImpl(OpenPanel.Main) + + /** Open the playback panel, closing the queue panel if needed. */ fun openPlayback() = openImpl(OpenPanel.Playback) + + /** + * Open the queue panel, assuming that it exists in the current layout, is collapsed, and with + * the playback panel already being expanded. + */ fun openQueue() = openImpl(OpenPanel.Queue) private fun openImpl(panel: OpenPanel) { @@ -618,15 +636,33 @@ constructor( } } +/** + * Command for controlling the main playback panel UI. + * + * @author Alexander Capehart (OxygenCobalt) + */ sealed interface OpenPanel { + /** Open the main view, collapsing all other panels. */ object Main : OpenPanel + /** Open the playback panel, collapsing the queue panel if applicable. */ object Playback : OpenPanel + /** + * Open the queue panel, assuming that it exists in the current layout, is collapsed, and with + * the playback panel already being expanded. Do nothing if these conditions are not met. + */ object Queue : OpenPanel } +/** + * Command for opening decision dialogs when playback from a [Song] is ambiguous. + * + * @author Alexander Capehart (OxygenCobalt) + */ sealed interface PlaybackDecision { + /** The [Song] currently attempting to be played from. */ val song: Song - + /** Navigate to a dialog to determine which [Artist] a [Song] should be played from. */ class PlayFromArtist(override val song: Song) : PlaybackDecision + /** Navigate to a dialog to determine which [Genre] a [Song] should be played from. */ class PlayFromGenre(override val song: Song) : PlaybackDecision } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/picker/ArtistPlaybackChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/decision/ArtistPlaybackChoiceAdapter.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/playback/picker/ArtistPlaybackChoiceAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/playback/decision/ArtistPlaybackChoiceAdapter.kt index be8bed183..025c71b89 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/picker/ArtistPlaybackChoiceAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/decision/ArtistPlaybackChoiceAdapter.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.playback.picker +package org.oxycblt.auxio.playback.decision import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/org/oxycblt/auxio/playback/picker/GenrePlaybackChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/decision/GenrePlaybackChoiceAdapter.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/playback/picker/GenrePlaybackChoiceAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/playback/decision/GenrePlaybackChoiceAdapter.kt index f5fdbf970..584f31e00 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/picker/GenrePlaybackChoiceAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/decision/GenrePlaybackChoiceAdapter.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.playback.picker +package org.oxycblt.auxio.playback.decision import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromArtistDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/decision/PlayFromArtistDialog.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromArtistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/playback/decision/PlayFromArtistDialog.kt index 2713fc934..8e318ab3c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromArtistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/decision/PlayFromArtistDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.playback.picker +package org.oxycblt.auxio.playback.decision import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromGenreDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/decision/PlayFromGenreDialog.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromGenreDialog.kt rename to app/src/main/java/org/oxycblt/auxio/playback/decision/PlayFromGenreDialog.kt index 0c0ac9ba0..831e5c6e0 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromGenreDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/decision/PlayFromGenreDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.playback.picker +package org.oxycblt.auxio.playback.decision import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/decision/PlaybackPickerViewModel.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/playback/decision/PlaybackPickerViewModel.kt index 644b5a580..d8dae6ae4 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/decision/PlaybackPickerViewModel.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.playback.picker +package org.oxycblt.auxio.playback.decision import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel 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 cae712f68..9417f8a03 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -212,37 +212,29 @@ class SearchFragment : ListFragment() { logD("Navigating to ${show.song}") findNavController().navigateSafe(SearchFragmentDirections.showSong(show.song.uid)) } - - // Songs should be scrolled to if the album matches, or a new detail - // fragment should be launched otherwise. is Show.SongAlbumDetails -> { logD("Navigating to the album of ${show.song}") findNavController() .navigateSafe(SearchFragmentDirections.showAlbum(show.song.album.uid)) } - - // If the album matches, no need to do anything. Otherwise launch a new - // detail fragment. is Show.AlbumDetails -> { logD("Navigating to ${show.album}") findNavController().navigateSafe(SearchFragmentDirections.showAlbum(show.album.uid)) } - - // Always launch a new ArtistDetailFragment. is Show.ArtistDetails -> { logD("Navigating to ${show.artist}") findNavController() .navigateSafe(SearchFragmentDirections.showArtist(show.artist.uid)) } - is Show.SongArtistDetails -> { + is Show.SongArtistDecision -> { logD("Navigating to artist choices for ${show.song}") findNavController() - .navigateSafe(SearchFragmentDirections.showArtists(show.song.uid)) + .navigateSafe(SearchFragmentDirections.showArtistChoices(show.song.uid)) } - is Show.AlbumArtistDetails -> { + is Show.AlbumArtistDecision -> { logD("Navigating to artist choices for ${show.album}") findNavController() - .navigateSafe(SearchFragmentDirections.showArtists(show.album.uid)) + .navigateSafe(SearchFragmentDirections.showArtistChoices(show.album.uid)) } is Show.GenreDetails -> { logD("Navigating to ${show.genre}") @@ -276,6 +268,8 @@ class SearchFragment : ListFragment() { SearchFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid) } findNavController().navigateSafe(directions) + // Keyboard is no longer needed. + hideKeyboard() } private fun updateSelection(selected: List) { diff --git a/app/src/main/res/menu/item_song.xml b/app/src/main/res/menu/item_song.xml index 68b232fd9..bb9efe006 100644 --- a/app/src/main/res/menu/item_song.xml +++ b/app/src/main/res/menu/item_song.xml @@ -1,14 +1,5 @@ - - - - - - - - - + android:id="@+id/show_artist_choices" + app:destination="@id/show_artist_choices_dialog" /> @@ -126,8 +126,8 @@ android:id="@+id/add_to_playlist" app:destination="@id/add_to_playlist_dialog" /> + android:id="@+id/show_artist_choices" + app:destination="@id/show_artist_choices_dialog" /> @@ -154,8 +154,8 @@ android:id="@+id/show_artist" app:destination="@id/artist_detail_fragment" /> + android:id="@+id/show_artist_choices" + app:destination="@id/show_artist_choices_dialog" /> @@ -219,8 +219,8 @@ android:id="@+id/show_artist" app:destination="@id/artist_detail_fragment" /> + android:id="@+id/show_artist_choices" + app:destination="@id/show_artist_choices_dialog" /> @@ -253,8 +253,8 @@ android:id="@+id/show_artist" app:destination="@id/artist_detail_fragment" /> + android:id="@+id/show_artist_choices" + app:destination="@id/show_artist_choices_dialog" /> @@ -274,7 +274,7 @@