list: add ability to play/shuffle songs in menu
Add play and shuffle options for all song menus. These will override the shuffle state, unlike other song play interactions. This required a good bit of refactoring to menu, some of which might be ported to other commands in future changes.
This commit is contained in:
parent
ecc84dd8c8
commit
32a0d97e5d
22 changed files with 373 additions and 280 deletions
|
@ -121,16 +121,16 @@ object IntegerTable {
|
|||
const val COVER_MODE_MEDIA_STORE = 0xA11D
|
||||
/** CoverMode.Quality */
|
||||
const val COVER_MODE_QUALITY = 0xA11E
|
||||
/** PlaySong.ByItself */
|
||||
const val PLAY_SONG_BY_ITSELF = 0xA11F
|
||||
/** PlaySong.FromAll */
|
||||
const val PLAY_SONG_FROM_ALL = 0xA120
|
||||
const val PLAY_SONG_FROM_ALL = 0xA11F
|
||||
/** PlaySong.FromAlbum */
|
||||
const val PLAY_SONG_FROM_ALBUM = 0xA121
|
||||
const val PLAY_SONG_FROM_ALBUM = 0xA120
|
||||
/** PlaySong.FromArtist */
|
||||
const val PLAY_SONG_FROM_ARTIST = 0xA122
|
||||
const val PLAY_SONG_FROM_ARTIST = 0xA121
|
||||
/** PlaySong.FromGenre */
|
||||
const val PLAY_SONG_FROM_GENRE = 0xA123
|
||||
const val PLAY_SONG_FROM_GENRE = 0xA122
|
||||
/** PlaySong.FromPlaylist */
|
||||
const val PLAY_SONG_FROM_PLAYLIST = 0xA124
|
||||
const val PLAY_SONG_FROM_PLAYLIST = 0xA123
|
||||
/** PlaySong.ByItself */
|
||||
const val PLAY_SONG_BY_ITSELF = 0xA124
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@ class AlbumDetailFragment :
|
|||
}
|
||||
|
||||
override fun onOpenMenu(item: Song, anchor: View) {
|
||||
listModel.openMenu(R.menu.item_album_song, item)
|
||||
listModel.openMenu(R.menu.item_album_song, item, detailModel.playInAlbumWith)
|
||||
}
|
||||
|
||||
override fun onPlay() {
|
||||
|
@ -302,8 +302,7 @@ class AlbumDetailFragment :
|
|||
if (menu == null) return
|
||||
val directions =
|
||||
when (menu) {
|
||||
is Menu.ForSong ->
|
||||
AlbumDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForSong -> AlbumDetailFragmentDirections.openSongMenu(menu.parcel)
|
||||
is Menu.ForAlbum,
|
||||
is Menu.ForArtist,
|
||||
is Menu.ForGenre,
|
||||
|
|
|
@ -186,7 +186,8 @@ class ArtistDetailFragment :
|
|||
|
||||
override fun onOpenMenu(item: Music, anchor: View) {
|
||||
when (item) {
|
||||
is Song -> listModel.openMenu(R.menu.item_artist_song, item)
|
||||
is Song ->
|
||||
listModel.openMenu(R.menu.item_artist_song, item, detailModel.playInArtistWith)
|
||||
is Album -> listModel.openMenu(R.menu.item_artist_album, item)
|
||||
else -> error("Unexpected datatype: ${item::class.simpleName}")
|
||||
}
|
||||
|
@ -306,10 +307,8 @@ class ArtistDetailFragment :
|
|||
if (menu == null) return
|
||||
val directions =
|
||||
when (menu) {
|
||||
is Menu.ForSong ->
|
||||
ArtistDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForAlbum ->
|
||||
ArtistDetailFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForSong -> ArtistDetailFragmentDirections.openSongMenu(menu.parcel)
|
||||
is Menu.ForAlbum -> ArtistDetailFragmentDirections.openAlbumMenu(menu.parcel)
|
||||
is Menu.ForArtist,
|
||||
is Menu.ForGenre,
|
||||
is Menu.ForPlaylist -> error("Unexpected menu $menu")
|
||||
|
|
|
@ -185,7 +185,7 @@ class GenreDetailFragment :
|
|||
override fun onOpenMenu(item: Music, anchor: View) {
|
||||
when (item) {
|
||||
is Artist -> listModel.openMenu(R.menu.item_parent, item)
|
||||
is Song -> listModel.openMenu(R.menu.item_song, item)
|
||||
is Song -> listModel.openMenu(R.menu.item_song, item, detailModel.playInGenreWith)
|
||||
else -> error("Unexpected datatype: ${item::class.simpleName}")
|
||||
}
|
||||
}
|
||||
|
@ -294,10 +294,8 @@ class GenreDetailFragment :
|
|||
if (menu == null) return
|
||||
val directions =
|
||||
when (menu) {
|
||||
is Menu.ForSong ->
|
||||
GenreDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForArtist ->
|
||||
GenreDetailFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForSong -> GenreDetailFragmentDirections.openSongMenu(menu.parcel)
|
||||
is Menu.ForArtist -> GenreDetailFragmentDirections.openArtistMenu(menu.parcel)
|
||||
is Menu.ForAlbum,
|
||||
is Menu.ForGenre,
|
||||
is Menu.ForPlaylist -> error("Unexpected menu $menu")
|
||||
|
|
|
@ -237,7 +237,7 @@ class PlaylistDetailFragment :
|
|||
}
|
||||
|
||||
override fun onOpenMenu(item: Song, anchor: View) {
|
||||
listModel.openMenu(R.menu.item_playlist_song, item)
|
||||
listModel.openMenu(R.menu.item_playlist_song, item, detailModel.playInPlaylistWith)
|
||||
}
|
||||
|
||||
override fun onPlay() {
|
||||
|
@ -344,8 +344,7 @@ class PlaylistDetailFragment :
|
|||
if (menu == null) return
|
||||
val directions =
|
||||
when (menu) {
|
||||
is Menu.ForSong ->
|
||||
PlaylistDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForSong -> PlaylistDetailFragmentDirections.openSongMenu(menu.parcel)
|
||||
is Menu.ForArtist,
|
||||
is Menu.ForAlbum,
|
||||
is Menu.ForGenre,
|
||||
|
|
|
@ -577,15 +577,11 @@ class HomeFragment :
|
|||
if (menu == null) return
|
||||
val directions =
|
||||
when (menu) {
|
||||
is Menu.ForSong -> HomeFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForAlbum ->
|
||||
HomeFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForArtist ->
|
||||
HomeFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForGenre ->
|
||||
HomeFragmentDirections.openGenreMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForPlaylist ->
|
||||
HomeFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForSong -> HomeFragmentDirections.openSongMenu(menu.parcel)
|
||||
is Menu.ForAlbum -> HomeFragmentDirections.openAlbumMenu(menu.parcel)
|
||||
is Menu.ForArtist -> HomeFragmentDirections.openArtistMenu(menu.parcel)
|
||||
is Menu.ForGenre -> HomeFragmentDirections.openGenreMenu(menu.parcel)
|
||||
is Menu.ForPlaylist -> HomeFragmentDirections.openPlaylistMenu(menu.parcel)
|
||||
}
|
||||
findNavController().navigateSafe(directions)
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ class SongListFragment :
|
|||
}
|
||||
|
||||
override fun onOpenMenu(item: Song, anchor: View) {
|
||||
listModel.openMenu(R.menu.item_song, item)
|
||||
listModel.openMenu(R.menu.item_song, item, homeModel.playWith)
|
||||
}
|
||||
|
||||
private fun updateSongs(songs: List<Song>) {
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
|
||||
package org.oxycblt.auxio.list
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
|
@ -33,6 +35,7 @@ import org.oxycblt.auxio.music.MusicRepository
|
|||
import org.oxycblt.auxio.music.MusicSettings
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaySong
|
||||
import org.oxycblt.auxio.util.Event
|
||||
import org.oxycblt.auxio.util.MutableEvent
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
@ -146,10 +149,12 @@ constructor(
|
|||
*
|
||||
* @param menuRes The resource of the menu to use.
|
||||
* @param song The [Song] to show.
|
||||
* @param playWith A [PlaySong] command to give context to what "Play" and "Shuffle" actions
|
||||
* should do.
|
||||
*/
|
||||
fun openMenu(@MenuRes menuRes: Int, song: Song) {
|
||||
fun openMenu(@MenuRes menuRes: Int, song: Song, playWith: PlaySong) {
|
||||
logD("Opening menu for $song")
|
||||
openImpl(Menu.ForSong(menuRes, song))
|
||||
openImpl(Menu.ForSong(menuRes, song, playWith))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,19 +221,63 @@ constructor(
|
|||
* @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
|
||||
/** The menu resource to inflate in the menu dialog. */
|
||||
@get:MenuRes val res: Int
|
||||
/** A [Parcel] version of this instance that can be used as a navigation argument. */
|
||||
val parcel: Parcel
|
||||
sealed interface Parcel : Parcelable
|
||||
|
||||
/** 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
|
||||
class ForSong(@MenuRes override val res: Int, val song: Song, val playWith: PlaySong) : Menu {
|
||||
override val parcel: Parcel
|
||||
get() {
|
||||
val playWithUid =
|
||||
when (playWith) {
|
||||
is PlaySong.FromArtist -> playWith.which?.uid
|
||||
is PlaySong.FromGenre -> playWith.which?.uid
|
||||
is PlaySong.FromPlaylist -> playWith.which.uid
|
||||
is PlaySong.FromAll,
|
||||
is PlaySong.FromAlbum,
|
||||
is PlaySong.ByItself -> null
|
||||
}
|
||||
|
||||
return Parcel(res, song.uid, playWith.intCode, playWithUid)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class Parcel(
|
||||
val res: Int,
|
||||
val songUid: Music.UID,
|
||||
val playWithCode: Int,
|
||||
val playWithUid: Music.UID?
|
||||
) : Menu.Parcel
|
||||
}
|
||||
|
||||
/** Navigate to a [Album] menu dialog. */
|
||||
class ForAlbum(@MenuRes override val res: Int, val album: Album) : Menu {
|
||||
override val parcel
|
||||
get() = Parcel(res, album.uid)
|
||||
@Parcelize data class Parcel(val res: Int, val albumUid: Music.UID) : Menu.Parcel
|
||||
}
|
||||
|
||||
/** Navigate to a [Artist] menu dialog. */
|
||||
class ForArtist(@MenuRes override val res: Int, val artist: Artist) : Menu {
|
||||
override val parcel
|
||||
get() = Parcel(res, artist.uid)
|
||||
@Parcelize data class Parcel(val res: Int, val artistUid: Music.UID) : Menu.Parcel
|
||||
}
|
||||
|
||||
/** Navigate to a [Genre] menu dialog. */
|
||||
class ForGenre(@MenuRes override val res: Int, val genre: Genre) : Menu {
|
||||
override val parcel
|
||||
get() = Parcel(res, genre.uid)
|
||||
@Parcelize data class Parcel(val res: Int, val genreUid: Music.UID) : Menu.Parcel
|
||||
}
|
||||
|
||||
/** Navigate to a [Playlist] menu dialog. */
|
||||
class ForPlaylist(@MenuRes override val res: Int, val playlist: Playlist) : Menu {
|
||||
override val parcel
|
||||
get() = Parcel(res, playlist.uid)
|
||||
@Parcelize data class Parcel(val res: Int, val playlistUid: Music.UID) : Menu.Parcel
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import org.oxycblt.auxio.databinding.DialogMenuBinding
|
||||
import org.oxycblt.auxio.list.ClickableListListener
|
||||
import org.oxycblt.auxio.list.ListViewModel
|
||||
import org.oxycblt.auxio.list.Menu
|
||||
import org.oxycblt.auxio.list.adapter.UpdateInstructions
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment
|
||||
import org.oxycblt.auxio.util.collectImmediately
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
@ -44,40 +44,36 @@ import org.oxycblt.auxio.util.logD
|
|||
*
|
||||
* TODO: Extend the amount of music info shown in the dialog
|
||||
*/
|
||||
abstract class MenuDialogFragment<T : Music> :
|
||||
abstract class MenuDialogFragment<M : Menu> :
|
||||
ViewBindingBottomSheetDialogFragment<DialogMenuBinding>(), ClickableListListener<MenuItem> {
|
||||
protected abstract val menuModel: MenuViewModel
|
||||
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
|
||||
abstract val parcel: Menu.Parcel
|
||||
|
||||
/**
|
||||
* Get the options to disable in the context of the currently shown [T].
|
||||
* Get the options to disable in the context of the currently shown [M].
|
||||
*
|
||||
* @param music The currently-shown music [T].
|
||||
* @param menu The currently-shown menu [M].
|
||||
*/
|
||||
abstract fun getDisabledItemIds(music: T): Set<Int>
|
||||
abstract fun getDisabledItemIds(menu: M): Set<Int>
|
||||
|
||||
/**
|
||||
* Update the displayed information about the currently shown [T].
|
||||
* Update the displayed information about the currently shown [M].
|
||||
*
|
||||
* @param binding The [DialogMenuBinding] to bind information to.
|
||||
* @param music The currently-shown music [T].
|
||||
* @param menu The currently-shown menu [M].
|
||||
*/
|
||||
abstract fun updateMusic(binding: DialogMenuBinding, music: T)
|
||||
abstract fun updateMenu(binding: DialogMenuBinding, menu: M)
|
||||
|
||||
/**
|
||||
* 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].
|
||||
* @param menu The currently-shown menu [M].
|
||||
*/
|
||||
abstract fun onClick(item: MenuItem, music: T)
|
||||
abstract fun onClick(item: MenuItem, menu: M)
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater)
|
||||
|
||||
|
@ -94,8 +90,8 @@ abstract class MenuDialogFragment<T : Music> :
|
|||
|
||||
// --- VIEWMODEL SETUP ---
|
||||
listModel.menu.consume()
|
||||
menuModel.setMusic(uid)
|
||||
collectImmediately(menuModel.currentMusic, this::updateMusic)
|
||||
menuModel.setMenu(parcel)
|
||||
collectImmediately(menuModel.currentMenu, this::updateMenu)
|
||||
}
|
||||
|
||||
override fun onDestroyBinding(binding: DialogMenuBinding) {
|
||||
|
@ -105,23 +101,25 @@ abstract class MenuDialogFragment<T : Music> :
|
|||
binding.menuOptionRecycler.adapter = null
|
||||
}
|
||||
|
||||
private fun updateMusic(music: Music?) {
|
||||
if (music == null) {
|
||||
logD("No music to show, navigating away")
|
||||
private fun updateMenu(menu: Menu?) {
|
||||
if (menu == null) {
|
||||
logD("No menu to show, navigating away")
|
||||
findNavController().navigateUp()
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST") val castedMusic = music as T
|
||||
@Suppress("UNCHECKED_CAST") val casted = menu as? M
|
||||
check(casted != null) { "Unexpected menu instance ${menu::class.simpleName}" }
|
||||
|
||||
// We need to inflate the menu on every music update since it might have changed
|
||||
// We need to inflate the menu on every menu update since it might have changed
|
||||
// what options are available (ex. if an artist with no songs has had new songs added).
|
||||
// Since we don't have (and don't want) a dummy view to inflate this menu, just
|
||||
// depend on the AndroidX Toolbar internal API and hope for the best.
|
||||
@SuppressLint("RestrictedApi") val builder = MenuBuilder(requireContext())
|
||||
MenuInflater(requireContext()).inflate(menuRes, builder)
|
||||
MenuInflater(requireContext()).inflate(casted.res, builder)
|
||||
|
||||
// Disable any menu options as specified by the impl
|
||||
val disabledIds = getDisabledItemIds(castedMusic)
|
||||
val disabledIds = getDisabledItemIds(casted)
|
||||
val visible =
|
||||
builder.children.mapTo(mutableListOf()) {
|
||||
it.isEnabled = !disabledIds.contains(it.itemId)
|
||||
|
@ -130,7 +128,7 @@ abstract class MenuDialogFragment<T : Music> :
|
|||
menuAdapter.update(visible, UpdateInstructions.Diff)
|
||||
|
||||
// Delegate to impl how to show music
|
||||
updateMusic(requireBinding(), castedMusic)
|
||||
updateMenu(requireBinding(), casted)
|
||||
}
|
||||
|
||||
final override fun onClick(item: MenuItem, viewHolder: RecyclerView.ViewHolder) {
|
||||
|
@ -138,6 +136,6 @@ abstract class MenuDialogFragment<T : Music> :
|
|||
// TODO: This should change if the app is 100% migrated to menu dialogs
|
||||
findNavController().navigateUp()
|
||||
// Delegate to impl on how to handle items
|
||||
@Suppress("UNCHECKED_CAST") onClick(item, menuModel.currentMusic.value as T)
|
||||
@Suppress("UNCHECKED_CAST") onClick(item, menuModel.currentMenu.value as M)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,10 +27,9 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.databinding.DialogMenuBinding
|
||||
import org.oxycblt.auxio.detail.DetailViewModel
|
||||
import org.oxycblt.auxio.list.ListViewModel
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.list.Menu
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicViewModel
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
@ -46,7 +45,7 @@ import org.oxycblt.auxio.util.showToast
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class SongMenuDialogFragment : MenuDialogFragment<Song>() {
|
||||
class SongMenuDialogFragment : MenuDialogFragment<Menu.ForSong>() {
|
||||
override val menuModel: MenuViewModel by activityViewModels()
|
||||
override val listModel: ListViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
|
@ -54,38 +53,37 @@ class SongMenuDialogFragment : MenuDialogFragment<Song>() {
|
|||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val args: SongMenuDialogFragmentArgs by navArgs()
|
||||
|
||||
override val menuRes: Int
|
||||
get() = args.menuRes
|
||||
override val uid: Music.UID
|
||||
get() = args.songUid
|
||||
override val parcel
|
||||
get() = args.parcel
|
||||
|
||||
// Nothing to disable in song menus.
|
||||
override fun getDisabledItemIds(music: Song) = setOf<Int>()
|
||||
override fun getDisabledItemIds(menu: Menu.ForSong) = setOf<Int>()
|
||||
|
||||
override fun updateMusic(binding: DialogMenuBinding, music: Song) {
|
||||
override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForSong) {
|
||||
val context = requireContext()
|
||||
binding.menuCover.bind(music)
|
||||
binding.menuCover.bind(menu.song)
|
||||
binding.menuType.text = getString(R.string.lbl_song)
|
||||
binding.menuName.text = music.name.resolve(context)
|
||||
binding.menuInfo.text = music.artists.resolveNames(context)
|
||||
binding.menuName.text = menu.song.name.resolve(context)
|
||||
binding.menuInfo.text = menu.song.artists.resolveNames(context)
|
||||
}
|
||||
|
||||
override fun onClick(item: MenuItem, music: Song) {
|
||||
override fun onClick(item: MenuItem, menu: Menu.ForSong) {
|
||||
when (item.itemId) {
|
||||
// TODO: Song play and shuffle as soon as PlaybackMode is refactored
|
||||
R.id.action_play -> playbackModel.playExplicit(menu.song, menu.playWith)
|
||||
R.id.action_shuffle -> playbackModel.shuffleExplicit(menu.song, menu.playWith)
|
||||
R.id.action_play_next -> {
|
||||
playbackModel.playNext(music)
|
||||
playbackModel.playNext(menu.song)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_queue_add -> {
|
||||
playbackModel.addToQueue(music)
|
||||
playbackModel.addToQueue(menu.song)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_artist_details -> detailModel.showArtist(music)
|
||||
R.id.action_album_details -> detailModel.showAlbum(music)
|
||||
R.id.action_share -> requireContext().share(music)
|
||||
R.id.action_playlist_add -> musicModel.addToPlaylist(music)
|
||||
R.id.action_detail -> detailModel.showSong(music)
|
||||
R.id.action_artist_details -> detailModel.showArtist(menu.song)
|
||||
R.id.action_album_details -> detailModel.showAlbum(menu.song)
|
||||
R.id.action_share -> requireContext().share(menu.song)
|
||||
R.id.action_playlist_add -> musicModel.addToPlaylist(menu.song)
|
||||
R.id.action_detail -> detailModel.showSong(menu.song)
|
||||
else -> error("Unexpected menu item selected $item")
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +95,7 @@ class SongMenuDialogFragment : MenuDialogFragment<Song>() {
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
|
||||
class AlbumMenuDialogFragment : MenuDialogFragment<Menu.ForAlbum>() {
|
||||
override val menuModel: MenuViewModel by viewModels()
|
||||
override val listModel: ListViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
|
@ -105,38 +103,36 @@ class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
|
|||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val args: AlbumMenuDialogFragmentArgs by navArgs()
|
||||
|
||||
override val menuRes: Int
|
||||
get() = args.menuRes
|
||||
override val uid: Music.UID
|
||||
get() = args.albumUid
|
||||
override val parcel
|
||||
get() = args.parcel
|
||||
|
||||
// Nothing to disable in album menus.
|
||||
override fun getDisabledItemIds(music: Album) = setOf<Int>()
|
||||
override fun getDisabledItemIds(menu: Menu.ForAlbum) = setOf<Int>()
|
||||
|
||||
override fun updateMusic(binding: DialogMenuBinding, music: Album) {
|
||||
override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForAlbum) {
|
||||
val context = requireContext()
|
||||
binding.menuCover.bind(music)
|
||||
binding.menuType.text = getString(music.releaseType.stringRes)
|
||||
binding.menuName.text = music.name.resolve(context)
|
||||
binding.menuInfo.text = music.artists.resolveNames(context)
|
||||
binding.menuCover.bind(menu.album)
|
||||
binding.menuType.text = getString(menu.album.releaseType.stringRes)
|
||||
binding.menuName.text = menu.album.name.resolve(context)
|
||||
binding.menuInfo.text = menu.album.artists.resolveNames(context)
|
||||
}
|
||||
|
||||
override fun onClick(item: MenuItem, music: Album) {
|
||||
override fun onClick(item: MenuItem, menu: Menu.ForAlbum) {
|
||||
when (item.itemId) {
|
||||
R.id.action_play -> playbackModel.play(music)
|
||||
R.id.action_shuffle -> playbackModel.shuffle(music)
|
||||
R.id.action_detail -> detailModel.showAlbum(music)
|
||||
R.id.action_play -> playbackModel.play(menu.album)
|
||||
R.id.action_shuffle -> playbackModel.shuffle(menu.album)
|
||||
R.id.action_detail -> detailModel.showAlbum(menu.album)
|
||||
R.id.action_play_next -> {
|
||||
playbackModel.playNext(music)
|
||||
playbackModel.playNext(menu.album)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_queue_add -> {
|
||||
playbackModel.addToQueue(music)
|
||||
playbackModel.addToQueue(menu.album)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_artist_details -> detailModel.showArtist(music)
|
||||
R.id.action_playlist_add -> musicModel.addToPlaylist(music)
|
||||
R.id.action_share -> requireContext().share(music)
|
||||
R.id.action_artist_details -> detailModel.showArtist(menu.album)
|
||||
R.id.action_playlist_add -> musicModel.addToPlaylist(menu.album)
|
||||
R.id.action_share -> requireContext().share(menu.album)
|
||||
else -> error("Unexpected menu item selected $item")
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +144,7 @@ class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
|
||||
class ArtistMenuDialogFragment : MenuDialogFragment<Menu.ForArtist>() {
|
||||
override val menuModel: MenuViewModel by viewModels()
|
||||
override val listModel: ListViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
|
@ -156,13 +152,11 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
|
|||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val args: ArtistMenuDialogFragmentArgs by navArgs()
|
||||
|
||||
override val menuRes: Int
|
||||
get() = args.menuRes
|
||||
override val uid: Music.UID
|
||||
get() = args.artistUid
|
||||
override val parcel
|
||||
get() = args.parcel
|
||||
|
||||
override fun getDisabledItemIds(music: Artist) =
|
||||
if (music.songs.isEmpty()) {
|
||||
override fun getDisabledItemIds(menu: Menu.ForArtist) =
|
||||
if (menu.artist.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(
|
||||
|
@ -176,37 +170,37 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
|
|||
setOf()
|
||||
}
|
||||
|
||||
override fun updateMusic(binding: DialogMenuBinding, music: Artist) {
|
||||
override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForArtist) {
|
||||
val context = requireContext()
|
||||
binding.menuCover.bind(music)
|
||||
binding.menuCover.bind(menu.artist)
|
||||
binding.menuType.text = getString(R.string.lbl_artist)
|
||||
binding.menuName.text = music.name.resolve(context)
|
||||
binding.menuName.text = menu.artist.name.resolve(context)
|
||||
binding.menuInfo.text =
|
||||
getString(
|
||||
R.string.fmt_two,
|
||||
context.getPlural(R.plurals.fmt_album_count, music.albums.size),
|
||||
if (music.songs.isNotEmpty()) {
|
||||
context.getPlural(R.plurals.fmt_song_count, music.songs.size)
|
||||
context.getPlural(R.plurals.fmt_album_count, menu.artist.albums.size),
|
||||
if (menu.artist.songs.isNotEmpty()) {
|
||||
context.getPlural(R.plurals.fmt_song_count, menu.artist.songs.size)
|
||||
} else {
|
||||
getString(R.string.def_song_count)
|
||||
})
|
||||
}
|
||||
|
||||
override fun onClick(item: MenuItem, music: Artist) {
|
||||
override fun onClick(item: MenuItem, menu: Menu.ForArtist) {
|
||||
when (item.itemId) {
|
||||
R.id.action_play -> playbackModel.play(music)
|
||||
R.id.action_shuffle -> playbackModel.shuffle(music)
|
||||
R.id.action_detail -> detailModel.showArtist(music)
|
||||
R.id.action_play -> playbackModel.play(menu.artist)
|
||||
R.id.action_shuffle -> playbackModel.shuffle(menu.artist)
|
||||
R.id.action_detail -> detailModel.showArtist(menu.artist)
|
||||
R.id.action_play_next -> {
|
||||
playbackModel.playNext(music)
|
||||
playbackModel.playNext(menu.artist)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_queue_add -> {
|
||||
playbackModel.addToQueue(music)
|
||||
playbackModel.addToQueue(menu.artist)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_playlist_add -> musicModel.addToPlaylist(music)
|
||||
R.id.action_share -> requireContext().share(music)
|
||||
R.id.action_playlist_add -> musicModel.addToPlaylist(menu.artist)
|
||||
R.id.action_share -> requireContext().share(menu.artist)
|
||||
else -> error("Unexpected menu item $item")
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +212,7 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
|
||||
class GenreMenuDialogFragment : MenuDialogFragment<Menu.ForGenre>() {
|
||||
override val menuModel: MenuViewModel by viewModels()
|
||||
override val listModel: ListViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
|
@ -226,40 +220,38 @@ class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
|
|||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val args: GenreMenuDialogFragmentArgs by navArgs()
|
||||
|
||||
override val menuRes: Int
|
||||
get() = args.menuRes
|
||||
override val uid: Music.UID
|
||||
get() = args.genreUid
|
||||
override val parcel
|
||||
get() = args.parcel
|
||||
|
||||
override fun getDisabledItemIds(music: Genre) = setOf<Int>()
|
||||
override fun getDisabledItemIds(menu: Menu.ForGenre) = setOf<Int>()
|
||||
|
||||
override fun updateMusic(binding: DialogMenuBinding, music: Genre) {
|
||||
override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForGenre) {
|
||||
val context = requireContext()
|
||||
binding.menuCover.bind(music)
|
||||
binding.menuCover.bind(menu.genre)
|
||||
binding.menuType.text = getString(R.string.lbl_genre)
|
||||
binding.menuName.text = music.name.resolve(context)
|
||||
binding.menuName.text = menu.genre.name.resolve(context)
|
||||
binding.menuInfo.text =
|
||||
getString(
|
||||
R.string.fmt_two,
|
||||
context.getPlural(R.plurals.fmt_artist_count, music.artists.size),
|
||||
context.getPlural(R.plurals.fmt_song_count, music.songs.size))
|
||||
context.getPlural(R.plurals.fmt_artist_count, menu.genre.artists.size),
|
||||
context.getPlural(R.plurals.fmt_song_count, menu.genre.songs.size))
|
||||
}
|
||||
|
||||
override fun onClick(item: MenuItem, music: Genre) {
|
||||
override fun onClick(item: MenuItem, menu: Menu.ForGenre) {
|
||||
when (item.itemId) {
|
||||
R.id.action_play -> playbackModel.play(music)
|
||||
R.id.action_shuffle -> playbackModel.shuffle(music)
|
||||
R.id.action_detail -> detailModel.showGenre(music)
|
||||
R.id.action_play -> playbackModel.play(menu.genre)
|
||||
R.id.action_shuffle -> playbackModel.shuffle(menu.genre)
|
||||
R.id.action_detail -> detailModel.showGenre(menu.genre)
|
||||
R.id.action_play_next -> {
|
||||
playbackModel.playNext(music)
|
||||
playbackModel.playNext(menu.genre)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_queue_add -> {
|
||||
playbackModel.addToQueue(music)
|
||||
playbackModel.addToQueue(menu.genre)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_playlist_add -> musicModel.addToPlaylist(music)
|
||||
R.id.action_share -> requireContext().share(music)
|
||||
R.id.action_playlist_add -> musicModel.addToPlaylist(menu.genre)
|
||||
R.id.action_share -> requireContext().share(menu.genre)
|
||||
else -> error("Unexpected menu item $item")
|
||||
}
|
||||
}
|
||||
|
@ -271,7 +263,7 @@ class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
|
||||
class PlaylistMenuDialogFragment : MenuDialogFragment<Menu.ForPlaylist>() {
|
||||
override val menuModel: MenuViewModel by viewModels()
|
||||
override val listModel: ListViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
|
@ -279,13 +271,11 @@ class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
|
|||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val args: PlaylistMenuDialogFragmentArgs by navArgs()
|
||||
|
||||
override val menuRes: Int
|
||||
get() = args.menuRes
|
||||
override val uid: Music.UID
|
||||
get() = args.playlistUid
|
||||
override val parcel
|
||||
get() = args.parcel
|
||||
|
||||
override fun getDisabledItemIds(music: Playlist) =
|
||||
if (music.songs.isEmpty()) {
|
||||
override fun getDisabledItemIds(menu: Menu.ForPlaylist) =
|
||||
if (menu.playlist.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(
|
||||
|
@ -299,35 +289,35 @@ class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
|
|||
setOf()
|
||||
}
|
||||
|
||||
override fun updateMusic(binding: DialogMenuBinding, music: Playlist) {
|
||||
override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForPlaylist) {
|
||||
val context = requireContext()
|
||||
binding.menuCover.bind(music)
|
||||
binding.menuCover.bind(menu.playlist)
|
||||
binding.menuType.text = getString(R.string.lbl_playlist)
|
||||
binding.menuName.text = music.name.resolve(context)
|
||||
binding.menuName.text = menu.playlist.name.resolve(context)
|
||||
binding.menuInfo.text =
|
||||
if (music.songs.isNotEmpty()) {
|
||||
context.getPlural(R.plurals.fmt_song_count, music.songs.size)
|
||||
if (menu.playlist.songs.isNotEmpty()) {
|
||||
context.getPlural(R.plurals.fmt_song_count, menu.playlist.songs.size)
|
||||
} else {
|
||||
getString(R.string.def_song_count)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(item: MenuItem, music: Playlist) {
|
||||
override fun onClick(item: MenuItem, menu: Menu.ForPlaylist) {
|
||||
when (item.itemId) {
|
||||
R.id.action_play -> playbackModel.play(music)
|
||||
R.id.action_shuffle -> playbackModel.shuffle(music)
|
||||
R.id.action_detail -> detailModel.showPlaylist(music)
|
||||
R.id.action_play -> playbackModel.play(menu.playlist)
|
||||
R.id.action_shuffle -> playbackModel.shuffle(menu.playlist)
|
||||
R.id.action_detail -> detailModel.showPlaylist(menu.playlist)
|
||||
R.id.action_play_next -> {
|
||||
playbackModel.playNext(music)
|
||||
playbackModel.playNext(menu.playlist)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_queue_add -> {
|
||||
playbackModel.addToQueue(music)
|
||||
playbackModel.addToQueue(menu.playlist)
|
||||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_rename -> musicModel.renamePlaylist(music)
|
||||
R.id.action_delete -> musicModel.deletePlaylist(music)
|
||||
R.id.action_share -> requireContext().share(music)
|
||||
R.id.action_rename -> musicModel.renamePlaylist(menu.playlist)
|
||||
R.id.action_delete -> musicModel.deletePlaylist(menu.playlist)
|
||||
R.id.action_share -> requireContext().share(menu.playlist)
|
||||
else -> error("Unexpected menu item $item")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.list.Menu
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.MusicRepository
|
||||
import org.oxycblt.auxio.playback.PlaySong
|
||||
import org.oxycblt.auxio.util.logW
|
||||
|
||||
/**
|
||||
|
@ -35,32 +37,62 @@ import org.oxycblt.auxio.util.logW
|
|||
@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
|
||||
private val _currentMenu = MutableStateFlow<Menu?>(null)
|
||||
/** The current [Menu] information being shown in a dialog. */
|
||||
val currentMenu: StateFlow<Menu?> = _currentMenu
|
||||
|
||||
init {
|
||||
musicRepository.addUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
||||
_currentMusic.value = _currentMusic.value?.let { musicRepository.find(it.uid) }
|
||||
_currentMenu.value = _currentMenu.value?.let { unpackParcel(it.parcel) }
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
musicRepository.removeUpdateListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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")
|
||||
fun setMenu(parcel: Menu.Parcel) {
|
||||
_currentMenu.value = unpackParcel(parcel)
|
||||
if (_currentMenu.value == null) {
|
||||
logW("Given menu parcel $parcel was invalid")
|
||||
}
|
||||
}
|
||||
|
||||
private fun unpackParcel(parcel: Menu.Parcel) =
|
||||
when (parcel) {
|
||||
is Menu.ForSong.Parcel -> unpackSongParcel(parcel)
|
||||
is Menu.ForAlbum.Parcel -> unpackAlbumParcel(parcel)
|
||||
is Menu.ForArtist.Parcel -> unpackArtistParcel(parcel)
|
||||
is Menu.ForGenre.Parcel -> unpackGenreParcel(parcel)
|
||||
is Menu.ForPlaylist.Parcel -> unpackPlaylistParcel(parcel)
|
||||
}
|
||||
|
||||
private fun unpackSongParcel(parcel: Menu.ForSong.Parcel): Menu.ForSong? {
|
||||
val song = musicRepository.deviceLibrary?.findSong(parcel.songUid) ?: return null
|
||||
val parent = parcel.playWithUid?.let(musicRepository::find) as MusicParent?
|
||||
val playWith = PlaySong.fromIntCode(parcel.playWithCode, parent) ?: return null
|
||||
return Menu.ForSong(parcel.res, song, playWith)
|
||||
}
|
||||
|
||||
private fun unpackAlbumParcel(parcel: Menu.ForAlbum.Parcel): Menu.ForAlbum? {
|
||||
val album = musicRepository.deviceLibrary?.findAlbum(parcel.albumUid) ?: return null
|
||||
return Menu.ForAlbum(parcel.res, album)
|
||||
}
|
||||
|
||||
private fun unpackArtistParcel(parcel: Menu.ForArtist.Parcel): Menu.ForArtist? {
|
||||
val artist = musicRepository.deviceLibrary?.findArtist(parcel.artistUid) ?: return null
|
||||
return Menu.ForArtist(parcel.res, artist)
|
||||
}
|
||||
|
||||
private fun unpackGenreParcel(parcel: Menu.ForGenre.Parcel): Menu.ForGenre? {
|
||||
val genre = musicRepository.deviceLibrary?.findGenre(parcel.genreUid) ?: return null
|
||||
return Menu.ForGenre(parcel.res, genre)
|
||||
}
|
||||
|
||||
private fun unpackPlaylistParcel(parcel: Menu.ForPlaylist.Parcel): Menu.ForPlaylist? {
|
||||
val playlist = musicRepository.userLibrary?.findPlaylist(parcel.playlistUid) ?: return null
|
||||
return Menu.ForPlaylist(parcel.res, playlist)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ interface DeviceLibrary {
|
|||
* Find a [Album] instance corresponding to the given [Music.UID].
|
||||
*
|
||||
* @param uid The [Music.UID] to search for.
|
||||
* @return The corresponding [Song], or null if one was not found.
|
||||
* @return The corresponding [Album], or null if one was not found.
|
||||
*/
|
||||
fun findAlbum(uid: Music.UID): Album?
|
||||
|
||||
|
@ -83,7 +83,7 @@ interface DeviceLibrary {
|
|||
* Find a [Artist] instance corresponding to the given [Music.UID].
|
||||
*
|
||||
* @param uid The [Music.UID] to search for.
|
||||
* @return The corresponding [Song], or null if one was not found.
|
||||
* @return The corresponding [Artist], or null if one was not found.
|
||||
*/
|
||||
fun findArtist(uid: Music.UID): Artist?
|
||||
|
||||
|
@ -91,7 +91,7 @@ interface DeviceLibrary {
|
|||
* Find a [Genre] instance corresponding to the given [Music.UID].
|
||||
*
|
||||
* @param uid The [Music.UID] to search for.
|
||||
* @return The corresponding [Song], or null if one was not found.
|
||||
* @return The corresponding [Genre], or null if one was not found.
|
||||
*/
|
||||
fun findGenre(uid: Music.UID): Genre?
|
||||
|
||||
|
|
|
@ -21,56 +21,103 @@ package org.oxycblt.auxio.playback
|
|||
import org.oxycblt.auxio.IntegerTable
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
|
||||
/**
|
||||
* Configuration to play a song in a desired way.
|
||||
*
|
||||
* Since songs are not [MusicParent]s, the way the queue is generated around them has a lot more
|
||||
* flexibility. The particular strategy used can be configured the user, but it also needs to be
|
||||
* transferred between views at points (such as menus). [PlaySong] provides both of these, being a
|
||||
* enum-like datatype when configuration is needed, and an algebraic datatype when data transfer is
|
||||
* needed.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
sealed interface PlaySong {
|
||||
/**
|
||||
* The integer representation of this instance.
|
||||
*
|
||||
* @see fromIntCode
|
||||
*/
|
||||
val intCode: Int
|
||||
|
||||
/** Play a Song from the entire library of songs. */
|
||||
object FromAll : PlaySong {
|
||||
override val intCode = IntegerTable.PLAY_SONG_FROM_ALL
|
||||
}
|
||||
|
||||
/** Play a song from it's album. */
|
||||
object FromAlbum : PlaySong {
|
||||
override val intCode = IntegerTable.PLAY_SONG_FROM_ALBUM
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a song from (possibly) one of it's [Artist]s.
|
||||
*
|
||||
* @param which The [Artist] to specifically play from. If null, the user will be prompted for
|
||||
* an [Artist] to choose of the song has multiple. Otherwise, the only [Artist] will be used.
|
||||
*/
|
||||
data class FromArtist(val which: Artist?) : PlaySong {
|
||||
override val intCode = IntegerTable.PLAY_SONG_FROM_ARTIST
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a song from (possibly) one of it's [Genre]s.
|
||||
*
|
||||
* @param which The [Genre] to specifically play from. If null, the user will be prompted for a
|
||||
* [Genre] to choose of the song has multiple. Otherwise, the only [Genre] will be used.
|
||||
*/
|
||||
data class FromGenre(val which: Genre?) : PlaySong {
|
||||
override val intCode = IntegerTable.PLAY_SONG_FROM_GENRE
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a song from one of it's [Playlist]s.
|
||||
*
|
||||
* @param which The [Playlist] to specifically play from. This must be provided.
|
||||
*/
|
||||
data class FromPlaylist(val which: Playlist) : PlaySong {
|
||||
override val intCode = IntegerTable.PLAY_SONG_FROM_PLAYLIST
|
||||
}
|
||||
|
||||
/** Only play the given song, include nothing else in the queue. */
|
||||
object ByItself : PlaySong {
|
||||
override val intCode = IntegerTable.PLAY_SONG_BY_ITSELF
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromIntCode(intCode: Int, inner: Music?): PlaySong? =
|
||||
/**
|
||||
* Convert a [PlaySong] integer representation into an instance.
|
||||
*
|
||||
* @param intCode An integer representation of a [PlaySong]
|
||||
* @param which Optional [MusicParent] to automatically populate a [FromArtist],
|
||||
* [FromGenre], or [FromPlaylist] instance. If the type of the [MusicParent] does not
|
||||
* match, it will be considered invalid and null will be returned.
|
||||
* @return The corresponding [PlaySong], or null if the [PlaySong] is invalid.
|
||||
* @see PlaySong.intCode
|
||||
*/
|
||||
fun fromIntCode(intCode: Int, which: MusicParent? = null): PlaySong? =
|
||||
when (intCode) {
|
||||
IntegerTable.PLAY_SONG_BY_ITSELF -> ByItself
|
||||
IntegerTable.PLAY_SONG_FROM_ALL -> FromAll
|
||||
IntegerTable.PLAY_SONG_FROM_ALBUM -> FromAlbum
|
||||
IntegerTable.PLAY_SONG_FROM_ARTIST ->
|
||||
if (inner is Artist?) {
|
||||
FromArtist(inner)
|
||||
if (which is Artist?) {
|
||||
FromArtist(which)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
IntegerTable.PLAY_SONG_FROM_GENRE ->
|
||||
if (inner is Genre?) {
|
||||
FromGenre(inner)
|
||||
if (which is Genre?) {
|
||||
FromGenre(which)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
IntegerTable.PLAY_SONG_FROM_PLAYLIST ->
|
||||
if (inner is Playlist) {
|
||||
FromPlaylist(inner)
|
||||
if (which is Playlist) {
|
||||
FromPlaylist(which)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
|
@ -75,17 +75,14 @@ class PlaybackSettingsImpl @Inject constructor(@ApplicationContext context: Cont
|
|||
get() =
|
||||
PlaySong.fromIntCode(
|
||||
sharedPreferences.getInt(
|
||||
getString(R.string.set_key_play_in_list_with), Int.MIN_VALUE),
|
||||
null)
|
||||
getString(R.string.set_key_play_in_list_with), Int.MIN_VALUE))
|
||||
?: PlaySong.FromAll
|
||||
|
||||
override val inParentPlaybackMode: PlaySong?
|
||||
get() =
|
||||
PlaySong.fromIntCode(
|
||||
sharedPreferences
|
||||
.getInt(getString(R.string.set_key_play_in_parent_with), Int.MIN_VALUE)
|
||||
.also { logD(it) },
|
||||
null)
|
||||
sharedPreferences.getInt(
|
||||
getString(R.string.set_key_play_in_parent_with), Int.MIN_VALUE))
|
||||
|
||||
override val barAction: ActionMode
|
||||
get() =
|
||||
|
|
|
@ -184,13 +184,13 @@ constructor(
|
|||
playWithImpl(song, with, isImplicitlyShuffled())
|
||||
}
|
||||
|
||||
// fun playExplicit(song: Song, with: PlaySong) {
|
||||
// playWithImpl(song, with, false)
|
||||
// }
|
||||
//
|
||||
// fun shuffleExplicit(song: Song, with: PlaySong) {
|
||||
// playWithImpl(song, with, true)
|
||||
// }
|
||||
fun playExplicit(song: Song, with: PlaySong) {
|
||||
playWithImpl(song, with, false)
|
||||
}
|
||||
|
||||
fun shuffleExplicit(song: Song, with: PlaySong) {
|
||||
playWithImpl(song, with, true)
|
||||
}
|
||||
|
||||
/** Shuffle all songs in the music library. */
|
||||
fun shuffleAll() {
|
||||
|
|
|
@ -184,7 +184,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
|||
|
||||
override fun onOpenMenu(item: Music, anchor: View) {
|
||||
when (item) {
|
||||
is Song -> listModel.openMenu(R.menu.item_song, item)
|
||||
is Song -> listModel.openMenu(R.menu.item_song, item, searchModel.playWith)
|
||||
is Album -> listModel.openMenu(R.menu.item_album, item)
|
||||
is Artist -> listModel.openMenu(R.menu.item_parent, item)
|
||||
is Genre -> listModel.openMenu(R.menu.item_parent, item)
|
||||
|
@ -256,16 +256,11 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
|||
if (menu == null) return
|
||||
val directions =
|
||||
when (menu) {
|
||||
is Menu.ForSong ->
|
||||
SearchFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForAlbum ->
|
||||
SearchFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForArtist ->
|
||||
SearchFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForGenre ->
|
||||
SearchFragmentDirections.openGenreMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForPlaylist ->
|
||||
SearchFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid)
|
||||
is Menu.ForSong -> SearchFragmentDirections.openSongMenu(menu.parcel)
|
||||
is Menu.ForAlbum -> SearchFragmentDirections.openAlbumMenu(menu.parcel)
|
||||
is Menu.ForArtist -> SearchFragmentDirections.openArtistMenu(menu.parcel)
|
||||
is Menu.ForGenre -> SearchFragmentDirections.openGenreMenu(menu.parcel)
|
||||
is Menu.ForPlaylist -> SearchFragmentDirections.openPlaylistMenu(menu.parcel)
|
||||
}
|
||||
findNavController().navigateSafe(directions)
|
||||
// Keyboard is no longer needed.
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- <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"
|
||||
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"
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- <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"
|
||||
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"
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- <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"
|
||||
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"
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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"
|
||||
|
|
|
@ -351,11 +351,8 @@
|
|||
android:label="song_menu_dialog"
|
||||
tools:layout="@layout/dialog_menu">
|
||||
<argument
|
||||
android:name="menuRes"
|
||||
app:argType="integer" />
|
||||
<argument
|
||||
android:name="songUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
android:name="parcel"
|
||||
app:argType="org.oxycblt.auxio.list.Menu$ForSong$Parcel" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
|
@ -364,11 +361,8 @@
|
|||
android:label="album_menu_dialog"
|
||||
tools:layout="@layout/dialog_menu">
|
||||
<argument
|
||||
android:name="menuRes"
|
||||
app:argType="integer"/>
|
||||
<argument
|
||||
android:name="albumUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
android:name="parcel"
|
||||
app:argType="org.oxycblt.auxio.list.Menu$ForAlbum$Parcel" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
|
@ -377,11 +371,8 @@
|
|||
android:label="artist_menu_dialog"
|
||||
tools:layout="@layout/dialog_menu">
|
||||
<argument
|
||||
android:name="menuRes"
|
||||
app:argType="integer" />
|
||||
<argument
|
||||
android:name="artistUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
android:name="parcel"
|
||||
app:argType="org.oxycblt.auxio.list.Menu$ForArtist$Parcel" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
|
@ -390,11 +381,8 @@
|
|||
android:label="genre_menu_dialog"
|
||||
tools:layout="@layout/dialog_menu">
|
||||
<argument
|
||||
android:name="menuRes"
|
||||
app:argType="integer" />
|
||||
<argument
|
||||
android:name="genreUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
android:name="parcel"
|
||||
app:argType="org.oxycblt.auxio.list.Menu$ForGenre$Parcel" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
|
@ -403,11 +391,8 @@
|
|||
android:label="playlist_menu_dialog"
|
||||
tools:layout="@layout/dialog_menu">
|
||||
<argument
|
||||
android:name="menuRes"
|
||||
app:argType="integer" />
|
||||
<argument
|
||||
android:name="playlistUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
android:name="parcel"
|
||||
app:argType="org.oxycblt.auxio.list.Menu$ForPlaylist$Parcel" />
|
||||
</dialog>
|
||||
|
||||
<fragment
|
||||
|
|
|
@ -108,8 +108,8 @@
|
|||
|
||||
<string-array name="entries_play_in_list_with">
|
||||
<item>@string/set_play_song_from_all</item>
|
||||
<item>@string/set_play_song_from_artist</item>
|
||||
<item>@string/set_play_song_from_album</item>
|
||||
<item>@string/set_play_song_from_artist</item>
|
||||
<item>@string/set_play_song_from_genre</item>
|
||||
<item>@string/set_play_song_by_itself</item>
|
||||
</string-array>
|
||||
|
@ -119,14 +119,14 @@
|
|||
<item>@integer/play_song_from_album</item>
|
||||
<item>@integer/play_song_from_artist</item>
|
||||
<item>@integer/play_song_from_genre</item>
|
||||
<item>@integer/play_song_itself</item>
|
||||
<item>@integer/play_song_by_itself</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="entries_play_in_parent_with">
|
||||
<item>@string/set_play_song_none</item>
|
||||
<item>@string/set_play_song_from_all</item>
|
||||
<item>@string/set_play_song_from_artist</item>
|
||||
<item>@string/set_play_song_from_album</item>
|
||||
<item>@string/set_play_song_from_artist</item>
|
||||
<item>@string/set_play_song_from_genre</item>
|
||||
<item>@string/set_play_song_by_itself</item>
|
||||
</string-array>
|
||||
|
@ -137,7 +137,7 @@
|
|||
<item>@integer/play_song_from_album</item>
|
||||
<item>@integer/play_song_from_artist</item>
|
||||
<item>@integer/play_song_from_genre</item>
|
||||
<item>@integer/play_song_itself</item>
|
||||
<item>@integer/play_song_by_itself</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="entries_replay_gain">
|
||||
|
@ -157,11 +157,12 @@
|
|||
<integer name="theme_dark">2</integer>
|
||||
|
||||
<integer name="play_song_none">-2147483648</integer>
|
||||
<integer name="play_song_itself">0xA11F</integer>
|
||||
<integer name="play_song_from_all">0xA120</integer>
|
||||
<integer name="play_song_from_album">0xA121</integer>
|
||||
<integer name="play_song_from_artist">0xA122</integer>
|
||||
<integer name="play_song_from_genre">0xA123</integer>
|
||||
<integer name="play_song_from_all">0xA11F</integer>
|
||||
<integer name="play_song_from_album">0xA120</integer>
|
||||
<integer name="play_song_from_artist">0xA121</integer>
|
||||
<integer name="play_song_from_genre">0xA122</integer>
|
||||
<integer name="play_song_from_playlist">0xA123</integer>
|
||||
<integer name="play_song_by_itself">0xA124</integer>
|
||||
|
||||
<integer name="replay_gain_track">0xA111</integer>
|
||||
<integer name="replay_gain_album">0xA112</integer>
|
||||
|
|
Loading…
Reference in a new issue