Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client

Merge branch 'main' of https://github.com/bluesky-social/social-app

+390 -236
+2 -1
bskyweb/cmd/bskyweb/server.go
··· 603 603 IP string `json:"ip"` 604 604 } 605 605 type IPCCResponse struct { 606 - CC string `json:"countryCode"` 606 + CC string `json:"countryCode"` 607 + AgeRestrictedGeo string `json:"isAgeRestrictedGeo"` 607 608 } 608 609 609 610 func (srv *Server) WebIpCC(c echo.Context) error {
+1 -1
src/App.native.tsx
··· 17 17 import * as Sentry from '@sentry/react-native' 18 18 19 19 import {KeyboardControllerProvider} from '#/lib/hooks/useEnableKeyboardController' 20 + import {Provider as HideBottomBarBorderProvider} from '#/lib/hooks/useHideBottomBarBorder' 20 21 import {QueryProvider} from '#/lib/react-query' 21 22 import {Provider as StatsigProvider, tryFetchGates} from '#/lib/statsig/statsig' 22 23 import {s} from '#/lib/styles' ··· 73 74 import {Splash} from '#/Splash' 74 75 import {BottomSheetProvider} from '../modules/bottom-sheet' 75 76 import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 76 - import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottomBarBorder' 77 77 78 78 SplashScreen.preventAutoHideAsync() 79 79 if (isIOS) {
+124 -22
src/Navigation.tsx
··· 1 - import * as React from 'react' 1 + import {useCallback, useRef} from 'react' 2 + import * as Notifications from 'expo-notifications' 2 3 import {i18n, type MessageDescriptor} from '@lingui/core' 3 4 import {msg} from '@lingui/macro' 4 5 import { ··· 10 11 createNavigationContainerRef, 11 12 DarkTheme, 12 13 DefaultTheme, 14 + type LinkingOptions, 13 15 NavigationContainer, 14 16 StackActions, 15 17 } from '@react-navigation/native' 16 18 17 19 import {timeout} from '#/lib/async/timeout' 18 20 import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' 21 + import { 22 + getNotificationPayload, 23 + type NotificationPayload, 24 + notificationToURL, 25 + storePayloadForAccountSwitch, 26 + } from '#/lib/hooks/useNotificationHandler' 19 27 import {useWebScrollRestoration} from '#/lib/hooks/useWebScrollRestoration' 28 + import {logger as notyLogger} from '#/lib/notifications/util' 20 29 import {buildStateObject} from '#/lib/routes/helpers' 21 30 import { 22 31 type AllNavigatorParams, ··· 71 80 import {ModerationScreen} from '#/screens/Moderation' 72 81 import {Screen as ModerationVerificationSettings} from '#/screens/Moderation/VerificationSettings' 73 82 import {Screen as ModerationInteractionSettings} from '#/screens/ModerationInteractionSettings' 83 + import {NotificationsActivityListScreen} from '#/screens/Notifications/ActivityList' 74 84 import {PostLikedByScreen} from '#/screens/Post/PostLikedBy' 75 85 import {PostQuotesScreen} from '#/screens/Post/PostQuotes' 76 86 import {PostRepostedByScreen} from '#/screens/Post/PostRepostedBy' ··· 94 104 import {FollowingFeedPreferencesScreen} from '#/screens/Settings/FollowingFeedPreferences' 95 105 import {InterestsSettingsScreen} from '#/screens/Settings/InterestsSettings' 96 106 import {LanguageSettingsScreen} from '#/screens/Settings/LanguageSettings' 107 + import {LegacyNotificationSettingsScreen} from '#/screens/Settings/LegacyNotificationSettings' 108 + import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings' 109 + import {ActivityNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/ActivityNotificationSettings' 110 + import {LikeNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/LikeNotificationSettings' 111 + import {LikesOnRepostsNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings' 112 + import {MentionNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/MentionNotificationSettings' 113 + import {MiscellaneousNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/MiscellaneousNotificationSettings' 114 + import {NewFollowerNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/NewFollowerNotificationSettings' 115 + import {QuoteNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/QuoteNotificationSettings' 116 + import {ReplyNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/ReplyNotificationSettings' 117 + import {RepostNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/RepostNotificationSettings' 118 + import {RepostsOnRepostsNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings' 97 119 import {PrivacyAndSecuritySettingsScreen} from '#/screens/Settings/PrivacyAndSecuritySettings' 98 120 import {SettingsScreen} from '#/screens/Settings/Settings' 99 121 import {ThreadPreferencesScreen} from '#/screens/Settings/ThreadPreferences' ··· 111 133 } from '#/components/dialogs/EmailDialog' 112 134 import {router} from '#/routes' 113 135 import {Referrer} from '../modules/expo-bluesky-swiss-army' 114 - import {NotificationsActivityListScreen} from './screens/Notifications/ActivityList' 115 - import {LegacyNotificationSettingsScreen} from './screens/Settings/LegacyNotificationSettings' 116 - import {NotificationSettingsScreen} from './screens/Settings/NotificationSettings' 117 - import {ActivityNotificationSettingsScreen} from './screens/Settings/NotificationSettings/ActivityNotificationSettings' 118 - import {LikeNotificationSettingsScreen} from './screens/Settings/NotificationSettings/LikeNotificationSettings' 119 - import {LikesOnRepostsNotificationSettingsScreen} from './screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings' 120 - import {MentionNotificationSettingsScreen} from './screens/Settings/NotificationSettings/MentionNotificationSettings' 121 - import {MiscellaneousNotificationSettingsScreen} from './screens/Settings/NotificationSettings/MiscellaneousNotificationSettings' 122 - import {NewFollowerNotificationSettingsScreen} from './screens/Settings/NotificationSettings/NewFollowerNotificationSettings' 123 - import {QuoteNotificationSettingsScreen} from './screens/Settings/NotificationSettings/QuoteNotificationSettings' 124 - import {ReplyNotificationSettingsScreen} from './screens/Settings/NotificationSettings/ReplyNotificationSettings' 125 - import {RepostNotificationSettingsScreen} from './screens/Settings/NotificationSettings/RepostNotificationSettings' 126 - import {RepostsOnRepostsNotificationSettingsScreen} from './screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings' 136 + import {useAccountSwitcher} from './lib/hooks/useAccountSwitcher' 137 + import {useNonReactiveCallback} from './lib/hooks/useNonReactiveCallback' 138 + import {useLoggedOutViewControls} from './state/shell/logged-out' 139 + import {useCloseAllActiveElements} from './state/util' 127 140 128 141 const navigationRef = createNavigationContainerRef<AllNavigatorParams>() 129 142 ··· 604 617 * in 3 distinct tab-stacks with a different root screen on each. 605 618 */ 606 619 function TabsNavigator() { 607 - const tabBar = React.useCallback( 620 + const tabBar = useCallback( 608 621 (props: JSX.IntrinsicAttributes & BottomTabBarProps) => ( 609 622 <BottomBar {...props} /> 610 623 ), ··· 771 784 772 785 const LINKING = { 773 786 // TODO figure out what we are going to use 787 + // note: `bluesky://` is what is used in app.config.js 774 788 prefixes: ['bsky://', 'bluesky://', 'https://bsky.app'], 775 789 776 790 getPathFromState(state: State) { ··· 827 841 return res 828 842 } 829 843 }, 830 - } 844 + } satisfies LinkingOptions<AllNavigatorParams> 845 + 846 + /** 847 + * Used to ensure we don't handle the same notification twice 848 + */ 849 + let lastHandledNotificationDateDedupe: number | undefined 831 850 832 851 function RoutesContainer({children}: React.PropsWithChildren<{}>) { 833 852 const theme = useColorSchemeStyle(DefaultTheme, DarkTheme) 834 - const {currentAccount} = useSession() 835 - const prevLoggedRouteName = React.useRef<string | undefined>(undefined) 853 + const {currentAccount, accounts} = useSession() 854 + const {onPressSwitchAccount} = useAccountSwitcher() 855 + const {setShowLoggedOut} = useLoggedOutViewControls() 856 + const prevLoggedRouteName = useRef<string | undefined>(undefined) 836 857 const emailDialogControl = useEmailDialogControl() 858 + const closeAllActiveElements = useCloseAllActiveElements() 859 + 860 + /** 861 + * Handle navigation to a conversation, or prepares for account switch. 862 + * 863 + * Non-reactive because we need the latest data from some hooks 864 + * after an async call - sfn 865 + */ 866 + const handleChatMessage = useNonReactiveCallback( 867 + (payload: Extract<NotificationPayload, {reason: 'chat-message'}>) => { 868 + notyLogger.debug(`handleChatMessage`, {payload}) 869 + 870 + if (payload.recipientDid !== currentAccount?.did) { 871 + // handled in useNotificationHandler after account switch finishes 872 + storePayloadForAccountSwitch(payload) 873 + closeAllActiveElements() 874 + 875 + const account = accounts.find(a => a.did === payload.recipientDid) 876 + if (account) { 877 + onPressSwitchAccount(account, 'Notification') 878 + } else { 879 + setShowLoggedOut(true) 880 + } 881 + } else { 882 + // @ts-expect-error nested navigators aren't typed -sfn 883 + navigate('MessagesTab', { 884 + screen: 'MessagesConversation', 885 + params: { 886 + conversation: payload.convoId, 887 + }, 888 + }) 889 + } 890 + }, 891 + ) 892 + 893 + async function handlePushNotificationEntry() { 894 + if (!isNative) return 895 + 896 + /** 897 + * The notification that caused the app to open, if applicable 898 + */ 899 + const response = await Notifications.getLastNotificationResponseAsync() 900 + 901 + if (response) { 902 + notyLogger.debug(`handlePushNotificationEntry: response`, {response}) 903 + 904 + if (response.notification.date === lastHandledNotificationDateDedupe) 905 + return 906 + lastHandledNotificationDateDedupe = response.notification.date 907 + 908 + const payload = getNotificationPayload(response.notification) 909 + 910 + if (payload) { 911 + notyLogger.metric( 912 + 'notifications:openApp', 913 + {reason: payload.reason, causedBoot: true}, 914 + {statsig: false}, 915 + ) 916 + 917 + if (payload.reason === 'chat-message') { 918 + handleChatMessage(payload) 919 + } else { 920 + const path = notificationToURL(payload) 921 + 922 + if (path === '/notifications') { 923 + resetToTab('NotificationsTab') 924 + notyLogger.debug(`handlePushNotificationEntry: default navigate`) 925 + } else if (path) { 926 + const [screen, params] = router.matchPath(path) 927 + // @ts-expect-error nested navigators aren't typed -sfn 928 + navigate('HomeTab', {screen, params}) 929 + notyLogger.debug(`handlePushNotificationEntry: navigate`, { 930 + screen, 931 + params, 932 + }) 933 + } 934 + } 935 + } 936 + } 937 + } 837 938 838 939 function onReady() { 839 940 prevLoggedRouteName.current = getCurrentRouteName() ··· 854 955 onStateChange={() => { 855 956 logger.metric( 856 957 'router:navigate', 857 - { 858 - from: prevLoggedRouteName.current, 859 - }, 958 + {from: prevLoggedRouteName.current}, 860 959 {statsig: false}, 861 960 ) 862 961 prevLoggedRouteName.current = getCurrentRouteName() ··· 866 965 logModuleInitTime() 867 966 onReady() 868 967 logger.metric('router:navigate', {}, {statsig: false}) 968 + handlePushNotificationEntry() 869 969 }} 870 970 // WARNING: Implicit navigation to nested navigators is depreciated in React Navigation 7.x 871 971 // However, there's a fair amount of places we do that, especially in when popping to the top of stacks. ··· 915 1015 return Promise.resolve() 916 1016 } 917 1017 918 - function resetToTab(tabName: 'HomeTab' | 'SearchTab' | 'NotificationsTab') { 1018 + function resetToTab( 1019 + tabName: 'HomeTab' | 'SearchTab' | 'MessagesTab' | 'NotificationsTab', 1020 + ) { 919 1021 if (navigationRef.isReady()) { 920 1022 navigate(tabName) 921 1023 if (navigationRef.canGoBack()) {
+136 -114
src/lib/hooks/useNotificationHandler.ts
··· 7 7 import {useQueryClient} from '@tanstack/react-query' 8 8 9 9 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' 10 + import {logger as notyLogger} from '#/lib/notifications/util' 10 11 import {type NavigationProp} from '#/lib/routes/types' 11 - import {Logger} from '#/logger' 12 - import {isAndroid} from '#/platform/detection' 12 + import {isAndroid, isIOS} from '#/platform/detection' 13 13 import {useCurrentConvoId} from '#/state/messages/current-convo-id' 14 14 import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed' 15 15 import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread' ··· 18 18 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 19 19 import {useCloseAllActiveElements} from '#/state/util' 20 20 import {resetToTab} from '#/Navigation' 21 + import {router} from '#/routes' 21 22 22 23 export type NotificationReason = 23 24 | 'like' ··· 39 40 * `notification.request.trigger.payload` being `undefined`, as specified in 40 41 * the source types. 41 42 */ 42 - type NotificationPayload = 43 + export type NotificationPayload = 43 44 | undefined 44 45 | { 45 46 reason: Exclude<NotificationReason, 'chat-message'> 46 47 uri: string 47 48 subject: string 49 + recipientDid: string 48 50 } 49 51 | { 50 52 reason: 'chat-message' ··· 60 62 shouldSetBadge: true, 61 63 } satisfies Notifications.NotificationBehavior 62 64 63 - // These need to stay outside the hook to persist between account switches 64 - let storedPayload: NotificationPayload 65 - let prevDate = 0 65 + /** 66 + * Cached notification payload if we handled a notification while the user was 67 + * using a different account. This is consumed after we finish switching 68 + * accounts. 69 + */ 70 + let storedAccountSwitchPayload: NotificationPayload 66 71 67 - const logger = Logger.create(Logger.Context.Notifications) 72 + /** 73 + * Used to ensure we don't handle the same notification twice 74 + */ 75 + let lastHandledNotificationDateDedupe = 0 68 76 69 77 export function useNotificationsHandler() { 70 78 const queryClient = useQueryClient() ··· 182 190 if (!payload) return 183 191 184 192 if (payload.reason === 'chat-message') { 185 - if (payload.recipientDid !== currentAccount?.did && !storedPayload) { 186 - storedPayload = payload 193 + notyLogger.debug(`useNotificationsHandler: handling chat message`, { 194 + payload, 195 + }) 196 + 197 + if ( 198 + payload.recipientDid !== currentAccount?.did && 199 + !storedAccountSwitchPayload 200 + ) { 201 + storePayloadForAccountSwitch(payload) 187 202 closeAllActiveElements() 188 203 189 204 const account = accounts.find(a => a.did === payload.recipientDid) ··· 227 242 }) 228 243 } 229 244 } else { 230 - switch (payload.reason) { 231 - case 'subscribed-post': 232 - const urip = new AtUri(payload.uri) 233 - if (urip.collection === 'app.bsky.feed.post') { 234 - setTimeout(() => { 235 - // @ts-expect-error types are weird here 236 - navigation.navigate('HomeTab', { 237 - screen: 'PostThread', 238 - params: { 239 - name: urip.host, 240 - rkey: urip.rkey, 241 - }, 242 - }) 243 - }, 500) 244 - } else { 245 - resetToTab('NotificationsTab') 246 - } 247 - break 248 - case 'like': 249 - case 'repost': 250 - case 'follow': 251 - case 'mention': 252 - case 'quote': 253 - case 'reply': 254 - case 'starterpack-joined': 255 - case 'like-via-repost': 256 - case 'repost-via-repost': 257 - case 'verified': 258 - case 'unverified': 259 - default: 260 - resetToTab('NotificationsTab') 261 - break 262 - // TODO implement these after we have an idea of how to handle each individual case 263 - // case 'follow': 264 - // const uri = new AtUri(payload.uri) 265 - // setTimeout(() => { 266 - // // @ts-expect-error types are weird here 267 - // navigation.navigate('HomeTab', { 268 - // screen: 'Profile', 269 - // params: { 270 - // name: uri.host, 271 - // }, 272 - // }) 273 - // }, 500) 274 - // break 275 - // case 'mention': 276 - // case 'reply': 277 - // const urip = new AtUri(payload.uri) 278 - // setTimeout(() => { 279 - // // @ts-expect-error types are weird here 280 - // navigation.navigate('HomeTab', { 281 - // screen: 'PostThread', 282 - // params: { 283 - // name: urip.host, 284 - // rkey: urip.rkey, 285 - // }, 286 - // }) 287 - // }, 500) 245 + const url = notificationToURL(payload) 246 + 247 + if (url === '/notifications') { 248 + resetToTab('NotificationsTab') 249 + } else if (url) { 250 + const [screen, params] = router.matchPath(url) 251 + // @ts-expect-error router is not typed :/ -sfn 252 + navigation.navigate('HomeTab', {screen, params}) 253 + notyLogger.debug(`useNotificationsHandler: navigate`, { 254 + screen, 255 + params, 256 + }) 288 257 } 289 258 } 290 259 } 291 260 292 261 Notifications.setNotificationHandler({ 293 262 handleNotification: async e => { 294 - if ( 295 - e.request.trigger == null || 296 - typeof e.request.trigger !== 'object' || 297 - !('type' in e.request.trigger) || 298 - e.request.trigger.type !== 'push' 299 - ) { 300 - return DEFAULT_HANDLER_OPTIONS 301 - } 263 + const payload = getNotificationPayload(e) 302 264 303 - logger.debug('Notifications: received', {e}) 304 - 305 - const payload = e.request.trigger.payload as NotificationPayload 265 + if (!payload) return DEFAULT_HANDLER_OPTIONS 306 266 307 - if (!payload) { 308 - return DEFAULT_HANDLER_OPTIONS 309 - } 267 + notyLogger.debug('useNotificationsHandler: incoming', {e, payload}) 310 268 311 269 if ( 312 270 payload.reason === 'chat-message' && ··· 329 287 330 288 const responseReceivedListener = 331 289 Notifications.addNotificationResponseReceivedListener(e => { 332 - if (e.notification.date === prevDate) { 333 - return 334 - } 335 - prevDate = e.notification.date 290 + if (e.notification.date === lastHandledNotificationDateDedupe) return 291 + lastHandledNotificationDateDedupe = e.notification.date 336 292 337 - logger.debug('Notifications: response received', { 293 + notyLogger.debug('useNotificationsHandler: response received', { 338 294 actionIdentifier: e.actionIdentifier, 339 295 }) 340 296 341 - if ( 342 - e.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER && 343 - e.notification.request.trigger != null && 344 - typeof e.notification.request.trigger === 'object' && 345 - 'type' in e.notification.request.trigger && 346 - e.notification.request.trigger.type === 'push' 347 - ) { 348 - const payload = e.notification.request.trigger 349 - .payload as NotificationPayload 297 + if (e.actionIdentifier !== Notifications.DEFAULT_ACTION_IDENTIFIER) { 298 + return 299 + } 350 300 351 - if (!payload) { 352 - logger.error('useNotificationsHandler: received no payload', { 353 - identifier: e.notification.request.identifier, 354 - }) 355 - return 356 - } 301 + const payload = getNotificationPayload(e.notification) 302 + 303 + if (payload) { 357 304 if (!payload.reason) { 358 - logger.error('useNotificationsHandler: received unknown payload', { 359 - payload, 360 - identifier: e.notification.request.identifier, 361 - }) 305 + notyLogger.error( 306 + 'useNotificationsHandler: received unknown payload', 307 + { 308 + payload, 309 + identifier: e.notification.request.identifier, 310 + }, 311 + ) 362 312 return 363 313 } 364 314 365 - logger.debug( 315 + notyLogger.debug( 366 316 'User pressed a notification, opening notifications tab', 367 317 {}, 368 318 ) 369 - logger.metric( 319 + notyLogger.metric( 370 320 'notifications:openApp', 371 - {reason: payload.reason}, 321 + {reason: payload.reason, causedBoot: false}, 372 322 {statsig: false}, 373 323 ) 374 324 ··· 383 333 truncateAndInvalidate(queryClient, RQKEY_NOTIFS('mentions')) 384 334 } 385 335 386 - logger.debug('Notifications: handleNotification', { 336 + notyLogger.debug('Notifications: handleNotification', { 387 337 content: e.notification.request.content, 388 - payload: e.notification.request.trigger.payload, 338 + payload: payload, 389 339 }) 390 340 391 341 handleNotification(payload) 392 342 Notifications.dismissAllNotificationsAsync() 343 + } else { 344 + notyLogger.error('useNotificationsHandler: received no payload', { 345 + identifier: e.notification.request.identifier, 346 + }) 393 347 } 394 348 }) 395 349 396 350 // Whenever there's a stored payload, that means we had to switch accounts before handling the notification. 397 351 // Whenever currentAccount changes, we should try to handle it again. 398 352 if ( 399 - storedPayload?.reason === 'chat-message' && 400 - currentAccount?.did === storedPayload.recipientDid 353 + storedAccountSwitchPayload?.reason === 'chat-message' && 354 + currentAccount?.did === storedAccountSwitchPayload.recipientDid 401 355 ) { 402 - handleNotification(storedPayload) 403 - storedPayload = undefined 356 + handleNotification(storedAccountSwitchPayload) 357 + storedAccountSwitchPayload = undefined 404 358 } 405 359 406 360 return () => { ··· 418 372 setShowLoggedOut, 419 373 ]) 420 374 } 375 + 376 + export function storePayloadForAccountSwitch(payload: NotificationPayload) { 377 + storedAccountSwitchPayload = payload 378 + } 379 + 380 + export function getNotificationPayload( 381 + e: Notifications.Notification, 382 + ): NotificationPayload | null { 383 + if ( 384 + e.request.trigger == null || 385 + typeof e.request.trigger !== 'object' || 386 + !('type' in e.request.trigger) || 387 + e.request.trigger.type !== 'push' 388 + ) { 389 + return null 390 + } 391 + 392 + const payload = ( 393 + isIOS ? e.request.trigger.payload : e.request.content.data 394 + ) as NotificationPayload 395 + 396 + if (payload) { 397 + return payload 398 + } else { 399 + return null 400 + } 401 + } 402 + 403 + export function notificationToURL( 404 + payload: NotificationPayload, 405 + ): string | undefined { 406 + switch (payload?.reason) { 407 + case 'like': 408 + case 'repost': 409 + case 'like-via-repost': 410 + case 'repost-via-repost': { 411 + const urip = new AtUri(payload.subject) 412 + if (urip.collection === 'app.bsky.feed.post') { 413 + return `/profile/${urip.host}/post/${urip.rkey}` 414 + } else { 415 + return '/notifications' 416 + } 417 + } 418 + case 'reply': 419 + case 'quote': 420 + case 'mention': 421 + case 'subscribed-post': { 422 + const urip = new AtUri(payload.uri) 423 + if (urip.collection === 'app.bsky.feed.post') { 424 + return `/profile/${urip.host}/post/${urip.rkey}` 425 + } else { 426 + return '/notifications' 427 + } 428 + } 429 + case 'follow': 430 + case 'starterpack-joined': { 431 + const urip = new AtUri(payload.uri) 432 + return `/profile/${urip.host}` 433 + } 434 + case 'chat-message': 435 + // should be handled separately 436 + return undefined 437 + case 'verified': 438 + case 'unverified': 439 + default: 440 + return '/notifications' 441 + } 442 + }
+10 -10
src/lib/notifications/notifications.ts
··· 6 6 import debounce from 'lodash.debounce' 7 7 8 8 import {PUBLIC_APPVIEW_DID, PUBLIC_STAGING_APPVIEW_DID} from '#/lib/constants' 9 - import {Logger} from '#/logger' 9 + import {logger as notyLogger} from '#/lib/notifications/util' 10 10 import {isNative} from '#/platform/detection' 11 11 import {type SessionAccount, useAgent, useSession} from '#/state/session' 12 12 import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler' 13 - 14 - const logger = Logger.create(Logger.Context.Notifications) 15 13 16 14 /** 17 15 * @private ··· 36 34 appId: 'social.deer', 37 35 }) 38 36 39 - logger.debug(`registerPushToken: success`, { 37 + notyLogger.debug(`registerPushToken: success`, { 40 38 tokenType: token.type, 41 39 token: token.data, 42 40 }) 43 41 } catch (error) { 44 - logger.error(`registerPushToken: failed`, {safeMessage: error}) 42 + notyLogger.error(`registerPushToken: failed`, {safeMessage: error}) 45 43 } 46 44 } 47 45 ··· 80 78 */ 81 79 async function getPushToken() { 82 80 const granted = (await Notifications.getPermissionsAsync()).granted 83 - logger.debug(`getPushToken`, {granted}) 81 + notyLogger.debug(`getPushToken`, {granted}) 84 82 if (granted) { 85 83 return Notifications.getDevicePushTokenAsync() 86 84 } ··· 115 113 */ 116 114 const token = await getPushToken() 117 115 118 - logger.debug(`useGetAndRegisterPushToken`, {token: token ?? 'undefined'}) 116 + notyLogger.debug(`useGetAndRegisterPushToken`, { 117 + token: token ?? 'undefined', 118 + }) 119 119 120 120 if (token) { 121 121 /** ··· 147 147 */ 148 148 if (!currentAccount) return 149 149 150 - logger.debug(`useNotificationsRegistration`) 150 + notyLogger.debug(`useNotificationsRegistration`) 151 151 152 152 /** 153 153 * Init push token, if permissions are granted already. If they weren't, ··· 168 168 */ 169 169 const subscription = Notifications.addPushTokenListener(async token => { 170 170 registerPushToken({token}) 171 - logger.debug(`addPushTokenListener callback`, {token}) 171 + notyLogger.debug(`addPushTokenListener callback`, {token}) 172 172 }) 173 173 174 174 return () => { ··· 202 202 203 203 const res = await Notifications.requestPermissionsAsync() 204 204 205 - logger.metric(`notifications:request`, { 205 + notyLogger.metric(`notifications:request`, { 206 206 context: context, 207 207 status: res.status, 208 208 })
+3
src/lib/notifications/util.ts
··· 1 + import {Logger} from '#/logger' 2 + 3 + export const logger = Logger.create(Logger.Context.Notifications)
+87 -87
src/locale/locales/en/messages.po
··· 509 509 msgid "A screenshot of a profile page with a bell icon next to the follow button, indicating the new activity notifications feature." 510 510 msgstr "" 511 511 512 - #: src/Navigation.tsx:509 512 + #: src/Navigation.tsx:522 513 513 #: src/screens/Settings/AboutSettings.tsx:75 514 514 #: src/screens/Settings/Settings.tsx:235 515 515 #: src/screens/Settings/Settings.tsx:238 ··· 536 536 msgid "Accessibility" 537 537 msgstr "" 538 538 539 - #: src/Navigation.tsx:368 539 + #: src/Navigation.tsx:381 540 540 msgid "Accessibility Settings" 541 541 msgstr "" 542 542 543 - #: src/Navigation.tsx:384 543 + #: src/Navigation.tsx:397 544 544 #: src/screens/Login/LoginForm.tsx:194 545 545 #: src/screens/Settings/AccountSettings.tsx:48 546 546 #: src/screens/Settings/Settings.tsx:165 ··· 604 604 msgid "Accounts with a scalloped blue check mark <0><1/></0> can verify others. These trusted verifiers are selected by Bluesky." 605 605 msgstr "" 606 606 607 - #: src/lib/hooks/useNotificationHandler.ts:174 607 + #: src/lib/hooks/useNotificationHandler.ts:182 608 608 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:102 609 609 #: src/screens/Settings/NotificationSettings/index.tsx:192 610 610 msgid "Activity from others" 611 611 msgstr "" 612 612 613 - #: src/Navigation.tsx:477 613 + #: src/Navigation.tsx:490 614 614 msgid "Activity notifications" 615 615 msgstr "" 616 616 ··· 990 990 msgid "Anyone who follows me" 991 991 msgstr "" 992 992 993 - #: src/Navigation.tsx:517 993 + #: src/Navigation.tsx:530 994 994 #: src/screens/Settings/AppIconSettings/index.tsx:67 995 995 #: src/screens/Settings/AppIconSettings/SettingsListItem.tsx:18 996 996 #: src/screens/Settings/AppIconSettings/SettingsListItem.tsx:23 ··· 1027 1027 msgid "App passwords" 1028 1028 msgstr "" 1029 1029 1030 - #: src/Navigation.tsx:336 1030 + #: src/Navigation.tsx:349 1031 1031 #: src/screens/Settings/AppPasswords.tsx:51 1032 1032 msgid "App Passwords" 1033 1033 msgstr "" ··· 1063 1063 msgid "Appeal this decision" 1064 1064 msgstr "" 1065 1065 1066 - #: src/Navigation.tsx:376 1066 + #: src/Navigation.tsx:389 1067 1067 #: src/screens/Settings/AppearanceSettings.tsx:88 1068 1068 #: src/screens/Settings/Settings.tsx:203 1069 1069 #: src/screens/Settings/Settings.tsx:206 ··· 1279 1279 msgid "Blocked accounts" 1280 1280 msgstr "" 1281 1281 1282 - #: src/Navigation.tsx:177 1282 + #: src/Navigation.tsx:190 1283 1283 #: src/view/screens/ModerationBlockedAccounts.tsx:104 1284 1284 msgid "Blocked Accounts" 1285 1285 msgstr "" ··· 1587 1587 msgid "Changes saved" 1588 1588 msgstr "" 1589 1589 1590 - #: src/lib/hooks/useNotificationHandler.ts:91 1591 - #: src/Navigation.tsx:534 1590 + #: src/lib/hooks/useNotificationHandler.ts:99 1591 + #: src/Navigation.tsx:547 1592 1592 #: src/view/shell/bottom-bar/BottomBar.tsx:221 1593 1593 #: src/view/shell/desktop/LeftNav.tsx:553 1594 1594 #: src/view/shell/Drawer.tsx:455 ··· 1601 1601 msgid "Chat deleted" 1602 1602 msgstr "" 1603 1603 1604 - #: src/lib/hooks/useNotificationHandler.ts:106 1604 + #: src/lib/hooks/useNotificationHandler.ts:114 1605 1605 msgid "Chat messages - silent" 1606 1606 msgstr "" 1607 1607 1608 - #: src/lib/hooks/useNotificationHandler.ts:97 1608 + #: src/lib/hooks/useNotificationHandler.ts:105 1609 1609 msgid "Chat messages - sound" 1610 1610 msgstr "" 1611 1611 ··· 1614 1614 msgid "Chat muted" 1615 1615 msgstr "" 1616 1616 1617 - #: src/Navigation.tsx:544 1617 + #: src/Navigation.tsx:557 1618 1618 #: src/screens/Messages/components/InboxPreview.tsx:24 1619 1619 msgid "Chat request inbox" 1620 1620 msgstr "" ··· 1625 1625 msgstr "" 1626 1626 1627 1627 #: src/components/dms/ConvoMenu.tsx:75 1628 - #: src/Navigation.tsx:539 1628 + #: src/Navigation.tsx:552 1629 1629 #: src/screens/Messages/ChatList.tsx:341 1630 1630 msgid "Chat settings" 1631 1631 msgstr "" ··· 1876 1876 msgid "Comics" 1877 1877 msgstr "" 1878 1878 1879 - #: src/Navigation.tsx:326 1879 + #: src/Navigation.tsx:339 1880 1880 #: src/view/screens/CommunityGuidelines.tsx:34 1881 1881 msgid "Community Guidelines" 1882 1882 msgstr "" ··· 1963 1963 msgid "Content and media" 1964 1964 msgstr "" 1965 1965 1966 - #: src/Navigation.tsx:493 1966 + #: src/Navigation.tsx:506 1967 1967 msgid "Content and Media" 1968 1968 msgstr "" 1969 1969 ··· 2154 2154 msgid "Copy TXT record value" 2155 2155 msgstr "" 2156 2156 2157 - #: src/Navigation.tsx:331 2157 + #: src/Navigation.tsx:344 2158 2158 #: src/view/screens/CopyrightPolicy.tsx:31 2159 2159 msgid "Copyright Policy" 2160 2160 msgstr "" ··· 2210 2210 2211 2211 #: src/components/StarterPack/ProfileStarterPacks.tsx:178 2212 2212 #: src/components/StarterPack/ProfileStarterPacks.tsx:287 2213 - #: src/Navigation.tsx:574 2213 + #: src/Navigation.tsx:587 2214 2214 msgid "Create a starter pack" 2215 2215 msgstr "" 2216 2216 ··· 2784 2784 msgid "Edit Moderation List" 2785 2785 msgstr "" 2786 2786 2787 - #: src/Navigation.tsx:341 2787 + #: src/Navigation.tsx:354 2788 2788 #: src/view/screens/Feeds.tsx:518 2789 2789 msgid "Edit My Feeds" 2790 2790 msgstr "" ··· 2826 2826 msgid "Edit who can reply" 2827 2827 msgstr "" 2828 2828 2829 - #: src/Navigation.tsx:579 2829 + #: src/Navigation.tsx:592 2830 2830 msgid "Edit your starter pack" 2831 2831 msgstr "" 2832 2832 ··· 3168 3168 msgid "Explicit sexual images." 3169 3169 msgstr "" 3170 3170 3171 - #: src/Navigation.tsx:736 3171 + #: src/Navigation.tsx:749 3172 3172 #: src/screens/Search/Shell.tsx:307 3173 3173 #: src/view/shell/desktop/LeftNav.tsx:635 3174 3174 #: src/view/shell/Drawer.tsx:403 ··· 3199 3199 msgid "External media may allow websites to collect information about you and your device. No information is sent or requested until you press the \"play\" button." 3200 3200 msgstr "" 3201 3201 3202 - #: src/Navigation.tsx:360 3202 + #: src/Navigation.tsx:373 3203 3203 #: src/screens/Settings/ExternalMediaPreferences.tsx:34 3204 3204 msgid "External Media Preferences" 3205 3205 msgstr "" ··· 3387 3387 msgid "Failed to verify handle. Please try again." 3388 3388 msgstr "" 3389 3389 3390 - #: src/Navigation.tsx:276 3390 + #: src/Navigation.tsx:289 3391 3391 msgid "Feed" 3392 3392 msgstr "" 3393 3393 ··· 3428 3428 msgid "Feedback sent!" 3429 3429 msgstr "" 3430 3430 3431 - #: src/Navigation.tsx:559 3431 + #: src/Navigation.tsx:572 3432 3432 #: src/screens/Search/SearchResults.tsx:68 3433 3433 #: src/screens/StarterPack/StarterPackScreen.tsx:190 3434 3434 #: src/view/screens/Feeds.tsx:511 ··· 3597 3597 msgid "Followed by <0>{0}</0>, <1>{1}</1>, and {2, plural, one {# other} other {# others}}" 3598 3598 msgstr "" 3599 3599 3600 - #: src/Navigation.tsx:230 3600 + #: src/Navigation.tsx:243 3601 3601 msgid "Followers of @{0} that you know" 3602 3602 msgstr "" 3603 3603 ··· 3632 3632 msgid "Following feed preferences" 3633 3633 msgstr "" 3634 3634 3635 - #: src/Navigation.tsx:347 3635 + #: src/Navigation.tsx:360 3636 3636 #: src/screens/Settings/FollowingFeedPreferences.tsx:56 3637 3637 msgid "Following Feed Preferences" 3638 3638 msgstr "" ··· 3919 3919 msgid "Harassment, trolling, or intolerance" 3920 3920 msgstr "" 3921 3921 3922 - #: src/Navigation.tsx:524 3922 + #: src/Navigation.tsx:537 3923 3923 msgid "Hashtag" 3924 3924 msgstr "" 3925 3925 ··· 4076 4076 msgid "Hold up! We’re gradually giving access to video, and you’re still waiting in line. Check back soon!" 4077 4077 msgstr "" 4078 4078 4079 - #: src/Navigation.tsx:731 4080 - #: src/Navigation.tsx:751 4079 + #: src/Navigation.tsx:744 4080 + #: src/Navigation.tsx:764 4081 4081 #: src/view/shell/bottom-bar/BottomBar.tsx:178 4082 4082 #: src/view/shell/desktop/LeftNav.tsx:617 4083 4083 #: src/view/shell/Drawer.tsx:429 ··· 4402 4402 msgid "Language selection" 4403 4403 msgstr "" 4404 4404 4405 - #: src/Navigation.tsx:203 4405 + #: src/Navigation.tsx:216 4406 4406 msgid "Language Settings" 4407 4407 msgstr "" 4408 4408 ··· 4546 4546 msgid "Like 10 posts to train the Discover feed" 4547 4547 msgstr "" 4548 4548 4549 - #: src/Navigation.tsx:437 4549 + #: src/Navigation.tsx:450 4550 4550 msgid "Like notifications" 4551 4551 msgstr "" 4552 4552 ··· 4558 4558 msgid "Like this labeler" 4559 4559 msgstr "" 4560 4560 4561 - #: src/Navigation.tsx:281 4562 - #: src/Navigation.tsx:286 4561 + #: src/Navigation.tsx:294 4562 + #: src/Navigation.tsx:299 4563 4563 msgid "Liked by" 4564 4564 msgstr "" 4565 4565 ··· 4581 4581 msgid "Liked by {likeCount, plural, one {# user} other {# users}}" 4582 4582 msgstr "" 4583 4583 4584 - #: src/lib/hooks/useNotificationHandler.ts:118 4584 + #: src/lib/hooks/useNotificationHandler.ts:126 4585 4585 #: src/screens/Settings/NotificationSettings/index.tsx:126 4586 4586 #: src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx:41 4587 4587 #: src/view/screens/Profile.tsx:229 4588 4588 msgid "Likes" 4589 4589 msgstr "" 4590 4590 4591 - #: src/lib/hooks/useNotificationHandler.ts:160 4591 + #: src/lib/hooks/useNotificationHandler.ts:168 4592 4592 #: src/screens/Settings/NotificationSettings/index.tsx:207 4593 4593 #: src/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings.tsx:41 4594 4594 msgid "Likes of your reposts" 4595 4595 msgstr "" 4596 4596 4597 - #: src/Navigation.tsx:461 4597 + #: src/Navigation.tsx:474 4598 4598 msgid "Likes of your reposts notifications" 4599 4599 msgstr "" 4600 4600 ··· 4610 4610 msgid "Linear" 4611 4611 msgstr "" 4612 4612 4613 - #: src/Navigation.tsx:236 4613 + #: src/Navigation.tsx:249 4614 4614 msgid "List" 4615 4615 msgstr "" 4616 4616 ··· 4672 4672 msgid "List unmuted" 4673 4673 msgstr "" 4674 4674 4675 - #: src/Navigation.tsx:157 4675 + #: src/Navigation.tsx:170 4676 4676 #: src/view/screens/Lists.tsx:65 4677 4677 #: src/view/screens/Profile.tsx:224 4678 4678 #: src/view/screens/Profile.tsx:232 ··· 4725 4725 msgid "Loading..." 4726 4726 msgstr "" 4727 4727 4728 - #: src/Navigation.tsx:306 4728 + #: src/Navigation.tsx:319 4729 4729 msgid "Log" 4730 4730 msgstr "" 4731 4731 ··· 4816 4816 msgid "Media that may be disturbing or inappropriate for some audiences." 4817 4817 msgstr "" 4818 4818 4819 - #: src/Navigation.tsx:421 4819 + #: src/Navigation.tsx:434 4820 4820 msgid "Mention notifications" 4821 4821 msgstr "" 4822 4822 ··· 4828 4828 msgid "Mentioned users" 4829 4829 msgstr "" 4830 4830 4831 - #: src/lib/hooks/useNotificationHandler.ts:139 4831 + #: src/lib/hooks/useNotificationHandler.ts:147 4832 4832 #: src/screens/Settings/NotificationSettings/index.tsx:159 4833 4833 #: src/screens/Settings/NotificationSettings/MentionNotificationSettings.tsx:41 4834 4834 #: src/view/screens/Notifications.tsx:101 ··· 4873 4873 msgid "Message options" 4874 4874 msgstr "" 4875 4875 4876 - #: src/Navigation.tsx:746 4876 + #: src/Navigation.tsx:759 4877 4877 msgid "Messages" 4878 4878 msgstr "" 4879 4879 ··· 4882 4882 msgid "Midnight" 4883 4883 msgstr "" 4884 4884 4885 - #: src/Navigation.tsx:485 4885 + #: src/Navigation.tsx:498 4886 4886 msgid "Miscellaneous notifications" 4887 4887 msgstr "" 4888 4888 ··· 4896 4896 msgid "Misleading Post" 4897 4897 msgstr "" 4898 4898 4899 - #: src/Navigation.tsx:162 4899 + #: src/Navigation.tsx:175 4900 4900 #: src/screens/Moderation/index.tsx:93 4901 4901 #: src/screens/Settings/Settings.tsx:179 4902 4902 #: src/screens/Settings/Settings.tsx:182 ··· 4935 4935 msgid "Moderation lists" 4936 4936 msgstr "" 4937 4937 4938 - #: src/Navigation.tsx:167 4938 + #: src/Navigation.tsx:180 4939 4939 #: src/view/screens/ModerationModlists.tsx:65 4940 4940 msgid "Moderation Lists" 4941 4941 msgstr "" ··· 4944 4944 msgid "moderation settings" 4945 4945 msgstr "" 4946 4946 4947 - #: src/Navigation.tsx:296 4947 + #: src/Navigation.tsx:309 4948 4948 msgid "Moderation states" 4949 4949 msgstr "" 4950 4950 ··· 5067 5067 msgid "Muted accounts" 5068 5068 msgstr "" 5069 5069 5070 - #: src/Navigation.tsx:172 5070 + #: src/Navigation.tsx:185 5071 5071 #: src/view/screens/ModerationMutedAccounts.tsx:118 5072 5072 msgid "Muted Accounts" 5073 5073 msgstr "" ··· 5186 5186 msgid "New Feature" 5187 5187 msgstr "" 5188 5188 5189 - #: src/Navigation.tsx:453 5189 + #: src/Navigation.tsx:466 5190 5190 msgid "New follower notifications" 5191 5191 msgstr "" 5192 5192 5193 - #: src/lib/hooks/useNotificationHandler.ts:153 5193 + #: src/lib/hooks/useNotificationHandler.ts:161 5194 5194 #: src/screens/Settings/NotificationSettings/index.tsx:137 5195 5195 #: src/screens/Settings/NotificationSettings/NewFollowerNotificationSettings.tsx:41 5196 5196 msgid "New followers" ··· 5447 5447 msgid "Not followed by anyone you're following" 5448 5448 msgstr "" 5449 5449 5450 - #: src/Navigation.tsx:152 5450 + #: src/Navigation.tsx:165 5451 5451 #: src/view/screens/Profile.tsx:125 5452 5452 msgid "Not Found" 5453 5453 msgstr "" ··· 5468 5468 msgid "Nothing here" 5469 5469 msgstr "" 5470 5470 5471 - #: src/Navigation.tsx:407 5472 - #: src/Navigation.tsx:554 5471 + #: src/Navigation.tsx:420 5472 + #: src/Navigation.tsx:567 5473 5473 #: src/view/screens/Notifications.tsx:136 5474 5474 msgid "Notification settings" 5475 5475 msgstr "" ··· 5482 5482 msgid "Notification Sounds" 5483 5483 msgstr "" 5484 5484 5485 - #: src/Navigation.tsx:549 5486 - #: src/Navigation.tsx:741 5485 + #: src/Navigation.tsx:562 5486 + #: src/Navigation.tsx:754 5487 5487 #: src/screens/Notifications/ActivityList.tsx:29 5488 5488 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:90 5489 5489 #: src/screens/Settings/NotificationSettings/index.tsx:92 ··· 5902 5902 msgid "People" 5903 5903 msgstr "" 5904 5904 5905 - #: src/Navigation.tsx:223 5905 + #: src/Navigation.tsx:236 5906 5906 msgid "People followed by @{0}" 5907 5907 msgstr "" 5908 5908 5909 - #: src/Navigation.tsx:216 5909 + #: src/Navigation.tsx:229 5910 5910 msgid "People following @{0}" 5911 5911 msgstr "" 5912 5912 ··· 6153 6153 msgid "Post by {0}" 6154 6154 msgstr "" 6155 6155 6156 - #: src/Navigation.tsx:249 6157 - #: src/Navigation.tsx:256 6158 - #: src/Navigation.tsx:263 6159 - #: src/Navigation.tsx:270 6156 + #: src/Navigation.tsx:262 6157 + #: src/Navigation.tsx:269 6158 + #: src/Navigation.tsx:276 6159 + #: src/Navigation.tsx:283 6160 6160 msgid "Post by @{0}" 6161 6161 msgstr "" 6162 6162 ··· 6194 6194 msgid "Post interaction settings" 6195 6195 msgstr "" 6196 6196 6197 - #: src/Navigation.tsx:183 6197 + #: src/Navigation.tsx:196 6198 6198 #: src/screens/ModerationInteractionSettings/index.tsx:34 6199 6199 msgid "Post Interaction Settings" 6200 6200 msgstr "" ··· 6293 6293 msgid "Privacy and security" 6294 6294 msgstr "" 6295 6295 6296 - #: src/Navigation.tsx:392 6297 - #: src/Navigation.tsx:400 6296 + #: src/Navigation.tsx:405 6297 + #: src/Navigation.tsx:413 6298 6298 #: src/screens/Settings/ActivityPrivacySettings.tsx:40 6299 6299 #: src/screens/Settings/PrivacyAndSecuritySettings.tsx:45 6300 6300 msgid "Privacy and Security" ··· 6304 6304 msgid "Privacy and Security settings" 6305 6305 msgstr "" 6306 6306 6307 - #: src/Navigation.tsx:316 6307 + #: src/Navigation.tsx:329 6308 6308 #: src/screens/Settings/AboutSettings.tsx:92 6309 6309 #: src/screens/Settings/AboutSettings.tsx:95 6310 6310 #: src/view/screens/PrivacyPolicy.tsx:31 ··· 6399 6399 msgid "QR code saved to your camera roll!" 6400 6400 msgstr "" 6401 6401 6402 - #: src/Navigation.tsx:429 6402 + #: src/Navigation.tsx:442 6403 6403 msgid "Quote notifications" 6404 6404 msgstr "" 6405 6405 ··· 6429 6429 msgid "Quote settings" 6430 6430 msgstr "" 6431 6431 6432 - #: src/lib/hooks/useNotificationHandler.ts:146 6432 + #: src/lib/hooks/useNotificationHandler.ts:154 6433 6433 #: src/screens/Post/PostQuotes.tsx:38 6434 6434 #: src/screens/Settings/NotificationSettings/index.tsx:170 6435 6435 #: src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx:41 ··· 6694 6694 6695 6695 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:267 6696 6696 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:279 6697 - #: src/lib/hooks/useNotificationHandler.ts:132 6697 + #: src/lib/hooks/useNotificationHandler.ts:140 6698 6698 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:215 6699 6699 #: src/screens/Settings/NotificationSettings/index.tsx:148 6700 6700 #: src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx:41 ··· 6730 6730 msgid "Reply Hidden by You" 6731 6731 msgstr "" 6732 6732 6733 - #: src/Navigation.tsx:413 6733 + #: src/Navigation.tsx:426 6734 6734 msgid "Reply notifications" 6735 6735 msgstr "" 6736 6736 ··· 6882 6882 msgid "Repost ({0, plural, one {# repost} other {# reposts}})" 6883 6883 msgstr "" 6884 6884 6885 - #: src/Navigation.tsx:445 6885 + #: src/Navigation.tsx:458 6886 6886 msgid "Repost notifications" 6887 6887 msgstr "" 6888 6888 ··· 6910 6910 msgid "Reposted by you" 6911 6911 msgstr "" 6912 6912 6913 - #: src/lib/hooks/useNotificationHandler.ts:125 6913 + #: src/lib/hooks/useNotificationHandler.ts:133 6914 6914 #: src/screens/Settings/NotificationSettings/index.tsx:181 6915 6915 #: src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx:41 6916 6916 msgid "Reposts" ··· 6921 6921 msgid "Reposts of this post" 6922 6922 msgstr "" 6923 6923 6924 - #: src/lib/hooks/useNotificationHandler.ts:167 6924 + #: src/lib/hooks/useNotificationHandler.ts:175 6925 6925 #: src/screens/Settings/NotificationSettings/index.tsx:222 6926 6926 #: src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx:41 6927 6927 msgid "Reposts of your reposts" 6928 6928 msgstr "" 6929 6929 6930 - #: src/Navigation.tsx:469 6930 + #: src/Navigation.tsx:482 6931 6931 msgid "Reposts of your reposts notifications" 6932 6932 msgstr "" 6933 6933 ··· 7147 7147 msgid "Search" 7148 7148 msgstr "" 7149 7149 7150 - #: src/Navigation.tsx:242 7150 + #: src/Navigation.tsx:255 7151 7151 #: src/screens/Profile/ProfileSearch.tsx:37 7152 7152 msgid "Search @{0}'s posts" 7153 7153 msgstr "" ··· 7463 7463 msgid "Sets email for password reset" 7464 7464 msgstr "" 7465 7465 7466 - #: src/Navigation.tsx:198 7466 + #: src/Navigation.tsx:211 7467 7467 #: src/screens/Settings/Settings.tsx:92 7468 7468 #: src/view/shell/desktop/LeftNav.tsx:727 7469 7469 #: src/view/shell/Drawer.tsx:572 ··· 7603 7603 msgid "Share your favorite feed!" 7604 7604 msgstr "" 7605 7605 7606 - #: src/Navigation.tsx:301 7606 + #: src/Navigation.tsx:314 7607 7607 msgid "Shared Preferences Tester" 7608 7608 msgstr "" 7609 7609 ··· 7959 7959 msgid "Start chat with {displayName}" 7960 7960 msgstr "" 7961 7961 7962 - #: src/Navigation.tsx:564 7963 - #: src/Navigation.tsx:569 7962 + #: src/Navigation.tsx:577 7963 + #: src/Navigation.tsx:582 7964 7964 #: src/screens/StarterPack/Wizard/index.tsx:186 7965 7965 msgid "Starter Pack" 7966 7966 msgstr "" ··· 8004 8004 msgid "Storage cleared, you need to restart the app now." 8005 8005 msgstr "" 8006 8006 8007 - #: src/Navigation.tsx:291 8007 + #: src/Navigation.tsx:304 8008 8008 #: src/screens/Settings/Settings.tsx:405 8009 8009 msgid "Storybook" 8010 8010 msgstr "" ··· 8085 8085 msgid "Sunset" 8086 8086 msgstr "" 8087 8087 8088 - #: src/Navigation.tsx:311 8088 + #: src/Navigation.tsx:324 8089 8089 #: src/view/screens/Support.tsx:31 8090 8090 #: src/view/screens/Support.tsx:34 8091 8091 msgid "Support" ··· 8180 8180 msgid "Terms" 8181 8181 msgstr "" 8182 8182 8183 - #: src/Navigation.tsx:321 8183 + #: src/Navigation.tsx:334 8184 8184 #: src/screens/Settings/AboutSettings.tsx:84 8185 8185 #: src/screens/Settings/AboutSettings.tsx:87 8186 8186 #: src/view/screens/TermsOfService.tsx:31 ··· 8711 8711 msgid "Threaded mode" 8712 8712 msgstr "" 8713 8713 8714 - #: src/Navigation.tsx:354 8714 + #: src/Navigation.tsx:367 8715 8715 msgid "Threads Preferences" 8716 8716 msgstr "" 8717 8717 ··· 8765 8765 msgid "Top replies first" 8766 8766 msgstr "" 8767 8767 8768 - #: src/Navigation.tsx:529 8768 + #: src/Navigation.tsx:542 8769 8769 msgid "Topic" 8770 8770 msgstr "" 8771 8771 ··· 9232 9232 msgid "Verification settings" 9233 9233 msgstr "" 9234 9234 9235 - #: src/Navigation.tsx:191 9235 + #: src/Navigation.tsx:204 9236 9236 #: src/screens/Moderation/VerificationSettings.tsx:32 9237 9237 msgid "Verification Settings" 9238 9238 msgstr "" ··· 9299 9299 msgid "Video failed to process" 9300 9300 msgstr "" 9301 9301 9302 - #: src/Navigation.tsx:585 9302 + #: src/Navigation.tsx:598 9303 9303 msgid "Video Feed" 9304 9304 msgstr "" 9305 9305 ··· 9823 9823 msgid "You can also temporarily deactivate your account instead, and reactivate it at any time." 9824 9824 msgstr "" 9825 9825 9826 - #: src/lib/hooks/useNotificationHandler.ts:93 9826 + #: src/lib/hooks/useNotificationHandler.ts:101 9827 9827 msgid "You can choose whether chat notifications have sound in the chat settings within the app" 9828 9828 msgstr "" 9829 9829 ··· 10205 10205 msgid "Your full username will be <0>@{0}</0>" 10206 10206 msgstr "" 10207 10207 10208 - #: src/Navigation.tsx:501 10208 + #: src/Navigation.tsx:514 10209 10209 #: src/screens/Search/modules/ExploreInterestsCard.tsx:67 10210 10210 #: src/screens/Settings/ContentAndMediaSettings.tsx:92 10211 10211 #: src/screens/Settings/ContentAndMediaSettings.tsx:95
+1
src/logger/metrics.ts
··· 26 26 } 27 27 'notifications:openApp': { 28 28 reason: NotificationReason 29 + causedBoot: boolean 29 30 } 30 31 'notifications:request': { 31 32 context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home'
+18
src/state/queries/notifications/util.ts
··· 7 7 AppBskyGraphStarterpack, 8 8 type AppBskyNotificationListNotifications, 9 9 type BskyAgent, 10 + hasMutedWord, 10 11 moderateNotification, 11 12 type ModerationOpts, 12 13 } from '@atproto/api' ··· 130 131 } 131 132 if (!moderationOpts) { 132 133 return false 134 + } 135 + if ( 136 + notif.reason === 'subscribed-post' && 137 + bsky.dangerousIsType<AppBskyFeedPost.Record>( 138 + notif.record, 139 + AppBskyFeedPost.isRecord, 140 + ) && 141 + hasMutedWord({ 142 + mutedWords: moderationOpts.prefs.mutedWords, 143 + text: notif.record.text, 144 + facets: notif.record.facets, 145 + outlineTags: notif.record.tags, 146 + languages: notif.record.langs, 147 + actor: notif.author, 148 + }) 149 + ) { 150 + return true 133 151 } 134 152 if (notif.author.viewer?.following) { 135 153 return false
+7 -1
src/view/com/notifications/NotificationFeed.tsx
··· 59 59 enabled: enabled && !!moderationOpts, 60 60 filter, 61 61 }) 62 - const isEmpty = !isFetching && !data?.pages[0]?.items.length 62 + // previously, this was `!isFetching && !data?.pages[0]?.items.length` 63 + // however, if the first page had no items (can happen in the mentions tab!) 64 + // it would flicker the empty state whenever it was loading. 65 + // therefore, we need to find if *any* page has items. in 99.9% of cases, 66 + // the `.find()` won't need to go any further than the first page -sfn 67 + const isEmpty = 68 + !isFetching && !data?.pages.find(page => page.items.length > 0) 63 69 64 70 const items = React.useMemo(() => { 65 71 let arr: any[] = []
+1
src/view/com/util/PostMeta.tsx
··· 122 122 </View> 123 123 )} 124 124 <WebOnlyInlineLinkText 125 + emoji 125 126 numberOfLines={1} 126 127 to={profileLink} 127 128 label={_(msg`View profile`)}