diff --git a/lib/notifications/display.dart b/lib/notifications/display.dart index d89312501e..00d1425a72 100644 --- a/lib/notifications/display.dart +++ b/lib/notifications/display.dart @@ -89,7 +89,7 @@ class NotificationDisplayManager { } } - static void _onMessageFcmMessage(MessageFcmMessage data, Map dataJson) { + static Future _onMessageFcmMessage(MessageFcmMessage data, Map dataJson) async { assert(debugLog('notif message content: ${data.content}')); final zulipLocalizations = GlobalLocalizations.zulipLocalizations; final title = switch (data.recipient) { @@ -103,13 +103,16 @@ class NotificationDisplayManager { FcmMessageDmRecipient() => data.senderFullName, }; - final conversationKey = _conversationKey(data); - ZulipBinding.instance.androidNotificationHost.notify( + final groupKey = _groupKey(data); + final conversationKey = _conversationKey(data, groupKey); + + await ZulipBinding.instance.androidNotificationHost.notify( // TODO the notification ID can be constant, instead of matching requestCode // (This is a legacy of `flutter_local_notifications`.) id: notificationIdAsHashOf(conversationKey), tag: conversationKey, channelId: NotificationChannelManager.kChannelId, + groupKey: groupKey, contentTitle: title, contentText: data.content, @@ -139,6 +142,22 @@ class NotificationDisplayManager { // TODO this doesn't set the Intent flags we set in zulip-mobile; is that OK? // (This is a legacy of `flutter_local_notifications`.) ), + autoCancel: true, + ); + + await ZulipBinding.instance.androidNotificationHost.notify( + id: notificationIdAsHashOf(groupKey), + channelId: NotificationChannelManager.kChannelId, + tag: groupKey, + groupKey: groupKey, + isGroupSummary: true, + color: kZulipBrandColor.value, + // TODO vary notification icon for debug + smallIconResourceName: 'zulip_notification', // This name must appear in keep.xml too: https://github.com/zulip/zulip-flutter/issues/528 + inboxStyle: InboxStyle( + // TODO(#570) Show organization name, not URL + summaryText: data.realmUri.toString()), + autoCancel: true, ); } @@ -157,8 +176,7 @@ class NotificationDisplayManager { | ((bytes[3] & 0x7f) << 24); } - static String _conversationKey(MessageFcmMessage data) { - final groupKey = _groupKey(data); + static String _conversationKey(MessageFcmMessage data, String groupKey) { final conversation = switch (data.recipient) { FcmMessageStreamRecipient(:var streamId, :var topic) => 'stream:$streamId:$topic', FcmMessageDmRecipient(:var allRecipientIds) => 'dm:${allRecipientIds.join(',')}', diff --git a/test/notifications/display_test.dart b/test/notifications/display_test.dart index 0109abbbb6..02362ea736 100644 --- a/test/notifications/display_test.dart +++ b/test/notifications/display_test.dart @@ -111,11 +111,13 @@ void main() { required String expectedTagComponent, }) { final expectedTag = '${data.realmUri}|${data.userId}|$expectedTagComponent'; + final expectedGroupKey = '${data.realmUri}|${data.userId}'; final expectedId = NotificationDisplayManager.notificationIdAsHashOf(expectedTag); const expectedIntentFlags = PendingIntentFlag.immutable | PendingIntentFlag.updateCurrent; - check(testBinding.androidNotificationHost.takeNotifyCalls()).single + final calls = testBinding.androidNotificationHost.takeNotifyCalls(); + check(calls[0]) ..id.equals(expectedId) ..tag.equals(expectedTag) ..channelId.equals(NotificationChannelManager.kChannelId) @@ -124,11 +126,31 @@ void main() { ..color.equals(kZulipBrandColor.value) ..smallIconResourceName.equals('zulip_notification') ..extras.isNull() + ..groupKey.equals(expectedGroupKey) + ..isGroupSummary.isNull() + ..inboxStyle.isNull() + ..autoCancel.equals(true) ..contentIntent.which((it) => it.isNotNull() ..requestCode.equals(expectedId) ..flags.equals(expectedIntentFlags) ..intentPayload.equals(jsonEncode(data.toJson())) ); + check(calls[1]) + ..id.equals(NotificationDisplayManager.notificationIdAsHashOf(expectedGroupKey)) + ..tag.equals(expectedGroupKey) + ..channelId.equals(NotificationChannelManager.kChannelId) + ..contentTitle.isNull() + ..contentText.isNull() + ..color.equals(kZulipBrandColor.value) + ..smallIconResourceName.equals('zulip_notification') + ..extras.isNull() + ..groupKey.equals(expectedGroupKey) + ..isGroupSummary.equals(true) + ..inboxStyle.which((it) => it.isNotNull() + ..summaryText.equals(data.realmUri.toString()) + ) + ..autoCancel.equals(true) + ..contentIntent.isNull(); } Future checkNotifications(FakeAsync async, MessageFcmMessage data, {