Extend Popup actions
Extend the action menus to every music model.
This commit is contained in:
parent
d2350de12a
commit
13b80585d2
24 changed files with 291 additions and 115 deletions
|
@ -59,9 +59,9 @@ class MainFragment : Fragment() {
|
|||
)
|
||||
|
||||
val navController = (
|
||||
childFragmentManager.findFragmentById(R.id.explore_nav_host)
|
||||
as NavHostFragment?
|
||||
)?.findNavController()
|
||||
childFragmentManager.findFragmentById(R.id.explore_nav_host)
|
||||
as NavHostFragment?
|
||||
)?.findNavController()
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
|
|
|
@ -14,4 +14,7 @@ interface PlaybackStateDAO {
|
|||
|
||||
@Query("DELETE FROM playback_state_table")
|
||||
fun clear()
|
||||
|
||||
@Query("SELECT * FROM playback_state_table ORDER BY id DESC LIMIT 1")
|
||||
fun getRecent(): PlaybackState?
|
||||
}
|
||||
|
|
|
@ -18,8 +18,11 @@ interface QueueDAO {
|
|||
}
|
||||
}
|
||||
|
||||
@Query("SELECT * FROM queue_table")
|
||||
fun getAll(): List<QueueItem>
|
||||
@Query("SELECT * FROM queue_table WHERE is_user_queue == 1")
|
||||
fun getUserQueue(): List<QueueItem>
|
||||
|
||||
@Query("SELECT * FROM queue_table WHERE is_user_queue == 0")
|
||||
fun getQueue(): List<QueueItem>
|
||||
|
||||
@Query("DELETE FROM queue_table")
|
||||
fun clear()
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.oxycblt.auxio.ui.setupAlbumSongActions
|
|||
class AlbumDetailFragment : DetailFragment() {
|
||||
|
||||
private val args: AlbumDetailFragmentArgs by navArgs()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -45,10 +44,8 @@ class AlbumDetailFragment : DetailFragment() {
|
|||
}
|
||||
|
||||
val songAdapter = DetailSongAdapter(
|
||||
{
|
||||
playbackModel.playSong(it, PlaybackMode.IN_ALBUM)
|
||||
},
|
||||
{ data, view ->
|
||||
doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) },
|
||||
doOnLongClick = { data, view ->
|
||||
PopupMenu(requireContext(), view).setupAlbumSongActions(
|
||||
data, requireContext(), detailModel, playbackModel
|
||||
)
|
||||
|
@ -76,6 +73,10 @@ class AlbumDetailFragment : DetailFragment() {
|
|||
R.id.action_play -> playbackModel.playAlbum(
|
||||
detailModel.currentAlbum.value!!, false
|
||||
)
|
||||
|
||||
R.id.action_queue_add -> playbackModel.addToUserQueue(
|
||||
detailModel.currentAlbum.value!!.songs
|
||||
)
|
||||
}
|
||||
|
||||
true
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
|
@ -15,10 +16,10 @@ import org.oxycblt.auxio.music.MusicStore
|
|||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.ui.applyDivider
|
||||
import org.oxycblt.auxio.ui.disable
|
||||
import org.oxycblt.auxio.ui.setupAlbumActions
|
||||
|
||||
class ArtistDetailFragment : DetailFragment() {
|
||||
private val args: ArtistDetailFragmentArgs by navArgs()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -40,15 +41,22 @@ class ArtistDetailFragment : DetailFragment() {
|
|||
)
|
||||
}
|
||||
|
||||
val albumAdapter = DetailAlbumAdapter {
|
||||
if (!detailModel.isNavigating) {
|
||||
detailModel.updateNavigationStatus(true)
|
||||
val albumAdapter = DetailAlbumAdapter(
|
||||
doOnClick = {
|
||||
if (!detailModel.isNavigating) {
|
||||
detailModel.updateNavigationStatus(true)
|
||||
|
||||
findNavController().navigate(
|
||||
ArtistDetailFragmentDirections.actionShowAlbum(it.id, false)
|
||||
findNavController().navigate(
|
||||
ArtistDetailFragmentDirections.actionShowAlbum(it.id, false)
|
||||
)
|
||||
}
|
||||
},
|
||||
doOnLongClick = { data, view ->
|
||||
PopupMenu(requireContext(), view).setupAlbumActions(
|
||||
data, requireContext(), playbackModel
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
|
@ -106,10 +114,4 @@ class ArtistDetailFragment : DetailFragment() {
|
|||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
detailModel.updateNavigationStatus(false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,18 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
|
||||
/**
|
||||
* A Base [Fragment] implementing a [OnBackPressedCallback] so that Auxio will navigate upwards
|
||||
* instead of out of the app if a Detail Fragment is currently open.
|
||||
* instead of out of the app if a Detail Fragment is currently open. Also carries the
|
||||
* multi-navigation fix.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
abstract class DetailFragment : Fragment() {
|
||||
protected val detailModel: DetailViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
|
||||
}
|
||||
|
@ -20,6 +24,7 @@ abstract class DetailFragment : Fragment() {
|
|||
super.onResume()
|
||||
|
||||
callback.isEnabled = true
|
||||
detailModel.updateNavigationStatus(false)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
|
@ -15,11 +16,11 @@ import org.oxycblt.auxio.music.MusicStore
|
|||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.ui.applyDivider
|
||||
import org.oxycblt.auxio.ui.disable
|
||||
import org.oxycblt.auxio.ui.setupArtistActions
|
||||
|
||||
class GenreDetailFragment : DetailFragment() {
|
||||
|
||||
private val args: GenreDetailFragmentArgs by navArgs()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -41,15 +42,22 @@ class GenreDetailFragment : DetailFragment() {
|
|||
)
|
||||
}
|
||||
|
||||
val artistAdapter = DetailArtistAdapter {
|
||||
if (!detailModel.isNavigating) {
|
||||
detailModel.updateNavigationStatus(true)
|
||||
val artistAdapter = DetailArtistAdapter(
|
||||
doOnClick = {
|
||||
if (!detailModel.isNavigating) {
|
||||
detailModel.updateNavigationStatus(true)
|
||||
|
||||
findNavController().navigate(
|
||||
GenreDetailFragmentDirections.actionShowArtist(it.id)
|
||||
findNavController().navigate(
|
||||
GenreDetailFragmentDirections.actionShowArtist(it.id)
|
||||
)
|
||||
}
|
||||
},
|
||||
doOnLongClick = { data, view ->
|
||||
PopupMenu(requireContext(), view).setupArtistActions(
|
||||
data, requireContext(), playbackModel
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
|
@ -106,10 +114,4 @@ class GenreDetailFragment : DetailFragment() {
|
|||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
detailModel.updateNavigationStatus(false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.oxycblt.auxio.detail.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
|
||||
|
@ -9,7 +10,8 @@ import org.oxycblt.auxio.recycler.DiffCallback
|
|||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||
|
||||
class DetailAlbumAdapter(
|
||||
private val doOnClick: (data: Album) -> Unit
|
||||
private val doOnClick: (data: Album) -> Unit,
|
||||
private val doOnLongClick: (data: Album, view: View) -> Unit
|
||||
) : ListAdapter<Album, DetailAlbumAdapter.ViewHolder>(DiffCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
|
@ -25,7 +27,7 @@ class DetailAlbumAdapter(
|
|||
// Generic ViewHolder for a detail album
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemArtistAlbumBinding,
|
||||
) : BaseViewHolder<Album>(binding, doOnClick, null) {
|
||||
) : BaseViewHolder<Album>(binding, doOnClick, doOnLongClick) {
|
||||
|
||||
override fun onBind(data: Album) {
|
||||
binding.album = data
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.oxycblt.auxio.detail.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import org.oxycblt.auxio.databinding.ItemGenreArtistBinding
|
||||
|
@ -9,7 +10,8 @@ import org.oxycblt.auxio.recycler.DiffCallback
|
|||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||
|
||||
class DetailArtistAdapter(
|
||||
private val doOnClick: (data: Artist) -> Unit
|
||||
private val doOnClick: (data: Artist) -> Unit,
|
||||
private val doOnLongClick: (data: Artist, view: View) -> Unit
|
||||
) : ListAdapter<Artist, DetailArtistAdapter.ViewHolder>(DiffCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
|
@ -25,7 +27,7 @@ class DetailArtistAdapter(
|
|||
// Generic ViewHolder for an album
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemGenreArtistBinding
|
||||
) : BaseViewHolder<Artist>(binding, doOnClick, null) {
|
||||
) : BaseViewHolder<Artist>(binding, doOnClick, doOnLongClick) {
|
||||
|
||||
override fun onBind(data: Artist) {
|
||||
binding.artist = data
|
||||
|
|
|
@ -30,6 +30,9 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
|
|||
import org.oxycblt.auxio.ui.applyColor
|
||||
import org.oxycblt.auxio.ui.applyDivider
|
||||
import org.oxycblt.auxio.ui.resolveAttr
|
||||
import org.oxycblt.auxio.ui.setupAlbumActions
|
||||
import org.oxycblt.auxio.ui.setupArtistActions
|
||||
import org.oxycblt.auxio.ui.setupGenreActions
|
||||
import org.oxycblt.auxio.ui.setupSongActions
|
||||
|
||||
// A Fragment to show all the music in the Library.
|
||||
|
@ -47,21 +50,15 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
|
|||
|
||||
val musicStore = MusicStore.getInstance()
|
||||
|
||||
val libraryAdapter = LibraryAdapter(libraryModel.showMode.value!!) {
|
||||
navToItem(it)
|
||||
}
|
||||
val libraryAdapter = LibraryAdapter(
|
||||
libraryModel.showMode.value!!,
|
||||
doOnClick = { navToItem(it) },
|
||||
doOnLongClick = { data, view -> showActionsForItem(data, view) }
|
||||
)
|
||||
|
||||
val searchAdapter = SearchAdapter(
|
||||
{
|
||||
navToItem(it)
|
||||
},
|
||||
{ data, view ->
|
||||
if (data is Song) {
|
||||
PopupMenu(requireContext(), view).setupSongActions(
|
||||
data, requireContext(), playbackModel
|
||||
)
|
||||
}
|
||||
}
|
||||
doOnClick = { navToItem(it) },
|
||||
doOnLongClick = { data, view -> showActionsForItem(data, view) }
|
||||
)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
@ -175,6 +172,19 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
|
|||
return false
|
||||
}
|
||||
|
||||
private fun showActionsForItem(data: BaseModel, view: View) {
|
||||
val menu = PopupMenu(requireContext(), view)
|
||||
when (data) {
|
||||
is Song -> menu.setupSongActions(data, requireContext(), playbackModel)
|
||||
is Album -> menu.setupAlbumActions(data, requireContext(), playbackModel)
|
||||
is Artist -> menu.setupArtistActions(data, requireContext(), playbackModel)
|
||||
is Genre -> menu.setupGenreActions(data, requireContext(), playbackModel)
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navToItem(baseModel: BaseModel) {
|
||||
// If the item is a song [That was selected through search], then update the playback
|
||||
// to that song instead of doing any navigation
|
||||
|
|
|
@ -22,7 +22,7 @@ class LibraryViewModel : ViewModel() {
|
|||
val searchHasFocus: Boolean get() = mSearchHasFocus
|
||||
|
||||
// TODO: Move these to prefs when they're added
|
||||
private val mShowMode = MutableLiveData(ShowMode.SHOW_ARTISTS)
|
||||
private val mShowMode = MutableLiveData(ShowMode.SHOW_GENRES)
|
||||
val showMode: LiveData<ShowMode> get() = mShowMode
|
||||
|
||||
private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.oxycblt.auxio.library.adapters
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.music.Album
|
||||
|
@ -11,12 +12,11 @@ import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder
|
|||
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder
|
||||
|
||||
// A ListAdapter that can contain three different types of ViewHolders depending
|
||||
// the ShowMode given.
|
||||
// It cannot display multiple ViewHolders *at once* however. That's what SearchAdapter is for.
|
||||
// The primary RecyclerView adapter for the library. Displays genres, artists, and albums.
|
||||
class LibraryAdapter(
|
||||
private val showMode: ShowMode,
|
||||
private val doOnClick: (data: BaseModel) -> Unit
|
||||
private val doOnClick: (data: BaseModel) -> Unit,
|
||||
private val doOnLongClick: (data: BaseModel, view: View) -> Unit
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
private var data: List<BaseModel>
|
||||
|
@ -37,10 +37,11 @@ class LibraryAdapter(
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
// Return a different View Holder depending on the show type
|
||||
return when (showMode) {
|
||||
ShowMode.SHOW_GENRES -> GenreViewHolder.from(parent.context, doOnClick)
|
||||
ShowMode.SHOW_ARTISTS -> ArtistViewHolder.from(parent.context, doOnClick)
|
||||
ShowMode.SHOW_ALBUMS -> AlbumViewHolder.from(parent.context, doOnClick)
|
||||
else -> ArtistViewHolder.from(parent.context, doOnClick)
|
||||
ShowMode.SHOW_GENRES -> GenreViewHolder.from(parent.context, doOnClick, doOnLongClick)
|
||||
ShowMode.SHOW_ARTISTS -> ArtistViewHolder.from(parent.context, doOnClick, doOnLongClick)
|
||||
ShowMode.SHOW_ALBUMS -> AlbumViewHolder.from(parent.context, doOnClick, doOnLongClick)
|
||||
|
||||
else -> error("Bad ShowMode given.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,14 +34,22 @@ class SearchAdapter(
|
|||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
GenreViewHolder.ITEM_TYPE -> GenreViewHolder.from(parent.context, doOnClick)
|
||||
ArtistViewHolder.ITEM_TYPE -> ArtistViewHolder.from(parent.context, doOnClick)
|
||||
AlbumViewHolder.ITEM_TYPE -> AlbumViewHolder.from(parent.context, doOnClick)
|
||||
SongViewHolder.ITEM_TYPE -> SongViewHolder.from(
|
||||
parent.context,
|
||||
doOnClick,
|
||||
doOnLongClick
|
||||
GenreViewHolder.ITEM_TYPE -> GenreViewHolder.from(
|
||||
parent.context, doOnClick, doOnLongClick
|
||||
)
|
||||
|
||||
ArtistViewHolder.ITEM_TYPE -> ArtistViewHolder.from(
|
||||
parent.context, doOnClick, doOnLongClick
|
||||
)
|
||||
|
||||
AlbumViewHolder.ITEM_TYPE -> AlbumViewHolder.from(
|
||||
parent.context, doOnClick, doOnLongClick
|
||||
)
|
||||
|
||||
SongViewHolder.ITEM_TYPE -> SongViewHolder.from(
|
||||
parent.context, doOnClick, doOnLongClick
|
||||
)
|
||||
|
||||
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
||||
|
||||
else -> error("Someone messed with the ViewHolder item types.")
|
||||
|
|
|
@ -86,7 +86,6 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
// Make marquee scroll work
|
||||
// TODO: Add nav here as well
|
||||
binding.playbackSong.isSelected = true
|
||||
|
||||
binding.playbackSeekBar.setOnSeekBarChangeListener(this)
|
||||
|
||||
// --- VIEWMODEL SETUP --
|
||||
|
@ -127,6 +126,8 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
} else {
|
||||
binding.playbackShuffle.imageTintList = controlColor
|
||||
}
|
||||
|
||||
Log.d(this::class.simpleName, "Shuffle swap")
|
||||
}
|
||||
|
||||
playbackModel.loopMode.observe(viewLifecycleOwner) {
|
||||
|
|
|
@ -226,6 +226,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
playbackManager.addToUserQueue(song)
|
||||
}
|
||||
|
||||
fun addToUserQueue(songs: List<Song>) {
|
||||
playbackManager.addToUserQueue(songs)
|
||||
}
|
||||
|
||||
// --- STATUS FUNCTIONS ---
|
||||
|
||||
// Flip the playing status.
|
||||
|
|
|
@ -141,7 +141,8 @@ class PlaybackStateManager private constructor() {
|
|||
mQueue = song.album.songs
|
||||
}
|
||||
|
||||
else -> {}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
|
||||
mMode = mode
|
||||
|
@ -254,7 +255,6 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
|
||||
updatePlayback(mQueue[mIndex])
|
||||
|
||||
forceQueueUpdate()
|
||||
}
|
||||
}
|
||||
|
@ -304,6 +304,12 @@ class PlaybackStateManager private constructor() {
|
|||
forceUserQueueUpdate()
|
||||
}
|
||||
|
||||
fun addToUserQueue(songs: List<Song>) {
|
||||
mUserQueue.addAll(songs)
|
||||
|
||||
forceUserQueueUpdate()
|
||||
}
|
||||
|
||||
fun removeUserQueueItem(index: Int) {
|
||||
Log.d(this::class.simpleName, "Removing item ${mUserQueue[index].name}.")
|
||||
|
||||
|
@ -426,8 +432,6 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
Log.d(this::class.simpleName, packQueue().size.toString())
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val playbackState = packToPlaybackState()
|
||||
val queueItems = packQueue()
|
||||
|
@ -450,13 +454,16 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
val states: List<PlaybackState>
|
||||
val state: PlaybackState?
|
||||
val queueItems: List<QueueItem>
|
||||
val userQueueItems: List<QueueItem>
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val database = AuxioDatabase.getInstance(context)
|
||||
states = database.playbackStateDAO.getAll()
|
||||
queueItems = database.queueDAO.getAll()
|
||||
|
||||
state = database.playbackStateDAO.getRecent()
|
||||
queueItems = database.queueDAO.getQueue()
|
||||
userQueueItems = database.queueDAO.getUserQueue()
|
||||
|
||||
database.playbackStateDAO.clear()
|
||||
database.queueDAO.clear()
|
||||
|
@ -466,7 +473,7 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
Log.d(this::class.simpleName, "Load finished in ${loadTime}ms")
|
||||
|
||||
if (states.isEmpty()) {
|
||||
if (state == null) {
|
||||
Log.d(this::class.simpleName, "Nothing here. Not restoring.")
|
||||
|
||||
mIsRestored = true
|
||||
|
@ -474,13 +481,13 @@ class PlaybackStateManager private constructor() {
|
|||
return
|
||||
}
|
||||
|
||||
Log.d(this::class.simpleName, "Old state found, ${states[0]}")
|
||||
Log.d(this::class.simpleName, "Old state found, $state")
|
||||
|
||||
unpackFromPlaybackState(states[0])
|
||||
unpackFromPlaybackState(state)
|
||||
|
||||
Log.d(this::class.simpleName, "Found queue of size ${queueItems.size}")
|
||||
|
||||
unpackQueue(queueItems)
|
||||
unpackQueues(queueItems, userQueueItems)
|
||||
|
||||
mSong?.let {
|
||||
mIndex = mQueue.indexOf(mSong)
|
||||
|
@ -543,18 +550,22 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun unpackQueue(queueItems: List<QueueItem>) {
|
||||
private fun unpackQueues(queueItems: List<QueueItem>, userQueueItems: List<QueueItem>) {
|
||||
val musicStore = MusicStore.getInstance()
|
||||
|
||||
queueItems.forEach { item ->
|
||||
// Traverse albums and then album songs instead of just the songs, as its faster.
|
||||
musicStore.albums.find { it.id == item.albumId }?.songs?.find { it.id == item.songId }?.let {
|
||||
if (item.isUserQueue) {
|
||||
mUserQueue.add(it)
|
||||
} else {
|
||||
musicStore.albums.find { it.id == item.albumId }
|
||||
?.songs?.find { it.id == item.songId }?.let {
|
||||
mQueue.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userQueueItems.forEach { item ->
|
||||
musicStore.albums.find { it.id == item.albumId }
|
||||
?.songs?.find { it.id == item.songId }?.let {
|
||||
mUserQueue.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
forceQueueUpdate()
|
||||
|
|
|
@ -19,8 +19,9 @@ import org.oxycblt.auxio.music.Song
|
|||
|
||||
class GenreViewHolder private constructor(
|
||||
private val binding: ItemGenreBinding,
|
||||
doOnClick: (Genre) -> Unit
|
||||
) : BaseViewHolder<Genre>(binding, doOnClick, null) {
|
||||
doOnClick: (Genre) -> Unit,
|
||||
doOnLongClick: (data: Genre, view: View) -> Unit
|
||||
) : BaseViewHolder<Genre>(binding, doOnClick, doOnLongClick) {
|
||||
|
||||
override fun onBind(data: Genre) {
|
||||
binding.genre = data
|
||||
|
@ -30,10 +31,14 @@ class GenreViewHolder private constructor(
|
|||
companion object {
|
||||
const val ITEM_TYPE = 0xA010
|
||||
|
||||
fun from(context: Context, doOnClick: (Genre) -> Unit): GenreViewHolder {
|
||||
fun from(
|
||||
context: Context,
|
||||
doOnClick: (Genre) -> Unit,
|
||||
doOnLongClick: (data: Genre, view: View) -> Unit
|
||||
): GenreViewHolder {
|
||||
return GenreViewHolder(
|
||||
ItemGenreBinding.inflate(LayoutInflater.from(context)),
|
||||
doOnClick
|
||||
doOnClick, doOnLongClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +47,8 @@ class GenreViewHolder private constructor(
|
|||
class ArtistViewHolder private constructor(
|
||||
private val binding: ItemArtistBinding,
|
||||
doOnClick: (Artist) -> Unit,
|
||||
) : BaseViewHolder<Artist>(binding, doOnClick, null) {
|
||||
doOnLongClick: (data: Artist, view: View) -> Unit
|
||||
) : BaseViewHolder<Artist>(binding, doOnClick, doOnLongClick) {
|
||||
|
||||
override fun onBind(data: Artist) {
|
||||
binding.artist = data
|
||||
|
@ -52,10 +58,14 @@ class ArtistViewHolder private constructor(
|
|||
companion object {
|
||||
const val ITEM_TYPE = 0xA011
|
||||
|
||||
fun from(context: Context, doOnClick: (Artist) -> Unit): ArtistViewHolder {
|
||||
fun from(
|
||||
context: Context,
|
||||
doOnClick: (Artist) -> Unit,
|
||||
doOnLongClick: (data: Artist, view: View) -> Unit
|
||||
): ArtistViewHolder {
|
||||
return ArtistViewHolder(
|
||||
ItemArtistBinding.inflate(LayoutInflater.from(context)),
|
||||
doOnClick
|
||||
doOnClick, doOnLongClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +73,9 @@ class ArtistViewHolder private constructor(
|
|||
|
||||
class AlbumViewHolder private constructor(
|
||||
private val binding: ItemAlbumBinding,
|
||||
doOnClick: (data: Album) -> Unit
|
||||
) : BaseViewHolder<Album>(binding, doOnClick, null) {
|
||||
doOnClick: (data: Album) -> Unit,
|
||||
doOnLongClick: (data: Album, view: View) -> Unit
|
||||
) : BaseViewHolder<Album>(binding, doOnClick, doOnLongClick) {
|
||||
|
||||
override fun onBind(data: Album) {
|
||||
binding.album = data
|
||||
|
@ -74,10 +85,14 @@ class AlbumViewHolder private constructor(
|
|||
companion object {
|
||||
const val ITEM_TYPE = 0xA012
|
||||
|
||||
fun from(context: Context, doOnClick: (data: Album) -> Unit): AlbumViewHolder {
|
||||
fun from(
|
||||
context: Context,
|
||||
doOnClick: (data: Album) -> Unit,
|
||||
doOnLongClick: (data: Album, view: View) -> Unit
|
||||
): AlbumViewHolder {
|
||||
return AlbumViewHolder(
|
||||
ItemAlbumBinding.inflate(LayoutInflater.from(context)),
|
||||
doOnClick,
|
||||
doOnClick, doOnLongClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,10 +43,8 @@ class SongsFragment : Fragment() {
|
|||
binding.songRecycler.apply {
|
||||
adapter = SongAdapter(
|
||||
musicStore.songs,
|
||||
{
|
||||
playbackModel.playSong(it, PlaybackMode.ALL_SONGS)
|
||||
},
|
||||
{ data, view ->
|
||||
doOnClick = { playbackModel.playSong(it, PlaybackMode.ALL_SONGS) },
|
||||
doOnLongClick = { data, view ->
|
||||
PopupMenu(requireContext(), view).setupSongActions(
|
||||
data, requireContext(), playbackModel
|
||||
)
|
||||
|
|
|
@ -14,6 +14,9 @@ import androidx.recyclerview.widget.DividerItemDecoration
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.detail.DetailViewModel
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
|
@ -64,8 +67,8 @@ fun PopupMenu.setupSongActions(song: Song, context: Context, playbackModel: Play
|
|||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_queue_add -> {
|
||||
doUserQueueAdd(context, song, playbackModel)
|
||||
|
||||
playbackModel.addToUserQueue(song)
|
||||
context.getString(R.string.label_queue_added).createToast(context)
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -95,7 +98,8 @@ fun PopupMenu.setupAlbumSongActions(
|
|||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_queue_add -> {
|
||||
doUserQueueAdd(context, song, playbackModel)
|
||||
playbackModel.addToUserQueue(song)
|
||||
context.getString(R.string.label_queue_added).createToast(context)
|
||||
|
||||
true
|
||||
}
|
||||
|
@ -116,12 +120,95 @@ fun PopupMenu.setupAlbumSongActions(
|
|||
show()
|
||||
}
|
||||
|
||||
private fun doUserQueueAdd(context: Context, song: Song, playbackModel: PlaybackViewModel) {
|
||||
// If the song was already added to the user queue, then don't add it again.
|
||||
// This is just to prevent a bug with DiffCallback that creates strange
|
||||
// behavior when duplicate user queue items are added.
|
||||
// FIXME: Fix the duplicate item DiffCallback issue
|
||||
playbackModel.addToUserQueue(song)
|
||||
fun PopupMenu.setupAlbumActions(
|
||||
album: Album,
|
||||
context: Context,
|
||||
playbackModel: PlaybackViewModel
|
||||
) {
|
||||
inflate(R.menu.menu_album_actions)
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_queue_add -> {
|
||||
playbackModel.addToUserQueue(album.songs)
|
||||
context.getString(R.string.label_queue_added).createToast(context)
|
||||
|
||||
context.getString(R.string.label_queue_added).createToast(context)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_play -> {
|
||||
playbackModel.playAlbum(album, false)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_shuffle -> {
|
||||
playbackModel.playAlbum(album, true)
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
|
||||
fun PopupMenu.setupArtistActions(
|
||||
artist: Artist,
|
||||
context: Context,
|
||||
playbackModel: PlaybackViewModel
|
||||
) {
|
||||
inflate(R.menu.menu_detail)
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_queue_add -> {
|
||||
playbackModel.addToUserQueue(artist.songs)
|
||||
context.getString(R.string.label_queue_added).createToast(context)
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_play -> {
|
||||
playbackModel.playArtist(artist, false)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_shuffle -> {
|
||||
playbackModel.playArtist(artist, true)
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
|
||||
fun PopupMenu.setupGenreActions(
|
||||
genre: Genre,
|
||||
context: Context,
|
||||
playbackModel: PlaybackViewModel
|
||||
) {
|
||||
inflate(R.menu.menu_detail)
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_queue_add -> {
|
||||
playbackModel.addToUserQueue(genre.songs)
|
||||
context.getString(R.string.label_queue_added).createToast(context)
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_play -> {
|
||||
playbackModel.playGenre(genre, false)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_shuffle -> {
|
||||
playbackModel.playGenre(genre, true)
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
android:layout_height="?android:attr/actionBarSize"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="@dimen/elevation_normal"
|
||||
app:menu="@menu/menu_detail"
|
||||
app:popupTheme="@style/AppThemeOverlay.Popup"
|
||||
app:menu="@menu/menu_album_actions"
|
||||
app:titleTextAppearance="@style/TextAppearance.Toolbar.Header"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
app:title="@string/title_library_fragment" />
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
android:layout_height="?android:attr/actionBarSize"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="@dimen/elevation_normal"
|
||||
app:popupTheme="@style/AppThemeOverlay.Popup"
|
||||
app:menu="@menu/menu_detail"
|
||||
app:titleTextAppearance="@style/TextAppearance.Toolbar.Header"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
|
|
19
app/src/main/res/menu/menu_album_actions.xml
Normal file
19
app/src/main/res/menu/menu_album_actions.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_play"
|
||||
android:title="@string/label_play"
|
||||
android:icon="@drawable/ic_play"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_shuffle"
|
||||
android:icon="@drawable/ic_shuffle"
|
||||
android:title="@string/label_shuffle"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_queue_add"
|
||||
android:icon="@drawable/ic_queue_add"
|
||||
app:showAsAction="never"
|
||||
android:title="@string/label_queue_add" />
|
||||
</menu>
|
|
@ -5,5 +5,5 @@
|
|||
android:id="@+id/action_queue"
|
||||
android:icon="@drawable/ic_queue"
|
||||
android:title="@string/label_queue"
|
||||
app:showAsAction="always" />
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
|
@ -28,6 +28,7 @@
|
|||
<string name="label_play_artist">Play from artist</string>
|
||||
<string name="label_play_album">Play from album</string>
|
||||
<string name="label_go_artist">Go to artist</string>
|
||||
<string name="label_go_album">Go to album</string>
|
||||
<string name="label_queue">Queue</string>
|
||||
<string name="label_queue_add">Add to queue</string>
|
||||
<string name="label_queue_added">Added to queue</string>
|
||||
|
|
Loading…
Reference in a new issue