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:
parent
eca385aea5
commit
affa8c1c11
8 changed files with 58 additions and 38 deletions
|
@ -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.
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue