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:
Alexander Capehart 2023-07-11 14:45:08 -06:00
parent ecc84dd8c8
commit 32a0d97e5d
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
22 changed files with 373 additions and 280 deletions

View file

@ -121,16 +121,16 @@ object IntegerTable {
const val COVER_MODE_MEDIA_STORE = 0xA11D const val COVER_MODE_MEDIA_STORE = 0xA11D
/** CoverMode.Quality */ /** CoverMode.Quality */
const val COVER_MODE_QUALITY = 0xA11E const val COVER_MODE_QUALITY = 0xA11E
/** PlaySong.ByItself */
const val PLAY_SONG_BY_ITSELF = 0xA11F
/** PlaySong.FromAll */ /** PlaySong.FromAll */
const val PLAY_SONG_FROM_ALL = 0xA120 const val PLAY_SONG_FROM_ALL = 0xA11F
/** PlaySong.FromAlbum */ /** PlaySong.FromAlbum */
const val PLAY_SONG_FROM_ALBUM = 0xA121 const val PLAY_SONG_FROM_ALBUM = 0xA120
/** PlaySong.FromArtist */ /** PlaySong.FromArtist */
const val PLAY_SONG_FROM_ARTIST = 0xA122 const val PLAY_SONG_FROM_ARTIST = 0xA121
/** PlaySong.FromGenre */ /** PlaySong.FromGenre */
const val PLAY_SONG_FROM_GENRE = 0xA123 const val PLAY_SONG_FROM_GENRE = 0xA122
/** PlaySong.FromPlaylist */ /** 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
} }

View file

@ -183,7 +183,7 @@ class AlbumDetailFragment :
} }
override fun onOpenMenu(item: Song, anchor: View) { 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() { override fun onPlay() {
@ -302,8 +302,7 @@ class AlbumDetailFragment :
if (menu == null) return if (menu == null) return
val directions = val directions =
when (menu) { when (menu) {
is Menu.ForSong -> is Menu.ForSong -> AlbumDetailFragmentDirections.openSongMenu(menu.parcel)
AlbumDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
is Menu.ForAlbum, is Menu.ForAlbum,
is Menu.ForArtist, is Menu.ForArtist,
is Menu.ForGenre, is Menu.ForGenre,

View file

@ -186,7 +186,8 @@ class ArtistDetailFragment :
override fun onOpenMenu(item: Music, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
when (item) { 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) is Album -> listModel.openMenu(R.menu.item_artist_album, item)
else -> error("Unexpected datatype: ${item::class.simpleName}") else -> error("Unexpected datatype: ${item::class.simpleName}")
} }
@ -306,10 +307,8 @@ class ArtistDetailFragment :
if (menu == null) return if (menu == null) return
val directions = val directions =
when (menu) { when (menu) {
is Menu.ForSong -> is Menu.ForSong -> ArtistDetailFragmentDirections.openSongMenu(menu.parcel)
ArtistDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) is Menu.ForAlbum -> ArtistDetailFragmentDirections.openAlbumMenu(menu.parcel)
is Menu.ForAlbum ->
ArtistDetailFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid)
is Menu.ForArtist, is Menu.ForArtist,
is Menu.ForGenre, is Menu.ForGenre,
is Menu.ForPlaylist -> error("Unexpected menu $menu") is Menu.ForPlaylist -> error("Unexpected menu $menu")

View file

@ -185,7 +185,7 @@ class GenreDetailFragment :
override fun onOpenMenu(item: Music, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
when (item) { when (item) {
is Artist -> listModel.openMenu(R.menu.item_parent, 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}") else -> error("Unexpected datatype: ${item::class.simpleName}")
} }
} }
@ -294,10 +294,8 @@ class GenreDetailFragment :
if (menu == null) return if (menu == null) return
val directions = val directions =
when (menu) { when (menu) {
is Menu.ForSong -> is Menu.ForSong -> GenreDetailFragmentDirections.openSongMenu(menu.parcel)
GenreDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) is Menu.ForArtist -> GenreDetailFragmentDirections.openArtistMenu(menu.parcel)
is Menu.ForArtist ->
GenreDetailFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid)
is Menu.ForAlbum, is Menu.ForAlbum,
is Menu.ForGenre, is Menu.ForGenre,
is Menu.ForPlaylist -> error("Unexpected menu $menu") is Menu.ForPlaylist -> error("Unexpected menu $menu")

View file

@ -237,7 +237,7 @@ class PlaylistDetailFragment :
} }
override fun onOpenMenu(item: Song, anchor: View) { 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() { override fun onPlay() {
@ -344,8 +344,7 @@ class PlaylistDetailFragment :
if (menu == null) return if (menu == null) return
val directions = val directions =
when (menu) { when (menu) {
is Menu.ForSong -> is Menu.ForSong -> PlaylistDetailFragmentDirections.openSongMenu(menu.parcel)
PlaylistDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid)
is Menu.ForArtist, is Menu.ForArtist,
is Menu.ForAlbum, is Menu.ForAlbum,
is Menu.ForGenre, is Menu.ForGenre,

View file

@ -577,15 +577,11 @@ class HomeFragment :
if (menu == null) return if (menu == null) return
val directions = val directions =
when (menu) { when (menu) {
is Menu.ForSong -> HomeFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) is Menu.ForSong -> HomeFragmentDirections.openSongMenu(menu.parcel)
is Menu.ForAlbum -> is Menu.ForAlbum -> HomeFragmentDirections.openAlbumMenu(menu.parcel)
HomeFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid) is Menu.ForArtist -> HomeFragmentDirections.openArtistMenu(menu.parcel)
is Menu.ForArtist -> is Menu.ForGenre -> HomeFragmentDirections.openGenreMenu(menu.parcel)
HomeFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid) is Menu.ForPlaylist -> HomeFragmentDirections.openPlaylistMenu(menu.parcel)
is Menu.ForGenre ->
HomeFragmentDirections.openGenreMenu(menu.menuRes, menu.music.uid)
is Menu.ForPlaylist ->
HomeFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid)
} }
findNavController().navigateSafe(directions) findNavController().navigateSafe(directions)
} }

View file

@ -141,7 +141,7 @@ class SongListFragment :
} }
override fun onOpenMenu(item: Song, anchor: View) { 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>) { private fun updateSongs(songs: List<Song>) {

View file

@ -18,12 +18,14 @@
package org.oxycblt.auxio.list package org.oxycblt.auxio.list
import android.os.Parcelable
import androidx.annotation.MenuRes import androidx.annotation.MenuRes
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.parcelize.Parcelize
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre 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.MusicSettings
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaySong
import org.oxycblt.auxio.util.Event import org.oxycblt.auxio.util.Event
import org.oxycblt.auxio.util.MutableEvent import org.oxycblt.auxio.util.MutableEvent
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -146,10 +149,12 @@ constructor(
* *
* @param menuRes The resource of the menu to use. * @param menuRes The resource of the menu to use.
* @param song The [Song] to show. * @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") 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) * @author Alexander Capehart (OxygenCobalt)
*/ */
sealed interface Menu { sealed interface Menu {
/** The android resource ID of the menu options to display in the dialog. */ /** The menu resource to inflate in the menu dialog. */
val menuRes: Int @get:MenuRes val res: Int
/** The [Music] that the menu should act on. */ /** A [Parcel] version of this instance that can be used as a navigation argument. */
val music: Music val parcel: Parcel
sealed interface Parcel : Parcelable
/** Navigate to a [Song] menu dialog. */ /** Navigate to a [Song] menu dialog. */
class ForSong(@MenuRes override val menuRes: Int, override val music: Song) : 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. */ /** Navigate to a [Album] menu dialog. */
class ForAlbum(@MenuRes override val menuRes: Int, override val music: Album) : Menu 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. */ /** Navigate to a [Artist] menu dialog. */
class ForArtist(@MenuRes override val menuRes: Int, override val music: Artist) : Menu 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. */ /** Navigate to a [Genre] menu dialog. */
class ForGenre(@MenuRes override val menuRes: Int, override val music: Genre) : Menu 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. */ /** Navigate to a [Playlist] menu dialog. */
class ForPlaylist(@MenuRes override val menuRes: Int, override val music: Playlist) : Menu 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
}
} }

