Skip to content

Commit c665d6e

Browse files
rajveermalviyagnprice
authored andcommitted
notif: Create group summary notification
Make use of Group Summary Notifications to group notifications based on different realms and also display the respective group label (which is currently the realm URL). See: https://developer.android.com/develop/ui/views/notifications/group#group-summary This change is a port of implementation in zulip-mobile: https://github.com/zulip/zulip-mobile/blob/6d5d56d175644cd0cdf47f3cd30ffadf6756bbdc/android/app/src/main/java/com/zulipmobile/notifications/NotificationUiManager.kt#L299-L382 Fixes: #569 Fixes: #571
1 parent 499573c commit c665d6e

File tree

2 files changed

+68
-19
lines changed

2 files changed

+68
-19
lines changed

lib/notifications/display.dart

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class NotificationDisplayManager {
8989
}
9090
}
9191

92-
static void _onMessageFcmMessage(MessageFcmMessage data, Map<String, dynamic> dataJson) {
92+
static Future<void> _onMessageFcmMessage(MessageFcmMessage data, Map<String, dynamic> dataJson) async {
9393
assert(debugLog('notif message content: ${data.content}'));
9494
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
9595
final title = switch (data.recipient) {
@@ -103,13 +103,16 @@ class NotificationDisplayManager {
103103
FcmMessageDmRecipient() =>
104104
data.senderFullName,
105105
};
106-
final conversationKey = _conversationKey(data);
107-
ZulipBinding.instance.androidNotificationHost.notify(
106+
final groupKey = _groupKey(data);
107+
final conversationKey = _conversationKey(data, groupKey);
108+
109+
await ZulipBinding.instance.androidNotificationHost.notify(
108110
// TODO the notification ID can be constant, instead of matching requestCode
109111
// (This is a legacy of `flutter_local_notifications`.)
110112
id: notificationIdAsHashOf(conversationKey),
111113
tag: conversationKey,
112114
channelId: NotificationChannelManager.kChannelId,
115+
groupKey: groupKey,
113116

114117
contentTitle: title,
115118
contentText: data.content,
@@ -140,6 +143,21 @@ class NotificationDisplayManager {
140143
// (This is a legacy of `flutter_local_notifications`.)
141144
),
142145
);
146+
147+
await ZulipBinding.instance.androidNotificationHost.notify(
148+
id: notificationIdAsHashOf(groupKey),
149+
tag: groupKey,
150+
channelId: NotificationChannelManager.kChannelId,
151+
groupKey: groupKey,
152+
isGroupSummary: true,
153+
154+
color: kZulipBrandColor.value,
155+
// TODO vary notification icon for debug
156+
smallIconResourceName: 'zulip_notification', // This name must appear in keep.xml too: https://github.com/zulip/zulip-flutter/issues/528
157+
inboxStyle: InboxStyle(
158+
// TODO(#570) Show organization name, not URL
159+
summaryText: data.realmUri.toString()),
160+
);
143161
}
144162

145163
/// A notification ID, derived as a hash of the given string key.
@@ -157,8 +175,7 @@ class NotificationDisplayManager {
157175
| ((bytes[3] & 0x7f) << 24);
158176
}
159177

160-
static String _conversationKey(MessageFcmMessage data) {
161-
final groupKey = _groupKey(data);
178+
static String _conversationKey(MessageFcmMessage data, String groupKey) {
162179
final conversation = switch (data.recipient) {
163180
FcmMessageStreamRecipient(:var streamId, :var topic) => 'stream:$streamId:$topic',
164181
FcmMessageDmRecipient(:var allRecipientIds) => 'dm:${allRecipientIds.join(',')}',

test/notifications/display_test.dart

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,24 +111,48 @@ void main() {
111111
required String expectedTagComponent,
112112
}) {
113113
final expectedTag = '${data.realmUri}|${data.userId}|$expectedTagComponent';
114+
final expectedGroupKey = '${data.realmUri}|${data.userId}';
114115
final expectedId =
115116
NotificationDisplayManager.notificationIdAsHashOf(expectedTag);
116117
const expectedIntentFlags =
117118
PendingIntentFlag.immutable | PendingIntentFlag.updateCurrent;
118-
check(testBinding.androidNotificationHost.takeNotifyCalls()).single
119-
..id.equals(expectedId)
120-
..tag.equals(expectedTag)
121-
..channelId.equals(NotificationChannelManager.kChannelId)
122-
..contentTitle.equals(expectedTitle)
123-
..contentText.equals(data.content)
124-
..color.equals(kZulipBrandColor.value)
125-
..smallIconResourceName.equals('zulip_notification')
126-
..extras.isNull()
127-
..contentIntent.which((it) => it.isNotNull()
128-
..requestCode.equals(expectedId)
129-
..flags.equals(expectedIntentFlags)
130-
..intentPayload.equals(jsonEncode(data.toJson()))
131-
);
119+
120+
check(testBinding.androidNotificationHost.takeNotifyCalls())
121+
..length.equals(2)
122+
..containsInOrder(<Condition<AndroidNotificationHostApiNotifyCall>>[
123+
(it) => it
124+
..id.equals(expectedId)
125+
..tag.equals(expectedTag)
126+
..channelId.equals(NotificationChannelManager.kChannelId)
127+
..contentTitle.equals(expectedTitle)
128+
..contentText.equals(data.content)
129+
..color.equals(kZulipBrandColor.value)
130+
..smallIconResourceName.equals('zulip_notification')
131+
..extras.isNull()
132+
..groupKey.equals(expectedGroupKey)
133+
..isGroupSummary.isNull()
134+
..inboxStyle.isNull()
135+
..autoCancel.isNull()
136+
..contentIntent.which((it) => it.isNotNull()
137+
..requestCode.equals(expectedId)
138+
..flags.equals(expectedIntentFlags)
139+
..intentPayload.equals(jsonEncode(data.toJson()))),
140+
(it) => it
141+
..id.equals(NotificationDisplayManager.notificationIdAsHashOf(expectedGroupKey))
142+
..tag.equals(expectedGroupKey)
143+
..channelId.equals(NotificationChannelManager.kChannelId)
144+
..contentTitle.isNull()
145+
..contentText.isNull()
146+
..color.equals(kZulipBrandColor.value)
147+
..smallIconResourceName.equals('zulip_notification')
148+
..extras.isNull()
149+
..groupKey.equals(expectedGroupKey)
150+
..isGroupSummary.equals(true)
151+
..inboxStyle.which((it) => it.isNotNull()
152+
..summaryText.equals(data.realmUri.toString()))
153+
..autoCancel.isNull()
154+
..contentIntent.isNull()
155+
]);
132156
}
133157

134158
Future<void> checkNotifications(FakeAsync async, MessageFcmMessage data, {
@@ -369,12 +393,16 @@ extension AndroidNotificationChannelChecks on Subject<AndroidNotificationChannel
369393
extension on Subject<AndroidNotificationHostApiNotifyCall> {
370394
Subject<String?> get tag => has((x) => x.tag, 'tag');
371395
Subject<int> get id => has((x) => x.id, 'id');
396+
Subject<bool?> get autoCancel => has((x) => x.autoCancel, 'autoCancel');
372397
Subject<String> get channelId => has((x) => x.channelId, 'channelId');
373398
Subject<int?> get color => has((x) => x.color, 'color');
374399
Subject<PendingIntent?> get contentIntent => has((x) => x.contentIntent, 'contentIntent');
375400
Subject<String?> get contentText => has((x) => x.contentText, 'contentText');
376401
Subject<String?> get contentTitle => has((x) => x.contentTitle, 'contentTitle');
377402
Subject<Map<String?, String?>?> get extras => has((x) => x.extras, 'extras');
403+
Subject<String?> get groupKey => has((x) => x.groupKey, 'groupKey');
404+
Subject<InboxStyle?> get inboxStyle => has((x) => x.inboxStyle, 'inboxStyle');
405+
Subject<bool?> get isGroupSummary => has((x) => x.isGroupSummary, 'isGroupSummary');
378406
Subject<String?> get smallIconResourceName => has((x) => x.smallIconResourceName, 'smallIconResourceName');
379407
}
380408

@@ -383,3 +411,7 @@ extension on Subject<PendingIntent> {
383411
Subject<String> get intentPayload => has((x) => x.intentPayload, 'intentPayload');
384412
Subject<int> get flags => has((x) => x.flags, 'flags');
385413
}
414+
415+
extension on Subject<InboxStyle> {
416+
Subject<String> get summaryText => has((x) => x.summaryText, 'summaryText');
417+
}

0 commit comments

Comments
 (0)