Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fde5fb01dc |
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
yarn lint-staged
|
||||
|
||||
@@ -12,4 +12,3 @@
|
||||
android
|
||||
ios
|
||||
src/locale/locales
|
||||
lib/react-compiler-runtime
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 +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 |
@@ -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: {
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": "latest",
|
||||
"project": "./bskyembed/tsconfig.json"
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
}
|
||||
@@ -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,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()}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`:
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
# OTA Deployments
|
||||
|
||||
## Overview
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
|
||||

|
||||
|
||||
- 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.json’s 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 isn’t 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 doesn’t 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
|
||||
|
||||

|
||||
|
||||
- Select the branch for the deployment you are releasing.
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 99 KiB |
@@ -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": {
|
||||
|
||||
@@ -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'),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
}))
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "react-compiler-runtime",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -118,7 +118,6 @@ export const fontWeight = {
|
||||
normal: '400',
|
||||
semibold: '500',
|
||||
bold: '600',
|
||||
heavy: '700',
|
||||
} as const
|
||||
|
||||
export const gradients = {
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)]}
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
export {FullWindowOverlay} from 'react-native-screens'
|
||||
@@ -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
|
||||
}
|
||||