@@ -5,7 +5,7 @@ import 'package:http/http.dart' as http;
5
5
import 'package:collection/collection.dart' ;
6
6
import 'package:crypto/crypto.dart' ;
7
7
import 'package:flutter/foundation.dart' ;
8
- import 'package:flutter/widgets.dart' ;
8
+ import 'package:flutter/widgets.dart' hide Notification ;
9
9
import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Person;
10
10
11
11
import '../api/notifications.dart' ;
@@ -84,7 +84,7 @@ class NotificationDisplayManager {
84
84
static void onFcmMessage (FcmMessage data, Map <String , dynamic > dataJson) {
85
85
switch (data) {
86
86
case MessageFcmMessage (): _onMessageFcmMessage (data, dataJson);
87
- case RemoveFcmMessage (): break ; // TODO(#341) handle
87
+ case RemoveFcmMessage (): _onRemoveFcmMessage (data);
88
88
case UnexpectedFcmMessage (): break ; // TODO(log)
89
89
}
90
90
}
@@ -155,6 +155,10 @@ class NotificationDisplayManager {
155
155
156
156
messagingStyle: messagingStyle,
157
157
number: messagingStyle.messages.length,
158
+ extras: {
159
+ // Used to decide when a `RemoveFcmMessage` event should clear this notification.
160
+ kExtraZulipMessageId: data.zulipMessageId.toString (),
161
+ },
158
162
159
163
contentIntent: PendingIntent (
160
164
// TODO make intent URLs distinct, instead of requestCode
@@ -202,6 +206,67 @@ class NotificationDisplayManager {
202
206
);
203
207
}
204
208
209
+ static void _onRemoveFcmMessage (RemoveFcmMessage data) async {
210
+ assert (debugLog ('notif remove zulipMessageIds: ${data .zulipMessageIds }' ));
211
+
212
+ final groupKey = _groupKey (data);
213
+ final activeNotifications =
214
+ await ZulipBinding .instance.androidNotificationHost.getActiveNotifications (
215
+ desiredExtras: [kExtraZulipMessageId]);
216
+
217
+ var haveRemaining = false ;
218
+ for (final statusBarNotification in activeNotifications) {
219
+ if (statusBarNotification == null ) continue ; // TODO(pigeon) eliminate this case
220
+ final notification = statusBarNotification.notification;
221
+
222
+ // Sadly we don't get toString on Pigeon data classes: flutter#59027
223
+ assert (debugLog (' existing notif'
224
+ ' id: ${statusBarNotification .id }, tag: ${statusBarNotification .tag },'
225
+ ' notification: (group: ${notification .group }, extras: ${notification .extras }))' ));
226
+
227
+ // Don't act on notifications that are for other Zulip accounts/identities.
228
+ if (notification.group != groupKey) continue ;
229
+
230
+ // Don't act on the summary notification for the group.
231
+ if (statusBarNotification.tag == groupKey) continue ;
232
+
233
+ final lastMessageIdStr = notification.extras[kExtraZulipMessageId];
234
+ assert (lastMessageIdStr != null );
235
+ if (lastMessageIdStr == null ) continue ; // TODO(log)
236
+ final lastMessageId = int .parse (lastMessageIdStr, radix: 10 );
237
+ if (data.zulipMessageIds.contains (lastMessageId)) {
238
+ // The latest Zulip message in this conversation was read.
239
+ // That's our cue to cancel the notification for the conversation.
240
+ await ZulipBinding .instance.androidNotificationHost
241
+ .cancel (tag: statusBarNotification.tag, id: statusBarNotification.id);
242
+ assert (debugLog (' … notif cancelled.' ));
243
+ } else {
244
+ // This notification is for another conversation that's still unread.
245
+ // We won't cancel the summary notification.
246
+ haveRemaining = true ;
247
+ }
248
+ }
249
+
250
+ if (! haveRemaining) {
251
+ // The notification group is now empty; it had no notifications we didn't
252
+ // just cancel, except the summary notification. Cancel that one too.
253
+ //
254
+ // Even though we enable the `autoCancel` flag for summary notification
255
+ // during creation, the summary notification doesn't get auto canceled if
256
+ // child notifications are canceled programatically as done above.
257
+ await ZulipBinding .instance.androidNotificationHost
258
+ .cancel (tag: groupKey, id: notificationIdAsHashOf (groupKey));
259
+ }
260
+ }
261
+
262
+ /// The key for the message-id entry in [Notification.extras] metadata.
263
+ ///
264
+ /// Currently, it is used to store the message-id in the respective
265
+ /// notification which is later fetched to determine if a [RemoveFcmMessage]
266
+ /// event should clear that specific notification.
267
+ @visibleForTesting
268
+ static const kExtraZulipMessageId = 'zulipMessageId' ;
269
+
205
270
/// A notification ID, derived as a hash of the given string key.
206
271
///
207
272
/// The result fits in 31 bits, the size of a nonnegative Java `int` ,
0 commit comments