playback: use gesture insets in playback layout

When using gesture navigation, swipe up events might conflict with the
slide up behavior of the playback layout. To fix this, inset the
playback bar based on the gesture insets instead of the system bar
insets.
This commit is contained in:
OxygenCobalt 2022-01-18 19:51:06 -07:00
parent e4f2906767
commit e5901fa9e2
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
11 changed files with 62 additions and 53 deletions

View file

@ -37,7 +37,7 @@ import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.isNight import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.replaceInsetsCompat import org.oxycblt.auxio.util.replaceInsetsCompat
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* The single [AppCompatActivity] for Auxio. * The single [AppCompatActivity] for Auxio.
@ -113,18 +113,25 @@ class MainActivity : AppCompatActivity() {
private fun applyEdgeToEdgeWindow(binding: ViewBinding) { private fun applyEdgeToEdgeWindow(binding: ViewBinding) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Do modern edge to edge, which happens to be around twice the size of the
// old way of doing things. Thanks android, very cool!
logD("Doing R+ edge-to-edge.") logD("Doing R+ edge-to-edge.")
window?.setDecorFitsSystemWindows(false) window?.setDecorFitsSystemWindows(false)
// "Should we automatically acquire the insets we need and return them
// whenever the user wants them?"
// "Nah, let's make the user define what insets they want instead through
// a barely-documented API that is not brought up in a single tutorial!"
// "Great idea!"
binding.root.setOnApplyWindowInsetsListener { _, insets -> binding.root.setOnApplyWindowInsetsListener { _, insets ->
WindowInsets.Builder() WindowInsets.Builder()
.setInsets( .setInsets(
WindowInsets.Type.systemBars(), WindowInsets.Type.systemBars(),
insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
) )
.setInsets(
WindowInsets.Type.systemGestures(),
insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemGestures())
)
.build() .build()
.applyLeftRightInsets(binding) .applyLeftRightInsets(binding)
} }
@ -145,7 +152,7 @@ class MainActivity : AppCompatActivity() {
} }
private fun WindowInsets.applyLeftRightInsets(binding: ViewBinding): WindowInsets { private fun WindowInsets.applyLeftRightInsets(binding: ViewBinding): WindowInsets {
val bars = systemBarsCompat val bars = systemBarInsetsCompat
binding.root.updatePadding( binding.root.updatePadding(
left = bars.left, left = bars.left,

View file

@ -23,7 +23,7 @@ import android.util.AttributeSet
import android.view.WindowInsets import android.view.WindowInsets
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* A container for a FloatingActionButton that enables edge-to-edge support. * A container for a FloatingActionButton that enables edge-to-edge support.
@ -43,7 +43,7 @@ class FloatingActionButtonContainer @JvmOverloads constructor(
} }
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
updatePadding(bottom = insets.systemBarsCompat.bottom) updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
return insets return insets
} }

View file

@ -44,7 +44,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.canScroll import org.oxycblt.auxio.util.canScroll
import org.oxycblt.auxio.util.resolveAttr import org.oxycblt.auxio.util.resolveAttr
import org.oxycblt.auxio.util.resolveDrawable import org.oxycblt.auxio.util.resolveDrawable
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
import kotlin.math.abs import kotlin.math.abs
/** /**
@ -315,7 +315,7 @@ class FastScrollRecyclerView @JvmOverloads constructor(
} }
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
val bars = insets.systemBarsCompat val bars = insets.systemBarInsetsCompat
updatePadding( updatePadding(
initialPadding.left, initialPadding.top, initialPadding.right, initialPadding.left, initialPadding.top, initialPadding.right,

View file

@ -19,6 +19,7 @@
package org.oxycblt.auxio.playback package org.oxycblt.auxio.playback
import android.content.Context import android.content.Context
import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.view.WindowInsets import android.view.WindowInsets
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
@ -31,7 +32,7 @@ import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.resolveAttr import org.oxycblt.auxio.util.resolveAttr
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* A view displaying the playback state in a compact manner. This is only meant to be used * A view displaying the playback state in a compact manner. This is only meant to be used
@ -48,17 +49,39 @@ class PlaybackBarView @JvmOverloads constructor(
id = R.id.playback_bar id = R.id.playback_bar
// Deliberately override the progress bar color [in a Lollipop-friendly way] so that // Deliberately override the progress bar color [in a Lollipop-friendly way] so that
// we use colorSecondary instead of colorSurfaceVariant. This is for two reasons: // we use colorSecondary instead of colorSurfaceVariant. This is because
// 1. colorSurfaceVariant is used with the assumption that the view that is using it // colorSurfaceVariant is used with the assumption that the view that is using it is
// is not elevated and is therefore not colored. This view is elevated. // not elevated and is therefore not colored. This view is elevated.
// 2. The way a solid color plays along with a ripple just doesnt look that good.
binding.playbackProgressBar.trackColor = MaterialColors.compositeARGBWithAlpha( binding.playbackProgressBar.trackColor = MaterialColors.compositeARGBWithAlpha(
R.attr.colorSecondary.resolveAttr(context), (255 * 0.2).toInt() R.attr.colorSecondary.resolveAttr(context), (255 * 0.2).toInt()
) )
} }
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
updatePadding(bottom = insets.systemBarsCompat.bottom) // Since we swipe up this view, we need to make sure it does not collide with
// any gesture events. So, apply the system gesture insets if present and then
// only default to the system bar insets when there are no other options.
val gesturePadding = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
insets.getInsets(WindowInsets.Type.systemGestures()).bottom
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
@Suppress("DEPRECATION")
insets.systemGestureInsets.bottom
}
else -> 0
}
updatePadding(
bottom =
if (gesturePadding != 0)
gesturePadding
else
insets.systemBarInsetsCompat.bottom
)
return insets return insets
} }

View file

@ -34,7 +34,7 @@ import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.ui.memberBinding import org.oxycblt.auxio.ui.memberBinding
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* A [Fragment] that displays more information about the song, along with more media controls. * A [Fragment] that displays more information about the song, along with more media controls.
@ -62,7 +62,7 @@ class PlaybackFragment : Fragment() {
binding.detailModel = detailModel binding.detailModel = detailModel
binding.root.setOnApplyWindowInsetsListener { _, insets -> binding.root.setOnApplyWindowInsetsListener { _, insets ->
val bars = insets.systemBarsCompat val bars = insets.systemBarInsetsCompat
binding.root.updatePadding( binding.root.updatePadding(
top = bars.top, top = bars.top,

View file

@ -3,10 +3,8 @@ package org.oxycblt.auxio.playback
import android.content.Context import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Insets
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.util.AttributeSet import android.util.AttributeSet
@ -26,9 +24,10 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.replaceInsetsCompat
import org.oxycblt.auxio.util.resolveAttr import org.oxycblt.auxio.util.resolveAttr
import org.oxycblt.auxio.util.resolveDrawable import org.oxycblt.auxio.util.resolveDrawable
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -169,7 +168,7 @@ class PlaybackLayout @JvmOverloads constructor(
} catch (e: Exception) { } catch (e: Exception) {
// Band-aid to stop the app crashing if we have to swap out the content view // Band-aid to stop the app crashing if we have to swap out the content view
// without warning (which we have to do sometimes because android is the worst // without warning (which we have to do sometimes because android is the worst
// thing ever // thing ever)
} }
} }
} }
@ -379,29 +378,11 @@ class PlaybackLayout @JvmOverloads constructor(
// We kind to do a reverse-measure to figure out how we should inset this view. // We kind to do a reverse-measure to figure out how we should inset this view.
// Find how much space is lost by the panel and then combine that with the // Find how much space is lost by the panel and then combine that with the
// bottom inset to find how much space we should apply. // bottom inset to find how much space we should apply.
val bars = insets.systemBarsCompat val bars = insets.systemBarInsetsCompat
val consumedByPanel = computePanelTopPosition(panelOffset) - measuredHeight val consumedByPanel = computePanelTopPosition(panelOffset) - measuredHeight
val adjustedBottomInset = (consumedByPanel + bars.bottom).coerceAtLeast(0) val adjustedBottomInset = (consumedByPanel + bars.bottom).coerceAtLeast(0)
return when { return insets.replaceInsetsCompat(bars.left, bars.top, bars.right, adjustedBottomInset)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
WindowInsets.Builder(insets)
.setInsets(
WindowInsets.Type.systemBars(),
Insets.of(bars.left, bars.top, bars.right, adjustedBottomInset)
)
.build()
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> {
@Suppress("DEPRECATION")
insets.replaceSystemWindowInsets(
bars.left, bars.top, bars.right, adjustedBottomInset
)
}
else -> insets
}
} }
override fun onSaveInstanceState(): Parcelable = Bundle().apply { override fun onSaveInstanceState(): Parcelable = Bundle().apply {
@ -536,8 +517,7 @@ class PlaybackLayout @JvmOverloads constructor(
/** /**
* Do the nice view animations that occur whenever we slide up the playback panel. * Do the nice view animations that occur whenever we slide up the playback panel.
* The way I transition is largely inspired by Android 12's notification panel, with the * The way I transition is largely inspired by Android 12's notification panel, with the
* compact view fading out completely before the panel view fades in. We don't fade out the * compact view fading out completely before the panel view fades in.
* content though so we have cohesion between the other sliding transitions.
*/ */
private fun updatePanelTransition() { private fun updatePanelTransition() {
val ratio = max(panelOffset, 0f) val ratio = max(panelOffset, 0f)
@ -546,7 +526,6 @@ class PlaybackLayout @JvmOverloads constructor(
val halfOutRatio = min(ratio / 0.5f, 1f) val halfOutRatio = min(ratio / 0.5f, 1f)
val halfInRatio = max(ratio - 0.5f, 0f) / 0.5f val halfInRatio = max(ratio - 0.5f, 0f) / 0.5f
// Optimize out drawing for this view completely
contentView.apply { contentView.apply {
alpha = outRatio alpha = outRatio
isInvisible = alpha == 0f isInvisible = alpha == 0f
@ -569,7 +548,7 @@ class PlaybackLayout @JvmOverloads constructor(
// [reminder that this view also applies the bottom window inset] and we can't // [reminder that this view also applies the bottom window inset] and we can't
// apply padding to the whole container layout since that would adjust the size // apply padding to the whole container layout since that would adjust the size
// of the playback view. This seems to be the least obtrusive way to do this. // of the playback view. This seems to be the least obtrusive way to do this.
lastInsets?.systemBarsCompat?.let { bars -> lastInsets?.systemBarInsetsCompat?.let { bars ->
val params = layoutParams as FrameLayout.LayoutParams val params = layoutParams as FrameLayout.LayoutParams
val oldTopMargin = params.topMargin val oldTopMargin = params.topMargin

View file

@ -38,7 +38,7 @@ import org.oxycblt.auxio.databinding.FragmentAboutBinding
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* A [BottomSheetDialogFragment] that shows Auxio's about screen. * A [BottomSheetDialogFragment] that shows Auxio's about screen.
@ -55,7 +55,7 @@ class AboutFragment : Fragment() {
val binding = FragmentAboutBinding.inflate(layoutInflater) val binding = FragmentAboutBinding.inflate(layoutInflater)
binding.aboutContents.setOnApplyWindowInsetsListener { _, insets -> binding.aboutContents.setOnApplyWindowInsetsListener { _, insets ->
binding.aboutContents.updatePadding(bottom = insets.systemBarsCompat.bottom) binding.aboutContents.updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
insets insets
} }

View file

@ -40,7 +40,7 @@ import org.oxycblt.auxio.settings.pref.IntListPreference
import org.oxycblt.auxio.util.isNight import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat]. * The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat].
@ -64,7 +64,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
clipToPadding = false clipToPadding = false
setOnApplyWindowInsetsListener { _, insets -> setOnApplyWindowInsetsListener { _, insets ->
updatePadding(bottom = insets.systemBarsCompat.bottom) updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
insets insets
} }

View file

@ -30,7 +30,7 @@ import androidx.core.content.res.ResourcesCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* An [AppBarLayout] that fixes a bug with the default implementation where the lifted state * An [AppBarLayout] that fixes a bug with the default implementation where the lifted state
@ -67,7 +67,7 @@ open class EdgeAppBarLayout @JvmOverloads constructor(
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
super.onApplyWindowInsets(insets) super.onApplyWindowInsets(insets)
updatePadding(top = insets.systemBarsCompat.top) updatePadding(top = insets.systemBarInsetsCompat.top)
return insets return insets
} }

View file

@ -23,7 +23,7 @@ import android.util.AttributeSet
import android.view.WindowInsets import android.view.WindowInsets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* A [RecyclerView] that automatically applies insets to itself. * A [RecyclerView] that automatically applies insets to itself.
@ -34,7 +34,7 @@ class EdgeRecyclerView @JvmOverloads constructor(
defStyleAttr: Int = -1 defStyleAttr: Int = -1
) : RecyclerView(context, attrs, defStyleAttr) { ) : RecyclerView(context, attrs, defStyleAttr) {
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
updatePadding(bottom = insets.systemBarsCompat.bottom) updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
return insets return insets
} }
} }

View file

@ -122,7 +122,7 @@ fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
* Resolve window insets in a version-aware manner. This can be used to apply padding to * Resolve window insets in a version-aware manner. This can be used to apply padding to
* a view that properly follows all the frustrating changes that were made between 8-11. * a view that properly follows all the frustrating changes that were made between 8-11.
*/ */
val WindowInsets.systemBarsCompat: Rect get() { val WindowInsets.systemBarInsetsCompat: Rect get() {
return when { return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
getInsets(WindowInsets.Type.systemBars()).run { getInsets(WindowInsets.Type.systemBars()).run {