Bluesky app fork with some witchin' additions 💫

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

+747 -189
+1
.github/workflows/build-submit-android.yml
··· 13 13 14 14 jobs: 15 15 build: 16 + if: github.repository == 'bluesky-social/social-app' 16 17 name: Build and Submit Android 17 18 runs-on: ubuntu-latest 18 19 steps:
+1
.github/workflows/build-submit-ios.yml
··· 13 13 14 14 jobs: 15 15 build: 16 + if: github.repository == 'bluesky-social/social-app' 16 17 name: Build and Submit iOS 17 18 runs-on: macos-15 18 19 steps:
+3 -2
.github/workflows/bundle-deploy-eas-update.yml
··· 20 20 21 21 jobs: 22 22 bundleDeploy: 23 + if: github.repository == 'bluesky-social/social-app' 23 24 name: Bundle and Deploy EAS Update 24 25 runs-on: ubuntu-latest 25 26 concurrency: ··· 150 151 needs: [bundleDeploy] 151 152 # Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be 152 153 # available here 153 - if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.changes-detected }} 154 + if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.changes-detected && github.repository == 'bluesky-social/social-app' }} 154 155 steps: 155 156 - name: Check for EXPO_TOKEN 156 157 run: > ··· 239 240 needs: [bundleDeploy] 240 241 # Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be 241 242 # available here 242 - if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.changes-detected }} 243 + if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.changes-detected && github.repository == 'bluesky-social/social-app'}} 243 244 244 245 steps: 245 246 - name: Check for EXPO_TOKEN
+202
.github/workflows/pull-request-comment.yml
··· 1 + --- 2 + name: PR Comment Trigger 3 + 4 + on: 5 + issue_comment: 6 + types: [created] 7 + 8 + # Permissiosn to make comments in the pull request 9 + permissions: 10 + pull-requests: write 11 + actions: write 12 + contents: read 13 + 14 + jobs: 15 + handle-comment: 16 + if: github.event.issue.pull_request 17 + runs-on: ubuntu-latest 18 + outputs: 19 + should-deploy: ${{ steps.check-org.outputs.result }} 20 + 21 + steps: 22 + - name: Check if bot is mentioned 23 + id: check-mention 24 + env: 25 + COMMENT: ${{ github.event.comment.body }} 26 + run: | 27 + if [[ "$COMMENT" == *"@github-actions"* ]] || \ 28 + [[ "$COMMENT" == *"github-actions[bot]"* ]]; then 29 + bot_mentioned=true 30 + else 31 + bot_mentioned=false 32 + fi 33 + 34 + 35 + if [[ "${{ github.event.comment.body }}" == *"ota"* ]]; then 36 + has_ota=true 37 + else 38 + has_ota=false 39 + fi 40 + 41 + 42 + if [[ "$bot_mentioned" == "true" ]] && [[ "$has_ota" == "true" ]]; then 43 + echo "mentioned=true" >> $GITHUB_OUTPUT 44 + else 45 + echo "mentioned=false" >> $GITHUB_OUTPUT 46 + fi 47 + 48 + - name: Check organization membership 49 + if: steps.check-mention.outputs.mentioned == 'true' 50 + id: check-org 51 + uses: actions/github-script@v7 52 + with: 53 + script: | 54 + try { 55 + const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ 56 + owner: context.repo.owner, 57 + repo: context.repo.repo, 58 + username: context.payload.comment.user.login 59 + }); 60 + 61 + const hasAccess = ['admin', 'write'].includes(perm.permission); 62 + console.log(`User has ${perm.permission} access`); 63 + 64 + return hasAccess; 65 + } catch(error) { 66 + console.log('User has no repository access'); 67 + return false; 68 + } 69 + 70 + bundle-deploy: 71 + name: Bundle and Deploy EAS Update 72 + runs-on: ubuntu-latest 73 + needs: [handle-comment] 74 + if: needs.handle-comment.outputs.should-deploy == 'true' 75 + 76 + steps: 77 + - name: Get PR HEAD SHA 78 + id: pr-info 79 + uses: actions/github-script@v7 80 + with: 81 + script: | 82 + const pr = await github.rest.pulls.get({ 83 + owner: context.repo.owner, 84 + repo: context.repo.repo, 85 + pull_number: ${{ github.event.issue.number }} 86 + }); 87 + 88 + console.log(`PR HEAD SHA: ${pr.data.head.sha}`); 89 + console.log(`PR HEAD REF: ${pr.data.head.ref}`); 90 + 91 + core.setOutput('head-sha', pr.data.head.sha); 92 + core.setOutput('head-ref', pr.data.head.ref); 93 + 94 + - name: 💬 Drop a comment 95 + uses: marocchino/sticky-pull-request-comment@v2 96 + with: 97 + header: pull-request-eas-build-${{ steps.pr-info.outputs.head-sha }} 98 + number: ${{ github.event.issue.number }} 99 + message: | 100 + An OTA deployment has been requested and is now running for `${{ steps.pr-info.outputs.head-sha }}`. 101 + 102 + [Here is some music to listen to while you wait...](https://www.youtube.com/watch?v=VBlFHuCzPgY) 103 + --- 104 + *Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖* 105 + 106 + - name: Check for EXPO_TOKEN 107 + run: > 108 + if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then 109 + echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" 110 + exit 1 111 + fi 112 + 113 + - name: ⬇️ Checkout 114 + uses: actions/checkout@v4 115 + with: 116 + ref: ${{ steps.pr-info.outputs.head-sha }} 117 + 118 + - name: 🔧 Setup Node 119 + uses: actions/setup-node@v4 120 + with: 121 + node-version-file: .nvmrc 122 + cache: yarn 123 + 124 + - name: Install dependencies 125 + run: yarn install --frozen-lockfile 126 + 127 + - name: Lint check 128 + run: yarn lint 129 + 130 + - name: Lint lockfile 131 + run: yarn lockfile-lint 132 + 133 + - name: 🔤 Compile translations 134 + run: yarn intl:build 2>&1 | tee i18n.log 135 + 136 + - name: Check for i18n compilation errors 137 + run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi 138 + 139 + - name: Type check 140 + run: yarn typecheck 141 + 142 + - name: 🔨 Setup EAS 143 + uses: expo/expo-github-action@v8 144 + with: 145 + expo-version: latest 146 + eas-version: latest 147 + token: ${{ secrets.EXPO_TOKEN }} 148 + 149 + - name: ⛏️ Setup Expo 150 + run: yarn global add eas-cli-local-build-plugin 151 + 152 + - name: 🪛 Setup jq 153 + uses: dcarbone/install-jq-action@v2 154 + 155 + - name: ✏️ Write environment variables 156 + run: | 157 + export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}' 158 + echo "${{ secrets.ENV_TOKEN }}" > .env 159 + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env 160 + echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env 161 + echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env 162 + echo "$json" > google-services.json 163 + 164 + - name: Setup Sentry vars for build-time injection 165 + id: sentry 166 + run: | 167 + echo "SENTRY_DIST=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT 168 + echo "SENTRY_RELEASE=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT 169 + 170 + - name: 🏗️ Create Bundle 171 + 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="testflight" yarn export 172 + 173 + - name: 📦 Package Bundle and 🚀 Deploy 174 + run: yarn use-build-number bash scripts/bundleUpdate.sh 175 + env: 176 + DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }} 177 + CHANNEL_NAME: pull-request-${{ github.event.issue.number }} 178 + RUNTIME_VERSION: 179 + 180 + - name: 💬 Drop a comment 181 + uses: marocchino/sticky-pull-request-comment@v2 182 + with: 183 + header: pull-request-eas-build-${{ steps.pr-info.outputs.head-sha }} 184 + number: ${{ github.event.issue.number }} 185 + message: | 186 + Your requested OTA deployment was successful! You may now apply it by opening the deep link below in your browser: 187 + 188 + `bluesky://intent/apply-ota?channel=pull-request-${{ github.event.issue.number }}` 189 + --- 190 + *Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖* 191 + 192 + 193 + - name: 💬 Drop a comment 194 + uses: marocchino/sticky-pull-request-comment@v2 195 + if: failure() 196 + with: 197 + header: pull-request-eas-build-${{ steps.pr-info.outputs.head-sha }} 198 + number: ${{ github.event.issue.number }} 199 + message: | 200 + Your requested OTA deployment was unsuccessful. See action logs for more details. 201 + --- 202 + *Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
+31
.github/workflows/sync-internal.yaml
··· 1 + name: Sync to internal repo 2 + 3 + on: 4 + push: 5 + branches: [main] 6 + 7 + jobs: 8 + sync: 9 + runs-on: ubuntu-latest 10 + if: github.repository == 'bluesky-social/social-app' 11 + steps: 12 + - name: Checkout public repo 13 + uses: actions/checkout@v4 14 + with: 15 + fetch-depth: 0 16 + - name: Generate GitHub App Token 17 + id: app-token 18 + uses: actions/create-github-app-token@v1 19 + with: 20 + app-id: ${{ vars.SYNC_INTERNAL_APP_ID }} 21 + private-key: ${{ secrets.SYNC_INTERNAL_PK }} 22 + repositories: social-app-internal 23 + - name: Push to internal repo 24 + env: 25 + TOKEN: ${{ steps.app-token.outputs.token }} 26 + run: | 27 + git config user.name "github-actions" 28 + git config user.email "test@users.noreply.github.com" 29 + git config --unset-all http.https://github.com/.extraheader 30 + git remote add internal https://x-access-token:${TOKEN}@github.com/bluesky-social/social-app-internal.git 31 + git push internal main --force
+2
__tests__/lib/strings/mention-manip.test.ts
··· 32 32 ['@alice hello', 7, undefined], 33 33 ['alice@alice', 0, undefined], 34 34 ['alice@alice', 6, undefined], 35 + ['hello @alice-com goodbye', 8, 'alice-com'], 35 36 ] 36 37 37 38 it.each(cases)( ··· 72 73 ['@alice hello', 7, '@alice hello'], 73 74 ['alice@alice', 0, 'alice@alice'], 74 75 ['alice@alice', 6, 'alice@alice'], 76 + ['hello @alice-com goodbye', 10, 'hello @alice.com goodbye'], 75 77 ] 76 78 77 79 it.each(cases)(
+16
app.config.js
··· 29 29 // ? 'production' 30 30 // : undefined 31 31 // const UPDATES_ENABLED = !!UPDATES_CHANNEL 32 + const UPDATES_ENABLED = IS_TESTFLIGHT || IS_PRODUCTION 32 33 33 34 const USE_SENTRY = Boolean(process.env.SENTRY_AUTH_TOKEN) 34 35 ··· 194 195 // checkAutomatically: 'NEVER', 195 196 // channel: UPDATES_CHANNEL, 196 197 // }, 198 + updates: { 199 + url: 'https://updates.bsky.app/manifest', 200 + enabled: UPDATES_ENABLED, 201 + fallbackToCacheTimeout: 30000, 202 + codeSigningCertificate: UPDATES_ENABLED 203 + ? './code-signing/certificate.pem' 204 + : undefined, 205 + codeSigningMetadata: UPDATES_ENABLED 206 + ? { 207 + keyid: 'main', 208 + alg: 'rsa-v1_5-sha256', 209 + } 210 + : undefined, 211 + checkAutomatically: 'NEVER', 212 + }, 197 213 plugins: [ 198 214 'expo-video', 199 215 'expo-localization',
+3
src/alf/atoms.ts
··· 67 67 zIndex: 50, 68 68 }, 69 69 70 + overflow_visible: { 71 + overflow: 'visible', 72 + }, 70 73 overflow_hidden: { 71 74 overflow: 'hidden', 72 75 },
+10 -6
src/components/KnownFollowers.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' 3 + import { 4 + type AppBskyActorDefs, 5 + moderateProfile, 6 + type ModerationOpts, 7 + } from '@atproto/api' 4 8 import {msg, Plural, Trans} from '@lingui/macro' 5 9 import {useLingui} from '@lingui/react' 6 10 ··· 8 12 import {sanitizeDisplayName} from '#/lib/strings/display-names' 9 13 import {UserAvatar} from '#/view/com/util/UserAvatar' 10 14 import {atoms as a, useTheme} from '#/alf' 11 - import {Link, LinkProps} from '#/components/Link' 15 + import {Link, type LinkProps} from '#/components/Link' 12 16 import {Text} from '#/components/Typography' 13 - import * as bsky from '#/types/bsky' 17 + import type * as bsky from '#/types/bsky' 14 18 15 19 const AVI_SIZE = 30 16 20 const AVI_SIZE_SMALL = 20 ··· 137 141 <> 138 142 <View 139 143 style={[ 144 + a.flex_row, 140 145 { 141 146 height: SIZE, 142 - width: SIZE + (slice.length - 1) * a.gap_md.gap, 143 147 }, 144 148 pressed && { 145 149 opacity: 0.5, ··· 149 153 <View 150 154 key={prof.did} 151 155 style={[ 152 - a.absolute, 153 156 a.rounded_full, 154 157 { 155 158 borderWidth: AVI_BORDER, 156 159 borderColor: t.atoms.bg.backgroundColor, 157 160 width: SIZE + AVI_BORDER * 2, 158 161 height: SIZE + AVI_BORDER * 2, 159 - left: i * a.gap_md.gap, 160 162 zIndex: AVI_BORDER - i, 163 + marginLeft: i > 0 ? -8 : 0, 161 164 }, 162 165 ]}> 163 166 <UserAvatar ··· 165 168 avatar={prof.avatar} 166 169 moderation={moderation.ui('avatar')} 167 170 type={prof.associated?.labeler ? 'labeler' : 'user'} 171 + noBorder 168 172 /> 169 173 </View> 170 174 ))}
+45
src/components/SearchError.tsx
··· 1 + import {View} from 'react-native' 2 + 3 + import {usePalette} from '#/lib/hooks/usePalette' 4 + import {atoms as a, useBreakpoints} from '#/alf' 5 + import * as Layout from '#/components/Layout' 6 + import {Text} from '#/components/Typography' 7 + import {TimesLarge_Stroke2_Corner0_Rounded} from './icons/Times' 8 + 9 + export function SearchError({ 10 + title, 11 + children, 12 + }: { 13 + title?: string 14 + children?: React.ReactNode 15 + }) { 16 + const {gtMobile} = useBreakpoints() 17 + const pal = usePalette('default') 18 + 19 + return ( 20 + <Layout.Content> 21 + <View 22 + style={[ 23 + a.align_center, 24 + a.gap_4xl, 25 + a.px_xl, 26 + { 27 + paddingVertical: 150, 28 + }, 29 + ]}> 30 + <TimesLarge_Stroke2_Corner0_Rounded width={32} fill={pal.colors.icon} /> 31 + <View 32 + style={[ 33 + a.align_center, 34 + {maxWidth: gtMobile ? 394 : 294}, 35 + gtMobile ? a.gap_md : a.gap_sm, 36 + ]}> 37 + <Text style={[a.font_bold, a.text_lg, a.text_center, a.leading_snug]}> 38 + {title} 39 + </Text> 40 + {children} 41 + </View> 42 + </View> 43 + </Layout.Content> 44 + ) 45 + }
+13 -4
src/components/VideoPostCard.tsx
··· 411 411 onPressOut={onPressOut} 412 412 style={[ 413 413 a.flex_col, 414 + t.atoms.shadow_sm, 414 415 { 415 416 alignItems: undefined, 416 417 justifyContent: undefined, ··· 421 422 <View 422 423 style={[ 423 424 a.justify_center, 424 - a.rounded_md, 425 + a.rounded_lg, 425 426 a.overflow_hidden, 427 + a.border, 428 + t.atoms.border_contrast_low, 426 429 { 427 430 backgroundColor: black, 428 431 aspectRatio: 9 / 16, ··· 443 446 a.inset_0, 444 447 a.justify_center, 445 448 a.align_center, 449 + a.border, 450 + t.atoms.border_contrast_low, 446 451 { 447 452 backgroundColor: 'black', 448 453 opacity: 0.2, ··· 462 467 <View 463 468 style={[ 464 469 a.justify_center, 465 - a.rounded_md, 470 + a.rounded_lg, 466 471 a.overflow_hidden, 472 + a.border, 473 + t.atoms.border_contrast_low, 467 474 { 468 475 backgroundColor: black, 469 476 aspectRatio: 9 / 16, ··· 534 541 const black = getBlackColor(t) 535 542 536 543 return ( 537 - <View style={[a.flex_1]}> 544 + <View style={[a.flex_1, t.atoms.shadow_sm]}> 538 545 <View 539 546 style={[ 540 - a.rounded_md, 547 + a.rounded_lg, 541 548 a.overflow_hidden, 549 + a.border, 550 + t.atoms.border_contrast_low, 542 551 { 543 552 backgroundColor: black, 544 553 aspectRatio: 9 / 16,
+16 -15
src/components/interstitials/TrendingVideos.tsx
··· 16 16 import {Button, ButtonIcon} from '#/components/Button' 17 17 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 18 18 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 19 - import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending' 20 19 import {Link} from '#/components/Link' 21 20 import * as Prompt from '#/components/Prompt' 22 21 import {Text} from '#/components/Typography' ··· 25 24 CompactVideoPostCardPlaceholder, 26 25 } from '#/components/VideoPostCard' 27 26 28 - const CARD_WIDTH = 100 27 + const CARD_WIDTH = 108 29 28 30 29 const FEED_DESC = `feedgen|${VIDEO_FEED_URI}` 31 30 const FEED_PARAMS: { ··· 68 67 return ( 69 68 <View 70 69 style={[ 71 - a.pt_lg, 70 + a.pt_sm, 72 71 a.pb_lg, 73 72 a.border_t, 73 + a.overflow_hidden, 74 74 t.atoms.border_contrast_low, 75 75 t.atoms.bg_contrast_25, 76 76 ]}> ··· 82 82 a.align_center, 83 83 a.justify_between, 84 84 ]}> 85 - <View style={[a.flex_1, a.flex_row, a.align_center, a.gap_xs]}> 86 - <Graph /> 87 - <Text style={[a.text_md, a.font_bold, a.leading_snug]}> 88 - <Trans>Trending Videos</Trans> 89 - </Text> 90 - </View> 85 + <Text style={[a.text_sm, a.font_bold, a.leading_snug]}> 86 + <Trans>Trending Videos</Trans> 87 + </Text> 91 88 <Button 92 89 label={_(msg`Dismiss this section`)} 93 90 size="tiny" 94 - variant="ghost" 91 + variant="solid" 95 92 color="secondary" 96 - shape="round" 93 + shape="square" 97 94 onPress={() => trendingPrompt.open()}> 98 - <ButtonIcon icon={X} /> 95 + <ButtonIcon icon={X} size="sm" /> 99 96 </Button> 100 97 </View> 101 98 ··· 104 101 horizontal 105 102 showsHorizontalScrollIndicator={false} 106 103 decelerationRate="fast" 107 - snapToInterval={CARD_WIDTH + a.gap_sm.gap}> 104 + snapToInterval={CARD_WIDTH + a.gap_md.gap} 105 + style={[a.overflow_visible]}> 108 106 <View 109 107 style={[ 110 108 a.flex_row, 111 - a.gap_sm, 109 + a.gap_md, 112 110 { 113 111 paddingLeft: gutters.paddingLeft, 114 112 paddingRight: gutters.paddingRight, ··· 193 191 a.justify_center, 194 192 a.align_center, 195 193 a.flex_1, 196 - a.rounded_md, 194 + a.rounded_lg, 195 + a.border, 196 + t.atoms.border_contrast_low, 197 197 t.atoms.bg, 198 + t.atoms.shadow_sm, 198 199 ]}> 199 200 {({pressed}) => ( 200 201 <View
+15 -3
src/lib/hooks/useIntentHandler.ts
··· 1 1 import React from 'react' 2 + import {Alert} from 'react-native' 2 3 import * as Linking from 'expo-linking' 3 4 4 5 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 5 - import {logEvent} from '#/lib/statsig/statsig' 6 + import {logger} from '#/logger' 6 7 import {isNative} from '#/platform/detection' 7 8 import {useSession} from '#/state/session' 8 9 import {useCloseAllActiveElements} from '#/state/util' ··· 12 13 } from '#/components/ageAssurance/AgeAssuranceRedirectDialog' 13 14 import {useIntentDialogs} from '#/components/intents/IntentDialogs' 14 15 import {Referrer} from '../../../modules/expo-bluesky-swiss-army' 16 + import {useApplyPullRequestOTAUpdate} from './useOTAUpdates' 15 17 16 - type IntentType = 'compose' | 'verify-email' | 'age-assurance' 18 + type IntentType = 'compose' | 'verify-email' | 'age-assurance' | 'apply-ota' 17 19 18 20 const VALID_IMAGE_REGEX = /^[\w.:\-_/]+\|\d+(\.\d+)?\|\d+(\.\d+)?$/ 19 21 ··· 27 29 const ageAssuranceRedirectDialogControl = 28 30 useAgeAssuranceRedirectDialogControl() 29 31 const {currentAccount} = useSession() 32 + const {tryApplyUpdate} = useApplyPullRequestOTAUpdate() 30 33 31 34 React.useEffect(() => { 32 35 const handleIncomingURL = (url: string) => { 33 36 const referrerInfo = Referrer.getReferrerInfo() 34 37 if (referrerInfo && referrerInfo.hostname !== 'bsky.app') { 35 - logEvent('deepLink:referrerReceived', { 38 + logger.metric('deepLink:referrerReceived', { 36 39 to: url, 37 40 referrer: referrerInfo?.referrer, 38 41 hostname: referrerInfo?.hostname, ··· 92 95 } 93 96 return 94 97 } 98 + case 'apply-ota': { 99 + const channel = params.get('channel') 100 + if (!channel) { 101 + Alert.alert('Error', 'No channel provided to look for.') 102 + } else { 103 + tryApplyUpdate(channel) 104 + } 105 + } 95 106 default: { 96 107 return 97 108 } ··· 111 122 verifyEmailIntent, 112 123 ageAssuranceRedirectDialogControl, 113 124 currentAccount, 125 + tryApplyUpdate, 114 126 ]) 115 127 } 116 128
+107 -29
src/lib/hooks/useOTAUpdates.ts
··· 1 1 import React from 'react' 2 - import {Alert, AppState, AppStateStatus} from 'react-native' 2 + import {Alert, AppState, type AppStateStatus} from 'react-native' 3 3 import {nativeBuildVersion} from 'expo-application' 4 4 import { 5 5 checkForUpdateAsync, ··· 29 29 ) 30 30 } 31 31 32 + async function setExtraParamsPullRequest(channel: string) { 33 + await setExtraParamAsync( 34 + isIOS ? 'ios-build-number' : 'android-build-number', 35 + // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is. 36 + // This just ensures it gets passed as a string 37 + `${nativeBuildVersion}`, 38 + ) 39 + await setExtraParamAsync('channel', channel) 40 + } 41 + 42 + async function updateTestflight() { 43 + await setExtraParams() 44 + 45 + const res = await checkForUpdateAsync() 46 + if (res.isAvailable) { 47 + await fetchUpdateAsync() 48 + Alert.alert( 49 + 'Update Available', 50 + 'A new version of the app is available. Relaunch now?', 51 + [ 52 + { 53 + text: 'No', 54 + style: 'cancel', 55 + }, 56 + { 57 + text: 'Relaunch', 58 + style: 'default', 59 + onPress: async () => { 60 + await reloadAsync() 61 + }, 62 + }, 63 + ], 64 + ) 65 + } 66 + } 67 + 68 + export function useApplyPullRequestOTAUpdate() { 69 + const {currentlyRunning} = useUpdates() 70 + const [pending, setPending] = React.useState(false) 71 + const currentChannel = currentlyRunning?.channel 72 + const isCurrentlyRunningPullRequestDeployment = 73 + currentChannel?.startsWith('pull-request') 74 + 75 + const tryApplyUpdate = async (channel: string) => { 76 + setPending(true) 77 + await setExtraParamsPullRequest(channel) 78 + const res = await checkForUpdateAsync() 79 + if (res.isAvailable) { 80 + Alert.alert( 81 + 'Deployment Available', 82 + `A deployment of ${channel} is availalble. Applying this deployment may result in a bricked installation, in which case you will need to reinstall the app and may lose local data. Are you sure you want to proceed?`, 83 + [ 84 + { 85 + text: 'No', 86 + style: 'cancel', 87 + }, 88 + { 89 + text: 'Relaunch', 90 + style: 'default', 91 + onPress: async () => { 92 + await fetchUpdateAsync() 93 + await reloadAsync() 94 + }, 95 + }, 96 + ], 97 + ) 98 + } else { 99 + Alert.alert( 100 + 'No Deployment Available', 101 + `No new deployments of ${channel} are currently available for your current native build.`, 102 + ) 103 + } 104 + setPending(false) 105 + } 106 + 107 + const revertToEmbedded = async () => { 108 + try { 109 + await updateTestflight() 110 + } catch (e: any) { 111 + logger.error('Internal OTA Update Error', {error: `${e}`}) 112 + } 113 + } 114 + 115 + return { 116 + tryApplyUpdate, 117 + revertToEmbedded, 118 + isCurrentlyRunningPullRequestDeployment, 119 + currentChannel, 120 + pending, 121 + } 122 + } 123 + 32 124 export function useOTAUpdates() { 33 125 const shouldReceiveUpdates = isEnabled && !__DEV__ 34 126 ··· 36 128 const lastMinimize = React.useRef(0) 37 129 const ranInitialCheck = React.useRef(false) 38 130 const timeout = React.useRef<NodeJS.Timeout>() 39 - const {isUpdatePending} = useUpdates() 131 + const {currentlyRunning, isUpdatePending} = useUpdates() 132 + const currentChannel = currentlyRunning?.channel 40 133 41 134 const setCheckTimeout = React.useCallback(() => { 42 135 timeout.current = setTimeout(async () => { ··· 60 153 61 154 const onIsTestFlight = React.useCallback(async () => { 62 155 try { 63 - await setExtraParams() 64 - 65 - const res = await checkForUpdateAsync() 66 - if (res.isAvailable) { 67 - await fetchUpdateAsync() 68 - 69 - Alert.alert( 70 - 'Update Available', 71 - 'A new version of the app is available. Relaunch now?', 72 - [ 73 - { 74 - text: 'No', 75 - style: 'cancel', 76 - }, 77 - { 78 - text: 'Relaunch', 79 - style: 'default', 80 - onPress: async () => { 81 - await reloadAsync() 82 - }, 83 - }, 84 - ], 85 - ) 86 - } 156 + await updateTestflight() 87 157 } catch (e: any) { 88 158 logger.error('Internal OTA Update Error', {error: `${e}`}) 89 159 } 90 160 }, []) 91 161 92 162 React.useEffect(() => { 163 + // We don't need to check anything if the current update is a PR update 164 + if (currentChannel?.startsWith('pull-request')) { 165 + return 166 + } 167 + 93 168 // We use this setTimeout to allow Statsig to initialize before we check for an update 94 169 // For Testflight users, we can prompt the user to update immediately whenever there's an available update. This 95 170 // is suspect however with the Apple App Store guidelines, so we don't want to prompt production users to update ··· 103 178 104 179 setCheckTimeout() 105 180 ranInitialCheck.current = true 106 - }, [onIsTestFlight, setCheckTimeout, shouldReceiveUpdates]) 181 + }, [onIsTestFlight, currentChannel, setCheckTimeout, shouldReceiveUpdates]) 107 182 108 183 // After the app has been minimized for 15 minutes, we want to either A. install an update if one has become available 109 184 // or B check for an update again. 110 185 React.useEffect(() => { 111 - if (!isEnabled) return 186 + // We also don't start this timeout if the user is on a pull request update 187 + if (!isEnabled || currentChannel?.startsWith('pull-request')) { 188 + return 189 + } 112 190 113 191 const subscription = AppState.addEventListener( 114 192 'change', ··· 138 216 clearTimeout(timeout.current) 139 217 subscription.remove() 140 218 } 141 - }, [isUpdatePending, setCheckTimeout]) 219 + }, [isUpdatePending, currentChannel, setCheckTimeout]) 142 220 }
+9
src/lib/hooks/useOTAUpdates.web.ts
··· 1 1 export function useOTAUpdates() {} 2 + export function useApplyPullRequestOTAUpdate() { 3 + return { 4 + tryApplyUpdate: () => {}, 5 + revertToEmbedded: () => {}, 6 + isCurrentlyRunningPullRequestDeployment: false, 7 + currentChannel: 'web-build', 8 + pending: false, 9 + } 10 + }
+1 -1
src/lib/strings/mention-manip.ts
··· 7 7 text: string, 8 8 cursorPos: number, 9 9 ): FoundMention | undefined { 10 - let re = /(^|\s)@([a-z0-9.]*)/gi 10 + let re = /(^|\s)@([a-z0-9.-]*)/gi 11 11 let match 12 12 while ((match = re.exec(text))) { 13 13 const spaceOffset = match[1].length
+133 -109
src/locale/locales/en/messages.po
··· 174 174 msgid "{0}, a list by {1}" 175 175 msgstr "" 176 176 177 - #: src/view/com/util/UserAvatar.tsx:570 178 - #: src/view/com/util/UserAvatar.tsx:588 177 + #: src/view/com/util/UserAvatar.tsx:572 178 + #: src/view/com/util/UserAvatar.tsx:590 179 179 msgid "{0}'s avatar" 180 180 msgstr "" 181 181 ··· 475 475 msgid "<0>{date}</0> at {time}" 476 476 msgstr "" 477 477 478 + #: src/screens/Search/SearchResults.tsx:255 479 + msgid "<0>Sign in</0><1> or </1><2>create an account</2><3> </3><4>to search for news, sports, politics, and everything else happening on Bluesky.</4>" 480 + msgstr "" 481 + 478 482 #: src/screens/StarterPack/Wizard/index.tsx:440 479 483 msgid "<0>You</0> and<1> </1><2>{0} </2>are included in your starter pack" 480 484 msgstr "" ··· 514 518 515 519 #: src/Navigation.tsx:523 516 520 #: src/screens/Settings/AboutSettings.tsx:75 517 - #: src/screens/Settings/Settings.tsx:238 518 - #: src/screens/Settings/Settings.tsx:241 521 + #: src/screens/Settings/Settings.tsx:240 522 + #: src/screens/Settings/Settings.tsx:243 519 523 msgid "About" 520 524 msgstr "" 521 525 ··· 534 538 msgstr "" 535 539 536 540 #: src/screens/Settings/AccessibilitySettings.tsx:44 537 - #: src/screens/Settings/Settings.tsx:214 538 - #: src/screens/Settings/Settings.tsx:217 541 + #: src/screens/Settings/Settings.tsx:216 542 + #: src/screens/Settings/Settings.tsx:219 539 543 msgid "Accessibility" 540 544 msgstr "" 541 545 ··· 546 550 #: src/Navigation.tsx:398 547 551 #: src/screens/Login/LoginForm.tsx:194 548 552 #: src/screens/Settings/AccountSettings.tsx:49 549 - #: src/screens/Settings/Settings.tsx:168 550 - #: src/screens/Settings/Settings.tsx:171 553 + #: src/screens/Settings/Settings.tsx:170 554 + #: src/screens/Settings/Settings.tsx:173 551 555 msgid "Account" 552 556 msgstr "" 553 557 ··· 578 582 msgid "Account Muted by List" 579 583 msgstr "" 580 584 581 - #: src/screens/Settings/Settings.tsx:532 585 + #: src/screens/Settings/Settings.tsx:548 582 586 msgid "Account options" 583 587 msgstr "" 584 588 585 - #: src/screens/Settings/Settings.tsx:568 589 + #: src/screens/Settings/Settings.tsx:584 586 590 msgid "Account removed from quick access" 587 591 msgstr "" 588 592 ··· 664 668 msgid "Add alt text (optional)" 665 669 msgstr "" 666 670 667 - #: src/screens/Settings/Settings.tsx:472 668 - #: src/screens/Settings/Settings.tsx:475 671 + #: src/screens/Settings/Settings.tsx:488 672 + #: src/screens/Settings/Settings.tsx:491 669 673 #: src/view/shell/desktop/LeftNav.tsx:259 670 674 #: src/view/shell/desktop/LeftNav.tsx:263 671 675 msgid "Add another account" ··· 1089 1093 1090 1094 #: src/Navigation.tsx:390 1091 1095 #: src/screens/Settings/AppearanceSettings.tsx:88 1092 - #: src/screens/Settings/Settings.tsx:206 1093 - #: src/screens/Settings/Settings.tsx:209 1096 + #: src/screens/Settings/Settings.tsx:208 1097 + #: src/screens/Settings/Settings.tsx:211 1094 1098 msgid "Appearance" 1095 1099 msgstr "" 1096 1100 ··· 1230 1234 1231 1235 #: src/components/dms/dialogs/NewChatDialog.tsx:54 1232 1236 #: src/components/dms/MessageProfileButton.tsx:58 1233 - #: src/screens/Messages/ChatList.tsx:348 1237 + #: src/screens/Messages/ChatList.tsx:362 1234 1238 #: src/screens/Messages/Conversation.tsx:228 1235 1239 msgid "Before you can message another user, you must first verify your email." 1236 1240 msgstr "" ··· 1519 1523 #: src/screens/Settings/AppIconSettings/index.tsx:225 1520 1524 #: src/screens/Settings/components/ChangeHandleDialog.tsx:78 1521 1525 #: src/screens/Settings/components/ChangeHandleDialog.tsx:85 1522 - #: src/screens/Settings/Settings.tsx:283 1526 + #: src/screens/Settings/Settings.tsx:285 1523 1527 #: src/screens/Takendown.tsx:99 1524 1528 #: src/screens/Takendown.tsx:102 1525 1529 #: src/view/com/composer/Composer.tsx:965 ··· 1669 1673 1670 1674 #: src/components/dms/ConvoMenu.tsx:75 1671 1675 #: src/Navigation.tsx:553 1672 - #: src/screens/Messages/ChatList.tsx:357 1676 + #: src/screens/Messages/ChatList.tsx:371 1673 1677 msgid "Chat settings" 1674 1678 msgstr "" 1675 1679 ··· 1684 1688 msgstr "" 1685 1689 1686 1690 #: src/screens/Messages/ChatList.tsx:76 1687 - #: src/screens/Messages/ChatList.tsx:373 1688 - #: src/screens/Messages/ChatList.tsx:397 1691 + #: src/screens/Messages/ChatList.tsx:387 1692 + #: src/screens/Messages/ChatList.tsx:411 1689 1693 msgid "Chats" 1690 1694 msgstr "" 1691 1695 ··· 1742 1746 msgid "Choose your username" 1743 1747 msgstr "" 1744 1748 1745 - #: src/screens/Settings/Settings.tsx:450 1749 + #: src/screens/Settings/Settings.tsx:457 1746 1750 msgid "Clear all storage data" 1747 1751 msgstr "" 1748 1752 1749 - #: src/screens/Settings/Settings.tsx:452 1753 + #: src/screens/Settings/Settings.tsx:459 1750 1754 msgid "Clear all storage data (restart after this)" 1751 1755 msgstr "" 1752 1756 ··· 2023 2027 msgid "Content & Media" 2024 2028 msgstr "" 2025 2029 2026 - #: src/screens/Settings/Settings.tsx:198 2027 - #: src/screens/Settings/Settings.tsx:201 2030 + #: src/screens/Settings/Settings.tsx:200 2031 + #: src/screens/Settings/Settings.tsx:203 2028 2032 msgid "Content and media" 2029 2033 msgstr "" 2030 2034 ··· 2303 2307 msgid "Create Account" 2304 2308 msgstr "" 2305 2309 2310 + #: src/screens/Search/SearchResults.tsx:266 2311 + msgid "create an account" 2312 + msgstr "" 2313 + 2306 2314 #: src/components/dialogs/Signin.tsx:86 2307 2315 #: src/components/dialogs/Signin.tsx:88 2308 2316 msgid "Create an account" ··· 2391 2399 msgid "Deactivate account" 2392 2400 msgstr "" 2393 2401 2394 - #: src/screens/Settings/Settings.tsx:415 2402 + #: src/screens/Settings/Settings.tsx:422 2395 2403 msgid "Debug Moderation" 2396 2404 msgstr "" 2397 2405 ··· 2440 2448 msgid "Delete chat" 2441 2449 msgstr "" 2442 2450 2443 - #: src/screens/Settings/Settings.tsx:422 2451 + #: src/screens/Settings/Settings.tsx:429 2444 2452 msgid "Delete chat declaration record" 2445 2453 msgstr "" 2446 2454 ··· 2556 2564 msgid "Developer mode enabled" 2557 2565 msgstr "" 2558 2566 2559 - #: src/screens/Settings/Settings.tsx:265 2560 - #: src/screens/Settings/Settings.tsx:268 2567 + #: src/screens/Settings/Settings.tsx:267 2568 + #: src/screens/Settings/Settings.tsx:270 2561 2569 msgid "Developer options" 2562 2570 msgstr "" 2563 2571 ··· 2652 2660 msgid "Dismiss interests" 2653 2661 msgstr "" 2654 2662 2655 - #: src/components/interstitials/TrendingVideos.tsx:92 2663 + #: src/components/interstitials/TrendingVideos.tsx:89 2656 2664 msgid "Dismiss this section" 2657 2665 msgstr "" 2658 2666 ··· 2825 2833 msgid "Edit" 2826 2834 msgstr "" 2827 2835 2828 - #: src/view/com/util/UserAvatar.tsx:437 2836 + #: src/view/com/util/UserAvatar.tsx:439 2829 2837 #: src/view/com/util/UserBanner.tsx:119 2830 2838 msgid "Edit avatar" 2831 2839 msgstr "" ··· 3127 3135 msgid "Error:" 3128 3136 msgstr "" 3129 3137 3130 - #: src/screens/Search/SearchResults.tsx:131 3138 + #: src/screens/Search/SearchResults.tsx:144 3131 3139 msgid "Error: {error}" 3132 3140 msgstr "" 3133 3141 ··· 3329 3337 msgid "Failed to delete starter pack" 3330 3338 msgstr "" 3331 3339 3332 - #: src/screens/Messages/ChatList.tsx:260 3340 + #: src/screens/Messages/ChatList.tsx:274 3333 3341 #: src/screens/Messages/Inbox.tsx:208 3334 3342 msgid "Failed to load conversations" 3335 3343 msgstr "" ··· 3505 3513 msgstr "" 3506 3514 3507 3515 #: src/Navigation.tsx:573 3508 - #: src/screens/Search/SearchResults.tsx:68 3516 + #: src/screens/Search/SearchResults.tsx:73 3509 3517 #: src/screens/StarterPack/StarterPackScreen.tsx:190 3510 3518 #: src/view/screens/Feeds.tsx:511 3511 3519 #: src/view/screens/Profile.tsx:230 ··· 3657 3665 msgid "Follow Back" 3658 3666 msgstr "" 3659 3667 3660 - #: src/components/KnownFollowers.tsx:234 3668 + #: src/components/KnownFollowers.tsx:238 3661 3669 msgid "Followed by <0>{0}</0>" 3662 3670 msgstr "" 3663 3671 3664 - #: src/components/KnownFollowers.tsx:220 3672 + #: src/components/KnownFollowers.tsx:224 3665 3673 msgid "Followed by <0>{0}</0> and {1, plural, one {# other} other {# others}}" 3666 3674 msgstr "" 3667 3675 3668 - #: src/components/KnownFollowers.tsx:207 3676 + #: src/components/KnownFollowers.tsx:211 3669 3677 msgid "Followed by <0>{0}</0> and <1>{1}</1>" 3670 3678 msgstr "" 3671 3679 3672 - #: src/components/KnownFollowers.tsx:189 3680 + #: src/components/KnownFollowers.tsx:193 3673 3681 msgid "Followed by <0>{0}</0>, <1>{1}</1>, and {2, plural, one {# other} other {# others}}" 3674 3682 msgstr "" 3675 3683 ··· 4022 4030 msgid "Having trouble?" 4023 4031 msgstr "" 4024 4032 4025 - #: src/screens/Settings/Settings.tsx:230 4026 - #: src/screens/Settings/Settings.tsx:234 4033 + #: src/screens/Settings/Settings.tsx:232 4034 + #: src/screens/Settings/Settings.tsx:236 4027 4035 #: src/view/shell/desktop/RightNav.tsx:120 4028 4036 #: src/view/shell/desktop/RightNav.tsx:121 4029 4037 #: src/view/shell/Drawer.tsx:370 ··· 4039 4047 msgstr "" 4040 4048 4041 4049 #: src/components/VideoPostCard.tsx:178 4042 - #: src/components/VideoPostCard.tsx:455 4050 + #: src/components/VideoPostCard.tsx:460 4043 4051 msgid "Hidden" 4044 4052 msgstr "" 4045 4053 ··· 4052 4060 msgstr "" 4053 4061 4054 4062 #: src/components/interstitials/Trending.tsx:131 4055 - #: src/components/interstitials/TrendingVideos.tsx:140 4063 + #: src/components/interstitials/TrendingVideos.tsx:138 4056 4064 #: src/components/moderation/ContentHider.tsx:203 4057 4065 #: src/components/moderation/LabelPreference.tsx:141 4058 4066 #: src/components/moderation/PostHider.tsx:134 ··· 4112 4120 msgid "Hide trending topics?" 4113 4121 msgstr "" 4114 4122 4115 - #: src/components/interstitials/TrendingVideos.tsx:138 4123 + #: src/components/interstitials/TrendingVideos.tsx:136 4116 4124 msgid "Hide trending videos?" 4117 4125 msgstr "" 4118 4126 ··· 4505 4513 msgstr "" 4506 4514 4507 4515 #: src/screens/Settings/LanguageSettings.tsx:78 4508 - #: src/screens/Settings/Settings.tsx:222 4509 - #: src/screens/Settings/Settings.tsx:225 4516 + #: src/screens/Settings/Settings.tsx:224 4517 + #: src/screens/Settings/Settings.tsx:227 4510 4518 msgid "Languages" 4511 4519 msgstr "" 4512 4520 ··· 4523 4531 msgstr "" 4524 4532 4525 4533 #: src/screens/Hashtag.tsx:95 4526 - #: src/screens/Search/SearchResults.tsx:52 4534 + #: src/screens/Search/SearchResults.tsx:57 4527 4535 #: src/screens/Topic.tsx:77 4528 4536 msgid "Latest" 4529 4537 msgstr "" ··· 5012 5020 5013 5021 #: src/Navigation.tsx:176 5014 5022 #: src/screens/Moderation/index.tsx:99 5015 - #: src/screens/Settings/Settings.tsx:182 5016 - #: src/screens/Settings/Settings.tsx:185 5023 + #: src/screens/Settings/Settings.tsx:184 5024 + #: src/screens/Settings/Settings.tsx:187 5017 5025 msgid "Moderation" 5018 5026 msgstr "" 5019 5027 ··· 5290 5298 msgstr "" 5291 5299 5292 5300 #: src/components/dms/dialogs/NewChatDialog.tsx:67 5293 - #: src/screens/Messages/ChatList.tsx:380 5294 - #: src/screens/Messages/ChatList.tsx:387 5301 + #: src/screens/Messages/ChatList.tsx:394 5302 + #: src/screens/Messages/ChatList.tsx:401 5295 5303 msgid "New chat" 5296 5304 msgstr "" 5297 5305 ··· 5513 5521 msgid "No results found for \"{query}\"" 5514 5522 msgstr "" 5515 5523 5516 - #: src/screens/Search/SearchResults.tsx:248 5517 - #: src/screens/Search/SearchResults.tsx:284 5518 - #: src/screens/Search/SearchResults.tsx:329 5524 + #: src/screens/Search/SearchResults.tsx:311 5525 + #: src/screens/Search/SearchResults.tsx:347 5526 + #: src/screens/Search/SearchResults.tsx:392 5519 5527 msgid "No results found for {query}" 5520 5528 msgstr "" 5521 5529 ··· 5561 5569 msgid "None" 5562 5570 msgstr "" 5563 5571 5564 - #: src/components/KnownFollowers.tsx:255 5572 + #: src/components/KnownFollowers.tsx:259 5565 5573 msgid "Not followed by anyone you're following" 5566 5574 msgstr "" 5567 5575 ··· 5582 5590 msgid "Note: This post is only visible to logged-in users." 5583 5591 msgstr "" 5584 5592 5585 - #: src/screens/Messages/ChatList.tsx:281 5593 + #: src/screens/Messages/ChatList.tsx:295 5586 5594 msgid "Nothing here" 5587 5595 msgstr "" 5588 5596 ··· 5614 5622 #: src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx:30 5615 5623 #: src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx:30 5616 5624 #: src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx:30 5617 - #: src/screens/Settings/Settings.tsx:190 5618 - #: src/screens/Settings/Settings.tsx:193 5625 + #: src/screens/Settings/Settings.tsx:192 5626 + #: src/screens/Settings/Settings.tsx:195 5619 5627 #: src/view/screens/Notifications.tsx:130 5620 5628 #: src/view/shell/bottom-bar/BottomBar.tsx:252 5621 5629 #: src/view/shell/desktop/LeftNav.tsx:655 ··· 5688 5696 msgid "on<0><1/><2><3/></2></0>" 5689 5697 msgstr "" 5690 5698 5691 - #: src/screens/Settings/Settings.tsx:372 5699 + #: src/screens/Settings/Settings.tsx:379 5692 5700 msgid "Onboarding reset" 5693 5701 msgstr "" 5694 5702 ··· 5786 5794 msgid "Open message options" 5787 5795 msgstr "" 5788 5796 5789 - #: src/screens/Settings/Settings.tsx:413 5797 + #: src/screens/Settings/Settings.tsx:420 5790 5798 msgid "Open moderation debug page" 5791 5799 msgstr "" 5792 5800 ··· 5815 5823 msgid "Open starter pack menu" 5816 5824 msgstr "" 5817 5825 5818 - #: src/screens/Settings/Settings.tsx:406 5819 - #: src/screens/Settings/Settings.tsx:420 5826 + #: src/screens/Settings/Settings.tsx:413 5827 + #: src/screens/Settings/Settings.tsx:427 5820 5828 msgid "Open storybook page" 5821 5829 msgstr "" 5822 5830 5823 - #: src/screens/Settings/Settings.tsx:399 5831 + #: src/screens/Settings/Settings.tsx:406 5824 5832 msgid "Open system log" 5825 5833 msgstr "" 5826 5834 ··· 5882 5890 msgid "Opens GIF select dialog" 5883 5891 msgstr "" 5884 5892 5885 - #: src/screens/Settings/Settings.tsx:231 5893 + #: src/screens/Settings/Settings.tsx:233 5886 5894 msgid "Opens helpdesk in browser" 5887 5895 msgstr "" 5888 5896 ··· 5894 5902 msgid "Opens list of invite codes" 5895 5903 msgstr "" 5896 5904 5897 - #: src/view/com/util/UserAvatar.tsx:574 5905 + #: src/view/com/util/UserAvatar.tsx:576 5898 5906 msgid "Opens live status dialog" 5899 5907 msgstr "" 5900 5908 ··· 5903 5911 msgstr "" 5904 5912 5905 5913 #: src/view/com/notifications/NotificationFeedItem.tsx:902 5906 - #: src/view/com/util/UserAvatar.tsx:592 5914 + #: src/view/com/util/UserAvatar.tsx:594 5907 5915 msgid "Opens this profile" 5908 5916 msgstr "" 5909 5917 ··· 6014 6022 msgid "Pause video" 6015 6023 msgstr "" 6016 6024 6017 - #: src/screens/Search/SearchResults.tsx:62 6025 + #: src/screens/Search/SearchResults.tsx:67 6018 6026 #: src/screens/StarterPack/StarterPackScreen.tsx:189 6019 6027 #: src/view/screens/ProfileList.tsx:170 6020 6028 msgid "People" ··· 6404 6412 msgid "Press to retry" 6405 6413 msgstr "" 6406 6414 6407 - #: src/components/KnownFollowers.tsx:125 6415 + #: src/components/KnownFollowers.tsx:129 6408 6416 msgid "Press to view followers of this account that you also follow" 6409 6417 msgstr "" 6410 6418 ··· 6428 6436 msgid "Privacy" 6429 6437 msgstr "" 6430 6438 6431 - #: src/screens/Settings/Settings.tsx:176 6432 - #: src/screens/Settings/Settings.tsx:179 6439 + #: src/screens/Settings/Settings.tsx:178 6440 + #: src/screens/Settings/Settings.tsx:181 6433 6441 msgid "Privacy and security" 6434 6442 msgstr "" 6435 6443 ··· 6678 6686 msgid "Reject chat request" 6679 6687 msgstr "" 6680 6688 6681 - #: src/screens/Messages/ChatList.tsx:264 6689 + #: src/screens/Messages/ChatList.tsx:278 6682 6690 #: src/screens/Messages/Inbox.tsx:212 6683 6691 msgid "Reload conversations" 6684 6692 msgstr "" ··· 6688 6696 #: src/components/FeedCard.tsx:343 6689 6697 #: src/components/StarterPack/Wizard/WizardListCard.tsx:102 6690 6698 #: src/components/StarterPack/Wizard/WizardListCard.tsx:109 6691 - #: src/screens/Settings/Settings.tsx:570 6699 + #: src/screens/Settings/Settings.tsx:586 6692 6700 #: src/view/com/modals/UserAddRemoveLists.tsx:235 6693 6701 #: src/view/com/posts/PostFeedErrorMessage.tsx:217 6694 6702 msgid "Remove" ··· 6702 6710 msgid "Remove {historyItem}" 6703 6711 msgstr "" 6704 6712 6705 - #: src/screens/Settings/Settings.tsx:549 6706 - #: src/screens/Settings/Settings.tsx:552 6713 + #: src/screens/Settings/Settings.tsx:565 6714 + #: src/screens/Settings/Settings.tsx:568 6707 6715 msgid "Remove account" 6708 6716 msgstr "" 6709 6717 ··· 6711 6719 msgid "Remove attachment" 6712 6720 msgstr "" 6713 6721 6714 - #: src/view/com/util/UserAvatar.tsx:496 6715 - #: src/view/com/util/UserAvatar.tsx:499 6722 + #: src/view/com/util/UserAvatar.tsx:498 6723 + #: src/view/com/util/UserAvatar.tsx:501 6716 6724 msgid "Remove Avatar" 6717 6725 msgstr "" 6718 6726 ··· 6742 6750 msgid "Remove from my feeds" 6743 6751 msgstr "" 6744 6752 6745 - #: src/screens/Settings/Settings.tsx:562 6753 + #: src/screens/Settings/Settings.tsx:578 6746 6754 msgid "Remove from quick access?" 6747 6755 msgstr "" 6748 6756 ··· 7107 7115 msgid "Resend Verification Email" 7108 7116 msgstr "" 7109 7117 7110 - #: src/screens/Settings/Settings.tsx:442 7111 - #: src/screens/Settings/Settings.tsx:444 7118 + #: src/screens/Settings/Settings.tsx:449 7119 + #: src/screens/Settings/Settings.tsx:451 7112 7120 msgid "Reset activity subscription nudge" 7113 7121 msgstr "" 7114 7122 ··· 7121 7129 msgid "Reset Code" 7122 7130 msgstr "" 7123 7131 7124 - #: src/screens/Settings/Settings.tsx:427 7125 - #: src/screens/Settings/Settings.tsx:429 7132 + #: src/screens/Settings/Settings.tsx:434 7133 + #: src/screens/Settings/Settings.tsx:436 7126 7134 msgid "Reset onboarding state" 7127 7135 msgstr "" 7128 7136 ··· 7148 7156 #: src/components/StarterPack/ProfileStarterPacks.tsx:346 7149 7157 #: src/screens/Login/LoginForm.tsx:323 7150 7158 #: src/screens/Login/LoginForm.tsx:330 7151 - #: src/screens/Messages/ChatList.tsx:270 7159 + #: src/screens/Messages/ChatList.tsx:284 7152 7160 #: src/screens/Messages/components/MessageListError.tsx:25 7153 7161 #: src/screens/Messages/Inbox.tsx:218 7154 7162 #: src/screens/Onboarding/StepInterests/index.tsx:217 ··· 7330 7338 msgid "Search GIFs" 7331 7339 msgstr "" 7332 7340 7341 + #: src/screens/Search/SearchResults.tsx:253 7342 + msgid "Search is currently unavailable when logged out" 7343 + msgstr "" 7344 + 7333 7345 #: src/screens/Profile/ProfileSearch.tsx:36 7334 7346 msgid "Search my posts" 7335 7347 msgstr "" ··· 7606 7618 msgstr "" 7607 7619 7608 7620 #: src/Navigation.tsx:212 7609 - #: src/screens/Settings/Settings.tsx:93 7621 + #: src/screens/Settings/Settings.tsx:95 7610 7622 #: src/view/shell/desktop/LeftNav.tsx:728 7611 7623 #: src/view/shell/Drawer.tsx:572 7612 7624 msgid "Settings" ··· 7866 7878 msgid "Shows information about when this post was created" 7867 7879 msgstr "" 7868 7880 7869 - #: src/screens/Settings/Settings.tsx:119 7881 + #: src/screens/Settings/Settings.tsx:121 7870 7882 msgid "Shows other accounts you can switch to" 7871 7883 msgstr "" 7872 7884 ··· 7875 7887 msgid "Shows the content" 7876 7888 msgstr "" 7877 7889 7890 + #: src/screens/Search/SearchResults.tsx:258 7891 + msgid "sign in" 7892 + msgstr "" 7893 + 7878 7894 #: src/components/dialogs/Signin.tsx:97 7879 7895 #: src/components/dialogs/Signin.tsx:99 7880 7896 #: src/screens/Login/index.tsx:122 ··· 7923 7939 msgid "Sign in to view post" 7924 7940 msgstr "" 7925 7941 7926 - #: src/screens/Settings/Settings.tsx:248 7927 7942 #: src/screens/Settings/Settings.tsx:250 7928 - #: src/screens/Settings/Settings.tsx:282 7943 + #: src/screens/Settings/Settings.tsx:252 7944 + #: src/screens/Settings/Settings.tsx:284 7929 7945 #: src/screens/SignupQueued.tsx:93 7930 7946 #: src/screens/SignupQueued.tsx:96 7931 7947 #: src/screens/Takendown.tsx:85 ··· 7939 7955 msgid "Sign Out" 7940 7956 msgstr "" 7941 7957 7942 - #: src/screens/Settings/Settings.tsx:279 7958 + #: src/screens/Settings/Settings.tsx:281 7943 7959 #: src/view/shell/desktop/LeftNav.tsx:208 7944 7960 msgid "Sign out?" 7945 7961 msgstr "" ··· 8143 8159 msgid "Step {0} of {1}" 8144 8160 msgstr "" 8145 8161 8146 - #: src/screens/Settings/Settings.tsx:377 8162 + #: src/screens/Settings/Settings.tsx:384 8147 8163 msgid "Storage cleared, you need to restart the app now." 8148 8164 msgstr "" 8149 8165 8150 8166 #: src/Navigation.tsx:305 8151 - #: src/screens/Settings/Settings.tsx:408 8167 + #: src/screens/Settings/Settings.tsx:415 8152 8168 msgid "Storybook" 8153 8169 msgstr "" 8154 8170 ··· 8241 8257 msgid "Support" 8242 8258 msgstr "" 8243 8259 8244 - #: src/screens/Settings/Settings.tsx:117 8245 - #: src/screens/Settings/Settings.tsx:131 8246 - #: src/screens/Settings/Settings.tsx:512 8260 + #: src/screens/Settings/Settings.tsx:119 8261 + #: src/screens/Settings/Settings.tsx:133 8262 + #: src/screens/Settings/Settings.tsx:528 8247 8263 #: src/view/shell/desktop/LeftNav.tsx:245 8248 8264 msgid "Switch account" 8249 8265 msgstr "" ··· 8270 8286 8271 8287 #: src/screens/Settings/AboutSettings.tsx:107 8272 8288 #: src/screens/Settings/AboutSettings.tsx:110 8273 - #: src/screens/Settings/Settings.tsx:401 8289 + #: src/screens/Settings/Settings.tsx:408 8274 8290 msgid "System log" 8275 8291 msgstr "" 8276 8292 ··· 8838 8854 msgid "This will delete \"{0}\" from your muted words. You can always add it back later." 8839 8855 msgstr "" 8840 8856 8841 - #: src/screens/Settings/Settings.tsx:564 8857 + #: src/screens/Settings/Settings.tsx:580 8842 8858 msgid "This will remove @{0} from the quick access list." 8843 8859 msgstr "" 8844 8860 ··· 8916 8932 msgstr "" 8917 8933 8918 8934 #: src/screens/Hashtag.tsx:84 8919 - #: src/screens/Search/SearchResults.tsx:42 8935 + #: src/screens/Search/SearchResults.tsx:47 8920 8936 #: src/screens/Topic.tsx:71 8921 8937 msgid "Top" 8922 8938 msgstr "" ··· 8952 8968 msgid "Trending" 8953 8969 msgstr "" 8954 8970 8955 - #: src/components/interstitials/TrendingVideos.tsx:88 8971 + #: src/components/interstitials/TrendingVideos.tsx:86 8956 8972 msgid "Trending Videos" 8957 8973 msgstr "" 8958 8974 ··· 9004 9020 msgid "Unable to delete" 9005 9021 msgstr "" 9006 9022 9023 + #: src/screens/Settings/Settings.tsx:465 9024 + msgid "Unapply Pull Request" 9025 + msgstr "" 9026 + 9027 + #: src/screens/Settings/Settings.tsx:467 9028 + msgid "Unapply Pull Request {currentChannel}" 9029 + msgstr "" 9030 + 9007 9031 #: src/components/ageAssurance/AgeRestrictedScreen.tsx:51 9008 9032 msgid "Unavailable" 9009 9033 msgstr "" ··· 9163 9187 msgid "Unpinned from your feeds" 9164 9188 msgstr "" 9165 9189 9166 - #: src/screens/Settings/Settings.tsx:434 9167 - #: src/screens/Settings/Settings.tsx:436 9190 + #: src/screens/Settings/Settings.tsx:441 9191 + #: src/screens/Settings/Settings.tsx:443 9168 9192 msgid "Unsnooze email reminder" 9169 9193 msgstr "" 9170 9194 ··· 9247 9271 msgid "Upload a text file to:" 9248 9272 msgstr "" 9249 9273 9250 - #: src/view/com/util/UserAvatar.tsx:467 9251 - #: src/view/com/util/UserAvatar.tsx:470 9274 + #: src/view/com/util/UserAvatar.tsx:469 9275 + #: src/view/com/util/UserAvatar.tsx:472 9252 9276 #: src/view/com/util/UserBanner.tsx:157 9253 9277 #: src/view/com/util/UserBanner.tsx:160 9254 9278 msgid "Upload from Camera" 9255 9279 msgstr "" 9256 9280 9257 - #: src/view/com/util/UserAvatar.tsx:484 9281 + #: src/view/com/util/UserAvatar.tsx:486 9258 9282 #: src/view/com/util/UserBanner.tsx:174 9259 9283 msgid "Upload from Files" 9260 9284 msgstr "" 9261 9285 9262 - #: src/view/com/util/UserAvatar.tsx:478 9263 - #: src/view/com/util/UserAvatar.tsx:482 9286 + #: src/view/com/util/UserAvatar.tsx:480 9287 + #: src/view/com/util/UserAvatar.tsx:484 9264 9288 #: src/view/com/util/UserBanner.tsx:168 9265 9289 #: src/view/com/util/UserBanner.tsx:172 9266 9290 msgid "Upload from Library" ··· 9584 9608 msgid "View information about these labels" 9585 9609 msgstr "" 9586 9610 9587 - #: src/components/interstitials/TrendingVideos.tsx:191 9588 - #: src/components/interstitials/TrendingVideos.tsx:210 9611 + #: src/components/interstitials/TrendingVideos.tsx:189 9612 + #: src/components/interstitials/TrendingVideos.tsx:211 9589 9613 #: src/screens/Search/modules/ExploreTrendingVideos.tsx:194 9590 9614 #: src/screens/Search/modules/ExploreTrendingVideos.tsx:213 9591 9615 msgid "View more" ··· 9795 9819 msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." 9796 9820 msgstr "" 9797 9821 9798 - #: src/screens/Search/SearchResults.tsx:222 9822 + #: src/screens/Search/SearchResults.tsx:285 9799 9823 msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." 9800 9824 msgstr "" 9801 9825 ··· 9863 9887 msgstr "" 9864 9888 9865 9889 #: src/screens/Home/NoFeedsPinned.tsx:79 9866 - #: src/screens/Messages/ChatList.tsx:248 9890 + #: src/screens/Messages/ChatList.tsx:262 9867 9891 #: src/screens/Messages/Inbox.tsx:197 9868 9892 msgid "Whoops!" 9869 9893 msgstr "" 9870 9894 9871 - #: src/components/interstitials/TrendingVideos.tsx:127 9895 + #: src/components/interstitials/TrendingVideos.tsx:125 9872 9896 #: src/screens/Search/modules/ExploreTrendingVideos.tsx:108 9873 9897 msgid "Whoops! Trending videos failed to load." 9874 9898 msgstr "" ··· 10064 10088 msgstr "" 10065 10089 10066 10090 #: src/components/interstitials/Trending.tsx:130 10067 - #: src/components/interstitials/TrendingVideos.tsx:139 10091 + #: src/components/interstitials/TrendingVideos.tsx:137 10068 10092 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:109 10069 10093 msgid "You can update this later from your settings." 10070 10094 msgstr "" ··· 10135 10159 msgid "You have muted this user" 10136 10160 msgstr "" 10137 10161 10138 - #: src/screens/Messages/ChatList.tsx:291 10162 + #: src/screens/Messages/ChatList.tsx:305 10139 10163 msgid "You have no conversations yet. Start one!" 10140 10164 msgstr "" 10141 10165 ··· 10242 10266 msgid "You previously deactivated @{0}." 10243 10267 msgstr "" 10244 10268 10245 - #: src/screens/Settings/Settings.tsx:388 10269 + #: src/screens/Settings/Settings.tsx:395 10246 10270 msgid "You probably want to restart the app now." 10247 10271 msgstr "" 10248 10272 ··· 10254 10278 msgid "You reacted {0} to {1}" 10255 10279 msgstr "" 10256 10280 10257 - #: src/screens/Settings/Settings.tsx:280 10281 + #: src/screens/Settings/Settings.tsx:282 10258 10282 #: src/view/shell/desktop/LeftNav.tsx:209 10259 10283 msgid "You will be signed out of all your accounts." 10260 10284 msgstr ""
+1 -1
src/screens/Profile/Header/Handle.tsx
··· 27 27 const showProfileInHandle = useShowLinkInHandle() 28 28 return ( 29 29 <View 30 - style={[a.flex_row, a.gap_xs, a.align_center, {maxWidth: '100%'}]} 30 + style={[a.flex_row, a.gap_sm, a.align_center, {maxWidth: '100%'}]} 31 31 pointerEvents={disableTaps ? 'none' : isIOS ? 'auto' : 'box-none'}> 32 32 <NewskieDialog profile={profile} disabled={disableTaps} /> 33 33 {profile.viewer?.followedBy && !blockHide ? (
+1 -1
src/screens/Profile/Header/ProfileHeaderStandard.tsx
··· 251 251 <ProfileMenu profile={profile} /> 252 252 </View> 253 253 <View 254 - style={[a.flex_col, a.gap_2xs, a.pb_sm, live ? a.pt_sm : a.pt_2xs]}> 254 + style={[a.flex_col, a.gap_sm, a.pb_sm, live ? a.pt_sm : a.pt_2xs]}> 255 255 <View style={[a.flex_row, a.align_center, a.gap_xs, a.flex_1]}> 256 256 <Text 257 257 emoji
+4 -3
src/screens/Profile/Header/Shell.tsx
··· 229 229 size={live.isActive ? 88 : 90} 230 230 avatar={profile.avatar} 231 231 moderation={moderation.ui('avatar')} 232 + noBorder 232 233 /> 233 234 {live.isActive && <LiveIndicator size="large" />} 234 235 </Animated.View> ··· 260 261 const styles = StyleSheet.create({ 261 262 backBtnWrapper: { 262 263 position: 'absolute', 263 - left: 10, 264 + left: 12, 264 265 width: 30, 265 266 height: 30, 266 267 overflow: 'hidden', ··· 279 280 }, 280 281 aviPosition: { 281 282 position: 'absolute', 282 - top: 110, 283 - left: 10, 283 + top: 104, 284 + left: 16, 284 285 }, 285 286 avi: { 286 287 width: 94,
+64 -1
src/screens/Search/SearchResults.tsx
··· 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 + import {usePalette} from '#/lib/hooks/usePalette' 7 8 import {augmentSearchQuery} from '#/lib/strings/helpers' 8 9 import {useActorSearch} from '#/state/queries/actor-search' 9 10 import {usePopularFeedsSearch} from '#/state/queries/feed' 10 11 import {useSearchPostsQuery} from '#/state/queries/search-posts' 11 12 import {useSession} from '#/state/session' 13 + import {useLoggedOutViewControls} from '#/state/shell/logged-out' 14 + import {useCloseAllActiveElements} from '#/state/util' 12 15 import {Pager} from '#/view/com/pager/Pager' 13 16 import {TabBar} from '#/view/com/pager/TabBar' 14 17 import {Post} from '#/view/com/post/Post' ··· 17 20 import {atoms as a, useTheme, web} from '#/alf' 18 21 import * as FeedCard from '#/components/FeedCard' 19 22 import * as Layout from '#/components/Layout' 23 + import {InlineLinkText} from '#/components/Link' 24 + import {SearchError} from '#/components/SearchError' 20 25 import {Text} from '#/components/Typography' 21 26 22 27 let SearchResults = ({ ··· 104 109 ) 105 110 } 106 111 107 - function EmptyState({message, error}: {message: string; error?: string}) { 112 + function EmptyState({ 113 + message, 114 + error, 115 + children, 116 + }: { 117 + message: string 118 + error?: string 119 + children?: React.ReactNode 120 + }) { 108 121 const t = useTheme() 109 122 110 123 return ( ··· 132 145 </Text> 133 146 </> 134 147 )} 148 + 149 + {children} 135 150 </View> 136 151 </View> 137 152 </Layout.Content> ··· 161 176 const {_} = useLingui() 162 177 const {currentAccount} = useSession() 163 178 const [isPTR, setIsPTR] = useState(false) 179 + const isLoggedin = Boolean(currentAccount?.did) 164 180 165 181 const augmentedQuery = useMemo(() => { 166 182 return augmentSearchQuery(query || '', {did: currentAccount?.did}) ··· 177 193 hasNextPage, 178 194 } = useSearchPostsQuery({query: augmentedQuery, sort, enabled: active}) 179 195 196 + const pal = usePalette('default') 197 + const t = useTheme() 180 198 const onPullToRefresh = useCallback(async () => { 181 199 setIsPTR(true) 182 200 await refetch() ··· 215 233 216 234 return temp 217 235 }, [posts, isFetchingNextPage]) 236 + 237 + const closeAllActiveElements = useCloseAllActiveElements() 238 + const {requestSwitchToAccount} = useLoggedOutViewControls() 239 + 240 + const showSignIn = () => { 241 + closeAllActiveElements() 242 + requestSwitchToAccount({requestedAccount: 'none'}) 243 + } 244 + 245 + const showCreateAccount = () => { 246 + closeAllActiveElements() 247 + requestSwitchToAccount({requestedAccount: 'new'}) 248 + } 249 + 250 + if (!isLoggedin) { 251 + return ( 252 + <SearchError 253 + title={_(msg`Search is currently unavailable when logged out`)}> 254 + <Text style={[a.text_md, a.text_center, a.leading_snug]}> 255 + <Trans> 256 + <InlineLinkText 257 + style={[pal.link]} 258 + label={_(msg`sign in`)} 259 + to={'#'} 260 + onPress={showSignIn}> 261 + Sign in 262 + </InlineLinkText> 263 + <Text style={t.atoms.text_contrast_medium}> or </Text> 264 + <InlineLinkText 265 + style={[pal.link]} 266 + label={_(msg`create an account`)} 267 + to={'#'} 268 + onPress={showCreateAccount}> 269 + create an account 270 + </InlineLinkText> 271 + <Text> </Text> 272 + <Text style={t.atoms.text_contrast_medium}> 273 + to search for news, sports, politics, and everything else 274 + happening on Bluesky. 275 + </Text> 276 + </Trans> 277 + </Text> 278 + </SearchError> 279 + ) 280 + } 218 281 219 282 return error ? ( 220 283 <EmptyState
+51 -1
src/screens/Settings/Settings.tsx
··· 1 1 import {useState} from 'react' 2 - import {LayoutAnimation, Pressable, View} from 'react-native' 2 + import {Alert, LayoutAnimation, Pressable, View} from 'react-native' 3 3 import {Linking} from 'react-native' 4 4 import {useReducedMotion} from 'react-native-reanimated' 5 5 import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' ··· 12 12 import {IS_INTERNAL} from '#/lib/app-info' 13 13 import {HELP_DESK_URL} from '#/lib/constants' 14 14 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' 15 + import {useApplyPullRequestOTAUpdate} from '#/lib/hooks/useOTAUpdates' 15 16 import { 16 17 type CommonNavigatorParams, 17 18 type NavigationProp, 18 19 } from '#/lib/routes/types' 19 20 import {sanitizeDisplayName} from '#/lib/strings/display-names' 20 21 import {sanitizeHandle} from '#/lib/strings/handles' 22 + import {isIOS, isNative} from '#/platform/detection' 21 23 import {useProfileShadow} from '#/state/cache/profile-shadow' 22 24 import * as persisted from '#/state/persisted' 23 25 import {clearStorage} from '#/state/persisted' ··· 371 373 const onboardingDispatch = useOnboardingDispatch() 372 374 const navigation = useNavigation<NavigationProp>() 373 375 const {mutate: deleteChatDeclarationRecord} = useDeleteActorDeclaration() 376 + const { 377 + tryApplyUpdate, 378 + revertToEmbedded, 379 + isCurrentlyRunningPullRequestDeployment, 380 + currentChannel, 381 + } = useApplyPullRequestOTAUpdate() 374 382 const [actyNotifNudged, setActyNotifNudged] = useActivitySubscriptionsNudged() 375 383 376 384 const resetOnboarding = async () => { ··· 399 407 setActyNotifNudged(false) 400 408 } 401 409 410 + const onPressApplyOta = () => { 411 + Alert.prompt( 412 + 'Apply OTA', 413 + 'Enter the channel for the OTA you wish to apply.', 414 + [ 415 + { 416 + style: 'cancel', 417 + text: 'Cancel', 418 + }, 419 + { 420 + style: 'default', 421 + text: 'Apply', 422 + onPress: channel => { 423 + tryApplyUpdate(channel ?? '') 424 + }, 425 + }, 426 + ], 427 + 'plain-text', 428 + isCurrentlyRunningPullRequestDeployment 429 + ? currentChannel 430 + : 'pull-request-', 431 + ) 432 + } 433 + 402 434 return ( 403 435 <> 404 436 <SettingsList.PressableItem ··· 459 491 <Trans>Clear all storage data (restart after this)</Trans> 460 492 </SettingsList.ItemText> 461 493 </SettingsList.PressableItem> 494 + {isIOS ? ( 495 + <SettingsList.PressableItem 496 + onPress={onPressApplyOta} 497 + label={_(msg`Apply Pull Request`)}> 498 + <SettingsList.ItemText> 499 + <Trans>Apply Pull Request</Trans> 500 + </SettingsList.ItemText> 501 + </SettingsList.PressableItem> 502 + ) : null} 503 + {isNative && isCurrentlyRunningPullRequestDeployment ? ( 504 + <SettingsList.PressableItem 505 + onPress={revertToEmbedded} 506 + label={_(msg`Unapply Pull Request`)}> 507 + <SettingsList.ItemText> 508 + <Trans>Unapply Pull Request {currentChannel}</Trans> 509 + </SettingsList.ItemText> 510 + </SettingsList.PressableItem> 511 + ) : null} 462 512 </> 463 513 ) 464 514 }
+5 -5
src/state/ageAssurance/const.ts
··· 2 2 3 3 import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' 4 4 5 - export const AGE_RESTRICTED_MODERATION_PREFS: ModerationPrefs = { 5 + export const makeAgeRestrictedModerationPrefs = ( 6 + prefs: ModerationPrefs, 7 + ): ModerationPrefs => ({ 8 + ...prefs, 6 9 adultContentEnabled: false, 7 10 labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, 8 - labelers: [], 9 - mutedWords: [], 10 - hiddenPosts: [], 11 - } 11 + })
+4 -2
src/state/queries/preferences/index.ts
··· 11 11 import {getAge} from '#/lib/strings/time' 12 12 import {logger} from '#/logger' 13 13 import {useAgeAssuranceContext} from '#/state/ageAssurance' 14 - import {AGE_RESTRICTED_MODERATION_PREFS} from '#/state/ageAssurance/const' 14 + import {makeAgeRestrictedModerationPrefs} from '#/state/ageAssurance/const' 15 15 import {STALE} from '#/state/queries' 16 16 import { 17 17 DEFAULT_HOME_FEED_PREFS, ··· 77 77 (data: UsePreferencesQueryResponse) => { 78 78 const isUnderage = (data.userAge || 0) < 18 79 79 if (isUnderage || isAgeRestricted) { 80 - data.moderationPrefs = AGE_RESTRICTED_MODERATION_PREFS 80 + data.moderationPrefs = makeAgeRestrictedModerationPrefs( 81 + data.moderationPrefs, 82 + ) 81 83 } 82 84 return data 83 85 },
+5 -4
src/view/com/composer/text-input/TextInput.tsx
··· 96 96 newRt.detectFacetsWithoutResolution() 97 97 setRichText(newRt) 98 98 99 - const prefix = getMentionAt( 100 - newText, 101 - textInputSelection.current?.start || 0, 102 - ) 99 + // NOTE: BinaryFiddler 100 + // onChangeText happens before onSelectionChange, cursorPos is out of bound if the user deletes characters, 101 + const cursorPos = textInputSelection.current?.start ?? 0 102 + const prefix = getMentionAt(newText, Math.min(cursorPos, newText.length)) 103 + 103 104 if (prefix) { 104 105 setAutocompletePrefix(prefix.value) 105 106 } else if (autocompletePrefix) {
+4 -2
src/view/com/util/UserAvatar.tsx
··· 74 74 type: UserAvatarType 75 75 moderation?: ModerationUI 76 76 usePlainRNImage?: boolean 77 + noBorder?: boolean 77 78 onLoad?: () => void 78 79 style?: StyleProp<ViewStyle> 79 80 } ··· 223 224 style, 224 225 live, 225 226 hideLiveBadge, 227 + noBorder, 226 228 }: UserAvatarProps): React.ReactNode => { 227 229 const t = useTheme() 228 230 const finalShape = overrideShape ?? (type === 'user' ? 'circle' : 'square') ··· 320 322 onLoad={onLoad} 321 323 /> 322 324 )} 323 - <MediaInsetBorder style={borderStyle} /> 325 + {!noBorder && <MediaInsetBorder style={borderStyle} />} 324 326 {live && size > 16 && !hideLiveBadge && ( 325 327 <LiveIndicator size={size > 32 ? 'small' : 'tiny'} /> 326 328 )} ··· 329 331 ) : ( 330 332 <View style={containerStyle}> 331 333 <DefaultAvatar type={type} shape={finalShape} size={size} /> 332 - <MediaInsetBorder style={borderStyle} /> 334 + {!noBorder && <MediaInsetBorder style={borderStyle} />} 333 335 {live && size > 16 && !hideLiveBadge && ( 334 336 <LiveIndicator size={size > 32 ? 'small' : 'tiny'} /> 335 337 )}