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 18 package com.android.systemui.statusbar.notification.stack.domain.interactor 19 20 import com.android.systemui.dagger.SysUISingleton 21 import com.android.systemui.scene.domain.interactor.SceneInteractor 22 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor 23 import com.android.systemui.shade.shared.model.ShadeMode 24 import com.android.systemui.statusbar.notification.stack.data.repository.NotificationPlaceholderRepository 25 import com.android.systemui.statusbar.notification.stack.data.repository.NotificationViewHeightRepository 26 import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent 27 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds 28 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding 29 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape 30 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState 31 import java.util.function.Consumer 32 import javax.inject.Inject 33 import kotlinx.coroutines.flow.Flow 34 import kotlinx.coroutines.flow.StateFlow 35 import kotlinx.coroutines.flow.asStateFlow 36 import kotlinx.coroutines.flow.combine 37 import kotlinx.coroutines.flow.distinctUntilChanged 38 import kotlinx.coroutines.flow.flowOf 39 40 /** An interactor which controls the appearance of the NSSL */ 41 @SysUISingleton 42 class NotificationStackAppearanceInteractor 43 @Inject 44 constructor( 45 private val viewHeightRepository: NotificationViewHeightRepository, 46 private val placeholderRepository: NotificationPlaceholderRepository, 47 sceneInteractor: SceneInteractor, 48 shadeModeInteractor: ShadeModeInteractor, 49 ) { 50 /** The bounds of the notification stack in the current scene. */ 51 val notificationShadeScrimBounds: StateFlow<ShadeScrimBounds?> = 52 placeholderRepository.notificationShadeScrimBounds.asStateFlow() 53 54 /** 55 * Whether the stack is expanding from GONE-with-HUN to SHADE 56 * 57 * TODO(b/296118689): implement this to match legacy QSController logic 58 */ 59 private val isExpandingFromHeadsUp: Flow<Boolean> = flowOf(false) 60 61 /** The rounding of the notification stack. */ 62 val shadeScrimRounding: Flow<ShadeScrimRounding> = 63 combine(shadeModeInteractor.shadeMode, isExpandingFromHeadsUp) { 64 shadeMode, 65 isExpandingFromHeadsUp -> 66 ShadeScrimRounding( 67 isTopRounded = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp), 68 isBottomRounded = shadeMode != ShadeMode.Single, 69 ) 70 } 71 .distinctUntilChanged() 72 73 /** The alpha of the Notification Stack for the brightness mirror */ 74 val alphaForBrightnessMirror: StateFlow<Float> = 75 placeholderRepository.alphaForBrightnessMirror.asStateFlow() 76 77 /** The alpha of the Notification Stack for lockscreen fade-in */ 78 val alphaForLockscreenFadeIn: StateFlow<Float> = 79 placeholderRepository.alphaForLockscreenFadeIn.asStateFlow() 80 81 /** The height of the keyguard's available space bounds */ 82 val constrainedAvailableSpace: StateFlow<Int> = 83 placeholderRepository.constrainedAvailableSpace.asStateFlow() 84 85 /** Scroll state of the notification shade. */ 86 val shadeScrollState: StateFlow<ShadeScrollState> = 87 placeholderRepository.shadeScrollState.asStateFlow() 88 89 /** 90 * The amount in px that the notification stack should scroll due to internal expansion. This 91 * should only happen when a notification expansion hits the bottom of the screen, so it is 92 * necessary to scroll up to keep expanding the notification. 93 */ 94 val syntheticScroll: Flow<Float> = viewHeightRepository.syntheticScroll.asStateFlow() 95 96 /** 97 * Whether the current touch gesture is overscroll. If true, it means the NSSL has already 98 * consumed part of the gesture. 99 */ 100 val isCurrentGestureOverscroll: Flow<Boolean> = 101 viewHeightRepository.isCurrentGestureOverscroll.asStateFlow() 102 103 /** Whether we should close any notification guts that are currently open. */ 104 val shouldCloseGuts: Flow<Boolean> = 105 combine( 106 sceneInteractor.isSceneContainerUserInputOngoing, 107 viewHeightRepository.isCurrentGestureInGuts, 108 ) { isUserInputOngoing, isCurrentGestureInGuts -> 109 isUserInputOngoing && !isCurrentGestureInGuts 110 } 111 112 /** Sets the alpha to apply to the NSSL for the brightness mirror */ 113 fun setAlphaForBrightnessMirror(alpha: Float) { 114 placeholderRepository.alphaForBrightnessMirror.value = alpha 115 } 116 117 /** Sets the alpha to apply to the NSSL for fade-in on lockscreen */ 118 fun setAlphaForLockscreenFadeIn(alpha: Float) { 119 placeholderRepository.alphaForLockscreenFadeIn.value = alpha 120 } 121 122 /** Sets the position of the notification stack in the current scene. */ 123 fun setNotificationShadeScrimBounds(bounds: ShadeScrimBounds?) { 124 checkValidBounds(bounds) 125 placeholderRepository.notificationShadeScrimBounds.value = bounds 126 } 127 128 /** 129 * Sends the bounds of the QuickSettings panel to the consumer set by [setQsPanelShapeConsumer]. 130 * 131 * Used to clip Notification content when the QuickSettings Overlay panel covers it. Sending 132 * `null` resets the negative shape clipping of the Notification Stack. 133 */ 134 fun sendQsPanelShape(shape: ShadeScrimShape?) { 135 checkValidBounds(shape?.bounds) 136 placeholderRepository.qsPanelShapeConsumer?.invoke(shape) 137 } 138 139 /** 140 * Sets a consumer to be notified when the QuickSettings Overlay panel changes size or position. 141 */ 142 fun setQsPanelShapeConsumer(consumer: ((ShadeScrimShape?) -> Unit)?) { 143 placeholderRepository.qsPanelShapeConsumer = consumer 144 } 145 146 /** Updates the current scroll state of the notification shade. */ 147 fun setScrollState(shadeScrollState: ShadeScrollState) { 148 placeholderRepository.shadeScrollState.value = shadeScrollState 149 } 150 151 /** Sets the amount (px) that the notification stack should scroll due to internal expansion. */ 152 fun setSyntheticScroll(delta: Float) { 153 viewHeightRepository.syntheticScroll.value = delta 154 } 155 156 /** Sends an [AccessibilityScrollEvent] to scroll the stack up or down. */ 157 fun sendAccessibilityScrollEvent(accessibilityScrollEvent: AccessibilityScrollEvent) { 158 placeholderRepository.accessibilityScrollEventConsumer?.accept(accessibilityScrollEvent) 159 } 160 161 /** Set a consumer for the [AccessibilityScrollEvent]s to be handled by the placeholder. */ 162 fun setAccessibilityScrollEventConsumer(consumer: Consumer<AccessibilityScrollEvent>?) { 163 placeholderRepository.accessibilityScrollEventConsumer = consumer 164 } 165 166 /** Sets whether the current touch gesture is overscroll. */ 167 fun setCurrentGestureOverscroll(isOverscroll: Boolean) { 168 viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll 169 } 170 171 fun setCurrentGestureInGuts(isInGuts: Boolean) { 172 viewHeightRepository.isCurrentGestureInGuts.value = isInGuts 173 } 174 175 fun setConstrainedAvailableSpace(height: Int) { 176 placeholderRepository.constrainedAvailableSpace.value = height 177 } 178 179 private fun checkValidBounds(bounds: ShadeScrimBounds?) { 180 check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } 181 } 182 } 183