Loading quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +21 −0 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.launcher3.taskbar.TaskbarControllers; import com.android.launcher3.taskbar.TaskbarInsetsController; import com.android.launcher3.taskbar.TaskbarStashController; import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator; import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController; import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner; import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController; import com.android.launcher3.util.MultiPropertyFactory; Loading Loading @@ -117,6 +118,8 @@ public class BubbleBarViewController { public boolean mOverflowAdded; private BubbleBarViewAnimator mBubbleBarViewAnimator; private final FrameLayout mBubbleBarContainer; private BubbleBarFlyoutController mBubbleBarFlyoutController; private final TimeSource mTimeSource = System::currentTimeMillis; Loading @@ -127,6 +130,7 @@ public class BubbleBarViewController { FrameLayout bubbleBarContainer) { mActivity = activity; mBarView = barView; mBubbleBarContainer = bubbleBarContainer; mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity); mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */); mIconSize = activity.getResources().getDimensionPixelSize( Loading @@ -141,6 +145,8 @@ public class BubbleBarViewController { mBubbleDragController = bubbleControllers.bubbleDragController; mTaskbarStashController = controllers.taskbarStashController; mTaskbarInsetsController = controllers.taskbarInsetsController; mBubbleBarFlyoutController = new BubbleBarFlyoutController( mBubbleBarContainer, createFlyoutPositioner(), createFlyoutTopBoundaryListener()); mBubbleBarViewAnimator = new BubbleBarViewAnimator( mBarView, mBubbleStashController, mBubbleBarController::showExpandedView); mTaskbarViewPropertiesProvider = taskbarViewPropertiesProvider; Loading Loading @@ -266,6 +272,21 @@ public class BubbleBarViewController { }; } private BubbleBarFlyoutController.TopBoundaryListener createFlyoutTopBoundaryListener() { return new BubbleBarFlyoutController.TopBoundaryListener() { @Override public void extendTopBoundary(int space) { int defaultSize = mActivity.getDefaultTaskbarWindowSize(); mActivity.setTaskbarWindowSize(defaultSize + space); } @Override public void resetTopBoundary() { mActivity.setTaskbarWindowSize(mActivity.getDefaultTaskbarWindowSize()); } }; } private void onBubbleClicked(BubbleView bubbleView) { bubbleView.markSeen(); BubbleBarItem bubble = bubbleView.getBubble(); Loading quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt +42 −6 Original line number Diff line number Diff line Loading @@ -21,20 +21,30 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.core.animation.ValueAnimator import com.android.launcher3.R import com.android.systemui.util.doOnEnd import com.android.systemui.util.doOnStart /** Creates and manages the visibility of the [BubbleBarFlyoutView]. */ class BubbleBarFlyoutController( class BubbleBarFlyoutController @JvmOverloads constructor( private val container: FrameLayout, private val positioner: BubbleBarFlyoutPositioner, private val topBoundaryListener: TopBoundaryListener, private val flyoutScheduler: FlyoutScheduler = HandlerScheduler(container), ) { private companion object { const val EXPAND_COLLAPSE_ANIMATION_DURATION_MS = 250L } private var flyout: BubbleBarFlyoutView? = null private val horizontalMargin = container.context.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin) fun setUpFlyout(message: BubbleBarFlyoutMessage) { flyout?.let(container::removeView) val flyout = BubbleBarFlyoutView(container.context, positioner) val flyout = BubbleBarFlyoutView(container.context, positioner, flyoutScheduler) flyout.translationY = positioner.targetTy Loading @@ -48,17 +58,43 @@ class BubbleBarFlyoutController( lp.marginEnd = horizontalMargin container.addView(flyout, lp) val animator = ValueAnimator.ofFloat(0f, 1f) val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS) animator.addUpdateListener { _ -> flyout.updateExpansionProgress(animator.animatedValue as Float) } animator.doOnStart { 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) topBoundaryListener.extendTopBoundary(space = -flyoutTop.toInt()) } flyout.showFromCollapsed(message) { animator.start() } this.flyout = flyout } fun hideFlyout() { fun hideFlyout(endAction: () -> Unit) { val flyout = this.flyout ?: return val animator = ValueAnimator.ofFloat(1f, 0f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS) animator.addUpdateListener { _ -> flyout.updateExpansionProgress(animator.animatedValue as Float) } animator.doOnEnd { container.removeView(flyout) this.flyout = null this@BubbleBarFlyoutController.flyout = null topBoundaryListener.resetTopBoundary() endAction() } animator.start() } /** Notifies when the top boundary of the flyout view changes. */ interface TopBoundaryListener { /** Requests to extend the top boundary of the parent to fully include the flyout. */ fun extendTopBoundary(space: Int) /** Resets the top boundary of the parent. */ fun resetTopBoundary() } } quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt +7 −4 Original line number Diff line number Diff line Loading @@ -36,14 +36,18 @@ import com.android.launcher3.R import com.android.launcher3.popup.RoundedArrowDrawable /** The flyout view used to notify the user of a new bubble notification. */ class BubbleBarFlyoutView(context: Context, private val positioner: BubbleBarFlyoutPositioner) : ConstraintLayout(context) { class BubbleBarFlyoutView( context: Context, private val positioner: BubbleBarFlyoutPositioner, scheduler: FlyoutScheduler? = null, ) : ConstraintLayout(context) { private companion object { // the minimum progress of the expansion animation before the content starts fading in. const val MIN_EXPANSION_PROGRESS_FOR_CONTENT_ALPHA = 0.75f } private val scheduler: FlyoutScheduler = scheduler ?: HandlerScheduler(this) private val title: TextView by lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_title) } Loading Loading @@ -197,11 +201,10 @@ class BubbleBarFlyoutView(context: Context, private val positioner: BubbleBarFly // post the request to start the expand animation to the looper so the view can measure // itself post(expandAnimation) scheduler.runAfterLayout(expandAnimation) } private fun setData(flyoutMessage: BubbleBarFlyoutMessage) { // the avatar is only displayed in group chat messages if (flyoutMessage.icon != null) { icon.visibility = VISIBLE icon.setImageDrawable(flyoutMessage.icon) Loading quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/FlyoutScheduler.kt 0 → 100644 +32 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.taskbar.bubbles.flyout import android.view.View /** Interface for scheduling jobs by flyout. */ fun interface FlyoutScheduler { /** Runs the given [block] after layout. */ fun runAfterLayout(block: () -> Unit) } /** A [FlyoutScheduler] that uses a Handler to schedule jobs. */ class HandlerScheduler(val view: View) : FlyoutScheduler { override fun runAfterLayout(block: () -> Unit) { view.post(block) } } quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt +104 −24 Original line number Diff line number Diff line Loading @@ -22,12 +22,15 @@ import android.graphics.PointF import android.view.Gravity import android.widget.FrameLayout import android.widget.TextView import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.R import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith Loading @@ -36,11 +39,15 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BubbleBarFlyoutControllerTest { @get:Rule val animatorTestRule = AnimatorTestRule() private lateinit var flyoutController: BubbleBarFlyoutController private lateinit var flyoutContainer: FrameLayout private lateinit var topBoundaryListener: FakeTopBoundaryListener private val context = ApplicationProvider.getApplicationContext<Context>() private val flyoutMessage = BubbleBarFlyoutMessage(icon = null, "sender name", "message") private var onLeft = true private var flyoutTy = 50f @Before fun setUp() { Loading @@ -50,18 +57,29 @@ class BubbleBarFlyoutControllerTest { override val isOnLeft get() = onLeft override val targetTy = 50f override val targetTy get() = flyoutTy override val distanceToCollapsedPosition = PointF(100f, 200f) override val collapsedSize = 30f override val collapsedColor = Color.BLUE override val collapsedElevation = 1f override val distanceToRevealTriangle = 50f } flyoutController = BubbleBarFlyoutController(flyoutContainer, positioner) topBoundaryListener = FakeTopBoundaryListener() val flyoutScheduler = FlyoutScheduler { block -> block.invoke() } flyoutController = BubbleBarFlyoutController( flyoutContainer, positioner, topBoundaryListener, flyoutScheduler, ) } @Test fun flyoutPosition_left() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) val flyout = flyoutContainer.getChildAt(0) Loading @@ -69,10 +87,12 @@ class BubbleBarFlyoutControllerTest { assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.LEFT) assertThat(flyout.translationY).isEqualTo(50f) } } @Test fun flyoutPosition_right() { onLeft = false InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) val flyout = flyoutContainer.getChildAt(0) Loading @@ -80,9 +100,11 @@ class BubbleBarFlyoutControllerTest { assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.RIGHT) assertThat(flyout.translationY).isEqualTo(50f) } } @Test fun flyoutMessage() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) val flyout = flyoutContainer.getChildAt(0) Loading @@ -91,12 +113,70 @@ class BubbleBarFlyoutControllerTest { val message = flyout.findViewById<TextView>(R.id.bubble_flyout_text) assertThat(message.text).isEqualTo("message") } } @Test fun hideFlyout_removedFromContainer() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) flyoutController.hideFlyout() flyoutController.hideFlyout {} animatorTestRule.advanceTimeBy(300) } assertThat(flyoutContainer.childCount).isEqualTo(0) } @Test fun showFlyout_extendsTopBoundary() { // set negative translation for the flyout so that it will request to extend the top // boundary flyoutTy = -50f InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) } InstrumentationRegistry.getInstrumentation().waitForIdleSync() InstrumentationRegistry.getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(300) } assertThat(topBoundaryListener.topBoundaryExtendedSpace).isEqualTo(50) } @Test fun showFlyout_withinBoundary() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) } InstrumentationRegistry.getInstrumentation().waitForIdleSync() InstrumentationRegistry.getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(300) } assertThat(topBoundaryListener.topBoundaryExtendedSpace).isEqualTo(0) } @Test fun hideFlyout_resetsTopBoundary() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) flyoutController.hideFlyout {} animatorTestRule.advanceTimeBy(300) } assertThat(topBoundaryListener.topBoundaryReset).isTrue() } class FakeTopBoundaryListener : BubbleBarFlyoutController.TopBoundaryListener { var topBoundaryExtendedSpace = 0 var topBoundaryReset = false override fun extendTopBoundary(space: Int) { topBoundaryExtendedSpace = space } override fun resetTopBoundary() { topBoundaryReset = true } } } Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +21 −0 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.launcher3.taskbar.TaskbarControllers; import com.android.launcher3.taskbar.TaskbarInsetsController; import com.android.launcher3.taskbar.TaskbarStashController; import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator; import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController; import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner; import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController; import com.android.launcher3.util.MultiPropertyFactory; Loading Loading @@ -117,6 +118,8 @@ public class BubbleBarViewController { public boolean mOverflowAdded; private BubbleBarViewAnimator mBubbleBarViewAnimator; private final FrameLayout mBubbleBarContainer; private BubbleBarFlyoutController mBubbleBarFlyoutController; private final TimeSource mTimeSource = System::currentTimeMillis; Loading @@ -127,6 +130,7 @@ public class BubbleBarViewController { FrameLayout bubbleBarContainer) { mActivity = activity; mBarView = barView; mBubbleBarContainer = bubbleBarContainer; mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity); mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */); mIconSize = activity.getResources().getDimensionPixelSize( Loading @@ -141,6 +145,8 @@ public class BubbleBarViewController { mBubbleDragController = bubbleControllers.bubbleDragController; mTaskbarStashController = controllers.taskbarStashController; mTaskbarInsetsController = controllers.taskbarInsetsController; mBubbleBarFlyoutController = new BubbleBarFlyoutController( mBubbleBarContainer, createFlyoutPositioner(), createFlyoutTopBoundaryListener()); mBubbleBarViewAnimator = new BubbleBarViewAnimator( mBarView, mBubbleStashController, mBubbleBarController::showExpandedView); mTaskbarViewPropertiesProvider = taskbarViewPropertiesProvider; Loading Loading @@ -266,6 +272,21 @@ public class BubbleBarViewController { }; } private BubbleBarFlyoutController.TopBoundaryListener createFlyoutTopBoundaryListener() { return new BubbleBarFlyoutController.TopBoundaryListener() { @Override public void extendTopBoundary(int space) { int defaultSize = mActivity.getDefaultTaskbarWindowSize(); mActivity.setTaskbarWindowSize(defaultSize + space); } @Override public void resetTopBoundary() { mActivity.setTaskbarWindowSize(mActivity.getDefaultTaskbarWindowSize()); } }; } private void onBubbleClicked(BubbleView bubbleView) { bubbleView.markSeen(); BubbleBarItem bubble = bubbleView.getBubble(); Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt +42 −6 Original line number Diff line number Diff line Loading @@ -21,20 +21,30 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.core.animation.ValueAnimator import com.android.launcher3.R import com.android.systemui.util.doOnEnd import com.android.systemui.util.doOnStart /** Creates and manages the visibility of the [BubbleBarFlyoutView]. */ class BubbleBarFlyoutController( class BubbleBarFlyoutController @JvmOverloads constructor( private val container: FrameLayout, private val positioner: BubbleBarFlyoutPositioner, private val topBoundaryListener: TopBoundaryListener, private val flyoutScheduler: FlyoutScheduler = HandlerScheduler(container), ) { private companion object { const val EXPAND_COLLAPSE_ANIMATION_DURATION_MS = 250L } private var flyout: BubbleBarFlyoutView? = null private val horizontalMargin = container.context.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin) fun setUpFlyout(message: BubbleBarFlyoutMessage) { flyout?.let(container::removeView) val flyout = BubbleBarFlyoutView(container.context, positioner) val flyout = BubbleBarFlyoutView(container.context, positioner, flyoutScheduler) flyout.translationY = positioner.targetTy Loading @@ -48,17 +58,43 @@ class BubbleBarFlyoutController( lp.marginEnd = horizontalMargin container.addView(flyout, lp) val animator = ValueAnimator.ofFloat(0f, 1f) val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS) animator.addUpdateListener { _ -> flyout.updateExpansionProgress(animator.animatedValue as Float) } animator.doOnStart { 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) topBoundaryListener.extendTopBoundary(space = -flyoutTop.toInt()) } flyout.showFromCollapsed(message) { animator.start() } this.flyout = flyout } fun hideFlyout() { fun hideFlyout(endAction: () -> Unit) { val flyout = this.flyout ?: return val animator = ValueAnimator.ofFloat(1f, 0f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS) animator.addUpdateListener { _ -> flyout.updateExpansionProgress(animator.animatedValue as Float) } animator.doOnEnd { container.removeView(flyout) this.flyout = null this@BubbleBarFlyoutController.flyout = null topBoundaryListener.resetTopBoundary() endAction() } animator.start() } /** Notifies when the top boundary of the flyout view changes. */ interface TopBoundaryListener { /** Requests to extend the top boundary of the parent to fully include the flyout. */ fun extendTopBoundary(space: Int) /** Resets the top boundary of the parent. */ fun resetTopBoundary() } }
quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt +7 −4 Original line number Diff line number Diff line Loading @@ -36,14 +36,18 @@ import com.android.launcher3.R import com.android.launcher3.popup.RoundedArrowDrawable /** The flyout view used to notify the user of a new bubble notification. */ class BubbleBarFlyoutView(context: Context, private val positioner: BubbleBarFlyoutPositioner) : ConstraintLayout(context) { class BubbleBarFlyoutView( context: Context, private val positioner: BubbleBarFlyoutPositioner, scheduler: FlyoutScheduler? = null, ) : ConstraintLayout(context) { private companion object { // the minimum progress of the expansion animation before the content starts fading in. const val MIN_EXPANSION_PROGRESS_FOR_CONTENT_ALPHA = 0.75f } private val scheduler: FlyoutScheduler = scheduler ?: HandlerScheduler(this) private val title: TextView by lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_title) } Loading Loading @@ -197,11 +201,10 @@ class BubbleBarFlyoutView(context: Context, private val positioner: BubbleBarFly // post the request to start the expand animation to the looper so the view can measure // itself post(expandAnimation) scheduler.runAfterLayout(expandAnimation) } private fun setData(flyoutMessage: BubbleBarFlyoutMessage) { // the avatar is only displayed in group chat messages if (flyoutMessage.icon != null) { icon.visibility = VISIBLE icon.setImageDrawable(flyoutMessage.icon) Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/FlyoutScheduler.kt 0 → 100644 +32 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.taskbar.bubbles.flyout import android.view.View /** Interface for scheduling jobs by flyout. */ fun interface FlyoutScheduler { /** Runs the given [block] after layout. */ fun runAfterLayout(block: () -> Unit) } /** A [FlyoutScheduler] that uses a Handler to schedule jobs. */ class HandlerScheduler(val view: View) : FlyoutScheduler { override fun runAfterLayout(block: () -> Unit) { view.post(block) } }
quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt +104 −24 Original line number Diff line number Diff line Loading @@ -22,12 +22,15 @@ import android.graphics.PointF import android.view.Gravity import android.widget.FrameLayout import android.widget.TextView import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.R import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith Loading @@ -36,11 +39,15 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BubbleBarFlyoutControllerTest { @get:Rule val animatorTestRule = AnimatorTestRule() private lateinit var flyoutController: BubbleBarFlyoutController private lateinit var flyoutContainer: FrameLayout private lateinit var topBoundaryListener: FakeTopBoundaryListener private val context = ApplicationProvider.getApplicationContext<Context>() private val flyoutMessage = BubbleBarFlyoutMessage(icon = null, "sender name", "message") private var onLeft = true private var flyoutTy = 50f @Before fun setUp() { Loading @@ -50,18 +57,29 @@ class BubbleBarFlyoutControllerTest { override val isOnLeft get() = onLeft override val targetTy = 50f override val targetTy get() = flyoutTy override val distanceToCollapsedPosition = PointF(100f, 200f) override val collapsedSize = 30f override val collapsedColor = Color.BLUE override val collapsedElevation = 1f override val distanceToRevealTriangle = 50f } flyoutController = BubbleBarFlyoutController(flyoutContainer, positioner) topBoundaryListener = FakeTopBoundaryListener() val flyoutScheduler = FlyoutScheduler { block -> block.invoke() } flyoutController = BubbleBarFlyoutController( flyoutContainer, positioner, topBoundaryListener, flyoutScheduler, ) } @Test fun flyoutPosition_left() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) val flyout = flyoutContainer.getChildAt(0) Loading @@ -69,10 +87,12 @@ class BubbleBarFlyoutControllerTest { assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.LEFT) assertThat(flyout.translationY).isEqualTo(50f) } } @Test fun flyoutPosition_right() { onLeft = false InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) val flyout = flyoutContainer.getChildAt(0) Loading @@ -80,9 +100,11 @@ class BubbleBarFlyoutControllerTest { assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.RIGHT) assertThat(flyout.translationY).isEqualTo(50f) } } @Test fun flyoutMessage() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) val flyout = flyoutContainer.getChildAt(0) Loading @@ -91,12 +113,70 @@ class BubbleBarFlyoutControllerTest { val message = flyout.findViewById<TextView>(R.id.bubble_flyout_text) assertThat(message.text).isEqualTo("message") } } @Test fun hideFlyout_removedFromContainer() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) flyoutController.hideFlyout() flyoutController.hideFlyout {} animatorTestRule.advanceTimeBy(300) } assertThat(flyoutContainer.childCount).isEqualTo(0) } @Test fun showFlyout_extendsTopBoundary() { // set negative translation for the flyout so that it will request to extend the top // boundary flyoutTy = -50f InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) } InstrumentationRegistry.getInstrumentation().waitForIdleSync() InstrumentationRegistry.getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(300) } assertThat(topBoundaryListener.topBoundaryExtendedSpace).isEqualTo(50) } @Test fun showFlyout_withinBoundary() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) } InstrumentationRegistry.getInstrumentation().waitForIdleSync() InstrumentationRegistry.getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(300) } assertThat(topBoundaryListener.topBoundaryExtendedSpace).isEqualTo(0) } @Test fun hideFlyout_resetsTopBoundary() { InstrumentationRegistry.getInstrumentation().runOnMainSync { flyoutController.setUpFlyout(flyoutMessage) assertThat(flyoutContainer.childCount).isEqualTo(1) flyoutController.hideFlyout {} animatorTestRule.advanceTimeBy(300) } assertThat(topBoundaryListener.topBoundaryReset).isTrue() } class FakeTopBoundaryListener : BubbleBarFlyoutController.TopBoundaryListener { var topBoundaryExtendedSpace = 0 var topBoundaryReset = false override fun extendTopBoundary(space: Int) { topBoundaryExtendedSpace = space } override fun resetTopBoundary() { topBoundaryReset = true } } }