diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 7ea262fb6..040951725 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -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 --- diff --git a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt b/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt index caa285ba5..0f7a5a9ec 100644 --- a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt +++ b/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt @@ -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? } diff --git a/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt b/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt index f46c64caa..64167eb2d 100644 --- a/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt +++ b/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt @@ -18,8 +18,11 @@ interface QueueDAO { } } - @Query("SELECT * FROM queue_table") - fun getAll(): List + @Query("SELECT * FROM queue_table WHERE is_user_queue == 1") + fun getUserQueue(): List + + @Query("SELECT * FROM queue_table WHERE is_user_queue == 0") + fun getQueue(): List @Query("DELETE FROM queue_table") fun clear() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 42790078f..939822d9b 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index d15000328..bdeaad6cd 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -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) - } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt index 87ba8147f..50cd1f0fa 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -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() { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index a3d3c5760..399c0e97f 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -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) - } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/adapters/DetailAlbumAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/adapters/DetailAlbumAdapter.kt index a080be676..e7f74e199 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/adapters/DetailAlbumAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/adapters/DetailAlbumAdapter.kt @@ -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(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(binding, doOnClick, null) { + ) : BaseViewHolder(binding, doOnClick, doOnLongClick) { override fun onBind(data: Album) { binding.album = data diff --git a/app/src/main/java/org/oxycblt/auxio/detail/adapters/DetailArtistAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/adapters/DetailArtistAdapter.kt index 161e92486..f3395436a 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/adapters/DetailArtistAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/adapters/DetailArtistAdapter.kt @@ -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(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(binding, doOnClick, null) { + ) : BaseViewHolder(binding, doOnClick, doOnLongClick) { override fun onBind(data: Artist) { binding.artist = data diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt index 362532668..bc432e3b7 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt index c4e28aade..b83289c2d 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt @@ -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 get() = mShowMode private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN) diff --git a/app/src/main/java/org/oxycblt/auxio/library/adapters/LibraryAdapter.kt b/app/src/main/java/org/oxycblt/auxio/library/adapters/LibraryAdapter.kt index f250fcbc0..cdd04c749 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/adapters/LibraryAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/adapters/LibraryAdapter.kt @@ -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() { private var data: List @@ -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.") } } diff --git a/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt b/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt index 7975cca74..110ba2f4d 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt @@ -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.") diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index 10c2ec1aa..f58b04155 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -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) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 1c6ec7c99..53aa9d48c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -226,6 +226,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { playbackManager.addToUserQueue(song) } + fun addToUserQueue(songs: List) { + playbackManager.addToUserQueue(songs) + } + // --- STATUS FUNCTIONS --- // Flip the playing status. diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 99c238d28..2dbc18f41 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -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) { + 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 + val state: PlaybackState? val queueItems: List + val userQueueItems: List 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) { + private fun unpackQueues(queueItems: List, userQueueItems: List) { 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() diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt index 56e106e64..4bf7c8905 100644 --- a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt @@ -19,8 +19,9 @@ import org.oxycblt.auxio.music.Song class GenreViewHolder private constructor( private val binding: ItemGenreBinding, - doOnClick: (Genre) -> Unit -) : BaseViewHolder(binding, doOnClick, null) { + doOnClick: (Genre) -> Unit, + doOnLongClick: (data: Genre, view: View) -> Unit +) : BaseViewHolder(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(binding, doOnClick, null) { + doOnLongClick: (data: Artist, view: View) -> Unit +) : BaseViewHolder(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(binding, doOnClick, null) { + doOnClick: (data: Album) -> Unit, + doOnLongClick: (data: Album, view: View) -> Unit +) : BaseViewHolder(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 ) } } diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt index 4967973cc..4fb2edc35 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -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 ) diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt index 597616ae3..4b069676b 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt @@ -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() } diff --git a/app/src/main/res/layout/fragment_album_detail.xml b/app/src/main/res/layout/fragment_album_detail.xml index 0c5a1b523..2b02eebd7 100644 --- a/app/src/main/res/layout/fragment_album_detail.xml +++ b/app/src/main/res/layout/fragment_album_detail.xml @@ -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" /> diff --git a/app/src/main/res/layout/fragment_artist_detail.xml b/app/src/main/res/layout/fragment_artist_detail.xml index a81c20c77..d5aa5c035 100644 --- a/app/src/main/res/layout/fragment_artist_detail.xml +++ b/app/src/main/res/layout/fragment_artist_detail.xml @@ -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" diff --git a/app/src/main/res/menu/menu_album_actions.xml b/app/src/main/res/menu/menu_album_actions.xml new file mode 100644 index 000000000..1d5afafd1 --- /dev/null +++ b/app/src/main/res/menu/menu_album_actions.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_playback.xml b/app/src/main/res/menu/menu_playback.xml index de755be64..eab41c715 100644 --- a/app/src/main/res/menu/menu_playback.xml +++ b/app/src/main/res/menu/menu_playback.xml @@ -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" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 81685a0f8..9b09a1edc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,6 +28,7 @@ Play from artist Play from album Go to artist + Go to album Queue Add to queue Added to queue