-
Notifications
You must be signed in to change notification settings - Fork 306
notif: Create summary notification #703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
eac15b4
to
4eb4ff8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This looks good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to add a small description for each of your commits. It's much easier for a review to get a gist of what's happening in each commit that way, and it's a nice to have feature of any code base.
test/notifications/display_test.dart
Outdated
@@ -166,7 +188,7 @@ void main() { | |||
final stream = eg.stream(); | |||
final message = eg.streamMessage(stream: stream); | |||
await checkNotifications(async, messageFcmMessage(message, streamName: null), | |||
expectedTitle: '(unknown stream) > ${message.subject}', | |||
expectedTitle: '#(unknown stream) > ${message.subject}', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to extract this out to the other commit on stream renaming? I'm guessing it isn't necessary for this commit to work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sumanthvrao Sorry but I am not sure what's requested here, this change is already in a separate commit and it is the first one in the sequence. Would you want this commit to be after the main commit?
Also this change is fixing #572 which is just adding a #
before stream names, the streams
-> channel
renaming is being done in #707 which includes changes for these notification strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, apologies if I am misunderstanding here, but how are the two changes in your summary notif commit
that add a # before stream name different from your first commit that does the same (for 2 other strings in different part of the codebase)? I was suggesting you merge the former into the first commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sorry for the misunderstanding, this should definitely belong in the first commit.
@@ -94,9 +94,9 @@ class NotificationDisplayManager { | |||
final zulipLocalizations = GlobalLocalizations.zulipLocalizations; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A general rule of thumb would be if this is an unrelated commit (e.g., its a nice to have commit, but isn't dependent on the later ones), you could move it earlier in the commit sequence?
@@ -157,7 +179,7 @@ void main() { | |||
final stream = eg.stream(); | |||
final message = eg.streamMessage(stream: stream); | |||
await checkNotifications(async, messageFcmMessage(message, streamName: stream.name), | |||
expectedTitle: '${stream.name} > ${message.subject}', | |||
expectedTitle: '#${stream.name} > ${message.subject}', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract out this one as well?
4eb4ff8
to
06b84ea
Compare
Thanks for the review @sumanthvrao! |
06b84ea
to
6830e57
Compare
Thanks @rajveermalviya! Could you please rebase this past the just-merged #730, which upgraded Pigeon? |
1dae196
to
3ea7cb5
Compare
@chrisbobbe rebased to main, PTAL :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Small comments below. Greg may have more detailed feedback on the code; he has more experience with it (and also with the UX of Android notifications).
I don't see the implementation for #571 by reading the code, but it does look like that issue is fixed. I noticed that your video shows the time ("1m") in the summary notification. I guess the platform just takes care of adding the time, so we don't have to do anything special to make it appear?
test/notifications/display_test.dart
Outdated
Subject<String?> get contentTitle => has((x) => x.contentTitle, 'contentTitle'); | ||
Subject<Map<String?, String?>?> get extras => has((x) => x.extras, 'extras'); | ||
Subject<String?> get smallIconResourceName => has((x) => x.smallIconResourceName, 'smallIconResourceName'); | ||
Subject<String?> get groupKey => has((x) => x.groupKey, 'groupKey'); | ||
Subject<bool?> get isGroupSummary => has((x) => x.isGroupSummary, 'isGroupSummary'); | ||
Subject<InboxStyle?> get inboxStyle => has((x) => x.inboxStyle, 'inboxStyle'); | ||
Subject<bool?> get autoCancel => has((x) => x.autoCancel, 'autoCancel'); | ||
} | ||
|
||
extension on Subject<PendingIntent> { | ||
Subject<int> get requestCode => has((x) => x.requestCode, 'requestCode'); | ||
Subject<String> get intentPayload => has((x) => x.intentPayload, 'intentPayload'); | ||
Subject<int> get flags => has((x) => x.flags, 'flags'); | ||
} | ||
|
||
extension on Subject<InboxStyle> { | ||
Subject<String> get summaryText => has((x) => x.summaryText, 'summaryText'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
notif: Add support group summary notifications to Pigeon bindings
The analyzer flags these as unused at this commit:
Running analyze...
Analyzing zulip-flutter...
info • The declaration 'groupKey' isn't referenced • test/notifications/display_test.dart:379:24 •
unused_element
info • The declaration 'isGroupSummary' isn't referenced • test/notifications/display_test.dart:380:22 •
unused_element
info • The declaration 'inboxStyle' isn't referenced • test/notifications/display_test.dart:381:28 •
unused_element
info • The declaration 'autoCancel' isn't referenced • test/notifications/display_test.dart:382:22 •
unused_element
info • The declaration 'summaryText' isn't referenced • test/notifications/display_test.dart:392:23 •
unused_element
5 issues found. (ran in 6.7s)
So let's either suppress this output until they get used, or introduce them in the same commit(s) that need them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A command I find helpful for running our automated checks on all the commits for a PR: git rebase --exec 'tools/check --diff @~'
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved their introduction to the later (main) commit: 14d52e4
inboxStyle: InboxStyle( | ||
// TODO(#570) Show organization name, not URL | ||
summaryText: data.realmUri.toString()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tldr: No, #128 will be fixed by #718
What #128 is talking about is actually called "MessagingStyle" notifications on Android, and here's how zulip-mobile implements it.
Whereas the usage here of InboxStyle
is just because it's a clunky API to create a "special" type of notification called Group summary notification: https://developer.android.com/develop/ui/views/notifications/group#group-summary
And preconditions for that "special" notification are (as mentioned in the above docs):
- recommended to be
InboxStyle
, with a description (we usesummaryText
). - distinct
groupKey
s for different groups, the summary notif and other notifs in the corresponding group should use samegroupKey
. isGroupSummary = true
for the summary notif.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also updated (main) commit description with the doc link, even though it was already present in the previous commit.
@@ -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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
notif: Create summary notification
I see that zulip-mobile also does this (setAutoCancel(true)
). Is this change coherent with the rest of what this commit is doing to create a summary notification? Or could it stand separately as its own commit? The answer will help me understand the user-facing changes that we're making, and how they're related.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this to a separate commit — it's not really needed for Group summary notifications, but was added just to make the UX better, and match the zulip-mobile implementation.
3ea7cb5
to
2b737db
Compare
Thanks for the review @chrisbobbe, addressed your comments in the latest revision. |
Yes, and the time it shows is actually when the notification was (received via FCM and) created, not the actual time of the message — these may defer if FCM delays the message delivery for various reasons. There is an API to set a custom timestamp using |
Cool, thanks! This revision LGTM; over to Greg for his review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @rajveermalviya, and thanks @sumanthvrao and @chrisbobbe for the reviews!
Generally this all looks great — just a few small comments below.
lib/notifications/display.dart
Outdated
await ZulipBinding.instance.androidNotificationHost.notify( | ||
id: notificationIdAsHashOf(groupKey), | ||
channelId: NotificationChannelManager.kChannelId, | ||
tag: groupKey, | ||
groupKey: groupKey, | ||
isGroupSummary: true, | ||
color: kZulipBrandColor.value, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: keep the ordering of these the same as the other call above:
await ZulipBinding.instance.androidNotificationHost.notify( | |
id: notificationIdAsHashOf(groupKey), | |
channelId: NotificationChannelManager.kChannelId, | |
tag: groupKey, | |
groupKey: groupKey, | |
isGroupSummary: true, | |
color: kZulipBrandColor.value, | |
await ZulipBinding.instance.androidNotificationHost.notify( | |
id: notificationIdAsHashOf(groupKey), | |
tag: groupKey, | |
channelId: NotificationChannelManager.kChannelId, | |
groupKey: groupKey, | |
isGroupSummary: true, | |
color: kZulipBrandColor.value, |
That's helpful especially because "id" and "tag" and "groupKey" inherently sound so similar, so it's helpful to have them listed in the same order when comparing the two calls.
lib/notifications/display.dart
Outdated
inboxStyle: InboxStyle( | ||
// TODO(#570) Show organization name, not URL | ||
summaryText: data.realmUri.toString()), | ||
autoCancel: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here on the summary notification, the corresponding code in zulip-mobile says (thanks for the handy link!):
// TODO Does this do something useful? There isn't a way to open these summary notifs.
setAutoCancel(true)
What if you leave this line out — does any behavior change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be no difference in behavior with it added or not, things verified in both conditions:
- Tapping on summary header, which does nothing; unless clicked on the collapse icon — which toggles between collapsed state.
- Tapping on child notif, opens the app and routes to the correct message, also clears that specific notif from the panel/statusbar (only if child notif had
setAutoCancel(true)
). - Keep opening (which also clears them) child notifs, and made sure summary notif automatically dismisses when there are no child notifs anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From Stackoverflow, it seems like the summary notification may linger if child notifs are removed programmatically and setAutoCancel(true)
on summary notif is not set. So, it may be a problem when #341 is implemented, but this is just a speculation though, will have to test and see there's really a difference while implementing #341.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, thanks for the investigation.
That issue where the summary lingers reminds me of a bug we briefly had in zulip-mobile:
zulip/zulip-mobile#5119
Looks like that didn't involve a lack of setAutoCancel(true)
, though — we had that on the summary notification from the beginning (in zulip/zulip-mobile@4229325).
Given that this logically shouldn't do anything (based on the reasoning in that TODO comment), and that we've also tested empirically and can't find it doing anything, let's leave it out. We can always add it back if we later discover after #341 that it's needed as a hack.
In any case, we definitely should not include the line while leaving out the TODO comment that was attached to it. That comment is useful information, which we shouldn't discard.
test/notifications/display_test.dart
Outdated
..containsInOrder([ | ||
(Subject<AndroidNotificationHostApiNotifyCall> it) => it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, can also put the type on the list:
..containsInOrder([ | |
(Subject<AndroidNotificationHostApiNotifyCall> it) => it | |
..containsInOrder(<Condition<AndroidNotificationHostApiNotifyCall>>[ | |
(it) => it |
When there's a longer list of shorter conditions, that way feels a lot better. In this case, either way is fine.
test/notifications/display_test.dart
Outdated
..length.equals(2) | ||
..containsInOrder([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
..length.equals(2) | |
..containsInOrder([ | |
.deepEquals([ |
That'd be equivalent, right? (Unless there's a subtlety I'm missing.)
test/notifications/display_test.dart
Outdated
..summaryText.equals(data.realmUri.toString()) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: similar to contentIntent
above:
..summaryText.equals(data.realmUri.toString()) | |
) | |
..summaryText.equals(data.realmUri.toString())) |
pigeon/notifications.dart
Outdated
String? smallIconResourceName, | ||
String? groupKey, | ||
bool? isGroupSummary, | ||
InboxStyle? inboxStyle, | ||
bool? autoCancel, | ||
// NotificationCompat.Builder has lots more methods; add as needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For adding parameters here, let's keep the builder parameters alphabetized — that helps make it easy to compare to the upstream docs at
https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder
because the list is so long.
I'll push a small NFC commit (→ 398749f) that just adds some comments here about that. Then the implementations of the class, and the list in AndroidNotificationHostApiNotifyCall
, should follow along with the same order. The call sites in lib/notifications/display.dart
can go for a logical order instead, though.
2b737db
to
e2421b8
Compare
e2421b8
to
1d0e040
Compare
Thanks for the review @gnprice! Pushed a new revision. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the revision! Just a couple of small comments.
smallIconResourceName?.let { setSmallIcon(context.resources.getIdentifier( | ||
it, "drawable", context.packageName)) } | ||
groupKey?.let { setGroup(it) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: match here the ordering of the parameters above (following up on #703 (comment) )
test/notifications/display_test.dart
Outdated
.deepEquals(<Condition<Object?>>[ | ||
(it) => it.isA<AndroidNotificationHostApiNotifyCall>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this isA
check seems unhelpful. It's statically a fact that each of these items is an AndroidNotificationHostApiNotifyCall
, so we shouldn't have to repeat that.
How about the version suggested at #703 (comment) ? Or the version that was in the previous revision — as that comment said, either of those is fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is if it's deepEquals(<Condition<AndroidNotificationHostApiNotifyCall>>[])
it doesn't recognize that the child is a Condition
, instead it goes on with comparing AndroidNotificationHostApiNotifyCall
with Condition<AndroidNotificationHostApiNotifyCall>
which obviously fails. This is unlike containsInOrder
which correctly checks for the generic type Condition<T>
.
So, that's why the use of <Condition<Object?>>
here and then asserting AndroidNotificationHostApiNotifyCall
in the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. OK, in that case let's stick with the ..length
/..containsInOrder
version. It's one line longer, but that's worth it in order to make the types make sense, given that the isA
here doesn't logically belong here as part of what the test is checking (because it'll statically always be true).
I did wonder if there was a subtlety I was missing 🙂 in why that version wasn't equivalent to deepEquals
: #703 (comment) — this definitely counts as such a subtlety.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general if there's something that comes up when you're acting on a review comment and it seems like there's an unexpected obstacle or things go in a different direction, please flag that proactively in the thread (either in reply to the previous comment, or as a new GitHub code-review comment), whether or not you end up going ahead and implementing the new direction.
That's one of the things that helps keep code reviews moving forward smoothly, because it saves the reviewer from having to guess about why your revision of that code came out the way it did.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sure, for this one specifically though (isA
), I'd looked for any existing use of deepEquals
in the repo after your initial feedback in #703 (comment) and had found this. That gave me the impression that it was an OK change to introduce. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted back to ..length
/..containsInOrder
in latest revision: https://github.com/zulip/zulip-flutter/compare/34fc3a81bd0f23d966819de7f9526d3056a63dab..0081b5cafa03915c8cd1e83300b06644a02a2d25
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. Yeah, that's a place where isA
is needed anyway, because statically it's a list of Route<dynamic>
but we want this element to specifically be a WidgetRoute
.
The previous comment that this really goes in a different direction from is the one about where in these checks to put the AndroidNotificationHostApiNotifyCall
type: #703 (comment)
So it turned out that the deepEquals
suggestion was incompatible with that comment. But a contradiction between different review comments I make is the kind of thing I'd very much like to learn about explicitly.
lib/notifications/display.dart
Outdated
inboxStyle: InboxStyle( | ||
// TODO(#570) Show organization name, not URL | ||
summaryText: data.realmUri.toString()), | ||
autoCancel: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, thanks for the investigation.
That issue where the summary lingers reminds me of a bug we briefly had in zulip-mobile:
zulip/zulip-mobile#5119
Looks like that didn't involve a lack of setAutoCancel(true)
, though — we had that on the summary notification from the beginning (in zulip/zulip-mobile@4229325).
Given that this logically shouldn't do anything (based on the reasoning in that TODO comment), and that we've also tested empirically and can't find it doing anything, let's leave it out. We can always add it back if we later discover after #341 that it's needed as a hack.
In any case, we definitely should not include the line while leaving out the TODO comment that was attached to it. That comment is useful information, which we shouldn't discard.
58f1d9d
to
34fc3a8
Compare
Thanks @gnprice for the review! Just pushed the latest revision. |
34fc3a8
to
0081b5c
Compare
This adds support for creating a group summary notification: https://developer.android.com/develop/ui/views/notifications/group#group-summary
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: zulip#569 Fixes: zulip#571
Set autoCancel flag for notifications, ensuring they automatically disappear from the panel when tapped. See: https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder#setAutoCancel(boolean) Also, zulip-mobile does this already: https://github.com/zulip/zulip-mobile/blob/e352f563ecf2fa9b09b688d5a65b6bc89b0358bc/android/app/src/main/java/com/zulipmobile/notifications/NotificationUiManager.kt#L353
OK, looks great — merging! Thanks @rajveermalviya for building this, and thanks again @sumanthvrao and @chrisbobbe for the reviews. Looking forward to the improved notifications 😄 |
0081b5c
to
365f9da
Compare
notif-test.mp4
Fixes: #572
Fixes: #569
Fixes: #571