all: redocument navigation system

Add documentation for the new navigation system, which largely
completes this change.
This commit is contained in:
Alexander Capehart 2023-07-04 21:44:34 -06:00
parent 7239256e27
commit 0cd59ce4e0
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
33 changed files with 330 additions and 107 deletions

View file

@ -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"

View file

@ -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 -> {

View file

@ -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")

View file

@ -71,6 +71,9 @@ constructor(
private val playbackSettings: PlaybackSettings
) : ViewModel(), MusicRepository.UpdateListener {
private val _toShow = MutableEvent<Show>()
/**
* A [Show] command that is awaiting a view capable of responding to it. Null if none currently.
*/
val toShow: Event<Show>
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
}

View file

@ -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")

View file

@ -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")

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.detail.picker
package org.oxycblt.auxio.detail.decision
import android.view.View
import android.view.ViewGroup

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.detail.picker
package org.oxycblt.auxio.detail.decision
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.detail.picker
package org.oxycblt.auxio.detail.decision
import android.os.Bundle
import android.view.LayoutInflater

View file

@ -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}")

View file

@ -51,12 +51,15 @@ constructor(
private val musicSettings: MusicSettings
) : ViewModel(), MusicRepository.UpdateListener {
private val _selected = MutableStateFlow(listOf<Music>())
/** 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<List<Music>>
get() = _selected
private val _Menu = MutableEvent<Menu>()
val menu: Event<Menu> = _Menu
private val _menu = MutableEvent<Menu>()
/**
* A [Menu] command that is awaiting a view capable of responding to it. Null if none currently.
*/
val menu: Event<Menu> = _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
}

View file

@ -86,4 +86,6 @@ abstract class SelectionFragment<VB : ViewBinding> :
}
else -> false
}
// TODO: Re-add the automatic selection handling
}

View file

@ -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<T : Music> :
ViewBindingBottomSheetDialogFragment<DialogMenuBinding>(), ClickableListListener<MenuItem> {
@ -48,10 +50,33 @@ abstract class MenuDialogFragment<T : Music> :
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<Int>
/**
* 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<T : Music> :
// --- VIEWMODEL SETUP ---
listModel.menu.consume()
menuModel.setCurrentMenu(uid)
menuModel.setMusic(uid)
collectImmediately(menuModel.currentMusic, this::updateMusic)
}

View file

@ -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<Song>() {
override val menuModel: MenuViewModel by activityViewModels()
@ -54,6 +59,7 @@ class SongMenuDialogFragment : MenuDialogFragment<Song>() {
override val uid: Music.UID
get() = args.songUid
// Nothing to disable in song menus.
override fun getDisabledItemIds(music: Song) = setOf<Int>()
override fun updateMusic(binding: DialogMenuBinding, music: Song) {
@ -66,6 +72,7 @@ class SongMenuDialogFragment : MenuDialogFragment<Song>() {
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<Song>() {
}
}
/**
* [MenuDialogFragment] implementation for a [AlbumMenuDialogFragment].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
override val menuModel: MenuViewModel by viewModels()
@ -98,6 +110,7 @@ class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
override val uid: Music.UID
get() = args.albumUid
// Nothing to disable in album menus.
override fun getDisabledItemIds(music: Album) = setOf<Int>()
override fun updateMusic(binding: DialogMenuBinding, music: Album) {
@ -129,6 +142,11 @@ class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
}
}
/**
* [MenuDialogFragment] implementation for a [Artist].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
override val menuModel: MenuViewModel by viewModels()
@ -145,6 +163,8 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
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<Artist>() {
}
}
/**
* [MenuDialogFragment] implementation for a [Genre].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
override val menuModel: MenuViewModel by viewModels()
@ -240,6 +265,11 @@ class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
}
}
/**
* [MenuDialogFragment] implementation for a [Playlist].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
override val menuModel: MenuViewModel by viewModels()
@ -256,6 +286,8 @@ class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
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,

View file

@ -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<Music?>(null)
/** The current [Music] information being shown in a menu dialog. */
val currentMusic: StateFlow<Music?> = _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")

View file

@ -53,6 +53,10 @@ constructor(
get() = _statistics
private val _playlistDecision = MutableEvent<PlaylistDecision>()
/**
* A [PlaylistDecision] command that is awaiting a view capable of responding to it. Null if
* none currently.
*/
val playlistDecision: Event<PlaylistDecision>
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<Song>) : 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<Song>) : PlaylistDecision
}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.picker
package org.oxycblt.auxio.music.decision
import android.os.Bundle
import android.view.LayoutInflater

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.picker
package org.oxycblt.auxio.music.decision
import android.os.Bundle
import android.view.LayoutInflater

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.picker
package org.oxycblt.auxio.music.decision
import android.os.Bundle
import android.view.LayoutInflater

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.picker
package org.oxycblt.auxio.music.decision
import android.view.View
import android.view.ViewGroup

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.picker
package org.oxycblt.auxio.music.decision
import android.view.View
import android.view.ViewGroup

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.picker
package org.oxycblt.auxio.music.decision
import android.content.Context
import androidx.lifecycle.ViewModel

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.picker
package org.oxycblt.auxio.music.decision
import android.os.Bundle
import android.view.LayoutInflater

View file

@ -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 -> {}

View file

