mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at switch-session-failure 248 lines 7.9 kB view raw
1import React from 'react' 2import * as Notifications from 'expo-notifications' 3import {CommonActions, useNavigation} from '@react-navigation/native' 4import {useQueryClient} from '@tanstack/react-query' 5 6import {logger} from '#/logger' 7import {track} from 'lib/analytics/analytics' 8import {useAccountSwitcher} from 'lib/hooks/useAccountSwitcher' 9import {NavigationProp} from 'lib/routes/types' 10import {logEvent} from 'lib/statsig/statsig' 11import {isAndroid} from 'platform/detection' 12import {useCurrentConvoId} from 'state/messages/current-convo-id' 13import {RQKEY as RQKEY_NOTIFS} from 'state/queries/notifications/feed' 14import {invalidateCachedUnreadPage} from 'state/queries/notifications/unread' 15import {truncateAndInvalidate} from 'state/queries/util' 16import {useSession} from 'state/session' 17import {useLoggedOutViewControls} from 'state/shell/logged-out' 18import {useCloseAllActiveElements} from 'state/util' 19import {resetToTab} from '#/Navigation' 20 21type NotificationReason = 22 | 'like' 23 | 'repost' 24 | 'follow' 25 | 'mention' 26 | 'reply' 27 | 'quote' 28 | 'chat-message' 29 30type NotificationPayload = 31 | { 32 reason: Exclude<NotificationReason, 'chat-message'> 33 uri: string 34 subject: string 35 } 36 | { 37 reason: 'chat-message' 38 convoId: string 39 messageId: string 40 recipientDid: string 41 } 42 43const DEFAULT_HANDLER_OPTIONS = { 44 shouldShowAlert: true, 45 shouldPlaySound: false, 46 shouldSetBadge: true, 47} 48 49// This needs to stay outside the hook to persist between account switches 50let storedPayload: NotificationPayload | undefined 51 52export function useNotificationsHandler() { 53 const queryClient = useQueryClient() 54 const {currentAccount, accounts} = useSession() 55 const {onPressSwitchAccount} = useAccountSwitcher() 56 const navigation = useNavigation<NavigationProp>() 57 const {currentConvoId} = useCurrentConvoId() 58 const {setShowLoggedOut} = useLoggedOutViewControls() 59 const closeAllActiveElements = useCloseAllActiveElements() 60 61 // Safety to prevent double handling of the same notification 62 const prevDate = React.useRef(0) 63 64 React.useEffect(() => { 65 if (!isAndroid) return 66 67 Notifications.setNotificationChannelAsync('chat-messages', { 68 name: 'Chat', 69 importance: Notifications.AndroidImportance.MAX, 70 sound: 'dm.mp3', 71 showBadge: true, 72 vibrationPattern: [250], 73 lockscreenVisibility: Notifications.AndroidNotificationVisibility.PRIVATE, 74 }) 75 76 Notifications.setNotificationChannelAsync('chat-messages-muted', { 77 name: 'Chat - Muted', 78 importance: Notifications.AndroidImportance.MAX, 79 sound: null, 80 showBadge: true, 81 vibrationPattern: [250], 82 lockscreenVisibility: Notifications.AndroidNotificationVisibility.PRIVATE, 83 }) 84 }, []) 85 86 React.useEffect(() => { 87 const handleNotification = (payload?: NotificationPayload) => { 88 if (!payload) return 89 90 if (payload.reason === 'chat-message') { 91 if (payload.recipientDid !== currentAccount?.did && !storedPayload) { 92 storedPayload = payload 93 closeAllActiveElements() 94 95 const account = accounts.find(a => a.did === payload.recipientDid) 96 if (account) { 97 onPressSwitchAccount(account, 'Notification') 98 } else { 99 setShowLoggedOut(true) 100 } 101 } else { 102 navigation.dispatch(state => { 103 if (state.routes[0].name === 'Messages') { 104 return CommonActions.navigate('MessagesConversation', { 105 conversation: payload.convoId, 106 }) 107 } else { 108 return CommonActions.navigate('MessagesTab', { 109 screen: 'Messages', 110 params: { 111 pushToConversation: payload.convoId, 112 }, 113 }) 114 } 115 }) 116 } 117 } else { 118 switch (payload.reason) { 119 case 'like': 120 case 'repost': 121 case 'follow': 122 case 'mention': 123 case 'quote': 124 case 'reply': 125 resetToTab('NotificationsTab') 126 break 127 // TODO implement these after we have an idea of how to handle each individual case 128 // case 'follow': 129 // const uri = new AtUri(payload.uri) 130 // setTimeout(() => { 131 // // @ts-expect-error types are weird here 132 // navigation.navigate('HomeTab', { 133 // screen: 'Profile', 134 // params: { 135 // name: uri.host, 136 // }, 137 // }) 138 // }, 500) 139 // break 140 // case 'mention': 141 // case 'reply': 142 // const urip = new AtUri(payload.uri) 143 // setTimeout(() => { 144 // // @ts-expect-error types are weird here 145 // navigation.navigate('HomeTab', { 146 // screen: 'PostThread', 147 // params: { 148 // name: urip.host, 149 // rkey: urip.rkey, 150 // }, 151 // }) 152 // }, 500) 153 } 154 } 155 } 156 157 Notifications.setNotificationHandler({ 158 handleNotification: async e => { 159 if (e.request.trigger.type !== 'push') return DEFAULT_HANDLER_OPTIONS 160 161 logger.debug( 162 'Notifications: received', 163 {e}, 164 logger.DebugContext.notifications, 165 ) 166 167 const payload = e.request.trigger.payload as NotificationPayload 168 if ( 169 payload.reason === 'chat-message' && 170 payload.recipientDid === currentAccount?.did 171 ) { 172 return { 173 shouldShowAlert: payload.convoId !== currentConvoId, 174 shouldPlaySound: false, 175 shouldSetBadge: false, 176 } 177 } 178 179 // Any notification other than a chat message should invalidate the unread page 180 invalidateCachedUnreadPage() 181 return DEFAULT_HANDLER_OPTIONS 182 }, 183 }) 184 185 const responseReceivedListener = 186 Notifications.addNotificationResponseReceivedListener(e => { 187 if (e.notification.date === prevDate.current) { 188 return 189 } 190 prevDate.current = e.notification.date 191 192 logger.debug( 193 'Notifications: response received', 194 { 195 actionIdentifier: e.actionIdentifier, 196 }, 197 logger.DebugContext.notifications, 198 ) 199 200 if ( 201 e.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER && 202 e.notification.request.trigger.type === 'push' 203 ) { 204 logger.debug( 205 'User pressed a notification, opening notifications tab', 206 {}, 207 logger.DebugContext.notifications, 208 ) 209 track('Notificatons:OpenApp') 210 logEvent('notifications:openApp', {}) 211 invalidateCachedUnreadPage() 212 truncateAndInvalidate(queryClient, RQKEY_NOTIFS()) 213 logger.debug('Notifications: handleNotification', { 214 content: e.notification.request.content, 215 payload: e.notification.request.trigger.payload, 216 }) 217 handleNotification( 218 e.notification.request.trigger.payload as NotificationPayload, 219 ) 220 Notifications.dismissAllNotificationsAsync() 221 } 222 }) 223 224 // Whenever there's a stored payload, that means we had to switch accounts before handling the notification. 225 // Whenever currentAccount changes, we should try to handle it again. 226 if ( 227 storedPayload?.reason === 'chat-message' && 228 currentAccount?.did === storedPayload.recipientDid 229 ) { 230 handleNotification(storedPayload) 231 storedPayload = undefined 232 } 233 234 return () => { 235 responseReceivedListener.remove() 236 } 237 }, [ 238 queryClient, 239 currentAccount, 240 currentConvoId, 241 accounts, 242 closeAllActiveElements, 243 currentAccount?.did, 244 navigation, 245 onPressSwitchAccount, 246 setShowLoggedOut, 247 ]) 248}