Bluesky app fork with some witchin' additions 💫

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

Changed files
+614 -358
.github
modules
expo-background-notification-handler
android
src
main
java
expo
modules
backgroundnotificationhandler
scripts
src
components
Post
Embed
VideoEmbed
VideoEmbedInner
web-controls
ProfileHoverCard
dialogs
lib
locale
locales
logger
__tests__
transports
screens
PostThread
Settings
state
messages
convo
events
queries
notifications
postgate
view
+9 -1
.github/workflows/bundle-deploy-eas-update.yml
··· 5 5 push: 6 6 branches: 7 7 - main 8 + - hailey/eas-fab 8 9 workflow_dispatch: 9 10 inputs: 10 11 channel: ··· 118 119 119 120 - name: 🏗️ Create Bundle 120 121 if: ${{ !steps.fingerprint.outputs.includes-changes }} 121 - run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export 122 + run: | 123 + SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export-ios 124 + mv ./dist ./ios-dist 125 + sed -i 's/"react-native-mmkv": "\^2\.12\.2"/"react-native-mmkv": "^3.3.0"/' package.json 126 + yarn install 127 + SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export-android 128 + mv ./dist ./android-dist 129 + 122 130 123 131 - name: 📦 Package Bundle and 🚀 Deploy 124 132 if: ${{ !steps.fingerprint.outputs.includes-changes }}
+15
modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt
··· 15 15 16 16 if (remoteMessage.data["reason"] == "chat-message") { 17 17 mutateWithChatMessage(remoteMessage) 18 + } else { 19 + mutateWithOtherReason(remoteMessage) 18 20 } 19 21 20 22 notifInterface.showMessage(remoteMessage) ··· 38 40 39 41 // TODO - Remove this once we have more backend capability 40 42 remoteMessage.data["badge"] = null 43 + } 44 + 45 + private fun mutateWithOtherReason(remoteMessage: RemoteMessage) { 46 + // If oreo or higher 47 + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { 48 + // If one of "like", "repost", "follow", "mention", "reply", "quote", "like-via-repost", "repost-via-repost" 49 + // assign to it's eponymous channel. otherwise do nothing, let expo handle it 50 + when (remoteMessage.data["reason"]) { 51 + "like", "repost", "follow", "mention", "reply", "quote", "like-via-repost", "repost-via-repost" -> { 52 + remoteMessage.data["channelId"] = remoteMessage.data["reason"] 53 + } 54 + } 55 + } 41 56 } 42 57 }
+3 -2
package.json
··· 61 61 "intl:push": "crowdin push translations --verbose -b main", 62 62 "nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android", 63 63 "update-extensions": "bash scripts/updateExtensions.sh", 64 - "export": "npx expo export --dump-sourcemap && yarn upload-native-sourcemaps", 64 + "export-ios": "npx expo export --platform ios --dump-sourcemap && yarn upload-native-sourcemaps", 65 + "export-android": "npx expo export --platform android --dump-sourcemap && yarn upload-native-sourcemaps", 65 66 "upload-native-sourcemaps": "npx sentry-expo-upload-sourcemaps dist", 66 67 "make-deploy-bundle": "bash scripts/bundleUpdate.sh", 67 68 "generate-webpack-stats-file": "EXPO_PUBLIC_GENERATE_STATS=1 yarn build-web", ··· 190 191 "react-native-gesture-handler": "2.25.0", 191 192 "react-native-get-random-values": "~1.11.0", 192 193 "react-native-ios-context-menu": "^1.15.3", 193 - "react-native-keyboard-controller": "^1.17.1", 194 + "react-native-keyboard-controller": "^1.17.5", 194 195 "react-native-mmkv": "^2.12.2", 195 196 "react-native-pager-view": "^6.7.1", 196 197 "react-native-progress": "bluesky-social/react-native-progress",
+17 -9
scripts/bundleUpdate.js
··· 3 3 const fsp = fs.promises 4 4 const path = require('path') 5 5 6 - const DIST_DIR = './dist' 6 + const IOS_DIST_DIR = './ios-dist' 7 + const ANDROID_DIST_DIR = './android-dist' 8 + 7 9 const BUNDLES_DIR = '/_expo/static/js' 8 - const IOS_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/ios') 9 - const ANDROID_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/android') 10 - const METADATA_PATH = path.join(DIST_DIR, '/metadata.json') 10 + 11 + const IOS_BUNDLE_DIR = path.join(IOS_DIST_DIR, BUNDLES_DIR, '/ios') 12 + const ANDROID_BUNDLE_DIR = path.join(ANDROID_DIST_DIR, BUNDLES_DIR, '/android') 13 + 14 + const IOS_METADATA_PATH = path.join(IOS_DIST_DIR, '/metadata.json') 15 + const ANDROID_METADATA_PATH = path.join(ANDROID_DIST_DIR, '/metadata.json') 16 + 11 17 const DEST_DIR = './bundleTempDir' 12 18 13 19 // Weird, don't feel like figuring out _why_ it wants this 14 - const METADATA = require(`../${METADATA_PATH}`) 15 - const IOS_METADATA_ASSETS = METADATA.fileMetadata.ios.assets 16 - const ANDROID_METADATA_ASSETS = METADATA.fileMetadata.android.assets 20 + const IOS_METADATA = require(`../${IOS_METADATA_PATH}`) 21 + const ANDROID_METADATA = require(`../${ANDROID_METADATA_PATH}`) 22 + 23 + const IOS_METADATA_ASSETS = IOS_METADATA.fileMetadata.ios.assets 24 + const ANDROID_METADATA_ASSETS = ANDROID_METADATA.fileMetadata.android.assets 17 25 18 26 const getMd5 = async path => { 19 27 return new Promise(res => { ··· 60 68 61 69 console.log('Getting ios asset md5s and moving them...') 62 70 for (const asset of IOS_METADATA_ASSETS) { 63 - const currPath = path.join(DIST_DIR, asset.path) 71 + const currPath = path.join(IOS_DIST_DIR, asset.path) 64 72 const md5 = await getMd5(currPath) 65 73 const withExtPath = `assets/${md5}.${asset.ext}` 66 74 iosAssets.push(withExtPath) ··· 69 77 70 78 console.log('Getting android asset md5s and moving them...') 71 79 for (const asset of ANDROID_METADATA_ASSETS) { 72 - const currPath = path.join(DIST_DIR, asset.path) 80 + const currPath = path.join(ANDROID_DIST_DIR, asset.path) 73 81 const md5 = await getMd5(currPath) 74 82 const withExtPath = `assets/${md5}.${asset.ext}` 75 83 androidAssets.push(withExtPath)
+2
src/App.native.tsx
··· 64 64 import {ThemeProvider as Alf} from '#/alf' 65 65 import {useColorModeTheme} from '#/alf/util/useColorModeTheme' 66 66 import {Provider as ContextMenuProvider} from '#/components/ContextMenu' 67 + import {NuxDialogs} from '#/components/dialogs/nuxs' 67 68 import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry' 68 69 import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs' 69 70 import {Provider as PortalProvider} from '#/components/Portal' ··· 156 157 <IntentDialogProvider> 157 158 <TestCtrls /> 158 159 <Shell /> 160 + <NuxDialogs /> 159 161 </IntentDialogProvider> 160 162 </GestureHandlerRootView> 161 163 </HideBottomBarBorderProvider>
+2
src/App.web.tsx
··· 54 54 import {ThemeProvider as Alf} from '#/alf' 55 55 import {useColorModeTheme} from '#/alf/util/useColorModeTheme' 56 56 import {Provider as ContextMenuProvider} from '#/components/ContextMenu' 57 + import {NuxDialogs} from '#/components/dialogs/nuxs' 57 58 import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry' 58 59 import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs' 59 60 import {Provider as PortalProvider} from '#/components/Portal' ··· 134 135 <HideBottomBarBorderProvider> 135 136 <IntentDialogProvider> 136 137 <Shell /> 138 + <NuxDialogs /> 137 139 </IntentDialogProvider> 138 140 </HideBottomBarBorderProvider> 139 141 </ServiceConfigProvider>
+16 -3
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils.tsx
··· 1 1 import {type RefObject, useCallback, useEffect, useRef, useState} from 'react' 2 2 3 3 import {isSafari} from '#/lib/browser' 4 + import {logger} from '#/logger' 4 5 import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 5 6 6 7 export function useVideoElement(ref: RefObject<HTMLVideoElement>) { ··· 79 80 await ref.current.play() 80 81 } catch (e: any) { 81 82 if ( 82 - !e.message?.includes(`The request is not allowed by the user agent`) 83 + !e.message?.includes( 84 + `The request is not allowed by the user agent`, 85 + ) && 86 + !e.message?.includes( 87 + `The play() request was interrupted by a call to pause()`, 88 + ) 83 89 ) { 84 90 throw e 85 91 } ··· 176 182 } else { 177 183 const promise = ref.current.play() 178 184 if (promise !== undefined) { 179 - promise.catch(err => { 180 - console.error('Error playing video:', err) 185 + promise.catch((err: any) => { 186 + if ( 187 + // ignore this common error. it's fine 188 + !err.message?.includes( 189 + `The play() request was interrupted by a call to pause()`, 190 + ) 191 + ) { 192 + logger.error('Error playing video:', {message: err}) 193 + } 181 194 }) 182 195 } 183 196 }
+1 -1
src/components/ProfileHoverCard/index.web.tsx
··· 74 74 return props.children 75 75 } else { 76 76 return ( 77 - <View onPointerMove={onPointerMove} style={[a.flex_shrink]}> 77 + <View onPointerMove={onPointerMove} style={[a.flex_shrink, props.style]}> 78 78 <ProfileHoverCardInner {...props} /> 79 79 </View> 80 80 )
+4 -2
src/components/ProfileHoverCard/types.ts
··· 1 1 import type React from 'react' 2 2 3 - export type ProfileHoverCardProps = { 4 - children: React.ReactElement 3 + import {type ViewStyleProp} from '#/alf' 4 + 5 + export type ProfileHoverCardProps = ViewStyleProp & { 6 + children: React.ReactNode 5 7 did: string 6 8 disable?: boolean 7 9 }
+9 -4
src/components/dialogs/PostInteractionSettingsDialog.tsx
··· 1 1 import React from 'react' 2 - import {StyleProp, View, ViewStyle} from 'react-native' 3 - import {AppBskyFeedDefs, AppBskyFeedPostgate, AtUri} from '@atproto/api' 2 + import {type StyleProp, View, type ViewStyle} from 'react-native' 3 + import { 4 + type AppBskyFeedDefs, 5 + type AppBskyFeedPostgate, 6 + AtUri, 7 + } from '@atproto/api' 4 8 import {msg, Trans} from '@lingui/macro' 5 9 import {useLingui} from '@lingui/react' 6 10 import {useQueryClient} from '@tanstack/react-query' ··· 22 26 import { 23 27 createThreadgateViewQueryKey, 24 28 getThreadgateView, 25 - ThreadgateAllowUISetting, 29 + type ThreadgateAllowUISetting, 26 30 threadgateViewToAllowUISetting, 27 31 useSetThreadgateAllowMutation, 28 32 useThreadgateViewQuery, ··· 558 562 await Promise.all([ 559 563 queryClient.prefetchQuery({ 560 564 queryKey: createPostgateQueryKey(postUri), 561 - queryFn: () => getPostgateRecord({agent, postUri}), 565 + queryFn: () => 566 + getPostgateRecord({agent, postUri}).then(res => res ?? null), 562 567 staleTime: STALE.SECONDS.THIRTY, 563 568 }), 564 569 queryClient.prefetchQuery({
+100 -8
src/lib/hooks/useNotificationHandler.ts
··· 1 - import React from 'react' 1 + import {useEffect} from 'react' 2 2 import * as Notifications from 'expo-notifications' 3 + import {type AppBskyNotificationListNotifications} from '@atproto/api' 4 + import {msg} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 3 6 import {CommonActions, useNavigation} from '@react-navigation/native' 4 7 import {useQueryClient} from '@tanstack/react-query' 5 8 ··· 25 28 | 'quote' 26 29 | 'chat-message' 27 30 | 'starterpack-joined' 31 + | 'like-via-repost' 32 + | 'repost-via-repost' 33 + | 'verified' 34 + | 'unverified' 28 35 29 36 /** 30 37 * Manually overridden type, but retains the possibility of ··· 66 73 const {currentConvoId} = useCurrentConvoId() 67 74 const {setShowLoggedOut} = useLoggedOutViewControls() 68 75 const closeAllActiveElements = useCloseAllActiveElements() 76 + const {_} = useLingui() 69 77 70 78 // On Android, we cannot control which sound is used for a notification on Android 71 79 // 28 or higher. Instead, we have to configure a notification channel ahead of time 72 80 // which has the sounds we want in the configuration for that channel. These two 73 81 // channels allow for the mute/unmute functionality we want for the background 74 82 // handler. 75 - React.useEffect(() => { 83 + useEffect(() => { 76 84 if (!isAndroid) return 85 + // assign both chat notifications to a group 86 + // NOTE: I don't think that it will retroactively move them into the group 87 + // if the channels already exist. no big deal imo -sfn 88 + const CHAT_GROUP = 'chat' 89 + Notifications.setNotificationChannelGroupAsync(CHAT_GROUP, { 90 + name: _(msg`Chat`), 91 + description: _( 92 + msg`You can choose whether chat notifications have sound in the chat settings within the app`, 93 + ), 94 + }) 77 95 Notifications.setNotificationChannelAsync('chat-messages', { 78 - name: 'Chat', 96 + name: _(msg`Chat messages - sound`), 97 + groupId: CHAT_GROUP, 79 98 importance: Notifications.AndroidImportance.MAX, 80 99 sound: 'dm.mp3', 81 100 showBadge: true, 82 101 vibrationPattern: [250], 83 102 lockscreenVisibility: Notifications.AndroidNotificationVisibility.PRIVATE, 84 103 }) 85 - 86 104 Notifications.setNotificationChannelAsync('chat-messages-muted', { 87 - name: 'Chat - Muted', 105 + name: _(msg`Chat messages - silent`), 106 + groupId: CHAT_GROUP, 88 107 importance: Notifications.AndroidImportance.MAX, 89 108 sound: null, 90 109 showBadge: true, 91 110 vibrationPattern: [250], 92 111 lockscreenVisibility: Notifications.AndroidNotificationVisibility.PRIVATE, 93 112 }) 94 - }, []) 95 113 96 - React.useEffect(() => { 114 + Notifications.setNotificationChannelAsync( 115 + 'like' satisfies AppBskyNotificationListNotifications.Notification['reason'], 116 + { 117 + name: _(msg`Likes`), 118 + importance: Notifications.AndroidImportance.HIGH, 119 + }, 120 + ) 121 + Notifications.setNotificationChannelAsync( 122 + 'repost' satisfies AppBskyNotificationListNotifications.Notification['reason'], 123 + { 124 + name: _(msg`Reposts`), 125 + importance: Notifications.AndroidImportance.HIGH, 126 + }, 127 + ) 128 + Notifications.setNotificationChannelAsync( 129 + 'reply' satisfies AppBskyNotificationListNotifications.Notification['reason'], 130 + { 131 + name: _(msg`Replies`), 132 + importance: Notifications.AndroidImportance.HIGH, 133 + }, 134 + ) 135 + Notifications.setNotificationChannelAsync( 136 + 'mention' satisfies AppBskyNotificationListNotifications.Notification['reason'], 137 + { 138 + name: _(msg`Mentions`), 139 + importance: Notifications.AndroidImportance.HIGH, 140 + }, 141 + ) 142 + Notifications.setNotificationChannelAsync( 143 + 'quote' satisfies AppBskyNotificationListNotifications.Notification['reason'], 144 + { 145 + name: _(msg`Quotes`), 146 + importance: Notifications.AndroidImportance.HIGH, 147 + }, 148 + ) 149 + Notifications.setNotificationChannelAsync( 150 + 'follow' satisfies AppBskyNotificationListNotifications.Notification['reason'], 151 + { 152 + name: _(msg`New followers`), 153 + importance: Notifications.AndroidImportance.HIGH, 154 + }, 155 + ) 156 + Notifications.setNotificationChannelAsync( 157 + 'like-via-repost' satisfies AppBskyNotificationListNotifications.Notification['reason'], 158 + { 159 + name: _(msg`Likes of your reposts`), 160 + importance: Notifications.AndroidImportance.HIGH, 161 + }, 162 + ) 163 + Notifications.setNotificationChannelAsync( 164 + 'repost-via-repost' satisfies AppBskyNotificationListNotifications.Notification['reason'], 165 + { 166 + name: _(msg`Reposts of your reposts`), 167 + importance: Notifications.AndroidImportance.HIGH, 168 + }, 169 + ) 170 + }, [_]) 171 + 172 + useEffect(() => { 97 173 const handleNotification = (payload?: NotificationPayload) => { 98 174 if (!payload) return 99 175 ··· 151 227 case 'quote': 152 228 case 'reply': 153 229 case 'starterpack-joined': 230 + case 'like-via-repost': 231 + case 'repost-via-repost': 232 + case 'verified': 233 + case 'unverified': 154 234 resetToTab('NotificationsTab') 155 235 break 156 236 // TODO implement these after we have an idea of how to handle each individual case ··· 242 322 const payload = e.notification.request.trigger 243 323 .payload as NotificationPayload 244 324 245 - if (!payload) return 325 + if (!payload) { 326 + logger.error('useNotificationsHandler: received no payload', { 327 + identifier: e.notification.request.identifier, 328 + }) 329 + return 330 + } 331 + if (!payload.reason) { 332 + logger.error('useNotificationsHandler: received unknown payload', { 333 + payload, 334 + identifier: e.notification.request.identifier, 335 + }) 336 + return 337 + } 246 338 247 339 logger.debug( 248 340 'User pressed a notification, opening notifications tab',
+5
src/lib/hooks/useOpenLink.ts
··· 11 11 isRelativeUrl, 12 12 toNiceDomain, 13 13 } from '#/lib/strings/url-helpers' 14 + import {logger} from '#/logger' 14 15 import {isNative} from '#/platform/detection' 15 16 import {useInAppBrowser} from '#/state/preferences/in-app-browser' 16 17 import {useTheme} from '#/alf' ··· 64 65 toolbarColor: t.atoms.bg.backgroundColor, 65 66 controlsColor: t.palette.primary_500, 66 67 createTask: false, 68 + }).catch(err => { 69 + if (__DEV__) 70 + logger.error('Could not open web browser', {message: err}) 71 + Linking.openURL(url) 67 72 }), 68 73 ) 69 74 return
+1
src/lib/statsig/gates.ts
··· 7 7 | 'old_postonboarding' 8 8 | 'onboarding_add_video_feed' 9 9 | 'post_threads_v2_unspecced' 10 + | 'reengagement_features' 10 11 | 'remove_show_latest_button' 11 12 | 'test_gate_1' 12 13 | 'test_gate_2'
+190 -155
src/locale/locales/en/messages.po
··· 95 95 msgid "{0, plural, one {following} other {following}}" 96 96 msgstr "" 97 97 98 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:474 98 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:468 99 99 #: src/view/com/post-thread/PostThreadItem.tsx:541 100 100 msgid "{0, plural, one {like} other {likes}}" 101 101 msgstr "" ··· 104 104 msgid "{0, plural, one {post} other {posts}}" 105 105 msgstr "" 106 106 107 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:458 107 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:452 108 108 #: src/view/com/post-thread/PostThreadItem.tsx:525 109 109 msgid "{0, plural, one {quote} other {quotes}}" 110 110 msgstr "" 111 111 112 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:440 112 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:434 113 113 #: src/view/com/post-thread/PostThreadItem.tsx:507 114 114 msgid "{0, plural, one {repost} other {reposts}}" 115 115 msgstr "" ··· 499 499 500 500 #: src/Navigation.tsx:490 501 501 #: src/screens/Settings/AboutSettings.tsx:75 502 - #: src/screens/Settings/Settings.tsx:234 503 - #: src/screens/Settings/Settings.tsx:237 502 + #: src/screens/Settings/Settings.tsx:238 503 + #: src/screens/Settings/Settings.tsx:241 504 504 msgid "About" 505 505 msgstr "" 506 506 ··· 519 519 msgstr "" 520 520 521 521 #: src/screens/Settings/AccessibilitySettings.tsx:46 522 - #: src/screens/Settings/Settings.tsx:210 523 - #: src/screens/Settings/Settings.tsx:213 522 + #: src/screens/Settings/Settings.tsx:214 523 + #: src/screens/Settings/Settings.tsx:217 524 524 msgid "Accessibility" 525 525 msgstr "" 526 526 ··· 531 531 #: src/Navigation.tsx:381 532 532 #: src/screens/Login/LoginForm.tsx:194 533 533 #: src/screens/Settings/AccountSettings.tsx:48 534 - #: src/screens/Settings/Settings.tsx:164 535 - #: src/screens/Settings/Settings.tsx:167 534 + #: src/screens/Settings/Settings.tsx:166 535 + #: src/screens/Settings/Settings.tsx:169 536 536 msgid "Account" 537 537 msgstr "" 538 538 ··· 563 563 msgid "Account Muted by List" 564 564 msgstr "" 565 565 566 - #: src/screens/Settings/Settings.tsx:514 566 + #: src/screens/Settings/Settings.tsx:518 567 567 msgid "Account options" 568 568 msgstr "" 569 569 570 - #: src/screens/Settings/Settings.tsx:550 570 + #: src/screens/Settings/Settings.tsx:554 571 571 msgid "Account removed from quick access" 572 572 msgstr "" 573 573 ··· 639 639 msgid "Add alt text (optional)" 640 640 msgstr "" 641 641 642 - #: src/screens/Settings/Settings.tsx:454 643 - #: src/screens/Settings/Settings.tsx:457 642 + #: src/screens/Settings/Settings.tsx:458 643 + #: src/screens/Settings/Settings.tsx:461 644 644 #: src/view/shell/desktop/LeftNav.tsx:260 645 645 #: src/view/shell/desktop/LeftNav.tsx:264 646 646 msgid "Add another account" ··· 772 772 msgid "alice@example.com" 773 773 msgstr "" 774 774 775 - #: src/view/screens/Notifications.tsx:86 775 + #: src/view/screens/Notifications.tsx:88 776 776 msgid "All" 777 777 msgstr "" 778 778 ··· 800 800 msgid "Allow new messages from" 801 801 msgstr "" 802 802 803 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:344 803 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:348 804 804 msgid "Allow quote posts" 805 805 msgstr "" 806 806 807 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:389 807 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:393 808 808 msgid "Allow replies from:" 809 809 msgstr "" 810 810 ··· 1040 1040 1041 1041 #: src/Navigation.tsx:373 1042 1042 #: src/screens/Settings/AppearanceSettings.tsx:85 1043 - #: src/screens/Settings/Settings.tsx:202 1044 - #: src/screens/Settings/Settings.tsx:205 1043 + #: src/screens/Settings/Settings.tsx:206 1044 + #: src/screens/Settings/Settings.tsx:209 1045 1045 msgid "Appearance" 1046 1046 msgstr "" 1047 1047 ··· 1050 1050 msgid "Apply default recommended feeds" 1051 1051 msgstr "" 1052 1052 1053 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:640 1053 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:634 1054 1054 #: src/view/com/post-thread/PostThreadItem.tsx:955 1055 1055 msgid "Archived from {0}" 1056 1056 msgstr "" 1057 1057 1058 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:609 1059 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:648 1058 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:603 1059 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:642 1060 1060 #: src/view/com/post-thread/PostThreadItem.tsx:924 1061 1061 #: src/view/com/post-thread/PostThreadItem.tsx:963 1062 1062 msgid "Archived post" ··· 1297 1297 msgid "Bluesky" 1298 1298 msgstr "" 1299 1299 1300 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:665 1300 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:659 1301 1301 #: src/view/com/post-thread/PostThreadItem.tsx:980 1302 1302 msgid "Bluesky cannot confirm the authenticity of the claimed date." 1303 1303 msgstr "" ··· 1455 1455 #: src/screens/Settings/AppIconSettings/index.tsx:225 1456 1456 #: src/screens/Settings/components/ChangeHandleDialog.tsx:78 1457 1457 #: src/screens/Settings/components/ChangeHandleDialog.tsx:85 1458 - #: src/screens/Settings/Settings.tsx:279 1458 + #: src/screens/Settings/Settings.tsx:283 1459 1459 #: src/screens/Takendown.tsx:99 1460 1460 #: src/screens/Takendown.tsx:102 1461 1461 #: src/view/com/composer/Composer.tsx:960 ··· 1466 1466 #: src/view/com/modals/ChangePassword.tsx:282 1467 1467 #: src/view/com/modals/CreateOrEditList.tsx:333 1468 1468 #: src/view/com/modals/CropImage.web.tsx:97 1469 - #: src/view/com/modals/LinkWarning.tsx:105 1470 - #: src/view/com/modals/LinkWarning.tsx:107 1471 1469 #: src/view/shell/desktop/LeftNav.tsx:213 1472 1470 msgid "Cancel" 1473 1471 msgstr "" ··· 1498 1496 1499 1497 #: src/screens/Search/Shell.tsx:341 1500 1498 msgid "Cancel search" 1501 - msgstr "" 1502 - 1503 - #: src/view/com/modals/LinkWarning.tsx:106 1504 - msgid "Cancels opening the linked website" 1505 1499 msgstr "" 1506 1500 1507 1501 #: src/components/PostControls/index.tsx:101 ··· 1567 1561 msgid "Changes hosting provider" 1568 1562 msgstr "" 1569 1563 1564 + #: src/lib/hooks/useNotificationHandler.ts:90 1570 1565 #: src/Navigation.tsx:515 1571 1566 #: src/view/shell/bottom-bar/BottomBar.tsx:221 1572 1567 #: src/view/shell/desktop/LeftNav.tsx:553 ··· 1580 1575 msgid "Chat deleted" 1581 1576 msgstr "" 1582 1577 1578 + #: src/lib/hooks/useNotificationHandler.ts:105 1579 + msgid "Chat messages - silent" 1580 + msgstr "" 1581 + 1582 + #: src/lib/hooks/useNotificationHandler.ts:96 1583 + msgid "Chat messages - sound" 1584 + msgstr "" 1585 + 1583 1586 #: src/components/dms/ConvoMenu.tsx:176 1584 1587 msgctxt "toast" 1585 1588 msgid "Chat muted" ··· 1668 1671 msgid "Choose your username" 1669 1672 msgstr "" 1670 1673 1671 - #: src/screens/Settings/Settings.tsx:432 1674 + #: src/screens/Settings/Settings.tsx:436 1672 1675 msgid "Clear all storage data" 1673 1676 msgstr "" 1674 1677 1675 - #: src/screens/Settings/Settings.tsx:434 1678 + #: src/screens/Settings/Settings.tsx:438 1676 1679 msgid "Clear all storage data (restart after this)" 1677 1680 msgstr "" 1678 1681 ··· 1697 1700 msgid "Click here to update your email" 1698 1701 msgstr "" 1699 1702 1700 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:337 1703 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:341 1701 1704 msgid "Click to disable quote posts of this post." 1702 1705 msgstr "" 1703 1706 1704 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:338 1707 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:342 1705 1708 msgid "Click to enable quote posts of this post." 1706 1709 msgstr "" 1707 1710 ··· 1762 1765 msgid "Close dialog" 1763 1766 msgstr "" 1764 1767 1765 - #: src/view/shell/index.web.tsx:85 1768 + #: src/view/shell/index.web.tsx:87 1766 1769 msgid "Close drawer menu" 1767 1770 msgstr "" 1768 1771 ··· 1920 1923 msgstr "" 1921 1924 1922 1925 #: src/screens/Settings/AccessibilitySettings.tsx:109 1923 - #: src/screens/Settings/Settings.tsx:194 1924 - #: src/screens/Settings/Settings.tsx:197 1926 + #: src/screens/Settings/Settings.tsx:198 1927 + #: src/screens/Settings/Settings.tsx:201 1925 1928 msgid "Content and media" 1926 1929 msgstr "" 1927 1930 ··· 2142 2145 msgid "Could not process your video" 2143 2146 msgstr "" 2144 2147 2145 - #: src/state/queries/notifications/settings.ts:47 2148 + #: src/state/queries/notifications/settings.ts:50 2146 2149 msgid "Could not update notification settings" 2147 2150 msgstr "" 2148 2151 ··· 2229 2232 msgid "Customization options" 2230 2233 msgstr "" 2231 2234 2232 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:103 2235 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:107 2233 2236 msgid "Customize who can interact with this post." 2234 2237 msgstr "" 2235 2238 ··· 2267 2270 msgid "Deactivate account" 2268 2271 msgstr "" 2269 2272 2270 - #: src/screens/Settings/Settings.tsx:406 2273 + #: src/screens/Settings/Settings.tsx:410 2271 2274 msgid "Debug Moderation" 2272 2275 msgstr "" 2273 2276 ··· 2316 2319 msgid "Delete chat" 2317 2320 msgstr "" 2318 2321 2319 - #: src/screens/Settings/Settings.tsx:413 2322 + #: src/screens/Settings/Settings.tsx:417 2320 2323 msgid "Delete chat declaration record" 2321 2324 msgstr "" 2322 2325 ··· 2425 2428 msgid "Developer mode enabled" 2426 2429 msgstr "" 2427 2430 2428 - #: src/screens/Settings/Settings.tsx:261 2429 - #: src/screens/Settings/Settings.tsx:264 2431 + #: src/screens/Settings/Settings.tsx:265 2432 + #: src/screens/Settings/Settings.tsx:268 2430 2433 msgid "Developer options" 2431 2434 msgstr "" 2432 2435 ··· 2731 2734 msgid "Edit People" 2732 2735 msgstr "" 2733 2736 2734 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:69 2735 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:239 2737 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:73 2738 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:243 2736 2739 msgid "Edit post interaction settings" 2737 2740 msgstr "" 2738 2741 ··· 2977 2980 msgid "Error: {error}" 2978 2981 msgstr "" 2979 2982 2980 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:394 2983 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:398 2981 2984 msgid "Everybody" 2982 2985 msgstr "" 2983 2986 ··· 2991 2994 2992 2995 #: src/screens/Messages/Settings.tsx:83 2993 2996 #: src/screens/Messages/Settings.tsx:86 2994 - #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:153 2995 - #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:167 2997 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:160 2998 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:174 2996 2999 msgid "Everyone" 2997 3000 msgstr "" 2998 3001 ··· 3245 3248 msgid "Failed to save settings. Please try again." 3246 3249 msgstr "" 3247 3250 3248 - #: src/screens/Settings/InterestsSettings.tsx:136 3251 + #: src/screens/Settings/InterestsSettings.tsx:132 3249 3252 msgctxt "toast" 3250 3253 msgid "Failed to save your interests." 3251 3254 msgstr "" ··· 3372 3375 3373 3376 #: src/screens/Search/components/SearchLanguageDropdown.tsx:70 3374 3377 msgid "Filter search by language (currently: {currentLanguageLabel})" 3378 + msgstr "" 3379 + 3380 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:154 3381 + msgid "Filter who you receive notifications from" 3375 3382 msgstr "" 3376 3383 3377 3384 #: src/screens/Onboarding/StepFinished.tsx:314 ··· 3589 3596 msgid "Frequently Posts Unwanted Content" 3590 3597 msgstr "" 3591 3598 3599 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:150 3600 + msgid "From" 3601 + msgstr "" 3602 + 3592 3603 #: src/screens/Hashtag.tsx:120 3593 3604 msgid "From @{sanitizedAuthor}" 3594 3605 msgstr "" ··· 3664 3675 msgid "Glaring violations of law or terms of service" 3665 3676 msgstr "" 3666 3677 3678 + #: src/components/dialogs/LinkWarning.tsx:111 3679 + #: src/components/dialogs/LinkWarning.tsx:117 3667 3680 #: src/components/Layout/Header/index.tsx:127 3668 3681 #: src/components/moderation/ScreenHider.tsx:154 3669 3682 #: src/components/moderation/ScreenHider.tsx:163 ··· 3799 3812 msgid "Having trouble?" 3800 3813 msgstr "" 3801 3814 3802 - #: src/screens/Settings/Settings.tsx:226 3803 3815 #: src/screens/Settings/Settings.tsx:230 3816 + #: src/screens/Settings/Settings.tsx:234 3804 3817 #: src/view/shell/desktop/RightNav.tsx:120 3805 3818 #: src/view/shell/desktop/RightNav.tsx:121 3806 3819 #: src/view/shell/Drawer.tsx:370 ··· 4067 4080 msgid "In-app" 4068 4081 msgstr "" 4069 4082 4070 - #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:134 4083 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:139 4071 4084 msgid "In-app notifications" 4072 4085 msgstr "" 4073 4086 ··· 4258 4271 msgstr "" 4259 4272 4260 4273 #: src/screens/Settings/LanguageSettings.tsx:78 4261 - #: src/screens/Settings/Settings.tsx:218 4262 - #: src/screens/Settings/Settings.tsx:221 4274 + #: src/screens/Settings/Settings.tsx:222 4275 + #: src/screens/Settings/Settings.tsx:225 4263 4276 msgid "Languages" 4264 4277 msgstr "" 4265 4278 ··· 4346 4359 msgid "Leave them all unchecked to see any language." 4347 4360 msgstr "" 4348 4361 4349 - #: src/view/com/modals/LinkWarning.tsx:65 4362 + #: src/components/dialogs/LinkWarning.tsx:67 4363 + #: src/components/dialogs/LinkWarning.tsx:75 4350 4364 msgid "Leaving Bluesky" 4351 4365 msgstr "" 4352 4366 ··· 4431 4445 msgid "Liked by {likeCount, plural, one {# user} other {# users}}" 4432 4446 msgstr "" 4433 4447 4434 - #: src/screens/Settings/NotificationSettings/index.tsx:159 4448 + #: src/lib/hooks/useNotificationHandler.ts:117 4449 + #: src/screens/Settings/NotificationSettings/index.tsx:126 4435 4450 #: src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx:41 4436 4451 #: src/view/screens/Profile.tsx:229 4437 4452 msgid "Likes" 4438 4453 msgstr "" 4439 4454 4455 + #: src/lib/hooks/useNotificationHandler.ts:159 4440 4456 #: src/screens/Settings/NotificationSettings/index.tsx:208 4441 4457 #: src/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings.tsx:41 4442 4458 msgid "Likes of your reposts" ··· 4446 4462 msgid "Likes of your reposts notifications" 4447 4463 msgstr "" 4448 4464 4449 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:467 4465 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:461 4450 4466 #: src/view/com/post-thread/PostThreadItem.tsx:243 4451 4467 msgid "Likes on this post" 4452 4468 msgstr "" ··· 4554 4570 msgid "Load more suggested feeds" 4555 4571 msgstr "" 4556 4572 4557 - #: src/view/screens/Notifications.tsx:270 4573 + #: src/view/screens/Notifications.tsx:278 4558 4574 msgid "Load new notifications" 4559 4575 msgstr "" 4560 4576 ··· 4614 4630 msgid "Make one for me" 4615 4631 msgstr "" 4616 4632 4617 - #: src/view/com/modals/LinkWarning.tsx:79 4633 + #: src/components/dialogs/LinkWarning.tsx:85 4618 4634 msgid "Make sure this is where you intend to go!" 4619 4635 msgstr "" 4620 4636 ··· 4668 4684 msgid "mentioned users" 4669 4685 msgstr "" 4670 4686 4671 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:423 4687 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:427 4672 4688 msgid "Mentioned users" 4673 4689 msgstr "" 4674 4690 4675 - #: src/screens/Settings/NotificationSettings/index.tsx:137 4691 + #: src/lib/hooks/useNotificationHandler.ts:138 4692 + #: src/screens/Settings/NotificationSettings/index.tsx:159 4676 4693 #: src/screens/Settings/NotificationSettings/MentionNotificationSettings.tsx:41 4677 - #: src/view/screens/Notifications.tsx:99 4694 + #: src/view/screens/Notifications.tsx:101 4678 4695 msgid "Mentions" 4679 4696 msgstr "" 4680 4697 ··· 4741 4758 4742 4759 #: src/Navigation.tsx:159 4743 4760 #: src/screens/Moderation/index.tsx:93 4744 - #: src/screens/Settings/Settings.tsx:178 4745 - #: src/screens/Settings/Settings.tsx:181 4761 + #: src/screens/Settings/Settings.tsx:180 4762 + #: src/screens/Settings/Settings.tsx:183 4746 4763 msgid "Moderation" 4747 4764 msgstr "" 4748 4765 ··· 5024 5041 msgid "New follower notifications" 5025 5042 msgstr "" 5026 5043 5027 - #: src/screens/Settings/NotificationSettings/index.tsx:181 5044 + #: src/lib/hooks/useNotificationHandler.ts:152 5045 + #: src/screens/Settings/NotificationSettings/index.tsx:137 5028 5046 #: src/screens/Settings/NotificationSettings/NewFollowerNotificationSettings.tsx:41 5029 5047 msgid "New followers" 5030 5048 msgstr "" ··· 5059 5077 5060 5078 #: src/screens/Profile/ProfileFeed/index.tsx:241 5061 5079 #: src/view/screens/Feeds.tsx:552 5062 - #: src/view/screens/Notifications.tsx:165 5080 + #: src/view/screens/Notifications.tsx:167 5063 5081 #: src/view/screens/Profile.tsx:510 5064 5082 #: src/view/screens/ProfileList.tsx:250 5065 5083 #: src/view/screens/ProfileList.tsx:288 ··· 5232 5250 msgid "No thanks" 5233 5251 msgstr "" 5234 5252 5235 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:405 5253 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:409 5236 5254 msgid "Nobody" 5237 5255 msgstr "" 5238 5256 ··· 5284 5302 5285 5303 #: src/Navigation.tsx:396 5286 5304 #: src/Navigation.tsx:530 5287 - #: src/view/screens/Notifications.tsx:134 5305 + #: src/view/screens/Notifications.tsx:136 5288 5306 msgid "Notification settings" 5289 5307 msgstr "" 5290 5308 ··· 5307 5325 #: src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx:30 5308 5326 #: src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx:30 5309 5327 #: src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx:30 5310 - #: src/screens/Settings/Settings.tsx:186 5311 5328 #: src/screens/Settings/Settings.tsx:189 5312 - #: src/view/screens/Notifications.tsx:128 5329 + #: src/screens/Settings/Settings.tsx:192 5330 + #: src/view/screens/Notifications.tsx:130 5313 5331 #: src/view/shell/bottom-bar/BottomBar.tsx:252 5314 5332 #: src/view/shell/desktop/LeftNav.tsx:654 5315 5333 #: src/view/shell/Drawer.tsx:482 ··· 5361 5379 msgstr "" 5362 5380 5363 5381 #: src/screens/Login/PasswordUpdatedForm.tsx:37 5364 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:670 5382 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:664 5365 5383 #: src/view/com/post-thread/PostThreadItem.tsx:985 5366 5384 msgid "Okay" 5367 5385 msgstr "" ··· 5381 5399 msgid "on<0><1/><2><3/></2></0>" 5382 5400 msgstr "" 5383 5401 5384 - #: src/screens/Settings/Settings.tsx:367 5402 + #: src/screens/Settings/Settings.tsx:371 5385 5403 msgid "Onboarding reset" 5386 5404 msgstr "" 5387 5405 ··· 5473 5491 msgid "Open message options" 5474 5492 msgstr "" 5475 5493 5476 - #: src/screens/Settings/Settings.tsx:404 5494 + #: src/screens/Settings/Settings.tsx:408 5477 5495 msgid "Open moderation debug page" 5478 5496 msgstr "" 5479 5497 ··· 5502 5520 msgid "Open starter pack menu" 5503 5521 msgstr "" 5504 5522 5505 - #: src/screens/Settings/Settings.tsx:397 5506 - #: src/screens/Settings/Settings.tsx:411 5523 + #: src/screens/Settings/Settings.tsx:401 5524 + #: src/screens/Settings/Settings.tsx:415 5507 5525 msgid "Open storybook page" 5508 5526 msgstr "" 5509 5527 5510 - #: src/screens/Settings/Settings.tsx:390 5528 + #: src/screens/Settings/Settings.tsx:394 5511 5529 msgid "Open system log" 5512 5530 msgstr "" 5513 5531 ··· 5569 5587 msgid "Opens GIF select dialog" 5570 5588 msgstr "" 5571 5589 5572 - #: src/screens/Settings/Settings.tsx:227 5590 + #: src/screens/Settings/Settings.tsx:231 5573 5591 msgid "Opens helpdesk in browser" 5592 + msgstr "" 5593 + 5594 + #: src/components/dialogs/LinkWarning.tsx:97 5595 + msgid "Opens link {0}" 5574 5596 msgstr "" 5575 5597 5576 5598 #: src/view/com/modals/InviteCodes.tsx:173 ··· 5585 5607 msgid "Opens password reset form" 5586 5608 msgstr "" 5587 5609 5588 - #: src/view/com/modals/LinkWarning.tsx:93 5589 - msgid "Opens the linked website" 5590 - msgstr "" 5591 - 5592 5610 #: src/view/com/notifications/NotificationFeedItem.tsx:850 5593 5611 #: src/view/com/util/UserAvatar.tsx:581 5594 5612 msgid "Opens this profile" ··· 5607 5625 msgid "Options:" 5608 5626 msgstr "" 5609 5627 5610 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:418 5628 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:422 5611 5629 msgid "Or combine these options:" 5612 5630 msgstr "" 5613 5631 ··· 5715 5733 msgid "People following @{0}" 5716 5734 msgstr "" 5717 5735 5718 - #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:171 5719 - #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:185 5736 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:178 5737 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:192 5720 5738 msgid "People I follow" 5721 5739 msgstr "" 5722 5740 ··· 5934 5952 msgid "Porn" 5935 5953 msgstr "" 5936 5954 5937 - #: src/screens/PostThread/index.tsx:490 5955 + #: src/screens/PostThread/index.tsx:495 5938 5956 #: src/view/com/post-thread/PostThread.tsx:561 5939 5957 msgctxt "description" 5940 5958 msgid "Post" ··· 5995 6013 msgid "Post Hidden by You" 5996 6014 msgstr "" 5997 6015 5998 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:100 6016 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:104 5999 6017 msgid "Post interaction settings" 6000 6018 msgstr "" 6001 6019 ··· 6043 6061 msgid "Posts hidden" 6044 6062 msgstr "" 6045 6063 6046 - #: src/view/com/modals/LinkWarning.tsx:60 6047 - msgid "Potentially Misleading Link" 6064 + #: src/components/dialogs/LinkWarning.tsx:73 6065 + msgid "Potentially misleading link" 6066 + msgstr "" 6067 + 6068 + #: src/components/dialogs/LinkWarning.tsx:66 6069 + msgid "Potentially misleading link warning" 6048 6070 msgstr "" 6049 6071 6050 6072 #: src/screens/Messages/components/MessageListError.tsx:19 ··· 6082 6104 msgid "Privacy" 6083 6105 msgstr "" 6084 6106 6085 - #: src/screens/Settings/Settings.tsx:172 6086 - #: src/screens/Settings/Settings.tsx:175 6107 + #: src/screens/Settings/Settings.tsx:174 6108 + #: src/screens/Settings/Settings.tsx:177 6087 6109 msgid "Privacy and security" 6088 6110 msgstr "" 6089 6111 ··· 6163 6185 msgid "Push" 6164 6186 msgstr "" 6165 6187 6166 - #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:117 6188 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:122 6167 6189 msgid "Push notifications" 6168 6190 msgstr "" 6169 6191 ··· 6213 6235 msgid "Quote posts disabled" 6214 6236 msgstr "" 6215 6237 6216 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:329 6238 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:333 6217 6239 msgid "Quote settings" 6218 6240 msgstr "" 6219 6241 6242 + #: src/lib/hooks/useNotificationHandler.ts:145 6220 6243 #: src/screens/Post/PostQuotes.tsx:38 6221 - #: src/screens/Settings/NotificationSettings/index.tsx:148 6244 + #: src/screens/Settings/NotificationSettings/index.tsx:170 6222 6245 #: src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx:41 6223 6246 msgid "Quotes" 6224 6247 msgstr "" 6225 6248 6226 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:451 6249 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:445 6227 6250 #: src/view/com/post-thread/PostThreadItem.tsx:269 6228 6251 msgid "Quotes of this post" 6229 6252 msgstr "" ··· 6296 6319 msgid "Reason:" 6297 6320 msgstr "" 6298 6321 6299 - #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:123 6322 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:128 6300 6323 msgid "Receive in-app notifications" 6301 6324 msgstr "" 6302 6325 6303 - #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:106 6326 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:111 6304 6327 msgid "Receive push notifications" 6305 6328 msgstr "" 6306 6329 ··· 6335 6358 #: src/components/FeedCard.tsx:343 6336 6359 #: src/components/StarterPack/Wizard/WizardListCard.tsx:102 6337 6360 #: src/components/StarterPack/Wizard/WizardListCard.tsx:109 6338 - #: src/screens/Settings/Settings.tsx:552 6361 + #: src/screens/Settings/Settings.tsx:556 6339 6362 #: src/view/com/feeds/FeedSourceCard.tsx:322 6340 6363 #: src/view/com/modals/UserAddRemoveLists.tsx:235 6341 6364 #: src/view/com/posts/PostFeedErrorMessage.tsx:213 ··· 6350 6373 msgid "Remove {historyItem}" 6351 6374 msgstr "" 6352 6375 6353 - #: src/screens/Settings/Settings.tsx:531 6354 - #: src/screens/Settings/Settings.tsx:534 6376 + #: src/screens/Settings/Settings.tsx:535 6377 + #: src/screens/Settings/Settings.tsx:538 6355 6378 msgid "Remove account" 6356 6379 msgstr "" 6357 6380 ··· 6392 6415 msgid "Remove from my feeds" 6393 6416 msgstr "" 6394 6417 6395 - #: src/screens/Settings/Settings.tsx:544 6418 + #: src/screens/Settings/Settings.tsx:548 6396 6419 msgid "Remove from quick access?" 6397 6420 msgstr "" 6398 6421 ··· 6487 6510 msgid "Replace with Discover" 6488 6511 msgstr "" 6489 6512 6490 - #: src/screens/Settings/NotificationSettings/index.tsx:126 6513 + #: src/lib/hooks/useNotificationHandler.ts:131 6514 + #: src/screens/Settings/NotificationSettings/index.tsx:148 6491 6515 #: src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx:41 6492 6516 #: src/view/screens/Profile.tsx:226 6493 6517 msgid "Replies" ··· 6525 6549 msgid "Reply notifications" 6526 6550 msgstr "" 6527 6551 6528 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:385 6552 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:389 6529 6553 msgid "Reply settings" 6530 6554 msgstr "" 6531 6555 6532 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:370 6556 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:374 6533 6557 msgid "Reply settings are chosen by the author of the thread" 6534 6558 msgstr "" 6535 6559 ··· 6701 6725 msgid "Reposted by you" 6702 6726 msgstr "" 6703 6727 6704 - #: src/screens/Settings/NotificationSettings/index.tsx:170 6728 + #: src/lib/hooks/useNotificationHandler.ts:124 6729 + #: src/screens/Settings/NotificationSettings/index.tsx:181 6705 6730 #: src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx:41 6706 6731 msgid "Reposts" 6707 6732 msgstr "" 6708 6733 6709 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:433 6734 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:427 6710 6735 #: src/view/com/post-thread/PostThreadItem.tsx:248 6711 6736 msgid "Reposts of this post" 6712 6737 msgstr "" 6713 6738 6739 + #: src/lib/hooks/useNotificationHandler.ts:166 6714 6740 #: src/screens/Settings/NotificationSettings/index.tsx:223 6715 6741 #: src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx:41 6716 6742 msgid "Reposts of your reposts" ··· 6768 6794 msgid "Reset Code" 6769 6795 msgstr "" 6770 6796 6771 - #: src/screens/Settings/Settings.tsx:418 6772 - #: src/screens/Settings/Settings.tsx:420 6797 + #: src/screens/Settings/Settings.tsx:422 6798 + #: src/screens/Settings/Settings.tsx:424 6773 6799 msgid "Reset onboarding state" 6774 6800 msgstr "" 6775 6801 ··· 6835 6861 msgstr "" 6836 6862 6837 6863 #: src/components/dialogs/BirthDateSettings.tsx:121 6838 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:479 6839 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:485 6864 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:483 6865 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:489 6840 6866 #: src/components/live/EditLiveDialog.tsx:216 6841 6867 #: src/components/live/EditLiveDialog.tsx:223 6842 6868 #: src/components/StarterPack/QrCodeDialog.tsx:192 ··· 7140 7166 msgstr "" 7141 7167 7142 7168 #: src/screens/Onboarding/StepInterests/index.tsx:192 7143 - #: src/screens/Settings/InterestsSettings.tsx:165 7169 + #: src/screens/Settings/InterestsSettings.tsx:161 7144 7170 msgid "Select your interests from the options below" 7145 7171 msgstr "" 7146 7172 7147 7173 #: src/screens/Settings/LanguageSettings.tsx:124 7148 7174 msgid "Select your preferred language for translations in your feed." 7175 + msgstr "" 7176 + 7177 + #: src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx:106 7178 + msgid "Select your preferred notification channels" 7149 7179 msgstr "" 7150 7180 7151 7181 #: src/view/com/util/forms/DropdownButton.tsx:297 ··· 7242 7272 msgstr "" 7243 7273 7244 7274 #: src/Navigation.tsx:195 7245 - #: src/screens/Settings/Settings.tsx:91 7275 + #: src/screens/Settings/Settings.tsx:93 7246 7276 #: src/view/shell/desktop/LeftNav.tsx:727 7247 7277 #: src/view/shell/Drawer.tsx:572 7248 7278 msgid "Settings" 7249 7279 msgstr "" 7250 7280 7251 - #: src/screens/Settings/NotificationSettings/index.tsx:154 7281 + #: src/screens/Settings/NotificationSettings/index.tsx:121 7252 7282 msgid "Settings for like notifications" 7253 7283 msgstr "" 7254 7284 7255 - #: src/screens/Settings/NotificationSettings/index.tsx:132 7285 + #: src/screens/Settings/NotificationSettings/index.tsx:154 7256 7286 msgid "Settings for mention notifications" 7257 7287 msgstr "" 7258 7288 7259 - #: src/screens/Settings/NotificationSettings/index.tsx:176 7289 + #: src/screens/Settings/NotificationSettings/index.tsx:132 7260 7290 msgid "Settings for new follower notifications" 7261 7291 msgstr "" 7262 7292 ··· 7272 7302 msgid "Settings for notifications for reposts of your reposts" 7273 7303 msgstr "" 7274 7304 7275 - #: src/screens/Settings/NotificationSettings/index.tsx:143 7305 + #: src/screens/Settings/NotificationSettings/index.tsx:165 7276 7306 msgid "Settings for quote notifications" 7277 7307 msgstr "" 7278 7308 7279 - #: src/screens/Settings/NotificationSettings/index.tsx:121 7309 + #: src/screens/Settings/NotificationSettings/index.tsx:143 7280 7310 msgid "Settings for reply notifications" 7281 7311 msgstr "" 7282 7312 7283 - #: src/screens/Settings/NotificationSettings/index.tsx:165 7313 + #: src/screens/Settings/NotificationSettings/index.tsx:176 7284 7314 msgid "Settings for repost notifications" 7285 7315 msgstr "" 7286 7316 ··· 7327 7357 msgid "Share author DID" 7328 7358 msgstr "" 7329 7359 7360 + #: src/components/dialogs/LinkWarning.tsx:96 7361 + #: src/components/dialogs/LinkWarning.tsx:104 7330 7362 #: src/components/StarterPack/ShareDialog.tsx:104 7331 7363 #: src/components/StarterPack/ShareDialog.tsx:111 7332 7364 msgid "Share link" 7333 7365 msgstr "" 7334 7366 7335 - #: src/view/com/modals/LinkWarning.tsx:89 7336 - #: src/view/com/modals/LinkWarning.tsx:95 7337 - msgid "Share Link" 7338 - msgstr "" 7339 - 7340 7367 #: src/components/StarterPack/ShareDialog.tsx:68 7341 7368 msgid "Share link dialog" 7342 7369 msgstr "" ··· 7380 7407 msgid "Shared Preferences Tester" 7381 7408 msgstr "" 7382 7409 7383 - #: src/view/com/modals/LinkWarning.tsx:92 7384 - msgid "Shares the linked website" 7385 - msgstr "" 7386 - 7387 7410 #: src/components/moderation/ContentHider.tsx:200 7388 7411 #: src/components/moderation/LabelPreference.tsx:137 7389 7412 #: src/components/moderation/PostHider.tsx:134 ··· 7496 7519 msgid "Show warning and filter from feeds" 7497 7520 msgstr "" 7498 7521 7499 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:611 7522 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:605 7500 7523 #: src/view/com/post-thread/PostThreadItem.tsx:926 7501 7524 msgid "Shows information about when this post was created" 7502 7525 msgstr "" 7503 7526 7504 - #: src/screens/Settings/Settings.tsx:115 7527 + #: src/screens/Settings/Settings.tsx:117 7505 7528 msgid "Shows other accounts you can switch to" 7506 7529 msgstr "" 7507 7530 ··· 7558 7581 msgid "Sign in to view post" 7559 7582 msgstr "" 7560 7583 7561 - #: src/screens/Settings/Settings.tsx:244 7562 - #: src/screens/Settings/Settings.tsx:246 7563 - #: src/screens/Settings/Settings.tsx:278 7584 + #: src/screens/Settings/Settings.tsx:248 7585 + #: src/screens/Settings/Settings.tsx:250 7586 + #: src/screens/Settings/Settings.tsx:282 7564 7587 #: src/screens/SignupQueued.tsx:93 7565 7588 #: src/screens/SignupQueued.tsx:96 7566 7589 #: src/screens/Takendown.tsx:85 ··· 7574 7597 msgid "Sign Out" 7575 7598 msgstr "" 7576 7599 7577 - #: src/screens/Settings/Settings.tsx:275 7600 + #: src/screens/Settings/Settings.tsx:279 7578 7601 #: src/view/shell/desktop/LeftNav.tsx:209 7579 7602 msgid "Sign out?" 7580 7603 msgstr "" ··· 7777 7800 msgid "Step {0} of {1}" 7778 7801 msgstr "" 7779 7802 7780 - #: src/screens/Settings/Settings.tsx:372 7803 + #: src/screens/Settings/Settings.tsx:376 7781 7804 msgid "Storage cleared, you need to restart the app now." 7782 7805 msgstr "" 7783 7806 7784 7807 #: src/Navigation.tsx:288 7785 - #: src/screens/Settings/Settings.tsx:399 7808 + #: src/screens/Settings/Settings.tsx:403 7786 7809 msgid "Storybook" 7787 7810 msgstr "" 7788 7811 ··· 7864 7887 msgid "Support" 7865 7888 msgstr "" 7866 7889 7867 - #: src/screens/Settings/Settings.tsx:113 7868 - #: src/screens/Settings/Settings.tsx:127 7869 - #: src/screens/Settings/Settings.tsx:494 7890 + #: src/screens/Settings/Settings.tsx:115 7891 + #: src/screens/Settings/Settings.tsx:129 7892 + #: src/screens/Settings/Settings.tsx:498 7870 7893 #: src/view/shell/desktop/LeftNav.tsx:246 7871 7894 msgid "Switch account" 7872 7895 msgstr "" ··· 7893 7916 7894 7917 #: src/screens/Settings/AboutSettings.tsx:107 7895 7918 #: src/screens/Settings/AboutSettings.tsx:110 7896 - #: src/screens/Settings/Settings.tsx:392 7919 + #: src/screens/Settings/Settings.tsx:396 7897 7920 msgid "System log" 7898 7921 msgstr "" 7899 7922 ··· 8195 8218 msgid "There was an issue! {0}" 8196 8219 msgstr "" 8197 8220 8198 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:217 8221 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:221 8199 8222 #: src/screens/List/ListHiddenScreen.tsx:63 8200 8223 #: src/screens/List/ListHiddenScreen.tsx:77 8201 8224 #: src/screens/List/ListHiddenScreen.tsx:99 ··· 8339 8362 msgid "This labeler hasn't declared what labels it publishes, and may not be active." 8340 8363 msgstr "" 8341 8364 8342 - #: src/view/com/modals/LinkWarning.tsx:72 8365 + #: src/components/dialogs/LinkWarning.tsx:79 8343 8366 msgid "This link is taking you to the following website:" 8344 8367 msgstr "" 8345 8368 ··· 8359 8382 msgid "This moderation service is unavailable. See below for more details. If this issue persists, contact us." 8360 8383 msgstr "" 8361 8384 8362 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:651 8385 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:645 8363 8386 #: src/view/com/post-thread/PostThreadItem.tsx:966 8364 8387 msgid "This post claims to have been created on <0>{0}</0>, but was first seen by Bluesky on <1>{1}</1>." 8365 8388 msgstr "" ··· 8441 8464 msgid "This will delete \"{0}\" from your muted words. You can always add it back later." 8442 8465 msgstr "" 8443 8466 8444 - #: src/screens/Settings/Settings.tsx:546 8467 + #: src/screens/Settings/Settings.tsx:550 8445 8468 msgid "This will remove @{0} from the quick access list." 8446 8469 msgstr "" 8447 8470 ··· 8539 8562 #: src/components/dms/MessageContextMenu.tsx:145 8540 8563 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:444 8541 8564 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:446 8542 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:573 8543 - #: src/screens/PostThread/components/ThreadItemAnchor.tsx:576 8565 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:567 8566 + #: src/screens/PostThread/components/ThreadItemAnchor.tsx:570 8544 8567 #: src/view/com/post-thread/PostThreadItem.tsx:888 8545 8568 #: src/view/com/post-thread/PostThreadItem.tsx:891 8546 8569 msgid "Translate" ··· 8758 8781 msgid "Unpinned from your feeds" 8759 8782 msgstr "" 8760 8783 8761 - #: src/screens/Settings/Settings.tsx:425 8762 - #: src/screens/Settings/Settings.tsx:427 8784 + #: src/screens/Settings/Settings.tsx:429 8785 + #: src/screens/Settings/Settings.tsx:431 8763 8786 msgid "Unsnooze email reminder" 8764 8787 msgstr "" 8765 8788 ··· 8974 8997 msgid "Users I follow" 8975 8998 msgstr "" 8976 8999 8977 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:456 9000 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:460 8978 9001 msgid "Users in \"{0}\"" 8979 9002 msgstr "" 8980 9003 8981 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:433 9004 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:437 8982 9005 msgid "Users you follow" 8983 9006 msgstr "" 8984 9007 ··· 9225 9248 msgid "Views video in immersive mode" 9226 9249 msgstr "" 9227 9250 9228 - #: src/view/com/modals/LinkWarning.tsx:89 9229 - #: src/view/com/modals/LinkWarning.tsx:95 9230 - msgid "Visit Site" 9251 + #: src/components/dialogs/LinkWarning.tsx:96 9252 + #: src/components/dialogs/LinkWarning.tsx:106 9253 + msgid "Visit site" 9254 + msgstr "" 9255 + 9256 + #: src/view/screens/Notifications.tsx:303 9257 + msgid "Visit your notification settings" 9231 9258 msgstr "" 9232 9259 9233 9260 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:81 ··· 9281 9308 msgid "We ran out of posts from your follows. Here's the latest from <0/>." 9282 9309 msgstr "" 9283 9310 9284 - #: src/screens/Settings/InterestsSettings.tsx:158 9311 + #: src/screens/Settings/InterestsSettings.tsx:154 9285 9312 msgid "We recommend selecting at least two interests." 9286 9313 msgstr "" 9287 9314 ··· 9573 9600 msgid "You can also temporarily deactivate your account instead, and reactivate it at any time." 9574 9601 msgstr "" 9575 9602 9603 + #: src/lib/hooks/useNotificationHandler.ts:92 9604 + msgid "You can choose whether chat notifications have sound in the chat settings within the app" 9605 + msgstr "" 9606 + 9576 9607 #: src/screens/Messages/Settings.tsx:111 9577 9608 msgid "You can continue ongoing conversations regardless of which setting you choose." 9578 9609 msgstr "" ··· 9586 9617 msgid "You can reactivate your account to continue logging in. Your profile and posts will be visible to other users." 9587 9618 msgstr "" 9588 9619 9589 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:81 9620 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:85 9590 9621 msgid "You can set default interaction settings in <0>Settings → Moderation → Interaction settings</0>." 9591 9622 msgstr "" 9592 9623 ··· 9634 9665 msgid "You have blocked this user. You cannot view their content." 9635 9666 msgstr "" 9636 9667 9668 + #: src/view/screens/Notifications.tsx:298 9669 + msgid "You have completely disabled reply, quote, and mention notifications, so this tab will no longer update. To adjust this, visit your <0>notification settings</0>." 9670 + msgstr "" 9671 + 9637 9672 #: src/screens/Login/SetNewPasswordForm.tsx:49 9638 9673 #: src/screens/Login/SetNewPasswordForm.tsx:95 9639 9674 #: src/view/com/modals/ChangePassword.tsx:87 ··· 9753 9788 msgid "You previously deactivated @{0}." 9754 9789 msgstr "" 9755 9790 9756 - #: src/screens/Settings/Settings.tsx:383 9791 + #: src/screens/Settings/Settings.tsx:387 9757 9792 msgid "You probably want to restart the app now." 9758 9793 msgstr "" 9759 9794 ··· 9765 9800 msgid "You reacted {0} to {1}" 9766 9801 msgstr "" 9767 9802 9768 - #: src/screens/Settings/Settings.tsx:276 9803 + #: src/screens/Settings/Settings.tsx:280 9769 9804 #: src/view/shell/desktop/LeftNav.tsx:210 9770 9805 msgid "You will be signed out of all your accounts." 9771 9806 msgstr "" ··· 9919 9954 msgid "Your first like!" 9920 9955 msgstr "" 9921 9956 9922 - #: src/components/dialogs/PostInteractionSettingsDialog.tsx:443 9957 + #: src/components/dialogs/PostInteractionSettingsDialog.tsx:447 9923 9958 msgid "Your followers" 9924 9959 msgstr "" 9925 9960 ··· 9943 9978 msgid "Your interests" 9944 9979 msgstr "" 9945 9980 9946 - #: src/screens/Settings/InterestsSettings.tsx:127 9981 + #: src/screens/Settings/InterestsSettings.tsx:123 9947 9982 msgctxt "toast" 9948 9983 msgid "Your interests have been updated!" 9949 9984 msgstr ""
+5 -3
src/logger/__tests__/logger.test.ts
··· 110 110 const timestamp = Date.now() 111 111 const sentryTimestamp = timestamp / 1000 112 112 113 + /* 113 114 sentryTransport( 114 115 LogLevel.Debug, 115 116 Logger.Context.Default, ··· 125 126 level: LogLevel.Debug, 126 127 timestamp: sentryTimestamp, 127 128 }) 129 + */ 128 130 129 131 sentryTransport( 130 132 LogLevel.Info, ··· 154 156 message, 155 157 data: {__context__: 'logger'}, 156 158 type: 'default', 157 - level: 'debug', // Sentry bug, log becomes debug 159 + level: 'log', 158 160 timestamp: sentryTimestamp, 159 161 }) 160 162 jest.runAllTimers() ··· 220 222 const sentryTimestamp = timestamp / 1000 221 223 222 224 sentryTransport( 223 - LogLevel.Debug, 225 + LogLevel.Info, 224 226 undefined, 225 227 message, 226 228 {error: new Error('foo')}, ··· 230 232 message, 231 233 data: {error: 'Error: foo'}, 232 234 type: 'default', 233 - level: LogLevel.Debug, 235 + level: LogLevel.Info, 234 236 timestamp: sentryTimestamp, 235 237 }) 236 238 })
+4 -1
src/logger/transports/sentry.ts
··· 1 1 import {isNetworkError} from '#/lib/strings/errors' 2 2 import {Sentry} from '#/logger/sentry/lib' 3 - import {LogLevel, Transport} from '#/logger/types' 3 + import {LogLevel, type Transport} from '#/logger/types' 4 4 import {prepareMetadata} from '#/logger/util' 5 5 6 6 export const sentryTransport: Transport = ( ··· 10 10 {type, tags, ...metadata}, 11 11 timestamp, 12 12 ) => { 13 + // Skip debug messages entirely for now - esb 14 + if (level === LogLevel.Debug) return 15 + 13 16 const meta = { 14 17 __context__: context, 15 18 ...prepareMetadata(metadata),
+44 -52
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 311 311 isRoot && [a.pt_lg], 312 312 ]}> 313 313 <View style={[a.flex_row, a.gap_md, a.pb_md]}> 314 - <PreviewableUserAvatar 315 - size={42} 316 - profile={post.author} 317 - moderation={moderation.ui('avatar')} 318 - type={post.author.associated?.labeler ? 'labeler' : 'user'} 319 - live={live} 320 - onBeforePress={onOpenAuthor} 321 - /> 322 - <View style={[a.flex_1, a.align_start]}> 323 - <ProfileHoverCard did={post.author.did}> 324 - <View style={[a.flex_1]}> 314 + <View collapsable={false}> 315 + <PreviewableUserAvatar 316 + size={42} 317 + profile={post.author} 318 + moderation={moderation.ui('avatar')} 319 + type={post.author.associated?.labeler ? 'labeler' : 'user'} 320 + live={live} 321 + onBeforePress={onOpenAuthor} 322 + /> 323 + </View> 324 + <Link 325 + to={authorHref} 326 + style={[a.flex_1]} 327 + label={sanitizeDisplayName( 328 + post.author.displayName || sanitizeHandle(post.author.handle), 329 + moderation.ui('displayName'), 330 + )} 331 + onPress={onOpenAuthor}> 332 + <View style={[a.flex_1, a.align_start]}> 333 + <ProfileHoverCard did={post.author.did} style={[a.w_full]}> 325 334 <View style={[a.flex_row, a.align_center]}> 326 - <Link 327 - to={authorHref} 328 - style={[a.flex_shrink]} 329 - label={sanitizeDisplayName( 335 + <Text 336 + emoji 337 + style={[ 338 + a.flex_shrink, 339 + a.text_lg, 340 + a.font_bold, 341 + a.leading_snug, 342 + ]} 343 + numberOfLines={1}> 344 + {sanitizeDisplayName( 330 345 post.author.displayName || 331 346 sanitizeHandle(post.author.handle), 332 347 moderation.ui('displayName'), 333 348 )} 334 - onPress={onOpenAuthor}> 335 - <Text 336 - emoji 337 - style={[ 338 - a.text_lg, 339 - a.font_bold, 340 - a.leading_snug, 341 - a.self_start, 342 - ]} 343 - numberOfLines={1}> 344 - {sanitizeDisplayName( 345 - post.author.displayName || 346 - sanitizeHandle(post.author.handle), 347 - moderation.ui('displayName'), 348 - )} 349 - </Text> 350 - </Link> 349 + </Text> 351 350 352 351 <View style={[{paddingLeft: 3, top: -1}]}> 353 352 <VerificationCheckButton profile={authorShadow} size="md" /> 354 353 </View> 355 354 </View> 356 - <View style={[a.align_start]}> 357 - <Link 358 - style={[a.flex_shrink]} 359 - to={authorHref} 360 - label={sanitizeHandle(post.author.handle, '@')}> 361 - <Text 362 - style={[ 363 - a.text_md, 364 - a.leading_snug, 365 - t.atoms.text_contrast_medium, 366 - ]} 367 - numberOfLines={1}> 368 - {sanitizeHandle(post.author.handle, '@')} 369 - </Text> 370 - </Link> 371 - </View> 372 - </View> 373 - </ProfileHoverCard> 374 - </View> 355 + <Text 356 + style={[ 357 + a.text_md, 358 + a.leading_snug, 359 + t.atoms.text_contrast_medium, 360 + ]} 361 + numberOfLines={1}> 362 + {sanitizeHandle(post.author.handle, '@')} 363 + </Text> 364 + </ProfileHoverCard> 365 + </View> 366 + </Link> 375 367 {showFollowButton && ( 376 - <View> 368 + <View collapsable={false}> 377 369 <PostThreadFollowBtn did={post.author.did} /> 378 370 </View> 379 371 )}
+12 -5
src/screens/PostThread/index.tsx
··· 54 54 * One query to rule them all 55 55 */ 56 56 const thread = usePostThread({anchor: uri}) 57 - const anchor = useMemo(() => { 57 + const {anchor, hasParents} = useMemo(() => { 58 + // eslint-disable-next-line @typescript-eslint/no-shadow 59 + let hasParents = false 58 60 for (const item of thread.data.items) { 59 61 if (item.type === 'threadPost' && item.depth === 0) { 60 - return item 62 + return {anchor: item, hasParents} 61 63 } 64 + hasParents = true 62 65 } 63 - return 66 + return {hasParents} 64 67 }, [thread.data.items]) 65 68 66 69 const {openComposer} = useOpenComposer() ··· 481 484 ], 482 485 ) 483 486 487 + const defaultListFooterHeight = hasParents ? windowHeight - 200 : undefined 488 + 484 489 return ( 485 490 <> 486 491 <Layout.Header.Outer headerRef={headerRef}> ··· 537 542 * back to the top of the screen when handling scroll. 538 543 */ 539 544 height={platform({ 540 - web: windowHeight - 200, 541 - default: deferParents ? windowHeight * 2 : windowHeight - 200, 545 + web: defaultListFooterHeight, 546 + default: deferParents 547 + ? windowHeight * 2 548 + : defaultListFooterHeight, 542 549 })} 543 550 style={isTombstoneView ? {borderTopWidth: 0} : undefined} 544 551 />
+1 -5
src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx
··· 53 53 </Admonition> 54 54 </View> 55 55 ) : ( 56 - <PreferenceControls 57 - name="reply" 58 - preference={preferences?.reply} 59 - allowDisableInApp={false} 60 - /> 56 + <PreferenceControls name="reply" preference={preferences?.reply} /> 61 57 )} 62 58 </SettingsList.Container> 63 59 </Layout.Content>
+5
src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx
··· 5 5 import {msg, Trans} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 7 8 + import {useGate} from '#/lib/statsig/statsig' 8 9 import {useNotificationSettingsUpdateMutation} from '#/state/queries/notifications/settings' 9 10 import {atoms as a, platform, useTheme} from '#/alf' 10 11 import * as Toggle from '#/components/forms/Toggle' ··· 27 28 preference?: AppBskyNotificationDefs.Preference | FilterablePreference 28 29 allowDisableInApp?: boolean 29 30 }) { 31 + const gate = useGate() 32 + 33 + if (!gate('reengagement_features')) return null 34 + 30 35 if (!preference) 31 36 return ( 32 37 <View style={[a.w_full, a.pt_5xl, a.align_center]}>
+12 -8
src/screens/Settings/Settings.tsx
··· 16 16 type CommonNavigatorParams, 17 17 type NavigationProp, 18 18 } from '#/lib/routes/types' 19 + import {useGate} from '#/lib/statsig/statsig' 19 20 import {sanitizeDisplayName} from '#/lib/strings/display-names' 20 21 import {sanitizeHandle} from '#/lib/strings/handles' 21 22 import {useProfileShadow} from '#/state/cache/profile-shadow' ··· 82 83 const {pendingDid, onPressSwitchAccount} = useAccountSwitcher() 83 84 const [showAccounts, setShowAccounts] = useState(false) 84 85 const [showDevOptions, setShowDevOptions] = useState(false) 86 + const gate = useGate() 85 87 86 88 return ( 87 89 <Layout.Screen> ··· 182 184 <Trans>Moderation</Trans> 183 185 </SettingsList.ItemText> 184 186 </SettingsList.LinkItem> 185 - <SettingsList.LinkItem 186 - to="/settings/notifications" 187 - label={_(msg`Notifications`)}> 188 - <SettingsList.ItemIcon icon={NotificationIcon} /> 189 - <SettingsList.ItemText> 190 - <Trans>Notifications</Trans> 191 - </SettingsList.ItemText> 192 - </SettingsList.LinkItem> 187 + {gate('reengagement_features') && ( 188 + <SettingsList.LinkItem 189 + to="/settings/notifications" 190 + label={_(msg`Notifications`)}> 191 + <SettingsList.ItemIcon icon={NotificationIcon} /> 192 + <SettingsList.ItemText> 193 + <Trans>Notifications</Trans> 194 + </SettingsList.ItemText> 195 + </SettingsList.LinkItem> 196 + )} 193 197 <SettingsList.LinkItem 194 198 to="/settings/content-and-media" 195 199 label={_(msg`Content and media`)}>
+44 -30
src/state/messages/convo/agent.ts
··· 10 10 import {nanoid} from 'nanoid/non-secure' 11 11 12 12 import {networkRetry} from '#/lib/async/retry' 13 + import {isNetworkError} from '#/lib/strings/errors' 13 14 import {Logger} from '#/logger' 14 15 import {isNative} from '#/platform/detection' 15 16 import { ··· 130 131 131 132 getSnapshot(): ConvoState { 132 133 if (!this.snapshot) this.snapshot = this.generateSnapshot() 133 - // logger.debug('Convo: snapshotted', {}) 134 + // logger.debug('snapshotted', {}) 134 135 return this.snapshot 135 136 } 136 137 ··· 392 393 break 393 394 } 394 395 395 - logger.debug(`Convo: dispatch '${action.event}'`, { 396 + logger.debug(`dispatch '${action.event}'`, { 396 397 id: this.id, 397 398 prev: prevStatus, 398 399 next: this.status, ··· 467 468 * Some validation prior to `Ready` status 468 469 */ 469 470 if (!this.convo) { 470 - throw new Error('Convo: could not find convo') 471 + throw new Error('could not find convo') 471 472 } 472 473 if (!this.sender) { 473 - throw new Error('Convo: could not find sender in convo') 474 + throw new Error('could not find sender in convo') 474 475 } 475 476 if (!this.recipients) { 476 - throw new Error('Convo: could not find recipients in convo') 477 + throw new Error('could not find recipients in convo') 477 478 } 478 479 479 480 const userIsDisabled = Boolean(this.sender.chatDisabled) ··· 484 485 this.dispatch({event: ConvoDispatchEvent.Ready}) 485 486 } 486 487 } catch (e: any) { 487 - logger.error(e, {message: 'Convo: setup failed'}) 488 + if (!isNetworkError(e)) { 489 + logger.error('setup failed', { 490 + safeMessage: e.message, 491 + }) 492 + } 488 493 489 494 this.dispatch({ 490 495 event: ConvoDispatchEvent.Error, ··· 589 594 this.sender = sender || this.sender 590 595 this.recipients = recipients || this.recipients 591 596 } catch (e: any) { 592 - logger.error(e, {message: `Convo: failed to refresh convo`}) 597 + if (!isNetworkError(e)) { 598 + logger.error(`failed to refresh convo`, { 599 + safeMessage: e.message, 600 + }) 601 + } 593 602 } 594 603 } 595 604 ··· 599 608 } 600 609 | undefined 601 610 async fetchMessageHistory() { 602 - logger.debug('Convo: fetch message history', {}) 611 + logger.debug('fetch message history', {}) 603 612 604 613 /* 605 614 * If oldestRev is null, we've fetched all history. ··· 653 662 } 654 663 } 655 664 } catch (e: any) { 656 - logger.error('Convo: failed to fetch message history') 665 + if (!isNetworkError(e)) { 666 + logger.error('failed to fetch message history', { 667 + safeMessage: e.message, 668 + }) 669 + } 657 670 658 671 this.fetchMessageHistoryError = { 659 672 retry: () => { ··· 802 815 // Ignore empty messages for now since they have no other purpose atm 803 816 if (!message.text.trim() && !message.embed) return 804 817 805 - logger.debug('Convo: send message', {}) 818 + logger.debug('send message', {}) 806 819 807 820 const tempId = nanoid() 808 821 ··· 836 849 837 850 async processPendingMessages() { 838 851 logger.debug( 839 - `Convo: processing messages (${this.pendingMessages.size} remaining)`, 852 + `processing messages (${this.pendingMessages.size} remaining)`, 840 853 {}, 841 854 ) 842 855 ··· 881 894 // continue queue processing 882 895 await this.processPendingMessages() 883 896 } catch (e: any) { 884 - logger.error(e, {message: `Convo: failed to send message`}) 885 897 this.handleSendMessageFailure(e) 886 898 this.isProcessingPendingMessages = false 887 899 } ··· 914 926 case 'recipient has disabled incoming messages': 915 927 break 916 928 default: 917 - logger.warn( 918 - `Convo handleSendMessageFailure could not handle error`, 919 - { 929 + if (!isNetworkError(e)) { 930 + logger.warn(`handleSendMessageFailure could not handle error`, { 920 931 status: e.status, 921 932 message: e.message, 922 - }, 923 - ) 933 + }) 934 + } 924 935 break 925 936 } 926 937 } 927 938 } else { 928 939 this.pendingMessageFailure = 'unrecoverable' 929 - logger.error(e, { 930 - message: `Convo handleSendMessageFailure received unknown error`, 931 - }) 940 + 941 + if (!isNetworkError(e)) { 942 + logger.error(`handleSendMessageFailure received unknown error`, { 943 + safeMessage: e.message, 944 + }) 945 + } 932 946 } 933 947 934 948 this.commit() ··· 944 958 this.commit() 945 959 946 960 logger.debug( 947 - `Convo: batch retrying ${this.pendingMessages.size} pending messages`, 961 + `batch retrying ${this.pendingMessages.size} pending messages`, 948 962 {}, 949 963 ) 950 964 ··· 977 991 978 992 this.commit() 979 993 980 - logger.debug( 981 - `Convo: sent ${this.pendingMessages.size} pending messages`, 982 - {}, 983 - ) 994 + logger.debug(`sent ${this.pendingMessages.size} pending messages`, {}) 984 995 } catch (e: any) { 985 - logger.error(e, {message: `Convo: failed to batch retry messages`}) 986 996 this.handleSendMessageFailure(e) 987 997 } 988 998 } 989 999 990 1000 async deleteMessage(messageId: string) { 991 - logger.debug('Convo: delete message', {}) 1001 + logger.debug('delete message', {}) 992 1002 993 1003 this.deletedMessages.add(messageId) 994 1004 this.commit() ··· 1004 1014 ) 1005 1015 }) 1006 1016 } catch (e: any) { 1007 - logger.error(e, {message: `Convo: failed to delete message`}) 1017 + if (!isNetworkError(e)) { 1018 + logger.error(`failed to delete message`, { 1019 + safeMessage: e.message, 1020 + }) 1021 + } 1008 1022 this.deletedMessages.delete(messageId) 1009 1023 this.commit() 1010 1024 throw e ··· 1232 1246 } 1233 1247 1234 1248 try { 1235 - logger.info(`Adding reaction ${emoji} to message ${messageId}`) 1249 + logger.debug(`Adding reaction ${emoji} to message ${messageId}`) 1236 1250 const {data} = await this.agent.chat.bsky.convo.addReaction( 1237 1251 {messageId, value: emoji, convoId: this.convoId}, 1238 1252 {encoding: 'application/json', headers: DM_SERVICE_HEADERS}, ··· 1297 1311 } 1298 1312 1299 1313 try { 1300 - logger.info(`Removing reaction ${emoji} from message ${messageId}`) 1314 + logger.debug(`Removing reaction ${emoji} from message ${messageId}`) 1301 1315 await this.agent.chat.bsky.convo.removeReaction( 1302 1316 {messageId, value: emoji, convoId: this.convoId}, 1303 1317 {encoding: 'application/json', headers: DM_SERVICE_HEADERS},
+18 -18
src/state/messages/events/agent.ts
··· 3 3 import {nanoid} from 'nanoid/non-secure' 4 4 5 5 import {networkRetry} from '#/lib/async/retry' 6 + import {isNetworkError} from '#/lib/strings/errors' 6 7 import {Logger} from '#/logger' 7 8 import { 8 9 BACKGROUND_POLL_INTERVAL, ··· 18 19 } from '#/state/messages/events/types' 19 20 import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 20 21 21 - const LOGGER_CONTEXT = 'MessagesEventBus' 22 22 const logger = Logger.create(Logger.Context.DMsAgent) 23 23 24 24 export class MessagesEventBus { ··· 91 91 } 92 92 93 93 background() { 94 - logger.debug(`${LOGGER_CONTEXT}: background`, {}) 94 + logger.debug(`background`, {}) 95 95 this.dispatch({event: MessagesEventBusDispatchEvent.Background}) 96 96 } 97 97 98 98 suspend() { 99 - logger.debug(`${LOGGER_CONTEXT}: suspend`, {}) 99 + logger.debug(`suspend`, {}) 100 100 this.dispatch({event: MessagesEventBusDispatchEvent.Suspend}) 101 101 } 102 102 103 103 resume() { 104 - logger.debug(`${LOGGER_CONTEXT}: resume`, {}) 104 + logger.debug(`resume`, {}) 105 105 this.dispatch({event: MessagesEventBusDispatchEvent.Resume}) 106 106 } 107 107 ··· 228 228 break 229 229 } 230 230 231 - logger.debug(`${LOGGER_CONTEXT}: dispatch '${action.event}'`, { 231 + logger.debug(`dispatch '${action.event}'`, { 232 232 id: this.id, 233 233 prev: prevStatus, 234 234 next: this.status, ··· 236 236 } 237 237 238 238 private async init() { 239 - logger.debug(`${LOGGER_CONTEXT}: init`, {}) 239 + logger.debug(`init`, {}) 240 240 241 241 try { 242 242 const response = await networkRetry(2, () => { ··· 260 260 261 261 this.dispatch({event: MessagesEventBusDispatchEvent.Ready}) 262 262 } catch (e: any) { 263 - logger.error(e, { 264 - message: `${LOGGER_CONTEXT}: init failed`, 265 - }) 263 + if (!isNetworkError(e)) { 264 + logger.error(`init failed`, { 265 + safeMessage: e.message, 266 + }) 267 + } 266 268 267 269 this.dispatch({ 268 270 event: MessagesEventBusDispatchEvent.Error, ··· 324 326 this.isPolling = true 325 327 326 328 // logger.debug( 327 - // `${LOGGER_CONTEXT}: poll`, 329 + // `poll`, 328 330 // { 329 331 // requestedPollIntervals: Array.from( 330 332 // this.requestedPollIntervals.values(), ··· 370 372 } 371 373 372 374 if (needsEmit) { 373 - try { 374 - this.emitter.emit('event', {type: 'logs', logs: batch}) 375 - } catch (e: any) { 376 - logger.error(e, { 377 - message: `${LOGGER_CONTEXT}: process latest events`, 378 - }) 379 - } 375 + this.emitter.emit('event', {type: 'logs', logs: batch}) 380 376 } 381 377 } catch (e: any) { 382 - logger.error(e, {message: `${LOGGER_CONTEXT}: poll events failed`}) 378 + if (!isNetworkError(e)) { 379 + logger.error(`poll events failed`, { 380 + safeMessage: e.message, 381 + }) 382 + } 383 383 384 384 this.dispatch({ 385 385 event: MessagesEventBusDispatchEvent.Error,
+4 -1
src/state/queries/notifications/settings.ts
··· 14 14 const RQKEY_ROOT = 'notification-settings' 15 15 const RQKEY = [RQKEY_ROOT] 16 16 17 - export function useNotificationSettingsQuery() { 17 + export function useNotificationSettingsQuery({ 18 + enabled, 19 + }: {enabled?: boolean} = {}) { 18 20 const agent = useAgent() 19 21 20 22 return useQuery({ ··· 23 25 const response = await agent.app.bsky.notification.getPreferences() 24 26 return response.data.preferences 25 27 }, 28 + enabled, 26 29 }) 27 30 } 28 31 export function useNotificationSettingsUpdateMutation() {
+3 -3
src/state/queries/postgate/index.ts
··· 2 2 import { 3 3 AppBskyEmbedRecord, 4 4 AppBskyEmbedRecordWithMedia, 5 - AppBskyFeedDefs, 5 + type AppBskyFeedDefs, 6 6 AppBskyFeedPostgate, 7 7 AtUri, 8 - BskyAgent, 8 + type BskyAgent, 9 9 } from '@atproto/api' 10 10 import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 11 11 ··· 139 139 staleTime: STALE.SECONDS.THIRTY, 140 140 queryKey: createPostgateQueryKey(postUri), 141 141 async queryFn() { 142 - return (await getPostgateRecord({agent, postUri})) ?? null 142 + return await getPostgateRecord({agent, postUri}).then(res => res ?? null) 143 143 }, 144 144 }) 145 145 }
+2 -2
src/view/com/notifications/NotificationFeed.tsx
··· 16 16 import {useNotificationFeedQuery} from '#/state/queries/notifications/feed' 17 17 import {EmptyState} from '#/view/com/util/EmptyState' 18 18 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 19 - import {List, type ListRef} from '#/view/com/util/List' 19 + import {List, type ListProps, type ListRef} from '#/view/com/util/List' 20 20 import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 21 21 import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' 22 22 import {NotificationFeedItem} from './NotificationFeedItem' ··· 39 39 scrollElRef?: ListRef 40 40 onPressTryAgain?: () => void 41 41 onScrolledDownChange: (isScrolledDown: boolean) => void 42 - ListHeaderComponent?: () => JSX.Element 42 + ListHeaderComponent?: ListProps['ListHeaderComponent'] 43 43 refreshNotifications: () => Promise<void> 44 44 }) { 45 45 const initialNumToRender = useInitialNumToRender()
+4 -4
src/view/com/util/List.tsx
··· 1 1 import React, {memo} from 'react' 2 - import {RefreshControl, ViewToken} from 'react-native' 2 + import {RefreshControl, type ViewToken} from 'react-native' 3 3 import { 4 - FlatListPropsWithLayout, 4 + type FlatListPropsWithLayout, 5 5 runOnJS, 6 6 useSharedValue, 7 7 } from 'react-native-reanimated' ··· 11 11 import {useDedupe} from '#/lib/hooks/useDedupe' 12 12 import {useScrollHandlers} from '#/lib/ScrollContext' 13 13 import {addStyle} from '#/lib/styles' 14 - import {isAndroid, isIOS} from '#/platform/detection' 14 + import {isIOS} from '#/platform/detection' 15 15 import {useLightbox} from '#/state/lightbox' 16 16 import {useTheme} from '#/alf' 17 17 import {FlatList_INTERNAL} from './Views' ··· 152 152 153 153 return ( 154 154 <FlatList_INTERNAL 155 - showsVerticalScrollIndicator={!isAndroid} // overridable 155 + showsVerticalScrollIndicator // overridable 156 156 onViewableItemsChanged={onViewableItemsChanged} 157 157 viewabilityConfig={viewabilityConfig} 158 158 {...props}
+54 -15
src/view/screens/Notifications.tsx
··· 1 - import React from 'react' 1 + import {useCallback, useEffect, useMemo, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' ··· 17 17 import {isNative} from '#/platform/detection' 18 18 import {emitSoftReset, listenSoftReset} from '#/state/events' 19 19 import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' 20 + import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' 20 21 import { 21 22 useUnreadNotifications, 22 23 useUnreadNotificationsApi, ··· 30 31 import {type ListMethods} from '#/view/com/util/List' 31 32 import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 32 33 import {MainScrollProvider} from '#/view/com/util/MainScrollProvider' 33 - import {atoms as a} from '#/alf' 34 + import {atoms as a, useTheme} from '#/alf' 34 35 import {web} from '#/alf' 36 + import {Admonition} from '#/components/Admonition' 35 37 import {ButtonIcon} from '#/components/Button' 36 38 import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2' 37 39 import * as Layout from '#/components/Layout' 38 - import {Link} from '#/components/Link' 40 + import {InlineLinkText, Link} from '#/components/Link' 39 41 import {Loader} from '#/components/Loader' 40 42 41 43 // We don't currently persist this across reloads since ··· 53 55 const unreadNotifs = useUnreadNotifications() 54 56 const hasNew = !!unreadNotifs 55 57 const {checkUnread: checkUnreadAll} = useUnreadNotificationsApi() 56 - const [isLoadingAll, setIsLoadingAll] = React.useState(false) 57 - const [isLoadingMentions, setIsLoadingMentions] = React.useState(false) 58 + const [isLoadingAll, setIsLoadingAll] = useState(false) 59 + const [isLoadingMentions, setIsLoadingMentions] = useState(false) 58 60 const initialActiveTab = lastActiveTab 59 - const [activeTab, setActiveTab] = React.useState(initialActiveTab) 61 + const [activeTab, setActiveTab] = useState(initialActiveTab) 60 62 const isLoading = activeTab === 0 ? isLoadingAll : isLoadingMentions 61 63 62 - const onPageSelected = React.useCallback( 64 + const onPageSelected = useCallback( 63 65 (index: number) => { 64 66 setActiveTab(index) 65 67 lastActiveTab = index ··· 68 70 ) 69 71 70 72 const queryClient = useQueryClient() 71 - const checkUnreadMentions = React.useCallback( 73 + const checkUnreadMentions = useCallback( 72 74 async ({invalidate}: {invalidate: boolean}) => { 73 75 if (invalidate) { 74 76 return truncateAndInvalidate(queryClient, NOTIFS_RQKEY('mentions')) ··· 80 82 [queryClient], 81 83 ) 82 84 83 - const sections = React.useMemo(() => { 85 + const sections = useMemo(() => { 84 86 return [ 85 87 { 86 88 title: _(msg`All`), ··· 186 188 }) { 187 189 const {_} = useLingui() 188 190 const setMinimalShellMode = useSetMinimalShellMode() 189 - const [isScrolledDown, setIsScrolledDown] = React.useState(false) 190 - const scrollElRef = React.useRef<ListMethods>(null) 191 + const [isScrolledDown, setIsScrolledDown] = useState(false) 192 + const scrollElRef = useRef<ListMethods>(null) 191 193 const queryClient = useQueryClient() 192 194 const isScreenFocused = useIsFocused() 193 195 const isFocusedAndActive = isScreenFocused && isActive 194 196 195 197 // event handlers 196 198 // = 197 - const scrollToTop = React.useCallback(() => { 199 + const scrollToTop = useCallback(() => { 198 200 scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0}) 199 201 setMinimalShellMode(false) 200 202 }, [scrollElRef, setMinimalShellMode]) 201 203 202 - const onPressLoadLatest = React.useCallback(() => { 204 + const onPressLoadLatest = useCallback(() => { 203 205 scrollToTop() 204 206 if (hasNew) { 205 207 // render what we have now ··· 238 240 // on-visible setup 239 241 // = 240 242 useFocusEffect( 241 - React.useCallback(() => { 243 + useCallback(() => { 242 244 if (isFocusedAndActive) { 243 245 setMinimalShellMode(false) 244 246 logger.debug('NotificationsScreen: Focus') ··· 246 248 } 247 249 }, [setMinimalShellMode, onFocusCheckLatest, isFocusedAndActive]), 248 250 ) 249 - React.useEffect(() => { 251 + 252 + useEffect(() => { 250 253 if (!isFocusedAndActive) { 251 254 return 252 255 } ··· 262 265 refreshNotifications={() => checkUnread({invalidate: true})} 263 266 onScrolledDownChange={setIsScrolledDown} 264 267 scrollElRef={scrollElRef} 268 + ListHeaderComponent={ 269 + filter === 'mentions' ? ( 270 + <DisabledNotificationsWarning active={isFocusedAndActive} /> 271 + ) : null 272 + } 265 273 /> 266 274 </MainScrollProvider> 267 275 {(isScrolledDown || hasNew) && ( ··· 274 282 </> 275 283 ) 276 284 } 285 + 286 + function DisabledNotificationsWarning({active}: {active: boolean}) { 287 + const t = useTheme() 288 + const {_} = useLingui() 289 + const {data} = useNotificationSettingsQuery({enabled: active}) 290 + 291 + if (!data) return null 292 + 293 + if (!data.reply.list && !data.quote.list && !data.mention.list) { 294 + // mention tab notifications are disabled 295 + return ( 296 + <View style={[a.py_md, a.px_lg, a.border_b, t.atoms.border_contrast_low]}> 297 + <Admonition type="warning"> 298 + <Trans> 299 + You have completely disabled reply, quote, and mention 300 + notifications, so this tab will no longer update. To adjust this, 301 + visit your{' '} 302 + <InlineLinkText 303 + label={_(msg`Visit your notification settings`)} 304 + to={{screen: 'NotificationSettings'}}> 305 + notification settings 306 + </InlineLinkText> 307 + . 308 + </Trans> 309 + </Admonition> 310 + </View> 311 + ) 312 + } 313 + 314 + return null 315 + }
-22
src/view/shell/createNativeStackNavigatorWithAuth.tsx
··· 40 40 import {SignupQueued} from '#/screens/SignupQueued' 41 41 import {Takendown} from '#/screens/Takendown' 42 42 import {atoms as a, useLayoutBreakpoints} from '#/alf' 43 - import {EmailDialog} from '#/components/dialogs/EmailDialog' 44 - import {InAppBrowserConsentDialog} from '#/components/dialogs/InAppBrowserConsent' 45 - import {LinkWarningDialog} from '#/components/dialogs/LinkWarning' 46 - import {MutedWordsDialog} from '#/components/dialogs/MutedWords' 47 - import {NuxDialogs} from '#/components/dialogs/nuxs' 48 - import {SigninDialog} from '#/components/dialogs/Signin' 49 - import {Outlet as PortalOutlet} from '#/components/Portal' 50 - import {BottomSheetOutlet} from '#/../modules/bottom-sheet' 51 43 import {BottomBarWeb} from './bottom-bar/BottomBarWeb' 52 44 import {DesktopLeftNav} from './desktop/LeftNav' 53 45 import {DesktopRightNav} from './desktop/RightNav' ··· 175 167 {!isMobile && <DesktopRightNav routeName={activeRoute.name} />} 176 168 </> 177 169 )} 178 - 179 - {/* Start: individual dialogs and outlets */} 180 - <MutedWordsDialog /> 181 - <SigninDialog /> 182 - <EmailDialog /> 183 - <LinkWarningDialog /> 184 - {!isWeb && <InAppBrowserConsentDialog />} 185 - <PortalOutlet /> 186 - <BottomSheetOutlet /> 187 - {/* End: individual dialogs and outlets */} 188 - 189 - {/* Start: dialog controllers */} 190 - <NuxDialogs /> 191 - {/* End: dialog controllers */} 192 170 </NavigationContent> 193 171 ) 194 172 }
+14
src/view/shell/index.tsx
··· 25 25 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 26 26 import {atoms as a, select, useTheme} from '#/alf' 27 27 import {setSystemUITheme} from '#/alf/util/systemUI' 28 + import {EmailDialog} from '#/components/dialogs/EmailDialog' 29 + import {InAppBrowserConsentDialog} from '#/components/dialogs/InAppBrowserConsent' 30 + import {LinkWarningDialog} from '#/components/dialogs/LinkWarning' 31 + import {MutedWordsDialog} from '#/components/dialogs/MutedWords' 32 + import {SigninDialog} from '#/components/dialogs/Signin' 33 + import {Outlet as PortalOutlet} from '#/components/Portal' 28 34 import {RoutesContainer, TabsNavigator} from '#/Navigation' 35 + import {BottomSheetOutlet} from '../../../modules/bottom-sheet' 29 36 import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView' 30 37 import {Composer} from './Composer' 31 38 import {DrawerContent} from './Drawer' ··· 145 152 </View> 146 153 <Composer winHeight={winDim.height} /> 147 154 <ModalsContainer /> 155 + <MutedWordsDialog /> 156 + <SigninDialog /> 157 + <EmailDialog /> 158 + <InAppBrowserConsentDialog /> 159 + <LinkWarningDialog /> 148 160 <Lightbox /> 161 + <PortalOutlet /> 162 + <BottomSheetOutlet /> 149 163 </> 150 164 ) 151 165 }
+10
src/view/shell/index.web.tsx
··· 17 17 import {ModalsContainer} from '#/view/com/modals/Modal' 18 18 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 19 19 import {atoms as a, select, useTheme} from '#/alf' 20 + import {EmailDialog} from '#/components/dialogs/EmailDialog' 21 + import {LinkWarningDialog} from '#/components/dialogs/LinkWarning' 22 + import {MutedWordsDialog} from '#/components/dialogs/MutedWords' 23 + import {SigninDialog} from '#/components/dialogs/Signin' 24 + import {Outlet as PortalOutlet} from '#/components/Portal' 20 25 import {FlatNavigator, RoutesContainer} from '#/Navigation' 21 26 import {Composer} from './Composer.web' 22 27 import {DrawerContent} from './Drawer' ··· 62 67 </ErrorBoundary> 63 68 <Composer winHeight={0} /> 64 69 <ModalsContainer /> 70 + <MutedWordsDialog /> 71 + <SigninDialog /> 72 + <EmailDialog /> 73 + <LinkWarningDialog /> 65 74 <Lightbox /> 75 + <PortalOutlet /> 66 76 67 77 {showDrawerDelayedExit && ( 68 78 <>
+4 -4
yarn.lock
··· 16843 16843 resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.6.tgz#69ec13f70d76e9245e275eed4140d0873a78f902" 16844 16844 integrity sha512-1pHnFTlBahins6UAajXUqeCOHew9l9C2C8tErnpGC3IyLJzvxD+TpYAixnCbrVS52f7+NvMttbiSI290XfwN0w== 16845 16845 16846 - react-native-keyboard-controller@^1.17.1: 16847 - version "1.17.1" 16848 - resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.17.1.tgz#46efe148c1bdd0ee22094dcb2660f70f81e6544e" 16849 - integrity sha512-YM3GYvtkuWimCKkZFURn5hIb1WiKOQqi2DijdwZSF5QSSzGqfqwzEEC3bm1xCN8HGHAEIXAaWIBUsc3Xp5L+Ng== 16846 + react-native-keyboard-controller@^1.17.5: 16847 + version "1.17.5" 16848 + resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.17.5.tgz#a517f0d42f73e69a03e768379934a3bb705595f5" 16849 + integrity sha512-2bZi4uH/beAcHiQ7nv6sxW03/UpNcnNAPpaSnQtg0cbU3ySThPRETMqr0ZupFLUSZovolyFhyFJLjxmQ7cavJg== 16850 16850 dependencies: 16851 16851 react-native-is-edge-to-edge "^1.1.6" 16852 16852