• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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