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