Compare commits

..

1 Commits

Author SHA1 Message Date
Eric Bailey fde5fb01dc Fix error styles 2024-05-16 16:12:41 -05:00
451 changed files with 37466 additions and 46269 deletions
-4
View File
@@ -13,7 +13,6 @@ module.exports = {
'lingui',
'simple-import-sort',
'bsky-internal',
'eslint-plugin-react-compiler',
],
rules: {
// Temporary until https://github.com/facebook/react-native/pull/43756 gets into a release.
@@ -31,7 +30,6 @@ module.exports = {
},
},
],
'bsky-internal/use-exact-imports': 'error',
'bsky-internal/use-typed-gates': 'error',
'simple-import-sort/imports': [
'warn',
@@ -69,8 +67,6 @@ module.exports = {
},
],
'simple-import-sort/exports': 'warn',
// TODO: Reenable when we figure out why it gets stuck on CI.
// 'react-compiler/react-compiler': 'error',
},
ignorePatterns: [
'**/__mocks__/*.ts',
+1 -46
View File
@@ -34,9 +34,6 @@ jobs:
node-version-file: .nvmrc
cache: yarn
- name: 🪛 Setup jq
uses: dcarbone/install-jq-action@v2
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
with:
@@ -99,17 +96,13 @@ jobs:
name: build-${{ steps.timestamp.outputs.time }}.apk
path: build.apk
- name: 📚 Get version from package.json
id: get-build-info
run: bash scripts/setGitHubOutput.sh
- name: 🔔 Notify Slack of Production Build
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"text": "Android production build for Google Play Store submission is ready!\n```Artifact: ${{ steps.upload-artifact-production.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"
"text": "Android build is ready for submission. This is a production build! Download the artifact here: ${{ steps.upload-artifact-production.outputs.artifact-url }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
@@ -126,41 +119,3 @@ jobs:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: 🏗️ Build Production APK
if: ${{ inputs.profile == 'production' }}
run: yarn use-build-number-with-bump eas build -p android --profile production-apk --local --output build.apk --non-interactive
- name: 🚀 Upload Production APK Artifact
id: upload-artifact-production-apk
if: ${{ inputs.profile == 'production' }}
uses: actions/upload-artifact@v4
with:
retention-days: 30
compression-level: 6
name: build-${{ steps.timestamp.outputs.time }}.apk
path: build.apk
- name: 🔔 Notify Slack of Production APK Build
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"text": "Android production build for GitHub/Obtanium is ready!\n```Artifact: ${{ steps.upload-artifact-production-apk.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@v4
if: ${{ inputs.profile == 'testflight' }}
with:
path: most-recent-testflight-commit.txt
key: most-recent-testflight-commit
- name: ✏️ Write commit hash to cache
if: ${{ inputs.profile == 'testflight' }}
run: echo ${{ github.sha }} > most-recent-testflight-commit.txt
-35
View File
@@ -34,9 +34,6 @@ jobs:
node-version-file: .nvmrc
cache: yarn
- name: 🪛 Setup jq
uses: dcarbone/install-jq-action@v2
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
with:
@@ -50,10 +47,6 @@ jobs:
- name: ⚙️ Install dependencies
run: yarn install
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.3'
- name: ☕️ Setup Cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
@@ -83,31 +76,3 @@ jobs:
- name: 🚀 Deploy
run: eas submit -p ios --non-interactive --path build.ipa
- name: 📚 Get version from package.json
id: get-build-info
run: bash scripts/setGitHubOutput.sh
- name: 🔔 Notify Slack of Production Build
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}```"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@v4
if: ${{ inputs.profile == 'testflight' }}
with:
path: most-recent-testflight-commit.txt
key: most-recent-testflight-commit
- name: ✏️ Write commit hash to cache
if: ${{ inputs.profile == 'testflight' }}
run: echo ${{ github.sha }} > most-recent-testflight-commit.txt
+63 -53
View File
@@ -26,7 +26,7 @@ jobs:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-deploy
cancel-in-progress: true
outputs:
changes-detected: ${{ steps.fingerprint.outputs.includes-changes }}
fingerprint-is-different: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different }}
steps:
- name: Check for EXPO_TOKEN
@@ -49,22 +49,69 @@ jobs:
with:
fetch-depth: 0
- name: ⬇️ Get last successful deployment commit from the cache
id: get-base-commit
uses: actions/cache@v4
with:
path: last-successful-commit-hash.txt
key: last-successful-deployment-commit-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
last-successful-deployment-commit-${{ github.ref_name }}-
- name: Add the last successful deployment commit to the output
id: last-successful-commit
run: echo base-commit=$(cat last-successful-commit-hash.txt) >> "$GITHUB_OUTPUT"
- name: ⬇️ Fetch commits from base branch
if: ${{ github.ref != 'refs/heads/main' }}
run: git fetch origin main:main --depth 100
# This should get the current production release's commit's hash to see if the update is compatible
- name: 🕵️ Get the base commit
id: base-commit
run: |
if ${{ inputs.channel == 'production' }}; then
echo base-commit=$(git show-ref -s ${{ inputs.runtimeVersion }}) >> "$GITHUB_OUTPUT"
else
echo base-commit=${{ steps.last-successful-commit.base-commit }} >> "$GITHUB_OUTPUT"
fi
- name: ✓ Make sure we found a base commit
run: |
if [ -z "${{ steps.base-commit.outputs.base-commit }}" && ${{ inputs.channel == 'production' }} ]; then
echo "Could not find a base commit for this release. Exiting."
exit 1
fi
- name: 🔧 Setup Node
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: yarn
- name: 📷 Check fingerprint and install dependencies
- name: ⚙️ Install Dependencies
run: yarn install
# Run the fingerprint
- name: 📷 Check fingerprint
id: fingerprint
uses: bluesky-social/github-actions/fingerprint-native@main
uses: expo/expo-github-action/fingerprint@main
with:
profile: ${{ inputs.channel || 'testflight' }}
previous-commit-tag: ${{ inputs.runtimeVersion }}
previous-git-commit: ${{ steps.base-commit.outputs.base-commit }}
- name: 👀 Debug fingerprint
id: fingerprint-debug
run: |
echo "previousGitCommit=${{ steps.fingerprint.outputs.previous-git-commit }} currentGitCommit=${{ steps.fingerprint.outputs.current-git-commit }}"
echo "isPreviousFingerprintEmpty=${{ steps.fingerprint.outputs.previous-fingerprint == '' }}"
fingerprintDiff='$(echo "${{ steps.fingerprint.outputs.fingerprint-diff }}")'
if [[ $fingerprintDiff =~ "bareRncliAutolinking" || $fingerprintDiff =~ "expoAutolinkingAndroid" || $fingerprintDiff =~ "expoAutolinkingIos" ]]; then
echo fingerprint-is-different="true" >> "$GITHUB_OUTPUT"
else
echo fingerprint-is-different="false" >> "$GITHUB_OUTPUT"
fi
- name: Lint check
run: yarn lint
@@ -80,22 +127,22 @@ jobs:
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
if: ${{ !steps.fingerprint.outputs.includes-changes }}
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
with:
expo-version: latest
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- name: ⛏️ Setup Expo
if: ${{ !steps.fingerprint.outputs.includes-changes }}
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
run: yarn global add eas-cli-local-build-plugin
- name: 🪛 Setup jq
if: ${{ !steps.fingerprint.outputs.includes-changes }}
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
uses: dcarbone/install-jq-action@v2
- name: ✏️ Write environment variables
if: ${{ !steps.fingerprint.outputs.includes-changes }}
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
run: |
export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}'
echo "${{ secrets.ENV_TOKEN }}" > .env
@@ -104,28 +151,19 @@ jobs:
echo "$json" > google-services.json
- name: 🏗️ Create Bundle
if: ${{ !steps.fingerprint.outputs.includes-changes }}
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
run: EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export
- name: 📦 Package Bundle and 🚀 Deploy
if: ${{ !steps.fingerprint.outputs.includes-changes }}
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
run: yarn use-build-number bash scripts/bundleUpdate.sh
env:
DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }}
RUNTIME_VERSION: ${{ inputs.runtimeVersion }}
CHANNEL_NAME: ${{ inputs.channel || 'testflight' }}
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@v4
if: ${{ !steps.fingerprint.outputs.includes-changes }}
with:
path: most-recent-testflight-commit.txt
key: most-recent-testflight-commit
- name: ✏️ Write commit hash to cache
if: ${{ !steps.fingerprint.outputs.includes-changes }}
run: echo ${{ github.sha }} > most-recent-testflight-commit.txt
- name: Save successful deployment commit hash
run: echo ${{ steps.fingerprint.outputs.current-git-commit }} > last-successful-commit-hash.txt
# GitHub actions are horrible so let's just copy paste this in
buildIfNecessaryIOS:
@@ -133,11 +171,11 @@ jobs:
runs-on: macos-14
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-build-ios
cancel-in-progress: true
cancel-in-progress: false
needs: [bundleDeploy]
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
# available here
if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.changes-detected }}
if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.fingerprint-is-different == 'true' }}
steps:
- name: Check for EXPO_TOKEN
run: >
@@ -170,10 +208,6 @@ jobs:
- name: ⚙️ Install dependencies
run: yarn install
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.3'
- name: ☕️ Setup Cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
@@ -204,18 +238,6 @@ jobs:
- name: 🚀 Deploy
run: eas submit -p ios --non-interactive --path build.ipa
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@v4
if: ${{ inputs.channel == 'testflight' }}
with:
path: most-recent-testflight-commit.txt
key: most-recent-testflight-commit
- name: ✏️ Write commit hash to cache
if: ${{ inputs.channel == 'testflight' }}
run: echo ${{ github.sha }} > most-recent-testflight-commit.txt
buildIfNecessaryAndroid:
name: Build and Submit Android
runs-on: ubuntu-latest
@@ -225,7 +247,7 @@ jobs:
needs: [ bundleDeploy ]
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
# available here
if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.changes-detected }}
if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.fingerprint-is-different == 'true' }}
steps:
- name: Check for EXPO_TOKEN
@@ -303,15 +325,3 @@ jobs:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@v4
if: ${{ inputs.channel != 'testflight' && inputs.channel != 'production' }}
with:
path: most-recent-testflight-commit.txt
key: most-recent-testflight-commit
- name: ✏️ Write commit hash to cache
if: ${{ inputs.channel != 'testflight' && inputs.channel != 'production' }}
run: echo ${{ github.sha }} > most-recent-testflight-commit.txt
+24 -11
View File
@@ -1,7 +1,7 @@
# Credit for fingerprint action https://github.com/expo/expo
# https://github.com/expo/expo/blob/main/.github/workflows/pr-labeler.yml
---
name: PR Tests
name: PR labeler
on:
push:
@@ -21,7 +21,7 @@ permissions:
jobs:
webpack-analyzer:
runs-on: ubuntu-22.04
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request'}}
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@v4
@@ -94,9 +94,10 @@ jobs:
| ${{ steps.get-diff.outputs.base_file_string }} | ${{ steps.get-diff.outputs.pr_file_string }} | ${{ steps.get-diff.outputs.diff_file_string }} (${{ steps.get-diff.outputs.percent }}%) |
---
fingerprint-native:
test-suite-fingerprint:
runs-on: ubuntu-22.04
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request'}}
if: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' }}
concurrency: fingerprint-${{ github.event_name != 'pull_request' && 'main' || github.run_id }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@v4
@@ -113,23 +114,35 @@ jobs:
node-version-file: .nvmrc
cache: yarn
- name: 📷 Check fingerprint and install dependencies
- name: ⚙️ Install Dependencies
run: yarn install
- name: Get the base commit
id: base-commit
run: echo base-commit=$(git log -n 1 main --pretty=format:'%H') >> "$GITHUB_OUTPUT"
- name: 📷 Check fingerprint
id: fingerprint
uses: bluesky-social/github-actions/fingerprint-native@main
uses: expo/expo-github-action/fingerprint@main
with:
profile: pull-request
previous-git-commit: ${{ steps.base-commit.outputs.base-commit }}
- name: 👀 Debug fingerprint
run: |
echo "previousGitCommit=${{ steps.fingerprint.outputs.previous-git-commit }} currentGitCommit=${{ steps.fingerprint.outputs.current-git-commit }}"
echo "isPreviousFingerprintEmpty=${{ steps.fingerprint.outputs.previous-fingerprint == '' }}"
- name: 💬 Drop a comment
uses: marocchino/sticky-pull-request-comment@v2
if: ${{ steps.fingerprint.outputs.includes-changes }}
if: ${{ github.event_name == 'pull_request' && steps.fingerprint.outputs.fingerprint-diff != '[]' }}
with:
header: fingerprint-diff
message: |
The Pull Request introduced fingerprint changes against the base commit:
The Pull Request introduced fingerprint changes against the base commit: ${{ steps.fingerprint.outputs.previous-git-commit }}
<details><summary>Fingerprint diff</summary>
```json
${{ steps.fingerprint.outputs.diff }}
${{ steps.fingerprint.outputs.fingerprint-diff }}
```
</details>
@@ -139,7 +152,7 @@ jobs:
- name: 💬 Delete comment
uses: marocchino/sticky-pull-request-comment@v2
if: ${{ !steps.fingerprint.outputs.includes-changes }}
if: ${{ github.event_name == 'pull_request' && steps.fingerprint.outputs.fingerprint-diff == '[]' }}
with:
header: fingerprint-diff
delete: true
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
yarn lint-staged
-1
View File
@@ -12,4 +12,3 @@
android
ios
src/locale/locales
lib/react-compiler-runtime
+1 -1
View File
@@ -131,7 +131,7 @@ appId: xyz.blueskyweb.app
- assertVisible:
id: "feedItem-by-bob.test"
- tapOn:
id: "e2eGotoFeeds"
id: "bottomBarFeedsBtn"
- tapOn:
id: "saved-feed-Good Ppl"
- assertVisible:
+4 -15
View File
@@ -36,15 +36,12 @@ appId: xyz.blueskyweb.app
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
- tapOn:
id: "editFeedsBtn"
- tapOn: "Edit Saved Feeds"
- tapOn:
label: "Tap on down arrow"
point: "79%,23%"
- tapOn:
id: "viewHeaderDrawerBtn"
- tapOn:
id: "viewHeaderDrawerBtn"
id: "bottomBarHomeBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "alice-favs"
@@ -57,15 +54,11 @@ appId: xyz.blueskyweb.app
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
- tapOn:
id: "editFeedsBtn"
- tapOn:
label: "Tap on down arrow"
point: "79%,23%"
- tapOn:
id: "viewHeaderDrawerBtn"
- tapOn:
id: "viewHeaderDrawerBtn"
id: "bottomBarHomeBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "Following"
@@ -78,15 +71,11 @@ appId: xyz.blueskyweb.app
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
- tapOn:
id: "editFeedsBtn"
- tapOn:
label: "Tap on unpin"
point: "91%,23%"
- tapOn:
id: "viewHeaderDrawerBtn"
- tapOn:
id: "viewHeaderDrawerBtn"
id: "bottomBarHomeBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "alice-favs"
+1 -3
View File
@@ -28,8 +28,6 @@ appId: xyz.blueskyweb.app
- tapOn: "Pin to Home"
- tapOn:
id: "bottomBarHomeBtn"
- tapOn:
id: "viewHeaderDrawerBtn"
- assertNotVisible: "Feeds ✨"
- tapOn:
@@ -52,7 +50,7 @@ appId: xyz.blueskyweb.app
text: "1"
- tapOn:
id: "repostBtn"
- tapOn: "Remove repost"
- tapOn: "Undo repost"
- assertNotVisible:
id: "repostCount"
+31
View File
@@ -0,0 +1,31 @@
# Remove this test when the old onboarding is deprecated
appId: xyz.blueskyweb.app
---
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users"
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- tapOn:
id: "e2eStartLongboarding"
- tapOn: "Continue to next step"
- tapOn: "Continue to the next step without following any accounts"
- tapOn: Show replies in Following feed
- tapOn: Show quote-posts in Following feed
- tapOn: Show re-posts in Following feed
- tapOn: Show replies in Following feed
- waitForAnimationToEnd
- tapOn: Continue to next step
- waitForAnimationToEnd
- tapOn: "Continue to the next step"
- waitForAnimationToEnd
- tapOn: Continue to next step
- waitForAnimationToEnd
- tapOn: Continue to next step
- waitForAnimationToEnd
- tapOn: "Complete onboarding and start using your account"
- waitForAnimationToEnd
- assertVisible: Following
+2 -2
View File
@@ -58,7 +58,7 @@ appId: xyz.blueskyweb.app
id: "repostBtn"
childOf:
id: "postThreadItem-by-bob.test"
- tapOn: "Remove repost"
- tapOn: "Undo repost"
- assertNotVisible:
id: "repostCount-expanded"
@@ -77,7 +77,7 @@ appId: xyz.blueskyweb.app
id: "repostBtn"
childOf:
id: "postThreadItem-by-carla.test"
- tapOn: "Remove repost"
- tapOn: "Undo repost"
- assertNotVisible:
id: "repostCount"
childOf:
+73 -80
View File
@@ -6,6 +6,7 @@ import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles'
import {enforceLen} from '../../src/lib/strings/helpers'
import {detectLinkables} from '../../src/lib/strings/rich-text-detection'
import {shortenLinks} from '../../src/lib/strings/rich-text-manip'
import {ago} from '../../src/lib/strings/time'
import {
makeRecordUri,
toNiceDomain,
@@ -141,6 +142,78 @@ describe('makeRecordUri', () => {
})
})
describe('ago', () => {
const oneYearDate = new Date(
new Date().setMonth(new Date().getMonth() - 11),
).setDate(new Date().getDate() - 28)
const inputs = [
1671461038,
'04 Dec 1995 00:12:00 GMT',
new Date(),
new Date().setSeconds(new Date().getSeconds() - 10),
new Date().setMinutes(new Date().getMinutes() - 10),
new Date().setHours(new Date().getHours() - 1),
new Date().setDate(new Date().getDate() - 1),
new Date().setDate(new Date().getDate() - 20),
new Date().setDate(new Date().getDate() - 25),
new Date().setDate(new Date().getDate() - 28),
new Date().setDate(new Date().getDate() - 29),
new Date().setDate(new Date().getDate() - 30),
new Date().setMonth(new Date().getMonth() - 1),
new Date(new Date().setMonth(new Date().getMonth() - 1)).setDate(
new Date().getDate() - 20,
),
new Date(new Date().setMonth(new Date().getMonth() - 1)).setDate(
new Date().getDate() - 25,
),
new Date(new Date().setMonth(new Date().getMonth() - 1)).setDate(
new Date().getDate() - 28,
),
new Date(new Date().setMonth(new Date().getMonth() - 1)).setDate(
new Date().getDate() - 29,
),
new Date().setMonth(new Date().getMonth() - 11),
new Date(new Date().setMonth(new Date().getMonth() - 11)).setDate(
new Date().getDate() - 20,
),
new Date(new Date().setMonth(new Date().getMonth() - 11)).setDate(
new Date().getDate() - 25,
),
oneYearDate,
]
const outputs = [
new Date(1671461038).toLocaleDateString(),
new Date('04 Dec 1995 00:12:00 GMT').toLocaleDateString(),
'now',
'10s',
'10m',
'1h',
'1d',
'20d',
'25d',
'28d',
'29d',
'1mo',
'1mo',
'1mo',
'1mo',
'2mo',
'2mo',
'11mo',
'11mo',
'11mo',
new Date(oneYearDate).toLocaleDateString(),
]
it('correctly calculates how much time passed, in a string', () => {
for (let i = 0; i < inputs.length; i++) {
const result = ago(inputs[i])
expect(result).toEqual(outputs[i])
}
})
})
describe('makeValidHandle', () => {
const inputs = [
'test-handle-123',
@@ -406,26 +479,6 @@ describe('parseEmbedPlayerFromUrl', () => {
'https://media.tenor.com/someID/someName.gif',
'https://media.tenor.com/someID',
'https://media.tenor.com',
'https://www.flickr.com/photos/username/albums/72177720308493661',
'https://flickr.com/photos/username/albums/72177720308493661',
'https://flickr.com/photos/username/albums/72177720308493661/',
'https://flickr.com/photos/username/albums/72177720308493661//',
'https://flic.kr/s/aHBqjAES3i',
'https://flickr.com/foetoes/username/albums/3903',
'https://flickr.com/albums/3903',
'https://flic.kr/s/OolI',
'https://flic.kr/t/aHBqjAES3i',
'https://www.flickr.com/groups/898944@N23/pool',
'https://flickr.com/groups/898944@N23/pool',
'https://flickr.com/groups/898944@N23/pool/',
'https://flickr.com/groups/898944@N23/pool//',
'https://flic.kr/go/8WJtR',
'https://www.flickr.com/groups/898944@N23/',
'https://www.flickr.com/groups',
]
const outputs = [
@@ -723,66 +776,6 @@ describe('parseEmbedPlayerFromUrl', () => {
undefined,
undefined,
undefined,
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
undefined,
undefined,
undefined,
undefined,
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
undefined,
undefined,
]
it('correctly grabs the correct id from uri', () => {
+2 -1
View File
@@ -175,6 +175,7 @@ module.exports = function (config) {
checkAutomatically: 'NEVER',
channel: UPDATES_CHANNEL,
},
assetBundlePatterns: ['**/*'],
plugins: [
'expo-localization',
Boolean(process.env.SENTRY_AUTH_TOKEN) && 'sentry-expo',
@@ -182,7 +183,7 @@ module.exports = function (config) {
'expo-build-properties',
{
ios: {
deploymentTarget: '14.0',
deploymentTarget: '13.4',
newArchEnabled: false,
},
android: {
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 21a1 1 0 0 1-.707-.293l-6-6a1 1 0 1 1 1.414-1.414L11 17.586V4a1 1 0 1 1 2 0v13.586l4.293-4.293a1 1 0 0 1 1.414 1.414l-6 6A1 1 0 0 1 12 21Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 285 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M3.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7ZM14.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7Z"/></svg>

Before

Width:  |  Height:  |  Size: 450 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2.004 5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936A1 1 0 0 1 5.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234c.104-.085.21-.177.316-.276.846-.793 1.684-2.043 1.684-3.958V6h-5v6h2a1 1 0 0 1 1 1v4.234Zm6-12.234a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234V13a1 1 0 0 0-1-1h-2V6h5v7c0 1.915-.838 3.165-1.684 3.958-.106.1-.212.191-.316.276Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 775 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2.003 5.999a2 2 0 0 1 2-1.999h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936a1 1 0 0 1-1.351-.935L5 14H4a2 2 0 0 1-2-2.001l.002-6Zm5 11.236L7 12.999A1 1 0 0 0 6 12H4l.003-6h5v7c0 1.915-.837 3.165-1.683 3.958-.106.1-.213.192-.317.277Zm6-11.235a2 2 0 0 1 2-2h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-1a2 2 0 0 1-2-2V6Zm7 0h-5v6h2a1 1 0 0 1 1 1v4.234c.105-.085.211-.177.317-.276.846-.793 1.684-2.043 1.684-3.958V6Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 821 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4.4 9.493C4.14 10.28 4 11.124 4 12a8 8 0 1 0 10.899-7.459l-.953 3.81a1 1 0 0 1-.726.727l-3.444.866-.772 1.533a1 1 0 0 1-1.493.35L4.4 9.493Zm.883-1.84L7.756 9.51l.44-.874a1 1 0 0 1 .649-.52l3.306-.832.807-3.227a7.993 7.993 0 0 0-7.676 3.597ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.43.162a1 1 0 0 1 .77-.29l1.89.121a1 1 0 0 1 .494.168l2.869 1.928a1 1 0 0 1 .336 1.277l-.973 1.946a1 1 0 0 1-.894.553h-2.92a1 1 0 0 1-.831-.445L9.225 14.5a1 1 0 0 1 .126-1.262l1.08-1.076Zm.915 1.913.177-.177 1.171.074 1.914 1.286-.303.607h-1.766l-1.194-1.79Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 714 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5H5Zm14 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z" clip-rule="evenodd"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm16 0H5v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5Zm0 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 513 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4 5a1 1 0 0 0 0 2h16a1 1 0 1 0 0-2H4Zm0 12a1 1 0 1 0 0 2h3a1 1 0 1 0 0-2H4Zm-1-5a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1Zm14-3c.552 0 1 .41 1 .917V13.5h3.583c.507 0 .917.448.917 1s-.41 1-.917 1H18v3.583c0 .507-.448.917-1 .917s-1-.41-1-.917V15.5h-3.583c-.507 0-.917-.448-.917-1s.41-1 .917-1H16V9.917C16 9.41 16.448 9 17 9Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 471 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 299 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#FFC404" fill-rule="evenodd" d="M11.183 8.561c0 .544.348.984.892.984.545 0 .893-.44.893-.985V6.985c0-.544-.348-.985-.893-.985-.543 0-.892.44-.892.985v1.576Zm5.94 7.481c0 .539-.438.942-.976.942H8.004c-.538 0-.975-.411-.975-.95 0-2.782 2.264-5.021 5.046-5.021 2.783 0 5.047 2.247 5.047 5.03Zm-.43-4.584a.983.983 0 0 1 0-1.393l1.114-1.114a.985.985 0 0 1 1.393 1.393l-1.114 1.114a.985.985 0 0 1-1.393 0Zm2.897 3.741h1.575c.544 0 .985.349.985.892 0 .544-.44.892-.985.892h-1.67a.872.872 0 0 1-.89-.887c0-.543.44-.897.985-.897Zm-14.045.893c0-.544-.44-.892-.985-.892H2.985c-.544 0-.985.349-.985.892 0 .544.44.892.985.892H4.56c.545 0 .985-.349.985-.892Zm1.913-6.027a.985.985 0 0 1-1.393 1.393L4.95 10.344A.985.985 0 0 1 6.344 8.95l1.114 1.114Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 848 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M8.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15C3.166 6.665 2.004 8.415 2.004 11v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5ZM19.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15-1.154 1.082-2.316 2.832-2.316 5.417v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5Z"/></svg>

Before

Width:  |  Height:  |  Size: 455 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7.574 4.178a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.163 8.163 0 0 1 1.569-1.15 7.029 7.029 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588a5.99 5.99 0 0 0-.316.276C4.842 7.835 4.004 9.085 4.004 11v7h5v-6h-2a1 1 0 0 1-1-1V6.766Zm12.57-2.588a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.166 8.166 0 0 1 1.569-1.15 7.038 7.038 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588c-.105.085-.21.177-.316.276-.846.793-1.684 2.043-1.684 3.958v7h5v-6h-2a1 1 0 0 1-1-1V6.766Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 857 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H15a3 3 0 1 1 0-6c0-.824.332-1.571.87-2.113C14.739 12.32 13.435 12 12 12Zm6 2a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 412 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.678 19c.71-2.909 3.092-5 6.322-5 .621 0 1.206.077 1.748.218a1 1 0 1 0 .504-1.936A8.931 8.931 0 0 0 12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H11a1 1 0 1 0 0-2H5.678ZM18 14a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 508 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M6.5 3a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v3.997a6.25 6.25 0 0 0 1.83 4.42l.377.376A1 1 0 0 1 20 12.5V15a1 1 0 0 1-1 1h-6v5a1 1 0 1 1-2 0v-5H5a1 1 0 0 1-1-1v-2.5a1 1 0 0 1 .293-.707l.376-.377A6.25 6.25 0 0 0 6.5 6.996V3.001Zm2 1v2.997a8.25 8.25 0 0 1-2.416 5.834L6 12.914V14h12v-1.086l-.084-.083A8.25 8.25 0 0 1 15.5 6.997V4h-7Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 465 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M16.293 2.293a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 0 1-1.414-1.414L17.586 7H5v4a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1h13.586l-1.293-1.293a1 1 0 0 1 0-1.414ZM21 13v5a1 1 0 0 1-1 1H6.414l1.293 1.293a1 1 0 1 1-1.414 1.414l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 0 1 1.414 1.414L6.414 17H19v-4a1 1 0 1 1 2 0Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 446 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M16.793 2.293a1 1 0 0 1 1.414 0L20.5 4.586a2 2 0 0 1 0 2.828l-2.293 2.293a1 1 0 0 1-1.414-1.414L18.086 7H7a2 2 0 0 0-2 2v2a1 1 0 1 1-2 0V9a4 4 0 0 1 4-4h11.086l-1.293-1.293a1 1 0 0 1 0-1.414ZM20 12a1 1 0 0 1 1 1v2a4 4 0 0 1-4 4H5.914l1.293 1.293a1 1 0 1 1-1.414 1.414L3.5 19.414a2 2 0 0 1 0-2.828l2.293-2.293a1 1 0 0 1 1.414 1.414L5.914 17H17a2 2 0 0 0 2-2v-2a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 517 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7 3a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2H8v2a1 1 0 1 1-2 0V8H4a1 1 0 0 1 0-2h2V4a1 1 0 0 1 1-1Zm6 4a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm4-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM3 14a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 1v4h4v-4H5Zm9.171-.829a1 1 0 0 1 1.415 0L17 15.585l1.414-1.414a1 1 0 1 1 1.414 1.414L18.414 17l1.414 1.414a1 1 0 0 1-1.414 1.414L17 18.414l-1.415 1.414a1 1 0 0 1-1.414-1.414l1.415-1.415-1.415-1.414a1 1 0 0 1 0-1.414Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 590 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M12.902 1.568a1 1 0 0 0-1.804 0L8.455 7.085l-6.085.799a1 1 0 0 0-.557 1.718l4.45 4.207-1.117 6.008a1 1 0 0 0 1.458 1.063L12 17.962l5.396 2.918a1 1 0 0 0 1.459-1.063l-1.117-6.008 4.45-4.207a1 1 0 0 0-.558-1.718l-6.085-.8-2.643-5.516Z"/></svg>

Before

Width:  |  Height:  |  Size: 335 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 1a1 1 0 0 1 .902.568l2.643 5.517 6.085.799a1 1 0 0 1 .557 1.718l-4.45 4.207 1.118 6.008a1 1 0 0 1-1.46 1.063L12 17.962 6.604 20.88a1 1 0 0 1-1.459-1.063l1.117-6.008-4.45-4.207a1 1 0 0 1 .558-1.718l6.085-.8 2.643-5.516A1 1 0 0 1 12 1Zm0 3.315-1.975 4.123a1 1 0 0 1-.772.56l-4.538.595 3.317 3.137a1 1 0 0 1 .296.91l-.834 4.485 4.03-2.179a1 1 0 0 1 .952 0l4.03 2.179-.834-4.485a1 1 0 0 1 .296-.91l3.317-3.137-4.538-.596a1 1 0 0 1-.772-.56L12 4.316Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 592 B

+4 -7
View File
@@ -8,6 +8,9 @@ module.exports = function (api) {
{
lazyImports: true,
native: {
// We should be able to remove this after upgrading Expo
// to a version that includes https://github.com/expo/expo/pull/24672.
unstable_transformProfile: 'hermes-stable',
// Disable ESM -> CJS compilation because Metro takes care of it.
// However, we need it in Jest tests since those run without Metro.
disableImportExportTransform: !isTestEnv,
@@ -16,13 +19,6 @@ module.exports = function (api) {
],
],
plugins: [
'macros',
[
'babel-plugin-react-compiler',
{
runtimeModule: 'react-compiler-runtime',
},
],
[
'module:react-native-dotenv',
{
@@ -50,6 +46,7 @@ module.exports = function (api) {
},
},
],
'macros',
'react-native-reanimated/plugin', // NOTE: this plugin MUST be last
],
env: {
+1 -1
View File
@@ -15,6 +15,6 @@
"parserOptions": {
"sourceType": "module",
"ecmaVersion": "latest",
"project": "./bskyembed/tsconfig.json"
"project": "./tsconfig.json"
}
}
+2 -3
View File
@@ -193,7 +193,7 @@ export function Embed({
function Info({children}: {children: ComponentChildren}) {
return (
<div className="w-full rounded-lg border py-2 px-2.5 flex-row flex gap-2 bg-neutral-50">
<img src={infoIcon} className="w-4 h-4 shrink-0 mt-0.5" />
<img src={infoIcon as string} className="w-4 h-4 shrink-0 mt-0.5" />
<p className="text-sm text-textLight">{children}</p>
</div>
)
@@ -293,8 +293,7 @@ function ExternalEmbed({
return (
<Link
href={content.external.uri}
className="w-full rounded-lg overflow-hidden border flex flex-col items-stretch"
disableTracking>
className="w-full rounded-lg overflow-hidden border flex flex-col items-stretch">
{content.external.thumb && (
<img
src={content.external.thumb}
+3 -5
View File
@@ -3,12 +3,10 @@ import {h} from 'preact'
export function Link({
href,
className,
disableTracking,
...props
}: {
href: string
className?: string
disableTracking?: boolean
} & h.JSX.HTMLAttributes<HTMLAnchorElement>) {
const searchParam = new URLSearchParams(window.location.search)
const ref_url = searchParam.get('ref_url')
@@ -21,9 +19,9 @@ export function Link({
return (
<a
href={`${href.startsWith('http') ? href : `https://bsky.app${href}`}${
disableTracking ? '' : `?${newSearchParam.toString()}`
}`}
href={`${
href.startsWith('http') ? href : `https://bsky.app${href}`
}?${newSearchParam.toString()}`}
target="_blank"
rel="noopener noreferrer nofollow"
onClick={evt => evt.stopPropagation()}
+9 -24
View File
@@ -1,9 +1,4 @@
import {
AppBskyFeedDefs,
AppBskyFeedPost,
AppBskyRichtextFacet,
RichText,
} from '@atproto/api'
import {AppBskyFeedDefs, AppBskyFeedPost, RichText} from '@atproto/api'
import {h} from 'preact'
import replyIcon from '../../assets/bubble_filled_stroke2_corner2_rounded.svg'
@@ -61,7 +56,7 @@ export function Post({thread}: Props) {
<Link
href={href}
className="transition-transform hover:scale-110 shrink-0 self-start">
<img src={logo} className="h-8" />
<img src={logo as string} className="h-8" />
</Link>
</div>
<PostContent record={record} />
@@ -76,7 +71,7 @@ export function Post({thread}: Props) {
<div className="border-t w-full pt-2.5 flex items-center gap-5 text-sm cursor-pointer">
{!!post.likeCount && (
<div className="flex items-center gap-2 cursor-pointer">
<img src={likeIcon} className="w-5 h-5" />
<img src={likeIcon as string} className="w-5 h-5" />
<p className="font-bold text-neutral-500 mb-px">
{post.likeCount}
</p>
@@ -84,14 +79,14 @@ export function Post({thread}: Props) {
)}
{!!post.repostCount && (
<div className="flex items-center gap-2 cursor-pointer">
<img src={repostIcon} className="w-5 h-5" />
<img src={repostIcon as string} className="w-5 h-5" />
<p className="font-bold text-neutral-500 mb-px">
{post.repostCount}
</p>
</div>
)}
<div className="flex items-center gap-2 cursor-pointer">
<img src={replyIcon} className="w-5 h-5" />
<img src={replyIcon as string} className="w-5 h-5" />
<p className="font-bold text-neutral-500 mb-px">Reply</p>
</div>
<div className="flex-1" />
@@ -123,23 +118,16 @@ function PostContent({record}: {record: AppBskyFeedPost.Record | null}) {
let counter = 0
for (const segment of rt.segments()) {
if (
segment.link &&
AppBskyRichtextFacet.validateLink(segment.link).success
) {
if (segment.isLink() && segment.link) {
richText.push(
<Link
key={counter}
href={segment.link.uri}
className="text-blue-400 hover:underline"
disableTracking={!segment.link.uri.startsWith('https://bsky.app')}>
className="text-blue-400 hover:underline">
{segment.text}
</Link>,
)
} else if (
segment.mention &&
AppBskyRichtextFacet.validateMention(segment.mention).success
) {
} else if (segment.isMention() && segment.mention) {
richText.push(
<Link
key={counter}
@@ -148,10 +136,7 @@ function PostContent({record}: {record: AppBskyFeedPost.Record | null}) {
{segment.text}
</Link>,
)
} else if (
segment.tag &&
AppBskyRichtextFacet.validateTag(segment.tag).success
) {
} else if (segment.isTag() && segment.tag) {
richText.push(
<Link
key={counter}
+2 -2
View File
@@ -112,7 +112,7 @@ function LandingPage() {
<Link
href="https://bsky.social/about"
className="transition-transform hover:scale-110">
<img src={logo} className="h-10" />
<img src={logo as string} className="h-10" />
</Link>
<h1 className="text-4xl font-bold text-center">Embed a Bluesky Post</h1>
@@ -125,7 +125,7 @@ function LandingPage() {
placeholder={DEFAULT_POST}
/>
<img src={arrowBottom} className="w-6" />
<img src={arrowBottom as string} className="w-6" />
{loading ? (
<Skeleton />
+2 -2
View File
@@ -52,7 +52,7 @@ function PwiOptOut({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) {
<Link
href={href}
className="transition-transform hover:scale-110 absolute top-4 right-4">
<img src={logo} className="h-6" />
<img src={logo as string} className="h-6" />
</Link>
<div className="w-full py-12 gap-4 flex flex-col items-center">
<p className="max-w-80 text-center w-full text-textLight">
@@ -75,7 +75,7 @@ function ErrorMessage() {
<Link
href="https://bsky.app/"
className="transition-transform hover:scale-110 absolute top-4 right-4">
<img src={logo} className="h-6" />
<img src={logo as string} className="h-6" />
</Link>
<p className="my-16 text-center w-full text-textLight">
Post not found, it may have been deleted.
-1
View File
@@ -207,7 +207,6 @@ func serve(cctx *cli.Context) error {
e.GET("/profile/:handleOrDID", server.WebProfile)
e.GET("/profile/:handleOrDID/follows", server.WebGeneric)
e.GET("/profile/:handleOrDID/followers", server.WebGeneric)
e.GET("/profile/:handleOrDID/known-followers", server.WebGeneric)
e.GET("/profile/:handleOrDID/lists/:rkey", server.WebGeneric)
e.GET("/profile/:handleOrDID/feed/:rkey", server.WebGeneric)
e.GET("/profile/:handleOrDID/feed/:rkey/liked-by", server.WebGeneric)
+1 -4
View File
@@ -4,10 +4,7 @@
- Set up your environment [using the expo instructions](https://docs.expo.dev/guides/local-app-development/).
- make sure that the JAVA_HOME points to the zulu-17 directory in your `.zshrc` or `.bashrc` file: `export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home`. DO NOT use another JDK or you will encounter build errors.
- If you're running macOS, make sure you are running the correct versions of Ruby and Cocoapods:-
- If you are using Apple Silicon and this is the first time you are building for RN 0.74+, you may need to run:
- `arch -arm64 brew install llvm`
- `sudo gem install ffi`
- If you're running macOS, make sure you are running the correct versions of Ruby and Cocoapods:
- Check if you've installed Cocoapods through `homebrew`. If you have, remove it:
- `brew info cocoapods`
- If output says `Installed`:
-95
View File
@@ -1,95 +0,0 @@
# OTA Deployments
## Overview
![OTA Deployment](./img/ota-flow.png)
## Internal Deployments
Internal OTA deployments should be performed automatically upon all merges into main. In cases where the fingerprint
diff results in incompatible native changes, a new client build will automatically be ran and deployed to TestFlight
(iOS) or delivered in Slack (Android).
## Production Deployments
### Prerequisites
- Find the latest production build number for both iOS and Android in Slack. These are listed in #client-builds
- Production builds always send the Version Number and Build Number in the Slack message. Search for the latest
production version number, and you should find the correct information.
![slack-build-info](./img/slack-build-info.png)
- It may also be useful to check the current production clients for these values. This will also help for testing. Note
that you will need to _fully_ remove the existing internal client build from your device, otherwise the given values in
the app may differ from the actual production values.
![app-build-number](./img/app-build-number.png)
- You should have signed in to EAS locally through npx eas login. You will need to modify the build number in a
subsequent step.
- Ensure that the commit the initial client was cut from is properly tagged in git. The tag should be in the format of 1.X.0
- Note: If the commit is not properly tagged, then the OTA deployment will simply fail since the GitHub Action will
not be able to find a commit to fingerprint and diff against.
### Preparation
- Create a new branch from the git tag that the initial release was cut from if no OTA deployment has been made yet for this
client. Name this branch `1.X.0-ota-1`
- If a deployment has been made previously for this release, increment the branch name, i.e. `1.x.0-ota-2`
- If necessary, cherry-pick the commit(s) that you wish to deploy
- Ensure that the package.jsons version field is set to the appropriate value. As long as used the correct git tag
to create your branch from, this should be properly set.
### Deployment
- Update the build number through EAS to match the build numbers of the
production iOS/Android apps
- Note: This isnt strictly necessary, but having a step that takes you off of GitHub and into the terminal provides
a little “friction” to avoid fat fingering a release. Since there are legitimate reasons to just “click and deploy”
for internal builds, I felt it useful to make sure it doesnt accidentally become a prod deployment.
- Set the build numbers to the values found in the prerequisite steps. Again, this should be the
build number for the current production release you want to deploy for.
- `npx eas build:version:set -p ios`
- `npx eas build:version:set -p android`
- These steps should spit out what the current build number is, save those values
for later too
- Run the deployment
- Navigate to https://github.com/bluesky-social/social-app/actions/workflows/bundle-deploy-eas-update.yml
- Select the “Run Workflow” dropdown
![run-workflow](./img/run-workflow.png)
- Select the branch for the deployment you are releasing.
![branch-selection](./img/branch-selection.png)
- Double check the branch selection.
- Select the production channel
- Enter the version for the client you are releasing to, i.e. 1.80.0
- Note: If you do enter an incorrect version here, the deployment will either:
- Fail because the action cannot find a commit with your misentered version
- Succeed - but with no users receiving the update. This is because the version you entered will not properly
correlate to a _build number_ as well, so no clients in the wild will be able to receive the update.
![other-ota-options](./img/other-ota-options.png)
- Triple check the branch selection.
- You selected the correct branch
- You selected the "Production" channel
- You entered the correct version in the format of `1.X.0`.
- Press “Run Workflow”
In about five minutes, the new deployment should be available for download. To test:
- Remove the internal build of the app from your device
- Download the app from the App Store/Google Play
- Launch the app once and wait approximately 15 seconds
- Relaunch the app
- Check the Settings page and scroll to the bottom. The commit hash should now be the latest commit on your deployed branch.
### Post Deployment
- Reset both platforms build numbers to what they were before the OTA
deployment. These values should have been logged by the EAS CLI when you
reset them to the production values prior to OTA.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

-14
View File
@@ -46,20 +46,6 @@
"EXPO_PUBLIC_ENV": "production"
}
},
"production-apk": {
"extends": "base",
"distribution": "internal",
"ios": {
"autoIncrement": false
},
"android": {
"autoIncrement": false
},
"channel": "production",
"env": {
"EXPO_PUBLIC_ENV": "production"
}
},
"testflight": {
"extends": "base",
"ios": {
-1
View File
@@ -3,7 +3,6 @@
module.exports = {
rules: {
'avoid-unwrapped-text': require('./avoid-unwrapped-text'),
'use-exact-imports': require('./use-exact-imports'),
'use-typed-gates': require('./use-typed-gates'),
},
}
-22
View File
@@ -1,22 +0,0 @@
/* eslint-disable bsky-internal/use-exact-imports */
const BANNED_IMPORTS = [
'@fortawesome/free-regular-svg-icons',
'@fortawesome/free-solid-svg-icons',
]
exports.create = function create(context) {
return {
Literal(node) {
if (typeof node.value !== 'string') {
return
}
if (BANNED_IMPORTS.includes(node.value)) {
context.report({
node,
message:
'Import the specific thing you want instead of the entire package',
})
}
},
}
}
+2 -7
View File
@@ -1,10 +1,10 @@
/* global jest */
import {configure} from '@testing-library/react-native'
import 'react-native-gesture-handler/jestSetup'
// IMPORTANT: this is what's used in the native runtime
import 'react-native-url-polyfill/auto'
import {configure} from '@testing-library/react-native'
configure({asyncUtilTimeout: 20000})
jest.mock('@react-native-async-storage/async-storage', () =>
@@ -90,8 +90,3 @@ jest.mock('sentry-expo', () => ({
}))
jest.mock('crypto', () => ({}))
jest.mock('expo-application', () => ({
nativeApplicationVersion: '1.0.0',
nativeBuildVersion: '1',
}))
-21
View File
@@ -1,21 +0,0 @@
const React = require('react')
const $empty = Symbol.for('react.memo_cache_sentinel')
/**
* DANGER: this hook is NEVER meant to be called directly!
*
* Note that this is a temporary userspace implementation of this function
* from React 19. It is not as efficient and may invalidate more frequently
* than the official API. Please upgrade to React 19 as soon as you can.
**/
export function c(size) {
// eslint-disable-next-line react-hooks/rules-of-hooks
return React.useState(() => {
const $ = new Array(size)
for (let ii = 0; ii < size; ii++) {
$[ii] = $empty
}
// @ts-ignore
$[$empty] = true
return $
})[0]
}
-9
View File
@@ -1,9 +0,0 @@
{
"name": "react-compiler-runtime",
"version": "0.0.1",
"license": "MIT",
"main": "index.js",
"peerDependencies": {
"react": "^18.2.0"
}
}
+5 -10
View File
@@ -1,5 +1,4 @@
import UserNotifications
import UIKit
let APP_GROUP = "group.app.bsky"
@@ -7,7 +6,7 @@ class NotificationService: UNNotificationServiceExtension {
var prefs = UserDefaults(suiteName: APP_GROUP)
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
guard let bestAttempt = createCopy(request.content),
guard var bestAttempt = createCopy(request.content),
let reason = request.content.userInfo["reason"] as? String
else {
contentHandler(request.content)
@@ -16,10 +15,11 @@ class NotificationService: UNNotificationServiceExtension {
if reason == "chat-message" {
mutateWithChatMessage(bestAttempt)
} else {
mutateWithBadge(bestAttempt)
}
// The badge should always be incremented when in the background
mutateWithBadge(bestAttempt)
contentHandler(bestAttempt)
}
@@ -32,12 +32,7 @@ class NotificationService: UNNotificationServiceExtension {
}
func mutateWithBadge(_ content: UNMutableNotificationContent) {
var count = prefs?.integer(forKey: "badgeCount") ?? 0
count += 1
// Set the new badge number for the notification, then store that value for using later
content.badge = NSNumber(value: count)
prefs?.setValue(count, forKey: "badgeCount")
content.badge = 1
}
func mutateWithChatMessage(_ content: UNMutableNotificationContent) {
@@ -35,8 +35,5 @@ class BackgroundNotificationHandler(
remoteMessage.data["sound"] = null
}
}
// TODO - Remove this once we have more backend capability
remoteMessage.data["badge"] = null
}
}
@@ -66,9 +66,5 @@ class ExpoBackgroundNotificationHandlerModule : Module() {
AsyncFunction("removeManyFromStringArrayAsync") { forKey: String, strings: Array<String> ->
NotificationPrefs(appContext.reactContext).removeManyFromStringArray(forKey, strings)
}
AsyncFunction("setBadgeCountAsync") { _: Int ->
// This does nothing on Android
}
}
}
@@ -10,8 +10,7 @@ let DEFAULTS: [String:Any] = [
"playSoundQuote": false,
"playSoundReply": false,
"playSoundRepost": false,
"mutedThreads": [:] as! [String:[String]],
"badgeCount": 0,
"mutedThreads": [:] as! [String:[String]]
]
/*
@@ -113,9 +112,5 @@ public class ExpoBackgroundNotificationHandlerModule: Module {
userDefaults?.setValue(curr, forKey: forKey)
}
}
AsyncFunction("setBadgeCountAsync") { (count: Int) in
userDefaults?.setValue(count, forKey: "badgeCount")
}
}
}
@@ -31,7 +31,6 @@ export type ExpoBackgroundNotificationHandlerModule = {
forKey: keyof BackgroundNotificationHandlerPreferences,
value: string[],
) => Promise<void>
setBadgeCountAsync: (count: number) => Promise<void>
}
// TODO there are more preferences in the native code, however they have not been added here yet.
@@ -24,5 +24,4 @@ export const BackgroundNotificationHandler = {
removeFromStringArrayAsync: async (_: string, __: string) => {},
addManyToStringArrayAsync: async (_: string, __: string[]) => {},
removeManyFromStringArrayAsync: async (_: string, __: string[]) => {},
setBadgeCountAsync: async (_: number) => {},
} as ExpoBackgroundNotificationHandlerModule
@@ -10,8 +10,8 @@ Pod::Spec.new do |s|
s.static_framework = true
s.dependency 'ExpoModulesCore'
s.dependency 'SDWebImage', '~> 5.19.1'
s.dependency 'SDWebImageWebPCoder', '~> 0.14.6'
s.dependency 'SDWebImage', '~> 5.17.0'
s.dependency 'SDWebImageWebPCoder', '~> 0.13.0'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
@@ -1,7 +1,5 @@
import React from 'react'
import {ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types'
export function ExpoScrollForwarderView({
children,
}: React.PropsWithChildren<ExpoScrollForwarderViewProps>) {
+53 -62
View File
@@ -1,6 +1,6 @@
{
"name": "bsky.app",
"version": "1.87.0",
"version": "1.83.0",
"private": true,
"engines": {
"node": ">=18"
@@ -49,7 +49,7 @@
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web"
},
"dependencies": {
"@atproto/api": "^0.12.20",
"@atproto/api": "^0.12.10",
"@bam.tech/react-native-image-resizer": "^3.0.4",
"@braintree/sanitize-url": "^6.0.2",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
@@ -58,25 +58,24 @@
"@expo/webpack-config": "^19.0.0",
"@floating-ui/dom": "^1.6.3",
"@floating-ui/react-dom": "^2.0.8",
"@formatjs/intl-locale": "^4.0.0",
"@formatjs/intl-numberformat": "^8.10.3",
"@formatjs/intl-pluralrules": "^5.2.14",
"@formatjs/intl-locale": "^3.4.3",
"@formatjs/intl-pluralrules": "^5.2.10",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@fortawesome/react-native-fontawesome": "^0.3.2",
"@fortawesome/react-native-fontawesome": "^0.3.0",
"@lingui/react": "^4.5.0",
"@mattermost/react-native-paste-input": "^0.7.1",
"@mattermost/react-native-paste-input": "^0.6.4",
"@miblanchard/react-native-slider": "^2.3.1",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-masked-view/masked-view": "0.3.0",
"@react-native-menu/menu": "^1.1.0",
"@react-native-menu/menu": "^0.8.0",
"@react-native-picker/picker": "2.6.1",
"@react-navigation/bottom-tabs": "^6.5.20",
"@react-navigation/drawer": "^6.6.15",
"@react-navigation/native": "^6.1.17",
"@react-navigation/native-stack": "^6.9.26",
"@react-navigation/bottom-tabs": "^6.5.7",
"@react-navigation/drawer": "^6.6.2",
"@react-navigation/native": "^6.1.6",
"@react-navigation/native-stack": "^6.9.12",
"@segment/analytics-next": "^1.51.3",
"@segment/analytics-react": "^1.0.0-rc1",
"@segment/analytics-react-native": "^2.10.1",
@@ -108,35 +107,35 @@
"base64-js": "^1.5.1",
"bcp-47-match": "^2.0.3",
"date-fns": "^2.30.0",
"deprecated-react-native-prop-types": "^5.0.0",
"email-validator": "^2.0.4",
"emoji-mart": "^5.5.2",
"eventemitter3": "^5.0.1",
"expo": "^51.0.8",
"expo-application": "^5.9.1",
"expo-build-properties": "^0.12.1",
"expo-camera": "~15.0.9",
"expo-clipboard": "^6.0.3",
"expo-dev-client": "^4.0.14",
"expo-device": "~6.0.2",
"expo-file-system": "^17.0.1",
"expo-haptics": "^13.0.1",
"expo-image": "~1.12.9",
"expo-image-manipulator": "^12.0.5",
"expo-image-picker": "~15.0.5",
"expo-linear-gradient": "^13.0.2",
"expo-linking": "^6.3.1",
"expo-localization": "~15.0.3",
"expo-media-library": "~16.0.3",
"expo-navigation-bar": "~3.0.4",
"expo-notifications": "~0.28.7",
"expo-sharing": "^12.0.1",
"expo-splash-screen": "~0.27.4",
"expo-status-bar": "~1.12.1",
"expo-system-ui": "~3.0.4",
"expo-task-manager": "~11.8.1",
"expo-updates": "~0.25.14",
"expo-web-browser": "~13.0.3",
"expo": "^50.0.17",
"expo-application": "^5.8.3",
"expo-build-properties": "^0.11.1",
"expo-camera": "~14.0.4",
"expo-clipboard": "^5.0.1",
"expo-constants": "~15.4.5",
"expo-dev-client": "~3.3.8",
"expo-device": "~5.9.3",
"expo-file-system": "^16.0.9",
"expo-haptics": "^12.8.1",
"expo-image": "~1.10.6",
"expo-image-manipulator": "^11.8.0",
"expo-image-picker": "~14.7.1",
"expo-linear-gradient": "^12.7.2",
"expo-linking": "^6.2.2",
"expo-localization": "~14.8.3",
"expo-media-library": "~15.9.1",
"expo-navigation-bar": "~2.8.1",
"expo-notifications": "~0.27.6",
"expo-sharing": "^11.10.0",
"expo-splash-screen": "~0.26.4",
"expo-status-bar": "~1.11.1",
"expo-system-ui": "~2.9.3",
"expo-task-manager": "~11.7.2",
"expo-updates": "~0.24.10",
"expo-web-browser": "~12.8.2",
"fast-text-encoding": "^1.0.6",
"history": "^5.3.0",
"js-sha256": "^0.9.0",
@@ -162,33 +161,31 @@
"psl": "^1.9.0",
"react": "18.2.0",
"react-avatar-editor": "^13.0.0",
"react-compiler-runtime": "file:./lib/react-compiler-runtime",
"react-dom": "^18.2.0",
"react-keyed-flatten-children": "^3.0.0",
"react-native": "0.74.1",
"react-native-date-picker": "^4.4.2",
"react-native": "0.73.2",
"react-native-date-picker": "^4.4.0",
"react-native-drawer-layout": "^4.0.0-alpha.3",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "~2.16.2",
"react-native-gesture-handler": "~2.14.0",
"react-native-get-random-values": "~1.11.0",
"react-native-image-crop-picker": "0.40.3",
"react-native-image-crop-picker": "^0.38.1",
"react-native-ios-context-menu": "^1.15.3",
"react-native-keyboard-controller": "^1.12.1",
"react-native-pager-view": "6.2.3",
"react-native-picker-select": "^9.1.3",
"react-native-picker-select": "^8.1.0",
"react-native-progress": "bluesky-social/react-native-progress",
"react-native-reanimated": "^3.11.0",
"react-native-reanimated": "^3.6.0",
"react-native-root-siblings": "^4.1.1",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "~3.31.1",
"react-native-svg": "^15.3.0",
"react-native-uitextview": "^1.1.7",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"react-native-svg": "14.1.0",
"react-native-uitextview": "^1.1.6",
"react-native-url-polyfill": "^1.3.0",
"react-native-uuid": "^2.0.2",
"react-native-uuid": "^2.0.1",
"react-native-view-shot": "^3.8.0",
"react-native-web": "~0.19.11",
"react-native-web": "~0.19.6",
"react-native-web-webview": "^1.0.2",
"react-native-webview": "13.10.2",
"react-native-webview": "13.6.4",
"react-responsive": "^9.0.2",
"react-textarea-autosize": "^8.5.3",
"rn-fetch-blob": "^0.12.0",
@@ -211,7 +208,7 @@
"@lingui/macro": "^4.5.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@react-native-community/eslint-config": "^3.0.0",
"@react-native/typescript-config": "^0.74.1",
"@react-native/typescript-config": "^0.74.0",
"@testing-library/jest-native": "^5.4.1",
"@testing-library/react-native": "^11.5.2",
"@tsconfig/react-native": "^2.0.3",
@@ -237,7 +234,6 @@
"babel-loader": "^9.1.2",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-module-resolver": "^5.0.0",
"babel-plugin-react-compiler": "^0.0.0-experimental-592953e-20240517",
"babel-plugin-react-native-web": "^0.18.12",
"babel-preset-expo": "^10.0.0",
"eslint": "^8.19.0",
@@ -245,7 +241,6 @@
"eslint-plugin-ft-flow": "^2.0.3",
"eslint-plugin-lingui": "^0.2.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-compiler": "^0.0.0-experimental-c8b3f72-20240517",
"eslint-plugin-react-native-a11y": "^3.3.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"html-webpack-plugin": "^5.5.0",
@@ -255,7 +250,7 @@
"jest-expo": "^50.0.1",
"jest-junit": "^15.0.0",
"lint-staged": "^13.2.3",
"metro-react-native-babel-preset": "^0.74.1",
"metro-react-native-babel-preset": "^0.73.7",
"prettier": "^2.8.3",
"react-native-dotenv": "^3.3.1",
"react-refresh": "^0.14.0",
@@ -271,11 +266,7 @@
},
"resolutions": {
"@types/react": "^18",
"**/zeed-dom": "0.10.9",
"**/zod": "3.23.8",
"**/expo-constants": "16.0.1",
"**/expo-device": "6.0.2",
"@react-native/babel-preset": "0.74.1"
"**/zeed-dom": "0.10.9"
},
"jest": {
"preset": "jest-expo/ios",
File diff suppressed because it is too large Load Diff
@@ -1,16 +0,0 @@
diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m
index e916023..0564d97 100644
--- a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m
+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m
@@ -22,6 +22,11 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
_backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_backedTextInputView.textInputDelegate = self;
+ // Disable inline predictions to prevent jank in the composer
+ if (@available(iOS 17.0, *)) {
+ _backedTextInputView.inlinePredictionType = UITextInlinePredictionTypeNo;
+ }
+
[self addSubview:_backedTextInputView];
}
@@ -0,0 +1,56 @@
diff --git a/node_modules/@react-navigation/native/lib/commonjs/useLinking.js b/node_modules/@react-navigation/native/lib/commonjs/useLinking.js
index ef4f368..2b0da35 100644
--- a/node_modules/@react-navigation/native/lib/commonjs/useLinking.js
+++ b/node_modules/@react-navigation/native/lib/commonjs/useLinking.js
@@ -273,8 +273,12 @@ function useLinking(ref, _ref) {
});
const currentIndex = history.index;
try {
- if (nextIndex !== -1 && nextIndex < currentIndex) {
- // An existing entry for this path exists and it's less than current index, go back to that
+ if (
+ nextIndex !== -1 &&
+ nextIndex < currentIndex &&
+ // We should only go back if the entry exists and it's less than current index
+ history.get(nextIndex - currentIndex)
+ ) { // An existing entry for this path exists and it's less than current index, go back to that
await history.go(nextIndex - currentIndex);
} else {
// We couldn't find an existing entry to go back to, so we'll go back by the delta
diff --git a/node_modules/@react-navigation/native/lib/module/useLinking.js b/node_modules/@react-navigation/native/lib/module/useLinking.js
index 62a3b43..11a5a28 100644
--- a/node_modules/@react-navigation/native/lib/module/useLinking.js
+++ b/node_modules/@react-navigation/native/lib/module/useLinking.js
@@ -264,8 +264,12 @@ export default function useLinking(ref, _ref) {
});
const currentIndex = history.index;
try {
- if (nextIndex !== -1 && nextIndex < currentIndex) {
- // An existing entry for this path exists and it's less than current index, go back to that
+ if (
+ nextIndex !== -1 &&
+ nextIndex < currentIndex &&
+ // We should only go back if the entry exists and it's less than current index
+ history.get(nextIndex - currentIndex)
+ ) { // An existing entry for this path exists and it's less than current index, go back to that
await history.go(nextIndex - currentIndex);
} else {
// We couldn't find an existing entry to go back to, so we'll go back by the delta
diff --git a/node_modules/@react-navigation/native/src/useLinking.tsx b/node_modules/@react-navigation/native/src/useLinking.tsx
index 3db40b7..9ba4ecd 100644
--- a/node_modules/@react-navigation/native/src/useLinking.tsx
+++ b/node_modules/@react-navigation/native/src/useLinking.tsx
@@ -381,7 +381,12 @@ export default function useLinking(
const currentIndex = history.index;
try {
- if (nextIndex !== -1 && nextIndex < currentIndex) {
+ if (
+ nextIndex !== -1 &&
+ nextIndex < currentIndex &&
+ // We should only go back if the entry exists and it's less than current index
+ history.get(nextIndex - currentIndex)
+ ) {
// An existing entry for this path exists and it's less than current index, go back to that
await history.go(nextIndex - currentIndex);
} else {
@@ -0,0 +1,5 @@
# React Navigation history bug patch
This patches react-navigation to fix the issues in https://github.com/bluesky-social/social-app/issues/710.
This is based on the PR found at https://github.com/react-navigation/react-navigation/pull/11833
@@ -1,9 +1,9 @@
diff --git a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt
index 1520465..6ea988a 100644
index 26c52af..b949a4c 100644
--- a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt
+++ b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt
@@ -42,7 +42,7 @@ class HapticsModule : Module() {
private fun vibrate(type: HapticsVibrationType) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- vibrator.vibrate(VibrationEffect.createWaveform(type.timings, type.amplitudes, -1))
+112
View File
@@ -0,0 +1,112 @@
diff --git a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
index 3f50f8c..ee47fa1 100644
--- a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
+++ b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
@@ -33,7 +33,9 @@ import kotlin.coroutines.resumeWithException
// TODO(@bbarthec): rename to ExpoImagePicker
private const val moduleName = "ExponentImagePicker"
+
class ImagePickerModule : Module() {
+ private var isPickerOpen = false
override fun definition() = ModuleDefinition {
Name(moduleName)
@@ -129,6 +131,11 @@ class ImagePickerModule : Module() {
options: ImagePickerOptions
): Any {
return try {
+ if(isPickerOpen) {
+ return ImagePickerResponse(canceled = true)
+ }
+
+ isPickerOpen = true
var result = launchPicker(pickerLauncher)
if (
!options.allowsMultipleSelection &&
@@ -143,6 +150,8 @@ class ImagePickerModule : Module() {
mediaHandler.readExtras(result.data, options)
} catch (cause: OperationCanceledException) {
return ImagePickerResponse(canceled = true)
+ } finally {
+ isPickerOpen = false
}
}
diff --git a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt
index ff15c91..9763012 100644
--- a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt
+++ b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/contracts/ImageLibraryContract.kt
@@ -5,12 +5,7 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.net.Uri
-import androidx.activity.result.PickVisualMediaRequest
-import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
-import androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia
import expo.modules.imagepicker.ImagePickerOptions
-import expo.modules.imagepicker.MediaTypes
-import expo.modules.imagepicker.UNLIMITED_SELECTION
import expo.modules.imagepicker.getAllDataUris
import expo.modules.imagepicker.toMediaType
import expo.modules.kotlin.activityresult.AppContextActivityResultContract
@@ -26,51 +21,26 @@ import java.io.Serializable
* @see [androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents]
*/
internal class ImageLibraryContract(
- private val appContextProvider: AppContextProvider
+ private val appContextProvider: AppContextProvider,
) : AppContextActivityResultContract<ImageLibraryContractOptions, ImagePickerContractResult> {
private val contentResolver: ContentResolver
get() = appContextProvider.appContext.reactContext?.contentResolver
?: throw Exceptions.ReactContextLost()
override fun createIntent(context: Context, input: ImageLibraryContractOptions): Intent {
- val request = PickVisualMediaRequest.Builder()
- .setMediaType(
- when (input.options.mediaTypes) {
- MediaTypes.VIDEOS -> {
- PickVisualMedia.VideoOnly
- }
-
- MediaTypes.IMAGES -> {
- PickVisualMedia.ImageOnly
- }
-
- else -> {
- PickVisualMedia.ImageAndVideo
- }
- }
- )
- .build()
+ val intent = Intent(Intent.ACTION_GET_CONTENT)
+ .addCategory(Intent.CATEGORY_OPENABLE)
+ .setType("image/*")
if (input.options.allowsMultipleSelection) {
- val selectionLimit = input.options.selectionLimit
-
- if (selectionLimit == 1) {
- // If multiple selection is allowed but the limit is 1, we should ignore
- // the multiple selection flag and just treat it as a single selection.
- return PickVisualMedia().createIntent(context, request)
+ if(input.options.selectionLimit == 1) {
+ return intent
}
- if (selectionLimit > 1) {
- return PickMultipleVisualMedia(selectionLimit).createIntent(context, request)
- }
-
- // If the selection limit is 0, it is the same as unlimited selection.
- if (selectionLimit == UNLIMITED_SELECTION) {
- return PickMultipleVisualMedia().createIntent(context, request)
- }
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
- return PickVisualMedia().createIntent(context, request)
+ return intent
}
override fun parseResult(input: ImageLibraryContractOptions, resultCode: Int, intent: Intent?) =
@@ -0,0 +1,3 @@
added by https://github.com/bluesky-social/social-app/pull/2384#pullrequestreview-1800985521
hackfixes the image picker on android so that the user can select from their typical image sources
-5
View File
@@ -1,5 +0,0 @@
## expo-modules-core Patch
This patch fixes crashes seen in some Android clients when using intents to open the app. See https://github.com/expo/expo/pull/29513.
Do not remove this patch until that PR lands in Expo and is released.
-14
View File
@@ -1,14 +0,0 @@
diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java b/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java
index bb74e80..0aa0202 100644
--- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java
+++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java
@@ -90,8 +90,8 @@ public class NativeModulesProxy extends ReactContextBaseJavaModule {
mModuleRegistry.ensureIsInitialized();
KotlinInteropModuleRegistry kotlinModuleRegistry = getKotlinInteropModuleRegistry();
- kotlinModuleRegistry.emitOnCreate();
kotlinModuleRegistry.installJSIInterop();
+ kotlinModuleRegistry.emitOnCreate();
Map<String, Object> constants = new HashMap<>(3);
constants.put(MODULES_CONSTANTS_KEY, new HashMap<>());
@@ -1,54 +1,29 @@
diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle
index b863077..8b5209e 100644
index 97bf4f4..6e9d427 100644
--- a/node_modules/expo-notifications/android/build.gradle
+++ b/node_modules/expo-notifications/android/build.gradle
@@ -32,6 +32,7 @@ dependencies {
@@ -118,6 +118,7 @@ dependencies {
api 'com.google.firebase:firebase-messaging:22.0.0'
api 'me.leolin:ShortcutBadger:1.1.22@aar'
+ implementation project(':expo-background-notification-handler')
if (project.findProject(':expo-modules-test-core')) {
testImplementation project(':expo-modules-test-core')
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt
index 63a46c5..f911834 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt
@@ -1,5 +1,6 @@
package expo.modules.notifications.badge
+import android.app.NotificationManager
import android.content.Context
import android.util.Log
import me.leolin.shortcutbadger.ShortcutBadgeException
@@ -12,7 +13,12 @@ object BadgeHelper {
fun setBadgeCount(context: Context, badgeCount: Int): Boolean {
return try {
- ShortcutBadger.applyCountOrThrow(context.applicationContext, badgeCount)
+ if (badgeCount == 0) {
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.cancelAll()
+ } else {
+ ShortcutBadger.applyCountOrThrow(context.applicationContext, badgeCount)
+ }
BadgeHelper.badgeCount = badgeCount
true
} catch (e: ShortcutBadgeException) {
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java
index 0af7fe0..8f2c8d8 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java
@@ -14,6 +14,7 @@ import expo.modules.notifications.notifications.enums.NotificationPriority;
import expo.modules.notifications.notifications.model.NotificationContent;
public class JSONNotificationContentBuilder extends NotificationContent.Builder {
+ private static final String CHANNEL_ID_KEY = "channelId";
private static final String TITLE_KEY = "title";
private static final String TEXT_KEY = "message";
private static final String SUBTITLE_KEY = "subtitle";
@@ -36,6 +37,7 @@ public class JSONNotificationContentBuilder extends NotificationContent.Builder
public NotificationContent.Builder setPayload(JSONObject payload) {
this.setTitle(getTitle(payload))
+ .setChannelId(getChannelId(payload))
@@ -58,7 +33,7 @@ index 0af7fe0..8f2c8d8 100644
@@ -60,6 +62,14 @@ public class JSONNotificationContentBuilder extends NotificationContent.Builder
return this;
}
+ protected String getChannelId(JSONObject payload) {
+ try {
+ return payload.getString(CHANNEL_ID_KEY);
@@ -71,7 +46,7 @@ index 0af7fe0..8f2c8d8 100644
try {
return payload.getString(TITLE_KEY);
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
index f1fed19..012757b 100644
index f1fed19..1619f59 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
@@ -20,6 +20,7 @@ import expo.modules.notifications.notifications.enums.NotificationPriority;
@@ -82,62 +57,60 @@ index f1fed19..012757b 100644
private String mTitle;
private String mText;
private String mSubtitle;
@@ -50,6 +51,11 @@ public class NotificationContent implements Parcelable, Serializable {
@@ -50,6 +51,9 @@ public class NotificationContent implements Parcelable, Serializable {
}
};
+ @Nullable
+ public String getChannelId() {
+ return mChannelId;
+ }
+ public String getChannelId() { return mChannelId; }
+
@Nullable
public String getTitle() {
return mTitle;
@@ -121,6 +127,7 @@ public class NotificationContent implements Parcelable, Serializable {
@@ -121,6 +125,7 @@ public class NotificationContent implements Parcelable, Serializable {
}
protected NotificationContent(Parcel in) {
+ mChannelId = in.readString();
mTitle = in.readString();
mText = in.readString();
mSubtitle = in.readString();
@@ -146,6 +153,7 @@ public class NotificationContent implements Parcelable, Serializable {
@@ -146,6 +151,7 @@ public class NotificationContent implements Parcelable, Serializable {
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mChannelId);
dest.writeString(mTitle);
dest.writeString(mText);
dest.writeString(mSubtitle);
@@ -166,6 +174,7 @@ public class NotificationContent implements Parcelable, Serializable {
@@ -166,6 +172,7 @@ public class NotificationContent implements Parcelable, Serializable {
private static final long serialVersionUID = 397666843266836802L;
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+ out.writeObject(mChannelId);
out.writeObject(mTitle);
out.writeObject(mText);
out.writeObject(mSubtitle);
@@ -190,6 +199,7 @@ public class NotificationContent implements Parcelable, Serializable {
@@ -190,6 +197,7 @@ public class NotificationContent implements Parcelable, Serializable {
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+ mChannelId = (String) in.readObject();
mTitle = (String) in.readObject();
mText = (String) in.readObject();
mSubtitle = (String) in.readObject();
@@ -240,6 +250,7 @@ public class NotificationContent implements Parcelable, Serializable {
@@ -240,6 +248,7 @@ public class NotificationContent implements Parcelable, Serializable {
}
public static class Builder {
+ private String mChannelId;
private String mTitle;
private String mText;
private String mSubtitle;
@@ -260,6 +271,11 @@ public class NotificationContent implements Parcelable, Serializable {
@@ -260,6 +269,11 @@ public class NotificationContent implements Parcelable, Serializable {
useDefaultVibrationPattern();
}
+ public Builder setChannelId(String channelId) {
+ mChannelId = channelId;
+ return this;
@@ -146,8 +119,8 @@ index f1fed19..012757b 100644
public Builder setTitle(String title) {
mTitle = title;
return this;
@@ -336,6 +352,7 @@ public class NotificationContent implements Parcelable, Serializable {
@@ -336,6 +350,7 @@ public class NotificationContent implements Parcelable, Serializable {
public NotificationContent build() {
NotificationContent content = new NotificationContent();
+ content.mChannelId = mChannelId;
@@ -155,20 +128,28 @@ index f1fed19..012757b 100644
content.mSubtitle = mSubtitle;
content.mText = mText;
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java
index 6bd9928..ee93d70 100644
index 6bd9928..aab71ea 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java
@@ -48,6 +48,10 @@ public class ExpoNotificationBuilder extends ChannelAwareNotificationBuilder {
@@ -7,7 +7,6 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.provider.Settings;
@@ -48,6 +47,10 @@ public class ExpoNotificationBuilder extends ChannelAwareNotificationBuilder {
NotificationContent content = getNotificationContent();
+ if (content.getChannelId() != null) {
+ builder.setChannelId(content.getChannelId());
+ }
+
builder.setAutoCancel(content.isAutoDismiss());
builder.setOngoing(content.isSticky());
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
index 55b3a8d..1b99d5b 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
@@ -183,7 +164,7 @@ index 55b3a8d..1b99d5b 100644
import org.json.JSONObject
import java.lang.ref.WeakReference
import java.util.*
-open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate {
+open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate, BackgroundNotificationHandlerInterface {
companion object {
@@ -191,7 +172,7 @@ index 55b3a8d..1b99d5b 100644
// than by static properties. Fortunately, using weak references we can
@@ -89,12 +92,21 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM
fun getBackgroundTasks() = sBackgroundTaskConsumerReferences.values.mapNotNull { it.get() }
override fun onMessageReceived(remoteMessage: RemoteMessage) {
- NotificationsService.receive(context, createNotification(remoteMessage))
- getBackgroundTasks().forEach {
@@ -206,7 +187,7 @@ index 55b3a8d..1b99d5b 100644
+ }
}
}
+ override fun showMessage(remoteMessage: RemoteMessage) {
+ NotificationsService.receive(context, createNotification(remoteMessage))
+ }
@@ -7,8 +7,3 @@ in `onMessageReceived` are sent to the module for handling.
It also allows us to set the Android notification channel ID from the notification `data`, rather
than the `notification` object in the payload.
### `setBadgeCountAsync` fix on Android
`ShortcutBadger`'s `setCount` doesn't work for clearing the badge on Android for some reason. Instead, let's use the
Android API for clearing the badge.
@@ -1,11 +1,11 @@
diff --git a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift
index b85291e..546709d 100644
--- a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift
+++ b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift
@@ -78,13 +78,20 @@ public final class ExpoUpdatesUpdate: Update {
status = UpdateStatus.StatusPending
diff --git a/node_modules/expo-updates/ios/EXUpdates/Update/NewUpdate.swift b/node_modules/expo-updates/ios/EXUpdates/Update/NewUpdate.swift
index 189a5f5..8d5b8e6 100644
--- a/node_modules/expo-updates/ios/EXUpdates/Update/NewUpdate.swift
+++ b/node_modules/expo-updates/ios/EXUpdates/Update/NewUpdate.swift
@@ -68,13 +68,20 @@ public final class NewUpdate: Update {
processedAssets.append(asset)
}
+ // Instead of relying on various hacks to get the correct format for the specific
+ // platform on the backend, we can just add this little patch..
+ let dateFormatter = DateFormatter()
@@ -23,4 +23,4 @@ index b85291e..546709d 100644
+ commitTime: date,
runtimeVersion: runtimeVersion,
keep: true,
status: status,
status: UpdateStatus.StatusPending,
@@ -4,4 +4,4 @@ This is a small patch to convert timestamp formats that are returned from the ba
backend to return the correct format for a specific format (the format required on Android is not the same as on iOS)
we can just add this conversion in.
Don't remove unless we make changes on the backend to support both platforms.
Don't remove unless we make changes on the backend to support both platforms.
@@ -1,29 +1,3 @@
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
index b0d71dc..9974932 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
@@ -377,10 +377,6 @@ - (void)textInputDidBeginEditing
self.backedTextInputView.attributedText = [NSAttributedString new];
}
- if (_selectTextOnFocus) {
- [self.backedTextInputView selectAll:nil];
- }
-
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:self.reactTag
text:[self.backedTextInputView.attributedText.string copy]
@@ -611,6 +607,10 @@ - (UIView *)reactAccessibilityElement
- (void)reactFocus
{
[self.backedTextInputView reactFocus];
+
+ if (_selectTextOnFocus) {
+ [self.backedTextInputView selectAll:nil];
+ }
}
- (void)reactBlur
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
index e9b330f..1ecdf0a 100644
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
@@ -31,7 +5,7 @@ index e9b330f..1ecdf0a 100644
@@ -16,4 +16,6 @@
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
@property (nonatomic, weak) UIScrollView *scrollView;
+- (void)forwarderBeginRefreshing;
+
@end
@@ -42,7 +16,7 @@ index b09e653..4c32b31 100644
@@ -198,9 +198,53 @@ - (void)refreshControlValueChanged
[self setCurrentRefreshingState:super.refreshing];
_refreshingProgrammatically = NO;
+ if (@available(iOS 17.4, *)) {
+ if (_currentRefreshingState) {
+ UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
@@ -55,7 +29,7 @@ index b09e653..4c32b31 100644
_onRefresh(nil);
}
}
+/*
+ This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native
+ libraries to perform a refresh of a scrollview and access the refresh control's onRefresh
@@ -64,15 +38,15 @@ index b09e653..4c32b31 100644
+- (void)forwarderBeginRefreshing
+{
+ _refreshingProgrammatically = NO;
+
+
+ [self sizeToFit];
+
+
+ if (!self.scrollView) {
+ return;
+ }
+
+
+ UIScrollView *scrollView = (UIScrollView *)self.scrollView;
+
+
+ [UIView animateWithDuration:0.3
+ delay:0
+ options:UIViewAnimationOptionBeginFromCurrentState
@@ -84,7 +58,7 @@ index b09e653..4c32b31 100644
+ completion:^(__unused BOOL finished) {
+ [super beginRefreshing];
+ [self setCurrentRefreshingState:super.refreshing];
+
+
+ if (self->_onRefresh) {
+ self->_onRefresh(nil);
+ }
@@ -93,19 +67,3 @@ index b09e653..4c32b31 100644
+}
+
@end
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
index 5f5e1ab..aac00b6 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
@@ -99,8 +99,9 @@ public class JavaTimerManager {
}
// If the JS thread is busy for multiple frames we cancel any other pending runnable.
- if (mCurrentIdleCallbackRunnable != null) {
- mCurrentIdleCallbackRunnable.cancel();
+ IdleCallbackRunnable currentRunnable = mCurrentIdleCallbackRunnable;
+ if (currentRunnable != null) {
+ currentRunnable.cancel();
}
mCurrentIdleCallbackRunnable = new IdleCallbackRunnable(frameTimeNanos);
@@ -11,10 +11,3 @@ in the RN repo: https://github.com/facebook/react-native/issues/43388
Patching `RCTRefreshControl.m` and `RCTRefreshControl.h` to add a new `forwarderBeginRefreshing` method to the class.
This method is used by `ExpoScrollForwarder` to initiate a refresh of the underlying `UIScrollView` from inside that
module.
## TextInput Patch - `selectTextOnFocus` fix
Patching `RCTBaseTextInputView.m` to fix an issue where `selectTextOnFocus` does not work as expected on iOS 17. This
patch _only_ fixes the Paper version of `TextInput`. If we migrate to Fabric and the fix has not been made upstream,
we can apply the same fix. See https://github.com/facebook/react-native/pull/44307.
@@ -1,237 +0,0 @@
diff --git a/node_modules/react-native-reanimated/src/createAnimatedComponent/commonTypes.ts b/node_modules/react-native-reanimated/src/createAnimatedComponent/commonTypes.ts
index 92ebe62..5f8207e 100644
--- a/node_modules/react-native-reanimated/src/createAnimatedComponent/commonTypes.ts
+++ b/node_modules/react-native-reanimated/src/createAnimatedComponent/commonTypes.ts
@@ -96,7 +96,8 @@ export interface AnimatedComponentRef extends Component {
export interface IAnimatedComponentInternal {
_styles: StyleProps[] | null;
_animatedProps?: Partial<AnimatedComponentProps<AnimatedProps>>;
- _viewTag: number;
+ _componentViewTag: number;
+ _eventViewTag: number;
_isFirstRender: boolean;
jestAnimatedStyle: { value: StyleProps };
_component: AnimatedComponentRef | HTMLElement | null;
diff --git a/node_modules/react-native-reanimated/src/createAnimatedComponent/createAnimatedComponent.tsx b/node_modules/react-native-reanimated/src/createAnimatedComponent/createAnimatedComponent.tsx
index 88b3fdf..2488ebc 100644
--- a/node_modules/react-native-reanimated/src/createAnimatedComponent/createAnimatedComponent.tsx
+++ b/node_modules/react-native-reanimated/src/createAnimatedComponent/createAnimatedComponent.tsx
@@ -122,7 +122,8 @@ export function createAnimatedComponent(
{
_styles: StyleProps[] | null = null;
_animatedProps?: Partial<AnimatedComponentProps<AnimatedProps>>;
- _viewTag = -1;
+ _componentViewTag = -1;
+ _eventViewTag = -1;
_isFirstRender = true;
jestAnimatedStyle: { value: StyleProps } = { value: {} };
_component: AnimatedComponentRef | HTMLElement | null = null;
@@ -143,7 +144,8 @@ export function createAnimatedComponent(
}
componentDidMount() {
- this._viewTag = this._getViewInfo().viewTag as number;
+ this._setComponentViewTag();
+ this._setEventViewTag();
this._attachNativeEvents();
this._jsPropsUpdater.addOnJSPropsChangeListener(this);
this._attachAnimatedStyles();
@@ -185,7 +187,10 @@ export function createAnimatedComponent(
if (this.props.sharedTransitionTag) {
this._configureSharedTransition(true);
}
- this._sharedElementTransition?.unregisterTransition(this._viewTag, true);
+ this._sharedElementTransition?.unregisterTransition(
+ this._componentViewTag,
+ true
+ );
const exiting = this.props.exiting;
if (
@@ -209,7 +214,7 @@ export function createAnimatedComponent(
: getReduceMotionFromConfig();
if (!reduceMotionInExiting) {
updateLayoutAnimations(
- this._viewTag,
+ this._componentViewTag,
LayoutAnimationType.EXITING,
maybeBuild(
exiting,
@@ -221,12 +226,22 @@ export function createAnimatedComponent(
}
}
- _getEventViewRef() {
- // Make sure to get the scrollable node for components that implement
- // `ScrollResponder.Mixin`.
- return (this._component as AnimatedComponentRef)?.getScrollableNode
- ? (this._component as AnimatedComponentRef).getScrollableNode?.()
- : this._component;
+ _setComponentViewTag() {
+ this._componentViewTag = this._getViewInfo().viewTag as number;
+ }
+
+ _setEventViewTag() {
+ // Setting the tag for registering events - since the event emitting view can be nested inside the main component
+ const componentAnimatedRef = this._component as AnimatedComponentRef;
+ if (componentAnimatedRef.getScrollableNode) {
+ const scrollableNode = componentAnimatedRef.getScrollableNode();
+ this._eventViewTag = findNodeHandle(scrollableNode) ?? -1;
+ } else {
+ this._eventViewTag =
+ findNodeHandle(
+ options?.setNativeProps ? this : componentAnimatedRef
+ ) ?? -1;
+ }
}
_attachNativeEvents() {
@@ -236,7 +251,7 @@ export function createAnimatedComponent(
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler
) {
- prop.workletEventHandler.registerForEvents(this._viewTag, key);
+ prop.workletEventHandler.registerForEvents(this._eventViewTag, key);
}
}
}
@@ -248,7 +263,7 @@ export function createAnimatedComponent(
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler
) {
- prop.workletEventHandler.unregisterFromEvents(this._viewTag);
+ prop.workletEventHandler.unregisterFromEvents(this._eventViewTag);
}
}
}
@@ -258,15 +273,17 @@ export function createAnimatedComponent(
for (const style of this._styles) {
style.viewsRef.remove(this);
}
- } else if (this._viewTag !== -1 && this._styles !== null) {
+ } else if (this._componentViewTag !== -1 && this._styles !== null) {
for (const style of this._styles) {
- style.viewDescriptors.remove(this._viewTag);
+ style.viewDescriptors.remove(this._componentViewTag);
}
if (this.props.animatedProps?.viewDescriptors) {
- this.props.animatedProps.viewDescriptors.remove(this._viewTag);
+ this.props.animatedProps.viewDescriptors.remove(
+ this._componentViewTag
+ );
}
if (isFabric()) {
- removeFromPropsRegistry(this._viewTag);
+ removeFromPropsRegistry(this._componentViewTag);
}
}
}
@@ -283,15 +300,19 @@ export function createAnimatedComponent(
const newProp = this.props[key];
if (!newProp) {
// Prop got deleted
- prevProp.workletEventHandler.unregisterFromEvents(this._viewTag);
+ prevProp.workletEventHandler.unregisterFromEvents(
+ this._eventViewTag
+ );
} else if (
has('workletEventHandler', newProp) &&
newProp.workletEventHandler instanceof WorkletEventHandler &&
newProp.workletEventHandler !== prevProp.workletEventHandler
) {
// Prop got changed
- prevProp.workletEventHandler.unregisterFromEvents(this._viewTag);
- newProp.workletEventHandler.registerForEvents(this._viewTag);
+ prevProp.workletEventHandler.unregisterFromEvents(
+ this._eventViewTag
+ );
+ newProp.workletEventHandler.registerForEvents(this._eventViewTag);
}
}
}
@@ -304,7 +325,7 @@ export function createAnimatedComponent(
!prevProps[key]
) {
// Prop got added
- newProp.workletEventHandler.registerForEvents(this._viewTag);
+ newProp.workletEventHandler.registerForEvents(this._eventViewTag);
}
}
}
@@ -381,7 +402,7 @@ export function createAnimatedComponent(
adaptViewConfig(viewConfig);
}
- this._viewTag = viewTag as number;
+ this._componentViewTag = viewTag as number;
// remove old styles
if (prevStyles) {
@@ -487,7 +508,11 @@ export function createAnimatedComponent(
AnimatedComponent.displayName
)
: undefined;
- updateLayoutAnimations(this._viewTag, LayoutAnimationType.LAYOUT, layout);
+ updateLayoutAnimations(
+ this._componentViewTag,
+ LayoutAnimationType.LAYOUT,
+ layout
+ );
}
_configureSharedTransition(isUnmounting = false) {
@@ -497,7 +522,7 @@ export function createAnimatedComponent(
const { sharedTransitionTag } = this.props;
if (!sharedTransitionTag) {
this._sharedElementTransition?.unregisterTransition(
- this._viewTag,
+ this._componentViewTag,
isUnmounting
);
this._sharedElementTransition = null;
@@ -508,7 +533,7 @@ export function createAnimatedComponent(
this._sharedElementTransition ??
new SharedTransition();
sharedElementTransition.registerTransition(
- this._viewTag,
+ this._componentViewTag,
sharedTransitionTag,
isUnmounting
);
@@ -527,7 +552,7 @@ export function createAnimatedComponent(
? (ref as HTMLElement)
: findNodeHandle(ref as Component);
- this._viewTag = tag as number;
+ this._componentViewTag = tag as number;
const { layout, entering, exiting, sharedTransitionTag } = this.props;
if (
diff --git a/node_modules/react-native-reanimated/lib/module/reanimated2/index.js b/node_modules/react-native-reanimated/lib/module/reanimated2/index.js
index ac9be5d..86d4605 100644
--- a/node_modules/react-native-reanimated/lib/module/reanimated2/index.js
+++ b/node_modules/react-native-reanimated/lib/module/reanimated2/index.js
@@ -47,4 +47,5 @@ export { LayoutAnimationConfig } from './component/LayoutAnimationConfig';
export { PerformanceMonitor } from './component/PerformanceMonitor';
export { startMapper, stopMapper } from './mappers';
export { startScreenTransition, finishScreenTransition, ScreenTransition } from './screenTransition';
+export { isReducedMotion } from './PlatformChecker';
//# sourceMappingURL=index.js.map
diff --git a/node_modules/react-native-reanimated/lib/typescript/reanimated2/index.d.ts b/node_modules/react-native-reanimated/lib/typescript/reanimated2/index.d.ts
index f01dc57..161ef22 100644
--- a/node_modules/react-native-reanimated/lib/typescript/reanimated2/index.d.ts
+++ b/node_modules/react-native-reanimated/lib/typescript/reanimated2/index.d.ts
@@ -36,3 +36,4 @@ export type { FlatListPropsWithLayout } from './component/FlatList';
export { startMapper, stopMapper } from './mappers';
export { startScreenTransition, finishScreenTransition, ScreenTransition, } from './screenTransition';
export type { AnimatedScreenTransition, GoBackGesture, ScreenTransitionConfig, } from './screenTransition';
+export { isReducedMotion } from './PlatformChecker';
diff --git a/node_modules/react-native-reanimated/src/reanimated2/index.ts b/node_modules/react-native-reanimated/src/reanimated2/index.ts
index 5885fa1..a3c693f 100644
--- a/node_modules/react-native-reanimated/src/reanimated2/index.ts
+++ b/node_modules/react-native-reanimated/src/reanimated2/index.ts
@@ -284,3 +284,4 @@ export type {
GoBackGesture,
ScreenTransitionConfig,
} from './screenTransition';
+export { isReducedMotion } from './PlatformChecker';
@@ -0,0 +1,30 @@
diff --git a/node_modules/react-native-reanimated/lib/module/reanimated2/index.js b/node_modules/react-native-reanimated/lib/module/reanimated2/index.js
index 91e49f4..c10d3fc 100644
--- a/node_modules/react-native-reanimated/lib/module/reanimated2/index.js
+++ b/node_modules/react-native-reanimated/lib/module/reanimated2/index.js
@@ -45,4 +45,5 @@ export { getUseOfValueInStyleWarning } from './pluginUtils';
export { withReanimatedTimer, advanceAnimationByTime, advanceAnimationByFrame, setUpTests, getAnimatedStyle } from './jestUtils';
export { LayoutAnimationConfig } from './component/LayoutAnimationConfig';
export { startMapper, stopMapper } from './mappers';
+export { isReducedMotion } from './PlatformChecker';
//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/node_modules/react-native-reanimated/lib/typescript/reanimated2/index.d.ts b/node_modules/react-native-reanimated/lib/typescript/reanimated2/index.d.ts
index 96bd913..ad63a09 100644
--- a/node_modules/react-native-reanimated/lib/typescript/reanimated2/index.d.ts
+++ b/node_modules/react-native-reanimated/lib/typescript/reanimated2/index.d.ts
@@ -33,3 +33,4 @@ export type { Adaptable, AdaptTransforms, AnimateProps, AnimatedProps, AnimatedT
export type { AnimatedScrollViewProps } from './component/ScrollView';
export type { FlatListPropsWithLayout } from './component/FlatList';
export { startMapper, stopMapper } from './mappers';
+export { isReducedMotion } from './PlatformChecker';
diff --git a/node_modules/react-native-reanimated/src/reanimated2/index.ts b/node_modules/react-native-reanimated/src/reanimated2/index.ts
index 096dc05..38fc01d 100644
--- a/node_modules/react-native-reanimated/src/reanimated2/index.ts
+++ b/node_modules/react-native-reanimated/src/reanimated2/index.ts
@@ -271,3 +271,4 @@ export type {
export type { AnimatedScrollViewProps } from './component/ScrollView';
export type { FlatListPropsWithLayout } from './component/FlatList';
export { startMapper, stopMapper } from './mappers';
+export { isReducedMotion } from './PlatformChecker';
\ No newline at end of file
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
outputIos=$(eas build:version:get -p ios)
outputAndroid=$(eas build:version:get -p android)
BSKY_IOS_BUILD_NUMBER=${outputIos#*buildNumber - }
BSKY_ANDROID_VERSION_CODE=${outputAndroid#*versionCode - }
echo PACKAGE_VERSION="$(jq -r '.version' package.json)" > "$GITHUB_OUTPUT"
echo BSKY_IOS_BUILD_NUMBER=$BSKY_IOS_BUILD_NUMBER >> "$GITHUB_OUTPUT"
echo BSKY_ANDROID_VERSION_CODE=$BSKY_ANDROID_VERSION_CODE >> "$GITHUB_OUTPUT"
+32 -42
View File
@@ -4,7 +4,6 @@ import 'view/icons'
import React, {useEffect, useState} from 'react'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
import {KeyboardProvider} from 'react-native-keyboard-controller'
import {RootSiblingParent} from 'react-native-root-siblings'
import {
initialWindowMetrics,
@@ -14,40 +13,36 @@ import * as SplashScreen from 'expo-splash-screen'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
import {QueryProvider} from '#/lib/react-query'
import {
initialize,
Provider as StatsigProvider,
tryFetchGates,
} from '#/lib/statsig/statsig'
import {s} from '#/lib/styles'
import {ThemeProvider} from '#/lib/ThemeContext'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs'
import {Provider as InvitesStateProvider} from '#/state/invites'
import {Provider as LightboxStateProvider} from '#/state/lightbox'
import {MessagesProvider} from '#/state/messages'
import {Provider as ModalStateProvider} from '#/state/modals'
import {init as initPersistedState} from '#/state/persisted'
import {Provider as PrefsStateProvider} from '#/state/preferences'
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation-opts'
import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread'
import {readLastActiveAccount} from '#/state/session/util'
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
import {QueryProvider} from 'lib/react-query'
import {s} from 'lib/styles'
import {ThemeProvider} from 'lib/ThemeContext'
import {Provider as DialogStateProvider} from 'state/dialogs'
import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as LightboxStateProvider} from 'state/lightbox'
import {Provider as ModalStateProvider} from 'state/modals'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as PrefsStateProvider} from 'state/preferences'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import {
Provider as SessionProvider,
SessionAccount,
useSession,
useSessionApi,
} from '#/state/session'
import {readLastActiveAccount} from '#/state/session/util'
import {Provider as ShellStateProvider} from '#/state/shell'
import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
import {TestCtrls} from '#/view/com/testing/TestCtrls'
import * as Toast from '#/view/com/util/Toast'
import {Shell} from '#/view/shell'
} from 'state/session'
import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
import {TestCtrls} from 'view/com/testing/TestCtrls'
import * as Toast from 'view/com/util/Toast'
import {Shell} from 'view/shell'
import {ThemeProvider as Alf} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {Provider as PortalProvider} from '#/components/Portal'
@@ -73,9 +68,6 @@ function InnerApp() {
try {
if (account) {
await resumeSession(account)
} else {
await initialize()
await tryFetchGates(undefined, 'prefer-fresh-gates')
}
} catch (e) {
logger.error(`session: resume failed`, {message: e})
@@ -112,12 +104,10 @@ function InnerApp() {
<SelectedFeedProvider>
<UnreadNotifsProvider>
<BackgroundNotificationPreferencesProvider>
<MutedThreadsProvider>
<GestureHandlerRootView style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</MutedThreadsProvider>
<GestureHandlerRootView style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
@@ -152,10 +142,10 @@ function App() {
* that is set up in the InnerApp component above.
*/
return (
<KeyboardProvider enabled={false} statusBarTranslucent={true}>
<SessionProvider>
<ShellStateProvider>
<PrefsStateProvider>
<SessionProvider>
<ShellStateProvider>
<PrefsStateProvider>
<MutedThreadsProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
@@ -169,10 +159,10 @@ function App() {
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</PrefsStateProvider>
</ShellStateProvider>
</SessionProvider>
</KeyboardProvider>
</MutedThreadsProvider>
</PrefsStateProvider>
</ShellStateProvider>
</SessionProvider>
)
}
+66 -69
View File
@@ -2,41 +2,40 @@ import 'lib/sentry' // must be near top
import 'view/icons'
import React, {useEffect, useState} from 'react'
import {KeyboardProvider} from 'react-native-keyboard-controller'
import {RootSiblingParent} from 'react-native-root-siblings'
import {SafeAreaProvider} from 'react-native-safe-area-context'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
import {QueryProvider} from '#/lib/react-query'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
import {ThemeProvider} from '#/lib/ThemeContext'
import {logger} from '#/logger'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs'
import {Provider as InvitesStateProvider} from '#/state/invites'
import {Provider as LightboxStateProvider} from '#/state/lightbox'
import {MessagesProvider} from '#/state/messages'
import {Provider as ModalStateProvider} from '#/state/modals'
import {init as initPersistedState} from '#/state/persisted'
import {Provider as PrefsStateProvider} from '#/state/preferences'
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation-opts'
import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread'
import {readLastActiveAccount} from '#/state/session/util'
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
import {QueryProvider} from 'lib/react-query'
import {ThemeProvider} from 'lib/ThemeContext'
import {Provider as DialogStateProvider} from 'state/dialogs'
import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as LightboxStateProvider} from 'state/lightbox'
import {Provider as ModalStateProvider} from 'state/modals'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as PrefsStateProvider} from 'state/preferences'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import {
Provider as SessionProvider,
SessionAccount,
useSession,
useSessionApi,
} from '#/state/session'
import {readLastActiveAccount} from '#/state/session/util'
import {Provider as ShellStateProvider} from '#/state/shell'
import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
import * as Toast from '#/view/com/util/Toast'
import {ToastContainer} from '#/view/com/util/Toast.web'
import {Shell} from '#/view/shell/index'
} from 'state/session'
import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
import * as Toast from 'view/com/util/Toast'
import {ToastContainer} from 'view/com/util/Toast.web'
import {Shell} from 'view/shell/index'
import {ThemeProvider as Alf} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {Provider as PortalProvider} from '#/components/Portal'
@@ -79,43 +78,39 @@ function InnerApp() {
if (!isReady) return null
return (
<KeyboardProvider enabled={false}>
<Alf theme={theme}>
<ThemeProvider theme={theme}>
<RootSiblingParent>
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<QueryProvider currentDid={currentAccount?.did}>
<StatsigProvider>
<MessagesProvider>
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
<LabelDefsProvider>
<ModerationOptsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<BackgroundNotificationPreferencesProvider>
<MutedThreadsProvider>
<SafeAreaProvider>
<Shell />
</SafeAreaProvider>
</MutedThreadsProvider>
</BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</ModerationOptsProvider>
</LabelDefsProvider>
</MessagesProvider>
</StatsigProvider>
</QueryProvider>
</React.Fragment>
<ToastContainer />
</RootSiblingParent>
</ThemeProvider>
</Alf>
</KeyboardProvider>
<Alf theme={theme}>
<ThemeProvider theme={theme}>
<RootSiblingParent>
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<QueryProvider currentDid={currentAccount?.did}>
<StatsigProvider>
<MessagesProvider>
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
<LabelDefsProvider>
<ModerationOptsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<BackgroundNotificationPreferencesProvider>
<SafeAreaProvider>
<Shell />
</SafeAreaProvider>
</BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</ModerationOptsProvider>
</LabelDefsProvider>
</MessagesProvider>
</StatsigProvider>
</QueryProvider>
</React.Fragment>
<ToastContainer />
</RootSiblingParent>
</ThemeProvider>
</Alf>
)
}
@@ -138,19 +133,21 @@ function App() {
<SessionProvider>
<ShellStateProvider>
<PrefsStateProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<I18nProvider>
<PortalProvider>
<InnerApp />
</PortalProvider>
</I18nProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
<MutedThreadsProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<I18nProvider>
<PortalProvider>
<InnerApp />
</PortalProvider>
</I18nProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</MutedThreadsProvider>
</PrefsStateProvider>
</ShellStateProvider>
</SessionProvider>
+42 -29
View File
@@ -22,6 +22,7 @@ import {buildStateObject} from 'lib/routes/helpers'
import {
AllNavigatorParams,
BottomTabNavigatorParams,
FeedsTabNavigatorParams,
FlatNavigatorParams,
HomeTabNavigatorParams,
MessagesTabNavigatorParams,
@@ -41,7 +42,6 @@ import {PreferencesThreads} from 'view/screens/PreferencesThreads'
import {SavedFeeds} from 'view/screens/SavedFeeds'
import HashtagScreen from '#/screens/Hashtag'
import {ModerationScreen} from '#/screens/Moderation'
import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers'
import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
import {init as initAnalytics} from './lib/analytics/analytics'
import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration'
@@ -54,8 +54,8 @@ import {useModalControls} from './state/modals'
import {useUnreadNotifications} from './state/queries/notifications/unread'
import {useSession} from './state/session'
import {
setEmailConfirmationRequested,
shouldRequestEmailConfirmation,
snoozeEmailConfirmationPrompt,
} from './state/shell/reminders'
import {AccessibilitySettingsScreen} from './view/screens/AccessibilitySettings'
import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines'
@@ -91,6 +91,7 @@ const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
const HomeTab = createNativeStackNavigatorWithAuth<HomeTabNavigatorParams>()
const SearchTab = createNativeStackNavigatorWithAuth<SearchTabNavigatorParams>()
const FeedsTab = createNativeStackNavigatorWithAuth<FeedsTabNavigatorParams>()
const NotificationsTab =
createNativeStackNavigatorWithAuth<NotificationsTabNavigatorParams>()
const MyProfileTab =
@@ -170,13 +171,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
title: title(msg`People followed by @${route.params.name}`),
})}
/>
<Stack.Screen
name="ProfileKnownFollowers"
getComponent={() => ProfileKnownFollowersScreen}
options={({route}) => ({
title: title(msg`Followers of @${route.params.name} that you know`),
})}
/>
<Stack.Screen
name="ProfileList"
getComponent={() => ProfileListScreen}
@@ -312,7 +306,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => MessagesSettingsScreen}
options={{title: title(msg`Chat settings`), requireAuth: true}}
/>
<Stack.Screen name="Feeds" getComponent={() => FeedsScreen} />
</>
)
}
@@ -337,6 +330,7 @@ function TabsNavigator() {
tabBar={tabBar}>
<Tab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} />
<Tab.Screen name="SearchTab" getComponent={() => SearchTabNavigator} />
<Tab.Screen name="FeedsTab" getComponent={() => FeedsTabNavigator} />
<Tab.Screen
name="NotificationsTab"
getComponent={() => NotificationsTabNavigator}
@@ -359,11 +353,11 @@ function HomeTabNavigator() {
return (
<HomeTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
animation: isAndroid ? 'none' : undefined,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
}}>
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
@@ -377,11 +371,11 @@ function SearchTabNavigator() {
return (
<SearchTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
animation: isAndroid ? 'none' : undefined,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
}}>
<SearchTab.Screen name="Search" getComponent={() => SearchScreen} />
@@ -390,16 +384,34 @@ function SearchTabNavigator() {
)
}
function FeedsTabNavigator() {
const pal = usePalette('default')
return (
<FeedsTab.Navigator
screenOptions={{
animation: isAndroid ? 'none' : undefined,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
}}>
<FeedsTab.Screen name="Feeds" getComponent={() => FeedsScreen} />
{commonScreens(FeedsTab as typeof HomeTab)}
</FeedsTab.Navigator>
)
}
function NotificationsTabNavigator() {
const pal = usePalette('default')
return (
<NotificationsTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
animation: isAndroid ? 'none' : undefined,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
}}>
<NotificationsTab.Screen
@@ -417,11 +429,11 @@ function MyProfileTabNavigator() {
return (
<MyProfileTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
animation: isAndroid ? 'none' : undefined,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
}}>
<MyProfileTab.Screen
@@ -442,20 +454,17 @@ function MessagesTabNavigator() {
return (
<MessagesTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
animation: isAndroid ? 'none' : undefined,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
}}>
<MessagesTab.Screen
name="Messages"
getComponent={() => MessagesScreen}
options={({route}) => ({
requireAuth: true,
animationTypeForReplace: route.params?.animation ?? 'push',
})}
options={{requireAuth: true}}
/>
{commonScreens(MessagesTab as typeof HomeTab)}
</MessagesTab.Navigator>
@@ -476,11 +485,10 @@ const FlatNavigator = () => {
<Flat.Navigator
screenListeners={screenListeners}
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
}}>
<Flat.Screen
@@ -493,6 +501,11 @@ const FlatNavigator = () => {
getComponent={() => SearchScreen}
options={{title: title(msg`Search`)}}
/>
<Flat.Screen
name="Feeds"
getComponent={() => FeedsScreen}
options={{title: title(msg`Feeds`)}}
/>
<Flat.Screen
name="Notifications"
getComponent={() => NotificationsScreen}
@@ -585,7 +598,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
if (currentAccount && shouldRequestEmailConfirmation(currentAccount)) {
openModal({name: 'verify-email', showReminder: true})
snoozeEmailConfirmationPrompt()
setEmailConfirmationRequested()
}
}
@@ -595,7 +608,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
linking={LINKING}
theme={theme}
onStateChange={() => {
logEvent('router:navigate:sampled', {
logEvent('router:navigate', {
from: prevLoggedRouteName.current,
})
prevLoggedRouteName.current = getCurrentRouteName()
@@ -604,7 +617,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
attachRouteToLogEvents(getCurrentRouteName)
logModuleInitTime()
onReady()
logEvent('router:navigate:sampled', {})
logEvent('router:navigate', {})
}}>
{children}
</NavigationContainer>
+7 -27
View File
@@ -1,8 +1,7 @@
import {Platform, StyleSheet} from 'react-native'
import {Platform} from 'react-native'
import * as tokens from '#/alf/tokens'
import {native, web} from '#/alf/util/platform'
import hairlineWidth = StyleSheet.hairlineWidth
export const atoms = {
/*
@@ -56,13 +55,6 @@ export const atoms = {
height: '100vh',
}),
/*
* Theme-independent bg colors
*/
bg_transparent: {
backgroundColor: 'transparent',
},
/*
* Border radius
*/
@@ -267,9 +259,6 @@ export const atoms = {
font_bold: {
fontWeight: tokens.fontWeight.bold,
},
font_heavy: {
fontWeight: tokens.fontWeight.heavy,
},
italic: {
fontStyle: 'italic',
},
@@ -281,19 +270,19 @@ export const atoms = {
borderWidth: 0,
},
border: {
borderWidth: hairlineWidth,
borderWidth: 1,
},
border_t: {
borderTopWidth: hairlineWidth,
borderTopWidth: 1,
},
border_b: {
borderBottomWidth: hairlineWidth,
borderBottomWidth: 1,
},
border_l: {
borderLeftWidth: hairlineWidth,
borderLeftWidth: 1,
},
border_r: {
borderRightWidth: hairlineWidth,
borderRightWidth: 1,
},
/*
@@ -852,7 +841,7 @@ export const atoms = {
marginRight: 'auto',
},
/*
* Pointer events & user select
* Pointer events
*/
pointer_events_none: {
pointerEvents: 'none',
@@ -860,15 +849,6 @@ export const atoms = {
pointer_events_auto: {
pointerEvents: 'auto',
},
user_select_none: {
userSelect: 'none',
},
user_select_text: {
userSelect: 'text',
},
user_select_all: {
userSelect: 'all',
},
/*
* Text decoration
*/
+3 -5
View File
@@ -1,14 +1,12 @@
import React from 'react'
import {Dimensions} from 'react-native'
import * as themes from '#/alf/themes'
export {atoms} from '#/alf/atoms'
export * as tokens from '#/alf/tokens'
export * from '#/alf/types'
export * from '#/alf/util/flatten'
export * as tokens from '#/alf/tokens'
export {atoms} from '#/alf/atoms'
export * from '#/alf/util/platform'
export * from '#/alf/util/themeSelector'
export * from '#/alf/util/flatten'
type BreakpointName = keyof typeof breakpoints
+5 -5
View File
@@ -1,11 +1,11 @@
import {atoms} from '#/alf/atoms'
import * as tokens from '#/alf/tokens'
import type {Mutable} from '#/alf/types'
import {atoms} from '#/alf/atoms'
import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration'
export type ThemeName = 'light' | 'dim' | 'dark'
export type ReadonlyTheme = typeof light
export type Theme = Mutable<ReadonlyTheme> & {name: ThemeName}
export type Theme = Mutable<ReadonlyTheme>
export type ReadonlyPalette = typeof lightPalette
export type Palette = Mutable<ReadonlyPalette>
@@ -193,7 +193,7 @@ export const dimPalette: Palette = {
} as const
export const light = {
name: 'light' as ThemeName,
name: 'light',
palette: lightPalette,
atoms: {
text: {
@@ -278,7 +278,7 @@ export const light = {
}
export const dark: Theme = {
name: 'dark' as ThemeName,
name: 'dark',
palette: darkPalette,
atoms: {
text: {
@@ -367,7 +367,7 @@ export const dark: Theme = {
export const dim: Theme = {
...dark,
name: 'dim' as ThemeName,
name: 'dim',
palette: dimPalette,
atoms: {
...dark.atoms,
-1
View File
@@ -118,7 +118,6 @@ export const fontWeight = {
normal: '400',
semibold: '500',
bold: '600',
heavy: '700',
} as const
export const gradients = {
-14
View File
@@ -1,14 +0,0 @@
import {ThemeName} from '#/alf/themes'
export function select<T>(name: ThemeName, options: Record<ThemeName, T>) {
switch (name) {
case 'light':
return options.light
case 'dark':
return options.dark || options.dim
case 'dim':
return options.dim || options.dark
default:
throw new Error(`select(theme, options) received unknown theme ${name}`)
}
}
+15 -17
View File
@@ -1,27 +1,25 @@
import React from 'react'
import {ColorSchemeName, useColorScheme} from 'react-native'
import {useThemePrefs} from 'state/shell'
import {isWeb} from 'platform/detection'
import {ThemeName, light, dark, dim} from '#/alf/themes'
import * as SystemUI from 'expo-system-ui'
import {isWeb} from 'platform/detection'
import {useThemePrefs} from 'state/shell'
import {dark, dim, light, ThemeName} from '#/alf/themes'
export function useColorModeTheme(): ThemeName {
const theme = useThemeName()
React.useLayoutEffect(() => {
updateDocument(theme)
SystemUI.setBackgroundColorAsync(getBackgroundColor(theme))
}, [theme])
return theme
}
export function useThemeName(): ThemeName {
const colorScheme = useColorScheme()
const {colorMode, darkTheme} = useThemePrefs()
return getThemeName(colorScheme, colorMode, darkTheme)
React.useLayoutEffect(() => {
const theme = getThemeName(colorScheme, colorMode, darkTheme)
updateDocument(theme)
SystemUI.setBackgroundColorAsync(getBackgroundColor(theme))
}, [colorMode, colorScheme, darkTheme])
return React.useMemo(
() => getThemeName(colorScheme, colorMode, darkTheme),
[colorScheme, colorMode, darkTheme],
)
}
function getThemeName(
@@ -55,7 +53,7 @@ function updateDocument(theme: ThemeName) {
}
}
export function getBackgroundColor(theme: ThemeName): string {
function getBackgroundColor(theme: ThemeName): string {
switch (theme) {
case 'light':
return light.atoms.bg.backgroundColor
+2 -2
View File
@@ -37,7 +37,7 @@ export function AccountList({
style={[
a.rounded_md,
a.overflow_hidden,
{borderWidth: 1},
a.border,
t.atoms.border_contrast_low,
]}>
{accounts.map(account => (
@@ -48,7 +48,7 @@ export function AccountList({
isCurrentAccount={account.did === currentAccount?.did}
isPendingAccount={account.did === pendingDid}
/>
<View style={[{borderBottomWidth: 1}, t.atoms.border_contrast_low]} />
<View style={[a.border_b, t.atoms.border_contrast_low]} />
</React.Fragment>
))}
<Button
+301 -328
View File
@@ -28,7 +28,7 @@ export type ButtonColor =
| 'gradient_sunset'
| 'gradient_nordic'
| 'gradient_bonfire'
export type ButtonSize = 'tiny' | 'xsmall' | 'small' | 'medium' | 'large'
export type ButtonSize = 'tiny' | 'small' | 'medium' | 'large'
export type ButtonShape = 'round' | 'square' | 'default'
export type VariantProps = {
/**
@@ -64,14 +64,13 @@ type NonTextElements =
export type ButtonProps = Pick<
PressableProps,
'disabled' | 'onPress' | 'testID' | 'onLongPress' | 'hitSlop'
'disabled' | 'onPress' | 'testID' | 'onLongPress'
> &
AccessibilityProps &
VariantProps & {
testID?: string
label: string
style?: StyleProp<ViewStyle>
hoverStyle?: StyleProp<ViewStyle>
children: NonTextElements | ((context: ButtonContext) => NonTextElements)
}
@@ -88,355 +87,329 @@ export function useButtonContext() {
return React.useContext(Context)
}
export const Button = React.forwardRef<View, ButtonProps>(
(
{
children,
variant,
color,
size,
shape = 'default',
label,
disabled = false,
style,
hoverStyle: hoverStyleProp,
...rest
},
ref,
) => {
const t = useTheme()
const [state, setState] = React.useState({
export function Button({
children,
variant,
color,
size,
shape = 'default',
label,
disabled = false,
style,
...rest
}: ButtonProps) {
const t = useTheme()
const [state, setState] = React.useState({
pressed: false,
hovered: false,
focused: false,
})
const onPressIn = React.useCallback(() => {
setState(s => ({
...s,
pressed: true,
}))
}, [setState])
const onPressOut = React.useCallback(() => {
setState(s => ({
...s,
pressed: false,
}))
}, [setState])
const onHoverIn = React.useCallback(() => {
setState(s => ({
...s,
hovered: true,
}))
}, [setState])
const onHoverOut = React.useCallback(() => {
setState(s => ({
...s,
hovered: false,
}))
}, [setState])
const onFocus = React.useCallback(() => {
setState(s => ({
...s,
focused: true,
}))
}, [setState])
const onBlur = React.useCallback(() => {
setState(s => ({
...s,
focused: false,
})
}))
}, [setState])
const onPressIn = React.useCallback(() => {
setState(s => ({
...s,
pressed: true,
}))
}, [setState])
const onPressOut = React.useCallback(() => {
setState(s => ({
...s,
pressed: false,
}))
}, [setState])
const onHoverIn = React.useCallback(() => {
setState(s => ({
...s,
hovered: true,
}))
}, [setState])
const onHoverOut = React.useCallback(() => {
setState(s => ({
...s,
hovered: false,
}))
}, [setState])
const onFocus = React.useCallback(() => {
setState(s => ({
...s,
focused: true,
}))
}, [setState])
const onBlur = React.useCallback(() => {
setState(s => ({
...s,
focused: false,
}))
}, [setState])
const {baseStyles, hoverStyles} = React.useMemo(() => {
const baseStyles: ViewStyle[] = []
const hoverStyles: ViewStyle[] = []
const light = t.name === 'light'
const {baseStyles, hoverStyles} = React.useMemo(() => {
const baseStyles: ViewStyle[] = []
const hoverStyles: ViewStyle[] = []
const light = t.name === 'light'
if (color === 'primary') {
if (variant === 'solid') {
if (!disabled) {
baseStyles.push({
backgroundColor: t.palette.primary_500,
})
hoverStyles.push({
backgroundColor: t.palette.primary_600,
})
} else {
baseStyles.push({
backgroundColor: t.palette.primary_700,
})
}
} else if (variant === 'outline') {
baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
if (color === 'primary') {
if (variant === 'solid') {
if (!disabled) {
baseStyles.push({
backgroundColor: t.palette.primary_500,
})
hoverStyles.push({
backgroundColor: t.palette.primary_600,
})
} else {
baseStyles.push({
backgroundColor: t.palette.primary_700,
})
if (!disabled) {
baseStyles.push(a.border, {
borderColor: t.palette.primary_500,
})
hoverStyles.push(a.border, {
backgroundColor: light
? t.palette.primary_50
: t.palette.primary_950,
})
} else {
baseStyles.push(a.border, {
borderColor: light
? t.palette.primary_200
: t.palette.primary_900,
})
}
} else if (variant === 'ghost') {
if (!disabled) {
baseStyles.push(t.atoms.bg)
hoverStyles.push({
backgroundColor: light
? t.palette.primary_100
: t.palette.primary_900,
})
}
}
} else if (color === 'secondary') {
if (variant === 'solid') {
if (!disabled) {
baseStyles.push({
backgroundColor: t.palette.contrast_25,
})
hoverStyles.push({
backgroundColor: t.palette.contrast_50,
})
} else {
baseStyles.push({
backgroundColor: t.palette.contrast_100,
})
}
} else if (variant === 'outline') {
baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
})
} else if (variant === 'outline') {
baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
})
if (!disabled) {
baseStyles.push(a.border, {
borderColor: t.palette.contrast_300,
})
hoverStyles.push(t.atoms.bg_contrast_50)
} else {
baseStyles.push(a.border, {
borderColor: t.palette.contrast_200,
})
}
} else if (variant === 'ghost') {
if (!disabled) {
baseStyles.push(t.atoms.bg)
hoverStyles.push({
backgroundColor: t.palette.contrast_100,
})
}
if (!disabled) {
baseStyles.push(a.border, {
borderColor: t.palette.primary_500,
})
hoverStyles.push(a.border, {
backgroundColor: light
? t.palette.primary_50
: t.palette.primary_950,
})
} else {
baseStyles.push(a.border, {
borderColor: light ? t.palette.primary_200 : t.palette.primary_900,
})
}
} else if (color === 'negative') {
if (variant === 'solid') {
if (!disabled) {
baseStyles.push({
backgroundColor: t.palette.negative_500,
})
hoverStyles.push({
backgroundColor: t.palette.negative_600,
})
} else {
baseStyles.push({
backgroundColor: t.palette.negative_700,
})
}
} else if (variant === 'outline') {
baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
} else if (variant === 'ghost') {
if (!disabled) {
baseStyles.push(t.atoms.bg)
hoverStyles.push({
backgroundColor: light
? t.palette.primary_100
: t.palette.primary_900,
})
if (!disabled) {
baseStyles.push(a.border, {
borderColor: t.palette.negative_500,
})
hoverStyles.push(a.border, {
backgroundColor: light
? t.palette.negative_50
: t.palette.negative_975,
})
} else {
baseStyles.push(a.border, {
borderColor: light
? t.palette.negative_200
: t.palette.negative_900,
})
}
} else if (variant === 'ghost') {
if (!disabled) {
baseStyles.push(t.atoms.bg)
hoverStyles.push({
backgroundColor: light
? t.palette.negative_100
: t.palette.negative_975,
})
}
}
}
if (shape === 'default') {
if (size === 'large') {
baseStyles.push(
{paddingVertical: 15},
a.px_2xl,
a.rounded_sm,
a.gap_md,
)
} else if (size === 'medium') {
baseStyles.push(
{paddingVertical: 12},
a.px_2xl,
a.rounded_sm,
a.gap_md,
)
} else if (size === 'small') {
baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm)
} else if (size === 'xsmall') {
baseStyles.push({paddingVertical: 6}, a.px_sm, a.rounded_sm, a.gap_sm)
} else if (size === 'tiny') {
baseStyles.push({paddingVertical: 4}, a.px_sm, a.rounded_xs, a.gap_xs)
}
} else if (shape === 'round' || shape === 'square') {
if (size === 'large') {
if (shape === 'round') {
baseStyles.push({height: 54, width: 54})
} else {
baseStyles.push({height: 50, width: 50})
}
} else if (size === 'small') {
baseStyles.push({height: 34, width: 34})
} else if (size === 'xsmall') {
baseStyles.push({height: 28, width: 28})
} else if (size === 'tiny') {
baseStyles.push({height: 20, width: 20})
} else if (color === 'secondary') {
if (variant === 'solid') {
if (!disabled) {
baseStyles.push({
backgroundColor: t.palette.contrast_25,
})
hoverStyles.push({
backgroundColor: t.palette.contrast_50,
})
} else {
baseStyles.push({
backgroundColor: t.palette.contrast_100,
})
}
} else if (variant === 'outline') {
baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
})
if (!disabled) {
baseStyles.push(a.border, {
borderColor: t.palette.contrast_300,
})
hoverStyles.push(t.atoms.bg_contrast_50)
} else {
baseStyles.push(a.border, {
borderColor: t.palette.contrast_200,
})
}
} else if (variant === 'ghost') {
if (!disabled) {
baseStyles.push(t.atoms.bg)
hoverStyles.push({
backgroundColor: t.palette.contrast_100,
})
}
}
} else if (color === 'negative') {
if (variant === 'solid') {
if (!disabled) {
baseStyles.push({
backgroundColor: t.palette.negative_500,
})
hoverStyles.push({
backgroundColor: t.palette.negative_600,
})
} else {
baseStyles.push({
backgroundColor: t.palette.negative_700,
})
}
} else if (variant === 'outline') {
baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
})
if (!disabled) {
baseStyles.push(a.border, {
borderColor: t.palette.negative_500,
})
hoverStyles.push(a.border, {
backgroundColor: light
? t.palette.negative_50
: t.palette.negative_975,
})
} else {
baseStyles.push(a.border, {
borderColor: light
? t.palette.negative_200
: t.palette.negative_900,
})
}
} else if (variant === 'ghost') {
if (!disabled) {
baseStyles.push(t.atoms.bg)
hoverStyles.push({
backgroundColor: light
? t.palette.negative_100
: t.palette.negative_975,
})
}
}
}
if (shape === 'default') {
if (size === 'large') {
baseStyles.push({paddingVertical: 15}, a.px_2xl, a.rounded_sm, a.gap_md)
} else if (size === 'medium') {
baseStyles.push({paddingVertical: 12}, a.px_2xl, a.rounded_sm, a.gap_md)
} else if (size === 'small') {
baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm)
} else if (size === 'tiny') {
baseStyles.push({paddingVertical: 4}, a.px_sm, a.rounded_xs, a.gap_xs)
}
} else if (shape === 'round' || shape === 'square') {
if (size === 'large') {
if (shape === 'round') {
baseStyles.push(a.rounded_full)
} else if (shape === 'square') {
if (size === 'tiny') {
baseStyles.push(a.rounded_xs)
} else {
baseStyles.push(a.rounded_sm)
}
baseStyles.push({height: 54, width: 54})
} else {
baseStyles.push({height: 50, width: 50})
}
} else if (size === 'small') {
baseStyles.push({height: 34, width: 34})
} else if (size === 'tiny') {
baseStyles.push({height: 20, width: 20})
}
if (shape === 'round') {
baseStyles.push(a.rounded_full)
} else if (shape === 'square') {
if (size === 'tiny') {
baseStyles.push(a.rounded_xs)
} else {
baseStyles.push(a.rounded_sm)
}
}
}
return {
baseStyles,
hoverStyles,
}
}, [t, variant, color, size, shape, disabled])
const {gradientColors, gradientHoverColors, gradientLocations} =
React.useMemo(() => {
const colors: string[] = []
const hoverColors: string[] = []
const locations: number[] = []
const gradient = {
primary: tokens.gradients.sky,
secondary: tokens.gradients.sky,
negative: tokens.gradients.sky,
gradient_sky: tokens.gradients.sky,
gradient_midnight: tokens.gradients.midnight,
gradient_sunrise: tokens.gradients.sunrise,
gradient_sunset: tokens.gradients.sunset,
gradient_nordic: tokens.gradients.nordic,
gradient_bonfire: tokens.gradients.bonfire,
}[color || 'primary']
if (variant === 'gradient') {
colors.push(...gradient.values.map(([_, color]) => color))
hoverColors.push(...gradient.values.map(_ => gradient.hover_value))
locations.push(...gradient.values.map(([location, _]) => location))
}
return {
baseStyles,
hoverStyles,
gradientColors: colors,
gradientHoverColors: hoverColors,
gradientLocations: locations,
}
}, [t, variant, color, size, shape, disabled])
}, [variant, color])
const {gradientColors, gradientHoverColors, gradientLocations} =
React.useMemo(() => {
const colors: string[] = []
const hoverColors: string[] = []
const locations: number[] = []
const gradient = {
primary: tokens.gradients.sky,
secondary: tokens.gradients.sky,
negative: tokens.gradients.sky,
gradient_sky: tokens.gradients.sky,
gradient_midnight: tokens.gradients.midnight,
gradient_sunrise: tokens.gradients.sunrise,
gradient_sunset: tokens.gradients.sunset,
gradient_nordic: tokens.gradients.nordic,
gradient_bonfire: tokens.gradients.bonfire,
}[color || 'primary']
const context = React.useMemo<ButtonContext>(
() => ({
...state,
variant,
color,
size,
disabled: disabled || false,
}),
[state, variant, color, size, disabled],
)
if (variant === 'gradient') {
colors.push(...gradient.values.map(([_, color]) => color))
hoverColors.push(...gradient.values.map(_ => gradient.hover_value))
locations.push(...gradient.values.map(([location, _]) => location))
}
const flattenedBaseStyles = flatten(baseStyles)
return {
gradientColors: colors,
gradientHoverColors: hoverColors,
gradientLocations: locations,
}
}, [variant, color])
const context = React.useMemo<ButtonContext>(
() => ({
...state,
variant,
color,
size,
return (
<Pressable
role="button"
accessibilityHint={undefined} // optional
{...rest}
aria-label={label}
aria-pressed={state.pressed}
accessibilityLabel={label}
disabled={disabled || false}
accessibilityState={{
disabled: disabled || false,
}),
[state, variant, color, size, disabled],
)
const flattenedBaseStyles = flatten(baseStyles)
return (
<Pressable
role="button"
accessibilityHint={undefined} // optional
{...rest}
ref={ref}
aria-label={label}
aria-pressed={state.pressed}
accessibilityLabel={label}
disabled={disabled || false}
accessibilityState={{
disabled: disabled || false,
}}
style={[
a.flex_row,
a.align_center,
a.justify_center,
flattenedBaseStyles,
flatten(style),
...(state.hovered || state.pressed
? [hoverStyles, flatten(hoverStyleProp)]
: []),
]}
onPressIn={onPressIn}
onPressOut={onPressOut}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}
onFocus={onFocus}
onBlur={onBlur}>
{variant === 'gradient' && (
<View
style={[
a.absolute,
a.inset_0,
a.overflow_hidden,
{borderRadius: flattenedBaseStyles.borderRadius},
]}>
<LinearGradient
colors={
state.hovered || state.pressed
? gradientHoverColors
: gradientColors
}
locations={gradientLocations}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[a.absolute, a.inset_0]}
/>
</View>
)}
<Context.Provider value={context}>
{typeof children === 'function' ? children(context) : children}
</Context.Provider>
</Pressable>
)
},
)
Button.displayName = 'Button'
}}
style={[
a.flex_row,
a.align_center,
a.justify_center,
flattenedBaseStyles,
...(state.hovered || state.pressed ? hoverStyles : []),
flatten(style),
]}
onPressIn={onPressIn}
onPressOut={onPressOut}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}
onFocus={onFocus}
onBlur={onBlur}>
{variant === 'gradient' && (
<View
style={[
a.absolute,
a.inset_0,
a.overflow_hidden,
{borderRadius: flattenedBaseStyles.borderRadius},
]}>
<LinearGradient
colors={
state.hovered || state.pressed
? gradientHoverColors
: gradientColors
}
locations={gradientLocations}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[a.absolute, a.inset_0]}
/>
</View>
)}
<Context.Provider value={context}>
{typeof children === 'function' ? children(context) : children}
</Context.Provider>
</Pressable>
)
}
export function useSharedButtonTextStyles() {
const t = useTheme()
+42 -54
View File
@@ -1,12 +1,5 @@
import React, {useImperativeHandle} from 'react'
import {
Dimensions,
Keyboard,
Pressable,
StyleProp,
View,
ViewStyle,
} from 'react-native'
import {Dimensions, Pressable, View} from 'react-native'
import Animated, {useAnimatedStyle} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import BottomSheet, {
@@ -32,7 +25,6 @@ import {
DialogOuterProps,
} from '#/components/Dialog/types'
import {createInput} from '#/components/forms/TextField'
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
import {Portal} from '#/components/Portal'
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
@@ -171,49 +163,46 @@ export function Outer({
return (
isOpen && (
<Portal>
<FullWindowOverlay>
<View
// iOS
accessibilityViewIsModal
// Android
importantForAccessibility="yes"
style={[a.absolute, a.inset_0]}
testID={testID}
onTouchMove={() => Keyboard.dismiss()}>
<BottomSheet
enableDynamicSizing={!hasSnapPoints}
enablePanDownToClose
keyboardBehavior="interactive"
android_keyboardInputMode="adjustResize"
keyboardBlurBehavior="restore"
topInset={insets.top}
{...sheetOptions}
snapPoints={sheetOptions.snapPoints || ['100%']}
ref={sheet}
index={openIndex}
backgroundStyle={{backgroundColor: 'transparent'}}
backdropComponent={Backdrop}
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
handleStyle={{display: 'none'}}
onClose={onCloseAnimationComplete}>
<Context.Provider value={context}>
<View
style={[
a.absolute,
a.inset_0,
t.atoms.bg,
{
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
height: Dimensions.get('window').height * 2,
},
]}
/>
{children}
</Context.Provider>
</BottomSheet>
</View>
</FullWindowOverlay>
<View
// iOS
accessibilityViewIsModal
// Android
importantForAccessibility="yes"
style={[a.absolute, a.inset_0]}
testID={testID}>
<BottomSheet
enableDynamicSizing={!hasSnapPoints}
enablePanDownToClose
keyboardBehavior="interactive"
android_keyboardInputMode="adjustResize"
keyboardBlurBehavior="restore"
topInset={insets.top}
{...sheetOptions}
snapPoints={sheetOptions.snapPoints || ['100%']}
ref={sheet}
index={openIndex}
backgroundStyle={{backgroundColor: 'transparent'}}
backdropComponent={Backdrop}
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
handleStyle={{display: 'none'}}
onClose={onCloseAnimationComplete}>
<Context.Provider value={context}>
<View
style={[
a.absolute,
a.inset_0,
t.atoms.bg,
{
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
height: Dimensions.get('window').height * 2,
},
]}
/>
{children}
</Context.Provider>
</BottomSheet>
</View>
</Portal>
)
)
@@ -268,10 +257,9 @@ export const ScrollableInner = React.forwardRef<
export const InnerFlatList = React.forwardRef<
BottomSheetFlatListMethods,
BottomSheetFlatListProps<any> & {webInnerStyle?: StyleProp<ViewStyle>}
BottomSheetFlatListProps<any>
>(function InnerFlatList({style, contentContainerStyle, ...props}, ref) {
const insets = useSafeAreaInsets()
return (
<BottomSheetFlatList
keyboardShouldPersistTaps="handled"
+8 -13
View File
@@ -2,10 +2,8 @@ import React, {useImperativeHandle} from 'react'
import {
FlatList,
FlatListProps,
StyleProp,
TouchableWithoutFeedback,
View,
ViewStyle,
} from 'react-native'
import Animated, {FadeIn, FadeInDown} from 'react-native-reanimated'
import {msg} from '@lingui/macro'
@@ -201,21 +199,18 @@ export const ScrollableInner = Inner
export const InnerFlatList = React.forwardRef<
FlatList,
FlatListProps<any> & {label: string} & {webInnerStyle?: StyleProp<ViewStyle>}
>(function InnerFlatList({label, style, webInnerStyle, ...props}, ref) {
FlatListProps<any> & {label: string}
>(function InnerFlatList({label, style, ...props}, ref) {
const {gtMobile} = useBreakpoints()
return (
<Inner
label={label}
style={[
// @ts-ignore web only -sfn
{
paddingHorizontal: 0,
maxHeight: 'calc(-36px + 100vh)',
overflow: 'hidden',
},
webInnerStyle,
]}>
// @ts-ignore web only -sfn
style={{
paddingHorizontal: 0,
maxHeight: 'calc(-36px + 100vh)',
overflow: 'hidden',
}}>
<FlatList
ref={ref}
style={[gtMobile ? a.px_2xl : a.px_xl, flatten(style)]}
-205
View File
@@ -1,205 +0,0 @@
import React from 'react'
import {GestureResponderEvent, View} from 'react-native'
import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api'
import {msg, plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {
useAddSavedFeedsMutation,
usePreferencesQuery,
useRemoveFeedMutation,
} from '#/state/queries/preferences'
import {sanitizeHandle} from 'lib/strings/handles'
import {useSession} from 'state/session'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import * as Toast from 'view/com/util/Toast'
import {useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {useRichText} from '#/components/hooks/useRichText'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
import {Link as InternalLink} from '#/components/Link'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
export function Default({feed}: {feed: AppBskyFeedDefs.GeneratorView}) {
return (
<Link feed={feed}>
<Outer>
<Header>
<Avatar src={feed.avatar} />
<TitleAndByline title={feed.displayName} creator={feed.creator} />
<Action uri={feed.uri} pin />
</Header>
<Description description={feed.description} />
<Likes count={feed.likeCount || 0} />
</Outer>
</Link>
)
}
export function Link({
children,
feed,
}: {
children: React.ReactElement
feed: AppBskyFeedDefs.GeneratorView
}) {
const href = React.useMemo(() => {
const urip = new AtUri(feed.uri)
const handleOrDid = feed.creator.handle || feed.creator.did
return `/profile/${handleOrDid}/feed/${urip.rkey}`
}, [feed])
return <InternalLink to={href}>{children}</InternalLink>
}
export function Outer({children}: {children: React.ReactNode}) {
return <View style={[a.flex_1, a.gap_md]}>{children}</View>
}
export function Header({children}: {children: React.ReactNode}) {
return <View style={[a.flex_row, a.align_center, a.gap_md]}>{children}</View>
}
export function Avatar({src}: {src: string | undefined}) {
return <UserAvatar type="algo" size={40} avatar={src} />
}
export function TitleAndByline({
title,
creator,
}: {
title: string
creator: AppBskyActorDefs.ProfileViewBasic
}) {
const t = useTheme()
return (
<View style={[a.flex_1]}>
<Text
style={[a.text_md, a.font_bold, a.flex_1, a.leading_snug]}
numberOfLines={1}>
{title}
</Text>
<Text
style={[a.flex_1, a.leading_snug, t.atoms.text_contrast_medium]}
numberOfLines={1}>
<Trans>Feed by {sanitizeHandle(creator.handle, '@')}</Trans>
</Text>
</View>
)
}
export function Description({description}: {description?: string}) {
const [rt, isResolving] = useRichText(description || '')
if (!description) return null
return isResolving ? (
<RichText value={description} style={[a.leading_snug]} />
) : (
<RichText value={rt} style={[a.leading_snug]} />
)
}
export function Likes({count}: {count: number}) {
const t = useTheme()
return (
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
{plural(count || 0, {
one: 'Liked by # user',
other: 'Liked by # users',
})}
</Text>
)
}
export function Action({uri, pin}: {uri: string; pin?: boolean}) {
const {hasSession} = useSession()
if (!hasSession) return null
return <ActionInner uri={uri} pin={pin} />
}
function ActionInner({uri, pin}: {uri: string; pin?: boolean}) {
const {_} = useLingui()
const {data: preferences} = usePreferencesQuery()
const {isPending: isAddSavedFeedPending, mutateAsync: saveFeeds} =
useAddSavedFeedsMutation()
const {isPending: isRemovePending, mutateAsync: removeFeed} =
useRemoveFeedMutation()
const savedFeedConfig = React.useMemo(() => {
return preferences?.savedFeeds?.find(
feed => feed.type === 'feed' && feed.value === uri,
)
}, [preferences?.savedFeeds, uri])
const removePromptControl = Prompt.usePromptControl()
const isPending = isAddSavedFeedPending || isRemovePending
const toggleSave = React.useCallback(
async (e: GestureResponderEvent) => {
e.preventDefault()
e.stopPropagation()
try {
if (savedFeedConfig) {
await removeFeed(savedFeedConfig)
} else {
await saveFeeds([
{
type: 'feed',
value: uri,
pinned: pin || false,
},
])
}
Toast.show(_(msg`Feeds updated!`))
} catch (e: any) {
logger.error(e, {context: `FeedCard: failed to update feeds`, pin})
Toast.show(_(msg`Failed to update feeds`))
}
},
[_, pin, saveFeeds, removeFeed, uri, savedFeedConfig],
)
const onPrompRemoveFeed = React.useCallback(
async (e: GestureResponderEvent) => {
e.preventDefault()
e.stopPropagation()
removePromptControl.open()
},
[removePromptControl],
)
return (
<>
<Button
disabled={isPending}
label={_(msg`Add this feed to your feeds`)}
size="small"
variant="ghost"
color="secondary"
shape="square"
onPress={savedFeedConfig ? onPrompRemoveFeed : toggleSave}>
{savedFeedConfig ? (
<ButtonIcon size="md" icon={isPending ? Loader : Trash} />
) : (
<ButtonIcon size="md" icon={isPending ? Loader : Plus} />
)}
</Button>
<Prompt.Basic
control={removePromptControl}
title={_(msg`Remove from my feeds?`)}
description={_(
msg`Are you sure you want to remove this from your feeds?`,
)}
onConfirm={toggleSave}
confirmButtonCta={_(msg`Remove`)}
confirmButtonColor="negative"
/>
</>
)
}
-1
View File
@@ -1 +0,0 @@
export {FullWindowOverlay} from 'react-native-screens'
-1
View File
@@ -1 +0,0 @@
export {Fragment as FullWindowOverlay} from 'react'
@@ -1,31 +0,0 @@
import React from 'react'
import {useKeyboardHandler} from 'react-native-keyboard-controller'
import Animated, {
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
export function KeyboardControllerPadding({maxHeight}: {maxHeight?: number}) {
const keyboardHeight = useSharedValue(0)
useKeyboardHandler(
{
onMove: e => {
'worklet'
if (maxHeight && e.height > maxHeight) {
keyboardHeight.value = maxHeight
} else {
keyboardHeight.value = e.height
}
},
},
[maxHeight],
)
const animatedStyle = useAnimatedStyle(() => ({
height: keyboardHeight.value,
}))
return <Animated.View style={animatedStyle} />
}
@@ -1,7 +0,0 @@
export function KeyboardControllerPadding({
maxHeight: _,
}: {
maxHeight?: number
}) {
return null
}

Some files were not shown because too many files have changed in this diff Show More