1 /*
<lambda>null2  * Copyright (C) 2023 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.statusbar.notification.stack.ui.viewbinder
18 
19 import android.view.View
20 import android.view.WindowInsets
21 import androidx.lifecycle.Lifecycle
22 import androidx.lifecycle.repeatOnLifecycle
23 import com.android.systemui.Flags.communalHub
24 import com.android.systemui.common.ui.view.onApplyWindowInsets
25 import com.android.systemui.common.ui.view.onLayoutChanged
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.dagger.qualifiers.Main
28 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
29 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
30 import com.android.systemui.lifecycle.repeatWhenAttached
31 import com.android.systemui.scene.shared.flag.SceneContainerFlag
32 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
33 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
34 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
35 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
36 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
37 import com.android.systemui.util.kotlin.DisposableHandles
38 import javax.inject.Inject
39 import kotlinx.coroutines.CoroutineDispatcher
40 import kotlinx.coroutines.DisposableHandle
41 import kotlinx.coroutines.flow.MutableStateFlow
42 import kotlinx.coroutines.flow.flatMapLatest
43 import kotlinx.coroutines.flow.update
44 import kotlinx.coroutines.launch
45 
46 /** Binds the shared notification container to its view-model. */
47 @SysUISingleton
48 class SharedNotificationContainerBinder
49 @Inject
50 constructor(
51     private val controller: NotificationStackScrollLayoutController,
52     private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
53     private val notificationScrollViewBinder: NotificationScrollViewBinder,
54     @Main private val mainImmediateDispatcher: CoroutineDispatcher,
55 ) {
56 
57     fun bind(
58         view: SharedNotificationContainer,
59         viewModel: SharedNotificationContainerViewModel,
60     ): DisposableHandle {
61         val disposables = DisposableHandles()
62 
63         disposables +=
64             view.repeatWhenAttached {
65                 repeatOnLifecycle(Lifecycle.State.CREATED) {
66                     launch {
67                         viewModel.configurationBasedDimensions.collect {
68                             view.updateConstraints(
69                                 useSplitShade = it.useSplitShade,
70                                 marginStart = it.marginStart,
71                                 marginTop = it.marginTop,
72                                 marginEnd = it.marginEnd,
73                                 marginBottom = it.marginBottom,
74                             )
75 
76                             controller.setOverExpansion(0f)
77                             controller.setOverScrollAmount(0)
78                             if (!FooterViewRefactor.isEnabled) {
79                                 controller.updateFooter()
80                             }
81                         }
82                     }
83                 }
84             }
85 
86         val burnInParams = MutableStateFlow(BurnInParameters())
87         val viewState =
88             ViewStateAccessor(
89                 alpha = { controller.getAlpha() },
90             )
91 
92         /*
93          * For animation sensitive coroutines, immediately run just like applicationScope does
94          * instead of doing a post() to the main thread. This extra delay can cause visible jitter.
95          */
96         disposables +=
97             view.repeatWhenAttached(mainImmediateDispatcher) {
98                 repeatOnLifecycle(Lifecycle.State.CREATED) {
99                     launch {
100                         // Only temporarily needed, until flexi notifs go live
101                         viewModel.shadeCollapseFadeIn.collect { fadeIn ->
102                             if (fadeIn) {
103                                 android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
104                                     duration = 250
105                                     addUpdateListener { animation ->
106                                         controller.setMaxAlphaForKeyguard(
107                                             animation.animatedFraction,
108                                             "SharedNotificationContainerVB (collapseFadeIn)"
109                                         )
110                                     }
111                                     start()
112                                 }
113                             }
114                         }
115                     }
116 
117                     launch {
118                         viewModel
119                             .getMaxNotifications { space, extraShelfSpace ->
120                                 val shelfHeight = controller.getShelfHeight().toFloat()
121                                 notificationStackSizeCalculator.computeMaxKeyguardNotifications(
122                                     controller.getView(),
123                                     space,
124                                     if (extraShelfSpace) shelfHeight else 0f,
125                                     shelfHeight,
126                                 )
127                             }
128                             .collect { controller.setMaxDisplayedNotifications(it) }
129                     }
130 
131                     if (!SceneContainerFlag.isEnabled) {
132                         launch {
133                             viewModel.bounds.collect {
134                                 val animate =
135                                     it.isAnimated || controller.isAddOrRemoveAnimationPending
136                                 controller.updateTopPadding(it.top, animate)
137                             }
138                         }
139                     }
140 
141                     if (!SceneContainerFlag.isEnabled) {
142                         launch {
143                             burnInParams
144                                 .flatMapLatest { params -> viewModel.translationY(params) }
145                                 .collect { y -> controller.setTranslationY(y) }
146                         }
147                     }
148 
149                     launch { viewModel.translationX.collect { x -> controller.translationX = x } }
150 
151                     launch {
152                         viewModel.keyguardAlpha(viewState).collect {
153                             controller.setMaxAlphaForKeyguard(it, "SharedNotificationContainerVB")
154                         }
155                     }
156 
157                     if (communalHub()) {
158                         launch {
159                             viewModel.glanceableHubAlpha.collect {
160                                 controller.setMaxAlphaForGlanceableHub(it)
161                             }
162                         }
163                     }
164                 }
165             }
166 
167         if (SceneContainerFlag.isEnabled) {
168             disposables += notificationScrollViewBinder.bindWhileAttached()
169         }
170 
171         controller.setOnHeightChangedRunnable { viewModel.notificationStackChanged() }
172         disposables += DisposableHandle { controller.setOnHeightChangedRunnable(null) }
173 
174         disposables +=
175             view.onApplyWindowInsets { _: View, insets: WindowInsets ->
176                 val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
177                 burnInParams.update { current ->
178                     current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
179                 }
180                 insets
181             }
182 
183         disposables += view.onLayoutChanged { viewModel.notificationStackChanged() }
184 
185         return disposables
186     }
187 }
188