@ -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<ActionMode>
get() = _currentBarAction
private val _openPanel = MutableEvent<OpenPanel>()
/**
* A [OpenPanel] command that is awaiting a view capable of responding to it. Null if none
* currently.
*/
val openPanel: Event<OpenPanel>
get() = _openPanel
private val _playbackDecision = MutableEvent<PlaybackDecision>()
/**
* A [PlaybackDecision] command that is awaiting a view capable of responding to it. Null if
* none currently.
*/
val playbackDecision: Event<PlaybackDecision>
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
}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback.picker
package org.oxycblt.auxio.playback.decision
import android.view.View
import android.view.ViewGroup

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback.picker
package org.oxycblt.auxio.playback.decision
import android.view.View
import android.view.ViewGroup

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback.picker
package org.oxycblt.auxio.playback.decision
import android.os.Bundle
import android.view.LayoutInflater

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback.picker
package org.oxycblt.auxio.playback.decision
import android.os.Bundle
import android.view.LayoutInflater

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback.picker
package org.oxycblt.auxio.playback.decision
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel

View file

@ -212,37 +212,29 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
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<Music, FragmentSearchBinding>() {
SearchFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid)
}
findNavController().navigateSafe(directions)
// Keyboard is no longer needed.
hideKeyboard()
}
private fun updateSelection(selected: List<Music>) {

View file

@ -1,14 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- TODO: Add when capable -->
<!-- <item-->
<!-- android:id="@+id/action_play"-->
<!-- android:title="@string/lbl_play"-->
<!-- android:icon="@drawable/ic_play_24" />-->
<!-- <item-->
<!-- android:id="@+id/action_shuffle"-->
<!-- android:title="@string/lbl_shuffle"-->
<!-- android:icon="@drawable/ic_shuffle_off_24" />-->
<item
android:id="@+id/action_play_next"
android:title="@string/lbl_play_next"

View file

@ -61,8 +61,8 @@
android:id="@+id/add_to_playlist"
app:destination="@id/add_to_playlist_dialog" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
android:id="@+id/show_artist_choices"
app:destination="@id/show_artist_choices_dialog" />
<action
android:id="@+id/play_from_artist"
app:destination="@id/play_from_artist_dialog" />
@ -126,8 +126,8 @@
android:id="@+id/add_to_playlist"
app:destination="@id/add_to_playlist_dialog" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
android:id="@+id/show_artist_choices"
app:destination="@id/show_artist_choices_dialog" />
<action
android:id="@+id/play_from_artist"
app:destination="@id/play_from_artist_dialog" />
@ -154,8 +154,8 @@
android:id="@+id/show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
android:id="@+id/show_artist_choices"
app:destination="@id/show_artist_choices_dialog" />
<action
android:id="@+id/open_song_menu"
app:destination="@id/song_menu_dialog" />
@ -219,8 +219,8 @@
android:id="@+id/show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
android:id="@+id/show_artist_choices"
app:destination="@id/show_artist_choices_dialog" />
<action
android:id="@+id/open_song_menu"
app:destination="@id/song_menu_dialog" />
@ -253,8 +253,8 @@
android:id="@+id/show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
android:id="@+id/show_artist_choices"
app:destination="@id/show_artist_choices_dialog" />
<action
android:id="@+id/open_song_menu"
app:destination="@id/song_menu_dialog" />
@ -274,7 +274,7 @@
<dialog
android:id="@+id/new_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.NewPlaylistDialog"
android:name="org.oxycblt.auxio.music.decision.NewPlaylistDialog"
android:label="new_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
@ -284,7 +284,7 @@
<dialog
android:id="@+id/rename_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.RenamePlaylistDialog"
android:name="org.oxycblt.auxio.music.decision.RenamePlaylistDialog"
android:label="rename_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
@ -294,7 +294,7 @@
<dialog
android:id="@+id/delete_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.DeletePlaylistDialog"
android:name="org.oxycblt.auxio.music.decision.DeletePlaylistDialog"
android:label="delete_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
@ -304,7 +304,7 @@
<dialog
android:id="@+id/add_to_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.AddToPlaylistDialog"
android:name="org.oxycblt.auxio.music.decision.AddToPlaylistDialog"
android:label="new_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
@ -316,9 +316,9 @@
</dialog>
<dialog
android:id="@+id/show_artists_dialog"
android:name="org.oxycblt.auxio.detail.picker.ShowArtistDialog"
android:label="show_artists_dialog"
android:id="@+id/show_artist_choices_dialog"
android:name="org.oxycblt.auxio.detail.decision.ShowArtistDialog"
android:label="show_artist_choices_dialog"
tools:layout="@layout/dialog_music_choices">
<argument
android:name="itemUid"
@ -327,7 +327,7 @@
<dialog
android:id="@+id/play_from_artist_dialog"
android:name="org.oxycblt.auxio.playback.picker.PlayFromArtistDialog"
android:name="org.oxycblt.auxio.playback.decision.PlayFromArtistDialog"
android:label="play_from_artist_dialog"
tools:layout="@layout/dialog_music_choices">
<argument
@ -337,7 +337,7 @@
<dialog
android:id="@+id/play_from_genre_dialog"
android:name="org.oxycblt.auxio.playback.picker.PlayFromGenreDialog"
android:name="org.oxycblt.auxio.playback.decision.PlayFromGenreDialog"
android:label="play_from_genre_dialog"
tools:layout="@layout/dialog_music_choices">
<argument