Bluesky app fork with some witchin' additions 💫

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

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