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
|
// Material
|
||||||
// TODO: Exactly figure out the conditions that the 1.7.0 ripple bug occurred so you can just
|
// TODO: Exactly figure out the conditions that the 1.7.0 ripple bug occurred so you can just
|
||||||
// PR a fix.
|
// 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
|
// Dependency Injection
|
||||||
implementation "com.google.dagger:dagger:$hilt_version"
|
implementation "com.google.dagger:dagger:$hilt_version"
|
||||||
|
|
|
@ -277,22 +277,20 @@ class AlbumDetailFragment :
|
||||||
.navigateSafe(AlbumDetailFragmentDirections.showAlbum(show.album.uid))
|
.navigateSafe(AlbumDetailFragmentDirections.showAlbum(show.album.uid))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always launch a new ArtistDetailFragment.
|
|
||||||
is Show.ArtistDetails -> {
|
is Show.ArtistDetails -> {
|
||||||
logD("Navigating to ${show.artist}")
|
logD("Navigating to ${show.artist}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(AlbumDetailFragmentDirections.showArtist(show.artist.uid))
|
.navigateSafe(AlbumDetailFragmentDirections.showArtist(show.artist.uid))
|
||||||
}
|
}
|
||||||
is Show.SongArtistDetails -> {
|
is Show.SongArtistDecision -> {
|
||||||
logD("Navigating to artist choices for ${show.song}")
|
logD("Navigating to artist choices for ${show.song}")
|
||||||
findNavController()
|
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}")
|
logD("Navigating to artist choices for ${show.album}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(AlbumDetailFragmentDirections.showArtist(show.album.uid))
|
.navigateSafe(AlbumDetailFragmentDirections.showArtistChoices(show.album.uid))
|
||||||
}
|
}
|
||||||
is Show.GenreDetails,
|
is Show.GenreDetails,
|
||||||
is Show.PlaylistDetails -> {
|
is Show.PlaylistDetails -> {
|
||||||
|
|
|
@ -302,8 +302,8 @@ class ArtistDetailFragment :
|
||||||
.navigateSafe(ArtistDetailFragmentDirections.showArtist(show.artist.uid))
|
.navigateSafe(ArtistDetailFragmentDirections.showArtist(show.artist.uid))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Show.SongArtistDetails,
|
is Show.SongArtistDecision,
|
||||||
is Show.AlbumArtistDetails,
|
is Show.AlbumArtistDecision,
|
||||||
is Show.GenreDetails,
|
is Show.GenreDetails,
|
||||||
is Show.PlaylistDetails -> {
|
is Show.PlaylistDetails -> {
|
||||||
error("Unexpected show command $show")
|
error("Unexpected show command $show")
|
||||||
|
|
|
@ -71,6 +71,9 @@ constructor(
|
||||||
private val playbackSettings: PlaybackSettings
|
private val playbackSettings: PlaybackSettings
|
||||||
) : ViewModel(), MusicRepository.UpdateListener {
|
) : ViewModel(), MusicRepository.UpdateListener {
|
||||||
private val _toShow = MutableEvent<Show>()
|
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>
|
val toShow: Event<Show>
|
||||||
get() = _toShow
|
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))
|
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))
|
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))
|
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) =
|
fun showArtist(song: Song) =
|
||||||
showImpl(
|
showImpl(
|
||||||
if (song.artists.size > 1) {
|
if (song.artists.size > 1) {
|
||||||
Show.SongArtistDetails(song)
|
Show.SongArtistDecision(song)
|
||||||
} else {
|
} else {
|
||||||
Show.ArtistDetails(song.artists.first())
|
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) =
|
fun showArtist(album: Album) =
|
||||||
showImpl(
|
showImpl(
|
||||||
if (album.artists.size > 1) {
|
if (album.artists.size > 1) {
|
||||||
Show.AlbumArtistDetails(album)
|
Show.AlbumArtistDecision(album)
|
||||||
} else {
|
} else {
|
||||||
Show.ArtistDetails(album.artists.first())
|
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))
|
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))
|
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))
|
fun showPlaylist(playlist: Playlist) = showImpl(Show.PlaylistDetails(playlist))
|
||||||
|
|
||||||
private fun showImpl(show: Show) {
|
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 {
|
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
|
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
|
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
|
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 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
|
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
|
data class PlaylistDetails(val playlist: Playlist) : Show
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,15 +279,15 @@ class GenreDetailFragment :
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(GenreDetailFragmentDirections.showArtist(show.artist.uid))
|
.navigateSafe(GenreDetailFragmentDirections.showArtist(show.artist.uid))
|
||||||
}
|
}
|
||||||
is Show.SongArtistDetails -> {
|
is Show.SongArtistDecision -> {
|
||||||
logD("Navigating to artist choices for ${show.song}")
|
logD("Navigating to artist choices for ${show.song}")
|
||||||
findNavController()
|
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}")
|
logD("Navigating to artist choices for ${show.album}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(GenreDetailFragmentDirections.showArtist(show.album.uid))
|
.navigateSafe(GenreDetailFragmentDirections.showArtistChoices(show.album.uid))
|
||||||
}
|
}
|
||||||
is Show.GenreDetails -> {
|
is Show.GenreDetails -> {
|
||||||
logD("Navigated to this genre")
|
logD("Navigated to this genre")
|
||||||
|
|
|
@ -303,38 +303,31 @@ class PlaylistDetailFragment :
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(PlaylistDetailFragmentDirections.showSong(show.song.uid))
|
.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 -> {
|
is Show.SongAlbumDetails -> {
|
||||||
logD("Navigating to the album of ${show.song}")
|
logD("Navigating to the album of ${show.song}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(PlaylistDetailFragmentDirections.showAlbum(show.song.album.uid))
|
.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 -> {
|
is Show.AlbumDetails -> {
|
||||||
logD("Navigating to ${show.album}")
|
logD("Navigating to ${show.album}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(PlaylistDetailFragmentDirections.showAlbum(show.album.uid))
|
.navigateSafe(PlaylistDetailFragmentDirections.showAlbum(show.album.uid))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always launch a new ArtistDetailFragment.
|
|
||||||
is Show.ArtistDetails -> {
|
is Show.ArtistDetails -> {
|
||||||
logD("Navigating to ${show.artist}")
|
logD("Navigating to ${show.artist}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(PlaylistDetailFragmentDirections.showArtist(show.artist.uid))
|
.navigateSafe(PlaylistDetailFragmentDirections.showArtist(show.artist.uid))
|
||||||
}
|
}
|
||||||
is Show.SongArtistDetails -> {
|
is Show.SongArtistDecision -> {
|
||||||
logD("Navigating to artist choices for ${show.song}")
|
logD("Navigating to artist choices for ${show.song}")
|
||||||
findNavController()
|
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}")
|
logD("Navigating to artist choices for ${show.album}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(PlaylistDetailFragmentDirections.showArtist(show.album.uid))
|
.navigateSafe(
|
||||||
|
PlaylistDetailFragmentDirections.showArtistChoices(show.album.uid))
|
||||||
}
|
}
|
||||||
is Show.PlaylistDetails -> {
|
is Show.PlaylistDetails -> {
|
||||||
logD("Navigated to this playlist")
|
logD("Navigated to this playlist")
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.detail.picker
|
package org.oxycblt.auxio.detail.decision
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2023 Auxio Project
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.detail.picker
|
package org.oxycblt.auxio.detail.decision
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.detail.picker
|
package org.oxycblt.auxio.detail.decision
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
|
@ -532,37 +532,31 @@ class HomeFragment :
|
||||||
logD("Navigating to ${show.song}")
|
logD("Navigating to ${show.song}")
|
||||||
findNavController().navigateSafe(HomeFragmentDirections.showSong(show.song.uid))
|
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 -> {
|
is Show.SongAlbumDetails -> {
|
||||||
logD("Navigating to the album of ${show.song}")
|
logD("Navigating to the album of ${show.song}")
|
||||||
applyAxisTransition(MaterialSharedAxis.X)
|
applyAxisTransition(MaterialSharedAxis.X)
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(HomeFragmentDirections.showAlbum(show.song.album.uid))
|
.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 -> {
|
is Show.AlbumDetails -> {
|
||||||
logD("Navigating to ${show.album}")
|
logD("Navigating to ${show.album}")
|
||||||
applyAxisTransition(MaterialSharedAxis.X)
|
applyAxisTransition(MaterialSharedAxis.X)
|
||||||
findNavController().navigateSafe(HomeFragmentDirections.showAlbum(show.album.uid))
|
findNavController().navigateSafe(HomeFragmentDirections.showAlbum(show.album.uid))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always launch a new ArtistDetailFragment.
|
|
||||||
is Show.ArtistDetails -> {
|
is Show.ArtistDetails -> {
|
||||||
logD("Navigating to ${show.artist}")
|
logD("Navigating to ${show.artist}")
|
||||||
applyAxisTransition(MaterialSharedAxis.X)
|
applyAxisTransition(MaterialSharedAxis.X)
|
||||||
findNavController().navigateSafe(HomeFragmentDirections.showArtist(show.artist.uid))
|
findNavController().navigateSafe(HomeFragmentDirections.showArtist(show.artist.uid))
|
||||||
}
|
}
|
||||||
is Show.SongArtistDetails -> {
|
is Show.SongArtistDecision -> {
|
||||||
logD("Navigating to artist choices for ${show.song}")
|
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}")
|
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 -> {
|
is Show.GenreDetails -> {
|
||||||
logD("Navigating to ${show.genre}")
|
logD("Navigating to ${show.genre}")
|
||||||
|
|
|
@ -51,12 +51,15 @@ constructor(
|
||||||
private val musicSettings: MusicSettings
|
private val musicSettings: MusicSettings
|
||||||
) : ViewModel(), MusicRepository.UpdateListener {
|
) : ViewModel(), MusicRepository.UpdateListener {
|
||||||
private val _selected = MutableStateFlow(listOf<Music>())
|
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>>
|
val selected: StateFlow<List<Music>>
|
||||||
get() = _selected
|
get() = _selected
|
||||||
|
|
||||||
private val _Menu = MutableEvent<Menu>()
|
private val _menu = MutableEvent<Menu>()
|
||||||
val menu: Event<Menu> = _Menu
|
/**
|
||||||
|
* A [Menu] command that is awaiting a view capable of responding to it. Null if none currently.
|
||||||
|
*/
|
||||||
|
val menu: Event<Menu> = _menu
|
||||||
|
|
||||||
init {
|
init {
|
||||||
musicRepository.addUpdateListener(this)
|
musicRepository.addUpdateListener(this)
|
||||||
|
@ -198,22 +201,34 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openImpl(menu: Menu) {
|
private fun openImpl(menu: Menu) {
|
||||||
val existing = _Menu.flow.value
|
val existing = _menu.flow.value
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
logW("Already opening $existing, ignoring $menu")
|
logW("Already opening $existing, ignoring $menu")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_Menu.put(menu)
|
_menu.put(menu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to navigate to a specific menu dialog configuration.
|
||||||
|
*
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
sealed interface Menu {
|
sealed interface Menu {
|
||||||
|
/** The android resource ID of the menu options to display in the dialog. */
|
||||||
val menuRes: Int
|
val menuRes: Int
|
||||||
|
/** The [Music] that the menu should act on. */
|
||||||
val music: Music
|
val music: Music
|
||||||
|
|
||||||
|
/** Navigate to a [Song] menu dialog. */
|
||||||
class ForSong(@MenuRes override val menuRes: Int, override val music: Song) : Menu
|
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
|
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
|
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
|
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
|
class ForPlaylist(@MenuRes override val menuRes: Int, override val music: Playlist) : Menu
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,4 +86,6 @@ abstract class SelectionFragment<VB : ViewBinding> :
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Re-add the automatic selection handling
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,8 @@ import org.oxycblt.auxio.util.logD
|
||||||
* options.
|
* options.
|
||||||
*
|
*
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*
|
||||||
|
* TODO: Extend the amount of music info shown in the dialog
|
||||||
*/
|
*/
|
||||||
abstract class MenuDialogFragment<T : Music> :
|
abstract class MenuDialogFragment<T : Music> :
|
||||||
ViewBindingBottomSheetDialogFragment<DialogMenuBinding>(), ClickableListListener<MenuItem> {
|
ViewBindingBottomSheetDialogFragment<DialogMenuBinding>(), ClickableListListener<MenuItem> {
|
||||||
|
@ -48,10 +50,33 @@ abstract class MenuDialogFragment<T : Music> :
|
||||||
protected abstract val listModel: ListViewModel
|
protected abstract val listModel: ListViewModel
|
||||||
private val menuAdapter = MenuItemAdapter(@Suppress("LeakingThis") this)
|
private val menuAdapter = MenuItemAdapter(@Suppress("LeakingThis") this)
|
||||||
|
|
||||||
|
/** The android resource ID of the menu options to display in the dialog. */
|
||||||
abstract val menuRes: Int
|
abstract val menuRes: Int
|
||||||
|
|
||||||
|
/** The [Music.UID] of the [T] to display menu options for. */
|
||||||
abstract val uid: Music.UID
|
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>
|
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)
|
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)
|
abstract fun onClick(item: MenuItem, music: T)
|
||||||
|
|
||||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater)
|
override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater)
|
||||||
|
@ -69,7 +94,7 @@ abstract class MenuDialogFragment<T : Music> :
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
listModel.menu.consume()
|
listModel.menu.consume()
|
||||||
menuModel.setCurrentMenu(uid)
|
menuModel.setMusic(uid)
|
||||||
collectImmediately(menuModel.currentMusic, this::updateMusic)
|
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.share
|
||||||
import org.oxycblt.auxio.util.showToast
|
import org.oxycblt.auxio.util.showToast
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MenuDialogFragment] implementation for a [Song].
|
||||||
|
*
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class SongMenuDialogFragment : MenuDialogFragment<Song>() {
|
class SongMenuDialogFragment : MenuDialogFragment<Song>() {
|
||||||
override val menuModel: MenuViewModel by activityViewModels()
|
override val menuModel: MenuViewModel by activityViewModels()
|
||||||
|
@ -54,6 +59,7 @@ class SongMenuDialogFragment : MenuDialogFragment<Song>() {
|
||||||
override val uid: Music.UID
|
override val uid: Music.UID
|
||||||
get() = args.songUid
|
get() = args.songUid
|
||||||
|
|
||||||
|
// Nothing to disable in song menus.
|
||||||
override fun getDisabledItemIds(music: Song) = setOf<Int>()
|
override fun getDisabledItemIds(music: Song) = setOf<Int>()
|
||||||
|
|
||||||
override fun updateMusic(binding: DialogMenuBinding, music: Song) {
|
override fun updateMusic(binding: DialogMenuBinding, music: Song) {
|
||||||
|
@ -66,6 +72,7 @@ class SongMenuDialogFragment : MenuDialogFragment<Song>() {
|
||||||
|
|
||||||
override fun onClick(item: MenuItem, music: Song) {
|
override fun onClick(item: MenuItem, music: Song) {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
|
// TODO: Song play and shuffle as soon as PlaybackMode is refactored
|
||||||
R.id.action_play_next -> {
|
R.id.action_play_next -> {
|
||||||
playbackModel.playNext(music)
|
playbackModel.playNext(music)
|
||||||
requireContext().showToast(R.string.lng_queue_added)
|
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
|
@AndroidEntryPoint
|
||||||
class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
|
class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
|
||||||
override val menuModel: MenuViewModel by viewModels()
|
override val menuModel: MenuViewModel by viewModels()
|
||||||
|
@ -98,6 +110,7 @@ class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
|
||||||
override val uid: Music.UID
|
override val uid: Music.UID
|
||||||
get() = args.albumUid
|
get() = args.albumUid
|
||||||
|
|
||||||
|
// Nothing to disable in album menus.
|
||||||
override fun getDisabledItemIds(music: Album) = setOf<Int>()
|
override fun getDisabledItemIds(music: Album) = setOf<Int>()
|
||||||
|
|
||||||
override fun updateMusic(binding: DialogMenuBinding, music: Album) {
|
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
|
@AndroidEntryPoint
|
||||||
class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
|
class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
|
||||||
override val menuModel: MenuViewModel by viewModels()
|
override val menuModel: MenuViewModel by viewModels()
|
||||||
|
@ -145,6 +163,8 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
|
||||||
|
|
||||||
override fun getDisabledItemIds(music: Artist) =
|
override fun getDisabledItemIds(music: Artist) =
|
||||||
if (music.songs.isEmpty()) {
|
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(
|
setOf(
|
||||||
R.id.action_play,
|
R.id.action_play,
|
||||||
R.id.action_shuffle,
|
R.id.action_shuffle,
|
||||||
|
@ -192,6 +212,11 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MenuDialogFragment] implementation for a [Genre].
|
||||||
|
*
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
|
class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
|
||||||
override val menuModel: MenuViewModel by viewModels()
|
override val menuModel: MenuViewModel by viewModels()
|
||||||
|
@ -240,6 +265,11 @@ class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MenuDialogFragment] implementation for a [Playlist].
|
||||||
|
*
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
|
class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
|
||||||
override val menuModel: MenuViewModel by viewModels()
|
override val menuModel: MenuViewModel by viewModels()
|
||||||
|
@ -256,6 +286,8 @@ class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
|
||||||
|
|
||||||
override fun getDisabledItemIds(music: Playlist) =
|
override fun getDisabledItemIds(music: Playlist) =
|
||||||
if (music.songs.isEmpty()) {
|
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(
|
setOf(
|
||||||
R.id.action_play,
|
R.id.action_play,
|
||||||
R.id.action_shuffle,
|
R.id.action_shuffle,
|
||||||
|
|
|
@ -27,10 +27,15 @@ import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.music.MusicRepository
|
import org.oxycblt.auxio.music.MusicRepository
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the state information for [MenuDialogFragment] implementations.
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MenuViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
class MenuViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
||||||
ViewModel(), MusicRepository.UpdateListener {
|
ViewModel(), MusicRepository.UpdateListener {
|
||||||
private val _currentMusic = MutableStateFlow<Music?>(null)
|
private val _currentMusic = MutableStateFlow<Music?>(null)
|
||||||
|
/** The current [Music] information being shown in a menu dialog. */
|
||||||
val currentMusic: StateFlow<Music?> = _currentMusic
|
val currentMusic: StateFlow<Music?> = _currentMusic
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -45,7 +50,13 @@ class MenuViewModel @Inject constructor(private val musicRepository: MusicReposi
|
||||||
musicRepository.removeUpdateListener(this)
|
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)
|
_currentMusic.value = musicRepository.find(uid)
|
||||||
if (_currentMusic.value == null) {
|
if (_currentMusic.value == null) {
|
||||||
logW("Given Music UID to show was invalid")
|
logW("Given Music UID to show was invalid")
|
||||||
|
|
|
@ -53,6 +53,10 @@ constructor(
|
||||||
get() = _statistics
|
get() = _statistics
|
||||||
|
|
||||||
private val _playlistDecision = MutableEvent<PlaylistDecision>()
|
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>
|
val playlistDecision: Event<PlaylistDecision>
|
||||||
get() = _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 {
|
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
|
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
|
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
|
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
|
data class Add(val songs: List<Song>) : PlaylistDecision
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.picker
|
package org.oxycblt.auxio.music.decision
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.picker
|
package org.oxycblt.auxio.music.decision
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.picker
|
package org.oxycblt.auxio.music.decision
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.picker
|
package org.oxycblt.auxio.music.decision
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.picker
|
package org.oxycblt.auxio.music.decision
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.picker
|
package org.oxycblt.auxio.music.decision
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.picker
|
package org.oxycblt.auxio.music.decision
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
|
@ -242,8 +242,8 @@ class PlaybackPanelFragment :
|
||||||
is Show.ArtistDetails,
|
is Show.ArtistDetails,
|
||||||
is Show.AlbumDetails -> playbackModel.openMain()
|
is Show.AlbumDetails -> playbackModel.openMain()
|
||||||
is Show.SongDetails,
|
is Show.SongDetails,
|
||||||
is Show.SongArtistDetails,
|
is Show.SongArtistDecision,
|
||||||
is Show.AlbumArtistDetails,
|
is Show.AlbumArtistDecision,
|
||||||
is Show.GenreDetails,
|
is Show.GenreDetails,
|
||||||
is Show.PlaylistDetails,
|
is Show.PlaylistDetails,
|
||||||
null -> {}
|
null -> {}
|
||||||
|
|
|
@ -90,14 +90,23 @@ constructor(
|
||||||
get() = _isShuffled
|
get() = _isShuffled
|
||||||
|
|
||||||
private val _currentBarAction = MutableStateFlow(playbackSettings.barAction)
|
private val _currentBarAction = MutableStateFlow(playbackSettings.barAction)
|
||||||
|
/** The current secondary action to show alongside the play button in the playback bar. */
|
||||||
val currentBarAction: StateFlow<ActionMode>
|
val currentBarAction: StateFlow<ActionMode>
|
||||||
get() = _currentBarAction
|
get() = _currentBarAction
|
||||||
|
|
||||||
private val _openPanel = MutableEvent<OpenPanel>()
|
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>
|
val openPanel: Event<OpenPanel>
|
||||||
get() = _openPanel
|
get() = _openPanel
|
||||||
|
|
||||||
private val _playbackDecision = MutableEvent<PlaybackDecision>()
|
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>
|
val playbackDecision: Event<PlaybackDecision>
|
||||||
get() = _playbackDecision
|
get() = _playbackDecision
|
||||||
|
|
||||||
|
@ -561,8 +570,17 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- UI CONTROL ---
|
// --- UI CONTROL ---
|
||||||
|
|
||||||
|
/** Open the main panel, closing all other panels. */
|
||||||
fun openMain() = openImpl(OpenPanel.Main)
|
fun openMain() = openImpl(OpenPanel.Main)
|
||||||
|
|
||||||
|
/** Open the playback panel, closing the queue panel if needed. */
|
||||||
fun openPlayback() = openImpl(OpenPanel.Playback)
|
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)
|
fun openQueue() = openImpl(OpenPanel.Queue)
|
||||||
|
|
||||||
private fun openImpl(panel: OpenPanel) {
|
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 {
|
sealed interface OpenPanel {
|
||||||
|
/** Open the main view, collapsing all other panels. */
|
||||||
object Main : OpenPanel
|
object Main : OpenPanel
|
||||||
|
/** Open the playback panel, collapsing the queue panel if applicable. */
|
||||||
object Playback : OpenPanel
|
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
|
object Queue : OpenPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command for opening decision dialogs when playback from a [Song] is ambiguous.
|
||||||
|
*
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
sealed interface PlaybackDecision {
|
sealed interface PlaybackDecision {
|
||||||
|
/** The [Song] currently attempting to be played from. */
|
||||||
val song: Song
|
val song: Song
|
||||||
|
/** Navigate to a dialog to determine which [Artist] a [Song] should be played from. */
|
||||||
class PlayFromArtist(override val song: Song) : PlaybackDecision
|
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
|
class PlayFromGenre(override val song: Song) : PlaybackDecision
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.picker
|
package org.oxycblt.auxio.playback.decision
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.picker
|
package org.oxycblt.auxio.playback.decision
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.picker
|
package org.oxycblt.auxio.playback.decision
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.picker
|
package org.oxycblt.auxio.playback.decision
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.picker
|
package org.oxycblt.auxio.playback.decision
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
@ -212,37 +212,29 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
logD("Navigating to ${show.song}")
|
logD("Navigating to ${show.song}")
|
||||||
findNavController().navigateSafe(SearchFragmentDirections.showSong(show.song.uid))
|
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 -> {
|
is Show.SongAlbumDetails -> {
|
||||||
logD("Navigating to the album of ${show.song}")
|
logD("Navigating to the album of ${show.song}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(SearchFragmentDirections.showAlbum(show.song.album.uid))
|
.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 -> {
|
is Show.AlbumDetails -> {
|
||||||
logD("Navigating to ${show.album}")
|
logD("Navigating to ${show.album}")
|
||||||
findNavController().navigateSafe(SearchFragmentDirections.showAlbum(show.album.uid))
|
findNavController().navigateSafe(SearchFragmentDirections.showAlbum(show.album.uid))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always launch a new ArtistDetailFragment.
|
|
||||||
is Show.ArtistDetails -> {
|
is Show.ArtistDetails -> {
|
||||||
logD("Navigating to ${show.artist}")
|
logD("Navigating to ${show.artist}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(SearchFragmentDirections.showArtist(show.artist.uid))
|
.navigateSafe(SearchFragmentDirections.showArtist(show.artist.uid))
|
||||||
}
|
}
|
||||||
is Show.SongArtistDetails -> {
|
is Show.SongArtistDecision -> {
|
||||||
logD("Navigating to artist choices for ${show.song}")
|
logD("Navigating to artist choices for ${show.song}")
|
||||||
findNavController()
|
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}")
|
logD("Navigating to artist choices for ${show.album}")
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigateSafe(SearchFragmentDirections.showArtists(show.album.uid))
|
.navigateSafe(SearchFragmentDirections.showArtistChoices(show.album.uid))
|
||||||
}
|
}
|
||||||
is Show.GenreDetails -> {
|
is Show.GenreDetails -> {
|
||||||
logD("Navigating to ${show.genre}")
|
logD("Navigating to ${show.genre}")
|
||||||
|
@ -276,6 +268,8 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
SearchFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid)
|
SearchFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid)
|
||||||
}
|
}
|
||||||
findNavController().navigateSafe(directions)
|
findNavController().navigateSafe(directions)
|
||||||
|
// Keyboard is no longer needed.
|
||||||
|
hideKeyboard()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSelection(selected: List<Music>) {
|
private fun updateSelection(selected: List<Music>) {
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<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
|
<item
|
||||||
android:id="@+id/action_play_next"
|
android:id="@+id/action_play_next"
|
||||||
android:title="@string/lbl_play_next"
|
android:title="@string/lbl_play_next"
|
||||||
|
|
|
@ -61,8 +61,8 @@
|
||||||
android:id="@+id/add_to_playlist"
|
android:id="@+id/add_to_playlist"
|
||||||
app:destination="@id/add_to_playlist_dialog" />
|
app:destination="@id/add_to_playlist_dialog" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/show_artists"
|
android:id="@+id/show_artist_choices"
|
||||||
app:destination="@id/show_artists_dialog" />
|
app:destination="@id/show_artist_choices_dialog" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/play_from_artist"
|
android:id="@+id/play_from_artist"
|
||||||
app:destination="@id/play_from_artist_dialog" />
|
app:destination="@id/play_from_artist_dialog" />
|
||||||
|
@ -126,8 +126,8 @@
|
||||||
android:id="@+id/add_to_playlist"
|
android:id="@+id/add_to_playlist"
|
||||||
app:destination="@id/add_to_playlist_dialog" />
|
app:destination="@id/add_to_playlist_dialog" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/show_artists"
|
android:id="@+id/show_artist_choices"
|
||||||
app:destination="@id/show_artists_dialog" />
|
app:destination="@id/show_artist_choices_dialog" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/play_from_artist"
|
android:id="@+id/play_from_artist"
|
||||||
app:destination="@id/play_from_artist_dialog" />
|
app:destination="@id/play_from_artist_dialog" />
|
||||||
|
@ -154,8 +154,8 @@
|
||||||
android:id="@+id/show_artist"
|
android:id="@+id/show_artist"
|
||||||
app:destination="@id/artist_detail_fragment" />
|
app:destination="@id/artist_detail_fragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/show_artists"
|
android:id="@+id/show_artist_choices"
|
||||||
app:destination="@id/show_artists_dialog" />
|
app:destination="@id/show_artist_choices_dialog" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/open_song_menu"
|
android:id="@+id/open_song_menu"
|
||||||
app:destination="@id/song_menu_dialog" />
|
app:destination="@id/song_menu_dialog" />
|
||||||
|
@ -219,8 +219,8 @@
|
||||||
android:id="@+id/show_artist"
|
android:id="@+id/show_artist"
|
||||||
app:destination="@id/artist_detail_fragment" />
|
app:destination="@id/artist_detail_fragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/show_artists"
|
android:id="@+id/show_artist_choices"
|
||||||
app:destination="@id/show_artists_dialog" />
|
app:destination="@id/show_artist_choices_dialog" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/open_song_menu"
|
android:id="@+id/open_song_menu"
|
||||||
app:destination="@id/song_menu_dialog" />
|
app:destination="@id/song_menu_dialog" />
|
||||||
|
@ -253,8 +253,8 @@
|
||||||
android:id="@+id/show_artist"
|
android:id="@+id/show_artist"
|
||||||
app:destination="@id/artist_detail_fragment" />
|
app:destination="@id/artist_detail_fragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/show_artists"
|
android:id="@+id/show_artist_choices"
|
||||||
app:destination="@id/show_artists_dialog" />
|
app:destination="@id/show_artist_choices_dialog" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/open_song_menu"
|
android:id="@+id/open_song_menu"
|
||||||
app:destination="@id/song_menu_dialog" />
|
app:destination="@id/song_menu_dialog" />
|
||||||
|
@ -274,7 +274,7 @@
|
||||||
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/new_playlist_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"
|
android:label="new_playlist_dialog"
|
||||||
tools:layout="@layout/dialog_playlist_name">
|
tools:layout="@layout/dialog_playlist_name">
|
||||||
<argument
|
<argument
|
||||||
|
@ -284,7 +284,7 @@
|
||||||
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/rename_playlist_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"
|
android:label="rename_playlist_dialog"
|
||||||
tools:layout="@layout/dialog_playlist_name">
|
tools:layout="@layout/dialog_playlist_name">
|
||||||
<argument
|
<argument
|
||||||
|
@ -294,7 +294,7 @@
|
||||||
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/delete_playlist_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"
|
android:label="delete_playlist_dialog"
|
||||||
tools:layout="@layout/dialog_playlist_name">
|
tools:layout="@layout/dialog_playlist_name">
|
||||||
<argument
|
<argument
|
||||||
|
@ -304,7 +304,7 @@
|
||||||
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/add_to_playlist_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"
|
android:label="new_playlist_dialog"
|
||||||
tools:layout="@layout/dialog_playlist_name">
|
tools:layout="@layout/dialog_playlist_name">
|
||||||
<argument
|
<argument
|
||||||
|
@ -316,9 +316,9 @@
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/show_artists_dialog"
|
android:id="@+id/show_artist_choices_dialog"
|
||||||
android:name="org.oxycblt.auxio.detail.picker.ShowArtistDialog"
|
android:name="org.oxycblt.auxio.detail.decision.ShowArtistDialog"
|
||||||
android:label="show_artists_dialog"
|
android:label="show_artist_choices_dialog"
|
||||||
tools:layout="@layout/dialog_music_choices">
|
tools:layout="@layout/dialog_music_choices">
|
||||||
<argument
|
<argument
|
||||||
android:name="itemUid"
|
android:name="itemUid"
|
||||||
|
@ -327,7 +327,7 @@
|
||||||
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/play_from_artist_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"
|
android:label="play_from_artist_dialog"
|
||||||
tools:layout="@layout/dialog_music_choices">
|
tools:layout="@layout/dialog_music_choices">
|
||||||
<argument
|
<argument
|
||||||
|
@ -337,7 +337,7 @@
|
||||||
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/play_from_genre_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"
|
android:label="play_from_genre_dialog"
|
||||||
tools:layout="@layout/dialog_music_choices">
|
tools:layout="@layout/dialog_music_choices">
|
||||||
<argument
|
<argument
|
||||||
|
|
Loading…
Reference in a new issue