Merge "Handle swipe down on collapsed bubble bar" into main

This commit is contained in:
Ats Jenk
2024-09-27 19:44:56 +00:00
committed by Android (Google) Code Review
2 changed files with 168 additions and 36 deletions
@@ -21,10 +21,15 @@ import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.R
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.anim.SpringAnimationBuilder
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarThresholdUtils
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.COLLAPSED
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.EXPANDED
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.STASHED
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.UNKNOWN
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
import com.android.launcher3.touch.OverScroll
@@ -34,8 +39,8 @@ class BubbleBarSwipeController {
private val context: Context
private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null
private var bubbleBarViewController: BubbleBarViewController? = null
private var bubbleStashController: BubbleStashController? = null
private lateinit var bubbleBarViewController: BubbleBarViewController
private lateinit var bubbleStashController: BubbleStashController
private var springAnimation: ValueAnimator? = null
private val animatedSwipeTranslation = AnimatedFloat(this::onSwipeUpdate)
@@ -43,6 +48,7 @@ class BubbleBarSwipeController {
private val unstashThreshold: Int
private val expandThreshold: Int
private val maxOverscroll: Int
private val stashThreshold: Int
private var swipeState: SwipeState = SwipeState()
@@ -54,6 +60,7 @@ class BubbleBarSwipeController {
unstashThreshold = dimensionProvider.unstashThreshold
expandThreshold = dimensionProvider.expandThreshold
maxOverscroll = dimensionProvider.maxOverscroll
stashThreshold = dimensionProvider.stashThreshold
}
fun init(bubbleControllers: BubbleControllers) {
@@ -66,23 +73,21 @@ class BubbleBarSwipeController {
/** Start tracking a new swipe gesture */
fun start() {
if (springAnimation != null) reset()
val stashed = bubbleStashController?.isStashed ?: false
val barVisible = bubbleStashController?.isBubbleBarVisible() ?: false
val expanded = bubbleBarViewController?.isExpanded ?: false
swipeState =
SwipeState(
stashedOnStart = stashed,
collapsedOnStart = !stashed && barVisible && !expanded,
expandedOnStart = expanded,
)
val startState =
when {
bubbleStashController.isStashed -> STASHED
bubbleBarViewController.isExpanded -> EXPANDED
bubbleStashController.isBubbleBarVisible() -> COLLAPSED
else -> UNKNOWN
}
swipeState = SwipeState(startState = startState)
}
/** Update swipe distance to [dy] */
fun swipeTo(dy: Float) {
// Only handle swipe up and stashed or collapsed bar
if (dy > 0 || swipeState.expandedOnStart) return
if (!canHandleSwipe(dy)) {
return
}
animatedSwipeTranslation.updateValue(dy)
val prevState = swipeState
@@ -90,38 +95,63 @@ class BubbleBarSwipeController {
val passedUnstashThreshold = isUnstash(dy) || prevState.passedUnstashThreshold
// Expand happens at the end of the gesture, always keep the current value
val passedExpandThreshold = isExpand(dy)
// Stash happens at the end of the gesture, always keep the current value
val passedStashThreshold = isStash(dy)
if (
passedUnstashThreshold != prevState.passedUnstashThreshold ||
passedExpandThreshold != prevState.passedExpandThreshold
passedExpandThreshold != prevState.passedExpandThreshold ||
passedStashThreshold != prevState.passedStashThreshold
) {
swipeState =
swipeState.copy(
passedUnstashThreshold = passedUnstashThreshold,
passedExpandThreshold = passedExpandThreshold,
passedStashThreshold = passedStashThreshold,
)
}
if (
swipeState.stashedOnStart &&
swipeState.startState == STASHED &&
swipeState.passedUnstashThreshold &&
!prevState.passedUnstashThreshold
) {
bubbleStashController?.showBubbleBar(expandBubbles = false)
bubbleStashController.showBubbleBar(expandBubbles = false)
}
}
/** Finish tracking swipe gesture. Animate views back to resting state */
fun finish() {
if (swipeState.passedExpandThreshold) {
bubbleStashController?.showBubbleBar(expandBubbles = true)
when {
swipeState.passedExpandThreshold &&
swipeState.startState in setOf(STASHED, COLLAPSED) -> {
bubbleStashController.showBubbleBar(expandBubbles = true)
}
swipeState.passedStashThreshold && swipeState.startState == COLLAPSED -> {
bubbleStashController.stashBubbleBar()
}
}
if (animatedSwipeTranslation.value == 0f) {
reset()
} else {
springToRest()
}
springToRest()
}
/** Returns `true` if we are tracking a swipe gesture */
fun isSwipeGesture(): Boolean {
return swipeState.passedUnstashThreshold || swipeState.passedExpandThreshold
return swipeState.passedUnstashThreshold ||
swipeState.passedExpandThreshold ||
swipeState.passedStashThreshold
}
private fun canHandleSwipe(dy: Float): Boolean {
return when (swipeState.startState) {
STASHED -> dy < 0 // stashed bar only handles swipe up
COLLAPSED -> true // collapsed bar can be swiped in either direction
UNKNOWN,
EXPANDED -> false // expanded bar can't be swiped
}
}
private fun isUnstash(dy: Float): Boolean {
@@ -132,6 +162,10 @@ class BubbleBarSwipeController {
return dy < -expandThreshold
}
private fun isStash(dy: Float): Boolean {
return dy > stashThreshold
}
private fun reset() {
springAnimation?.let {
if (it.isRunning) {
@@ -147,7 +181,7 @@ class BubbleBarSwipeController {
private fun onSwipeUpdate(value: Float) {
val dampedSwipe = -OverScroll.dampedScroll(-value, maxOverscroll).toFloat()
bubbleStashedHandleViewController?.setTranslationYForSwipe(dampedSwipe)
bubbleBarViewController?.setTranslationYForSwipe(dampedSwipe)
bubbleBarViewController.setTranslationYForSwipe(dampedSwipe)
}
private fun springToRest() {
@@ -163,19 +197,26 @@ class BubbleBarSwipeController {
}
internal data class SwipeState(
val stashedOnStart: Boolean = false,
val collapsedOnStart: Boolean = false,
val expandedOnStart: Boolean = false,
val startState: StartState = UNKNOWN,
val passedUnstashThreshold: Boolean = false,
val passedExpandThreshold: Boolean = false,
val passedStashThreshold: Boolean = false,
)
internal enum class StartState {
UNKNOWN,
STASHED,
COLLAPSED,
EXPANDED,
}
/** Allows overriding the dimension provider for testing */
@VisibleForTesting
interface DimensionProvider {
val unstashThreshold: Int
val expandThreshold: Int
val maxOverscroll: Int
val stashThreshold: Int
}
private class DefaultDimensionProvider(taskbarActivityContext: TaskbarActivityContext) :
@@ -183,6 +224,7 @@ class BubbleBarSwipeController {
override val unstashThreshold: Int
override val expandThreshold: Int
override val maxOverscroll: Int
override val stashThreshold: Int
init {
val resources = taskbarActivityContext.resources
@@ -198,6 +240,7 @@ class BubbleBarSwipeController {
taskbarActivityContext.deviceProfile,
)
maxOverscroll = taskbarActivityContext.deviceProfile.heightPx - unstashThreshold
stashThreshold = resources.getDimensionPixelSize(R.dimen.taskbar_to_nav_threshold)
}
}
}
@@ -25,6 +25,7 @@ import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
import com.android.launcher3.touch.OverScroll
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlin.math.abs
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -46,11 +47,13 @@ class BubbleBarSwipeControllerTest {
const val UNSTASH_THRESHOLD = 100
const val EXPAND_THRESHOLD = 200
const val MAX_OVERSCROLL = 300
const val STASH_THRESHOLD = 50
const val UP_BELOW_UNSTASH = -UNSTASH_THRESHOLD + 10f
const val UP_ABOVE_UNSTASH = -UNSTASH_THRESHOLD - 10f
const val UP_ABOVE_EXPAND = -EXPAND_THRESHOLD - 10f
const val DOWN_BELOW_UNSTASH = UNSTASH_THRESHOLD + 10f
const val DOWN_UNDER_STASH = STASH_THRESHOLD - 10f
const val DOWN_OVER_STASH = STASH_THRESHOLD + 10f
}
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -82,6 +85,9 @@ class BubbleBarSwipeControllerTest {
override val maxOverscroll: Int
get() = MAX_OVERSCROLL
override val stashThreshold: Int
get() = STASH_THRESHOLD
}
bubbleBarSwipeController = BubbleBarSwipeController(context, dimensionProvider)
@@ -102,8 +108,12 @@ class BubbleBarSwipeControllerTest {
bubbleBarSwipeController.init(bubbleControllers)
}
// region Test that views have damped translation on swipe
private fun testViewsHaveDampedTranslationOnSwipe(swipe: Float) {
val dampedTranslation = -OverScroll.dampedScroll(-swipe, MAX_OVERSCROLL).toFloat()
val isUp = swipe < 0
val damped = OverScroll.dampedScroll(abs(swipe), MAX_OVERSCROLL).toFloat()
val dampedTranslation = if (isUp) -damped else damped
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(swipe)
@@ -142,6 +152,22 @@ class BubbleBarSwipeControllerTest {
testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_EXPAND)
}
@Test
fun swipeDown_collapsedBar_belowStashThreshold_viewsHaveDampedTranslation() {
setUpCollapsedBar()
testViewsHaveDampedTranslationOnSwipe(DOWN_UNDER_STASH)
}
@Test
fun swipeDown_collapsedBar_overStashThreshold_viewsHaveDampedTranslation() {
setUpCollapsedBar()
testViewsHaveDampedTranslationOnSwipe(DOWN_OVER_STASH)
}
// endregion
// region Test that translation on views is reset on finish
private fun testViewsTranslationResetOnFinish(swipe: Float) {
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
@@ -194,6 +220,16 @@ class BubbleBarSwipeControllerTest {
testViewsTranslationResetOnFinish(UP_ABOVE_EXPAND)
}
@Test
fun swipeDown_collapsedBar_aboveStashThreshold_animateTranslationToZeroOnFinish() {
setUpCollapsedBar()
testViewsTranslationResetOnFinish(DOWN_OVER_STASH)
}
// endregion
// region Test swipe interactions on stashed bar
@Test
fun swipeUp_stashedBar_belowUnstashThreshold_doesNotShowBar() {
setUpStashedBar()
@@ -282,12 +318,28 @@ class BubbleBarSwipeControllerTest {
}
@Test
fun swipeUp_expandedBar_swipeIgnored() {
fun swipeDown_stashedBar_swipeIgnored() {
setUpStashedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
}
verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
verify(bubbleStashController, never()).showBubbleBar(any())
}
// endregion
// region Test swipe interactions on expanded bar
@Test
fun swipe_expandedBar_swipeIgnored() {
setUpExpandedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
bubbleBarSwipeController.swipeTo(DOWN_BELOW_UNSTASH)
bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
bubbleBarSwipeController.finish()
}
verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
@@ -295,18 +347,55 @@ class BubbleBarSwipeControllerTest {
verify(bubbleStashController, never()).showBubbleBar(any())
}
// endregion
// region Test swipe interactions on collapsed bar
@Test
fun swipeDown_stashedBar_swipeIgnored() {
setUpStashedBar()
fun swipeDown_collapsedBar_underStashThreshold_doesNotHideBar() {
setUpCollapsedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(DOWN_BELOW_UNSTASH)
bubbleBarSwipeController.swipeTo(DOWN_UNDER_STASH)
bubbleBarSwipeController.finish()
}
verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
verify(bubbleStashController, never()).showBubbleBar(any())
verify(bubbleStashController, never()).stashBubbleBar()
}
@Test
fun swipeDown_collapsedBar_overStashThreshold_doesNotHideBarBeforeFinish() {
setUpCollapsedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
}
verify(bubbleStashController, never()).stashBubbleBar()
getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
verify(bubbleStashController).stashBubbleBar()
}
@Test
fun swipeDown_collapsedBar_underStashThreshold_isSwipeGestureFalse() {
setUpCollapsedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(DOWN_UNDER_STASH)
}
assertThat(bubbleBarSwipeController.isSwipeGesture()).isFalse()
}
@Test
fun swipeDown_collapsedBar_overStashThreshold_isSwipeGestureTrue() {
setUpCollapsedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
}
assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
}
// endregion
private fun setUpStashedBar() {
whenever(bubbleStashController.isStashed).thenReturn(true)
whenever(bubbleStashController.isBubbleBarVisible()).thenReturn(false)