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 
17 package com.android.systemui.statusbar.notification.footer.ui.viewbinder
18 
19 import android.view.View
20 import androidx.lifecycle.lifecycleScope
21 import com.android.app.tracing.coroutines.launchTraced as launch
22 import com.android.internal.jank.InteractionJankMonitor
23 import com.android.systemui.lifecycle.repeatWhenAttached
24 import com.android.systemui.statusbar.notification.NotificationActivityStarter
25 import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent
26 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
27 import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter
28 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
29 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
30 import com.android.systemui.util.ui.isAnimating
31 import com.android.systemui.util.ui.stopAnimating
32 import com.android.systemui.util.ui.value
33 import kotlinx.coroutines.DisposableHandle
34 import kotlinx.coroutines.coroutineScope
35 
36 /** Binds a [FooterView] to its [view model][FooterViewModel]. */
37 object FooterViewBinder {
38     fun bindWhileAttached(
39         footer: FooterView,
40         viewModel: FooterViewModel,
41         clearAllNotifications: View.OnClickListener,
42         launchNotificationSettings: View.OnClickListener,
43         launchNotificationHistory: View.OnClickListener,
44         notificationActivityStarter: NotificationActivityStarter,
45     ): DisposableHandle {
46         return footer.repeatWhenAttached {
47             lifecycleScope.launch {
48                 bind(
49                     footer,
50                     viewModel,
51                     clearAllNotifications,
52                     launchNotificationSettings,
53                     launchNotificationHistory,
54                     notificationActivityStarter,
55                 )
56             }
57         }
58     }
59 
60     suspend fun bind(
61         footer: FooterView,
62         viewModel: FooterViewModel,
63         clearAllNotifications: View.OnClickListener,
64         launchNotificationSettings: View.OnClickListener,
65         launchNotificationHistory: View.OnClickListener,
66         notificationActivityStarter: NotificationActivityStarter,
67     ) = coroutineScope {
68         launch { bindClearAllButton(footer, viewModel, clearAllNotifications) }
69         if (!NotifRedesignFooter.isEnabled) {
70             launch {
71                 bindManageOrHistoryButton(
72                     footer,
73                     viewModel,
74                     launchNotificationSettings,
75                     launchNotificationHistory,
76                     notificationActivityStarter,
77                 )
78             }
79         } else {
80             launch { bindSettingsButton(footer, viewModel, notificationActivityStarter) }
81             launch { bindHistoryButton(footer, viewModel, notificationActivityStarter) }
82         }
83         launch { bindMessage(footer, viewModel) }
84     }
85 
86     private suspend fun bindClearAllButton(
87         footer: FooterView,
88         viewModel: FooterViewModel,
89         clearAllNotifications: View.OnClickListener,
90     ) = coroutineScope {
91         launch {
92             viewModel.clearAllButton.labelId.collect { textId ->
93                 footer.setClearAllButtonText(textId)
94             }
95         }
96 
97         launch {
98             viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
99                 footer.setClearAllButtonDescription(textId)
100             }
101         }
102 
103         launch {
104             viewModel.clearAllButton.isVisible.collect { isVisible ->
105                 if (isVisible.value) {
106                     footer.setClearAllButtonClickListener(clearAllNotifications)
107                 } else {
108                     // When the button isn't visible, it also shouldn't react to clicks. This is
109                     // necessary because when the clear all button is not visible, it's actually
110                     // just the alpha that becomes 0 so it can still be tapped.
111                     footer.setClearAllButtonClickListener(null)
112                 }
113 
114                 if (isVisible.isAnimating) {
115                     footer.setClearAllButtonVisible(isVisible.value, /* animate= */ true) { _ ->
116                         isVisible.stopAnimating()
117                     }
118                 } else {
119                     footer.setClearAllButtonVisible(isVisible.value, /* animate= */ false)
120                 }
121             }
122         }
123     }
124 
125     private suspend fun bindSettingsButton(
126         footer: FooterView,
127         viewModel: FooterViewModel,
128         notificationActivityStarter: NotificationActivityStarter,
129     ) = coroutineScope {
130         val settingsIntent =
131             SettingsIntent.forNotificationSettings(
132                 cujType = InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON
133             )
134         val onClickListener = { view: View ->
135             notificationActivityStarter.startSettingsIntent(view, settingsIntent)
136         }
137         footer.setSettingsButtonClickListener(onClickListener)
138 
139         launch {
140             // NOTE: This visibility change is never animated. We also don't need to do anything
141             // special about the onClickListener here, since we're changing the visibility to
142             // GONE so it won't be clickable anyway.
143             viewModel.settingsButtonVisible.collect { footer.setSettingsButtonVisible(it) }
144         }
145     }
146 
147     private suspend fun bindHistoryButton(
148         footer: FooterView,
149         viewModel: FooterViewModel,
150         notificationActivityStarter: NotificationActivityStarter,
151     ) = coroutineScope {
152         val settingsIntent =
153             SettingsIntent.forNotificationHistory(
154                 cujType = InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON
155             )
156         val onClickListener = { view: View ->
157             notificationActivityStarter.startSettingsIntent(view, settingsIntent)
158         }
159         footer.setHistoryButtonClickListener(onClickListener)
160 
161         launch {
162             // NOTE: This visibility change is never animated. We also don't need to do anything
163             // special about the onClickListener here, since we're changing the visibility to
164             // GONE so it won't be clickable anyway.
165             viewModel.historyButtonVisible.collect { footer.setHistoryButtonVisible(it) }
166         }
167     }
168 
169     private suspend fun bindManageOrHistoryButton(
170         footer: FooterView,
171         viewModel: FooterViewModel,
172         launchNotificationSettings: View.OnClickListener,
173         launchNotificationHistory: View.OnClickListener,
174         notificationActivityStarter: NotificationActivityStarter,
175     ) = coroutineScope {
176         launch {
177             if (ModesEmptyShadeFix.isEnabled) {
178                 viewModel.manageOrHistoryButtonClick.collect { settingsIntent ->
179                     val onClickListener = { view: View ->
180                         notificationActivityStarter.startSettingsIntent(view, settingsIntent)
181                     }
182                     footer.setManageButtonClickListener(onClickListener)
183                 }
184             } else {
185                 viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
186                     if (shouldLaunchHistory) {
187                         footer.setManageButtonClickListener(launchNotificationHistory)
188                     } else {
189                         footer.setManageButtonClickListener(launchNotificationSettings)
190                     }
191                 }
192             }
193         }
194 
195         launch {
196             viewModel.manageOrHistoryButton.labelId.collect { textId ->
197                 footer.setManageOrHistoryButtonText(textId)
198             }
199         }
200 
201         launch {
202             viewModel.manageOrHistoryButton.accessibilityDescriptionId.collect { textId ->
203                 footer.setManageOrHistoryButtonDescription(textId)
204             }
205         }
206 
207         launch {
208             viewModel.manageOrHistoryButton.isVisible.collect { isVisible ->
209                 // NOTE: This visibility change is never animated. We also don't need to do anything
210                 // special about the onClickListener here, since we're changing the visibility to
211                 // GONE so it won't be clickable anyway.
212                 footer.setManageOrHistoryButtonVisible(isVisible.value)
213             }
214         }
215     }
216 
217     private suspend fun bindMessage(footer: FooterView, viewModel: FooterViewModel) =
218         coroutineScope {
219             // Bind the resource IDs
220             footer.setMessageString(viewModel.message.messageId)
221             footer.setMessageIcon(viewModel.message.iconId)
222 
223             launch {
224                 viewModel.message.isVisible.collect { visible ->
225                     footer.setFooterLabelVisible(visible)
226                 }
227             }
228         }
229 }
230