-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Handle push notifications for DMs #3895
Conversation
Your Render PR Server URL is https://social-app-pr-3895.onrender.com. Follow its progress at https://dashboard.render.com/web/srv-cot5lhg21fec739ehsc0. |
|
prep merge move `useNotificationsListener` into shell progress better structure only show messages notifications while using app if it is the current account progress only emit on native current chat emitter only show alerts for the current chat type add logs setup handlers
ead83b0
to
417de2b
Compare
setTimeout(() => { | ||
// @ts-expect-error types are weird here | ||
navigation.navigate('MessagesTab', { | ||
screen: 'MessagesConversation', | ||
params: { | ||
conversation: payload.convoId, | ||
}, | ||
}) | ||
}, 500) |
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 setTimeout
s for .navigate
in this code are not "necessary", but they have a better feel then the very immediate navigation.
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.
Another thing that I'd like to look at here is handling adding the Messages
screen onto this stack if it doesn't exist already. The react nav documentation is a bit unclear on how to do this, so maybe I can find an example elsewhere in the codebase.
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.
Yeah having Messages as the immediate screen when pressing "back" would be awesome
Notifications.setNotificationHandler({ | ||
handleNotification: async e => { | ||
if (e.request.trigger.type !== 'push') return DEFAULT_HANDLER_OPTIONS | ||
|
||
logger.debug( | ||
'Notifications: received', | ||
{e}, | ||
logger.DebugContext.notifications, | ||
) | ||
|
||
const payload = e.request.trigger.payload as NotificationRecord | ||
if ( | ||
payload.reason === 'chat-message' && | ||
payload.recipientDid === currentAccount?.did && | ||
currentConvoId !== payload.convoId | ||
) { | ||
return { | ||
shouldShowAlert: true, | ||
shouldPlaySound: false, | ||
shouldSetBadge: false, | ||
} | ||
} | ||
|
||
// Any notification other than a chat message should invalidate the unread page | ||
invalidateCachedUnreadPage() | ||
return DEFAULT_HANDLER_OPTIONS | ||
}, | ||
}) |
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 replaces the old addNotificationReceivedListener
, because it lets us also control visibility of the push notification.
if ( | ||
storedPayload?.reason === 'chat-message' && | ||
currentAccount?.did === storedPayload.recipientDid | ||
) { | ||
handleNotification(storedPayload) | ||
storedPayload = undefined | ||
} |
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.
When we encounter a case where you open a notification that doesn't belong to the currently signed in account, we switch the account above and store the payload. Once currentAccount
changes, we will re-handle the notification.
This only applies to chat-message
right now because we don't have a recipientDid
yet on other notification payloads.
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.
Nice one
navigation.reset({ | ||
index: 0, | ||
routes: [{name: 'Messages'}], | ||
}) |
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 now, in cases where we got to this screen from a notification, adding the Messages
route to the start of this creates the "pop" animation that we are looking for.
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.
Code LGTM, pending discussion of behaviors
type NotificationReason = | ||
| 'like' | ||
| 'repost' | ||
| 'follow' | ||
| 'mention' | ||
| 'reply' | ||
| 'quote' | ||
| 'chat-message' | ||
|
||
type NotificationRecord = | ||
| { | ||
reason: Exclude<NotificationReason, 'chat-message'> | ||
uri: string | ||
subject: string | ||
} | ||
| { | ||
reason: 'chat-message' | ||
convoId: string | ||
messageId: string | ||
recipientDid: 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.
sickos-yes.png
setTimeout(() => { | ||
// @ts-expect-error types are weird here | ||
navigation.navigate('MessagesTab', { | ||
screen: 'MessagesConversation', | ||
params: { | ||
conversation: payload.convoId, | ||
}, | ||
}) | ||
}, 500) |
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.
Yeah having Messages as the immediate screen when pressing "back" would be awesome
if ( | ||
storedPayload?.reason === 'chat-message' && | ||
currentAccount?.did === storedPayload.recipientDid | ||
) { | ||
handleNotification(storedPayload) | ||
storedPayload = undefined | ||
} |
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.
Nice one
Why
Edit
For now, let's merge this in with only the DM notification logic, since that is the primary thing we need right now. We should investigate other platforms and determine how we want to handle each individual case here. I.e. where do we want to push for a certain type of notification.
This needed a good bit of thinking to get right, but I think it is mostly there. We're tackling a few birds with one stone here!
Note about points 3 and 4
We need to consider one thing when it comes to points one and two here. When you open the app through one of these notifications, we won't actually "remove" these items from your "unread" notifications. This is a tricky thing to do, because we are currently making notifications as read with a "last seen" time. If we updated that time when you pressed a notification for a mention, we'd be wiping all your unreads up to that point - not just the one you're tapping.
With that said, we can comment out those handlers for now. It sort of comes down to "which is the better experience" I think right now. We'll talk about how to improve this as well, but it was a super easy thing to add in here where I needed to rework the handler anyway.
Test Plan
Testing this is not fun. There isn't any way to receive notifications on the simulator, and even if you build for a device, you still cannot receive the notifications as the push notification token is a development token rather than a production one. With that said, you can send "fake" notifications.
xcrun simctl list devices | grep Booted
testnotif.json
. An example payload would bexcrun simctl push "iPhone 15 Pro" xyz.blueskyweb.app testMention.json
Pressing a mention or reply notification takes you to the post
Those types of notifications still do not show up when the app is foregrounded
Screen.Recording.2024-05-08.at.12.20.22.PM.mov
Pressing a follow notification takes you to the profile
Pressing a message notification takes you to the convo
Pressing a notification for an account that isn't the current one switches your account