1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.taskbar.bubbles.stashing 18 19 import android.animation.Animator 20 import android.animation.AnimatorSet 21 import android.animation.ValueAnimator 22 import android.content.Context 23 import android.graphics.Rect 24 import android.view.MotionEvent 25 import android.view.View 26 import androidx.annotation.VisibleForTesting 27 import androidx.core.animation.doOnEnd 28 import androidx.core.animation.doOnStart 29 import androidx.dynamicanimation.animation.SpringForce 30 import com.android.app.animation.Interpolators.EMPHASIZED 31 import com.android.app.animation.Interpolators.LINEAR 32 import com.android.launcher3.R 33 import com.android.launcher3.anim.AnimatedFloat 34 import com.android.launcher3.anim.SpringAnimationBuilder 35 import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_IN_ANIM_ALPHA_DURATION_MS 36 import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_OUT_ANIM_ALPHA_DELAY_MS 37 import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_OUT_ANIM_ALPHA_DURATION_MS 38 import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_OUT_ANIM_POSITION_DURATION_MS 39 import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.inShiftX 40 import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.outShift 41 import com.android.launcher3.taskbar.TaskbarInsetsController 42 import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY 43 import com.android.launcher3.taskbar.TaskbarStashController.TRANSIENT_TASKBAR_STASH_ALPHA_DURATION 44 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController 45 import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController 46 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState 47 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION 48 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION 49 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction 50 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.TaskbarHotseatDimensionsProvider 51 import com.android.launcher3.util.MultiPropertyFactory 52 import com.android.wm.shell.shared.animation.PhysicsAnimator 53 import com.android.wm.shell.shared.bubbles.BubbleBarLocation 54 import com.android.wm.shell.shared.bubbles.ContextUtils.isRtl 55 import kotlin.math.max 56 57 class TransientBubbleStashController( 58 private val taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider, 59 private val context: Context, 60 ) : BubbleStashController { 61 62 private lateinit var bubbleBarViewController: BubbleBarViewController 63 private lateinit var taskbarInsetsController: TaskbarInsetsController 64 private lateinit var controllersAfterInitAction: ControllersAfterInitAction 65 66 // stash view properties 67 private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null 68 private var stashHandleViewAlpha: MultiPropertyFactory<View>.MultiProperty? = null 69 private var translationYDuringStash = AnimatedFloat { transY -> 70 bubbleStashedHandleViewController?.setTranslationYForStash(transY) 71 bubbleBarViewController.setTranslationYForStash(transY) 72 } 73 private val stashHandleStashVelocity = 74 context.resources.getDimension(R.dimen.bubblebar_stashed_handle_spring_velocity_dp_per_s) 75 private var stashedHeight: Int = 0 76 77 // bubble bar properties 78 private lateinit var bubbleBarAlpha: MultiPropertyFactory<View>.MultiProperty 79 private lateinit var bubbleBarBubbleAlpha: AnimatedFloat 80 private lateinit var bubbleBarBackgroundAlpha: AnimatedFloat 81 private lateinit var bubbleBarTranslationYAnimator: AnimatedFloat 82 private lateinit var bubbleBarBubbleTranslationY: AnimatedFloat 83 private lateinit var bubbleBarBackgroundScaleX: AnimatedFloat 84 private lateinit var bubbleBarBackgroundScaleY: AnimatedFloat 85 private val handleCenterFromScreenBottom = 86 context.resources.getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f 87 88 private var animator: AnimatorSet? = null 89 override var bubbleBarVerticalCenterForHome: Int = 0 90 91 override var isStashed: Boolean = false 92 @VisibleForTesting set 93 94 override var launcherState: BubbleLauncherState = BubbleLauncherState.IN_APP 95 set(state) { 96 if (field == state) return 97 field = state 98 val hasBubbles = bubbleBarViewController.hasBubbles() 99 bubbleBarViewController.onBubbleBarConfigurationChanged(hasBubbles) 100 if (!hasBubbles) { 101 // if there are no bubbles, there's no need to update the bubble bar, just keep the 102 // isStashed state up to date so that we can process state changes when bubbles are 103 // created. 104 isStashed = launcherState == BubbleLauncherState.IN_APP 105 return 106 } 107 if (field == BubbleLauncherState.HOME) { 108 // When to home we need to animate the bubble bar 109 // here to align with hotseat center. 110 animateBubbleBarYToHotseat() 111 } else if (field == BubbleLauncherState.OVERVIEW) { 112 // When transitioning to overview we need to animate the bubble bar to align with 113 // the taskbar bottom. 114 animateBubbleBarYToTaskbar() 115 } 116 // Only stash if we're in an app, otherwise we're in home or overview where we should 117 // be un-stashed 118 updateStashedAndExpandedState(field == BubbleLauncherState.IN_APP, expand = false) 119 } 120 121 override var isSysuiLocked: Boolean = false 122 set(isLocked) { 123 if (field == isLocked) return 124 field = isLocked 125 if (!isLocked && bubbleBarViewController.hasBubbles()) { 126 animateAfterUnlock() 127 } 128 } 129 130 override val isTransientTaskBar: Boolean = true 131 132 override val bubbleBarTranslationYForHotseat: Float 133 get() { 134 val bubbleBarHeight = bubbleBarViewController.bubbleBarCollapsedHeight 135 return -bubbleBarVerticalCenterForHome + bubbleBarHeight / 2 136 } 137 138 override val bubbleBarTranslationYForTaskbar: Float = 139 -taskbarHotseatDimensionsProvider.getTaskbarBottomSpace().toFloat() 140 141 /** Not supported in transient mode */ 142 override var inAppDisplayOverrideProgress: Float = 0f 143 144 /** Check if we have handle view controller */ 145 override val hasHandleView: Boolean 146 get() = bubbleStashedHandleViewController != null 147 148 override fun init( 149 taskbarInsetsController: TaskbarInsetsController, 150 bubbleBarViewController: BubbleBarViewController, 151 bubbleStashedHandleViewController: BubbleStashedHandleViewController?, 152 controllersAfterInitAction: ControllersAfterInitAction, 153 ) { 154 this.taskbarInsetsController = taskbarInsetsController 155 this.bubbleBarViewController = bubbleBarViewController 156 this.bubbleStashedHandleViewController = bubbleStashedHandleViewController 157 this.controllersAfterInitAction = controllersAfterInitAction 158 bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY 159 bubbleBarBubbleTranslationY = bubbleBarViewController.bubbleOffsetY 160 // bubble bar has only alpha property, getting it at index 0 161 bubbleBarAlpha = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0) 162 bubbleBarBubbleAlpha = bubbleBarViewController.bubbleBarBubbleAlpha 163 bubbleBarBackgroundAlpha = bubbleBarViewController.bubbleBarBackgroundAlpha 164 bubbleBarBackgroundScaleX = bubbleBarViewController.bubbleBarBackgroundScaleX 165 bubbleBarBackgroundScaleY = bubbleBarViewController.bubbleBarBackgroundScaleY 166 stashedHeight = bubbleStashedHandleViewController?.stashedHeight ?: 0 167 stashHandleViewAlpha = bubbleStashedHandleViewController?.stashedHandleAlpha?.get(0) 168 } 169 170 private fun animateAfterUnlock() { 171 val animatorSet = AnimatorSet() 172 if (isBubblesShowingOnHome || isBubblesShowingOnOverview) { 173 isStashed = false 174 animatorSet.playTogether( 175 bubbleBarBackgroundScaleX.animateToValue(1f), 176 bubbleBarBackgroundScaleY.animateToValue(1f), 177 bubbleBarTranslationYAnimator.animateToValue(bubbleBarTranslationY), 178 bubbleBarAlpha.animateToValue(1f), 179 bubbleBarBubbleAlpha.animateToValue(1f), 180 bubbleBarBackgroundAlpha.animateToValue(1f), 181 ) 182 } else { 183 isStashed = true 184 stashHandleViewAlpha?.let { animatorSet.playTogether(it.animateToValue(1f)) } 185 } 186 animatorSet 187 .updateBarVisibility(isStashed) 188 .updateTouchRegionOnAnimationEnd() 189 .setDuration(BAR_STASH_DURATION) 190 .start() 191 } 192 193 override fun showBubbleBarImmediate() { 194 showBubbleBarImmediate(bubbleBarTranslationY) 195 } 196 197 override fun showBubbleBarImmediate(bubbleBarTranslationY: Float) { 198 showBubbleBarImmediateVisually(bubbleBarTranslationY) 199 onIsStashedChanged() 200 } 201 202 private fun showBubbleBarImmediateVisually(bubbleBarTranslationY: Float) { 203 bubbleStashedHandleViewController?.setTranslationYForSwipe(0f) 204 stashHandleViewAlpha?.value = 0f 205 this.bubbleBarTranslationYAnimator.updateValue(bubbleBarTranslationY) 206 bubbleBarAlpha.setValue(1f) 207 bubbleBarBubbleAlpha.updateValue(1f) 208 bubbleBarBackgroundAlpha.updateValue(1f) 209 bubbleBarBackgroundScaleX.updateValue(1f) 210 bubbleBarBackgroundScaleY.updateValue(1f) 211 isStashed = false 212 bubbleBarViewController.setHiddenForStashed(false) 213 } 214 215 override fun stashBubbleBarImmediate() { 216 stashBubbleBarImmediateVisually() 217 onIsStashedChanged() 218 } 219 220 private fun stashBubbleBarImmediateVisually() { 221 bubbleStashedHandleViewController?.setTranslationYForSwipe(0f) 222 stashHandleViewAlpha?.value = 1f 223 this.bubbleBarTranslationYAnimator.updateValue(getStashTranslation()) 224 bubbleBarAlpha.setValue(0f) 225 // Reset bubble and background alpha to 1 and only keep the bubble bar alpha at 0 226 bubbleBarBubbleAlpha.updateValue(1f) 227 bubbleBarBackgroundAlpha.updateValue(1f) 228 bubbleBarBackgroundScaleX.updateValue(getStashScaleX()) 229 bubbleBarBackgroundScaleY.updateValue(getStashScaleY()) 230 isStashed = true 231 bubbleBarViewController.setHiddenForStashed(true) 232 } 233 234 override fun getTouchableHeight(): Int = 235 when { 236 isStashed -> stashedHeight 237 isBubbleBarVisible() -> bubbleBarViewController.bubbleBarCollapsedHeight.toInt() 238 else -> 0 239 } 240 241 override fun isBubbleBarVisible(): Boolean = bubbleBarViewController.hasBubbles() && !isStashed 242 243 override fun onNewBubbleAnimationInterrupted(isStashed: Boolean, bubbleBarTranslationY: Float) { 244 if (isStashed) { 245 stashBubbleBarImmediate() 246 } else { 247 showBubbleBarImmediate(bubbleBarTranslationY) 248 } 249 } 250 251 /** Check if [ev] belongs to the stash handle or the bubble bar views. */ 252 override fun isEventOverBubbleBarViews(ev: MotionEvent): Boolean { 253 val isOverHandle = bubbleStashedHandleViewController?.isEventOverHandle(ev) ?: false 254 return isOverHandle || bubbleBarViewController.isEventOverAnyItem(ev) 255 } 256 257 /** Set the bubble bar stash handle location . */ 258 override fun setBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) { 259 bubbleStashedHandleViewController?.setBubbleBarLocation(bubbleBarLocation) 260 } 261 262 override fun stashBubbleBar() { 263 updateStashedAndExpandedState(stash = true, expand = false) 264 } 265 266 override fun stashBubbleBarToLocation( 267 fromLocation: BubbleBarLocation, 268 toLocation: BubbleBarLocation, 269 ) { 270 if (fromLocation.isSameSideWith(toLocation)) { 271 updateStashedAndExpandedState( 272 stash = true, 273 expand = false, 274 updateTouchRegionOnEnd = false, 275 ) 276 return 277 } 278 cancelAnimation() 279 animator = 280 AnimatorSet().apply { 281 playSequentially( 282 bubbleBarViewController.animateBubbleBarLocationOut(toLocation), 283 createHandleInAnimator(location = toLocation), 284 ) 285 start() 286 } 287 } 288 289 override fun showBubbleBar(expandBubbles: Boolean, bubbleBarGesture: Boolean) { 290 updateStashedAndExpandedState( 291 stash = false, 292 expand = expandBubbles, 293 bubbleBarGesture = bubbleBarGesture, 294 ) 295 } 296 297 override fun showBubbleBarAtLocation( 298 fromLocation: BubbleBarLocation, 299 toLocation: BubbleBarLocation, 300 ) { 301 if (fromLocation.isSameSideWith(toLocation)) { 302 updateStashedAndExpandedState( 303 stash = false, 304 expand = false, 305 updateTouchRegionOnEnd = false, 306 ) 307 return 308 } 309 cancelAnimation() 310 val bubbleBarInAnimation = 311 bubbleBarViewController.animateBubbleBarLocationIn(fromLocation, toLocation).apply { 312 doOnStart { showBubbleBarImmediateVisually(bubbleBarTranslationY) } 313 } 314 animator = 315 AnimatorSet().apply { 316 playSequentially( 317 createHandleOutAnimator(location = toLocation), 318 bubbleBarInAnimation, 319 ) 320 start() 321 } 322 } 323 324 override fun getDiffBetweenHandleAndBarCenters(): Float { 325 // the difference between the centers of the handle and the bubble bar is the difference 326 // between their distance from the bottom of the screen. 327 val barCenter: Float = bubbleBarViewController.bubbleBarCollapsedHeight / 2f 328 return handleCenterFromScreenBottom - barCenter 329 } 330 331 override fun getStashedHandleTranslationForNewBubbleAnimation(): Float { 332 return -handleCenterFromScreenBottom 333 } 334 335 override fun getStashedHandlePhysicsAnimator(): PhysicsAnimator<View>? { 336 return bubbleStashedHandleViewController?.physicsAnimator 337 } 338 339 override fun updateTaskbarTouchRegion() { 340 taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged() 341 } 342 343 override fun setHandleTranslationY(translationY: Float) { 344 bubbleStashedHandleViewController?.setTranslationYForSwipe(translationY) 345 } 346 347 override fun getHandleTranslationY(): Float? = bubbleStashedHandleViewController?.translationY 348 349 override fun getHandleBounds(bounds: Rect) { 350 bubbleStashedHandleViewController?.getBounds(bounds) 351 } 352 353 private fun getStashTranslation(): Float { 354 return (bubbleBarTranslationY - stashedHeight) / 2f 355 } 356 357 @VisibleForTesting 358 fun getStashScaleX(): Float { 359 val handleWidth = bubbleStashedHandleViewController?.stashedWidth ?: 0 360 return handleWidth / bubbleBarViewController.bubbleBarCollapsedWidth 361 } 362 363 @VisibleForTesting 364 fun getStashScaleY(): Float { 365 val handleHeight = bubbleStashedHandleViewController?.stashedHeight ?: 0 366 return handleHeight / bubbleBarViewController.bubbleBarCollapsedHeight 367 } 368 369 /** 370 * Create a stash animation. 371 * 372 * @param isStashed whether it's a stash animation or an unstash animation 373 * @param duration duration of the animation 374 * @return the animation 375 */ 376 @Suppress("SameParameterValue") 377 private fun createStashAnimator(isStashed: Boolean, duration: Long): AnimatorSet { 378 val animatorSet = AnimatorSet() 379 380 animatorSet.play( 381 createBackgroundAlphaAnimator(isStashed).apply { 382 val alphaDuration = 383 if (isStashed) duration else TRANSIENT_TASKBAR_STASH_ALPHA_DURATION 384 val alphaDelay = if (isStashed) TASKBAR_STASH_ALPHA_START_DELAY else 0L 385 this.duration = max(0L, alphaDuration - alphaDelay) 386 this.startDelay = alphaDelay 387 this.interpolator = LINEAR 388 } 389 ) 390 391 animatorSet.play( 392 bubbleBarBubbleAlpha 393 .animateToValue(getBarAlphaStart(isStashed), getBarAlphaEnd(isStashed)) 394 .apply { 395 this.duration = TRANSIENT_TASKBAR_STASH_ALPHA_DURATION 396 this.startDelay = TASKBAR_STASH_ALPHA_START_DELAY 397 this.interpolator = LINEAR 398 } 399 ) 400 401 animatorSet.play( 402 createSpringOnStashAnimator(isStashed).apply { 403 this.duration = duration 404 this.interpolator = LINEAR 405 } 406 ) 407 408 animatorSet.play( 409 bubbleBarViewController.createRevealAnimatorForStashChange(isStashed).apply { 410 this.duration = duration 411 this.interpolator = EMPHASIZED 412 } 413 ) 414 415 // Animate bubble translation to keep reveal animation in the bounds of the bar 416 val bubbleTyStart = if (isStashed) 0f else -bubbleBarTranslationY 417 val bubbleTyEnd = if (isStashed) -bubbleBarTranslationY else 0f 418 animatorSet.play( 419 bubbleBarBubbleTranslationY.animateToValue(bubbleTyStart, bubbleTyEnd).apply { 420 this.duration = duration 421 this.interpolator = EMPHASIZED 422 } 423 ) 424 425 animatorSet.play( 426 bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.apply { 427 this.duration = duration 428 this.interpolator = EMPHASIZED 429 } 430 ) 431 432 val pivotX = if (bubbleBarViewController.isBubbleBarOnLeft) 0f else 1f 433 animatorSet.play( 434 createScaleAnimator(isStashed).apply { 435 this.duration = duration 436 this.interpolator = EMPHASIZED 437 this.setBubbleBarPivotDuringAnim(pivotX, 1f) 438 } 439 ) 440 441 val translationYTarget = if (isStashed) getStashTranslation() else bubbleBarTranslationY 442 animatorSet.play( 443 bubbleBarTranslationYAnimator.animateToValue(translationYTarget).apply { 444 this.duration = duration 445 this.interpolator = EMPHASIZED 446 } 447 ) 448 449 animatorSet.doOnStart { 450 // Update the start value for bubble view and background alpha when the entire animation 451 // begins. 452 // Alpha animation has a delay, and if we set the initial values at the start of the 453 // alpha animation, it will cause flickers. 454 bubbleBarBubbleAlpha.updateValue(getBarAlphaStart(isStashed)) 455 bubbleBarBackgroundAlpha.updateValue(getBarAlphaStart(isStashed)) 456 // We animate alpha for background and bubble views separately. Make sure the container 457 // is always visible. 458 bubbleBarAlpha.value = 1f 459 } 460 animatorSet.doOnEnd { 461 cancelAnimation() 462 controllersAfterInitAction.runAfterInit { 463 if (isStashed) { 464 bubbleBarAlpha.value = 0f 465 // reset bubble view alpha 466 bubbleBarBubbleAlpha.updateValue(1f) 467 bubbleBarBackgroundAlpha.updateValue(1f) 468 // reset stash translation 469 translationYDuringStash.updateValue(0f) 470 bubbleBarBubbleTranslationY.updateValue(0f) 471 bubbleBarViewController.isExpanded = false 472 } 473 taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged() 474 } 475 } 476 return animatorSet 477 } 478 479 private fun createBackgroundAlphaAnimator(isStashed: Boolean): AnimatorSet { 480 return AnimatorSet().apply { 481 play( 482 bubbleBarBackgroundAlpha.animateToValue( 483 getBarAlphaStart(isStashed), 484 getBarAlphaEnd(isStashed), 485 ) 486 ) 487 play(stashHandleViewAlpha?.animateToValue(getHandleAlphaEnd(isStashed))) 488 } 489 } 490 491 private fun getBarAlphaStart(isStashed: Boolean): Float { 492 return if (isStashed) 1f else 0f 493 } 494 495 private fun getBarAlphaEnd(isStashed: Boolean): Float { 496 return if (isStashed) 0f else 1f 497 } 498 499 private fun getHandleAlphaEnd(isStashed: Boolean): Float { 500 return if (isStashed) 1f else 0f 501 } 502 503 private fun createSpringOnStashAnimator(isStashed: Boolean): Animator { 504 if (!isStashed) { 505 // Animate the stash translation back to 0 506 return translationYDuringStash.animateToValue(0f) 507 } 508 // Apply a spring to the handle 509 return SpringAnimationBuilder(context) 510 .setStartValue(translationYDuringStash.value) 511 .setEndValue(0f) 512 .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY) 513 .setStiffness(SpringForce.STIFFNESS_LOW) 514 .setStartVelocity(stashHandleStashVelocity) 515 .build(translationYDuringStash, AnimatedFloat.VALUE) 516 } 517 518 private fun createScaleAnimator(isStashed: Boolean): AnimatorSet { 519 val scaleXTarget = if (isStashed) getStashScaleX() else 1f 520 val scaleYTarget = if (isStashed) getStashScaleY() else 1f 521 return AnimatorSet().apply { 522 play(bubbleBarBackgroundScaleX.animateToValue(scaleXTarget)) 523 play(bubbleBarBackgroundScaleY.animateToValue(scaleYTarget)) 524 } 525 } 526 527 private fun onIsStashedChanged() { 528 controllersAfterInitAction.runAfterInit { 529 taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged() 530 bubbleStashedHandleViewController?.onIsStashedChanged() 531 } 532 } 533 534 private fun animateBubbleBarYToHotseat() { 535 translateBubbleBarYUpdateTouchRegionOnCompletion(bubbleBarTranslationYForHotseat) 536 } 537 538 private fun animateBubbleBarYToTaskbar() { 539 translateBubbleBarYUpdateTouchRegionOnCompletion(bubbleBarTranslationYForTaskbar) 540 } 541 542 private fun translateBubbleBarYUpdateTouchRegionOnCompletion(toY: Float) { 543 bubbleBarViewController.bubbleBarTranslationY 544 .animateToValue(toY) 545 .updateTouchRegionOnAnimationEnd() 546 .setDuration(BAR_TRANSLATION_DURATION) 547 .start() 548 } 549 550 @VisibleForTesting 551 fun updateStashedAndExpandedState( 552 stash: Boolean, 553 expand: Boolean, 554 bubbleBarGesture: Boolean = false, 555 updateTouchRegionOnEnd: Boolean = true, 556 ) { 557 if (bubbleBarViewController.isHiddenForNoBubbles) { 558 // If there are no bubbles the bar and handle are invisible, nothing to do here. 559 return 560 } 561 val isStashed = stash && !isBubblesShowingOnHome && !isBubblesShowingOnOverview 562 if (this.isStashed != isStashed) { 563 this.isStashed = isStashed 564 565 // notify the view controller that the stash state is about to change so that it can 566 // cancel an ongoing animation if there is one. 567 bubbleBarViewController.onStashStateChanging() 568 cancelAnimation() 569 animator = 570 createStashAnimator(isStashed, BAR_STASH_DURATION).apply { 571 updateBarVisibility(isStashed) 572 if (updateTouchRegionOnEnd) { 573 updateTouchRegionOnAnimationEnd() 574 } 575 start() 576 } 577 } 578 if (bubbleBarViewController.isExpanded != expand) { 579 val maybeShowEdu = expand && bubbleBarGesture 580 bubbleBarViewController.setExpanded(expand, maybeShowEdu) 581 } 582 } 583 584 private fun cancelAnimation() { 585 animator?.cancel() 586 animator = null 587 } 588 589 override fun getHandleViewAlpha(): MultiPropertyFactory<View>.MultiProperty? = 590 // only return handle alpha if the bubble bar is stashed and has bubbles 591 if (isStashed && bubbleBarViewController.hasBubbles()) { 592 stashHandleViewAlpha 593 } else { 594 null 595 } 596 597 private fun Animator.updateTouchRegionOnAnimationEnd(): Animator { 598 doOnEnd { onIsStashedChanged() } 599 return this 600 } 601 602 private fun <T : Animator> T.updateBarVisibility(stashed: Boolean): T { 603 if (stashed) { 604 doOnEnd { bubbleBarViewController.setHiddenForStashed(true) } 605 } else { 606 doOnStart { bubbleBarViewController.setHiddenForStashed(false) } 607 } 608 return this 609 } 610 611 // TODO(b/399678274) add tests 612 private fun createHandleInAnimator(location: BubbleBarLocation): Animator? { 613 val stashHandleViewController = bubbleStashedHandleViewController ?: return null 614 val handleAlpha = stashHandleViewController.stashedHandleAlpha.get(0) 615 val shift = context.inShiftX 616 val startX = if (location.isOnLeft(context.isRtl)) shift else -shift 617 val handleTranslationX = 618 ValueAnimator.ofFloat(startX, 0f).apply { 619 addUpdateListener { v -> 620 stashHandleViewController.setTranslationX(v.animatedValue as Float) 621 } 622 duration = FADE_IN_ANIM_ALPHA_DURATION_MS 623 } 624 val handleAlphaAnimation = 625 handleAlpha.animateToValue(1f).apply { duration = FADE_IN_ANIM_ALPHA_DURATION_MS } 626 return AnimatorSet().apply { 627 playTogether(handleTranslationX, handleAlphaAnimation) 628 doOnStart { stashBubbleBarImmediateVisually() } 629 } 630 } 631 632 private fun createHandleOutAnimator(location: BubbleBarLocation): Animator? { 633 val stashHandleViewController = bubbleStashedHandleViewController ?: return null 634 val handleAlpha = stashHandleViewController.stashedHandleAlpha.get(0) 635 val shift = context.outShift 636 val endX = if (location.isOnLeft(context.isRtl)) -shift else shift 637 val handleTranslationX = 638 ValueAnimator.ofFloat(0f, endX).apply { 639 addUpdateListener { v -> 640 stashHandleViewController.setTranslationX(v.animatedValue as Float) 641 } 642 duration = FADE_OUT_ANIM_POSITION_DURATION_MS 643 // in case item dropped to the opposite side - need to clear translation 644 doOnEnd { stashHandleViewController.setTranslationX(0f) } 645 } 646 val handleAlphaAnimation = 647 handleAlpha.animateToValue(0f).apply { 648 duration = FADE_OUT_ANIM_ALPHA_DURATION_MS 649 startDelay = FADE_OUT_ANIM_ALPHA_DELAY_MS 650 } 651 return AnimatorSet().apply { playTogether(handleTranslationX, handleAlphaAnimation) } 652 } 653 654 private fun Animator.setBubbleBarPivotDuringAnim(pivotX: Float, pivotY: Float): Animator { 655 var initialPivotX = Float.NaN 656 var initialPivotY = Float.NaN 657 doOnStart { 658 initialPivotX = bubbleBarViewController.bubbleBarRelativePivotX 659 initialPivotY = bubbleBarViewController.bubbleBarRelativePivotY 660 bubbleBarViewController.setBubbleBarRelativePivot(pivotX, pivotY) 661 } 662 doOnEnd { 663 if (!initialPivotX.isNaN() && !initialPivotY.isNaN()) { 664 bubbleBarViewController.setBubbleBarRelativePivot(initialPivotX, initialPivotY) 665 } 666 } 667 return this 668 } 669 670 private fun BubbleBarLocation.isSameSideWith(anotherLocation: BubbleBarLocation): Boolean { 671 val isRtl = context.isRtl 672 return this.isOnLeft(isRtl) == anotherLocation.isOnLeft(isRtl) 673 } 674 } 675