• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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