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.shade.domain.interactor.ShadeInteractor
22 import com.android.systemui.shade.shared.model.ShadeMode
23 import com.android.systemui.statusbar.notification.stack.data.repository.NotificationPlaceholderRepository
24 import com.android.systemui.statusbar.notification.stack.data.repository.NotificationViewHeightRepository
25 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
26 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
27 import javax.inject.Inject
28 import kotlinx.coroutines.flow.Flow
29 import kotlinx.coroutines.flow.StateFlow
30 import kotlinx.coroutines.flow.asStateFlow
31 import kotlinx.coroutines.flow.combine
32 import kotlinx.coroutines.flow.distinctUntilChanged
33 import kotlinx.coroutines.flow.flowOf
34 
35 /** An interactor which controls the appearance of the NSSL */
36 @SysUISingleton
37 class NotificationStackAppearanceInteractor
38 @Inject
39 constructor(
40     private val viewHeightRepository: NotificationViewHeightRepository,
41     private val placeholderRepository: NotificationPlaceholderRepository,
42     shadeInteractor: ShadeInteractor,
43 ) {
44     /** The bounds of the notification stack in the current scene. */
45     val shadeScrimBounds: StateFlow<ShadeScrimBounds?> =
46         placeholderRepository.shadeScrimBounds.asStateFlow()
47 
48     /**
49      * Whether the stack is expanding from GONE-with-HUN to SHADE
50      *
51      * TODO(b/296118689): implement this to match legacy QSController logic
52      */
53     private val isExpandingFromHeadsUp: Flow<Boolean> = flowOf(false)
54 
55     /** The rounding of the notification stack. */
56     val shadeScrimRounding: Flow<ShadeScrimRounding> =
57         combine(
58                 shadeInteractor.shadeMode,
59                 isExpandingFromHeadsUp,
60             ) { shadeMode, isExpandingFromHeadsUp ->
61                 ShadeScrimRounding(
62                     isTopRounded = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp),
63                     isBottomRounded = shadeMode != ShadeMode.Single,
64                 )
65             }
66             .distinctUntilChanged()
67 
68     /** The alpha of the Notification Stack for the brightness mirror */
69     val alphaForBrightnessMirror: StateFlow<Float> =
70         placeholderRepository.alphaForBrightnessMirror.asStateFlow()
71 
72     /** The height of the keyguard's available space bounds */
73     val constrainedAvailableSpace: StateFlow<Int> =
74         placeholderRepository.constrainedAvailableSpace.asStateFlow()
75 
76     /**
77      * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
78      * further.
79      */
80     val scrolledToTop: StateFlow<Boolean> = placeholderRepository.scrolledToTop.asStateFlow()
81 
82     /**
83      * The amount in px that the notification stack should scroll due to internal expansion. This
84      * should only happen when a notification expansion hits the bottom of the screen, so it is
85      * necessary to scroll up to keep expanding the notification.
86      */
87     val syntheticScroll: Flow<Float> = viewHeightRepository.syntheticScroll.asStateFlow()
88 
89     /**
90      * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
91      * consumed part of the gesture.
92      */
93     val isCurrentGestureOverscroll: Flow<Boolean> =
94         viewHeightRepository.isCurrentGestureOverscroll.asStateFlow()
95 
96     /** Sets the alpha to apply to the NSSL for the brightness mirror */
97     fun setAlphaForBrightnessMirror(alpha: Float) {
98         placeholderRepository.alphaForBrightnessMirror.value = alpha
99     }
100 
101     /** Sets the position of the notification stack in the current scene. */
102     fun setShadeScrimBounds(bounds: ShadeScrimBounds?) {
103         check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
104         placeholderRepository.shadeScrimBounds.value = bounds
105     }
106 
107     /** Sets whether the notification stack is scrolled to the top. */
108     fun setScrolledToTop(scrolledToTop: Boolean) {
109         placeholderRepository.scrolledToTop.value = scrolledToTop
110     }
111 
112     /** Sets the amount (px) that the notification stack should scroll due to internal expansion. */
113     fun setSyntheticScroll(delta: Float) {
114         viewHeightRepository.syntheticScroll.value = delta
115     }
116 
117     /** Sets whether the current touch gesture is overscroll. */
118     fun setCurrentGestureOverscroll(isOverscroll: Boolean) {
119         viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll
120     }
121 
122     fun setConstrainedAvailableSpace(height: Int) {
123         placeholderRepository.constrainedAvailableSpace.value = height
124     }
125 }
126