queue: add ability to play songs [#92]

Add the ability to jump to arbitrary points in the queue.

This comes at the cost of the long-press option to move items, since
they simply cannot co-exist without visual issues.
This commit is contained in:
OxygenCobalt 2022-07-25 12:19:41 -06:00
parent eca385aea5
commit affa8c1c11
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 58 additions and 38 deletions

View file

@ -12,6 +12,7 @@ at the cost of longer loading times
- Added Last Added sorting
- Search now takes sort tags and file names in account [#184]
- Added option to clear playback state in settings
- Added ability to play songs from queue
#### What's Improved
- App now exposes an (immutable) queue.

View file

@ -72,6 +72,11 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
indexingNotification = IndexingNotification(this)
observingNotification = ObservingNotification(this)
wakeLock =
getSystemServiceSafe(PowerManager::class)
.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ".IndexerService")
settings = Settings(this, this)
indexerContentObserver = SystemContentObserver()
@ -81,11 +86,6 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
onStartIndexing()
}
wakeLock =
getSystemServiceSafe(PowerManager::class)
.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ".IndexerService")
logD("Service created.")
}

View file

@ -36,6 +36,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
/**
@ -195,6 +196,19 @@ class PlaybackViewModel(application: Application) :
playbackManager.prev()
}
/**
* Go to an item in the queue using it's recyclerview adapter index. No-ops if out of bounds.
*/
fun goto(adapterIndex: Int) {
val index = adapterIndex + (playbackManager.queue.size - _nextUp.value.size)
logD(adapterIndex)
logD(playbackManager.queue.size - _nextUp.value.size)
if (index in playbackManager.queue.indices) {
playbackManager.goto(index)
}
}
/** Remove a queue item using it's recyclerview adapter index. */
fun removeQueueDataItem(adapterIndex: Int) {
val index = adapterIndex + (playbackManager.queue.size - _nextUp.value.size)

View file

@ -19,32 +19,26 @@ package org.oxycblt.auxio.playback.queue
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.view.MotionEvent
import android.view.View
import androidx.core.view.isInvisible
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
import org.oxycblt.auxio.ui.recycler.MonoAdapter
import org.oxycblt.auxio.ui.recycler.SongViewHolder
import org.oxycblt.auxio.ui.recycler.SyncBackingData
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.disableDropShadowCompat
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.stateList
import org.oxycblt.auxio.util.textSafe
import org.oxycblt.auxio.ui.recycler.*
import org.oxycblt.auxio.util.*
class QueueAdapter(listener: QueueItemListener) :
class QueueAdapter(private val listener: QueueItemListener) :
MonoAdapter<Song, QueueItemListener, QueueSongViewHolder>(listener) {
override val data = SyncBackingData(this, QueueSongViewHolder.DIFFER)
override val creator = QueueSongViewHolder.CREATOR
}
interface QueueItemListener {
fun onClick(viewHolder: RecyclerView.ViewHolder)
fun onPickUp(viewHolder: RecyclerView.ViewHolder)
}
@ -57,13 +51,13 @@ private constructor(
val backgroundView: View
get() = binding.background
init {
binding.body.background =
MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
fillColor = (binding.body.background as ColorDrawable).color.stateList
}
val backgroundDrawable =
MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
fillColor = binding.context.getAttrColorSafe(R.attr.colorSurface).stateList
}
binding.root.disableDropShadowCompat()
init {
binding.body.background = backgroundDrawable
}
@SuppressLint("ClickableViewAccessibility")
@ -77,6 +71,8 @@ private constructor(
binding.songName.requestLayout()
binding.songInfo.requestLayout()
binding.body.setOnClickListener { listener.onClick(this) }
// Roll our own drag handlers as the default ones suck
binding.songDragHandle.setOnTouchListener { _, motionEvent ->
binding.songDragHandle.performClick()
@ -85,11 +81,6 @@ private constructor(
true
} else false
}
binding.body.setOnLongClickListener {
listener.onPickUp(this)
true
}
}
companion object {

View file

@ -22,7 +22,6 @@ import android.view.animation.AccelerateDecelerateInterpolator
import androidx.core.view.isInvisible
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.shape.MaterialShapeDrawable
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@ -87,7 +86,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
if (shouldLift && isCurrentlyActive && actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
logD("Lifting queue item")
val bg = holder.bodyView.background as MaterialShapeDrawable
val bg = holder.backgroundDrawable
val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_small)
holder.itemView
.animate()
@ -125,7 +124,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
if (holder.itemView.translationZ != 0f) {
logD("Dropping queue item")
val bg = holder.bodyView.background as MaterialShapeDrawable
val bg = holder.backgroundDrawable
holder.itemView
.animate()
.translationZ(0.0f)

View file

@ -61,6 +61,10 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
binding.queueRecycler.adapter = null
}
override fun onClick(viewHolder: RecyclerView.ViewHolder) {
playbackModel.goto(viewHolder.bindingAdapterPosition)
}
override fun onPickUp(viewHolder: RecyclerView.ViewHolder) {
touchHelper.startDrag(viewHolder)
}

View file

@ -200,9 +200,9 @@ class PlaybackStateManager private constructor() {
// Increment the index, if it cannot be incremented any further, then
// repeat and pause/resume playback depending on the setting
if (index < _queue.lastIndex) {
goto(index + 1, true)
gotoImpl(index + 1, true)
} else {
goto(0, repeatMode == RepeatMode.ALL)
gotoImpl(0, repeatMode == RepeatMode.ALL)
}
}
@ -214,11 +214,16 @@ class PlaybackStateManager private constructor() {
rewind()
isPlaying = true
} else {
goto(max(index - 1, 0), true)
gotoImpl(max(index - 1, 0), true)
}
}
private fun goto(idx: Int, play: Boolean) {
@Synchronized
fun goto(index: Int) {
gotoImpl(index, true)
}
private fun gotoImpl(idx: Int, play: Boolean) {
index = idx
seekTo(0)
notifyIndexMoved()

View file

@ -21,11 +21,15 @@
android:src="@drawable/ic_delete_24"
app:tint="?attr/colorSurface" />
<androidx.constraintlayout.widget.ConstraintLayout
<FrameLayout
android:id="@+id/body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface">
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground">
<org.oxycblt.auxio.image.StyledImageView
android:id="@+id/song_album_cover"
@ -73,5 +77,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/song_album_cover" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</FrameLayout>