Skip to content

Commit

Permalink
notif: Use the same notification ID for every notification
Browse files Browse the repository at this point in the history
Matching the `requestCode` is no longer necessary due to the removal
of `pakcage:flutter_local_notifications`, which required it.
  • Loading branch information
rajveermalviya committed Oct 25, 2024
1 parent fcc5bcb commit 08743be
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 29 deletions.
34 changes: 11 additions & 23 deletions lib/notifications/display.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:http/http.dart' as http;
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart' hide Notification;
Expand Down Expand Up @@ -170,9 +168,7 @@ class NotificationDisplayManager {
}).buildUrl();

await _androidHost.notify(
// TODO the notification ID can be constant, instead of matching requestCode
// (This is a legacy of `flutter_local_notifications`.)
id: notificationIdAsHashOf(conversationKey),
id: kNotificationId,
tag: conversationKey,
channelId: NotificationChannelManager.kChannelId,
groupKey: groupKey,
Expand Down Expand Up @@ -215,7 +211,7 @@ class NotificationDisplayManager {
);

await _androidHost.notify(
id: notificationIdAsHashOf(groupKey),
id: kNotificationId,
tag: groupKey,
channelId: NotificationChannelManager.kChannelId,
groupKey: groupKey,
Expand Down Expand Up @@ -300,11 +296,18 @@ class NotificationDisplayManager {
// Even though we enable the `autoCancel` flag for summary notification
// during creation, the summary notification doesn't get auto canceled if
// child notifications are canceled programatically as done above.
await _androidHost.cancel(
tag: groupKey, id: notificationIdAsHashOf(groupKey));
await _androidHost.cancel(tag: groupKey, id: kNotificationId);
}
}

/// The constant numeric "ID" we use for all non-test notifications,
/// along with unique tags.
///
/// Because we construct a unique string "tag" for each distinct
/// notification, and Android notifications are identified by the
/// pair (tag, ID), it's simplest to leave these numeric IDs all the same.
static const kNotificationId = 0x00C0FFEE;

/// A key we use in [Notification.extras] for the [Message.id] of the
/// latest Zulip message in the notification's conversation.
///
Expand All @@ -313,21 +316,6 @@ class NotificationDisplayManager {
@visibleForTesting
static const kExtraLastZulipMessageId = 'lastZulipMessageId';

/// A notification ID, derived as a hash of the given string key.
///
/// The result fits in 31 bits, the size of a nonnegative Java `int`,
/// so that it can be used as an Android notification ID. (It's possible
/// negative values would work too, which would add one bit.)
///
/// This is a cryptographic hash, meaning that collisions are about as
/// unlikely as one could hope for given the size of the hash.
@visibleForTesting
static int notificationIdAsHashOf(String key) {
final bytes = sha256.convert(utf8.encode(key)).bytes;
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16)
| ((bytes[3] & 0x7f) << 24);
}

static String _conversationKey(MessageFcmMessage data, String groupKey) {
final conversation = switch (data.recipient) {
FcmMessageChannelRecipient(:var streamId, :var topic) => 'stream:$streamId:$topic',
Expand Down
10 changes: 4 additions & 6 deletions test/notifications/display_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,6 @@ void main() {

final expectedTag = '${data.realmUrl}|${data.userId}|$expectedTagComponent';
final expectedGroupKey = '${data.realmUrl}|${data.userId}';
final expectedId =
NotificationDisplayManager.notificationIdAsHashOf(expectedTag);
const expectedPendingIntentFlags = PendingIntentFlag.immutable;
const expectedIntentFlags = IntentFlag.activityClearTop | IntentFlag.activityNewTask;
final expectedSelfUserKey = '${data.realmUrl}|${data.userId}';
Expand Down Expand Up @@ -256,7 +254,7 @@ void main() {
check(testBinding.androidNotificationHost.takeNotifyCalls())
.deepEquals(<Condition<Object?>>[
(it) => it.isA<AndroidNotificationHostApiNotifyCall>()
..id.equals(expectedId)
..id.equals(NotificationDisplayManager.kNotificationId)
..tag.equals(expectedTag)
..channelId.equals(NotificationChannelManager.kChannelId)
..contentTitle.isNull()
Expand Down Expand Up @@ -288,7 +286,7 @@ void main() {
..dataUrl.equals(expectedIntentDataUrl.toString())
..flags.equals(expectedIntentFlags))),
(it) => it.isA<AndroidNotificationHostApiNotifyCall>()
..id.equals(NotificationDisplayManager.notificationIdAsHashOf(expectedGroupKey))
..id.equals(NotificationDisplayManager.kNotificationId)
..tag.equals(expectedGroupKey)
..channelId.equals(NotificationChannelManager.kChannelId)
..contentTitle.isNull()
Expand Down Expand Up @@ -344,7 +342,7 @@ void main() {
final expectedGroupKey = '${data.realmUrl}|${data.userId}';
final expectedTag = '$expectedGroupKey|$tagComponent';
return (it) => it.isA<StatusBarNotification>()
..id.equals(NotificationDisplayManager.notificationIdAsHashOf(expectedTag))
..id.equals(NotificationDisplayManager.kNotificationId)
..notification.which((it) => it
..group.equals(expectedGroupKey)
..extras.deepEquals(<String, String>{
Expand All @@ -355,7 +353,7 @@ void main() {

Condition<Object?> conditionSummaryActiveNotif(String expectedGroupKey) {
return (it) => it.isA<StatusBarNotification>()
..id.equals(NotificationDisplayManager.notificationIdAsHashOf(expectedGroupKey))
..id.equals(NotificationDisplayManager.kNotificationId)
..notification.which((it) => it
..group.equals(expectedGroupKey)
..extras.isEmpty())
Expand Down

0 comments on commit 08743be

Please sign in to comment.