Skip to content

Commit 004951d

Browse files
committed
notif: Ignore notifications for logged out accounts
Fixes: #1264
1 parent a5af8d3 commit 004951d

File tree

4 files changed

+120
-13
lines changed

4 files changed

+120
-13
lines changed

lib/model/actions.dart

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22

3+
import '../notifications/display.dart';
34
import '../notifications/receive.dart';
45
import 'store.dart';
56

@@ -11,6 +12,8 @@ Future<void> logOutAccount(GlobalStore globalStore, int accountId) async {
1112
// Unawaited, to not block removing the account on this request.
1213
unawaited(unregisterToken(globalStore, accountId));
1314

15+
unawaited(NotificationDisplayManager.removeNotificationsForAccount(account.realmUrl, account.userId));
16+
1417
await globalStore.removeAccount(accountId);
1518
}
1619

lib/notifications/display.dart

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import 'dart:async';
22
import 'dart:io';
33

4-
import 'package:http/http.dart' as http;
54
import 'package:collection/collection.dart';
65
import 'package:flutter/foundation.dart';
76
import 'package:flutter/widgets.dart' hide Notification;
7+
import 'package:http/http.dart' as http;
88

99
import '../api/model/model.dart';
1010
import '../api/notifications.dart';
@@ -217,10 +217,12 @@ class NotificationChannelManager {
217217
/// Service for managing the notifications shown to the user.
218218
class NotificationDisplayManager {
219219
static Future<void> init() async {
220+
assert(defaultTargetPlatform == TargetPlatform.android, 'NotificationDisplayManager only supports Android');
220221
await NotificationChannelManager.ensureChannel();
221222
}
222223

223224
static void onFcmMessage(FcmMessage data, Map<String, dynamic> dataJson) {
225+
assert(defaultTargetPlatform == TargetPlatform.android, 'NotificationDisplayManager only supports Android');
224226
switch (data) {
225227
case MessageFcmMessage(): _onMessageFcmMessage(data, dataJson);
226228
case RemoveFcmMessage(): _onRemoveFcmMessage(data);
@@ -231,9 +233,16 @@ class NotificationDisplayManager {
231233
static Future<void> _onMessageFcmMessage(MessageFcmMessage data, Map<String, dynamic> dataJson) async {
232234
assert(debugLog('notif message content: ${data.content}'));
233235
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
234-
final groupKey = _groupKey(data);
236+
final groupKey = _groupKey(data.realmUrl, data.userId);
235237
final conversationKey = _conversationKey(data, groupKey);
236238

239+
final globalStore = await ZulipBinding.instance.getGlobalStore();
240+
final account = globalStore.accounts.firstWhereOrNull((account) =>
241+
account.realmUrl.origin == data.realmUrl.origin && account.userId == data.userId);
242+
if (account == null) {
243+
return;
244+
}
245+
237246
final oldMessagingStyle = await _androidHost
238247
.getActiveNotificationMessagingStyleByTag(conversationKey);
239248

@@ -365,7 +374,7 @@ class NotificationDisplayManager {
365374
// There may be a lot of messages mentioned here, across a lot of
366375
// conversations. But they'll all be for one account, so they'll
367376
// fall under one notification group.
368-
final groupKey = _groupKey(data);
377+
final groupKey = _groupKey(data.realmUrl, data.userId);
369378

370379
// Find any conversations we can cancel the notification for.
371380
// The API doesn't lend itself to removing individual messages as
@@ -445,10 +454,10 @@ class NotificationDisplayManager {
445454
return '$groupKey|$conversation';
446455
}
447456

448-
static String _groupKey(FcmMessageWithIdentity data) {
457+
static String _groupKey(Uri realmUrl, int userId) {
449458
// The realm URL can't contain a `|`, because `|` is not a URL code point:
450459
// https://url.spec.whatwg.org/#url-code-points
451-
return "${data.realmUrl}|${data.userId}";
460+
return "$realmUrl|$userId";
452461
}
453462

454463
static String _personKey(Uri realmUrl, int userId) => "$realmUrl|$userId";
@@ -464,6 +473,8 @@ class NotificationDisplayManager {
464473
required BuildContext context,
465474
required Uri url,
466475
}) {
476+
assert(defaultTargetPlatform == TargetPlatform.android);
477+
467478
final globalStore = GlobalStoreWidget.of(context);
468479

469480
assert(debugLog('got notif: url: $url'));
@@ -492,6 +503,7 @@ class NotificationDisplayManager {
492503
/// generated with [NotificationOpenPayload.buildUrl] while creating
493504
/// the notification.
494505
static Future<void> navigateForNotification(Uri url) async {
506+
assert(defaultTargetPlatform == TargetPlatform.android);
495507
assert(debugLog('opened notif: url: $url'));
496508

497509
NavigatorState navigator = await ZulipApp.navigator;
@@ -518,6 +530,18 @@ class NotificationDisplayManager {
518530
}
519531
return null;
520532
}
533+
534+
static Future<void> removeNotificationsForAccount(Uri realmUri, int userId) async {
535+
if (defaultTargetPlatform != TargetPlatform.android) return;
536+
537+
final groupKey = _groupKey(realmUri, userId);
538+
final activeNotifications = await _androidHost.getActiveNotifications(desiredExtras: [kExtraLastZulipMessageId]);
539+
for (final statusBarNotification in activeNotifications) {
540+
if (statusBarNotification.notification.group == groupKey) {
541+
await _androidHost.cancel(tag: statusBarNotification.tag, id: statusBarNotification.id);
542+
}
543+
}
544+
}
521545
}
522546

523547
/// The information contained in 'zulip://notification/…' internal

test/model/actions_test.dart

+8
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ void main() {
9292
check(connection.isOpen).isFalse();
9393
check(newConnection.isOpen).isTrue(); // still busy with unregister-token
9494

95+
// Check that notifications were removed
96+
final activeNotifications = await testBinding.androidNotificationHost.getActiveNotifications(desiredExtras: []);
97+
check(activeNotifications).isEmpty();
98+
9599
async.elapse(unregisterDelay - TestGlobalStore.removeAccountDuration);
96100
check(newConnection.isOpen).isFalse();
97101
}));
@@ -118,6 +122,10 @@ void main() {
118122
check(connection.isOpen).isFalse();
119123
check(newConnection.isOpen).isTrue(); // for the unregister-token request
120124

125+
// Check that notifications were removed
126+
final activeNotifications = await testBinding.androidNotificationHost.getActiveNotifications(desiredExtras: []);
127+
check(activeNotifications).isEmpty();
128+
121129
async.elapse(unregisterDelay - TestGlobalStore.removeAccountDuration);
122130
check(newConnection.isOpen).isFalse();
123131
}));

0 commit comments

Comments
 (0)