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 androidx.lifecycle.Lifecycle 20 import androidx.lifecycle.repeatOnLifecycle 21 import com.android.app.tracing.coroutines.launchTraced as launch 22 import com.android.systemui.Flags 23 import com.android.systemui.common.ui.view.onLayoutChanged 24 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor 25 import com.android.systemui.dagger.SysUISingleton 26 import com.android.systemui.dagger.qualifiers.Main 27 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 28 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor 29 import com.android.systemui.lifecycle.repeatWhenAttached 30 import com.android.systemui.scene.shared.flag.SceneContainerFlag 31 import com.android.systemui.shared.Flags.extendedWallpaperEffects 32 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController 33 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator 34 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer 35 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel 36 import com.android.systemui.util.kotlin.DisposableHandles 37 import javax.inject.Inject 38 import kotlinx.coroutines.CoroutineDispatcher 39 import kotlinx.coroutines.DisposableHandle 40 import kotlinx.coroutines.flow.combine 41 import kotlinx.coroutines.flow.map 42 43 /** Binds the shared notification container to its view-model. */ 44 @SysUISingleton 45 class SharedNotificationContainerBinder 46 @Inject 47 constructor( 48 private val controller: NotificationStackScrollLayoutController, 49 private val notificationStackSizeCalculator: NotificationStackSizeCalculator, 50 private val notificationScrollViewBinder: NotificationScrollViewBinder, 51 private val communalSettingsInteractor: CommunalSettingsInteractor, 52 @Main private val mainImmediateDispatcher: CoroutineDispatcher, 53 val keyguardInteractor: KeyguardInteractor, 54 ) { 55 56 private val calculateMaxNotifications: (Float, Boolean) -> Int = { space, extraShelfSpace -> 57 val shelfHeight = controller.getShelfHeight().toFloat() 58 notificationStackSizeCalculator.computeMaxKeyguardNotifications( 59 controller.view, 60 space, 61 if (extraShelfSpace) shelfHeight else 0f, 62 shelfHeight, 63 ) 64 } 65 66 fun bind( 67 view: SharedNotificationContainer, 68 viewModel: SharedNotificationContainerViewModel, 69 ): DisposableHandle { 70 val disposables = DisposableHandles() 71 disposables += 72 view.repeatWhenAttached { 73 repeatOnLifecycle(Lifecycle.State.CREATED) { 74 launch { 75 viewModel.configurationBasedDimensions.collect { 76 view.updateConstraints( 77 horizontalPosition = it.horizontalPosition, 78 marginStart = it.marginStart, 79 marginTop = it.marginTop, 80 marginEnd = it.marginEnd, 81 marginBottom = it.marginBottom, 82 nsslAlpha = controller.alpha, 83 ) 84 85 controller.setOverExpansion(0f) 86 controller.setOverScrollAmount(0) 87 } 88 } 89 } 90 } 91 92 val viewState = ViewStateAccessor(alpha = { controller.getAlpha() }) 93 94 /* 95 * For animation sensitive coroutines, immediately run just like applicationScope does 96 * instead of doing a post() to the main thread. This extra delay can cause visible jitter. 97 */ 98 disposables += 99 view.repeatWhenAttached(mainImmediateDispatcher) { 100 repeatOnLifecycle(Lifecycle.State.CREATED) { 101 if (!SceneContainerFlag.isEnabled) { 102 launch { 103 viewModel.shadeCollapseFadeIn.collect { fadeIn -> 104 if (fadeIn) { 105 android.animation.ValueAnimator.ofFloat(0f, 1f).apply { 106 duration = 250 107 addUpdateListener { animation -> 108 controller.setMaxAlphaForKeyguard( 109 animation.animatedFraction, 110 "SharedNotificationContainerVB (collapseFadeIn)", 111 ) 112 } 113 start() 114 } 115 } 116 } 117 } 118 } 119 120 launch { 121 viewModel.getLockscreenDisplayConfig(calculateMaxNotifications).collect { 122 (isOnLockscreen, maxNotifications) -> 123 if (SceneContainerFlag.isEnabled) { 124 controller.setOnLockscreen(isOnLockscreen) 125 } 126 controller.setMaxDisplayedNotifications(maxNotifications) 127 } 128 } 129 130 if (!SceneContainerFlag.isEnabled) { 131 launch { 132 viewModel.bounds.collect { 133 val animate = 134 it.isAnimated || controller.isAddOrRemoveAnimationPending 135 controller.updateTopPadding(it.top, animate) 136 } 137 } 138 } 139 140 if (!SceneContainerFlag.isEnabled) { 141 launch { 142 viewModel.translationY.collect { y -> controller.setTranslationY(y) } 143 } 144 } 145 146 if (!SceneContainerFlag.isEnabled) { 147 if (extendedWallpaperEffects()) { 148 launch { 149 combine( 150 viewModel.getNotificationStackAbsoluteBottom( 151 calculateMaxNotifications = calculateMaxNotifications, 152 calculateHeight = { maxNotifications -> 153 notificationStackSizeCalculator.computeHeight( 154 maxNotifs = maxNotifications, 155 shelfHeight = 156 controller.getShelfHeight().toFloat(), 157 stack = controller.view, 158 ) 159 }, 160 controller.getShelfHeight().toFloat(), 161 ), 162 viewModel.configurationBasedDimensions.map { it.marginTop }, 163 ::Pair, 164 ) 165 .collect { (bottom: Float, marginTop: Int) -> 166 keyguardInteractor.setNotificationStackAbsoluteBottom( 167 marginTop + bottom 168 ) 169 } 170 } 171 } 172 } 173 174 launch { viewModel.translationX.collect { x -> controller.translationX = x } } 175 176 launch { 177 viewModel.keyguardAlpha(viewState, this).collect { 178 controller.setMaxAlphaForKeyguard(it, "SharedNotificationContainerVB") 179 } 180 } 181 182 if (!SceneContainerFlag.isEnabled) { 183 launch { 184 // For when the entire view should fade, such as with the brightness 185 // slider 186 viewModel.panelAlpha.collect { controller.setMaxAlphaFromView(it) } 187 } 188 } 189 190 if (Flags.bouncerUiRevamp()) { 191 launch { viewModel.blurRadius.collect { controller.setBlurRadius(it) } } 192 } 193 194 if (communalSettingsInteractor.isCommunalFlagEnabled()) { 195 launch { 196 viewModel.glanceableHubAlpha.collect { 197 controller.setMaxAlphaForGlanceableHub(it) 198 } 199 } 200 } 201 } 202 } 203 204 if (SceneContainerFlag.isEnabled) { 205 disposables += notificationScrollViewBinder.bindWhileAttached() 206 } 207 208 controller.setOnHeightChangedRunnable { viewModel.notificationStackChanged() } 209 disposables += DisposableHandle { controller.setOnHeightChangedRunnable(null) } 210 disposables += view.onLayoutChanged { viewModel.notificationStackChanged() } 211 212 return disposables 213 } 214 } 215