-
-
Notifications
You must be signed in to change notification settings - Fork 665
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
[Android] Improve notification UI. #4842
Conversation
Thanks @AkashDhiman! This looks like a lot of good changes. Looking forward to having this implemented.
Yeah. This is an example of where to really do a great job, we need some of the data that the app has in Redux and that we don't currently have a great way to share for the notifications code to use. This is related to #4841, in that it's another motivation for wanting to change our model for storing data. I wouldn't want to let that block all the other progress this PR is about, though. For this PR, maybe just have it say something like "Group PM"? (And maybe plural if there's more of them? Though getting that right in a translatable way is a bit complicated.) From your first screenshot, it looks like if we do that, then the name of the first sender will come right after that, so when there are several PM threads they'll still be possible to tell apart. I like the switch to The fact that it simplifies The new
It looks like this becomes the notification ID we use with the Android SDK. What constraints does that ask for on how we pick and use the notification ID? I think grouping by
Those two reasons are what the Probably a step that would be helpful for using Relatedly, if we store the conversations in That's a bug we have today, so it's not exactly a regression -- but it becomes more conspicuous with these improvements to the UI that make it easier to get more information from it 😛 . So that's why the sketch in In the first commit:
This sounds great! I like deleting code 😄 There's a couple of things you can do to make this change easier for me to review and merge:
|
3250c0c
to
2825477
Compare
@gnprice Ready for a re-review 🙂, I recommend merging #4938 as this PR has it as its base. |
9e5edc4
to
6575678
Compare
pushed again changing 1 small thing in (5e8c31b: |
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 revisions! Inline comments below.
I really like the prospect of having the system effectively track the different messages we're showing notifications for, rather than having to do that ourselves. That is a nice simplification.
I'm guessing that's also the source of the challenge with maintaining the functionality of RemoveFcmMessage
, i.e. to remove a notification from the UI when you go and read the relevant message from some other client. In the MessagingStyle doc:
https://developer.android.com/reference/androidx/core/app/NotificationCompat.MessagingStyle
I see there's an addMessage
, but nothing with a name like removeMessage
.
Here's a couple of ideas -- but I'm curious also what you've looked at so far, and maybe a thread on #mobile-team is the best place for detailed discussion:
- There is
getMessages
, which returns aList
. TheList
interface hasList#remove
, but it's optional -- someList
implementations will throw on it. So possibly we could use that, though it'd be a bit shady because the API ofMessagingStyle
doesn't really guarantee it'll work (even if empirically it currently does.) - It seems like the sticky case here is when the user has read some but not all of the messages that are in a given UI notification (so, that are all in a given conversation.) Once they read all of the messages in the conversation, we can just clear that whole notification.
- I think it's actually a pretty fine UX if nothing in the notification UI changes until you read all of the relevant messages for a given conversation. So if we accept that, then that leaves the problem of how to know when that's happened.
- I think a pretty acceptable approximation would be: once you read the latest of the messages in a given conversation, we presume you've read all of them, and clear that conversation's UI-notification. That may help keep this implementation nice and simple.
- But I'd also be open to solutions that detect exactly when you've read all of them, instead of the one latest message, if you see a nice way to arrange that.
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" /> | ||
</intent-filter> | ||
</service> | ||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> |
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.
This looks like a conflict-resolution error; this line was recently deleted (moved to another file), and I'm guessing you didn't intend to add it back.
@@ -36,7 +35,9 @@ static void emitToken(@Nullable ReactContext reactContext) { | |||
*/ | |||
@ReactMethod | |||
public void getToken(Promise promise) { | |||
promise.resolve(FirebaseInstanceId.getInstance().getToken()); | |||
FirebaseInstanceId.getInstance().getInstanceId() |
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 those links to release notes -- those are very helpful!
Something related but not done in this commit is to update
firebase-messaging itself, which as of now stands at 22.0.0 .
Yeah. Reading through the entries in that release-notes page for "Instance ID" and "Cloud Messaging", it looks like there are some further changes we'll need to adapt to as we do that upgrade.
@@ -1,10 +1,12 @@ | |||
<resources> | |||
<string name="app_name">Zulip</string> | |||
<string name="notification_channel_name">Notifications</string> | |||
<string name="notification_channel_name">Messages</string> | |||
<string name="notification_channel_description">All the messages, received in Zulip.</string> |
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.
(This comment is a reminder to myself to wordsmith this message when I'm about to merge this change.)
// activeNotifications are not available in NotificationCompatManager | ||
// Hence we have to use instance of NotificationManager. | ||
val notificationManager = | ||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? |
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.
Huh, odd that it isn't available in NotificationCompatManager
. Do you know what the story is there?
I'd expect the compat library to cover all the functionality that's there in the latest Android SDK. So when a given method or property isn't there, my first thought is that that's a signal that there's some other preferred way to do the same thing.
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 know what the story is there?
Not sure about this, I couldn't find any reference for why this is the case.
On a side note it is also possible to get active notification via NotificationListenerService#getActiveNotifications so they could have nicely consolidated this for most of the android versions. This is how one can get activeNotifications
in API < 23.
if (activeStatusBarNotifications != null) { | ||
for (statusBarNotification in activeStatusBarNotifications) { | ||
val notification = statusBarNotification.notification | ||
if (notification.extras.getString("conversationKey") == conversationKey) { |
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.
Neat. Can we simplify this a bit more, by using a string "tag" instead of this extra?
That is, use StatusBarNotification#getTag
:
https://developer.android.com/reference/android/service/notification/StatusBarNotification#getTag()
and when we're posting the Notification
in the first place, use the form of notify
that takes a string "tag":
https://developer.android.com/reference/android/app/NotificationManager#notify(java.lang.String,%20int,%20android.app.Notification)
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.
doing it this way in the new revision thanks for that!
// return Uri.parse("${ContentResolver.SCHEME_ANDROID_RESOURCE}://${context.packageName}/${R.raw.zulip}") | ||
return Settings.System.DEFAULT_NOTIFICATION_URI | ||
updateNotification(context, fcmMessage) | ||
} // TODO handle case for RemoveFcmMessage |
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.
This is an important bit! 🙂 Definitely interested in seeing how we can handle this part too, with the new design.
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.
This is the only thing that remains to be implemented properly, I have made a "hacky" version but it has few problems, see 1b9d2e7 for detail.
} else if (ACTION_CLEAR.equals(intent.getAction())) { | ||
FCMPushNotifications.onClear(this, conversations); | ||
// TODO implement dismiss |
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.
We probably actually don't want this action anymore. Its functionality was to clear the whole contents of the one UI notification we currently show (by clearing out all the different notification messages we have in the conversations map); but with the new form of notification, the system provides better UI for dismissing notifications.
android/app/src/main/java/com/zulipmobile/notifications/FCMPushNotifications.kt
Show resolved
Hide resolved
fcmMessage.sender.fullName | ||
} | ||
} | ||
val groupKey = "${fcmMessage.identity?.realmUri.toString()};${fcmMessage.identity?.userId}" |
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.
Let's pull this line out as its own helper function, taking an Identity
and returning a String
to be used as the notification-group key.
The reason that's helpful is that it helps put a spotlight on the semantics this is supposed to have:
- It should be constant for a given identity.
- It should be distinct for distinct identities.
Then the one tweak I'd like to make to this definition, thanks to that framing, is: instead of ;
let's use |
.
I think using ;
does in fact meet those two criteria for the behavior, because it can't appear in the user ID (which is just going to be a decimal integer, because we're stringifying it from an int right here.) But it can perhaps appear in the realm URL -- it can certainly appear in URLs, in particular because it's a "URL code point" in the URL spec's terms -- which makes it a bit more subtle than it could be to see that it works. Using |
means it's easier to see, in more ways, that it behaves correctly, because |
is not a URL code point and can't appear in the URL (nor in the user ID.)
} | ||
} | ||
val groupKey = "${fcmMessage.identity?.realmUri.toString()};${fcmMessage.identity?.userId}" | ||
val conversationKey = "$groupKey;$title" |
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.
Similarly let's use |
here, just to keep it the same delimiter in both places.
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.
And let's pull this out as its own helper function too, this one taking the FcmMessage
. This helper's job is:
- The result should be constant for a given conversation.
- It should be distinct for distinct conversations.
That points at a few improvements we can make, relative to the solution of using the user-facing title as it's defined above:
- For type
Recipient.Pm
, usesender.id
instead ofsender.fullName
. That way (a) it doesn't get messed up if they change their name, (b) there's no possibility of the key getting confused because the delimiter character appeared in the data.- Before relying on
sender.id
, we should make it non-null, the same way asidentity
as discussed above; our notes say this also dates to server v1.8.0. - If we didn't reliably have
sender.id
, thensender.email
would be a next-best solution.
- Before relying on
- For
Recipient.GroupPm
,recipient.pmUsers
is perfect. Best to skip thegetString
, in fact, so this internal data structure's workings aren't complicated by interactions with a translated user-facing string. - For
Recipient.Stream
... ideally we'd use the stream ID instead of the stream name, but it looks like we don't currently have that in the data. For now, I guess we can settle for a TODO comment in the code saying to switch to stream ID. And for a delimiter, let's use the character\x00
, for the same reason as we do that inkeyFromNarrow
innarrow.js
:// '\x00' is the one character not allowed in Zulip stream names. // (See `check_stream_name` in zulip.git:zerver/lib/streams.py.) topic: (streamName, topic) => `topic:s:${streamName}\x00${topic}`,
- Oh also, looking at
keyFromNarrow
reminds me: for these conversation keys let's also have a different fixed prefix for each of the three types of recipient. That way, each of the three different ways we format a conversation key always has a distinct prefix, so it's easy to be sure that they'll never accidentally collide with each other.
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.
done, using \u0000
instead of \x00
, kotlin was not letting me use \x
, I hope that's 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.
Sure -- that should be another way (and I guess the only way, in Kotlin) of spelling the same thing.
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.
(One more comment on a bit I just noticed.)
val soundUri = getNotificationSoundUri(context) | ||
val audioAttr = AudioAttributes.Builder() | ||
.setUsage(AudioAttributes.USAGE_NOTIFICATION).build() | ||
builder.setSound(soundUri, audioAttr) |
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.
This is one chunk that I notice doesn't have a counterpart in the new code.
Looking at the API docs, I see that it has no effect on Android 8 O and up!
https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder#setSound(android.net.Uri)
So it wasn't doing us a lot of good. But: that's because we're supposed to be calling the corresponding method on the notification channel's builder instead:
https://developer.android.com/reference/androidx/core/app/NotificationChannelCompat.Builder#setSound(android.net.Uri,%20android.media.AudioAttributes)
So it seems like that's something we neglected to take care of when we started using a notification channel a few years ago. But seeing this code and deleting it gives us a nice reminder of how we should be doing it. Would you add a few lines to our createNotificationChannel
to fix that?
The very cleanest thing would be to make sure we continue to do it on the notification itself, for the sake of pre-Android 8 devices, as well as on the channel. But at this point those devices are well into the single-digit percent (they were 9.0% almost a year ago), so graceful degradation is okay, and I'd be happy with a version that only does it on the channel.
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 have added audioAttr
use in channel as defined previously for notification builder.
NotificationCompat.Builder
doesn't allow me to call setSound
with audioAttr
(there is no function with that signature in NotificationCompat.Builder
) so i don't think i can easily add it there.
// value, hence using "SupressLint". | ||
val channel = | ||
NotificationChannel(CHANNEL_ID, name, NotificationManagerCompat.IMPORTANCE_HIGH).apply { | ||
description = context.getString(R.string.notification_channel_description) |
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 -- can you post a screenshot of how this description appears in the UI? That would help me wordsmith the text for the description.
I'm not actually seeing anything that looks like this when I browse around the notification settings for other apps on my phone, which is making me suspect that my phone (Android 11 on a Pixel) just ignores the description. I also don't see anything that looks like it in the handy guide doc you linked:
https://developer.android.com/training/notify-user/channels
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.
Fascinating, thanks.
Do you see this on any other apps you happen to have lying around? I just looked at this part of the UI for several well-known apps -- including Facebook Messenger, which has an extensive set of notification categories, so they've clearly paid attention to this area of the API, and also including Android Messages, which ditto plus it's actually from the Android group at Google -- and I don't see a grayed-out message like that at all. There's the "Advanced" section, and then nothing after that. (Also, just to confirm: what Android version are you on?)
If other app makers, including the Google Android group itself, aren't using this feature, then we clearly don't need to feel obligated to do so, and I'm inclined to skip it. I think the category's name itself can convey all the same information -- perhaps we should rename it "All messages" to be clearer -- and the way the UI ends up presenting this description text feels pretty out-of-context and not likely to be helpful.
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 looked again at the settings UI where this name appears, and I think "Messages" works well as the name and "All messages" would be unnecessarily redundant.
A couple of quick comparison points:
- FB Messenger has "Chats and calls".
- Android Messages has "Incoming messages".
OK, I've merged 6/13 commits from the initial part of this PR! c2087fa android notif [nfc]: Remove I fixed up the conflict-resolution error mentioned at the top of my review today. I also dropped the channel description for now because I'm not sure how that will show up; see comment just above this. The only other change is just that I tweaked the summary lines to say "android notif". That slightly more specific context is helpful for interpreting summaries like "Use compat version for notification modules." -- aha, it's Android, so that's the Android compat library aka AndroidX -- and it's also helpful just for imagining what kind of code this is touching, because the Android-native code is fairly distinct in some ways from the JS code. Looking forward to the next revision, and to getting to merge the main changes here soon 😄 |
specifically: - sender.id - identity - realmUri Context at: - zulip#4842 (comment)
9de656f
to
1b9d2e7
Compare
Thanks for the revision! Two high-level comments on the "remove" case: // TODO figure out a way to cancel individual group notifications
// misbehaves with multiple realm, shows empty notification.
// This cancels all group Notification when no other notification
// is present.
if (Build.VERSION.SDK_INT >= 24) {
if (getActiveNotification(context)?.all { statusBarNotification -> statusBarNotification.isGroup } == true) {
NotificationManagerCompat.from(context).cancelAll()
}
} So this is scanning through all the existing notifications and checking whether it's the case that they're all… well, if this works then it's checking that they're all group summaries, I guess. (The docs actually say that To get the desired behavior, it seems like we want to do something pretty similar, except instead of checking whether there are any individual (i.e., non-group-summary) notifications at all, we want to check whether there are any of those for each group key. So for example we could do a scan through these and build up a for (fcmMessageId in fcmMessage.messageIds) {
val activeNotifications = getActiveNotification(context) ?: return
for (statusBarNotification in activeNotifications) {
val notification = statusBarNotification.notification
val messageId = notification.extras.getInt("zulipMessageId")
if (fcmMessageId == messageId) { We can make this linear instead of quadratic by turning val activeNotifications = getActiveNotification(context) ?: return
for (statusBarNotification in activeNotifications) {
val notification = statusBarNotification.notification
val messageId = notification.extras.getInt("zulipMessageId")
if (fcmMessage.messageIds.contains(messageId)) { |
specifically: - sender.id - identity - identity.realmUri Context at: - zulip#4842 (comment)
@gnprice did the required changes, I think the notifications are getting dismissed as expected now, you can review it again. |
// statusBarNotification.groupKey also contains package name | ||
// in the beginning and hence doesn't match the groupKey we | ||
// set in notification builder; therefore we use endsWith. | ||
if (statusBarNotification.groupKey.endsWith(groupKey)) { |
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 interesting.
Can we compute the exact groupKey
we expect to see here? Perhaps something like "com.zulipmobile" + extractGroupKey(fcmMessage.identity)
? (Except that hardcodes the package name, and we should instead use whatever the appropriate API is to refer to it.) (Also, whatever computation is needed should go in its own tiny helper function, probably right next to extractGroupKey
itself, so it can get a name and javadoc to help explain what it means.)
Then we could use ==
instead of endsWith
, which would be cleaner because it's easier to confidently reason about. With endsWith
, we have to worry about whether it's possible for one of our groupKey
values to just happen to end with another one, so that there'd be an accidental match here. It'd be possible to design the format of the group keys to rule that out, if we have to… but it's cleaner, if we can, to just not have that be a possible problem in the first place.
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.
using statusBarNotification.notification.group == groupKey
, the groupKey
in statusBarNotification
contains "ranking" which may also have a number concatenated at the beginning which I am not sure how to compute.
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.
Huh interesting. Well, glad that solution works.
private fun getActiveNotification(context: Context): Array<StatusBarNotification>? = | ||
(context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?)?.activeNotifications |
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.
Let's give this a plural name, getActiveNotifications
. That matches the underlying activeNotifications
property, as well as the fact that the return value is an array with potentially multiple notifications.
android/app/src/main/java/com/zulipmobile/notifications/FCMPushNotifications.kt
Outdated
Show resolved
Hide resolved
val statusBarNotifications = getActiveNotification(context) ?: return | ||
val groupKey = extractGroupKey(fcmMessage.identity) | ||
for (statusBarNotification in statusBarNotifications) { | ||
val notification = statusBarNotification.notification | ||
val messageId = notification.extras.getInt("zulipMessageId") | ||
if (fcmMessage.messageIds.contains(messageId)) { | ||
NotificationManagerCompat.from(context).cancel(statusBarNotification.tag, statusBarNotification.id) |
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 believe the key context for what this logic is doing is:
- Each
StatusBarNotification
corresponds to one Zulip conversation. - The
zulipMessageId
extra corresponds to the last Zulip-message ID in that conversation.
Is that right? If so, I think a couple of one-line comments would help orient the reader to what's meant to be going on here. For example… actually, better. Let's name the extra to help make that clear, like lastZulipMessageId
. Then something like:
val statusBarNotifications = getActiveNotification(context) ?: return | |
val groupKey = extractGroupKey(fcmMessage.identity) | |
for (statusBarNotification in statusBarNotifications) { | |
val notification = statusBarNotification.notification | |
val messageId = notification.extras.getInt("zulipMessageId") | |
if (fcmMessage.messageIds.contains(messageId)) { | |
NotificationManagerCompat.from(context).cancel(statusBarNotification.tag, statusBarNotification.id) | |
val statusBarNotifications = getActiveNotification(context) ?: return | |
val groupKey = extractGroupKey(fcmMessage.identity) | |
// Find any conversations we can cancel the notification for. | |
// The API doesn't lend itself to removing individual messages as | |
// they're read, so we wait until we're ready to remove the whole | |
// conversation's notification. | |
for (statusBarNotification in statusBarNotifications) { | |
// Each statusBarNotification represents one Zulip conversation. | |
val notification = statusBarNotification.notification | |
val lastMessageId = notification.extras.getInt("lastZulipMessageId") | |
if (fcmMessage.messageIds.contains(lastMessageId)) { | |
// The latest Zulip message in this conversation was read. | |
// That's our cue to cancel the notification for the conversation. | |
NotificationManagerCompat.from(context).cancel(statusBarNotification.tag, statusBarNotification.id) |
That's a one-line comment to mention the first point, and some more comments for the "why" of what we're doing with the conversation's last Zulip-message ID.
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 also, in that first comment a link to #4842 (review) would be helpful, to give a pointer back into the history of our reasoning.
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.
changed.
|
||
val builder = NotificationCompat.Builder(context, CHANNEL_ID) | ||
builder.apply { | ||
color = Color.rgb(100, 146, 254) |
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, yeah, good idea! That'd be a great change to make either before or after the commit where you add a new place we use the color.
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 @AkashDhiman for the revision! This is looking great. Comments below, and above because GitHub does that when I reply on a previous thread, and a few other comments further above in replies on other threads:
#4842 (comment)
#4842 (comment)
#4842 (comment)
#4842 (comment)
I merged #4967 a little while ago, and I think that should mean that when rebasing you'll get to drop this commit:
28dadc9 notif: Make some MessageFcmMessage properties Non Nullable.
because that took care of ensuring we always have those properties.
val audioAttr: AudioAttributes = AudioAttributes.Builder() | ||
.setUsage(AudioAttributes.USAGE_NOTIFICATION).build() |
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.
This initializer looks like it'll involve executing a bit of code; which I think means that'll happen when this JVM class is loaded, so basically at app startup. Generally it's best to avoid executing code at the top level like this, because it adds up across a codebase to make programs slow to start up. (Also because in some situations it can suffer races, though I don't think that's an issue here.)
Instead, this can be a little helper function instead of a constant. Or, if we're using it in just one place, it could go inside that function.
val messagingStyle = when (notification) { | ||
null -> NotificationCompat.MessagingStyle(selfUser) | ||
else -> NotificationCompat.MessagingStyle | ||
.extractMessagingStyleFromNotification(notification) | ||
?: NotificationCompat.MessagingStyle(selfUser) | ||
} |
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 fun Kotlin tip to simplify this sort of logic: ?.let
. Here:
val messagingStyle = notification?.let {
NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(it)
} ?: NotificationCompat.MessagingStyle(selfUser)
That lets you deduplicate the NotificationCompat.MessagingStyle(selfUser)
fallback case.
// here notifications are identified by TAG + notificationId | ||
// simply using notificationId is not sufficient and will | ||
// fail in case of different account in same realm. | ||
notify(groupKey, fcmMessage.identity.realmId, summaryNotification.build()) | ||
notify(conversationKey, notificationId, builder.build()) |
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, I'm thinking about this comment. It's true, we may have several notifications here with the same "id", the same second argument to notify
, because it's the Zulip message ID. And we've designed the "tag", the first argument, so that it on the other hand is unique.
But we don't actually need both the tag and the ID in order to uniquely identify a notification, do we? I think just the tag alone does it.
Is there a thing this comment is telling the reader to do? It reads like it's warning about a pitfall, where some other code we write needs to be careful not to do something wrong. But after studying it, I think the point is just as an explanation of why we use this three-argument form of notify
, with the tag arguments.
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.
Looking again at these notification "id"s, the second argument to each of these notify
calls:
Do we ever use the fact that the summaries have the realm ID as their notification ID? I think we don't; and I think if we did, that would probably be a bug, because realm IDs are per-server and in fact are highly likely to collide between any two servers other than Zulip Cloud (because they'll probably both be the only realm on the server.) If we don't, how about just using a constant ID, like 1? That would help make it clear that we're relying entirely on the tags.
Similarly, do we ever use the fact that the notification IDs for individual conversations vary, or that they're Zulip message IDs? I think we don't, in which case it'd similarly be good to just use a constant. It can even be the same constant -- we've designed these keys so they don't collide between the two types, either.
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.
using NOTIFICATION_ID, like the main branch here.
Sorry for the delay on this @gnprice, I have addressed the last review. 2 small things that remain from last time's review:
Between this revision and the previous one, The layout seems to have changed a bit, but I am not sure why, hence before merging I recommend we figure out the reason and try to move back to that layout, It has larger text for heading and a default group conversation icon. New Layout (Expanded Group Conversation) |
This commit allows the notification to automatically dismiss themselves on receiving a message of type `RemoveFcmMessage`. Caveat: * Whole of the conversation (PM, group PM, or stream > topic) is removed from the notif, in case it contains a message with message id that is provided by the RemoveFcmMessage payload. A better behavior is to remove only the specific messages provided in the payload.
Thanks! Glad that was a straightforward fix. (And here in chat is where you debugged it, for cross-reference.) This looks good; I'll merge. The remaining open items were:
Thanks again @AkashDhiman for all your work on this! |
OK, and those followups all filed (see backlinks above.) I think this thread is complete! |
There's hardly anything left in this file, after zulip#4842 made it unnecessary to maintain our own data structure of conversations because we can use the system's data structures of active notifications. Fold what little is left into the main file that uses it (indeed the only file that uses its one nontrivial item.) Also add some javadoc while we're at it.
There's hardly anything left in this file, after zulip#4842 made it unnecessary to maintain our own data structure of conversations because we can use the system's data structures of active notifications. Fold what little is left into the main file that uses it (indeed the only file that uses its one nontrivial item.) Also add some javadoc while we're at it.
There's hardly anything left in this file, after zulip#4842 made it unnecessary to maintain our own data structure of conversations because we can use the system's data structures of active notifications. Fold what little is left into the main file that uses it (indeed the only file that uses its one nontrivial item.) Also add some javadoc while we're at it.
There's hardly anything left in this file, after zulip#4842 made it unnecessary to maintain our own data structure of conversations because we can use the system's data structures of active notifications. Fold what little is left into the main file that uses it (indeed the only file that uses its one nontrivial item.) Also add some javadoc while we're at it.
There's hardly anything left in this file, after zulip#4842 made it unnecessary to maintain our own data structure of conversations because we can use the system's data structures of active notifications. Fold what little is left into the main file that uses it (indeed the only file that uses its one nontrivial item.) Also add some javadoc while we're at it.
Original discussion here: zulip#4842 (comment) zulip#4842 (comment)
There's hardly anything left in this file, after zulip#4842 made it unnecessary to maintain our own data structure of conversations because we can use the system's data structures of active notifications. Fold what little is left into the main file that uses it (indeed the only file that uses its one nontrivial item.) Also add some javadoc while we're at it.
Original discussion here: zulip#4842 (comment) zulip#4842 (comment)
There's hardly anything left in this file, after zulip#4842 made it unnecessary to maintain our own data structure of conversations because we can use the system's data structures of active notifications. Fold what little is left into the main file that uses it (indeed the only file that uses its one nontrivial item.) Also add some javadoc while we're at it.
Original discussion here: zulip#4842 (comment) zulip#4842 (comment)
There's hardly anything left in this file, after zulip#4842 made it unnecessary to maintain our own data structure of conversations because we can use the system's data structures of active notifications. Fold what little is left into the main file that uses it (indeed the only file that uses its one nontrivial item.) Also add some javadoc while we're at it.
Original discussion here: zulip#4842 (comment) zulip#4842 (comment)
There's hardly anything left in this file, after zulip#4842 made it unnecessary to maintain our own data structure of conversations because we can use the system's data structures of active notifications. Fold what little is left into the main file that uses it (indeed the only file that uses its one nontrivial item.) Also add some javadoc while we're at it.
The first 3 commits are updating some related code.
The next 4 commits are adding/removing some data structures to facilitate building up of this UI.
The last 2 commits are involved in changing the notification structure
Summary notification is not yet implemented correctly in this PR, and will show incorrect information (gives summary for all notifications on a summary notification for 1 realm) when API < 24 and user is logged in with multiple realms.
Some methods may no longer be needed, yet are not removed because they may prove useful in implementing other features.
This skips #4633 and fixes part of #2691, part of because summary notification is not yet implemented properly.
some screenshots of the same:
Summary notification:
Summary expanded
Individually Expanded
Message from Another realm
Future Work