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.systemui.keyguard.ui.viewmodel 18 19 import android.util.LayoutDirection 20 import com.android.app.animation.Interpolators.EMPHASIZED 21 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor 22 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor 23 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor 24 import com.android.systemui.dagger.SysUISingleton 25 import com.android.systemui.dagger.qualifiers.Application 26 import com.android.systemui.keyguard.dagger.GlanceableHubBlurComponent 27 import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION 28 import com.android.systemui.keyguard.shared.model.Edge 29 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB 30 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN 31 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow 32 import com.android.systemui.keyguard.ui.StateToValue 33 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition 34 import com.android.systemui.keyguard.ui.transitions.GlanceableHubTransition 35 import com.android.systemui.res.R 36 import com.android.systemui.scene.shared.model.Scenes 37 import com.android.systemui.shade.ShadeDisplayAware 38 import javax.inject.Inject 39 import kotlin.time.Duration.Companion.milliseconds 40 import kotlinx.coroutines.CoroutineScope 41 import kotlinx.coroutines.ExperimentalCoroutinesApi 42 import kotlinx.coroutines.flow.Flow 43 import kotlinx.coroutines.flow.SharingStarted 44 import kotlinx.coroutines.flow.StateFlow 45 import kotlinx.coroutines.flow.combineTransform 46 import kotlinx.coroutines.flow.filterNotNull 47 import kotlinx.coroutines.flow.flatMapLatest 48 import kotlinx.coroutines.flow.flowOf 49 import kotlinx.coroutines.flow.map 50 import kotlinx.coroutines.flow.stateIn 51 52 /** 53 * Breaks down GLANCEABLE_HUB->LOCKSCREEN transition into discrete steps for corresponding views to 54 * consume. 55 */ 56 @OptIn(ExperimentalCoroutinesApi::class) 57 @SysUISingleton 58 class GlanceableHubToLockscreenTransitionViewModel 59 @Inject 60 constructor( 61 @Application applicationScope: CoroutineScope, 62 @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, 63 animationFlow: KeyguardTransitionAnimationFlow, 64 communalSceneInteractor: CommunalSceneInteractor, 65 communalSettingsInteractor: CommunalSettingsInteractor, 66 private val blurFactory: GlanceableHubBlurComponent.Factory, 67 ) : GlanceableHubTransition, DeviceEntryIconTransition { 68 private val transitionAnimation = 69 animationFlow 70 .setup( 71 duration = TO_LOCKSCREEN_DURATION, 72 edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN), 73 ) 74 .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN)) 75 76 // Whether screen rotation will happen with the transition. Only emit when idle so ongoing 77 // animation won't be interrupted when orientation is updated during the transition. 78 private val willRotateToPortraitInTransition: StateFlow<Boolean> = 79 if (!communalSettingsInteractor.isV2FlagEnabled()) { 80 flowOf(false) 81 } else { 82 communalSceneInteractor.isIdleOnCommunal.combineTransform( 83 communalSceneInteractor.willRotateToPortrait 84 ) { isIdle, willRotate -> 85 if (isIdle) emit(willRotate) 86 } 87 } 88 .stateIn(applicationScope, SharingStarted.Eagerly, false) 89 90 override val windowBlurRadius: Flow<Float> = 91 blurFactory.create(transitionAnimation).getBlurProvider().exitBlurRadius 92 93 val keyguardAlpha: Flow<Float> = 94 willRotateToPortraitInTransition.flatMapLatest { willRotate -> 95 transitionAnimation.sharedFlow( 96 duration = 167.milliseconds, 97 // If will rotate, start later to leave time for screen rotation. 98 startTime = if (willRotate) 500.milliseconds else 167.milliseconds, 99 onStep = { step -> 100 if (willRotate) { 101 if (!communalSceneInteractor.rotatedToPortrait.value) { 102 0f 103 } else { 104 1f 105 } 106 } else { 107 step 108 } 109 }, 110 onFinish = { 1f }, 111 onCancel = { 0f }, 112 name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha", 113 ) 114 } 115 116 // Show UMO as long as keyguard is not visible. 117 val showUmo: Flow<Boolean> = keyguardAlpha.map { alpha -> alpha == 0f } 118 119 val keyguardTranslationX: Flow<StateToValue> = 120 configurationInteractor 121 .directionalDimensionPixelSize( 122 LayoutDirection.LTR, 123 R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x, 124 ) 125 .flatMapLatest { translatePx: Int -> 126 transitionAnimation.sharedFlowWithState( 127 duration = TO_LOCKSCREEN_DURATION, 128 onStep = { value -> 129 // do not animate translation-x if screen rotation will happen 130 if (willRotateToPortraitInTransition.value) { 131 0f 132 } else { 133 -translatePx + value * translatePx 134 } 135 }, 136 interpolator = EMPHASIZED, 137 // Move notifications back to their original position since they can be 138 // accessed from the shade, and also keyguard elements in case the animation 139 // is cancelled. 140 onFinish = { 0f }, 141 onCancel = { 0f }, 142 name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX", 143 ) 144 } 145 146 val notificationAlpha: Flow<Float> = keyguardAlpha 147 148 val shortcutsAlpha: Flow<Float> = keyguardAlpha 149 150 val statusBarAlpha: Flow<Float> = keyguardAlpha 151 152 val notificationTranslationX: Flow<Float> = 153 keyguardTranslationX.map { it.value }.filterNotNull() 154 155 val deviceEntryBackgroundViewAlpha: Flow<Float> = keyguardAlpha 156 157 override val deviceEntryParentViewAlpha: Flow<Float> = keyguardAlpha 158 } 159