diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt index fdbbbb0588..f389d7e057 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt @@ -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 } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt index bb8a392977..af8aaf8d50 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt @@ -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 diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt index 0eea741a4c..50bb9bc8da 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt @@ -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(R.id.bubble_bar_flyout_view) + assertThat(flyout.findViewById(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(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(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(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(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