From 4fb4120342cbed0e3ad2134107576b67c9aad807 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 8 Nov 2020 10:10:36 -0700 Subject: [PATCH] Add ability to play from user queue Add the ability to play from the user queue, also append some extra song actions. --- .../java/org/oxycblt/auxio/MainActivity.kt | 2 +- .../java/org/oxycblt/auxio/MainFragment.kt | 8 +- .../auxio/detail/AlbumDetailFragment.kt | 4 +- .../auxio/detail/ArtistDetailFragment.kt | 4 +- .../auxio/detail/GenreDetailFragment.kt | 4 +- .../oxycblt/auxio/library/LibraryFragment.kt | 13 ++- .../auxio/playback/PlaybackFragment.kt | 28 +----- .../auxio/playback/queue/QueueListFragment.kt | 14 ++- .../playback/state/PlaybackStateManager.kt | 27 +++-- .../org/oxycblt/auxio/songs/SongsFragment.kt | 24 +---- .../org/oxycblt/auxio/ui/InterfaceUtils.kt | 98 +++++++++++++++++++ .../oxycblt/auxio/{theme => ui}/ThemeUtils.kt | 58 +---------- app/src/main/res/menu/menu_song_actions.xml | 8 ++ app/src/main/res/values/strings.xml | 4 +- 14 files changed, 160 insertions(+), 136 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt rename app/src/main/java/org/oxycblt/auxio/{theme => ui}/ThemeUtils.kt (64%) diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index 70a931bd5..4ba9c0091 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -7,7 +7,7 @@ import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import org.oxycblt.auxio.playback.PlaybackService -import org.oxycblt.auxio.theme.accent +import org.oxycblt.auxio.ui.accent // FIXME: Fix bug where fast navigation will break the animations and // lead to nothing being displayed [Possibly Un-fixable] diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 62bc38314..5aaecde3a 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -17,10 +17,10 @@ import org.oxycblt.auxio.library.LibraryFragment import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.songs.SongsFragment -import org.oxycblt.auxio.theme.accent -import org.oxycblt.auxio.theme.getInactiveAlpha -import org.oxycblt.auxio.theme.getTransparentAccent -import org.oxycblt.auxio.theme.toColor +import org.oxycblt.auxio.ui.accent +import org.oxycblt.auxio.ui.getInactiveAlpha +import org.oxycblt.auxio.ui.getTransparentAccent +import org.oxycblt.auxio.ui.toColor class MainFragment : Fragment() { private val playbackModel: PlaybackViewModel by activityViewModels() 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 a6b433793..13df0c5e6 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -15,8 +15,8 @@ import org.oxycblt.auxio.detail.adapters.DetailSongAdapter import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.state.PlaybackMode -import org.oxycblt.auxio.theme.applyDivider -import org.oxycblt.auxio.theme.disable +import org.oxycblt.auxio.ui.applyDivider +import org.oxycblt.auxio.ui.disable class AlbumDetailFragment : Fragment() { 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 b9f9473eb..4739d5846 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -14,8 +14,8 @@ import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.theme.applyDivider -import org.oxycblt.auxio.theme.disable +import org.oxycblt.auxio.ui.applyDivider +import org.oxycblt.auxio.ui.disable class ArtistDetailFragment : Fragment() { private val args: ArtistDetailFragmentArgs by navArgs() 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 b2d0539f4..501bde764 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -14,8 +14,8 @@ import org.oxycblt.auxio.databinding.FragmentGenreDetailBinding import org.oxycblt.auxio.detail.adapters.DetailArtistAdapter import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.theme.applyDivider -import org.oxycblt.auxio.theme.disable +import org.oxycblt.auxio.ui.applyDivider +import org.oxycblt.auxio.ui.disable class GenreDetailFragment : Fragment() { 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 e6b3f4188..f39438efc 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt @@ -27,9 +27,10 @@ import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.state.PlaybackMode -import org.oxycblt.auxio.theme.applyColor -import org.oxycblt.auxio.theme.applyDivider -import org.oxycblt.auxio.theme.resolveAttr +import org.oxycblt.auxio.ui.applyColor +import org.oxycblt.auxio.ui.applyDivider +import org.oxycblt.auxio.ui.resolveAttr +import org.oxycblt.auxio.ui.showActionMenuForSong // A Fragment to show all the music in the Library. class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { @@ -54,7 +55,11 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { { navToItem(it) }, - { data, view -> } + { data, view -> + if (data is Song) { + showActionMenuForSong(requireContext(), data, view, playbackModel) + } + } ) // --- UI SETUP --- 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 05d0250a6..babb2ef93 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -16,10 +16,8 @@ import androidx.navigation.fragment.findNavController import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentPlaybackBinding import org.oxycblt.auxio.playback.state.LoopMode -import org.oxycblt.auxio.theme.accent -import org.oxycblt.auxio.theme.disable -import org.oxycblt.auxio.theme.enable -import org.oxycblt.auxio.theme.toColor +import org.oxycblt.auxio.ui.accent +import org.oxycblt.auxio.ui.toColor // TODO: Add a swipe-to-next-track function using a ViewPager class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { @@ -99,20 +97,6 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { } } - playbackModel.index.observe(viewLifecycleOwner) { - if (it > 0) { - binding.playbackSkipPrev.enable(requireContext()) - } else { - binding.playbackSkipPrev.disable(requireContext()) - } - - if (it < playbackModel.queue.value!!.lastIndex) { - binding.playbackSkipNext.enable(requireContext()) - } else { - binding.playbackSkipNext.disable(requireContext()) - } - } - playbackModel.isPlaying.observe(viewLifecycleOwner) { if (it) { // Animate the playing status and switch the button to the accent color @@ -186,14 +170,6 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { queueMenuItem.isEnabled = true queueMenuItem.icon = iconQueueActive } - - // If someone edits the queue to make it have no songs left, then disable the - // skip next button. - if (playbackModel.index.value!! == it.size) { - binding.playbackSkipNext.disable(requireContext()) - } else { - binding.playbackSkipNext.enable(requireContext()) - } } playbackModel.userQueue.observe(viewLifecycleOwner) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueListFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueListFragment.kt index 36a7adb71..3d05b0cd7 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueListFragment.kt @@ -14,7 +14,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentQueueListBinding import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.state.PlaybackMode -import org.oxycblt.auxio.theme.applyDivider +import org.oxycblt.auxio.ui.applyDivider class QueueListFragment(private val type: Int) : Fragment() { private val playbackModel: PlaybackViewModel by activityViewModels() @@ -53,13 +53,11 @@ class QueueListFragment(private val type: Int) : Fragment() { } playbackModel.mode.observe(viewLifecycleOwner) { - if (it == PlaybackMode.ALL_SONGS) { - binding.queueHeader.setText(R.string.label_next_songs) - } else { - binding.queueHeader.text = getString( - R.string.format_next_from, playbackModel.parent.value!!.name - ) - } + binding.queueHeader.text = getString( + R.string.format_next_from, + if (it == PlaybackMode.ALL_SONGS) getString(R.string.title_all_songs) + else playbackModel.parent.value!!.name + ) } playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { 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 ab05e3389..c08e44d11 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 @@ -216,19 +216,26 @@ class PlaybackStateManager private constructor() { fun next() { resetLoopMode() - if (mIndex < mQueue.lastIndex) { - mIndex = mIndex.inc() + if (mUserQueue.isNotEmpty()) { + updatePlayback(mUserQueue[0]) + mUserQueue.removeAt(0) + + forceUserQueueUpdate() } else { - // TODO: Implement option to make the playlist loop instead of stop - mQueue = mutableListOf() - mSong = null + if (mIndex < mQueue.lastIndex) { + mIndex = mIndex.inc() + } else { + // TODO: Implement option to make the playlist loop instead of stop + mQueue = mutableListOf() + mSong = null - return + return + } + + updatePlayback(mQueue[mIndex]) + + forceQueueUpdate() } - - updatePlayback(mQueue[mIndex]) - - forceQueueUpdate() } fun prev() { 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 ec574788b..2d034b0fa 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -5,16 +5,15 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.PopupMenu import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentSongsBinding import org.oxycblt.auxio.music.MusicStore -import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.state.PlaybackMode -import org.oxycblt.auxio.theme.applyDivider +import org.oxycblt.auxio.ui.applyDivider +import org.oxycblt.auxio.ui.showActionMenuForSong class SongsFragment : Fragment() { private val playbackModel: PlaybackViewModel by activityViewModels() @@ -47,7 +46,7 @@ class SongsFragment : Fragment() { playbackModel.playSong(it, PlaybackMode.ALL_SONGS) }, { data, view -> - showActionMenuForSong(data, view) + showActionMenuForSong(requireContext(), data, view, playbackModel) } ) applyDivider() @@ -58,21 +57,4 @@ class SongsFragment : Fragment() { return binding.root } - - private fun showActionMenuForSong(song: Song, view: View) { - // TODO: Replace this with something nicer - PopupMenu(requireContext(), view).apply { - inflate(R.menu.menu_song_actions) - setOnMenuItemClickListener { - if (it.itemId == R.id.action_queue_add) { - playbackModel.addToUserQueue(song) - - return@setOnMenuItemClickListener true - } - - false - } - show() - } - } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt new file mode 100644 index 000000000..cdd85e4ad --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt @@ -0,0 +1,98 @@ +package org.oxycblt.auxio.ui + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.drawable.ColorDrawable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.view.MenuItem +import android.view.View +import android.widget.ImageButton +import android.widget.PopupMenu +import android.widget.Toast +import androidx.annotation.ColorInt +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.R +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.playback.state.PlaybackMode + +// Functions for managing UI elements [Not Colors] + +fun showActionMenuForSong( + context: Context, + song: Song, + view: View, + playbackModel: PlaybackViewModel +) { + // TODO: Replace this with a BottomSheet dialog? + PopupMenu(context, view).apply { + inflate(R.menu.menu_song_actions) + setOnMenuItemClickListener { + return@setOnMenuItemClickListener when (it.itemId) { + R.id.action_queue_add -> { + playbackModel.addToUserQueue(song) + + Toast.makeText( + context, + context.getString(R.string.label_queue_added), + Toast.LENGTH_SHORT + ).show() + + true + } + + R.id.action_play_artist -> { + playbackModel.playSong(song, PlaybackMode.IN_ARTIST) + + true + } + + R.id.action_play_album -> { + playbackModel.playSong(song, PlaybackMode.IN_ALBUM) + + true + } + + else -> false + } + } + show() + } +} + +// Apply a color to a Menu Item +fun MenuItem.applyColor(@ColorInt color: Int) { + SpannableString(title).apply { + setSpan(ForegroundColorSpan(color), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE) + title = this + } +} + +// Disable an ImageButton +fun ImageButton.disable(context: Context) { + if (isEnabled) { + imageTintList = ColorStateList.valueOf( + R.color.inactive_color.toColor(context) + ) + + isEnabled = false + } +} + +// Apply a custom vertical divider +fun RecyclerView.applyDivider() { + val div = DividerItemDecoration( + context, + DividerItemDecoration.VERTICAL + ) + + div.setDrawable( + ColorDrawable( + R.color.divider_color.toColor(context) + ) + ) + + addItemDecoration(div) +} diff --git a/app/src/main/java/org/oxycblt/auxio/theme/ThemeUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/ThemeUtils.kt similarity index 64% rename from app/src/main/java/org/oxycblt/auxio/theme/ThemeUtils.kt rename to app/src/main/java/org/oxycblt/auxio/ui/ThemeUtils.kt index 75166634f..2a9095709 100644 --- a/app/src/main/java/org/oxycblt/auxio/theme/ThemeUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ThemeUtils.kt @@ -1,22 +1,16 @@ -package org.oxycblt.auxio.theme +package org.oxycblt.auxio.ui import android.content.Context -import android.content.res.ColorStateList -import android.graphics.drawable.ColorDrawable -import android.text.SpannableString -import android.text.style.ForegroundColorSpan import android.util.TypedValue -import android.view.MenuItem -import android.widget.ImageButton import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R +// Functions for managing colors/accents/whatever. + // Pairs of the base accent and its theme private val ACCENTS = listOf( Pair(R.color.red, R.style.Theme_Red), // 0 @@ -85,49 +79,3 @@ fun resolveAttr(context: Context, @AttrRes attr: Int): Int { return color.toColor(context) } - -// Apply a color to a Menu Item -fun MenuItem.applyColor(@ColorInt color: Int) { - SpannableString(title).apply { - setSpan(ForegroundColorSpan(color), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE) - title = this - } -} - -// Disable an ImageButton -fun ImageButton.disable(context: Context) { - if (isEnabled) { - imageTintList = ColorStateList.valueOf( - R.color.inactive_color.toColor(context) - ) - - isEnabled = false - } -} - -// Enable an ImageButton -fun ImageButton.enable(context: Context) { - if (!isEnabled) { - imageTintList = ColorStateList.valueOf( - R.color.control_color.toColor(context) - ) - - isEnabled = true - } -} - -// Apply a custom vertical divider -fun RecyclerView.applyDivider() { - val div = DividerItemDecoration( - context, - DividerItemDecoration.VERTICAL - ) - - div.setDrawable( - ColorDrawable( - R.color.divider_color.toColor(context) - ) - ) - - addItemDecoration(div) -} diff --git a/app/src/main/res/menu/menu_song_actions.xml b/app/src/main/res/menu/menu_song_actions.xml index 125309274..018077ecd 100644 --- a/app/src/main/res/menu/menu_song_actions.xml +++ b/app/src/main/res/menu/menu_song_actions.xml @@ -4,4 +4,12 @@ android:id="@+id/action_queue_add" android:title="@string/label_queue_add" android:icon="@drawable/ic_user_queue" /> + + \ 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 ed599206e..7a56ef79c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,10 +25,12 @@ Z-A Shuffle Play + Play from artist + Play from album Queue Add to queue + Added to queue Next in Queue - Next from: All Songs Nothing here. Music Playback The music playback service for Auxio.