Allow updating the flyout message

Add functions to allow updating the contents of the flyout while it
is either expanding or fully expanded.

Flag: com.android.wm.shell.enable_bubble_bar
Bug: 277815200
Test: atest BubbleBarFlyoutControllerTest
Change-Id: Iea3fc7df792e02605df6b44c1da39e267f6d9d43
This commit is contained in:
Liran Binyamin
2024-10-28 09:51:34 -04:00
parent aa3f772622
commit 35e6157303
3 changed files with 118 additions and 22 deletions
@@ -72,32 +72,63 @@ constructor(
lp.marginEnd = horizontalMargin
container.addView(flyout, lp)
this.flyout = flyout
flyout.showFromCollapsed(message) { showFlyout(AnimationType.COLLAPSE, onEnd) }
}
private fun showFlyout(animationType: AnimationType, endAction: () -> Unit) {
val flyout = this.flyout ?: return
val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
animator.addUpdateListener { _ ->
flyout.updateExpansionProgress(animator.animatedValue as Float)
when (animationType) {
AnimationType.FADE ->
animator.addUpdateListener { _ -> flyout.alpha = animator.animatedValue as Float }
AnimationType.COLLAPSE ->
animator.addUpdateListener { _ ->
flyout.updateExpansionProgress(animator.animatedValue as Float)
}
}
animator.addListener(
onStart = {
val flyoutTop = flyout.top + flyout.translationY
// If the top position of the flyout is negative, then it's bleeding over the
// top boundary of its parent view
if (flyoutTop < 0) callbacks.extendTopBoundary(space = -flyoutTop.toInt())
},
onStart = { extendTopBoundary() },
onEnd = {
onEnd()
endAction()
flyout.setOnClickListener { callbacks.flyoutClicked() }
},
)
flyout.showFromCollapsed(message) { animator.start() }
this.flyout = flyout
animator.start()
}
fun updateFlyoutFullyExpanded(message: BubbleBarFlyoutMessage, onEnd: () -> Unit) {
val flyout = flyout ?: return
hideFlyout(AnimationType.FADE) {
flyout.updateData(message) { showFlyout(AnimationType.FADE, onEnd) }
}
}
fun updateFlyoutWhileExpanding(message: BubbleBarFlyoutMessage) {
val flyout = flyout ?: return
flyout.updateData(message) { extendTopBoundary() }
}
private fun extendTopBoundary() {
val flyout = flyout ?: return
val flyoutTop = flyout.top + flyout.translationY
// If the top position of the flyout is negative, then it's bleeding over the
// top boundary of its parent view
if (flyoutTop < 0) callbacks.extendTopBoundary(space = -flyoutTop.toInt())
}
fun cancelFlyout(endAction: () -> Unit) {
hideFlyout(AnimationType.FADE, endAction)
hideFlyout(AnimationType.FADE) {
cleanupFlyoutView()
endAction()
}
}
fun collapseFlyout(endAction: () -> Unit) {
hideFlyout(AnimationType.COLLAPSE, endAction)
hideFlyout(AnimationType.COLLAPSE) {
cleanupFlyoutView()
endAction()
}
}
private fun hideFlyout(animationType: AnimationType, endAction: () -> Unit) {
@@ -112,15 +143,15 @@ constructor(
flyout.updateExpansionProgress(animator.animatedValue as Float)
}
}
animator.addListener(
onStart = { flyout.setOnClickListener(null) },
onEnd = {
container.removeView(flyout)
this@BubbleBarFlyoutController.flyout = null
callbacks.resetTopBoundary()
endAction()
},
)
animator.addListener(onStart = { flyout.setOnClickListener(null) }, onEnd = { endAction() })
animator.start()
}
private fun cleanupFlyoutView() {
container.removeView(flyout)
this@BubbleBarFlyoutController.flyout = null
callbacks.resetTopBoundary()
}
fun hasFlyout() = flyout != null
}
@@ -198,6 +198,8 @@ class BubbleBarFlyoutView(
} else {
-positioner.distanceToCollapsedPosition.x
}
// TODO: b/277815200 - before collapsing, recalculate translationToCollapsedPosition because
// the collapsed position may have changed
val tyToCollapsedPosition =
positioner.distanceToCollapsedPosition.y + triangleHeight - triangleOverlap
translationToCollapsedPosition = PointF(txToCollapsedPosition, tyToCollapsedPosition)
@@ -217,6 +219,12 @@ class BubbleBarFlyoutView(
scheduler.runAfterLayout(expandAnimation)
}
/** Updates the content of the flyout and schedules [afterLayout] to run after a layout pass. */
fun updateData(flyoutMessage: BubbleBarFlyoutMessage, afterLayout: () -> Unit) {
setData(flyoutMessage)
scheduler.runAfterLayout(afterLayout)
}
private fun setData(flyoutMessage: BubbleBarFlyoutMessage) {
if (flyoutMessage.icon != null) {
icon.visibility = VISIBLE
@@ -115,11 +115,13 @@ class BubbleBarFlyoutControllerTest {
fun hideFlyout_removedFromContainer() {
InstrumentationRegistry.getInstrumentation().runOnMainSync {
flyoutController.setUpAndShowFlyout(flyoutMessage) {}
assertThat(flyoutController.hasFlyout()).isTrue()
assertThat(flyoutContainer.childCount).isEqualTo(1)
flyoutController.collapseFlyout {}
animatorTestRule.advanceTimeBy(300)
}
assertThat(flyoutContainer.childCount).isEqualTo(0)
assertThat(flyoutController.hasFlyout()).isFalse()
}
@Test
@@ -189,6 +191,61 @@ class BubbleBarFlyoutControllerTest {
assertThat(flyoutCallbacks.flyoutClicked).isTrue()
}
@Test
fun updateFlyoutWhileExpanding() {
InstrumentationRegistry.getInstrumentation().runOnMainSync {
flyoutController.setUpAndShowFlyout(flyoutMessage) {}
assertThat(flyoutController.hasFlyout()).isTrue()
val flyout = flyoutContainer.findViewById<View>(R.id.bubble_bar_flyout_view)
assertThat(flyout.findViewById<TextView>(R.id.bubble_flyout_text).text)
.isEqualTo("message")
// advance the animation about halfway
animatorTestRule.advanceTimeBy(100)
}
assertThat(flyoutController.hasFlyout()).isTrue()
val newFlyoutMessage = flyoutMessage.copy(message = "new message")
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val flyout = flyoutContainer.findViewById<View>(R.id.bubble_bar_flyout_view)
// set negative translation to verify that the top boundary extends as a result of
// updating while expanding
flyout.translationY = -50f
flyoutController.updateFlyoutWhileExpanding(newFlyoutMessage)
assertThat(flyout.findViewById<TextView>(R.id.bubble_flyout_text).text)
.isEqualTo("new message")
}
assertThat(flyoutCallbacks.topBoundaryExtendedSpace).isEqualTo(50)
}
@Test
fun updateFlyoutFullyExpanded() {
InstrumentationRegistry.getInstrumentation().runOnMainSync {
flyoutController.setUpAndShowFlyout(flyoutMessage) {}
animatorTestRule.advanceTimeBy(300)
}
assertThat(flyoutController.hasFlyout()).isTrue()
val newFlyoutMessage = flyoutMessage.copy(message = "new message")
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val flyout = flyoutContainer.findViewById<View>(R.id.bubble_bar_flyout_view)
// set negative translation to verify that the top boundary extends as a result of
// updating while fully expanded
flyout.translationY = -50f
flyoutController.updateFlyoutFullyExpanded(newFlyoutMessage) {}
// advance the timer so that the fade out animation plays
animatorTestRule.advanceTimeBy(250)
assertThat(flyout.alpha).isEqualTo(0)
assertThat(flyout.findViewById<TextView>(R.id.bubble_flyout_text).text)
.isEqualTo("new message")
// advance the timer so that the fade in animation plays
animatorTestRule.advanceTimeBy(250)
assertThat(flyout.alpha).isEqualTo(1)
}
assertThat(flyoutCallbacks.topBoundaryExtendedSpace).isEqualTo(50)
}
class FakeFlyoutCallbacks : FlyoutCallbacks {
var topBoundaryExtendedSpace = 0