all: redocument navigation system
Add documentation for the new navigation system, which largely completes this change.
This commit is contained in:
parent
7239256e27
commit
0cd59ce4e0
33 changed files with 330 additions and 107 deletions
|
@ -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"
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -86,4 +86,6 @@ abstract class SelectionFragment<VB : ViewBinding> :
|
|||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
// TODO: Re-add the automatic selection handling
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 -> {}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue