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 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
17 
18 import android.content.res.Resources
19 import android.graphics.Rect
20 import com.android.systemui.dagger.qualifiers.Background
21 import com.android.systemui.dagger.qualifiers.Main
22 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
23 import com.android.systemui.plugins.DarkIconDispatcher
24 import com.android.systemui.res.R
25 import com.android.systemui.shade.domain.interactor.ShadeInteractor
26 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
27 import com.android.systemui.statusbar.notification.icon.domain.interactor.StatusBarNotificationIconsInteractor
28 import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
29 import com.android.systemui.util.kotlin.pairwise
30 import com.android.systemui.util.kotlin.sample
31 import com.android.systemui.util.ui.AnimatableEvent
32 import com.android.systemui.util.ui.AnimatedValue
33 import com.android.systemui.util.ui.toAnimatedValueFlow
34 import javax.inject.Inject
35 import kotlin.coroutines.CoroutineContext
36 import kotlinx.coroutines.flow.Flow
37 import kotlinx.coroutines.flow.combine
38 import kotlinx.coroutines.flow.conflate
39 import kotlinx.coroutines.flow.distinctUntilChanged
40 import kotlinx.coroutines.flow.filterNotNull
41 import kotlinx.coroutines.flow.flowOn
42 import kotlinx.coroutines.flow.map
43 
44 /** View-model for the row of notification icons displayed in the status bar, */
45 class NotificationIconContainerStatusBarViewModel
46 @Inject
47 constructor(
48     @Background bgContext: CoroutineContext,
49     darkIconInteractor: DarkIconInteractor,
50     iconsInteractor: StatusBarNotificationIconsInteractor,
51     headsUpIconInteractor: HeadsUpNotificationIconInteractor,
52     keyguardInteractor: KeyguardInteractor,
53     @Main resources: Resources,
54     shadeInteractor: ShadeInteractor,
55 ) {
56 
57     private val maxIcons = resources.getInteger(R.integer.max_notif_static_icons)
58 
59     /** Are changes to the icon container animated? */
60     val animationsEnabled: Flow<Boolean> =
61         combine(
62                 shadeInteractor.isShadeTouchable,
63                 keyguardInteractor.isKeyguardShowing,
64             ) { panelTouchesEnabled, isKeyguardShowing ->
65                 panelTouchesEnabled && !isKeyguardShowing
66             }
67             .flowOn(bgContext)
68             .conflate()
69             .distinctUntilChanged()
70 
71     /** The colors with which to display the notification icons. */
72     val iconColors: Flow<NotificationIconColorLookup> =
73         darkIconInteractor.darkState
74             .map { (areas: Collection<Rect>, tint: Int) ->
75                 NotificationIconColorLookup { viewBounds: Rect ->
76                     if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
77                         IconColorsImpl(tint, areas)
78                     } else {
79                         null
80                     }
81                 }
82             }
83             .flowOn(bgContext)
84             .conflate()
85             .distinctUntilChanged()
86 
87     /** [NotificationIconsViewData] indicating which icons to display in the view. */
88     val icons: Flow<NotificationIconsViewData> =
89         iconsInteractor.statusBarNotifs
90             .map { entries ->
91                 NotificationIconsViewData(
92                     visibleIcons = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) },
93                     iconLimit = maxIcons,
94                 )
95             }
96             .flowOn(bgContext)
97             .conflate()
98             .distinctUntilChanged()
99 
100     /** An Icon to show "isolated" in the IconContainer. */
101     val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
102         headsUpIconInteractor.isolatedNotification
103             .combine(icons) { isolatedNotif, iconsViewData ->
104                 isolatedNotif?.let {
105                     iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif }
106                 }
107             }
108             .distinctUntilChanged()
109             .flowOn(bgContext)
110             .conflate()
111             .distinctUntilChanged()
112             .pairwise(initialValue = null)
113             .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
114                 val animate =
115                     when {
116                         iconInfo?.notifKey == prev?.notifKey -> false
117                         iconInfo == null || prev == null -> shadeExpansion == 0f
118                         else -> false
119                     }
120                 AnimatableEvent(iconInfo, animate)
121             }
122             .toAnimatedValueFlow()
123 
124     /** Location to show an isolated icon, if there is one. */
125     val isolatedIconLocation: Flow<Rect> =
126         headsUpIconInteractor.isolatedIconLocation.filterNotNull().conflate().distinctUntilChanged()
127 
128     private class IconColorsImpl(
129         override val tint: Int,
130         private val areas: Collection<Rect>,
131     ) : NotificationIconColors {
132         override fun staticDrawableColor(viewBounds: Rect): Int {
133             return if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
134                 tint
135             } else {
136                 DarkIconDispatcher.DEFAULT_ICON_TINT
137             }
138         }
139     }
140 }
141