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