playback: fix more queue issues
Fix more issues with the queue, such as landscape edge-to-edge not working correctly and drag scroll events not resulting in the appbar lifting.
This commit is contained in:
parent
0268700fb4
commit
37a8cdccb6
7 changed files with 44 additions and 58 deletions
|
@ -32,9 +32,14 @@ import kotlin.math.sign
|
||||||
/**
|
/**
|
||||||
* The Drag callback used by the queue recyclerview. Delivers updates to [PlaybackViewModel]
|
* The Drag callback used by the queue recyclerview. Delivers updates to [PlaybackViewModel]
|
||||||
* and [QueueAdapter] simultaneously.
|
* and [QueueAdapter] simultaneously.
|
||||||
|
* @param onItemScroll Callback for when an item begins to scroll off-screen. Argument passed
|
||||||
|
* is the dY value.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouchHelper.Callback() {
|
class QueueDragCallback(
|
||||||
|
private val playbackModel: PlaybackViewModel,
|
||||||
|
private val onItemScroll: (Int) -> Unit
|
||||||
|
) : ItemTouchHelper.Callback() {
|
||||||
private lateinit var queueAdapter: QueueAdapter
|
private lateinit var queueAdapter: QueueAdapter
|
||||||
private var shouldLift = true
|
private var shouldLift = true
|
||||||
|
|
||||||
|
@ -57,7 +62,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
|
||||||
totalSize: Int,
|
totalSize: Int,
|
||||||
msSinceStartScroll: Long
|
msSinceStartScroll: Long
|
||||||
): Int {
|
): Int {
|
||||||
// Fix to make QueueFragment scroll when an item is scrolled out of bounds.
|
// Fix to make QueueFragment scroll slower when an item is scrolled out of bounds.
|
||||||
// Adapted from NewPipe: https://github.com/TeamNewPipe/NewPipe
|
// Adapted from NewPipe: https://github.com/TeamNewPipe/NewPipe
|
||||||
val standardSpeed = super.interpolateOutOfBoundsScroll(
|
val standardSpeed = super.interpolateOutOfBoundsScroll(
|
||||||
recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll
|
recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll
|
||||||
|
@ -71,7 +76,13 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return clampedAbsVelocity * sign(viewSizeOutOfBounds.toDouble()).toInt()
|
val result = clampedAbsVelocity * sign(viewSizeOutOfBounds.toDouble()).toInt()
|
||||||
|
|
||||||
|
recyclerView.post {
|
||||||
|
onItemScroll(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMove(
|
override fun onMove(
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
@ -38,10 +39,8 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.util.isEdgeOn
|
import org.oxycblt.auxio.util.isEdgeOn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [Fragment] that contains both the user queue and the next queue, with the abielity to
|
* A [Fragment] that contains both the user queue and the next queue, with the ability to
|
||||||
* edit them as well.
|
* edit them as well.
|
||||||
* TODO: Edge can be improved here by turning off the landscape checks and simply padding the
|
|
||||||
* root view on the irregular landscape mode [I think]
|
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class QueueFragment : Fragment() {
|
class QueueFragment : Fragment() {
|
||||||
|
@ -54,7 +53,17 @@ class QueueFragment : Fragment() {
|
||||||
): View {
|
): View {
|
||||||
val binding = FragmentQueueBinding.inflate(inflater)
|
val binding = FragmentQueueBinding.inflate(inflater)
|
||||||
|
|
||||||
val callback = QueueDragCallback(playbackModel)
|
val callback = QueueDragCallback(playbackModel) { dY ->
|
||||||
|
// By default, CoordinatorLayout is not aware of scroll events originating from
|
||||||
|
// when an item is scrolled off-screen, so we manually add a scroll event ourselves.
|
||||||
|
(binding.queueAppbar.layoutParams as CoordinatorLayout.LayoutParams).behavior
|
||||||
|
?.onNestedPreScroll(
|
||||||
|
binding.queueCoordinator, binding.queueAppbar,
|
||||||
|
binding.queueRecycler, 0, dY,
|
||||||
|
IntArray(2), 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val helper = ItemTouchHelper(callback)
|
val helper = ItemTouchHelper(callback)
|
||||||
val queueAdapter = QueueAdapter(helper, playbackModel)
|
val queueAdapter = QueueAdapter(helper, playbackModel)
|
||||||
var lastShuffle = playbackModel.isShuffling.value
|
var lastShuffle = playbackModel.isShuffling.value
|
||||||
|
@ -101,6 +110,7 @@ class QueueFragment : Fragment() {
|
||||||
if (isShuffling != lastShuffle) {
|
if (isShuffling != lastShuffle) {
|
||||||
lastShuffle = isShuffling
|
lastShuffle = isShuffling
|
||||||
|
|
||||||
|
binding.queueRecycler.scrollBy(0, 100)
|
||||||
binding.queueRecycler.scrollToPosition(0)
|
binding.queueRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,15 +122,21 @@ class QueueFragment : Fragment() {
|
||||||
if (isEdgeOn()) {
|
if (isEdgeOn()) {
|
||||||
// Account for the side navigation bar if required.
|
// Account for the side navigation bar if required.
|
||||||
binding.root.setOnApplyWindowInsetsListener { v, insets ->
|
binding.root.setOnApplyWindowInsetsListener { v, insets ->
|
||||||
val right = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
insets.getInsets(WindowInsets.Type.systemBars()).right
|
val bars = insets.getInsets(WindowInsets.Type.systemBars())
|
||||||
|
|
||||||
|
v.updatePadding(
|
||||||
|
left = bars.left,
|
||||||
|
right = bars.right
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
insets.systemWindowInsetRight
|
v.updatePadding(
|
||||||
|
left = insets.systemWindowInsetLeft,
|
||||||
|
right = insets.systemWindowInsetRight
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
v.updatePadding(right = right)
|
|
||||||
|
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.oxycblt.auxio.util.queryAll
|
||||||
/**
|
/**
|
||||||
* A SQLite database for managing the persistent playback state and queue.
|
* A SQLite database for managing the persistent playback state and queue.
|
||||||
* Yes. I know Room exists. But that would needlessly bloat my app and has crippling bugs.
|
* Yes. I know Room exists. But that would needlessly bloat my app and has crippling bugs.
|
||||||
|
* TODO: Improve the boundary between this and [PlaybackStateManager]
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class PlaybackStateDatabase(context: Context) :
|
class PlaybackStateDatabase(context: Context) :
|
||||||
|
|
|
@ -18,14 +18,11 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.util
|
package org.oxycblt.auxio.util
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.WindowManager
|
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
|
@ -142,43 +139,3 @@ fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
|
||||||
* Check if edge-to-edge is on. Really a glorified version check.
|
* Check if edge-to-edge is on. Really a glorified version check.
|
||||||
*/
|
*/
|
||||||
fun isEdgeOn(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
|
fun isEdgeOn(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if we are in the "Irregular" landscape mode (e.g landscape, but nav bar is on the sides)
|
|
||||||
* Used to disable most of edge-to-edge if that's the case, as I cant get it to work on this mode.
|
|
||||||
* @return True if we are in the irregular landscape mode, false if not.
|
|
||||||
*/
|
|
||||||
fun Activity.isIrregularLandscape(): Boolean {
|
|
||||||
return isLandscape() && !isSystemBarOnBottom(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the system bars are on the bottom.
|
|
||||||
* @return If the system bars are on the bottom, false if no.
|
|
||||||
*/
|
|
||||||
private fun isSystemBarOnBottom(activity: Activity): Boolean {
|
|
||||||
val metrics = DisplayMetrics()
|
|
||||||
|
|
||||||
var width: Int
|
|
||||||
var height: Int
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
activity.windowManager.currentWindowMetrics.bounds.also {
|
|
||||||
width = it.width()
|
|
||||||
height = it.height()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
activity.getSystemServiceSafe(WindowManager::class).apply {
|
|
||||||
defaultDisplay.getMetrics(metrics)
|
|
||||||
|
|
||||||
width = metrics.widthPixels
|
|
||||||
height = metrics.heightPixels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val config = activity.resources.configuration
|
|
||||||
val canMove = (width != height && config.smallestScreenWidthDp < 600)
|
|
||||||
|
|
||||||
return (!canMove || width < height)
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
android:name="org.oxycblt.auxio.playback.CompactPlaybackFragment"
|
android:name="org.oxycblt.auxio.playback.CompactPlaybackFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:elevation="8dp"
|
android:elevation="@dimen/elevation_normal"
|
||||||
android:background="?attr/colorSurface"
|
android:background="?attr/colorSurface"
|
||||||
tools:layout="@layout/fragment_compact_playback" />
|
tools:layout="@layout/fragment_compact_playback" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
tools:context=".playback.queue.QueueFragment">
|
tools:context=".playback.queue.QueueFragment">
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:id="@+id/queue_coordinator"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
|
@ -14,8 +15,8 @@
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/queue_appbar"
|
android:id="@+id/queue_appbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
app:liftOnScroll="true">
|
app:liftOnScroll="true">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
@ -27,7 +28,6 @@
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/queue_recycler"
|
android:id="@+id/queue_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
<!-- Misc -->
|
<!-- Misc -->
|
||||||
<dimen name="elevation_small">2dp</dimen>
|
<dimen name="elevation_small">2dp</dimen>
|
||||||
<dimen name="elevation_normal">4dp</dimen>
|
<dimen name="elevation_normal">4dp</dimen>
|
||||||
|
<dimen name="elevation_large">8dp</dimen>
|
||||||
|
|
||||||
<dimen name="offset_thumb">4dp</dimen>
|
<dimen name="offset_thumb">4dp</dimen>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue