1 /* <lambda>null2 * Copyright (C) 2024 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 package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel 18 19 import android.content.Context 20 import android.icu.text.MessageFormat 21 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor 22 import com.android.systemui.dagger.qualifiers.Background 23 import com.android.systemui.dump.DumpManager 24 import com.android.systemui.modes.shared.ModesUi 25 import com.android.systemui.res.R 26 import com.android.systemui.shade.ShadeDisplayAware 27 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor 28 import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent 29 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor 30 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix 31 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterMessageViewModel 32 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor 33 import com.android.systemui.util.kotlin.FlowDumperImpl 34 import dagger.assisted.AssistedFactory 35 import dagger.assisted.AssistedInject 36 import java.util.Locale 37 import kotlinx.coroutines.CoroutineDispatcher 38 import kotlinx.coroutines.flow.Flow 39 import kotlinx.coroutines.flow.StateFlow 40 import kotlinx.coroutines.flow.combine 41 import kotlinx.coroutines.flow.distinctUntilChanged 42 import kotlinx.coroutines.flow.flowOf 43 import kotlinx.coroutines.flow.flowOn 44 import kotlinx.coroutines.flow.map 45 import kotlinx.coroutines.flow.onStart 46 47 /** 48 * ViewModel for the empty shade (aka the "No notifications" text shown when there are no 49 * notifications. 50 */ 51 class EmptyShadeViewModel 52 @AssistedInject 53 constructor( 54 @ShadeDisplayAware private val context: Context, 55 zenModeInteractor: ZenModeInteractor, 56 seenNotificationsInteractor: SeenNotificationsInteractor, 57 notificationSettingsInteractor: NotificationSettingsInteractor, 58 configurationInteractor: ConfigurationInteractor, 59 @Background bgDispatcher: CoroutineDispatcher, 60 dumpManager: DumpManager, 61 ) : FlowDumperImpl(dumpManager) { 62 val areNotificationsHiddenInShade: Flow<Boolean> by lazy { 63 if (ModesEmptyShadeFix.isEnabled) { 64 zenModeInteractor.areNotificationsHiddenInShade 65 .dumpWhileCollecting("areNotificationsHiddenInShade") 66 .flowOn(bgDispatcher) 67 } else { 68 zenModeInteractor.areNotificationsHiddenInShade.dumpWhileCollecting( 69 "areNotificationsHiddenInShade" 70 ) 71 } 72 } 73 74 val hasFilteredOutSeenNotifications: StateFlow<Boolean> = 75 seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue( 76 "hasFilteredOutSeenNotifications" 77 ) 78 79 private val primaryLocale by lazy { 80 configurationInteractor.configurationValues 81 .map { it.locales.get(0) ?: Locale.getDefault() } 82 .onStart { emit(Locale.getDefault()) } 83 .distinctUntilChanged() 84 } 85 86 val text: Flow<String> by lazy { 87 if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) { 88 flowOf(context.getString(R.string.empty_shade_text)) 89 } else { 90 // Note: Flag modes_ui_empty_shade includes two pieces: refactoring the empty shade to 91 // recommended architecture, and making it so it reacts to changes for the new Modes. 92 // The former does not depend on the modes flags being on, but the latter does. 93 if (ModesUi.isEnabled) { 94 combine(zenModeInteractor.modesHidingNotifications, primaryLocale) { 95 modes, 96 locale -> 97 // Create a string that is either "No notifications" if no modes are 98 // filtering them out, or something like "Notifications paused by SomeMode" 99 // otherwise. 100 val msgFormat = 101 MessageFormat( 102 context.getString(R.string.modes_suppressing_shade_text), 103 locale, 104 ) 105 val count = modes.count() 106 val args: MutableMap<String, Any> = HashMap() 107 args["count"] = count 108 if (count >= 1) { 109 args["mode"] = modes[0].name 110 } 111 msgFormat.format(args) 112 } 113 } else { 114 areNotificationsHiddenInShade.map { areNotificationsHiddenInShade -> 115 if (areNotificationsHiddenInShade) { 116 context.getString(R.string.dnd_suppressing_shade_text) 117 } else { 118 context.getString(R.string.empty_shade_text) 119 } 120 } 121 } 122 .flowOn(bgDispatcher) 123 } 124 } 125 126 val footer: FooterMessageViewModel by lazy { 127 ModesEmptyShadeFix.unsafeAssertInNewMode() 128 FooterMessageViewModel( 129 messageId = R.string.unlock_to_see_notif_text, 130 iconId = R.drawable.ic_friction_lock_closed, 131 isVisible = hasFilteredOutSeenNotifications, 132 ) 133 } 134 135 val onClick: Flow<SettingsIntent> by lazy { 136 ModesEmptyShadeFix.unsafeAssertInNewMode() 137 combine( 138 zenModeInteractor.modesHidingNotifications, 139 notificationSettingsInteractor.isNotificationHistoryEnabled, 140 ) { modes, isNotificationHistoryEnabled -> 141 if (modes.isNotEmpty()) { 142 if (modes.size == 1) { 143 SettingsIntent.forModeSettings(modes[0].id) 144 } else { 145 SettingsIntent.forModesSettings() 146 } 147 } else { 148 if (isNotificationHistoryEnabled) { 149 SettingsIntent.forNotificationHistory() 150 } else { 151 SettingsIntent.forNotificationSettings() 152 } 153 } 154 } 155 .flowOn(bgDispatcher) 156 } 157 158 @AssistedFactory 159 interface Factory { 160 fun create(): EmptyShadeViewModel 161 } 162 } 163