From c664d22a43c9ae732fa3e574b367a75165494819 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Fri, 6 Nov 2020 20:06:23 -0700 Subject: [PATCH] Fix bugs with Queue - Fix a bug where the queue would scroll up when the player moved to another song. - Fix bug where queue would show even with no songs. - Fix a crash from removing the last song of a queue. --- .../auxio/playback/NotificationUtils.kt | 3 ++- .../auxio/playback/PlaybackFragment.kt | 25 +++++++++++++++++++ .../oxycblt/auxio/playback/PlaybackService.kt | 3 +-- .../auxio/playback/PlaybackViewModel.kt | 4 +-- .../auxio/playback/queue/QueueFragment.kt | 16 +++++++++++- .../playback/state/PlaybackStateCallback.kt | 17 ------------- .../playback/state/PlaybackStateManager.kt | 18 ++++++++++--- .../main/res/drawable/ic_queue_inactive.xml | 11 ++++++++ 8 files changed, 70 insertions(+), 27 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateCallback.kt create mode 100644 app/src/main/res/drawable/ic_queue_inactive.xml diff --git a/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt b/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt index dd62119f0..6efa89ae4 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt @@ -145,7 +145,8 @@ private fun newAction(action: String, context: Context): NotificationCompat.Acti NotificationUtils.ACTION_SKIP_NEXT -> R.drawable.ic_skip_next NotificationUtils.ACTION_EXIT -> R.drawable.ic_exit - else -> R.drawable.ic_play + + else -> R.drawable.ic_error } return NotificationCompat.Action.Builder( 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 52c5debc2..f363c14c8 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -5,6 +5,7 @@ import android.graphics.drawable.AnimatedVectorDrawable import android.os.Bundle import android.util.Log import android.view.LayoutInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.SeekBar @@ -46,6 +47,17 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { requireContext(), R.drawable.ic_play_to_pause ) as AnimatedVectorDrawable + // Can't set the tint of a MenuItem below Android 8, so use icons instead. + val iconQueueActive = ContextCompat.getDrawable( + requireContext(), R.drawable.ic_queue + ) + + val iconQueueInactive = ContextCompat.getDrawable( + requireContext(), R.drawable.ic_queue_inactive + ) + + val queueMenuItem: MenuItem + // --- UI SETUP --- binding.lifecycleOwner = viewLifecycleOwner @@ -64,6 +76,8 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { true } + + queueMenuItem = menu.findItem(R.id.action_queue) } // Make marquee scroll work @@ -163,6 +177,17 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { } } + playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { + // Disable the option to open the queue if there's nothing in it. + if (it.isEmpty()) { + queueMenuItem.isEnabled = false + queueMenuItem.icon = iconQueueInactive + } else { + queueMenuItem.isEnabled = true + queueMenuItem.icon = iconQueueActive + } + } + Log.d(this::class.simpleName, "Fragment Created.") return binding.root diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt index 873c94ffa..efe2151e7 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt @@ -39,12 +39,11 @@ import org.oxycblt.auxio.music.coil.getBitmap import org.oxycblt.auxio.music.toURI import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.PlaybackMode -import org.oxycblt.auxio.playback.state.PlaybackStateCallback import org.oxycblt.auxio.playback.state.PlaybackStateManager // A Service that manages the single ExoPlayer instance and manages the system-side // aspects of playback. -class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback { +class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Callback { private val player: SimpleExoPlayer by lazy { SimpleExoPlayer.Builder(applicationContext).build() } 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 d6c0f293e..acf63faee 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -12,13 +12,12 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.toDuration import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.PlaybackMode -import org.oxycblt.auxio.playback.state.PlaybackStateCallback import org.oxycblt.auxio.playback.state.PlaybackStateManager // A ViewModel that acts as an intermediary between the UI and PlaybackStateManager // TODO: Implement User Queue // TODO: Implement Persistence through a Database -class PlaybackViewModel : ViewModel(), PlaybackStateCallback { +class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { // Playback private val mSong = MutableLiveData() val song: LiveData get() = mSong @@ -59,7 +58,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateCallback { it.slice((mIndex.value!! + 1) until it.size) } - // Service setup private val playbackManager = PlaybackStateManager.getInstance() init { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index 5c29ad60f..397362b26 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetDialogFragment import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentQueueBinding @@ -47,11 +48,24 @@ class QueueFragment : BottomSheetDialogFragment() { // --- VIEWMODEL SETUP --- playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { + if (it.isEmpty()) { + dismiss() + + return@observe + } + // If the first item is being moved, then scroll to the top position on completion // to prevent ListAdapter from scrolling uncontrollably. if (queueAdapter.currentList.isNotEmpty() && it[0].id != queueAdapter.currentList[0].id) { queueAdapter.submitList(it.toMutableList()) { - binding.queueRecycler.scrollToPosition(0) + // Make sure that the RecyclerView doesn't scroll to the top if the first item + // changed, but is not visible. + val firstItem = (binding.queueRecycler.layoutManager as LinearLayoutManager) + .findFirstVisibleItemPosition() + + if (firstItem == -1 || firstItem == 0) { + binding.queueRecycler.scrollToPosition(0) + } } } else { queueAdapter.submitList(it.toMutableList()) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateCallback.kt deleted file mode 100644 index e545be3ee..000000000 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateCallback.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.oxycblt.auxio.playback.state - -import org.oxycblt.auxio.music.Song - -interface PlaybackStateCallback { - fun onSongUpdate(song: Song?) {} - fun onPositionUpdate(position: Long) {} - fun onQueueUpdate(queue: MutableList) {} - fun onModeUpdate(mode: PlaybackMode) {} - fun onIndexUpdate(index: Int) {} - fun onPlayingUpdate(isPlaying: Boolean) {} - fun onShuffleUpdate(isShuffling: Boolean) {} - fun onLoopUpdate(mode: LoopMode) {} - - // Service callbacks - fun onSeekConfirm(position: Long) {} -} 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 b2a0c5a8a..6b745a786 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 @@ -77,13 +77,13 @@ class PlaybackStateManager private constructor() { // --- CALLBACKS --- - private val callbacks = mutableListOf() + private val callbacks = mutableListOf() - fun addCallback(callback: PlaybackStateCallback) { + fun addCallback(callback: Callback) { callbacks.add(callback) } - fun removeCallback(callback: PlaybackStateCallback) { + fun removeCallback(callback: Callback) { callbacks.remove(callback) } @@ -397,4 +397,16 @@ class PlaybackStateManager private constructor() { } } } + + interface Callback { + fun onSongUpdate(song: Song?) {} + fun onPositionUpdate(position: Long) {} + fun onQueueUpdate(queue: MutableList) {} + fun onModeUpdate(mode: PlaybackMode) {} + fun onIndexUpdate(index: Int) {} + fun onPlayingUpdate(isPlaying: Boolean) {} + fun onShuffleUpdate(isShuffling: Boolean) {} + fun onLoopUpdate(mode: LoopMode) {} + fun onSeekConfirm(position: Long) {} + } } diff --git a/app/src/main/res/drawable/ic_queue_inactive.xml b/app/src/main/res/drawable/ic_queue_inactive.xml new file mode 100644 index 000000000..fa59cf34f --- /dev/null +++ b/app/src/main/res/drawable/ic_queue_inactive.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file