View file

@ -30,8 +30,8 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.DialogMenuBinding import org.oxycblt.auxio.databinding.DialogMenuBinding
import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.ListViewModel import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD 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 * 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> { ViewBindingBottomSheetDialogFragment<DialogMenuBinding>(), ClickableListListener<MenuItem> {
protected abstract val menuModel: MenuViewModel protected abstract val menuModel: MenuViewModel
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 parcel: Menu.Parcel
abstract val menuRes: Int
/** The [Music.UID] of the [T] to display menu options for. */
abstract val uid: Music.UID
/** /**
* Get the options to disable in the context of the currently shown [T]. * 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 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. * Forward the clicked [MenuItem] to it's corresponding handler in another module.
* *
* @param item The [MenuItem] that was clicked. * @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) override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater)
@ -94,8 +90,8 @@ abstract class MenuDialogFragment<T : Music> :
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
listModel.menu.consume() listModel.menu.consume()
menuModel.setMusic(uid) menuModel.setMenu(parcel)
collectImmediately(menuModel.currentMusic, this::updateMusic) collectImmediately(menuModel.currentMenu, this::updateMenu)
} }
override fun onDestroyBinding(binding: DialogMenuBinding) { override fun onDestroyBinding(binding: DialogMenuBinding) {
@ -105,23 +101,25 @@ abstract class MenuDialogFragment<T : Music> :
binding.menuOptionRecycler.adapter = null binding.menuOptionRecycler.adapter = null
} }
private fun updateMusic(music: Music?) { private fun updateMenu(menu: Menu?) {
if (music == null) { if (menu == null) {
logD("No music to show, navigating away") logD("No menu to show, navigating away")
findNavController().navigateUp() 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). // 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 // 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. // depend on the AndroidX Toolbar internal API and hope for the best.
@SuppressLint("RestrictedApi") val builder = MenuBuilder(requireContext()) @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 // Disable any menu options as specified by the impl
val disabledIds = getDisabledItemIds(castedMusic) val disabledIds = getDisabledItemIds(casted)
val visible = val visible =
builder.children.mapTo(mutableListOf()) { builder.children.mapTo(mutableListOf()) {
it.isEnabled = !disabledIds.contains(it.itemId) it.isEnabled = !disabledIds.contains(it.itemId)
@ -130,7 +128,7 @@ abstract class MenuDialogFragment<T : Music> :
menuAdapter.update(visible, UpdateInstructions.Diff) menuAdapter.update(visible, UpdateInstructions.Diff)
// Delegate to impl how to show music // Delegate to impl how to show music
updateMusic(requireBinding(), castedMusic) updateMenu(requireBinding(), casted)
} }
final override fun onClick(item: MenuItem, viewHolder: RecyclerView.ViewHolder) { 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 // TODO: This should change if the app is 100% migrated to menu dialogs
findNavController().navigateUp() findNavController().navigateUp()
// Delegate to impl on how to handle items // 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)
} }
} }

View file

@ -27,10 +27,9 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMenuBinding import org.oxycblt.auxio.databinding.DialogMenuBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ListViewModel 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.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
@ -46,7 +45,7 @@ import org.oxycblt.auxio.util.showToast
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class SongMenuDialogFragment : MenuDialogFragment<Song>() { class SongMenuDialogFragment : MenuDialogFragment<Menu.ForSong>() {
override val menuModel: MenuViewModel by activityViewModels() override val menuModel: MenuViewModel by activityViewModels()
override val listModel: ListViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
@ -54,38 +53,37 @@ class SongMenuDialogFragment : MenuDialogFragment<Song>() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val args: SongMenuDialogFragmentArgs by navArgs() private val args: SongMenuDialogFragmentArgs by navArgs()
override val menuRes: Int override val parcel
get() = args.menuRes get() = args.parcel
override val uid: Music.UID
get() = args.songUid
// Nothing to disable in song menus. // 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() val context = requireContext()
binding.menuCover.bind(music) binding.menuCover.bind(menu.song)
binding.menuType.text = getString(R.string.lbl_song) binding.menuType.text = getString(R.string.lbl_song)
binding.menuName.text = music.name.resolve(context) binding.menuName.text = menu.song.name.resolve(context)
binding.menuInfo.text = music.artists.resolveNames(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) { 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 -> { R.id.action_play_next -> {
playbackModel.playNext(music) playbackModel.playNext(menu.song)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_queue_add -> { R.id.action_queue_add -> {
playbackModel.addToQueue(music) playbackModel.addToQueue(menu.song)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_artist_details -> detailModel.showArtist(music) R.id.action_artist_details -> detailModel.showArtist(menu.song)
R.id.action_album_details -> detailModel.showAlbum(music) R.id.action_album_details -> detailModel.showAlbum(menu.song)
R.id.action_share -> requireContext().share(music) R.id.action_share -> requireContext().share(menu.song)
R.id.action_playlist_add -> musicModel.addToPlaylist(music) R.id.action_playlist_add -> musicModel.addToPlaylist(menu.song)
R.id.action_detail -> detailModel.showSong(music) R.id.action_detail -> detailModel.showSong(menu.song)
else -> error("Unexpected menu item selected $item") else -> error("Unexpected menu item selected $item")
} }
} }
@ -97,7 +95,7 @@ class SongMenuDialogFragment : MenuDialogFragment<Song>() {
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class AlbumMenuDialogFragment : MenuDialogFragment<Album>() { class AlbumMenuDialogFragment : MenuDialogFragment<Menu.ForAlbum>() {
override val menuModel: MenuViewModel by viewModels() override val menuModel: MenuViewModel by viewModels()
override val listModel: ListViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
@ -105,38 +103,36 @@ class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val args: AlbumMenuDialogFragmentArgs by navArgs() private val args: AlbumMenuDialogFragmentArgs by navArgs()
override val menuRes: Int override val parcel
get() = args.menuRes get() = args.parcel
override val uid: Music.UID
get() = args.albumUid
// Nothing to disable in album menus. // 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() val context = requireContext()
binding.menuCover.bind(music) binding.menuCover.bind(menu.album)
binding.menuType.text = getString(music.releaseType.stringRes) binding.menuType.text = getString(menu.album.releaseType.stringRes)
binding.menuName.text = music.name.resolve(context) binding.menuName.text = menu.album.name.resolve(context)
binding.menuInfo.text = music.artists.resolveNames(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) { when (item.itemId) {
R.id.action_play -> playbackModel.play(music) R.id.action_play -> playbackModel.play(menu.album)
R.id.action_shuffle -> playbackModel.shuffle(music) R.id.action_shuffle -> playbackModel.shuffle(menu.album)
R.id.action_detail -> detailModel.showAlbum(music) R.id.action_detail -> detailModel.showAlbum(menu.album)
R.id.action_play_next -> { R.id.action_play_next -> {
playbackModel.playNext(music) playbackModel.playNext(menu.album)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_queue_add -> { R.id.action_queue_add -> {
playbackModel.addToQueue(music) playbackModel.addToQueue(menu.album)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_artist_details -> detailModel.showArtist(music) R.id.action_artist_details -> detailModel.showArtist(menu.album)
R.id.action_playlist_add -> musicModel.addToPlaylist(music) R.id.action_playlist_add -> musicModel.addToPlaylist(menu.album)
R.id.action_share -> requireContext().share(music) R.id.action_share -> requireContext().share(menu.album)
else -> error("Unexpected menu item selected $item") else -> error("Unexpected menu item selected $item")
} }
} }
@ -148,7 +144,7 @@ class AlbumMenuDialogFragment : MenuDialogFragment<Album>() {
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() { class ArtistMenuDialogFragment : MenuDialogFragment<Menu.ForArtist>() {
override val menuModel: MenuViewModel by viewModels() override val menuModel: MenuViewModel by viewModels()
override val listModel: ListViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
@ -156,13 +152,11 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val args: ArtistMenuDialogFragmentArgs by navArgs() private val args: ArtistMenuDialogFragmentArgs by navArgs()
override val menuRes: Int override val parcel
get() = args.menuRes get() = args.parcel
override val uid: Music.UID
get() = args.artistUid
override fun getDisabledItemIds(music: Artist) = override fun getDisabledItemIds(menu: Menu.ForArtist) =
if (music.songs.isEmpty()) { if (menu.artist.songs.isEmpty()) {
// Disable any operations that require some kind of songs to work with, as there won't // Disable any operations that require some kind of songs to work with, as there won't
// be any in an empty artist. // be any in an empty artist.
setOf( setOf(
@ -176,37 +170,37 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
setOf() setOf()
} }
override fun updateMusic(binding: DialogMenuBinding, music: Artist) { override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForArtist) {
val context = requireContext() val context = requireContext()
binding.menuCover.bind(music) binding.menuCover.bind(menu.artist)
binding.menuType.text = getString(R.string.lbl_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 = binding.menuInfo.text =
getString( getString(
R.string.fmt_two, R.string.fmt_two,
context.getPlural(R.plurals.fmt_album_count, music.albums.size), context.getPlural(R.plurals.fmt_album_count, menu.artist.albums.size),
if (music.songs.isNotEmpty()) { if (menu.artist.songs.isNotEmpty()) {
context.getPlural(R.plurals.fmt_song_count, music.songs.size) context.getPlural(R.plurals.fmt_song_count, menu.artist.songs.size)
} else { } else {
getString(R.string.def_song_count) getString(R.string.def_song_count)
}) })
} }
override fun onClick(item: MenuItem, music: Artist) { override fun onClick(item: MenuItem, menu: Menu.ForArtist) {
when (item.itemId) { when (item.itemId) {
R.id.action_play -> playbackModel.play(music) R.id.action_play -> playbackModel.play(menu.artist)
R.id.action_shuffle -> playbackModel.shuffle(music) R.id.action_shuffle -> playbackModel.shuffle(menu.artist)
R.id.action_detail -> detailModel.showArtist(music) R.id.action_detail -> detailModel.showArtist(menu.artist)
R.id.action_play_next -> { R.id.action_play_next -> {
playbackModel.playNext(music) playbackModel.playNext(menu.artist)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_queue_add -> { R.id.action_queue_add -> {
playbackModel.addToQueue(music) playbackModel.addToQueue(menu.artist)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_playlist_add -> musicModel.addToPlaylist(music) R.id.action_playlist_add -> musicModel.addToPlaylist(menu.artist)
R.id.action_share -> requireContext().share(music) R.id.action_share -> requireContext().share(menu.artist)
else -> error("Unexpected menu item $item") else -> error("Unexpected menu item $item")
} }
} }
@ -218,7 +212,7 @@ class ArtistMenuDialogFragment : MenuDialogFragment<Artist>() {
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class GenreMenuDialogFragment : MenuDialogFragment<Genre>() { class GenreMenuDialogFragment : MenuDialogFragment<Menu.ForGenre>() {
override val menuModel: MenuViewModel by viewModels() override val menuModel: MenuViewModel by viewModels()
override val listModel: ListViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
@ -226,40 +220,38 @@ class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val args: GenreMenuDialogFragmentArgs by navArgs() private val args: GenreMenuDialogFragmentArgs by navArgs()
override val menuRes: Int override val parcel
get() = args.menuRes get() = args.parcel
override val uid: Music.UID
get() = args.genreUid
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() val context = requireContext()
binding.menuCover.bind(music) binding.menuCover.bind(menu.genre)
binding.menuType.text = getString(R.string.lbl_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 = binding.menuInfo.text =
getString( getString(
R.string.fmt_two, R.string.fmt_two,
context.getPlural(R.plurals.fmt_artist_count, music.artists.size), context.getPlural(R.plurals.fmt_artist_count, menu.genre.artists.size),
context.getPlural(R.plurals.fmt_song_count, music.songs.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) { when (item.itemId) {
R.id.action_play -> playbackModel.play(music) R.id.action_play -> playbackModel.play(menu.genre)
R.id.action_shuffle -> playbackModel.shuffle(music) R.id.action_shuffle -> playbackModel.shuffle(menu.genre)
R.id.action_detail -> detailModel.showGenre(music) R.id.action_detail -> detailModel.showGenre(menu.genre)
R.id.action_play_next -> { R.id.action_play_next -> {
playbackModel.playNext(music) playbackModel.playNext(menu.genre)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_queue_add -> { R.id.action_queue_add -> {
playbackModel.addToQueue(music) playbackModel.addToQueue(menu.genre)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_playlist_add -> musicModel.addToPlaylist(music) R.id.action_playlist_add -> musicModel.addToPlaylist(menu.genre)
R.id.action_share -> requireContext().share(music) R.id.action_share -> requireContext().share(menu.genre)
else -> error("Unexpected menu item $item") else -> error("Unexpected menu item $item")
} }
} }
@ -271,7 +263,7 @@ class GenreMenuDialogFragment : MenuDialogFragment<Genre>() {
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() { class PlaylistMenuDialogFragment : MenuDialogFragment<Menu.ForPlaylist>() {
override val menuModel: MenuViewModel by viewModels() override val menuModel: MenuViewModel by viewModels()
override val listModel: ListViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
@ -279,13 +271,11 @@ class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val args: PlaylistMenuDialogFragmentArgs by navArgs() private val args: PlaylistMenuDialogFragmentArgs by navArgs()
override val menuRes: Int override val parcel
get() = args.menuRes get() = args.parcel
override val uid: Music.UID
get() = args.playlistUid
override fun getDisabledItemIds(music: Playlist) = override fun getDisabledItemIds(menu: Menu.ForPlaylist) =
if (music.songs.isEmpty()) { if (menu.playlist.songs.isEmpty()) {
// Disable any operations that require some kind of songs to work with, as there won't // Disable any operations that require some kind of songs to work with, as there won't
// be any in an empty playlist. // be any in an empty playlist.
setOf( setOf(
@ -299,35 +289,35 @@ class PlaylistMenuDialogFragment : MenuDialogFragment<Playlist>() {
setOf() setOf()
} }
override fun updateMusic(binding: DialogMenuBinding, music: Playlist) { override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForPlaylist) {
val context = requireContext() val context = requireContext()
binding.menuCover.bind(music) binding.menuCover.bind(menu.playlist)
binding.menuType.text = getString(R.string.lbl_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 = binding.menuInfo.text =
if (music.songs.isNotEmpty()) { if (menu.playlist.songs.isNotEmpty()) {
context.getPlural(R.plurals.fmt_song_count, music.songs.size) context.getPlural(R.plurals.fmt_song_count, menu.playlist.songs.size)
} else { } else {
getString(R.string.def_song_count) getString(R.string.def_song_count)
} }
} }
override fun onClick(item: MenuItem, music: Playlist) { override fun onClick(item: MenuItem, menu: Menu.ForPlaylist) {
when (item.itemId) { when (item.itemId) {
R.id.action_play -> playbackModel.play(music) R.id.action_play -> playbackModel.play(menu.playlist)
R.id.action_shuffle -> playbackModel.shuffle(music) R.id.action_shuffle -> playbackModel.shuffle(menu.playlist)
R.id.action_detail -> detailModel.showPlaylist(music) R.id.action_detail -> detailModel.showPlaylist(menu.playlist)
R.id.action_play_next -> { R.id.action_play_next -> {
playbackModel.playNext(music) playbackModel.playNext(menu.playlist)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_queue_add -> { R.id.action_queue_add -> {
playbackModel.addToQueue(music) playbackModel.addToQueue(menu.playlist)
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_rename -> musicModel.renamePlaylist(music) R.id.action_rename -> musicModel.renamePlaylist(menu.playlist)
R.id.action_delete -> musicModel.deletePlaylist(music) R.id.action_delete -> musicModel.deletePlaylist(menu.playlist)
R.id.action_share -> requireContext().share(music) R.id.action_share -> requireContext().share(menu.playlist)
else -> error("Unexpected menu item $item") else -> error("Unexpected menu item $item")
} }
} }

View file

@ -23,8 +23,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow 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.music.MusicRepository
import org.oxycblt.auxio.playback.PlaySong
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
/** /**
@ -35,32 +37,62 @@ import org.oxycblt.auxio.util.logW
@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 _currentMenu = MutableStateFlow<Menu?>(null)
/** The current [Music] information being shown in a menu dialog. */ /** The current [Menu] information being shown in a dialog. */
val currentMusic: StateFlow<Music?> = _currentMusic val currentMenu: StateFlow<Menu?> = _currentMenu
init { init {
musicRepository.addUpdateListener(this) musicRepository.addUpdateListener(this)
} }
override fun onMusicChanges(changes: MusicRepository.Changes) { 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() { override fun onCleared() {
musicRepository.removeUpdateListener(this) musicRepository.removeUpdateListener(this)
} }
/** fun setMenu(parcel: Menu.Parcel) {
* Set a new [currentMusic] from it's [Music.UID]. [currentMusic] will be updated to align with _currentMenu.value = unpackParcel(parcel)
* the new album. if (_currentMenu.value == null) {
* logW("Given menu parcel $parcel was invalid")
* @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")
} }
} }
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)
}
} }

View file

@ -75,7 +75,7 @@ interface DeviceLibrary {
* Find a [Album] instance corresponding to the given [Music.UID]. * Find a [Album] instance corresponding to the given [Music.UID].
* *
* @param uid The [Music.UID] to search for. * @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? fun findAlbum(uid: Music.UID): Album?
@ -83,7 +83,7 @@ interface DeviceLibrary {
* Find a [Artist] instance corresponding to the given [Music.UID]. * Find a [Artist] instance corresponding to the given [Music.UID].
* *
* @param uid The [Music.UID] to search for. * @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? fun findArtist(uid: Music.UID): Artist?
@ -91,7 +91,7 @@ interface DeviceLibrary {
* Find a [Genre] instance corresponding to the given [Music.UID]. * Find a [Genre] instance corresponding to the given [Music.UID].
* *
* @param uid The [Music.UID] to search for. * @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? fun findGenre(uid: Music.UID): Genre?

View file

@ -21,56 +21,103 @@ package org.oxycblt.auxio.playback
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Playlist 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 { sealed interface PlaySong {
/**
* The integer representation of this instance.
*
* @see fromIntCode
*/
val intCode: Int val intCode: Int
/** Play a Song from the entire library of songs. */
object FromAll : PlaySong { object FromAll : PlaySong {
override val intCode = IntegerTable.PLAY_SONG_FROM_ALL override val intCode = IntegerTable.PLAY_SONG_FROM_ALL
} }
/** Play a song from it's album. */
object FromAlbum : PlaySong { object FromAlbum : PlaySong {
override val intCode = IntegerTable.PLAY_SONG_FROM_ALBUM 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 { data class FromArtist(val which: Artist?) : PlaySong {
override val intCode = IntegerTable.PLAY_SONG_FROM_ARTIST 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 { data class FromGenre(val which: Genre?) : PlaySong {
override val intCode = IntegerTable.PLAY_SONG_FROM_GENRE 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 { data class FromPlaylist(val which: Playlist) : PlaySong {
override val intCode = IntegerTable.PLAY_SONG_FROM_PLAYLIST override val intCode = IntegerTable.PLAY_SONG_FROM_PLAYLIST
} }
/** Only play the given song, include nothing else in the queue. */
object ByItself : PlaySong { object ByItself : PlaySong {
override val intCode = IntegerTable.PLAY_SONG_BY_ITSELF override val intCode = IntegerTable.PLAY_SONG_BY_ITSELF
} }
companion object { 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) { when (intCode) {
IntegerTable.PLAY_SONG_BY_ITSELF -> ByItself IntegerTable.PLAY_SONG_BY_ITSELF -> ByItself
IntegerTable.PLAY_SONG_FROM_ALL -> FromAll
IntegerTable.PLAY_SONG_FROM_ALBUM -> FromAlbum IntegerTable.PLAY_SONG_FROM_ALBUM -> FromAlbum
IntegerTable.PLAY_SONG_FROM_ARTIST -> IntegerTable.PLAY_SONG_FROM_ARTIST ->
if (inner is Artist?) { if (which is Artist?) {
FromArtist(inner) FromArtist(which)
} else { } else {
null null
} }
IntegerTable.PLAY_SONG_FROM_GENRE -> IntegerTable.PLAY_SONG_FROM_GENRE ->
if (inner is Genre?) { if (which is Genre?) {
FromGenre(inner) FromGenre(which)
} else { } else {
null null
} }
IntegerTable.PLAY_SONG_FROM_PLAYLIST -> IntegerTable.PLAY_SONG_FROM_PLAYLIST ->
if (inner is Playlist) { if (which is Playlist) {
FromPlaylist(inner) FromPlaylist(which)
} else { } else {
null null
} }

View file

@ -75,17 +75,14 @@ class PlaybackSettingsImpl @Inject constructor(@ApplicationContext context: Cont
get() = get() =
PlaySong.fromIntCode( PlaySong.fromIntCode(
sharedPreferences.getInt( sharedPreferences.getInt(
getString(R.string.set_key_play_in_list_with), Int.MIN_VALUE), getString(R.string.set_key_play_in_list_with), Int.MIN_VALUE))
null)
?: PlaySong.FromAll ?: PlaySong.FromAll
override val inParentPlaybackMode: PlaySong? override val inParentPlaybackMode: PlaySong?
get() = get() =
PlaySong.fromIntCode( PlaySong.fromIntCode(
sharedPreferences sharedPreferences.getInt(
.getInt(getString(R.string.set_key_play_in_parent_with), Int.MIN_VALUE) getString(R.string.set_key_play_in_parent_with), Int.MIN_VALUE))
.also { logD(it) },
null)
override val barAction: ActionMode override val barAction: ActionMode
get() = get() =

View file

@ -184,13 +184,13 @@ constructor(
playWithImpl(song, with, isImplicitlyShuffled()) playWithImpl(song, with, isImplicitlyShuffled())
} }
// fun playExplicit(song: Song, with: PlaySong) { fun playExplicit(song: Song, with: PlaySong) {
// playWithImpl(song, with, false) playWithImpl(song, with, false)
// } }
//
// fun shuffleExplicit(song: Song, with: PlaySong) { fun shuffleExplicit(song: Song, with: PlaySong) {
// playWithImpl(song, with, true) playWithImpl(song, with, true)
// } }
/** Shuffle all songs in the music library. */ /** Shuffle all songs in the music library. */
fun shuffleAll() { fun shuffleAll() {

View file

@ -184,7 +184,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
override fun onOpenMenu(item: Music, anchor: View) { override fun onOpenMenu(item: Music, anchor: View) {
when (item) { 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 Album -> listModel.openMenu(R.menu.item_album, item)
is Artist -> listModel.openMenu(R.menu.item_parent, item) is Artist -> listModel.openMenu(R.menu.item_parent, item)
is Genre -> 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 if (menu == null) return
val directions = val directions =
when (menu) { when (menu) {
is Menu.ForSong -> is Menu.ForSong -> SearchFragmentDirections.openSongMenu(menu.parcel)
SearchFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) is Menu.ForAlbum -> SearchFragmentDirections.openAlbumMenu(menu.parcel)
is Menu.ForAlbum -> is Menu.ForArtist -> SearchFragmentDirections.openArtistMenu(menu.parcel)
SearchFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid) is Menu.ForGenre -> SearchFragmentDirections.openGenreMenu(menu.parcel)
is Menu.ForArtist -> is Menu.ForPlaylist -> SearchFragmentDirections.openPlaylistMenu(menu.parcel)
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)
} }
findNavController().navigateSafe(directions) findNavController().navigateSafe(directions)
// Keyboard is no longer needed. // Keyboard is no longer needed.

View file

@ -1,13 +1,13 @@
<?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">
<!-- <item--> <item
<!-- android:id="@+id/action_play"--> android:id="@+id/action_play"
<!-- android:title="@string/lbl_play"--> android:title="@string/lbl_play"
<!-- android:icon="@drawable/ic_play_24" />--> android:icon="@drawable/ic_play_24" />
<!-- <item--> <item
<!-- android:id="@+id/action_shuffle"--> android:id="@+id/action_shuffle"
<!-- android:title="@string/lbl_shuffle"--> android:title="@string/lbl_shuffle"
<!-- android:icon="@drawable/ic_shuffle_off_24"/>--> 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"

View file

@ -1,13 +1,13 @@
<?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">
<!-- <item--> <item
<!-- android:id="@+id/action_play"--> android:id="@+id/action_play"
<!-- android:title="@string/lbl_play"--> android:title="@string/lbl_play"
<!-- android:icon="@drawable/ic_play_24" />--> android:icon="@drawable/ic_play_24" />
<!-- <item--> <item
<!-- android:id="@+id/action_shuffle"--> android:id="@+id/action_shuffle"
<!-- android:title="@string/lbl_shuffle"--> android:title="@string/lbl_shuffle"
<!-- android:icon="@drawable/ic_shuffle_off_24"/>--> 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"

View file

@ -1,13 +1,13 @@
<?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">
<!-- <item--> <item
<!-- android:id="@+id/action_play"--> android:id="@+id/action_play"
<!-- android:title="@string/lbl_play"--> android:title="@string/lbl_play"
<!-- android:icon="@drawable/ic_play_24" />--> android:icon="@drawable/ic_play_24" />
<!-- <item--> <item
<!-- android:id="@+id/action_shuffle"--> android:id="@+id/action_shuffle"
<!-- android:title="@string/lbl_shuffle"--> android:title="@string/lbl_shuffle"
<!-- android:icon="@drawable/ic_shuffle_off_24" />--> 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"

View file

@ -1,5 +1,13 @@
<?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">
<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"

View file

@ -351,11 +351,8 @@
android:label="song_menu_dialog" android:label="song_menu_dialog"
tools:layout="@layout/dialog_menu"> tools:layout="@layout/dialog_menu">
<argument <argument
android:name="menuRes" android:name="parcel"
app:argType="integer" /> app:argType="org.oxycblt.auxio.list.Menu$ForSong$Parcel" />
<argument
android:name="songUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog> </dialog>
<dialog <dialog
@ -364,11 +361,8 @@
android:label="album_menu_dialog" android:label="album_menu_dialog"
tools:layout="@layout/dialog_menu"> tools:layout="@layout/dialog_menu">
<argument <argument
android:name="menuRes" android:name="parcel"
app:argType="integer"/> app:argType="org.oxycblt.auxio.list.Menu$ForAlbum$Parcel" />
<argument
android:name="albumUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog> </dialog>
<dialog <dialog
@ -377,11 +371,8 @@
android:label="artist_menu_dialog" android:label="artist_menu_dialog"
tools:layout="@layout/dialog_menu"> tools:layout="@layout/dialog_menu">
<argument <argument
android:name="menuRes" android:name="parcel"
app:argType="integer" /> app:argType="org.oxycblt.auxio.list.Menu$ForArtist$Parcel" />
<argument
android:name="artistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog> </dialog>
<dialog <dialog
@ -390,11 +381,8 @@
android:label="genre_menu_dialog" android:label="genre_menu_dialog"
tools:layout="@layout/dialog_menu"> tools:layout="@layout/dialog_menu">
<argument <argument
android:name="menuRes" android:name="parcel"
app:argType="integer" /> app:argType="org.oxycblt.auxio.list.Menu$ForGenre$Parcel" />
<argument
android:name="genreUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog> </dialog>
<dialog <dialog
@ -403,11 +391,8 @@
android:label="playlist_menu_dialog" android:label="playlist_menu_dialog"
tools:layout="@layout/dialog_menu"> tools:layout="@layout/dialog_menu">
<argument <argument
android:name="menuRes" android:name="parcel"
app:argType="integer" /> app:argType="org.oxycblt.auxio.list.Menu$ForPlaylist$Parcel" />
<argument
android:name="playlistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog> </dialog>
<fragment <fragment

View file

@ -108,8 +108,8 @@
<string-array name="entries_play_in_list_with"> <string-array name="entries_play_in_list_with">
<item>@string/set_play_song_from_all</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_album</item>
<item>@string/set_play_song_from_artist</item>
<item>@string/set_play_song_from_genre</item> <item>@string/set_play_song_from_genre</item>
<item>@string/set_play_song_by_itself</item> <item>@string/set_play_song_by_itself</item>
</string-array> </string-array>
@ -119,14 +119,14 @@
<item>@integer/play_song_from_album</item> <item>@integer/play_song_from_album</item>
<item>@integer/play_song_from_artist</item> <item>@integer/play_song_from_artist</item>
<item>@integer/play_song_from_genre</item> <item>@integer/play_song_from_genre</item>
<item>@integer/play_song_itself</item> <item>@integer/play_song_by_itself</item>
</integer-array> </integer-array>
<string-array name="entries_play_in_parent_with"> <string-array name="entries_play_in_parent_with">
<item>@string/set_play_song_none</item> <item>@string/set_play_song_none</item>
<item>@string/set_play_song_from_all</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_album</item>
<item>@string/set_play_song_from_artist</item>
<item>@string/set_play_song_from_genre</item> <item>@string/set_play_song_from_genre</item>
<item>@string/set_play_song_by_itself</item> <item>@string/set_play_song_by_itself</item>
</string-array> </string-array>
@ -137,7 +137,7 @@
<item>@integer/play_song_from_album</item> <item>@integer/play_song_from_album</item>
<item>@integer/play_song_from_artist</item> <item>@integer/play_song_from_artist</item>
<item>@integer/play_song_from_genre</item> <item>@integer/play_song_from_genre</item>
<item>@integer/play_song_itself</item> <item>@integer/play_song_by_itself</item>
</integer-array> </integer-array>
<string-array name="entries_replay_gain"> <string-array name="entries_replay_gain">
@ -157,11 +157,12 @@
<integer name="theme_dark">2</integer> <integer name="theme_dark">2</integer>
<integer name="play_song_none">-2147483648</integer> <integer name="play_song_none">-2147483648</integer>
<integer name="play_song_itself">0xA11F</integer> <integer name="play_song_from_all">0xA11F</integer>
<integer name="play_song_from_all">0xA120</integer> <integer name="play_song_from_album">0xA120</integer>
<integer name="play_song_from_album">0xA121</integer> <integer name="play_song_from_artist">0xA121</integer>
<integer name="play_song_from_artist">0xA122</integer> <integer name="play_song_from_genre">0xA122</integer>
<integer name="play_song_from_genre">0xA123</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_track">0xA111</integer>
<integer name="replay_gain_album">0xA112</integer> <integer name="replay_gain_album">0xA112</integer>