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.notifications.ui.composable
18
19 import androidx.compose.animation.core.Animatable
20 import androidx.compose.animation.core.tween
21 import androidx.compose.foundation.background
22 import androidx.compose.foundation.layout.Box
23 import androidx.compose.foundation.layout.fillMaxSize
24 import androidx.compose.runtime.Composable
25 import androidx.compose.runtime.LaunchedEffect
26 import androidx.compose.runtime.remember
27 import androidx.compose.runtime.rememberCoroutineScope
28 import androidx.compose.runtime.snapshotFlow
29 import androidx.compose.ui.Modifier
30 import androidx.compose.ui.graphics.graphicsLayer
31 import androidx.lifecycle.compose.collectAsStateWithLifecycle
32 import com.android.compose.animation.scene.ContentScope
33 import com.android.compose.animation.scene.content.state.TransitionState
34 import com.android.systemui.scene.shared.model.Overlays
35 import com.android.systemui.scene.shared.model.Scenes
36 import com.android.systemui.shade.shared.model.ShadeMode
37 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLockscreenScrimViewModel
38 import kotlinx.coroutines.launch
39
40 /**
41 * A full-screen notifications scrim that is only visible after transitioning from Shade scene to
42 * Lockscreen Scene and ending user input, at which point it fades out, visually completing the
43 * transition.
44 */
45 @Composable
46 fun ContentScope.NotificationLockscreenScrim(
47 viewModel: NotificationLockscreenScrimViewModel,
48 modifier: Modifier = Modifier,
49 ) {
50 val coroutineScope = rememberCoroutineScope()
51 val shadeMode = viewModel.shadeMode.collectAsStateWithLifecycle()
52
53 // Important: Make sure that shouldShowScrimFadeOut() is checked the first time the Lockscreen
54 // scene is composed.
55 val useFadeOutOnComposition =
56 remember(shadeMode.value) {
57 layoutState.currentTransition?.let { currentTransition ->
58 shouldShowScrimFadeOut(currentTransition, shadeMode.value)
59 } ?: false
60 }
61
62 val alphaAnimatable = remember { Animatable(1f) }
63
64 LaunchedEffect(
65 alphaAnimatable,
66 layoutState.currentTransition,
67 useFadeOutOnComposition,
68 shadeMode,
69 ) {
70 val currentTransition = layoutState.currentTransition
71 if (
72 useFadeOutOnComposition &&
73 currentTransition != null &&
74 shouldShowScrimFadeOut(currentTransition, shadeMode.value) &&
75 currentTransition.isUserInputOngoing
76 ) {
77 // keep scrim visible until user lifts their finger.
78 viewModel.setAlphaForLockscreenFadeIn(0f)
79 alphaAnimatable.snapTo(1f)
80 } else if (
81 useFadeOutOnComposition &&
82 (currentTransition == null ||
83 (shouldShowScrimFadeOut(currentTransition, shadeMode.value) &&
84 !currentTransition.isUserInputOngoing))
85 ) {
86 // we no longer want to keep the scrim from fading out, so animate the scrim fade-out
87 // and pipe the progress to the view model as well, so NSSL can fade-in the stack in
88 // tandem.
89 viewModel.setAlphaForLockscreenFadeIn(0f)
90 coroutineScope.launch {
91 snapshotFlow { alphaAnimatable.value }
92 .collect { viewModel.setAlphaForLockscreenFadeIn(1 - it) }
93 }
94 alphaAnimatable.animateTo(0f, tween())
95 } else {
96 // disable the scrim fade logic.
97 viewModel.setAlphaForLockscreenFadeIn(1f)
98 alphaAnimatable.snapTo(0f)
99 }
100 }
101
102 val isBouncerToLockscreen =
103 layoutState.currentTransition?.isTransitioning(
104 from = Overlays.Bouncer,
105 to = Scenes.Lockscreen,
106 ) ?: false
107
108 Box(
109 modifier
110 .fillMaxSize()
111 .element(viewModel.element.key)
112 .graphicsLayer { alpha = alphaAnimatable.value }
113 .background(viewModel.element.color(isBouncerToLockscreen))
114 )
115 }
116
shouldShowScrimFadeOutnull117 private fun shouldShowScrimFadeOut(
118 currentTransition: TransitionState.Transition,
119 shadeMode: ShadeMode,
120 ): Boolean {
121 return shadeMode != ShadeMode.Dual &&
122 currentTransition.isInitiatedByUserInput &&
123 (currentTransition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) ||
124 currentTransition.isTransitioning(from = Overlays.Bouncer, to = Scenes.Lockscreen))
125 }
126