add new notification types
This commit is contained in:
@@ -44,11 +44,20 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
|
|
||||||
if reason == "chat-message" || reason == "chat-reaction" {
|
if reason == "chat-message" || reason == "chat-reaction" {
|
||||||
mutateWithChatMessage(bestAttempt)
|
mutateWithChatMessage(bestAttempt)
|
||||||
|
mutateChatMessageBody(bestAttempt, userInfo: request.content.userInfo)
|
||||||
|
mutateWithGroupSubtitle(bestAttempt, userInfo: request.content.userInfo)
|
||||||
let finalContent = createCommunicationNotification(
|
let finalContent = createCommunicationNotification(
|
||||||
from: bestAttempt,
|
from: bestAttempt,
|
||||||
userInfo: request.content.userInfo
|
userInfo: request.content.userInfo
|
||||||
)
|
)
|
||||||
contentHandler(finalContent)
|
contentHandler(finalContent)
|
||||||
|
} else if reason == "chat-added-to-group"
|
||||||
|
|| reason == "chat-removed-from-group"
|
||||||
|
|| reason == "chat-join-request-rejected" {
|
||||||
|
mutateWithChatMessage(bestAttempt)
|
||||||
|
mutateWithGroupSubtitle(bestAttempt, userInfo: request.content.userInfo)
|
||||||
|
mutateWithBadge(bestAttempt)
|
||||||
|
contentHandler(bestAttempt)
|
||||||
} else {
|
} else {
|
||||||
mutateWithBadge(bestAttempt)
|
mutateWithBadge(bestAttempt)
|
||||||
contentHandler(bestAttempt)
|
contentHandler(bestAttempt)
|
||||||
@@ -87,11 +96,18 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
customIdentifier: nil
|
customIdentifier: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var speakableGroupName: INSpeakableString? = nil
|
||||||
|
if userInfo["convoKind"] as? String == "group",
|
||||||
|
let groupName = userInfo["convoGroupName"] as? String,
|
||||||
|
!groupName.isEmpty {
|
||||||
|
speakableGroupName = INSpeakableString(spokenPhrase: groupName)
|
||||||
|
}
|
||||||
|
|
||||||
let intent = INSendMessageIntent(
|
let intent = INSendMessageIntent(
|
||||||
recipients: nil,
|
recipients: nil,
|
||||||
outgoingMessageType: .outgoingMessageText,
|
outgoingMessageType: .outgoingMessageText,
|
||||||
content: content.body,
|
content: content.body,
|
||||||
speakableGroupName: nil,
|
speakableGroupName: speakableGroupName,
|
||||||
conversationIdentifier: convoId,
|
conversationIdentifier: convoId,
|
||||||
serviceName: nil,
|
serviceName: nil,
|
||||||
sender: sender,
|
sender: sender,
|
||||||
@@ -157,6 +173,38 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For group convos, surface the group name as the notification subtitle.
|
||||||
|
// The sender's display name is shown as the title (overridden by
|
||||||
|
// `INSendMessageIntent` for chat-message/chat-reaction).
|
||||||
|
func mutateWithGroupSubtitle(
|
||||||
|
_ content: UNMutableNotificationContent,
|
||||||
|
userInfo: [AnyHashable: Any]
|
||||||
|
) {
|
||||||
|
guard userInfo["convoKind"] as? String == "group",
|
||||||
|
let groupName = userInfo["convoGroupName"] as? String,
|
||||||
|
!groupName.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content.subtitle = groupName
|
||||||
|
}
|
||||||
|
|
||||||
|
// System messages (`add_member`, `convo_locked`, `edit_group`, etc.) are
|
||||||
|
// delivered through `chat-message` but have a server-rendered description
|
||||||
|
// in `title`. iOS overrides the title with the sender name once we apply
|
||||||
|
// `INSendMessageIntent`, so we move the title text into the body before
|
||||||
|
// building the intent.
|
||||||
|
func mutateChatMessageBody(
|
||||||
|
_ content: UNMutableNotificationContent,
|
||||||
|
userInfo: [AnyHashable: Any]
|
||||||
|
) {
|
||||||
|
guard let messageKind = userInfo["messageKind"] as? String,
|
||||||
|
messageKind != "message",
|
||||||
|
!content.title.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content.body = content.title
|
||||||
|
}
|
||||||
|
|
||||||
func mutateWithDefaultSound(_ content: UNMutableNotificationContent) {
|
func mutateWithDefaultSound(_ content: UNMutableNotificationContent) {
|
||||||
content.sound = UNNotificationSound.default
|
content.sound = UNNotificationSound.default
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-8
@@ -22,8 +22,9 @@ import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
|
|||||||
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {
|
import {
|
||||||
|
type ChatNotificationPayload,
|
||||||
getNotificationPayload,
|
getNotificationPayload,
|
||||||
type NotificationPayload,
|
isChatNotificationPayload,
|
||||||
notificationToURL,
|
notificationToURL,
|
||||||
storePayloadForAccountSwitch,
|
storePayloadForAccountSwitch,
|
||||||
} from '#/lib/hooks/useNotificationHandler'
|
} from '#/lib/hooks/useNotificationHandler'
|
||||||
@@ -926,14 +927,14 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
|||||||
const linkingUrl = Linking.useLinkingURL()
|
const linkingUrl = Linking.useLinkingURL()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle navigation to a conversation, or prepares for account switch.
|
* Handle navigation to the messages tab, or prepares for account switch.
|
||||||
*
|
*
|
||||||
* Non-reactive because we need the latest data from some hooks
|
* Non-reactive because we need the latest data from some hooks
|
||||||
* after an async call - sfn
|
* after an async call - sfn
|
||||||
*/
|
*/
|
||||||
const handleChatMessage = useNonReactiveCallback(
|
const handleChatNotification = useNonReactiveCallback(
|
||||||
(payload: Extract<NotificationPayload, {reason: 'chat-message'}>) => {
|
(payload: ChatNotificationPayload) => {
|
||||||
notyLogger.debug(`handleChatMessage`, {payload})
|
notyLogger.debug(`handleChatNotification`, {payload})
|
||||||
|
|
||||||
if (payload.recipientDid !== currentAccount?.did) {
|
if (payload.recipientDid !== currentAccount?.did) {
|
||||||
// handled in useNotificationHandler after account switch finishes
|
// handled in useNotificationHandler after account switch finishes
|
||||||
@@ -946,7 +947,10 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
|||||||
} else {
|
} else {
|
||||||
setShowLoggedOut(true)
|
setShowLoggedOut(true)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (
|
||||||
|
payload.reason === 'chat-message' ||
|
||||||
|
payload.reason === 'chat-reaction'
|
||||||
|
) {
|
||||||
// @ts-expect-error nested navigators aren't typed -sfn
|
// @ts-expect-error nested navigators aren't typed -sfn
|
||||||
navigate('MessagesTab', {
|
navigate('MessagesTab', {
|
||||||
screen: 'Messages',
|
screen: 'Messages',
|
||||||
@@ -954,6 +958,12 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
|||||||
pushToConversation: payload.convoId,
|
pushToConversation: payload.convoId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// chat-added-to-group, chat-removed-from-group,
|
||||||
|
// chat-join-request-rejected: open the Messages list without
|
||||||
|
// targeting a specific conversation
|
||||||
|
// @ts-expect-error nested navigators aren't typed -sfn
|
||||||
|
navigate('MessagesTab', {screen: 'Messages'})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -989,8 +999,8 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
|||||||
causedBoot: true,
|
causedBoot: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (payload.reason === 'chat-message') {
|
if (isChatNotificationPayload(payload)) {
|
||||||
handleChatMessage(payload)
|
handleChatNotification(payload)
|
||||||
} else {
|
} else {
|
||||||
const path = notificationToURL(payload)
|
const path = notificationToURL(payload)
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export type NotificationReason =
|
|||||||
| 'quote'
|
| 'quote'
|
||||||
| 'chat-message'
|
| 'chat-message'
|
||||||
| 'chat-reaction'
|
| 'chat-reaction'
|
||||||
|
| 'chat-added-to-group'
|
||||||
|
| 'chat-removed-from-group'
|
||||||
|
| 'chat-join-request-rejected'
|
||||||
| 'starterpack-joined'
|
| 'starterpack-joined'
|
||||||
| 'like-via-repost'
|
| 'like-via-repost'
|
||||||
| 'repost-via-repost'
|
| 'repost-via-repost'
|
||||||
@@ -37,6 +40,8 @@ export type NotificationReason =
|
|||||||
| 'unverified'
|
| 'unverified'
|
||||||
| 'subscribed-post'
|
| 'subscribed-post'
|
||||||
|
|
||||||
|
type ChatNotificationReason = Extract<NotificationReason, `chat-${string}`>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manually overridden type, but retains the possibility of
|
* Manually overridden type, but retains the possibility of
|
||||||
* `notification.request.trigger.payload` being `undefined`, as specified in
|
* `notification.request.trigger.payload` being `undefined`, as specified in
|
||||||
@@ -45,7 +50,7 @@ export type NotificationReason =
|
|||||||
export type NotificationPayload =
|
export type NotificationPayload =
|
||||||
| undefined
|
| undefined
|
||||||
| {
|
| {
|
||||||
reason: Exclude<NotificationReason, 'chat-message' | 'chat-reaction'>
|
reason: Exclude<NotificationReason, ChatNotificationReason>
|
||||||
uri: string
|
uri: string
|
||||||
subject: string
|
subject: string
|
||||||
recipientDid: string
|
recipientDid: string
|
||||||
@@ -62,6 +67,25 @@ export type NotificationPayload =
|
|||||||
messageId: string
|
messageId: string
|
||||||
recipientDid: string
|
recipientDid: string
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
reason:
|
||||||
|
| 'chat-added-to-group'
|
||||||
|
| 'chat-removed-from-group'
|
||||||
|
| 'chat-join-request-rejected'
|
||||||
|
convoId: string
|
||||||
|
recipientDid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChatNotificationPayload = Extract<
|
||||||
|
NonNullable<NotificationPayload>,
|
||||||
|
{reason: ChatNotificationReason}
|
||||||
|
>
|
||||||
|
|
||||||
|
export function isChatNotificationPayload(
|
||||||
|
payload: NonNullable<NotificationPayload>,
|
||||||
|
): payload is ChatNotificationPayload {
|
||||||
|
return payload.reason.startsWith('chat-')
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_HANDLER_OPTIONS = {
|
const DEFAULT_HANDLER_OPTIONS = {
|
||||||
shouldShowBanner: false,
|
shouldShowBanner: false,
|
||||||
@@ -199,10 +223,7 @@ export function useNotificationsHandler() {
|
|||||||
const handleNotification = (payload?: NotificationPayload) => {
|
const handleNotification = (payload?: NotificationPayload) => {
|
||||||
if (!payload) return
|
if (!payload) return
|
||||||
|
|
||||||
if (
|
if (isChatNotificationPayload(payload)) {
|
||||||
payload.reason === 'chat-message' ||
|
|
||||||
payload.reason === 'chat-reaction'
|
|
||||||
) {
|
|
||||||
logger.debug(`useNotificationsHandler: handling chat notification`, {
|
logger.debug(`useNotificationsHandler: handling chat notification`, {
|
||||||
payload,
|
payload,
|
||||||
})
|
})
|
||||||
@@ -220,7 +241,10 @@ export function useNotificationsHandler() {
|
|||||||
} else {
|
} else {
|
||||||
setShowLoggedOut(true)
|
setShowLoggedOut(true)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (
|
||||||
|
payload.reason === 'chat-message' ||
|
||||||
|
payload.reason === 'chat-reaction'
|
||||||
|
) {
|
||||||
navigation.dispatch(state => {
|
navigation.dispatch(state => {
|
||||||
if (state.routes[0].name === 'Messages') {
|
if (state.routes[0].name === 'Messages') {
|
||||||
if (
|
if (
|
||||||
@@ -253,6 +277,13 @@ export function useNotificationsHandler() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// chat-added-to-group, chat-removed-from-group,
|
||||||
|
// chat-join-request-rejected: open the Messages list without
|
||||||
|
// targeting a specific conversation
|
||||||
|
navigation.dispatch(
|
||||||
|
CommonActions.navigate('MessagesTab', {screen: 'Messages'}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const url = notificationToURL(payload)
|
const url = notificationToURL(payload)
|
||||||
@@ -280,8 +311,7 @@ export function useNotificationsHandler() {
|
|||||||
logger.debug('useNotificationsHandler: incoming', {e, payload})
|
logger.debug('useNotificationsHandler: incoming', {e, payload})
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(payload.reason === 'chat-message' ||
|
isChatNotificationPayload(payload) &&
|
||||||
payload.reason === 'chat-reaction') &&
|
|
||||||
payload.recipientDid === currentAccount?.did
|
payload.recipientDid === currentAccount?.did
|
||||||
) {
|
) {
|
||||||
const shouldAlert = payload.convoId !== currentConvoId
|
const shouldAlert = payload.convoId !== currentConvoId
|
||||||
@@ -352,8 +382,8 @@ export function useNotificationsHandler() {
|
|||||||
// Whenever there's a stored payload, that means we had to switch accounts before handling the notification.
|
// Whenever there's a stored payload, that means we had to switch accounts before handling the notification.
|
||||||
// Whenever currentAccount changes, we should try to handle it again.
|
// Whenever currentAccount changes, we should try to handle it again.
|
||||||
if (
|
if (
|
||||||
(storedAccountSwitchPayload?.reason === 'chat-message' ||
|
storedAccountSwitchPayload &&
|
||||||
storedAccountSwitchPayload?.reason === 'chat-reaction') &&
|
isChatNotificationPayload(storedAccountSwitchPayload) &&
|
||||||
currentAccount?.did === storedAccountSwitchPayload.recipientDid
|
currentAccount?.did === storedAccountSwitchPayload.recipientDid
|
||||||
) {
|
) {
|
||||||
handleNotification(storedAccountSwitchPayload)
|
handleNotification(storedAccountSwitchPayload)
|
||||||
@@ -442,6 +472,9 @@ export function notificationToURL(payload: NotificationPayload): string | null {
|
|||||||
}
|
}
|
||||||
case 'chat-message':
|
case 'chat-message':
|
||||||
case 'chat-reaction':
|
case 'chat-reaction':
|
||||||
|
case 'chat-added-to-group':
|
||||||
|
case 'chat-removed-from-group':
|
||||||
|
case 'chat-join-request-rejected':
|
||||||
// should be handled separately
|
// should be handled separately
|
||||||
return null
|
return null
|
||||||
case 'verified':
|
case 'verified':
|
||||||
|
|||||||
Reference in New Issue
Block a user