Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 13ec071aab | |||
| b507b22d9e | |||
| 90455c0c6f | |||
| 846c730d49 | |||
| 490bfceded | |||
| a9322c8457 | |||
| a5b7b6f12f | |||
| 2c32e775df | |||
| cad7db56cf | |||
| 2e260be80c | |||
| 571d16e8cf | |||
| 134c7b7427 | |||
| e11f52ad96 | |||
| eb59bd3d9c | |||
| 017a26164b | |||
| 0e0110aa13 | |||
| 4625c0bda1 | |||
| 14047b78f6 | |||
| 0eabdfe2b4 | |||
| c999ff38f9 | |||
| 1045b09592 | |||
| 1e620b19cc | |||
| e2b842ad4e | |||
| bf7845339c | |||
| 45a3e6c0d5 | |||
| ce7b9dc430 | |||
| 2a453cd9ca | |||
| 4890648116 | |||
| caf18daa6a | |||
| 4c5a4e471f | |||
| 7b3916c8cc | |||
| bc42d26bc5 | |||
| cf63c2ca07 | |||
| 665a0430a3 | |||
| 4303bca860 | |||
| 342f820ec0 | |||
| df2f62e94d | |||
| 0bb0a7616e | |||
| 00596a2d2f | |||
| c3f88e0a48 | |||
| 5aadb9e41b | |||
| 7ed2a4f388 | |||
| 5253e688a0 | |||
| a0ea634349 | |||
| c16cd36b64 | |||
| e6c867a961 | |||
| fe73c13530 | |||
| a18b25d16c | |||
| c710187094 | |||
| 29de23bbae | |||
| ca6c96f509 | |||
| 4ef7a6b64c | |||
| f3bc962494 | |||
| 2fab992c04 | |||
| 1cdbfc7092 | |||
| 75ffb3d243 | |||
| d01b9ed441 | |||
| 1b9448c6eb | |||
| a2365e08b4 | |||
| a0bd804262 | |||
| 2e80fa3dac | |||
| ce2fffc88c | |||
| 6c28d7d535 | |||
| a7e0070b15 | |||
| 207527f1d5 | |||
| 36697755c7 | |||
| 1c2a7b0b5a | |||
| a6455b3de3 | |||
| a260ecd307 | |||
| 0b06a93833 | |||
| f1372abec5 | |||
| 0f96669f8c | |||
| 0edd3bd3b4 | |||
| e01b24702e | |||
| d1ec73a289 | |||
| 433fa33b27 | |||
| 4c8fd006f6 | |||
| 3f7dc9a8e5 | |||
| 0d02a076d9 | |||
| 521ec8e044 | |||
| 973538d246 | |||
| 25f8506c41 | |||
| 04dc6dc9ca | |||
| e7fe9ab553 | |||
| abbdeb4f28 | |||
| 544f7befe0 | |||
| 46ea3fdbee | |||
| 077fd843f0 | |||
| 1eb678cabd | |||
| ea718190ca | |||
| 2aa30f5279 | |||
| 7b245935ea | |||
| 55795dc262 | |||
| 1470223480 | |||
| 8ec8a64472 | |||
| 69f656f283 | |||
| 278a54850b | |||
| b6a6b1bab4 | |||
| 686f15a62b | |||
| d992b27716 | |||
| 6f44abee35 | |||
| ba002d8252 | |||
| e1e936068a | |||
| 9a7f6ad638 | |||
| 6a8dd353ee | |||
| c1e090e9e3 | |||
| 9f66b7fe67 | |||
| eb2008cd8e | |||
| f3edd6dfd2 | |||
| 91f9bc7ccb | |||
| c34c67e4df | |||
| 70dbc94766 | |||
| 118d385b10 | |||
| 54e5ea0aff | |||
| 555dd6a4db | |||
| 1fe94ba290 | |||
| e27134c7f1 | |||
| b1550f9c02 | |||
| dba0bd8379 | |||
| a770f5635b | |||
| 6e80b340c8 | |||
| 0a7734f9ed | |||
| 8a602bf9f6 | |||
| 3f47c8cf06 | |||
| aa3ee8db4e |
@@ -66,3 +66,4 @@ jobs:
|
||||
SENTRY_DIST=${{ steps.vars.outputs.SENTRY_DIST }}
|
||||
SENTRY_RELEASE=${{ steps.vars.outputs.SENTRY_RELEASE }}
|
||||
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN=${{ secrets.SENTRY_DSN }}
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
echo "SENTRY_RELEASE=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 🏗️ EAS Build
|
||||
run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} yarn use-build-number-with-bump eas build -p android --profile ${{ inputs.profile || 'testflight-android' }} --local --output build.aab --non-interactive
|
||||
run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} yarn use-build-number-with-bump eas build -p android --profile ${{ inputs.profile || 'testflight-android' }} --local --output build.aab --non-interactive
|
||||
|
||||
- name: ✍️ Rename Testflight bundle
|
||||
if: ${{ inputs.profile != 'production' }}
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
|
||||
- name: 🏗️ Build Production APK
|
||||
if: ${{ inputs.profile == 'production' }}
|
||||
run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} yarn use-build-number-with-bump eas build -p android --profile production-apk --local --output build.apk --non-interactive
|
||||
run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} 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
|
||||
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
echo "SENTRY_RELEASE=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 🏗️ EAS Build
|
||||
run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} yarn use-build-number-with-bump eas build -p ios --profile ${{ inputs.profile || 'testflight' }} --local --output build.ipa --non-interactive
|
||||
run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} yarn use-build-number-with-bump eas build -p ios --profile ${{ inputs.profile || 'testflight' }} --local --output build.ipa --non-interactive
|
||||
|
||||
- name: 🚀 Deploy
|
||||
run: eas submit -p ios --non-interactive --path build.ipa
|
||||
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
|
||||
- name: 🏗️ Create Bundle
|
||||
if: ${{ !steps.fingerprint.outputs.includes-changes }}
|
||||
run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export
|
||||
run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export
|
||||
|
||||
- name: 📦 Package Bundle and 🚀 Deploy
|
||||
if: ${{ !steps.fingerprint.outputs.includes-changes }}
|
||||
|
||||
@@ -39,6 +39,8 @@ ENV SENTRY_RELEASE=$SENTRY_RELEASE
|
||||
ARG SENTRY_DIST
|
||||
# Default to RENDER_GIT_COMMIT if not set by GitHub workflows
|
||||
ENV SENTRY_DIST=${SENTRY_DIST:-$RENDER_GIT_COMMIT}
|
||||
ARG SENTRY_DSN
|
||||
ENV SENTRY_DSN=$SENTRY_DSN
|
||||
|
||||
#
|
||||
# Copy everything into the container
|
||||
@@ -64,7 +66,7 @@ RUN \. "$NVM_DIR/nvm.sh" && \
|
||||
yarn && \
|
||||
yarn intl:build 2>&1 | tee i18n.log && \
|
||||
if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compile errors!\n\n"; fi && \
|
||||
EXPO_PUBLIC_BUNDLE_IDENTIFIER=$EXPO_PUBLIC_BUNDLE_IDENTIFIER EXPO_PUBLIC_BUNDLE_DATE=$() SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$SENTRY_RELEASE SENTRY_DIST=$SENTRY_DIST yarn build-web
|
||||
EXPO_PUBLIC_BUNDLE_IDENTIFIER=$EXPO_PUBLIC_BUNDLE_IDENTIFIER EXPO_PUBLIC_BUNDLE_DATE=$() SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$SENTRY_RELEASE SENTRY_DIST=$SENTRY_DIST SENTRY_DSN=$SENTRY_DSN yarn build-web
|
||||
|
||||
# DEBUG
|
||||
RUN find ./bskyweb/static && find ./web-build/static
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2023–2025 Bluesky PBC
|
||||
Copyright 2023–2025 Bluesky Social PBC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ The Bluesky Social application encompasses a set of schemas and APIs built in th
|
||||
- Open an issue and give some time for discussion before submitting a PR.
|
||||
- Stay away from PRs like...
|
||||
- Changing "Post" to "Skeet."
|
||||
- Refactoring the codebase, e.g., to replace MobX with Redux or something.
|
||||
- Refactoring the codebase, e.g., to replace React Query with Redux Toolkit or something.
|
||||
- Adding entirely new features without prior discussion.
|
||||
|
||||
Remember, we serve a wide community of users. Our day-to-day involves us constantly asking "which top priority is our top priority." If you submit well-written PRs that solve problems concisely, that's an awesome contribution. Otherwise, as much as we'd love to accept your ideas and contributions, we really don't have the bandwidth. That's what forking is for!
|
||||
|
||||
@@ -98,20 +98,13 @@ appId: xyz.blueskyweb.app
|
||||
- tapOn: "People"
|
||||
|
||||
- tapOn: "Start adding people"
|
||||
- assertVisible:
|
||||
id: "listAddUserModal"
|
||||
- tapOn:
|
||||
id: "searchInput"
|
||||
- tapOn: "Search"
|
||||
- inputText: "b"
|
||||
- pressKey: Enter
|
||||
- tapOn:
|
||||
id: "user-bob.test-addBtn"
|
||||
- tapOn:
|
||||
id: "doneBtn"
|
||||
- assertNotVisible:
|
||||
id: "listAddUserModal"
|
||||
- assertVisible:
|
||||
id: "user-bob.test"
|
||||
- tapOn: "Add user to list"
|
||||
- swipe:
|
||||
direction: DOWN
|
||||
- assertVisible: "View Bob's profile"
|
||||
|
||||
- tapOn: "Posts"
|
||||
- assertVisible:
|
||||
@@ -142,23 +135,16 @@ appId: xyz.blueskyweb.app
|
||||
id: "e2eGotoLists"
|
||||
- tapOn: "Good Ppl"
|
||||
|
||||
- tapOn: "People"
|
||||
- assertVisible:
|
||||
label: "Removes users on curatelists from the list"
|
||||
id: "user-bob.test"
|
||||
# - tapOn: "People"
|
||||
- assertVisible: "View Bob's profile"
|
||||
- tapOn:
|
||||
point: "90%,43%"
|
||||
- assertVisible:
|
||||
id: "userAddRemoveListsModal"
|
||||
- tapOn:
|
||||
id: "user-bob.test-addBtn"
|
||||
- tapOn:
|
||||
id: "doneBtn"
|
||||
- assertNotVisible:
|
||||
id: "userAddRemoveListsModal"
|
||||
|
||||
- tapOn:
|
||||
label: "Adds and removes users on curatelists from the profile"
|
||||
id: "bottomBarSearchBtn"
|
||||
- tapOn: "Search for posts, users, or feeds"
|
||||
- inputText: "bob"
|
||||
|
||||
@@ -12,10 +12,11 @@ appId: xyz.blueskyweb.app
|
||||
id: "e2eStartOnboarding"
|
||||
- tapOn: "Select an avatar"
|
||||
- waitForAnimationToEnd
|
||||
- assertVisible: "Search your library…"
|
||||
- tapOn:
|
||||
point: "50%,22%"
|
||||
- waitForAnimationToEnd
|
||||
- tapOn: "Choose"
|
||||
- tapOn: "Done"
|
||||
- waitForAnimationToEnd
|
||||
- tapOn: "Continue to next step"
|
||||
- assertVisible: "What are your interests?"
|
||||
|
||||
@@ -78,13 +78,13 @@ appId: xyz.blueskyweb.app
|
||||
id: "changeBannerBtn"
|
||||
- tapOn: "Upload from Library"
|
||||
- waitForAnimationToEnd
|
||||
- tapOn: "Choose"
|
||||
- tapOn: "Done"
|
||||
- waitForAnimationToEnd
|
||||
- tapOn:
|
||||
id: "changeAvatarBtn"
|
||||
- tapOn: "Upload from Library"
|
||||
- waitForAnimationToEnd
|
||||
- tapOn: "Choose"
|
||||
- tapOn: "Done"
|
||||
- waitForAnimationToEnd
|
||||
- tapOn:
|
||||
id: "editProfileSaveBtn"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const CID = jest.fn().mockImplementation(() => {
|
||||
return {}
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
export const from = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
digest: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve('')
|
||||
}),
|
||||
}
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
export const openPicker = jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({uri: ''}))
|
||||
export const openCamera = jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({uri: ''}))
|
||||
export const openCropper = jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({uri: ''}))
|
||||
@@ -1,11 +1,11 @@
|
||||
import {RichText} from '@atproto/api'
|
||||
|
||||
import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player'
|
||||
import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
|
||||
import {
|
||||
createStarterPackGooglePlayUri,
|
||||
createStarterPackLinkFromAndroidReferrer,
|
||||
parseStarterPackUri,
|
||||
} from 'lib/strings/starter-pack'
|
||||
} from '#/lib/strings/starter-pack'
|
||||
import {cleanError} from '../../src/lib/strings/errors'
|
||||
import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles'
|
||||
import {enforceLen} from '../../src/lib/strings/helpers'
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
const pkg = require('./package.json')
|
||||
|
||||
const DARK_SPLASH_ANDROID_BACKGROUND = '#0f141b'
|
||||
|
||||
module.exports = function (config) {
|
||||
module.exports = function (_config) {
|
||||
/**
|
||||
* App version number. Should be incremented as part of a release cycle.
|
||||
*/
|
||||
@@ -81,6 +79,7 @@ module.exports = function (config) {
|
||||
'eu',
|
||||
'fi',
|
||||
'fr',
|
||||
'fy',
|
||||
'ga',
|
||||
'gd',
|
||||
'gl',
|
||||
@@ -96,6 +95,7 @@ module.exports = function (config) {
|
||||
'nl',
|
||||
'pl',
|
||||
'pt-BR',
|
||||
'pt-PT',
|
||||
'ro',
|
||||
'ru',
|
||||
'sv',
|
||||
@@ -140,12 +140,10 @@ module.exports = function (config) {
|
||||
},
|
||||
androidStatusBar: {
|
||||
barStyle: 'light-content',
|
||||
backgroundColor: '#00000000',
|
||||
},
|
||||
// Dark nav bar in light mode is better than light nav bar in dark mode
|
||||
androidNavigationBar: {
|
||||
barStyle: 'light-content',
|
||||
backgroundColor: DARK_SPLASH_ANDROID_BACKGROUND,
|
||||
},
|
||||
android: {
|
||||
icon: './assets/app-icons/android_icon_default_light.png',
|
||||
@@ -197,6 +195,11 @@ module.exports = function (config) {
|
||||
plugins: [
|
||||
'expo-video',
|
||||
'expo-localization',
|
||||
'expo-web-browser',
|
||||
[
|
||||
'react-native-edge-to-edge',
|
||||
{android: {enforceNavigationBarContrast: false}},
|
||||
],
|
||||
USE_SENTRY && [
|
||||
'@sentry/react-native/expo',
|
||||
{
|
||||
@@ -240,12 +243,10 @@ module.exports = function (config) {
|
||||
'./plugins/withAndroidManifestPlugin.js',
|
||||
'./plugins/withAndroidManifestFCMIconPlugin.js',
|
||||
'./plugins/withAndroidStylesAccentColorPlugin.js',
|
||||
'./plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js',
|
||||
'./plugins/withAndroidDayNightThemePlugin.js',
|
||||
'./plugins/withAndroidNoJitpackPlugin.js',
|
||||
'./plugins/withNoBundleCompression.js',
|
||||
'./plugins/shareExtension/withShareExtensions.js',
|
||||
'./plugins/notificationsExtension/withNotificationsExtension.js',
|
||||
'./plugins/withAppDelegateReferrer.js',
|
||||
[
|
||||
'expo-font',
|
||||
{
|
||||
@@ -357,16 +358,6 @@ module.exports = function (config) {
|
||||
},
|
||||
],
|
||||
['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}],
|
||||
[
|
||||
'react-native-vision-camera',
|
||||
{
|
||||
enableLocation: false,
|
||||
cameraPermissionText: 'Bluesky needs access to your camera.',
|
||||
enableMicrophonePermission: true,
|
||||
microphonePermissionText:
|
||||
'Bluesky needs access to your microphone.',
|
||||
},
|
||||
],
|
||||
].filter(Boolean),
|
||||
extra: {
|
||||
eas: {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M20 13.75a1 1 0 0 1 1 1V18a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3v-3.25a1 1 0 1 1 2 0V18a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-3.25a1 1 0 0 1 1-1ZM12 3a1 1 0 0 1 .707.293l4.5 4.5a1 1 0 1 1-1.414 1.414L13 6.414v8.836a1 1 0 1 1-2 0V6.414L8.207 9.207a1 1 0 1 1-1.414-1.414l4.5-4.5A1 1 0 0 1 12 3Z"/></svg>
|
||||
|
After Width: | Height: | Size: 381 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M11.839 4.744c0-1.488 1.724-2.277 2.846-1.364l.107.094 7.66 7.256.128.134c.558.652.558 1.62 0 2.272l-.128.135-7.66 7.255c-1.115 1.057-2.953.267-2.953-1.27v-2.748c-3.503.055-5.417.41-6.592.97-.997.474-1.525 1.122-2.084 2.14l-.243.46c-.558 1.088-2.09.583-2.08-.515l.015-.748c.111-3.68.777-6.5 2.546-8.415 1.83-1.98 4.63-2.771 8.438-2.884V4.744Zm2 3.256c0 .79-.604 1.41-1.341 1.494l-.149.01c-3.9.057-6.147.813-7.48 2.254-.963 1.043-1.562 2.566-1.842 4.79.38-.327.826-.622 1.361-.877 1.656-.788 4.08-1.14 7.938-1.169l.153.007A1.5 1.5 0 0 1 13.839 16v2.675L20.884 12l-7.045-6.676V8Z"/></svg>
|
||||
|
After Width: | Height: | Size: 680 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M11 20V6.164l-4.293 4.293a1 1 0 1 1-1.414-1.414l5.293-5.293.151-.138a2 2 0 0 1 2.677.138l5.293 5.293.068.076a1 1 0 0 1-1.406 1.406l-.076-.068L13 6.164V20a1 1 0 0 1-2 0Z"/></svg>
|
||||
|
After Width: | Height: | Size: 271 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M18.535 5.465a5.003 5.003 0 0 0-7.076 0l-.005.005-.752.742a1 1 0 1 1-1.404-1.424l.749-.74a7.003 7.003 0 0 1 9.904 9.905l-.002.003-.737.746a1 1 0 1 1-1.424-1.404l.747-.757a5.003 5.003 0 0 0 0-7.076ZM6.202 9.288a1 1 0 0 1 .01 1.414l-.747.757a5.003 5.003 0 1 0 7.076 7.076l.005-.005.752-.742a1 1 0 1 1 1.404 1.424l-.746.737-.003.002a7.003 7.003 0 0 1-9.904-9.904l.74-.75a1 1 0 0 1 1.413-.009Zm8.505.005a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414-1.414l4-4a1 1 0 0 1 1.414 0Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 610 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M2 12a9.97 9.97 0 0 1 2.929-7.07l.076-.068a1 1 0 0 1 1.407 1.406l-.07.076A7.97 7.97 0 0 0 4 12a7.97 7.97 0 0 0 2.078 5.38l.265.277.07.076a1 1 0 0 1-1.408 1.407l-.076-.07-.331-.346A9.97 9.97 0 0 1 2 12Zm18 0a7.97 7.97 0 0 0-2.078-5.379l-.265-.278-.07-.076a1 1 0 0 1 1.408-1.406l.076.068.331.347A9.97 9.97 0 0 1 22 12a9.97 9.97 0 0 1-2.929 7.07 1 1 0 1 1-1.414-1.413A7.97 7.97 0 0 0 20 12ZM6 12c0-1.656.673-3.158 1.758-4.243a1 1 0 0 1 1.414 1.414A4 4 0 0 0 8 12.001a3.98 3.98 0 0 0 1.04 2.689l.132.138.068.077a1 1 0 0 1-1.407 1.406l-.075-.069-.2-.208A5.98 5.98 0 0 1 6 12Zm10 0a3.98 3.98 0 0 0-1.04-2.69l-.132-.139-.068-.075a1 1 0 0 1 1.407-1.407l.075.068.2.208A5.98 5.98 0 0 1 18 12a6 6 0 0 1-1.758 4.243 1 1 0 0 1-1.414-1.415A4 4 0 0 0 16 12Zm-6 0a2 2 0 1 1 4 0 2 2 0 0 1-4 0Z"/></svg>
|
||||
|
After Width: | Height: | Size: 879 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="12" fill="#208BFE"/><path fill="#fff" fill-rule="evenodd" d="M18.311 7.421a1.437 1.437 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L6.42 12.74a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z" clip-rule="evenodd"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="11.5" fill="#1183FE"/><path fill="#fff" fill-rule="evenodd" d="M17.659 8.175a1.36 1.36 0 0 1 0 1.925l-6.224 6.223a1.36 1.36 0 0 1-1.925 0L6.4 13.212a1.361 1.361 0 0 1 1.925-1.925l2.149 2.148 5.26-5.26a1.36 1.36 0 0 1 1.925 0Z" clip-rule="evenodd"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 359 B After Width: | Height: | Size: 354 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#208BFE" d="M8.792 1.54a4.11 4.11 0 0 1 6.416 0 4.13 4.13 0 0 0 3.146 1.54c2.616.04 4.544 2.5 4 5.1a4.28 4.28 0 0 0 .777 3.462c1.6 2.104.912 5.17-1.427 6.36a4.21 4.21 0 0 0-2.177 2.774c-.62 2.584-3.408 3.948-5.781 2.83a4.1 4.1 0 0 0-3.492 0c-2.373 1.118-5.16-.246-5.78-2.83a4.21 4.21 0 0 0-2.178-2.775c-2.34-1.19-3.028-4.256-1.427-6.36a4.28 4.28 0 0 0 .777-3.46c-.544-2.602 1.384-5.06 4-5.1a4.13 4.13 0 0 0 3.146-1.54Z"/><path fill="#fff" fill-rule="evenodd" d="M17.659 8.399a1.36 1.36 0 0 1 0 1.925l-6.224 6.223a1.36 1.36 0 0 1-1.925 0L6.4 13.435a1.361 1.361 0 1 1 1.925-1.925l2.149 2.15 5.26-5.261a1.36 1.36 0 0 1 1.925 0Z" clip-rule="evenodd"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#1185FE" d="M8.792 1.615a4.154 4.154 0 0 1 6.416 0 4.15 4.15 0 0 0 3.146 1.515 4.154 4.154 0 0 1 4 5.017 4.15 4.15 0 0 0 .777 3.404 4.154 4.154 0 0 1-1.427 6.255 4.15 4.15 0 0 0-2.177 2.73 4.154 4.154 0 0 1-5.781 2.784 4.15 4.15 0 0 0-3.492 0 4.154 4.154 0 0 1-5.78-2.784 4.15 4.15 0 0 0-2.178-2.73A4.154 4.154 0 0 1 .87 11.551a4.15 4.15 0 0 0 .776-3.404 4.154 4.154 0 0 1 4-5.017 4.15 4.15 0 0 0 3.146-1.515Z"/><path fill="#fff" fill-rule="evenodd" d="M17.861 8.26a1.44 1.44 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L5.97 13.58a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z" clip-rule="evenodd"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 738 B After Width: | Height: | Size: 733 B |
@@ -17,7 +17,7 @@ module.exports = function (api) {
|
||||
],
|
||||
plugins: [
|
||||
'macros',
|
||||
['babel-plugin-react-compiler', {target: '18'}],
|
||||
['babel-plugin-react-compiler', {target: '19'}],
|
||||
[
|
||||
'module:react-native-dotenv',
|
||||
{
|
||||
@@ -37,10 +37,6 @@ module.exports = function (api) {
|
||||
alias: {
|
||||
// This needs to be mirrored in tsconfig.json
|
||||
'#': './src',
|
||||
lib: './src/lib',
|
||||
platform: './src/platform',
|
||||
state: './src/state',
|
||||
view: './src/view',
|
||||
crypto: './src/platform/crypto.ts',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"translation": "/src/locale/locales/%two_letters_code%/messages.po",
|
||||
"languages_mapping": {
|
||||
"two_letters_code": {
|
||||
"pt": "pt-PT",
|
||||
"pt-BR": "pt-BR",
|
||||
"en-GB": "en-GB",
|
||||
"zh-CN": "zh-CN",
|
||||
|
||||
@@ -15,7 +15,7 @@ This is NOT required when developing for web.
|
||||
|
||||
- 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'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`
|
||||
@@ -33,7 +33,7 @@ This is NOT required when developing for web.
|
||||
- After initial setup:
|
||||
- Copy `google-services.json.example` to `google-services.json` or provide your own `google-services.json`. (A real firebase project is NOT required)
|
||||
- `npx expo prebuild` -> you will also need to run this anytime `app.json` or native `package.json` deps change
|
||||
|
||||
|
||||
### Running the Native App
|
||||
|
||||
- iOS: `yarn ios`
|
||||
@@ -162,6 +162,9 @@ See [testing.md](./testing.md).
|
||||
`./platform/polyfills.*.ts` adds polyfills to the environment. Currently, this includes:
|
||||
|
||||
- TextEncoder / TextDecoder
|
||||
- react-native-url-polyfill
|
||||
- Array#findLast (on web)
|
||||
- atob (on native)
|
||||
|
||||
### Sentry sourcemaps
|
||||
|
||||
|
||||
@@ -1,95 +1,138 @@
|
||||
# OTA Deployments
|
||||
|
||||
## Overview
|
||||
## Automatic internal OTAs
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
OTA deployments to TestFlight/APK installs happen automatically upon all merges
|
||||
into main. In cases where the fingerprint diff shows incompatible native
|
||||
changes, a new client build will automatically be ran and deployed to TestFlight
|
||||
(iOS) or delivered in Slack (Android).
|
||||
|
||||
## Production Deployments
|
||||
## Production OTAs
|
||||
|
||||
### Prerequisites
|
||||
Production OTAs can only update the JavaScript bundle. Changes to native modules
|
||||
must be done as a full release cycle through the app stores.
|
||||
|
||||
- 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.
|
||||
|
||||

|
||||
> [!TIP]
|
||||
> If you're using a TestFlight build, in order to reference the correct build
|
||||
> number and to verify the success of an OTA, you will need to delete the
|
||||
> TestFlight app itself, delete the Bluesky app entirely, and re-install from
|
||||
> the App Store.
|
||||
|
||||
- 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.
|
||||
### 1. Find the build numbers
|
||||
|
||||

|
||||
Find the latest production build numbers for iOS and Android in Slack. These are
|
||||
spit out into `#bot-client-builds` after each release. You can also find this
|
||||
information under the `About` section in app settings.
|
||||
|
||||
- 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.
|
||||
| Slack | In app |
|
||||
| ----- | ------ |
|
||||
|  |  |
|
||||
|
||||
### Preparation
|
||||
### 2. Ensure the release is tagged
|
||||
|
||||
- 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.
|
||||
You need to ensure that the latest release was properly tagged using the format
|
||||
`1.X.0`. 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.
|
||||
|
||||
### Deployment
|
||||
### 3. Create an OTA branch
|
||||
|
||||
- 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”
|
||||
Create a branch based off the last commit that was deployed in the most recent
|
||||
release. This could be the commit that was tagged `1.x.0`, or a later commit,
|
||||
but it **needs to be the tip of the latest production release** in any case.
|
||||
Double check yourself by ensuring that the `version` in `package.json` matches
|
||||
what's in the latest release.
|
||||
|
||||
In about five minutes, the new deployment should be available for download. To test:
|
||||
This new OTA branch should follow the format `1.X.0-ota-1`. If one or more OTAs
|
||||
have already been deployed for this release, incremement the branch name e.g.
|
||||
`1.x.0-ota-2`.
|
||||
|
||||
- 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.
|
||||
### 4. Add commits to the OTA branch
|
||||
|
||||
### Post Deployment
|
||||
Cherry pick in the commits that need to be deployed on top of the most recent
|
||||
release or OTA.
|
||||
|
||||
- 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.
|
||||
### 5. Manually set build numbers
|
||||
|
||||
Log in to the EAS CLI with `eas login` and manually set the build numbers to the
|
||||
values you found in **Step 1**.
|
||||
|
||||
```sh
|
||||
> npx eas build:version:set -p ios
|
||||
>
|
||||
> Project @blueskysocial/bluesky with bundle identifier "xyz.blueskyweb.app" is configured with buildNumber 1011.
|
||||
> ✔ What version would you like to set? … 1009
|
||||
>
|
||||
> npx eas build:version:set -p android
|
||||
>
|
||||
> Project @blueskysocial/bluesky with application ID "xyz.blueskyweb.app" is configured with versionCode 641.
|
||||
> ✔ What version would you like to set? … 639
|
||||
```
|
||||
|
||||
👉 **Save the previous values,** in this case `1011` and `641`, so you can reset
|
||||
them after the OTA completes.
|
||||
|
||||
### 6. Run the GitHub actions
|
||||
You'll need to run two separate actions: one to deploy the iOS/Android OTA
|
||||
itself, and one to build the web Docker container.
|
||||
|
||||
**For the iOS/Android OTA,** head to [Actions > Bundle and Deploy EAS
|
||||
Update](https://github.com/bluesky-social/social-app/actions/workflows/bundle-deploy-eas-update.yml)
|
||||
and run the action.
|
||||
|
||||
| Steps | |
|
||||
| ----- | --- |
|
||||
| Select your OTA branch `1.x.0-ota-x`, select `production` in the dropdown, enter the git tag of the latest release `1.x.0`, and click "Run workflow" |  |
|
||||
|
||||
> [!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.
|
||||
|
||||
**For web,** head to [Actions >
|
||||
build-and-push-bskyweb-aws](https://github.com/bluesky-social/social-app/actions/workflows/build-and-push-bskyweb-aws.yaml)
|
||||
and run the action.
|
||||
|
||||
| Steps | |
|
||||
| ----- | --- |
|
||||
| Select your OTA branch `1.x.0-ota-x` and click "Run workflow" |  |
|
||||
|
||||
### 7. Deploy web
|
||||
|
||||
Once the web Docker container build finishes, go to your `1.x.0-ota-x` branch,
|
||||
copy the most recent commit hash. Post this hash in `#ops-deploys` and request
|
||||
someone with web deploy access deploy the built container.
|
||||
|
||||
### 8. Confirm successful deployment
|
||||
|
||||
In about five minutes, the new deployment should be deployed and devices will
|
||||
begin downloading and installing in the background.
|
||||
|
||||
To confirm this, as mentioned above, you must completely clear the TestFlight
|
||||
build from your device and re-install from the App Store. Then, you'll need to:
|
||||
- Launch the app (or quit and reopen) and wait ~15s for the download to complete
|
||||
- Quit and reopen the app
|
||||
- Check the `Settings > About` page and confirm the has matches the most recent has on your OTA branch
|
||||
|
||||
### 9. Reset build numbers
|
||||
|
||||
Grab the build numbers you saved in **Step 5** and reverse the EAS CLI commands
|
||||
to reset the build numbers.
|
||||
|
||||
```sh
|
||||
> npx eas build:version:set -p ios
|
||||
>
|
||||
> Project @blueskysocial/bluesky with bundle identifier "xyz.blueskyweb.app" is configured with buildNumber 1009.
|
||||
> ✔ What version would you like to set? … 1011
|
||||
>
|
||||
> npx eas build:version:set -p android
|
||||
>
|
||||
> Project @blueskysocial/bluesky with application ID "xyz.blueskyweb.app" is configured with versionCode 639.
|
||||
> ✔ What version would you like to set? … 641
|
||||
```
|
||||
|
||||
## Overview diagram
|
||||
|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 558 KiB |
|
After Width: | Height: | Size: 48 KiB |
@@ -15,6 +15,7 @@ module.exports = {
|
||||
'eu',
|
||||
'fi',
|
||||
'fr',
|
||||
'fy',
|
||||
'ga',
|
||||
'gd',
|
||||
'gl',
|
||||
@@ -30,6 +31,7 @@ module.exports = {
|
||||
'nl',
|
||||
'pl',
|
||||
'pt-BR',
|
||||
'pt-PT',
|
||||
'ro',
|
||||
'ru',
|
||||
'sv',
|
||||
|
||||
@@ -16,22 +16,21 @@ if (process.env.BSKY_PROFILE) {
|
||||
|
||||
cfg.resolver.assetExts = [...cfg.resolver.assetExts, 'woff2']
|
||||
|
||||
// Enabled by default in RN 0.79+, but this breaks Lingui + others
|
||||
cfg.resolver.unstable_enablePackageExports = false
|
||||
|
||||
cfg.resolver.resolveRequest = (context, moduleName, platform) => {
|
||||
// HACK: manually resolve a few packages that use `exports` in `package.json`.
|
||||
// A proper solution is to enable `unstable_enablePackageExports` but this needs careful testing.
|
||||
if (moduleName.startsWith('multiformats/hashes/hasher')) {
|
||||
return context.resolveRequest(
|
||||
context,
|
||||
'multiformats/dist/src/hashes/hasher',
|
||||
'multiformats/cjs/src/hashes/hasher',
|
||||
platform,
|
||||
)
|
||||
}
|
||||
if (moduleName.startsWith('multiformats/cid')) {
|
||||
return context.resolveRequest(
|
||||
context,
|
||||
'multiformats/dist/src/cid',
|
||||
platform,
|
||||
)
|
||||
return context.resolveRequest(context, 'multiformats/cjs/src/cid', platform)
|
||||
}
|
||||
if (moduleName === '@ipld/dag-cbor') {
|
||||
return context.resolveRequest(context, '@ipld/dag-cbor/src', platform)
|
||||
|
||||
@@ -317,6 +317,9 @@ class BottomSheetView(
|
||||
// View overrides to pass to DialogRootViewGroup instead
|
||||
|
||||
override fun dispatchProvideStructure(structure: ViewStructure?) {
|
||||
if (structure == null) {
|
||||
return
|
||||
}
|
||||
dialogRootViewGroup.dispatchProvideStructure(structure)
|
||||
}
|
||||
|
||||
|
||||
@@ -139,19 +139,7 @@ class DialogRootViewGroup(
|
||||
return super.onHoverEvent(event)
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onChildStartedNativeGesture(ev: MotionEvent?) {
|
||||
eventDispatcher?.let {
|
||||
if (ev != null) {
|
||||
jSTouchDispatcher.onChildStartedNativeGesture(ev, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChildStartedNativeGesture(
|
||||
childView: View,
|
||||
ev: MotionEvent,
|
||||
) {
|
||||
override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
|
||||
eventDispatcher?.let { jSTouchDispatcher.onChildStartedNativeGesture(ev, it) }
|
||||
jSPointerDispatcher?.onChildStartedNativeGesture(childView, ev, eventDispatcher)
|
||||
}
|
||||
|
||||
@@ -144,7 +144,8 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate {
|
||||
}
|
||||
|
||||
func updateLayout() {
|
||||
if self.prevLayoutDetentIdentifier == self.selectedDetentIdentifier,
|
||||
// Allow updates either when identifiers match OR when prevLayoutDetentIdentifier is nil (first real content update)
|
||||
if self.prevLayoutDetentIdentifier == self.selectedDetentIdentifier || self.prevLayoutDetentIdentifier == nil,
|
||||
let contentHeight = self.innerView?.subviews.first?.frame.size.height {
|
||||
self.sheetVc?.updateDetents(contentHeight: self.clampHeight(contentHeight),
|
||||
preventExpansion: self.preventExpansion)
|
||||
@@ -154,9 +155,15 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate {
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
guard let sheetVc = self.sheetVc else {
|
||||
return
|
||||
}
|
||||
|
||||
self.isClosing = true
|
||||
self.sheetVc?.dismiss(animated: true) { [weak self] in
|
||||
self?.destroy()
|
||||
DispatchQueue.main.async {
|
||||
sheetVc.dismiss(animated: true) { [weak self] in
|
||||
self?.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import * as React from 'react'
|
||||
import {
|
||||
Dimensions,
|
||||
LayoutChangeEvent,
|
||||
NativeSyntheticEvent,
|
||||
type LayoutChangeEvent,
|
||||
type NativeSyntheticEvent,
|
||||
Platform,
|
||||
StyleProp,
|
||||
type StyleProp,
|
||||
View,
|
||||
ViewStyle,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
|
||||
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {BottomSheetState, BottomSheetViewProps} from './BottomSheet.types'
|
||||
import {
|
||||
type BottomSheetState,
|
||||
type BottomSheetViewProps,
|
||||
} from './BottomSheet.types'
|
||||
import {BottomSheetPortalProvider} from './BottomSheetPortal'
|
||||
import {Context as PortalContext} from './BottomSheetPortal'
|
||||
|
||||
@@ -27,7 +30,10 @@ const NativeView: React.ComponentType<
|
||||
|
||||
const NativeModule = requireNativeModule('BottomSheet')
|
||||
|
||||
const isIOS15 = Platform.OS === 'ios' && Number(Platform.Version) < 16
|
||||
const isIOS15 =
|
||||
Platform.OS === 'ios' &&
|
||||
// semvar - can be 3 segments, so can't use Number(Platform.Version)
|
||||
Number(Platform.Version.split('.').at(0)) < 16
|
||||
|
||||
export class BottomSheetNativeComponent extends React.Component<
|
||||
BottomSheetViewProps,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {BottomSheetViewProps} from './BottomSheet.types'
|
||||
import {type BottomSheetViewProps} from './BottomSheet.types'
|
||||
|
||||
export function BottomSheetNativeComponent(_: BottomSheetViewProps) {
|
||||
throw new Error('BottomSheetNativeComponent is not available on web')
|
||||
|
||||
@@ -10,7 +10,7 @@ Pod::Spec.new do |s|
|
||||
s.static_framework = true
|
||||
|
||||
s.dependency 'ExpoModulesCore'
|
||||
s.dependency 'SDWebImage', '~> 5.19.1'
|
||||
s.dependency 'SDWebImage', '~> 5.21.0'
|
||||
s.dependency 'SDWebImageWebPCoder', '~> 0.14.6'
|
||||
|
||||
# Swift/Objective-C compatibility
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Alan Hughes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,3 @@
|
||||
# expo-emoji-picker
|
||||
|
||||
Based on [react-native-emoji-popup](https://github.com/okwasniewski/react-native-emoji-popup) and [expo-emoji-picker](https://github.com/alanjhughes/expo-emoji-picker)
|
||||
@@ -0,0 +1,46 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
group = 'expo.community.modules.emojipicker'
|
||||
version = '0.1.0'
|
||||
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
useCoreDependencies()
|
||||
useExpoPublishing()
|
||||
|
||||
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
|
||||
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
|
||||
// Most of the time, you may like to manage the Android SDK versions yourself.
|
||||
def useManagedAndroidSdkVersions = false
|
||||
if (useManagedAndroidSdkVersions) {
|
||||
useDefaultAndroidSdkVersions()
|
||||
} else {
|
||||
buildscript {
|
||||
// Simple helper that allows the root project to override versions declared by this library.
|
||||
ext.safeExtGet = { prop, fallback ->
|
||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||
}
|
||||
}
|
||||
project.android {
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet("minSdkVersion", 21)
|
||||
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "expo.community.modules.emojipicker"
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "0.1.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
dependencies {
|
||||
implementation "androidx.emoji2:emoji2-emojipicker:1.5.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest>
|
||||
</manifest>
|
||||
@@ -0,0 +1,15 @@
|
||||
package expo.community.modules.emojipicker
|
||||
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
import java.net.URL
|
||||
|
||||
class EmojiPickerModule : Module() {
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name("EmojiPicker")
|
||||
|
||||
View(EmojiPickerModuleView::class) {
|
||||
Events("onEmojiSelected")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package expo.community.modules.emojipicker
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.emoji2.emojipicker.EmojiPickerView
|
||||
import expo.modules.kotlin.AppContext
|
||||
import expo.modules.kotlin.viewevent.EventDispatcher
|
||||
import expo.modules.kotlin.views.ExpoView
|
||||
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class EmojiPickerModuleView(context: Context, appContext: AppContext) :
|
||||
ExpoView(context, appContext) {
|
||||
private var emojiView: EmojiPickerView = EmojiPickerView(context)
|
||||
private val onEmojiSelected by EventDispatcher()
|
||||
|
||||
init {
|
||||
setupView()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
addView(
|
||||
emojiView, LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
|
||||
emojiView.setOnEmojiPickedListener { emoji ->
|
||||
onEmojiSelected(mapOf("emoji" to emoji.emoji))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
removeView(emojiView)
|
||||
setupView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"platforms": ["apple", "android"],
|
||||
"apple": {
|
||||
"modules": ["EmojiPickerModule"]
|
||||
},
|
||||
"android": {
|
||||
"modules": ["expo.community.modules.emojipicker.EmojiPickerModule"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export {default as EmojiPicker} from './src/EmojiPicker'
|
||||
export {default} from './src/EmojiPickerModule'
|
||||
export * from './src/EmojiPickerModule.types'
|
||||
@@ -0,0 +1,25 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'EmojiPickerModule'
|
||||
s.version = '1.0.0'
|
||||
s.summary = 'An emoji picker for use in Bluesky'
|
||||
s.description = 'An emoji picker for use in Bluesky'
|
||||
s.author = 'alanjhughes'
|
||||
s.homepage = 'https://github.com/bluesky-social/social-app'
|
||||
s.platforms = {
|
||||
:ios => '15.1',
|
||||
:tvos => '15.1'
|
||||
}
|
||||
s.source = { git: '' }
|
||||
s.swift_version = '5.4'
|
||||
s.static_framework = true
|
||||
|
||||
s.dependency 'ExpoModulesCore'
|
||||
s.dependency 'MCEmojiPicker'
|
||||
|
||||
# Swift/Objective-C compatibility
|
||||
s.pod_target_xcconfig = {
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
}
|
||||
|
||||
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
import ExpoModulesCore
|
||||
|
||||
public class EmojiPickerModule: Module {
|
||||
public func definition() -> ModuleDefinition {
|
||||
Name("EmojiPicker")
|
||||
|
||||
View(EmojiPickerView.self) {
|
||||
Events("onEmojiSelected")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import ExpoModulesCore
|
||||
import WebKit
|
||||
import MCEmojiPicker
|
||||
|
||||
class EmojiPickerView: ExpoView, MCEmojiPickerDelegate {
|
||||
let onEmojiSelected = EventDispatcher()
|
||||
|
||||
override func layoutSubviews() {
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
|
||||
self.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
@objc func handleTap(_ gesture: UITapGestureRecognizer) {
|
||||
presentEmojiPicker()
|
||||
}
|
||||
|
||||
func presentEmojiPicker() {
|
||||
let emojiPicker = MCEmojiPickerViewController()
|
||||
let reactRootVC = reactViewController()
|
||||
emojiPicker.sourceView = self
|
||||
emojiPicker.delegate = self
|
||||
reactRootVC?.present(emojiPicker, animated: true)
|
||||
}
|
||||
|
||||
func didGetEmoji(emoji: String) {
|
||||
onEmojiSelected([
|
||||
"emoji": emoji
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import {useMemo} from 'react'
|
||||
import {useColorScheme} from 'react-native'
|
||||
|
||||
import {type EmojiPickerViewProps} from './EmojiPickerModule.types'
|
||||
import EmojiPickerNativeView from './EmojiPickerView'
|
||||
|
||||
const EmojiPicker = ({onEmojiSelected}: EmojiPickerViewProps) => {
|
||||
const scheme = useColorScheme()
|
||||
const styles = useMemo(
|
||||
() =>
|
||||
({
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
backgroundColor: scheme === 'dark' ? '#000' : '#fff',
|
||||
} as const),
|
||||
[scheme],
|
||||
)
|
||||
|
||||
return (
|
||||
<EmojiPickerNativeView
|
||||
onEmojiSelected={emoji => {
|
||||
onEmojiSelected(emoji)
|
||||
}}
|
||||
style={styles}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default EmojiPicker
|
||||
@@ -0,0 +1,15 @@
|
||||
import {type EmojiPickerViewProps} from './EmojiPickerModule.types'
|
||||
import EmojiPickerNativeView from './EmojiPickerView'
|
||||
|
||||
const EmojiPicker = ({children, onEmojiSelected}: EmojiPickerViewProps) => {
|
||||
return (
|
||||
<EmojiPickerNativeView
|
||||
onEmojiSelected={emoji => {
|
||||
onEmojiSelected(emoji)
|
||||
}}>
|
||||
{children}
|
||||
</EmojiPickerNativeView>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmojiPicker
|
||||
@@ -0,0 +1,5 @@
|
||||
import {NativeModule, requireNativeModule} from 'expo'
|
||||
|
||||
declare class EmojiPickerModule extends NativeModule {}
|
||||
|
||||
export default requireNativeModule<EmojiPickerModule>('EmojiPicker')
|
||||
@@ -0,0 +1,20 @@
|
||||
import {type ViewProps} from 'react-native'
|
||||
|
||||
export type EmojiSelectionListener = (event: {
|
||||
nativeEvent: SelectionEvent
|
||||
}) => void
|
||||
|
||||
export type SelectionEvent = {
|
||||
emoji: string
|
||||
}
|
||||
|
||||
export type EmojiPickerViewProps = ViewProps & {
|
||||
/*
|
||||
* Callback that will be called when an emoji is selected.
|
||||
*/
|
||||
onEmojiSelected: (emoji: string) => void
|
||||
}
|
||||
|
||||
export type EmojiPickerNativeViewProps = ViewProps & {
|
||||
onEmojiSelected: EmojiSelectionListener
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {requireNativeView} from 'expo'
|
||||
import type * as React from 'react'
|
||||
|
||||
import {
|
||||
type EmojiPickerNativeViewProps,
|
||||
type EmojiPickerViewProps,
|
||||
} from './EmojiPickerModule.types'
|
||||
|
||||
const NativeView: React.ComponentType<EmojiPickerNativeViewProps> =
|
||||
requireNativeView('EmojiPicker')
|
||||
|
||||
export default function EmojiPicker(props: EmojiPickerViewProps) {
|
||||
return (
|
||||
<NativeView
|
||||
{...props}
|
||||
onEmojiSelected={({nativeEvent}) => {
|
||||
props.onEmojiSelected(nativeEvent.emoji)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from 'react'
|
||||
|
||||
export interface ExpoScrollForwarderViewProps {
|
||||
scrollViewTag: number | null
|
||||
children: React.ReactNode
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react'
|
||||
import {requireNativeViewManager} from 'expo-modules-core'
|
||||
|
||||
import {ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types'
|
||||
import {type ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types'
|
||||
|
||||
const NativeView: React.ComponentType<ExpoScrollForwarderViewProps> =
|
||||
requireNativeViewManager('ExpoScrollForwarder')
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import React from 'react'
|
||||
|
||||
import {ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types'
|
||||
import {type ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types'
|
||||
|
||||
export function ExpoScrollForwarderView({
|
||||
children,
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.101.0",
|
||||
"version": "1.103.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"expo": {
|
||||
"autolinking": {
|
||||
"android": {
|
||||
"buildFromSource": [
|
||||
"expo-notifications",
|
||||
"expo-haptics",
|
||||
"expo-media-library"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "is-ci || husky install",
|
||||
"postinstall": "patch-package && yarn intl:compile",
|
||||
@@ -58,13 +69,13 @@
|
||||
"icons:optimize": "svgo -f ./assets/icons"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.15.3",
|
||||
"@atproto/api": "^0.15.9",
|
||||
"@bitdrift/react-native": "^0.6.8",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
"@expo/html-elements": "^0.4.2",
|
||||
"@expo/webpack-config": "^19.0.0",
|
||||
"@expo/html-elements": "^0.12.4",
|
||||
"@expo/webpack-config": "^19.0.1",
|
||||
"@floating-ui/dom": "^1.6.3",
|
||||
"@floating-ui/react-dom": "^2.0.8",
|
||||
"@formatjs/intl-locale": "^4.2.8",
|
||||
@@ -80,17 +91,13 @@
|
||||
"@mattermost/react-native-paste-input": "^0.7.1",
|
||||
"@miblanchard/react-native-slider": "^2.3.1",
|
||||
"@mozzius/expo-dynamic-app-icon": "^1.5.0",
|
||||
"@radix-ui/react-dismissable-layer": "^1.1.5",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.6",
|
||||
"@radix-ui/react-focus-guards": "^1.1.1",
|
||||
"@radix-ui/react-focus-scope": "^1.1.2",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@react-native-menu/menu": "^1.1.7",
|
||||
"@react-native-picker/picker": "2.10.3",
|
||||
"@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-native-async-storage/async-storage": "2.1.2",
|
||||
"@react-native-menu/menu": "^1.2.3",
|
||||
"@react-native-picker/picker": "2.11.0",
|
||||
"@react-navigation/bottom-tabs": "^7.3.13",
|
||||
"@react-navigation/drawer": "^7.3.12",
|
||||
"@react-navigation/native": "^7.1.9",
|
||||
"@react-navigation/native-stack": "^7.3.13",
|
||||
"@sentry/react-native": "~6.10.0",
|
||||
"@tanstack/query-async-storage-persister": "^5.25.0",
|
||||
"@tanstack/react-query": "^5.8.1",
|
||||
@@ -123,38 +130,37 @@
|
||||
"emoji-mart": "^5.5.2",
|
||||
"emoji-regex": "^10.4.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"expo": "~52.0.42",
|
||||
"expo-application": "~6.0.2",
|
||||
"expo-blur": "^14.0.3",
|
||||
"expo-build-properties": "~0.13.2",
|
||||
"expo-camera": "~16.0.18",
|
||||
"expo-clipboard": "~7.0.1",
|
||||
"expo-dev-client": "~5.0.19",
|
||||
"expo-device": "~7.0.3",
|
||||
"expo-file-system": "~18.0.12",
|
||||
"expo-font": "~13.0.4",
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-image": "~2.0.7",
|
||||
"expo-image-manipulator": "~13.0.6",
|
||||
"expo-image-picker": "~16.0.6",
|
||||
"expo-linear-gradient": "~14.0.2",
|
||||
"expo-linking": "~7.0.5",
|
||||
"expo-localization": "~16.0.1",
|
||||
"expo-media-library": "~17.0.6",
|
||||
"expo-navigation-bar": "~4.0.9",
|
||||
"expo-notifications": "~0.29.14",
|
||||
"expo-screen-orientation": "~8.0.4",
|
||||
"expo-sharing": "~13.0.1",
|
||||
"expo-splash-screen": "~0.29.22",
|
||||
"expo-status-bar": "~2.0.1",
|
||||
"expo-system-ui": "~4.0.9",
|
||||
"expo-task-manager": "~12.0.6",
|
||||
"expo-updates": "~0.27.4",
|
||||
"expo-video": "~2.0.6",
|
||||
"expo-web-browser": "~14.0.2",
|
||||
"expo": "^53.0.5",
|
||||
"expo-application": "~6.1.4",
|
||||
"expo-blur": "~14.1.4",
|
||||
"expo-build-properties": "~0.14.6",
|
||||
"expo-camera": "~16.1.6",
|
||||
"expo-clipboard": "~7.1.4",
|
||||
"expo-dev-client": "~5.1.7",
|
||||
"expo-device": "~7.1.4",
|
||||
"expo-file-system": "~18.1.8",
|
||||
"expo-font": "~13.3.0",
|
||||
"expo-haptics": "~14.1.4",
|
||||
"expo-image": "~2.1.6",
|
||||
"expo-image-crop-tool": "^0.1.8",
|
||||
"expo-image-manipulator": "~13.1.5",
|
||||
"expo-image-picker": "~16.1.4",
|
||||
"expo-linear-gradient": "~14.1.4",
|
||||
"expo-linking": "~7.1.4",
|
||||
"expo-localization": "~16.1.5",
|
||||
"expo-media-library": "~17.1.6",
|
||||
"expo-notifications": "~0.31.1",
|
||||
"expo-screen-orientation": "~8.1.5",
|
||||
"expo-sharing": "~13.1.5",
|
||||
"expo-splash-screen": "~0.30.8",
|
||||
"expo-system-ui": "~5.0.7",
|
||||
"expo-task-manager": "~13.1.5",
|
||||
"expo-updates": "~0.28.12",
|
||||
"expo-video": "~2.1.8",
|
||||
"expo-web-browser": "~14.1.6",
|
||||
"fast-text-encoding": "^1.0.6",
|
||||
"history": "^5.3.0",
|
||||
"hls.js": "^1.5.11",
|
||||
"hls.js": "^1.6.2",
|
||||
"js-sha256": "^0.9.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lande": "^1.0.10",
|
||||
@@ -163,46 +169,45 @@
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.shuffle": "^4.2.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"multiformats": "^13.1.0",
|
||||
"multiformats": "9.9.0",
|
||||
"nanoid": "^5.0.5",
|
||||
"normalize-url": "^8.0.0",
|
||||
"patch-package": "^6.5.1",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"psl": "^1.9.0",
|
||||
"react": "18.3.1",
|
||||
"react-compiler-runtime": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"react-dom": "18.3.1",
|
||||
"radix-ui": "^1.2.0",
|
||||
"react": "19.0.0",
|
||||
"react-compiler-runtime": "^19.1.0-rc.1",
|
||||
"react-dom": "19.0.0",
|
||||
"react-image-crop": "^11.0.7",
|
||||
"react-keyed-flatten-children": "^3.0.0",
|
||||
"react-native": "0.76.9",
|
||||
"react-native-compressor": "1.10.3",
|
||||
"react-native-date-picker": "^5.0.7",
|
||||
"react-native-drawer-layout": "^4.1.1",
|
||||
"react-native-emoji-popup": "^0.1.2",
|
||||
"react-native-gesture-handler": "2.20.2",
|
||||
"react-is": "19",
|
||||
"react-keyed-flatten-children": "^5.0.0",
|
||||
"react-native": "0.79.2",
|
||||
"react-native-compressor": "1.11.0",
|
||||
"react-native-date-picker": "^5.0.12",
|
||||
"react-native-drawer-layout": "^4.1.6",
|
||||
"react-native-edge-to-edge": "^1.6.0",
|
||||
"react-native-gesture-handler": "2.25.0",
|
||||
"react-native-get-random-values": "~1.11.0",
|
||||
"react-native-image-crop-picker": "^0.41.6",
|
||||
"react-native-ios-context-menu": "^1.15.3",
|
||||
"react-native-keyboard-controller": "^1.14.5",
|
||||
"react-native-keyboard-controller": "^1.17.1",
|
||||
"react-native-mmkv": "^2.12.2",
|
||||
"react-native-pager-view": "6.5.1",
|
||||
"react-native-picker-select": "^9.3.1",
|
||||
"react-native-pager-view": "6.7.1",
|
||||
"react-native-progress": "bluesky-social/react-native-progress",
|
||||
"react-native-qrcode-styled": "^0.3.3",
|
||||
"react-native-reanimated": "~3.16.1",
|
||||
"react-native-reanimated": "~3.17.5",
|
||||
"react-native-root-siblings": "^4.1.1",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
"react-native-svg": "15.8.0",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-screens": "^4.11.1",
|
||||
"react-native-svg": "15.11.2",
|
||||
"react-native-uitextview": "^1.4.0",
|
||||
"react-native-url-polyfill": "^1.3.0",
|
||||
"react-native-uuid": "^2.0.2",
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-view-shot": "^4.0.3",
|
||||
"react-native-vision-camera": "^4.6.3",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-web": "~0.20.0",
|
||||
"react-native-web-webview": "^1.0.2",
|
||||
"react-native-webview": "13.12.5",
|
||||
"react-remove-scroll-bar": "^2.3.6",
|
||||
"react-native-webview": "13.13.5",
|
||||
"react-remove-scroll-bar": "^2.3.8",
|
||||
"react-responsive": "^9.0.2",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
"rn-fetch-blob": "^0.12.0",
|
||||
@@ -214,77 +219,74 @@
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@atproto/dev-env": "^0.3.87",
|
||||
"@atproto/dev-env": "^0.3.133",
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@did-plc/server": "^0.0.1",
|
||||
"@expo/config-plugins": "9.0.10",
|
||||
"@expo/config-plugins": "~10.0.2",
|
||||
"@lingui/cli": "^4.14.1",
|
||||
"@lingui/macro": "^4.14.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||
"@react-native/eslint-config": "^0.76.9",
|
||||
"@react-native/typescript-config": "^0.76.9",
|
||||
"@react-native/eslint-config": "^0.79.2",
|
||||
"@react-native/typescript-config": "^0.79.2",
|
||||
"@sentry/webpack-plugin": "^3.2.2",
|
||||
"@testing-library/jest-native": "^5.4.3",
|
||||
"@testing-library/react-native": "^12.8.1",
|
||||
"@testing-library/react-native": "^13.2.0",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/lodash.chunk": "^4.2.7",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/lodash.isequal": "^4.5.6",
|
||||
"@types/lodash.shuffle": "^4.2.7",
|
||||
"@types/psl": "^1.1.1",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/react-responsive": "^8.0.5",
|
||||
"@types/react-test-renderer": "^17.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"babel-preset-expo": "^12.0.2",
|
||||
"babel-plugin-react-compiler": "^19.1.0-rc.1",
|
||||
"babel-preset-expo": "~13.1.11",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-plugin-bsky-internal": "link:./eslint",
|
||||
"eslint-plugin-ft-flow": "^2.0.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-lingui": "^0.2.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"eslint-plugin-react-compiler": "^19.1.0-rc.1",
|
||||
"eslint-plugin-react-native-a11y": "^3.3.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||
"file-loader": "6.2.0",
|
||||
"husky": "^8.0.3",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~52.0.6",
|
||||
"jest-expo": "~53.0.3",
|
||||
"jest-junit": "^16.0.0",
|
||||
"lint-staged": "^13.2.3",
|
||||
"lockfile-lint": "^4.14.0",
|
||||
"metro-react-native-babel-preset": "^0.76.9",
|
||||
"metro-react-native-babel-preset": "^0.77.0",
|
||||
"prettier": "^2.8.3",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"react-refresh": "^0.14.0",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"svgo": "^3.3.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "~5.8.3",
|
||||
"webpack-bundle-analyzer": "^4.10.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@expo/image-utils": "0.6.3",
|
||||
"@react-native/babel-preset": "0.76.9",
|
||||
"@react-native/normalize-colors": "0.76.1",
|
||||
"@react-native/babel-preset": "0.79.2",
|
||||
"@react-native/normalize-colors": "0.79.2",
|
||||
"@types/react": "^18",
|
||||
"**/expo-constants": "17.0.3",
|
||||
"**/expo-device": "7.0.1",
|
||||
"**/zod": "3.23.8"
|
||||
"**/zod": "3.23.8",
|
||||
"**/multiformats": "9.9.0"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo/ios",
|
||||
"setupFilesAfterEnv": [
|
||||
"./jest/jestSetup.js",
|
||||
"@testing-library/jest-native/extend-expect"
|
||||
"./jest/jestSetup.js"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
diff --git a/node_modules/expo-image-manipulator/src/ImageManipulator.web.ts b/node_modules/expo-image-manipulator/src/ImageManipulator.web.ts
|
||||
new file mode 100644
|
||||
index 0000000..babbb3b
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-image-manipulator/src/ImageManipulator.web.ts
|
||||
@@ -0,0 +1,60 @@
|
||||
+import { useReleasingSharedObject } from 'expo-modules-core';
|
||||
+import { SharedRef } from 'expo-modules-core/types';
|
||||
+
|
||||
+import { Action, ImageResult, SaveFormat, SaveOptions } from './ImageManipulator.types';
|
||||
+import { ImageManipulatorContext } from './ImageManipulatorContext';
|
||||
+import ExpoImageManipulator from './NativeImageManipulatorModule';
|
||||
+import { validateArguments } from './validators';
|
||||
+
|
||||
+// @needsAudit
|
||||
+/**
|
||||
+ * Manipulate the image provided via `uri`. Available modifications are rotating, flipping (mirroring),
|
||||
+ * resizing and cropping. Each invocation results in a new file. With one invocation you can provide
|
||||
+ * a set of actions to perform over the image. Overwriting the source file would not have an effect
|
||||
+ * in displaying the result as images are cached.
|
||||
+ * @param uri URI of the file to manipulate. Should be on the local file system or a base64 data URI.
|
||||
+ * @param actions An array of objects representing manipulation options. Each object should have
|
||||
+ * __only one__ of the keys that corresponds to specific transformation.
|
||||
+ * @param saveOptions A map defining how modified image should be saved.
|
||||
+ * @return Promise which fulfils with [`ImageResult`](#imageresult) object.
|
||||
+ * @deprecated It has been replaced by the new, contextual and object-oriented API.
|
||||
+ * Use [`ImageManipulator.manipulate`](#manipulateuri) or [`useImageManipulator`](#useimagemanipulatoruri) instead.
|
||||
+ */
|
||||
+export async function manipulateAsync(
|
||||
+ uri: string,
|
||||
+ actions: Action[] = [],
|
||||
+ saveOptions: SaveOptions = {}
|
||||
+): Promise<ImageResult> {
|
||||
+ validateArguments(uri, actions, saveOptions);
|
||||
+
|
||||
+ const { format = SaveFormat.JPEG, ...rest } = saveOptions;
|
||||
+ const context = ExpoImageManipulator.manipulate(uri);
|
||||
+
|
||||
+ for (const action of actions) {
|
||||
+ if ('resize' in action) {
|
||||
+ context.resize(action.resize);
|
||||
+ } else if ('rotate' in action) {
|
||||
+ context.rotate(action.rotate);
|
||||
+ } else if ('flip' in action) {
|
||||
+ context.flip(action.flip);
|
||||
+ } else if ('crop' in action) {
|
||||
+ context.crop(action.crop);
|
||||
+ } else if ('extent' in action && context.extent) {
|
||||
+ context.extent(action.extent);
|
||||
+ }
|
||||
+ }
|
||||
+ const image = await context.renderAsync(saveOptions.compress);
|
||||
+ const result = await image.saveAsync({ format, ...rest });
|
||||
+
|
||||
+ // These shared objects will not be used anymore, so free up some memory.
|
||||
+ context.release();
|
||||
+ image.release();
|
||||
+
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
+export function useImageManipulator(source: string | SharedRef<'image'>): ImageManipulatorContext {
|
||||
+ return useReleasingSharedObject(() => ExpoImageManipulator.manipulate(source), [source]);
|
||||
+}
|
||||
+
|
||||
+export { ExpoImageManipulator as ImageManipulator };
|
||||
diff --git a/node_modules/expo-image-manipulator/src/ImageManipulatorContext.ts b/node_modules/expo-image-manipulator/src/ImageManipulatorContext.ts
|
||||
index 120d8d3..f8aa49c 100644
|
||||
--- a/node_modules/expo-image-manipulator/src/ImageManipulatorContext.ts
|
||||
+++ b/node_modules/expo-image-manipulator/src/ImageManipulatorContext.ts
|
||||
@@ -52,7 +52,7 @@ export declare class ImageManipulatorContext extends SharedObject {
|
||||
/**
|
||||
* Awaits for all manipulation tasks to finish and resolves with a reference to the resulted native image.
|
||||
*/
|
||||
- renderAsync(): Promise<ImageRef>;
|
||||
+ renderAsync(compress?: number): Promise<ImageRef>;
|
||||
}
|
||||
|
||||
export default ExpoImageManipulator.Context as typeof ImageManipulatorContext;
|
||||
diff --git a/node_modules/expo-image-manipulator/src/web/ImageManipulatorContext.web.ts b/node_modules/expo-image-manipulator/src/web/ImageManipulatorContext.web.ts
|
||||
index 428848c..363a57a 100644
|
||||
--- a/node_modules/expo-image-manipulator/src/web/ImageManipulatorContext.web.ts
|
||||
+++ b/node_modules/expo-image-manipulator/src/web/ImageManipulatorContext.web.ts
|
||||
@@ -41,7 +41,7 @@ export default class ImageManipulatorContext extends SharedObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
- async renderAsync(): Promise<ImageManipulatorImageRef> {
|
||||
+ async renderAsync(compress?: number): Promise<ImageManipulatorImageRef> {
|
||||
const canvas = await this.currentTask;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
@@ -49,7 +49,7 @@ export default class ImageManipulatorContext extends SharedObject {
|
||||
const url = blob ? URL.createObjectURL(blob) : canvas.toDataURL();
|
||||
|
||||
resolve(new ImageManipulatorImageRef(url, canvas.width, canvas.height));
|
||||
- });
|
||||
+ }, typeof compress === 'number' ? 'image/jpeg' : undefined, compress);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
diff --git a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
|
||||
index 64e6efe..e61485e 100644
|
||||
index f1255e8..a9b49e5 100644
|
||||
--- a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
|
||||
+++ b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
|
||||
@@ -111,12 +111,12 @@ class MediaLibraryModule : Module() {
|
||||
@@ -112,11 +112,9 @@ class MediaLibraryModule : Module() {
|
||||
}
|
||||
|
||||
AsyncFunction("createAssetAsync") { localUri: String, promise: Promise ->
|
||||
AsyncFunction("createAssetAsync") { localUri: String, albumId: String?, promise: Promise ->
|
||||
- throwUnlessPermissionsGranted {
|
||||
+ //throwUnlessPermissionsGranted {
|
||||
withModuleScope(promise) {
|
||||
CreateAsset(context, localUri, promise)
|
||||
.execute()
|
||||
}
|
||||
- }
|
||||
+ //}
|
||||
- withModuleScope(promise) {
|
||||
- CreateAssetWithAlbumId(context, localUri, promise, true, albumId)
|
||||
- .execute()
|
||||
- }
|
||||
+ withModuleScope(promise) {
|
||||
+ CreateAssetWithAlbumId(context, localUri, promise, true, albumId)
|
||||
+ .execute()
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("addAssetsToAlbumAsync") { assetsId: List<String>, albumId: String, copyToAlbum: Boolean, promise: Promise ->
|
||||
@@ -1,169 +0,0 @@
|
||||
diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle
|
||||
index 13bffbb..5ebbede 100644
|
||||
--- a/node_modules/expo-notifications/android/build.gradle
|
||||
+++ b/node_modules/expo-notifications/android/build.gradle
|
||||
@@ -46,6 +46,7 @@ dependencies {
|
||||
implementation 'com.google.firebase:firebase-messaging:24.0.1'
|
||||
|
||||
implementation '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/notifications/interfaces/INotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
index 7b99e6c..45a450d 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
@@ -15,6 +15,7 @@ import org.json.JSONObject
|
||||
* This interface exists to provide a common API for both classes.
|
||||
* */
|
||||
interface INotificationContent : Parcelable {
|
||||
+ val channelId: String?
|
||||
val title: String?
|
||||
val text: String?
|
||||
val subText: String?
|
||||
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 191b64e..fe8b3c5 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
|
||||
@@ -35,6 +35,7 @@ import kotlin.coroutines.Continuation;
|
||||
* Refactoring this class may require a migration strategy for the data stored in SharedPreferences.
|
||||
*/
|
||||
public class NotificationContent implements Parcelable, Serializable, INotificationContent {
|
||||
+ private String mChannelId;
|
||||
private String mTitle;
|
||||
private String mText;
|
||||
private String mSubtitle;
|
||||
@@ -65,6 +66,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
}
|
||||
};
|
||||
|
||||
+ @Nullable
|
||||
+ public String getChannelId() {
|
||||
+ return mChannelId;
|
||||
+ }
|
||||
+
|
||||
@Nullable
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
@@ -158,6 +164,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
}
|
||||
|
||||
protected NotificationContent(Parcel in) {
|
||||
+ mChannelId = in.readString();
|
||||
mTitle = in.readString();
|
||||
mText = in.readString();
|
||||
mSubtitle = in.readString();
|
||||
@@ -183,6 +190,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
+ dest.writeString(mChannelId);
|
||||
dest.writeString(mTitle);
|
||||
dest.writeString(mText);
|
||||
dest.writeString(mSubtitle);
|
||||
@@ -203,6 +211,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
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);
|
||||
@@ -285,6 +294,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
useDefaultVibrationPattern();
|
||||
}
|
||||
|
||||
+ public Builder setChannelId(String channelId) {
|
||||
+ content.mChannelId = channelId;
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
public Builder setTitle(String title) {
|
||||
content.mTitle = title;
|
||||
return this;
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
index 39b5aad..e50797d 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
@@ -11,6 +11,9 @@ import org.json.JSONObject
|
||||
* */
|
||||
@JvmInline
|
||||
value class NotificationData(private val data: Map<String, String>) {
|
||||
+ val channelId: String?
|
||||
+ get() = data["channelId"]
|
||||
+
|
||||
val title: String?
|
||||
get() = data["title"]
|
||||
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
index d2cc6cf..6a48ff2 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
@@ -31,6 +31,8 @@ class RemoteNotificationContent(private val remoteMessage: RemoteMessage) : INot
|
||||
return remoteMessage.notification?.imageUrl != null
|
||||
}
|
||||
|
||||
+ override val channelId = remoteMessage.notification?.channelId ?: notificationData.channelId
|
||||
+
|
||||
override val title = remoteMessage.notification?.title ?: notificationData.title
|
||||
|
||||
override val text = remoteMessage.notification?.body ?: notificationData.message
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
index 8ca6ec5..57c3599 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
@@ -101,6 +101,9 @@ open class ExpoNotificationBuilder(
|
||||
builder.setOngoing(content.isSticky)
|
||||
|
||||
// see "Notification anatomy" https://developer.android.com/develop/ui/views/notifications#Templates
|
||||
+ content.channelId?.let {
|
||||
+ builder.setChannelId(it)
|
||||
+ }
|
||||
builder.setContentTitle(content.title)
|
||||
builder.setContentText(content.text)
|
||||
builder.setSubText(content.subText)
|
||||
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 9f22441..5f92f80 100644
|
||||
--- 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
|
||||
@@ -2,6 +2,9 @@ package expo.modules.notifications.service.delegates
|
||||
|
||||
import android.content.Context
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandler
|
||||
+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandlerInterface
|
||||
+import expo.modules.backgroundnotificationhandler.ExpoBackgroundNotificationHandlerModule
|
||||
import expo.modules.interfaces.taskManager.TaskServiceProviderHelper
|
||||
import expo.modules.notifications.notifications.RemoteMessageSerializer
|
||||
import expo.modules.notifications.notifications.background.BackgroundRemoteNotificationTaskConsumer
|
||||
@@ -17,7 +20,8 @@ import expo.modules.notifications.tokens.interfaces.FirebaseTokenListener
|
||||
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 {
|
||||
// Unfortunately we cannot save state between instances of a service other way
|
||||
// than by static properties. Fortunately, using weak references we can
|
||||
@@ -94,8 +98,17 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM
|
||||
DebugLogging.logRemoteMessage("FirebaseMessagingDelegate.onMessageReceived: message", remoteMessage)
|
||||
val notification = createNotification(remoteMessage)
|
||||
DebugLogging.logNotification("FirebaseMessagingDelegate.onMessageReceived: notification", notification)
|
||||
- NotificationsService.receive(context, notification)
|
||||
- runTaskManagerTasks(remoteMessage)
|
||||
+
|
||||
+ if (!ExpoBackgroundNotificationHandlerModule.isForegrounded) {
|
||||
+ BackgroundNotificationHandler(context, this).handleMessage(remoteMessage)
|
||||
+ } else {
|
||||
+ showMessage(remoteMessage)
|
||||
+ runTaskManagerTasks(remoteMessage)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ override fun showMessage(remoteMessage: RemoteMessage) {
|
||||
+ NotificationsService.receive(context, createNotification(remoteMessage))
|
||||
}
|
||||
|
||||
private fun runTaskManagerTasks(remoteMessage: RemoteMessage) {
|
||||
@@ -0,0 +1,992 @@
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/checksums/checksums.lock b/node_modules/expo-notifications/android/.gradle/8.10/checksums/checksums.lock
|
||||
new file mode 100644
|
||||
index 0000000..883ef6a
|
||||
Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/8.10/checksums/checksums.lock differ
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/dependencies-accessors/gc.properties b/node_modules/expo-notifications/android/.gradle/8.10/dependencies-accessors/gc.properties
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/fileChanges/last-build.bin b/node_modules/expo-notifications/android/.gradle/8.10/fileChanges/last-build.bin
|
||||
new file mode 100644
|
||||
index 0000000..f76dd23
|
||||
Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/8.10/fileChanges/last-build.bin differ
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/fileHashes/fileHashes.lock b/node_modules/expo-notifications/android/.gradle/8.10/fileHashes/fileHashes.lock
|
||||
new file mode 100644
|
||||
index 0000000..774caf7
|
||||
Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/8.10/fileHashes/fileHashes.lock differ
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/gc.properties b/node_modules/expo-notifications/android/.gradle/8.10/gc.properties
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock
|
||||
new file mode 100644
|
||||
index 0000000..a3c1514
|
||||
Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/cache.properties b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/cache.properties
|
||||
new file mode 100644
|
||||
index 0000000..0e5b4da
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/cache.properties
|
||||
@@ -0,0 +1,2 @@
|
||||
+#Thu Apr 24 20:44:36 PDT 2025
|
||||
+gradle.version=8.10
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/config.properties b/node_modules/expo-notifications/android/.gradle/config.properties
|
||||
new file mode 100644
|
||||
index 0000000..0bd71c6
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.gradle/config.properties
|
||||
@@ -0,0 +1,2 @@
|
||||
+#Thu Apr 24 20:44:32 PDT 2025
|
||||
+java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/vcs-1/gc.properties b/node_modules/expo-notifications/android/.gradle/vcs-1/gc.properties
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/.gitignore b/node_modules/expo-notifications/android/.idea/.gitignore
|
||||
new file mode 100644
|
||||
index 0000000..26d3352
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/.gitignore
|
||||
@@ -0,0 +1,3 @@
|
||||
+# Default ignored files
|
||||
+/shelf/
|
||||
+/workspace.xml
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/AndroidProjectSystem.xml b/node_modules/expo-notifications/android/.idea/AndroidProjectSystem.xml
|
||||
new file mode 100644
|
||||
index 0000000..4a53bee
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/AndroidProjectSystem.xml
|
||||
@@ -0,0 +1,6 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="AndroidProjectSystem">
|
||||
+ <option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/caches/deviceStreaming.xml b/node_modules/expo-notifications/android/.idea/caches/deviceStreaming.xml
|
||||
new file mode 100644
|
||||
index 0000000..9e9ba09
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/caches/deviceStreaming.xml
|
||||
@@ -0,0 +1,607 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="DeviceStreaming">
|
||||
+ <option name="deviceSelectionList">
|
||||
+ <list>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="27" />
|
||||
+ <option name="brand" value="DOCOMO" />
|
||||
+ <option name="codename" value="F01L" />
|
||||
+ <option name="id" value="F01L" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="FUJITSU" />
|
||||
+ <option name="name" value="F-01L" />
|
||||
+ <option name="screenDensity" value="360" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1280" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="OnePlus" />
|
||||
+ <option name="codename" value="OP5552L1" />
|
||||
+ <option name="id" value="OP5552L1" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="OnePlus" />
|
||||
+ <option name="name" value="CPH2415" />
|
||||
+ <option name="screenDensity" value="480" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2412" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="OPPO" />
|
||||
+ <option name="codename" value="OP573DL1" />
|
||||
+ <option name="id" value="OP573DL1" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="OPPO" />
|
||||
+ <option name="name" value="CPH2557" />
|
||||
+ <option name="screenDensity" value="480" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="28" />
|
||||
+ <option name="brand" value="DOCOMO" />
|
||||
+ <option name="codename" value="SH-01L" />
|
||||
+ <option name="id" value="SH-01L" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="SHARP" />
|
||||
+ <option name="name" value="AQUOS sense2 SH-01L" />
|
||||
+ <option name="screenDensity" value="480" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2160" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="Lenovo" />
|
||||
+ <option name="codename" value="TB370FU" />
|
||||
+ <option name="formFactor" value="Tablet" />
|
||||
+ <option name="id" value="TB370FU" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Lenovo" />
|
||||
+ <option name="name" value="Tab P12" />
|
||||
+ <option name="screenDensity" value="340" />
|
||||
+ <option name="screenX" value="1840" />
|
||||
+ <option name="screenY" value="2944" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="a15" />
|
||||
+ <option name="id" value="a15" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="A15" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="a35x" />
|
||||
+ <option name="id" value="a35x" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="A35" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="31" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="a51" />
|
||||
+ <option name="id" value="a51" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy A51" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="akita" />
|
||||
+ <option name="id" value="akita" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 8a" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="arcfox" />
|
||||
+ <option name="id" value="arcfox" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="razr plus 2024" />
|
||||
+ <option name="screenDensity" value="360" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="1272" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="austin" />
|
||||
+ <option name="id" value="austin" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="moto g 5G (2022)" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1600" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="b0q" />
|
||||
+ <option name="id" value="b0q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S22 Ultra" />
|
||||
+ <option name="screenDensity" value="600" />
|
||||
+ <option name="screenX" value="1440" />
|
||||
+ <option name="screenY" value="3088" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="32" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="bluejay" />
|
||||
+ <option name="id" value="bluejay" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 6a" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="caiman" />
|
||||
+ <option name="id" value="caiman" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9 Pro" />
|
||||
+ <option name="screenDensity" value="360" />
|
||||
+ <option name="screenX" value="960" />
|
||||
+ <option name="screenY" value="2142" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="comet" />
|
||||
+ <option name="default" value="true" />
|
||||
+ <option name="id" value="comet" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9 Pro Fold" />
|
||||
+ <option name="screenDensity" value="390" />
|
||||
+ <option name="screenX" value="2076" />
|
||||
+ <option name="screenY" value="2152" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="29" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="crownqlteue" />
|
||||
+ <option name="id" value="crownqlteue" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Note9" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="2220" />
|
||||
+ <option name="screenY" value="1080" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="dm2q" />
|
||||
+ <option name="id" value="dm2q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="S23 Plus" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="dm3q" />
|
||||
+ <option name="id" value="dm3q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S23 Ultra" />
|
||||
+ <option name="screenDensity" value="600" />
|
||||
+ <option name="screenX" value="1440" />
|
||||
+ <option name="screenY" value="3088" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="e1q" />
|
||||
+ <option name="default" value="true" />
|
||||
+ <option name="id" value="e1q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S24" />
|
||||
+ <option name="screenDensity" value="480" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="e3q" />
|
||||
+ <option name="id" value="e3q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S24 Ultra" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1440" />
|
||||
+ <option name="screenY" value="3120" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="eos" />
|
||||
+ <option name="id" value="eos" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Eos" />
|
||||
+ <option name="screenDensity" value="320" />
|
||||
+ <option name="screenX" value="384" />
|
||||
+ <option name="screenY" value="384" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="felix" />
|
||||
+ <option name="id" value="felix" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Fold" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="2208" />
|
||||
+ <option name="screenY" value="1840" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="felix" />
|
||||
+ <option name="id" value="felix" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Fold" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="2208" />
|
||||
+ <option name="screenY" value="1840" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="felix_camera" />
|
||||
+ <option name="id" value="felix_camera" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Fold (Camera-enabled)" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="2208" />
|
||||
+ <option name="screenY" value="1840" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="fogona" />
|
||||
+ <option name="id" value="fogona" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="moto g play - 2024" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1600" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="g0q" />
|
||||
+ <option name="id" value="g0q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="SM-S906U1" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gta9pwifi" />
|
||||
+ <option name="id" value="gta9pwifi" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="SM-X210" />
|
||||
+ <option name="screenDensity" value="240" />
|
||||
+ <option name="screenX" value="1200" />
|
||||
+ <option name="screenY" value="1920" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gts7xllite" />
|
||||
+ <option name="id" value="gts7xllite" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="SM-T738U" />
|
||||
+ <option name="screenDensity" value="340" />
|
||||
+ <option name="screenX" value="1600" />
|
||||
+ <option name="screenY" value="2560" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gts8uwifi" />
|
||||
+ <option name="formFactor" value="Tablet" />
|
||||
+ <option name="id" value="gts8uwifi" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Tab S8 Ultra" />
|
||||
+ <option name="screenDensity" value="320" />
|
||||
+ <option name="screenX" value="1848" />
|
||||
+ <option name="screenY" value="2960" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gts8wifi" />
|
||||
+ <option name="formFactor" value="Tablet" />
|
||||
+ <option name="id" value="gts8wifi" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Tab S8" />
|
||||
+ <option name="screenDensity" value="274" />
|
||||
+ <option name="screenX" value="1600" />
|
||||
+ <option name="screenY" value="2560" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gts9fe" />
|
||||
+ <option name="id" value="gts9fe" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Tab S9 FE 5G" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="1440" />
|
||||
+ <option name="screenY" value="2304" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="husky" />
|
||||
+ <option name="id" value="husky" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 8 Pro" />
|
||||
+ <option name="screenDensity" value="390" />
|
||||
+ <option name="screenX" value="1008" />
|
||||
+ <option name="screenY" value="2244" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="30" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="java" />
|
||||
+ <option name="id" value="java" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="G20" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1600" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="komodo" />
|
||||
+ <option name="id" value="komodo" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9 Pro XL" />
|
||||
+ <option name="screenDensity" value="360" />
|
||||
+ <option name="screenX" value="1008" />
|
||||
+ <option name="screenY" value="2244" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="lynx" />
|
||||
+ <option name="id" value="lynx" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 7a" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="maui" />
|
||||
+ <option name="id" value="maui" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="moto g play - 2023" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1600" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="o1q" />
|
||||
+ <option name="id" value="o1q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S21" />
|
||||
+ <option name="screenDensity" value="421" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="31" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="oriole" />
|
||||
+ <option name="id" value="oriole" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 6" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="panther" />
|
||||
+ <option name="id" value="panther" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 7" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="q5q" />
|
||||
+ <option name="id" value="q5q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Z Fold5" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1812" />
|
||||
+ <option name="screenY" value="2176" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="q6q" />
|
||||
+ <option name="id" value="q6q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Z Fold6" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1856" />
|
||||
+ <option name="screenY" value="2160" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="30" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="r11" />
|
||||
+ <option name="formFactor" value="Wear OS" />
|
||||
+ <option name="id" value="r11" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Watch" />
|
||||
+ <option name="screenDensity" value="320" />
|
||||
+ <option name="screenX" value="384" />
|
||||
+ <option name="screenY" value="384" />
|
||||
+ <option name="type" value="WEAR_OS" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="r11q" />
|
||||
+ <option name="id" value="r11q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="SM-S711U" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="30" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="redfin" />
|
||||
+ <option name="id" value="redfin" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 5" />
|
||||
+ <option name="screenDensity" value="440" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="shiba" />
|
||||
+ <option name="id" value="shiba" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 8" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="t2q" />
|
||||
+ <option name="id" value="t2q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S21 Plus" />
|
||||
+ <option name="screenDensity" value="394" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="tangorpro" />
|
||||
+ <option name="formFactor" value="Tablet" />
|
||||
+ <option name="id" value="tangorpro" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Tablet" />
|
||||
+ <option name="screenDensity" value="320" />
|
||||
+ <option name="screenX" value="1600" />
|
||||
+ <option name="screenY" value="2560" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="tokay" />
|
||||
+ <option name="default" value="true" />
|
||||
+ <option name="id" value="tokay" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2424" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="35" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="tokay" />
|
||||
+ <option name="default" value="true" />
|
||||
+ <option name="id" value="tokay" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2424" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ </list>
|
||||
+ </option>
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/gradle.xml b/node_modules/expo-notifications/android/.idea/gradle.xml
|
||||
new file mode 100644
|
||||
index 0000000..b838237
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/gradle.xml
|
||||
@@ -0,0 +1,12 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="GradleSettings">
|
||||
+ <option name="linkedExternalProjectsSettings">
|
||||
+ <GradleProjectSettings>
|
||||
+ <option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
+ <option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
+ <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
+ </GradleProjectSettings>
|
||||
+ </option>
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/migrations.xml b/node_modules/expo-notifications/android/.idea/migrations.xml
|
||||
new file mode 100644
|
||||
index 0000000..f8051a6
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/migrations.xml
|
||||
@@ -0,0 +1,10 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="ProjectMigrations">
|
||||
+ <option name="MigrateToGradleLocalJavaHome">
|
||||
+ <set>
|
||||
+ <option value="$PROJECT_DIR$" />
|
||||
+ </set>
|
||||
+ </option>
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/misc.xml b/node_modules/expo-notifications/android/.idea/misc.xml
|
||||
new file mode 100644
|
||||
index 0000000..3040d03
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/misc.xml
|
||||
@@ -0,0 +1,10 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
+ <component name="ProjectRootManager">
|
||||
+ <output url="file://$PROJECT_DIR$/build/classes" />
|
||||
+ </component>
|
||||
+ <component name="ProjectType">
|
||||
+ <option name="id" value="Android" />
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/runConfigurations.xml b/node_modules/expo-notifications/android/.idea/runConfigurations.xml
|
||||
new file mode 100644
|
||||
index 0000000..16660f1
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/runConfigurations.xml
|
||||
@@ -0,0 +1,17 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="RunConfigurationProducerService">
|
||||
+ <option name="ignoredProducers">
|
||||
+ <set>
|
||||
+ <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
+ <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
+ <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
+ </set>
|
||||
+ </option>
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/workspace.xml b/node_modules/expo-notifications/android/.idea/workspace.xml
|
||||
new file mode 100644
|
||||
index 0000000..df26928
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/workspace.xml
|
||||
@@ -0,0 +1,47 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="AutoImportSettings">
|
||||
+ <option name="autoReloadType" value="NONE" />
|
||||
+ </component>
|
||||
+ <component name="ChangeListManager">
|
||||
+ <list default="true" id="fed6a9c0-2e93-4b6e-953a-d1cd1e93b59f" name="Changes" comment="" />
|
||||
+ <option name="SHOW_DIALOG" value="false" />
|
||||
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
+ <option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
+ </component>
|
||||
+ <component name="ClangdSettings">
|
||||
+ <option name="formatViaClangd" value="false" />
|
||||
+ </component>
|
||||
+ <component name="ProjectColorInfo"><![CDATA[{
|
||||
+ "associatedIndex": 4
|
||||
+}]]></component>
|
||||
+ <component name="ProjectId" id="2wCjuanPzVGKP91vdmftQVgUlaM" />
|
||||
+ <component name="ProjectViewState">
|
||||
+ <option name="hideEmptyMiddlePackages" value="true" />
|
||||
+ <option name="showLibraryContents" value="true" />
|
||||
+ </component>
|
||||
+ <component name="PropertiesComponent"><![CDATA[{
|
||||
+ "keyToString": {
|
||||
+ "RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
+ "RunOnceActivity.cidr.known.project.marker": "true",
|
||||
+ "RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||
+ "android.gradle.sync.needed": "true",
|
||||
+ "cf.first.check.clang-format": "false",
|
||||
+ "cidr.known.project.marker": "true",
|
||||
+ "kotlin-language-version-configured": "true",
|
||||
+ "last_opened_file_path": "/Users/hailey/bsky/social-app/node_modules/expo-notifications/android"
|
||||
+ }
|
||||
+}]]></component>
|
||||
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
+ <component name="TaskManager">
|
||||
+ <task active="true" id="Default" summary="Default task">
|
||||
+ <changelist id="fed6a9c0-2e93-4b6e-953a-d1cd1e93b59f" name="Changes" comment="" />
|
||||
+ <created>1745552672693</created>
|
||||
+ <option name="number" value="Default" />
|
||||
+ <option name="presentableId" value="Default" />
|
||||
+ <updated>1745552672693</updated>
|
||||
+ </task>
|
||||
+ <servers />
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle
|
||||
index bc479ee..1ebfa00 100644
|
||||
--- a/node_modules/expo-notifications/android/build.gradle
|
||||
+++ b/node_modules/expo-notifications/android/build.gradle
|
||||
@@ -42,6 +42,7 @@ dependencies {
|
||||
implementation 'com.google.firebase:firebase-messaging:24.0.1'
|
||||
|
||||
implementation '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/local.properties b/node_modules/expo-notifications/android/local.properties
|
||||
new file mode 100644
|
||||
index 0000000..ab4c86d
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/local.properties
|
||||
@@ -0,0 +1,8 @@
|
||||
+## This file must *NOT* be checked into Version Control Systems,
|
||||
+# as it contains information specific to your local configuration.
|
||||
+#
|
||||
+# Location of the SDK. This is only used by Gradle.
|
||||
+# For customization when using a Version Control System, please read the
|
||||
+# header note.
|
||||
+#Thu Apr 24 20:44:32 PDT 2025
|
||||
+sdk.dir=/Users/hailey/Library/Android/sdk
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
index 7b99e6c..45a450d 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
@@ -15,6 +15,7 @@ import org.json.JSONObject
|
||||
* This interface exists to provide a common API for both classes.
|
||||
* */
|
||||
interface INotificationContent : Parcelable {
|
||||
+ val channelId: String?
|
||||
val title: String?
|
||||
val text: String?
|
||||
val subText: String?
|
||||
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 191b64e..fe8b3c5 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
|
||||
@@ -35,6 +35,7 @@ import kotlin.coroutines.Continuation;
|
||||
* Refactoring this class may require a migration strategy for the data stored in SharedPreferences.
|
||||
*/
|
||||
public class NotificationContent implements Parcelable, Serializable, INotificationContent {
|
||||
+ private String mChannelId;
|
||||
private String mTitle;
|
||||
private String mText;
|
||||
private String mSubtitle;
|
||||
@@ -65,6 +66,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
}
|
||||
};
|
||||
|
||||
+ @Nullable
|
||||
+ public String getChannelId() {
|
||||
+ return mChannelId;
|
||||
+ }
|
||||
+
|
||||
@Nullable
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
@@ -158,6 +164,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
}
|
||||
|
||||
protected NotificationContent(Parcel in) {
|
||||
+ mChannelId = in.readString();
|
||||
mTitle = in.readString();
|
||||
mText = in.readString();
|
||||
mSubtitle = in.readString();
|
||||
@@ -183,6 +190,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
+ dest.writeString(mChannelId);
|
||||
dest.writeString(mTitle);
|
||||
dest.writeString(mText);
|
||||
dest.writeString(mSubtitle);
|
||||
@@ -203,6 +211,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
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);
|
||||
@@ -285,6 +294,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
useDefaultVibrationPattern();
|
||||
}
|
||||
|
||||
+ public Builder setChannelId(String channelId) {
|
||||
+ content.mChannelId = channelId;
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
public Builder setTitle(String title) {
|
||||
content.mTitle = title;
|
||||
return this;
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
index 3af254c..3c77e9d 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
@@ -11,6 +11,9 @@ import org.json.JSONObject
|
||||
* */
|
||||
@JvmInline
|
||||
value class NotificationData(private val data: Map<String, String>) {
|
||||
+ val channelId: String?
|
||||
+ get() = data["channelId"]
|
||||
+
|
||||
val title: String?
|
||||
get() = data["title"]
|
||||
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
index d2cc6cf..6a48ff2 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
@@ -31,6 +31,8 @@ class RemoteNotificationContent(private val remoteMessage: RemoteMessage) : INot
|
||||
return remoteMessage.notification?.imageUrl != null
|
||||
}
|
||||
|
||||
+ override val channelId = remoteMessage.notification?.channelId ?: notificationData.channelId
|
||||
+
|
||||
override val title = remoteMessage.notification?.title ?: notificationData.title
|
||||
|
||||
override val text = remoteMessage.notification?.body ?: notificationData.message
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
index 98f003f..2f745e8 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
@@ -101,6 +101,9 @@ open class ExpoNotificationBuilder(
|
||||
builder.setOngoing(content.isSticky)
|
||||
|
||||
// see "Notification anatomy" https://developer.android.com/develop/ui/views/notifications#Templates
|
||||
+ content.channelId?.let {
|
||||
+ builder.setChannelId(it)
|
||||
+ }
|
||||
builder.setContentTitle(content.title)
|
||||
builder.setContentText(content.text)
|
||||
builder.setSubText(content.subText)
|
||||
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 90ca4ff..9d4cb09 100644
|
||||
--- 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
|
||||
@@ -3,6 +3,9 @@ package expo.modules.notifications.service.delegates
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandler
|
||||
+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandlerInterface
|
||||
+import expo.modules.backgroundnotificationhandler.ExpoBackgroundNotificationHandlerModule
|
||||
import expo.modules.interfaces.taskManager.TaskServiceProviderHelper
|
||||
import expo.modules.notifications.notifications.RemoteMessageSerializer
|
||||
import expo.modules.notifications.notifications.background.BackgroundRemoteNotificationTaskConsumer
|
||||
@@ -18,7 +21,7 @@ import expo.modules.notifications.tokens.interfaces.FirebaseTokenListener
|
||||
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 {
|
||||
// Unfortunately we cannot save state between instances of a service other way
|
||||
// than by static properties. Fortunately, using weak references we can
|
||||
@@ -105,8 +108,19 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM
|
||||
DebugLogging.logRemoteMessage("FirebaseMessagingDelegate.onMessageReceived: message", remoteMessage)
|
||||
val notification = createNotification(remoteMessage)
|
||||
DebugLogging.logNotification("FirebaseMessagingDelegate.onMessageReceived: notification", notification)
|
||||
- NotificationsService.receive(context, notification)
|
||||
- runTaskManagerTasks(context.applicationContext, RemoteMessageSerializer.toBundle(remoteMessage))
|
||||
+ if (!ExpoBackgroundNotificationHandlerModule.isForegrounded) {
|
||||
+ BackgroundNotificationHandler(context, this).handleMessage(remoteMessage)
|
||||
+ } else {
|
||||
+ NotificationsService.receive(context, notification)
|
||||
+ runTaskManagerTasks(
|
||||
+ context.applicationContext,
|
||||
+ RemoteMessageSerializer.toBundle(remoteMessage)
|
||||
+ )
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ override fun showMessage(remoteMessage: RemoteMessage) {
|
||||
+ NotificationsService.receive(context, createNotification(remoteMessage))
|
||||
}
|
||||
|
||||
protected fun createNotification(remoteMessage: RemoteMessage): Notification {
|
||||
@@ -1,366 +0,0 @@
|
||||
diff --git a/node_modules/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts b/node_modules/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts
|
||||
index 62f52a7..ca30165 100644
|
||||
--- a/node_modules/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts
|
||||
+++ b/node_modules/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts
|
||||
@@ -426,9 +426,10 @@ export interface ViewStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle {
|
||||
*/
|
||||
pointerEvents?: 'box-none' | 'none' | 'box-only' | 'auto' | undefined;
|
||||
isolation?: 'auto' | 'isolate' | undefined;
|
||||
- cursor?: CursorValue | undefined;
|
||||
+ cursor?: CursorValue | string | undefined;
|
||||
boxShadow?: ReadonlyArray<BoxShadowValue> | string | undefined;
|
||||
filter?: ReadonlyArray<FilterFunction> | string | undefined;
|
||||
+ transformOrigin?: string | (string | number)[] | undefined;
|
||||
}
|
||||
|
||||
export type FontVariant =
|
||||
@@ -536,7 +537,11 @@ export interface TextStyle extends TextStyleIOS, TextStyleAndroid, ViewStyle {
|
||||
textShadowOffset?: {width: number; height: number} | undefined;
|
||||
textShadowRadius?: number | undefined;
|
||||
textTransform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase' | undefined;
|
||||
- userSelect?: 'auto' | 'none' | 'text' | 'contain' | 'all' | undefined;
|
||||
+ userSelect?: 'auto' | 'none' | 'text' | 'contain' | 'all' | string | undefined;
|
||||
+ cursor?: CursorValue | string | undefined;
|
||||
+ boxShadow?: ReadonlyArray<BoxShadowValue> | string | undefined;
|
||||
+ filter?: ReadonlyArray<FilterFunction> | string | undefined;
|
||||
+ transformOrigin?: string | (string | number)[] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -558,5 +563,8 @@ export interface ImageStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle {
|
||||
tintColor?: ColorValue | undefined;
|
||||
opacity?: AnimatableNumericValue | undefined;
|
||||
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | undefined;
|
||||
- cursor?: CursorValue | undefined;
|
||||
+ cursor?: CursorValue | string | undefined;
|
||||
+ boxShadow?: ReadonlyArray<BoxShadowValue> | string | undefined;
|
||||
+ filter?: ReadonlyArray<FilterFunction> | string | undefined;
|
||||
+ transformOrigin?: string | (string | number)[] | undefined;
|
||||
}
|
||||
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm
|
||||
index 93af874..106f8ec 100644
|
||||
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm
|
||||
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm
|
||||
@@ -66,28 +66,51 @@
|
||||
* ScrollView, we force it to be centered, but when you zoom or the content otherwise
|
||||
* becomes larger than the ScrollView, there is no padding around the content but it
|
||||
* can still fill the whole view.
|
||||
+ * This implementation is based on https://petersteinberger.com/blog/2013/how-to-center-uiscrollview/.
|
||||
*/
|
||||
-- (void)setContentOffset:(CGPoint)contentOffset
|
||||
+-(void)centerContentIfNeeded
|
||||
{
|
||||
- if (_isSetContentOffsetDisabled) {
|
||||
+ if (!_centerContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
- if (_centerContent && !CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
|
||||
- CGSize scrollViewSize = self.bounds.size;
|
||||
- if (self.contentSize.width <= scrollViewSize.width) {
|
||||
- contentOffset.x = -(scrollViewSize.width - self.contentSize.width) / 2.0;
|
||||
- }
|
||||
- if (self.contentSize.height <= scrollViewSize.height) {
|
||||
- contentOffset.y = -(scrollViewSize.height - self.contentSize.height) / 2.0;
|
||||
- }
|
||||
+ CGSize contentSize = self.contentSize;
|
||||
+ CGSize boundsSize = self.bounds.size;
|
||||
+ if (CGSizeEqualToSize(contentSize, CGSizeZero) ||
|
||||
+ CGSizeEqualToSize(boundsSize, CGSizeZero)) {
|
||||
+ return;
|
||||
}
|
||||
|
||||
+ CGFloat top = 0, left = 0;
|
||||
+ if (contentSize.width < boundsSize.width) {
|
||||
+ left = (boundsSize.width - contentSize.width) * 0.5f;
|
||||
+ }
|
||||
+ if (contentSize.height < boundsSize.height) {
|
||||
+ top = (boundsSize.height - contentSize.height) * 0.5f;
|
||||
+ }
|
||||
+ self.contentInset = UIEdgeInsetsMake(top, left, top, left);
|
||||
+}
|
||||
+
|
||||
+- (void)setContentOffset:(CGPoint)contentOffset
|
||||
+{
|
||||
+ if (_isSetContentOffsetDisabled) {
|
||||
+ return;
|
||||
+ }
|
||||
super.contentOffset = CGPointMake(
|
||||
RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"),
|
||||
RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
|
||||
}
|
||||
|
||||
+- (void)setFrame:(CGRect)frame {
|
||||
+ [super setFrame:frame];
|
||||
+ [self centerContentIfNeeded];
|
||||
+}
|
||||
+
|
||||
+- (void)didAddSubview:(UIView *)subview {
|
||||
+ [super didAddSubview:subview];
|
||||
+ [self centerContentIfNeeded];
|
||||
+}
|
||||
+
|
||||
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
|
||||
{
|
||||
if ([_overridingDelegate respondsToSelector:@selector(touchesShouldCancelInContentView:)]) {
|
||||
@@ -257,6 +280,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
+- (void)scrollViewDidZoom:(__unused UIScrollView *)scrollView {
|
||||
+ [self centerContentIfNeeded];
|
||||
+}
|
||||
+
|
||||
#pragma mark -
|
||||
|
||||
- (BOOL)isHorizontal:(UIScrollView *)scrollView
|
||||
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
index e9b330f..ec5f58c 100644
|
||||
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
@@ -15,5 +15,8 @@
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
|
||||
@property (nonatomic, weak) UIScrollView *scrollView;
|
||||
+@property (nonatomic, copy) UIColor *customTintColor;
|
||||
+
|
||||
+- (void)forwarderBeginRefreshing;
|
||||
|
||||
@end
|
||||
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
index 53bfd04..ff1b1ed 100644
|
||||
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
@@ -23,6 +23,7 @@
|
||||
UIColor *_titleColor;
|
||||
CGFloat _progressViewOffset;
|
||||
BOOL _hasMovedToWindow;
|
||||
+ UIColor *_customTintColor;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
@@ -58,6 +59,12 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
|
||||
_isInitialRender = false;
|
||||
}
|
||||
|
||||
+- (void)didMoveToSuperview
|
||||
+{
|
||||
+ [super didMoveToSuperview];
|
||||
+ [self setTintColor:_customTintColor];
|
||||
+}
|
||||
+
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
@@ -221,4 +228,50 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
|
||||
}
|
||||
}
|
||||
|
||||
+// Fix for https://github.com/facebook/react-native/issues/43388
|
||||
+// A bug in iOS 17.4 causes the haptic to not play when refreshing if the tintColor
|
||||
+// is set before the refresh control gets added to the scrollview. We'll call this
|
||||
+// function whenever the superview changes. We'll also call it if the value of customTintColor
|
||||
+// changes.
|
||||
+- (void)setTintColor:(UIColor *)tintColor
|
||||
+{
|
||||
+ if ([self.superview isKindOfClass:[UIScrollView class]] && self.tintColor != tintColor) {
|
||||
+ [super setTintColor:tintColor];
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// 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
|
||||
+// function.
|
||||
+- (void)forwarderBeginRefreshing
|
||||
+{
|
||||
+ _refreshingProgrammatically = NO;
|
||||
+
|
||||
+ [self sizeToFit];
|
||||
+
|
||||
+ if (!self.scrollView) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ UIScrollView *scrollView = (UIScrollView *)self.scrollView;
|
||||
+
|
||||
+ [UIView animateWithDuration:0.3
|
||||
+ delay:0
|
||||
+ options:UIViewAnimationOptionBeginFromCurrentState
|
||||
+ animations:^(void) {
|
||||
+ // Whenever we call this method, the scrollview will always be at a position of
|
||||
+ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl
|
||||
+ [scrollView setContentOffset:CGPointMake(0, -65)];
|
||||
+ }
|
||||
+ completion:^(__unused BOOL finished) {
|
||||
+ [super beginRefreshing];
|
||||
+ [self setCurrentRefreshingState:super.refreshing];
|
||||
+
|
||||
+ if (self->_onRefresh) {
|
||||
+ self->_onRefresh(nil);
|
||||
+ }
|
||||
+ }
|
||||
+ ];
|
||||
+}
|
||||
+
|
||||
@end
|
||||
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
index 40aaf9c..1c60164 100644
|
||||
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
@@ -22,11 +22,12 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL)
|
||||
-RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(progressViewOffset, CGFloat)
|
||||
|
||||
+RCT_REMAP_VIEW_PROPERTY(tintColor, customTintColor, UIColor)
|
||||
+
|
||||
RCT_EXPORT_METHOD(setNativeRefreshing : (nonnull NSNumber *)viewTag toRefreshing : (BOOL)refreshing)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
index 6f41b5c..9b4f77f 100644
|
||||
--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
+++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
@@ -159,26 +159,8 @@
|
||||
return !shouldDisableScrollInteraction;
|
||||
}
|
||||
|
||||
-/*
|
||||
- * Automatically centers the content such that if the content is smaller than the
|
||||
- * ScrollView, we force it to be centered, but when you zoom or the content otherwise
|
||||
- * becomes larger than the ScrollView, there is no padding around the content but it
|
||||
- * can still fill the whole view.
|
||||
- */
|
||||
- (void)setContentOffset:(CGPoint)contentOffset
|
||||
{
|
||||
- UIView *contentView = [self contentView];
|
||||
- if (contentView && _centerContent && !CGSizeEqualToSize(contentView.frame.size, CGSizeZero)) {
|
||||
- CGSize subviewSize = contentView.frame.size;
|
||||
- CGSize scrollViewSize = self.bounds.size;
|
||||
- if (subviewSize.width <= scrollViewSize.width) {
|
||||
- contentOffset.x = -(scrollViewSize.width - subviewSize.width) / 2.0;
|
||||
- }
|
||||
- if (subviewSize.height <= scrollViewSize.height) {
|
||||
- contentOffset.y = -(scrollViewSize.height - subviewSize.height) / 2.0;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
super.contentOffset = CGPointMake(
|
||||
RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"),
|
||||
RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
|
||||
@@ -433,6 +415,11 @@ static inline void RCTApplyTransformationAccordingLayoutDirection(
|
||||
// Does nothing
|
||||
}
|
||||
|
||||
+- (void)setFrame:(CGRect)frame {
|
||||
+ [super setFrame:frame];
|
||||
+ [self centerContentIfNeeded];
|
||||
+}
|
||||
+
|
||||
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[super insertReactSubview:view atIndex:atIndex];
|
||||
@@ -449,6 +436,8 @@ static inline void RCTApplyTransformationAccordingLayoutDirection(
|
||||
_contentView = view;
|
||||
RCTApplyTransformationAccordingLayoutDirection(_contentView, self.reactLayoutDirection);
|
||||
[_scrollView addSubview:view];
|
||||
+
|
||||
+ [self centerContentIfNeeded];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,9 +647,46 @@ static inline void RCTApplyTransformationAccordingLayoutDirection(
|
||||
}
|
||||
|
||||
RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, onMomentumScrollBegin)
|
||||
-RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll)
|
||||
RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
|
||||
|
||||
+-(void)scrollViewDidZoom : (UIScrollView *)scrollView
|
||||
+{
|
||||
+ [self centerContentIfNeeded];
|
||||
+
|
||||
+ RCT_SEND_SCROLL_EVENT(onScroll, nil);
|
||||
+ RCT_FORWARD_SCROLL_EVENT(scrollViewDidZoom : scrollView);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Automatically centers the content such that if the content is smaller than the
|
||||
+ * ScrollView, we force it to be centered, but when you zoom or the content otherwise
|
||||
+ * becomes larger than the ScrollView, there is no padding around the content but it
|
||||
+ * can still fill the whole view.
|
||||
+ * This implementation is based on https://petersteinberger.com/blog/2013/how-to-center-uiscrollview/.
|
||||
+ */
|
||||
+-(void)centerContentIfNeeded
|
||||
+{
|
||||
+ if (!_scrollView.centerContent) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ CGSize contentSize = self.contentSize;
|
||||
+ CGSize boundsSize = self.bounds.size;
|
||||
+ if (CGSizeEqualToSize(contentSize, CGSizeZero) ||
|
||||
+ CGSizeEqualToSize(boundsSize, CGSizeZero)) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ CGFloat top = 0, left = 0;
|
||||
+ if (contentSize.width < boundsSize.width) {
|
||||
+ left = (boundsSize.width - contentSize.width) * 0.5f;
|
||||
+ }
|
||||
+ if (contentSize.height < boundsSize.height) {
|
||||
+ top = (boundsSize.height - contentSize.height) * 0.5f;
|
||||
+ }
|
||||
+ _scrollView.contentInset = UIEdgeInsetsMake(top, left, top, left);
|
||||
+}
|
||||
+
|
||||
- (void)addScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener
|
||||
{
|
||||
[_scrollListeners addObject:scrollListener];
|
||||
@@ -939,6 +965,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
|
||||
CGSize contentSize = self.contentSize;
|
||||
if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) {
|
||||
_scrollView.contentSize = contentSize;
|
||||
+ [self centerContentIfNeeded];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1061,6 +1088,22 @@ RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, showsHorizontalSc
|
||||
RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, showsVerticalScrollIndicator, BOOL)
|
||||
RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat);
|
||||
|
||||
+- (void)setScrollIndicatorInsets:(UIEdgeInsets)value
|
||||
+{
|
||||
+ [_scrollView setScrollIndicatorInsets:value];
|
||||
+}
|
||||
+
|
||||
+- (UIEdgeInsets)scrollIndicatorInsets
|
||||
+{
|
||||
+ UIEdgeInsets verticalScrollIndicatorInsets = [_scrollView verticalScrollIndicatorInsets];
|
||||
+ UIEdgeInsets horizontalScrollIndicatorInsets = [_scrollView horizontalScrollIndicatorInsets];
|
||||
+ return UIEdgeInsetsMake(
|
||||
+ verticalScrollIndicatorInsets.top,
|
||||
+ horizontalScrollIndicatorInsets.left,
|
||||
+ verticalScrollIndicatorInsets.bottom,
|
||||
+ horizontalScrollIndicatorInsets.right);
|
||||
+}
|
||||
+
|
||||
- (void)setAutomaticallyAdjustsScrollIndicatorInsets:(BOOL)automaticallyAdjusts API_AVAILABLE(ios(13.0))
|
||||
{
|
||||
// `automaticallyAdjustsScrollIndicatorInsets` is available since iOS 13.
|
||||
diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m
|
||||
index cd1e7eb..c1d0172 100644
|
||||
--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m
|
||||
+++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m
|
||||
@@ -83,6 +83,7 @@ RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
|
||||
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
||||
+RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(verticalScrollIndicatorInsets, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(scrollToOverflowEnabled, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
|
||||
@@ -0,0 +1,119 @@
|
||||
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
index e9b330f..ec5f58c 100644
|
||||
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
@@ -15,5 +15,8 @@
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
|
||||
@property (nonatomic, weak) UIScrollView *scrollView;
|
||||
+@property (nonatomic, copy) UIColor *customTintColor;
|
||||
+
|
||||
+- (void)forwarderBeginRefreshing;
|
||||
|
||||
@end
|
||||
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
index 53bfd04..ff1b1ed 100644
|
||||
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
@@ -23,6 +23,7 @@
|
||||
UIColor *_titleColor;
|
||||
CGFloat _progressViewOffset;
|
||||
BOOL _hasMovedToWindow;
|
||||
+ UIColor *_customTintColor;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
@@ -58,6 +59,12 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
|
||||
_isInitialRender = false;
|
||||
}
|
||||
|
||||
+- (void)didMoveToSuperview
|
||||
+{
|
||||
+ [super didMoveToSuperview];
|
||||
+ [self setTintColor:_customTintColor];
|
||||
+}
|
||||
+
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
@@ -221,4 +228,50 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
|
||||
}
|
||||
}
|
||||
|
||||
+// Fix for https://github.com/facebook/react-native/issues/43388
|
||||
+// A bug in iOS 17.4 causes the haptic to not play when refreshing if the tintColor
|
||||
+// is set before the refresh control gets added to the scrollview. We'll call this
|
||||
+// function whenever the superview changes. We'll also call it if the value of customTintColor
|
||||
+// changes.
|
||||
+- (void)setTintColor:(UIColor *)tintColor
|
||||
+{
|
||||
+ if ([self.superview isKindOfClass:[UIScrollView class]] && self.tintColor != tintColor) {
|
||||
+ [super setTintColor:tintColor];
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// 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
|
||||
+// function.
|
||||
+- (void)forwarderBeginRefreshing
|
||||
+{
|
||||
+ _refreshingProgrammatically = NO;
|
||||
+
|
||||
+ [self sizeToFit];
|
||||
+
|
||||
+ if (!self.scrollView) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ UIScrollView *scrollView = (UIScrollView *)self.scrollView;
|
||||
+
|
||||
+ [UIView animateWithDuration:0.3
|
||||
+ delay:0
|
||||
+ options:UIViewAnimationOptionBeginFromCurrentState
|
||||
+ animations:^(void) {
|
||||
+ // Whenever we call this method, the scrollview will always be at a position of
|
||||
+ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl
|
||||
+ [scrollView setContentOffset:CGPointMake(0, -65)];
|
||||
+ }
|
||||
+ completion:^(__unused BOOL finished) {
|
||||
+ [super beginRefreshing];
|
||||
+ [self setCurrentRefreshingState:super.refreshing];
|
||||
+
|
||||
+ if (self->_onRefresh) {
|
||||
+ self->_onRefresh(nil);
|
||||
+ }
|
||||
+ }
|
||||
+ ];
|
||||
+}
|
||||
+
|
||||
@end
|
||||
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
index 40aaf9c..1c60164 100644
|
||||
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
@@ -22,11 +22,12 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL)
|
||||
-RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(progressViewOffset, CGFloat)
|
||||
|
||||
+RCT_REMAP_VIEW_PROPERTY(tintColor, customTintColor, UIColor)
|
||||
+
|
||||
RCT_EXPORT_METHOD(setNativeRefreshing : (nonnull NSNumber *)viewTag toRefreshing : (BOOL)refreshing)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m
|
||||
index cd1e7eb..c1d0172 100644
|
||||
--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m
|
||||
+++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m
|
||||
@@ -83,6 +83,7 @@ RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
|
||||
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
||||
+RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(verticalScrollIndicatorInsets, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(scrollToOverflowEnabled, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
|
||||
@@ -11,7 +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.
|
||||
|
||||
## ScrollView centerContent fix
|
||||
|
||||
Includes https://github.com/facebook/react-native/pull/47591 early. Delete when it's in a release.
|
||||
@@ -1,47 +0,0 @@
|
||||
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml b/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml
|
||||
index 391f303..8e2c3db 100644
|
||||
--- a/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml
|
||||
+++ b/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<activity
|
||||
android:name="com.yalantis.ucrop.UCropActivity"
|
||||
- android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
+ android:theme="@style/Theme.UCropNoEdgeToEdge" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml b/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml
|
||||
new file mode 100644
|
||||
index 0000000..396d5a8
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml
|
||||
@@ -0,0 +1,5 @@
|
||||
+<resources>
|
||||
+ <style name="Theme.UCropNoEdgeToEdge" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
|
||||
+ </style>
|
||||
+</resources>
|
||||
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml
|
||||
new file mode 100644
|
||||
index 0000000..50129b6
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml
|
||||
@@ -0,0 +1,3 @@
|
||||
+<resources>
|
||||
+ <style name="Theme.UCropNoEdgeToEdge" parent="Theme.AppCompat.Light.NoActionBar"/>
|
||||
+</resources>
|
||||
diff --git a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
|
||||
index 9f20973..68d4766 100644
|
||||
--- a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
|
||||
+++ b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
|
||||
@@ -126,7 +126,8 @@ - (void) setConfiguration:(NSDictionary *)options
|
||||
|
||||
- (UIViewController*) getRootVC {
|
||||
UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
||||
- while (root.presentedViewController != nil) {
|
||||
+ while (root.presentedViewController != nil &&
|
||||
+ !root.presentedViewController.isBeingDismissed) {
|
||||
root = root.presentedViewController;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
diff --git a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp
|
||||
index eae3989..432745a 100644
|
||||
--- a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp
|
||||
+++ b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp
|
||||
@@ -416,6 +416,10 @@ void NativeProxy::progressLayoutAnimation(
|
||||
tag, newPropsJNI, isSharedTransition);
|
||||
}
|
||||
|
||||
+void NativeProxy::endLayoutAnimation(int tag, bool shouldRemove) {
|
||||
+ layoutAnimations_->cthis()->endLayoutAnimation(tag, shouldRemove);
|
||||
+}
|
||||
+
|
||||
PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() {
|
||||
#ifdef RCT_NEW_ARCH_ENABLED
|
||||
// nothing
|
||||
@@ -455,14 +459,7 @@ PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() {
|
||||
auto progressLayoutAnimation =
|
||||
bindThis(&NativeProxy::progressLayoutAnimation);
|
||||
|
||||
- auto endLayoutAnimation = [weakThis = weak_from_this()](
|
||||
- int tag, bool removeView) {
|
||||
- auto strongThis = weakThis.lock();
|
||||
- if (!strongThis) {
|
||||
- return;
|
||||
- }
|
||||
- strongThis->layoutAnimations_->cthis()->endLayoutAnimation(tag, removeView);
|
||||
- };
|
||||
+ auto endLayoutAnimation = bindThis(&NativeProxy::endLayoutAnimation);
|
||||
|
||||
auto maybeFlushUiUpdatesQueueFunction =
|
||||
bindThis(&NativeProxy::maybeFlushUIUpdatesQueue);
|
||||
diff --git a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h
|
||||
index 2ee2cc8..2edb5c9 100644
|
||||
--- a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h
|
||||
+++ b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h
|
||||
@@ -234,6 +234,8 @@ class NativeProxy : public jni::HybridClass<NativeProxy>,
|
||||
const jsi::Object &newProps,
|
||||
bool isSharedTransition);
|
||||
|
||||
+ void endLayoutAnimation(int tag, bool shouldRemove);
|
||||
+
|
||||
/***
|
||||
* Wraps a method of `NativeProxy` in a function object capturing `this`
|
||||
* @tparam TReturn return type of passed method
|
||||
@@ -0,0 +1,3 @@
|
||||
## react-native-view-shot patch
|
||||
|
||||
Temporary patch for web, where `view`'s type has changed.
|
||||
@@ -0,0 +1,28 @@
|
||||
diff --git a/node_modules/react-native-view-shot/src/index.js b/node_modules/react-native-view-shot/src/index.js
|
||||
index fa76d7e..018b686 100644
|
||||
--- a/node_modules/react-native-view-shot/src/index.js
|
||||
+++ b/node_modules/react-native-view-shot/src/index.js
|
||||
@@ -125,13 +125,17 @@ export function captureRef<T: React$ElementType>(
|
||||
}
|
||||
}
|
||||
if (typeof view !== "number") {
|
||||
- const node = findNodeHandle(view);
|
||||
- if (!node) {
|
||||
- return Promise.reject(
|
||||
- new Error("findNodeHandle failed to resolve view=" + String(view))
|
||||
- );
|
||||
+ if (Platform.OS == 'web') {
|
||||
+ view = view;
|
||||
+ } else {
|
||||
+ const node = findNodeHandle(view);
|
||||
+ if (!node) {
|
||||
+ return Promise.reject(
|
||||
+ new Error("findNodeHandle failed to resolve view=" + String(view))
|
||||
+ );
|
||||
+ }
|
||||
+ view = node;
|
||||
}
|
||||
- view = node;
|
||||
}
|
||||
const { options, errors } = validateOptions(optionsObject);
|
||||
if (__DEV__ && errors.length > 0) {
|
||||
@@ -0,0 +1,27 @@
|
||||
// Based on https://github.com/expo/expo/pull/33957
|
||||
// Could be removed once the app has been updated to Expo 53
|
||||
const {withAndroidStyles} = require('@expo/config-plugins')
|
||||
|
||||
module.exports = function withAndroidDayNightThemePlugin(appConfig) {
|
||||
const cleanupList = new Set([
|
||||
'colorPrimary',
|
||||
'android:editTextBackground',
|
||||
'android:textColor',
|
||||
'android:editTextStyle',
|
||||
])
|
||||
|
||||
return withAndroidStyles(appConfig, config => {
|
||||
config.modResults.resources.style = config.modResults.resources.style
|
||||
?.map(style => {
|
||||
if (style.$.name === 'AppTheme' && style.item != null) {
|
||||
style.item = style.item.filter(item => !cleanupList.has(item.$.name))
|
||||
}
|
||||
return style
|
||||
})
|
||||
.filter(style => {
|
||||
return style.$.name !== 'ResetEditText'
|
||||
})
|
||||
|
||||
return config
|
||||
})
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
const {withStringsXml, AndroidConfig} = require('@expo/config-plugins')
|
||||
|
||||
module.exports = function withAndroidSplashScreenStatusBarTranslucentPlugin(
|
||||
appConfig,
|
||||
) {
|
||||
return withStringsXml(appConfig, function (decoratedAppConfig) {
|
||||
try {
|
||||
decoratedAppConfig.modResults = AndroidConfig.Strings.setStringItem(
|
||||
[
|
||||
{
|
||||
_: 'true',
|
||||
$: {
|
||||
name: 'expo_splash_screen_status_bar_translucent',
|
||||
translatable: 'false',
|
||||
},
|
||||
},
|
||||
],
|
||||
decoratedAppConfig.modResults,
|
||||
)
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`withAndroidSplashScreenStatusBarTranslucentPlugin failed`,
|
||||
e,
|
||||
)
|
||||
}
|
||||
return decoratedAppConfig
|
||||
})
|
||||
}
|
||||
@@ -12,7 +12,7 @@ module.exports = function withAndroidStylesAccentColorPlugin(appConfig) {
|
||||
decoratedAppConfig.modResults,
|
||||
{
|
||||
add: true,
|
||||
parent: AndroidConfig.Styles.getAppThemeLightNoActionBarGroup(),
|
||||
parent: AndroidConfig.Styles.getAppThemeGroup(),
|
||||
name: 'colorAccent',
|
||||
value: '@color/colorPrimary',
|
||||
},
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
const {withAppDelegate} = require('@expo/config-plugins')
|
||||
const {mergeContents} = require('@expo/config-plugins/build/utils/generateCode')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
module.exports = config => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withAppDelegate(config, async config => {
|
||||
const delegatePath = path.join(
|
||||
config.modRequest.platformProjectRoot,
|
||||
'AppDelegate.mm',
|
||||
)
|
||||
module.exports = config =>
|
||||
withAppDelegate(config, config => {
|
||||
let contents = config.modResults.contents
|
||||
|
||||
let newContents = config.modResults.contents
|
||||
newContents = mergeContents({
|
||||
src: newContents,
|
||||
contents = mergeContents({
|
||||
src: contents,
|
||||
anchor: '// Linking API',
|
||||
newSrc: `
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
[defaults setObject:options[UIApplicationOpenURLOptionsSourceApplicationKey] forKey:@"referrerApp"];\n`,
|
||||
offset: 2,
|
||||
// @generated begin referrer info – deep links
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.set(
|
||||
options[.sourceApplication] as? String,
|
||||
forKey: "referrerApp"
|
||||
)
|
||||
// @generated end referrer info – deep links
|
||||
`,
|
||||
offset: 6,
|
||||
tag: 'referrer info - deep links',
|
||||
comment: '//',
|
||||
}).contents
|
||||
|
||||
newContents = mergeContents({
|
||||
src: newContents,
|
||||
contents = mergeContents({
|
||||
src: contents,
|
||||
anchor: '// Universal Links',
|
||||
newSrc: `
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
[defaults setURL:userActivity.referrerURL forKey:@"referrer"];\n`,
|
||||
offset: 2,
|
||||
// @generated begin referrer info – universal links
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.set(userActivity.referrerURL, forKey: "referrer")
|
||||
// @generated end referrer info – universal links
|
||||
`,
|
||||
offset: 6,
|
||||
tag: 'referrer info - universal links',
|
||||
comment: '//',
|
||||
}).contents
|
||||
|
||||
config.modResults.contents = newContents
|
||||
|
||||
config.modResults.contents = contents
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
const {withAppBuildGradle} = require('@expo/config-plugins')
|
||||
|
||||
/**
|
||||
* A Config Plugin to disable bundle compression in Android build.gradle.
|
||||
* @param {import('@expo/config-plugins').ConfigPlugin} config
|
||||
* @returns {import('@expo/config-plugins').ConfigPlugin}
|
||||
*/
|
||||
module.exports = function withNoBundleCompression(config) {
|
||||
return withAppBuildGradle(config, androidConfig => {
|
||||
let buildGradle = androidConfig.modResults.contents
|
||||
|
||||
const hasAndroidResources = buildGradle.includes('androidResources {')
|
||||
const hasNoCompress = buildGradle.includes('noCompress')
|
||||
|
||||
if (hasAndroidResources) {
|
||||
if (hasNoCompress) {
|
||||
if (
|
||||
buildGradle.includes('noCompress += ["bundle"]') ||
|
||||
buildGradle.includes("noCompress += 'bundle'") ||
|
||||
buildGradle.includes('noCompress += "bundle"')
|
||||
) {
|
||||
return androidConfig
|
||||
}
|
||||
|
||||
const lines = buildGradle.split('\n')
|
||||
const modifiedLines = lines.map(line => {
|
||||
if (line.trim().startsWith('noCompress')) {
|
||||
if (line.includes('+=')) {
|
||||
return line.replace(/\]/, ', "bundle"]')
|
||||
} else if (line.includes('=')) {
|
||||
return line.replace('=', '+= ["bundle",') + ']'
|
||||
}
|
||||
}
|
||||
return line
|
||||
})
|
||||
androidConfig.modResults.contents = modifiedLines.join('\n')
|
||||
} else {
|
||||
const androidResources = buildGradle.indexOf('androidResources {')
|
||||
if (androidResources === -1) {
|
||||
throw new Error(
|
||||
`Cannot find androidResources { block in build.gradle!`,
|
||||
)
|
||||
}
|
||||
const insertPosition = buildGradle.indexOf('\n', androidResources) + 1
|
||||
const newContent =
|
||||
buildGradle.slice(0, insertPosition) +
|
||||
' noCompress += ["bundle"]\n' +
|
||||
buildGradle.slice(insertPosition)
|
||||
|
||||
androidConfig.modResults.contents = newContent
|
||||
}
|
||||
} else {
|
||||
const androidBlock = buildGradle.indexOf('android {')
|
||||
if (androidBlock === -1) {
|
||||
throw new Error(`Cannot find android { block in build.gradle!`)
|
||||
}
|
||||
const insertPosition = buildGradle.indexOf('\n', androidBlock) + 1
|
||||
const newContent =
|
||||
buildGradle.slice(0, insertPosition) +
|
||||
' androidResources {\n' +
|
||||
' noCompress += ["bundle"]\n' +
|
||||
' }\n' +
|
||||
buildGradle.slice(insertPosition)
|
||||
|
||||
androidConfig.modResults.contents = newContent
|
||||
}
|
||||
|
||||
return androidConfig
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'react-native-url-polyfill/auto'
|
||||
import '#/logger/sentry/setup'
|
||||
import '#/logger/bitdrift/setup'
|
||||
import '#/view/icons'
|
||||
@@ -44,22 +43,22 @@ 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 {Provider as ServiceAccountManager} from '#/state/service-config'
|
||||
import {
|
||||
Provider as SessionProvider,
|
||||
SessionAccount,
|
||||
type SessionAccount,
|
||||
useSession,
|
||||
useSessionApi,
|
||||
} from '#/state/session'
|
||||
import {readLastActiveAccount} from '#/state/session/util'
|
||||
import {Provider as ShellStateProvider} from '#/state/shell'
|
||||
import {Provider as ComposerProvider} from '#/state/shell/composer'
|
||||
import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar'
|
||||
import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
|
||||
import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
|
||||
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
||||
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
|
||||
import {Provider as TrendingConfigProvider} from '#/state/trending-config'
|
||||
import {Provider as UnstablePostSourceProvider} from '#/state/unstable-post-source'
|
||||
import {TestCtrls} from '#/view/com/testing/TestCtrls'
|
||||
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
@@ -151,16 +150,18 @@ function InnerApp() {
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<ProgressGuideProvider>
|
||||
<TrendingConfigProvider>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<IntentDialogProvider>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
</IntentDialogProvider>
|
||||
</GestureHandlerRootView>
|
||||
</TrendingConfigProvider>
|
||||
<ServiceAccountManager>
|
||||
<UnstablePostSourceProvider>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<IntentDialogProvider>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
</IntentDialogProvider>
|
||||
</GestureHandlerRootView>
|
||||
</UnstablePostSourceProvider>
|
||||
</ServiceAccountManager>
|
||||
</ProgressGuideProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
@@ -219,9 +220,7 @@ function App() {
|
||||
<StarterPackProvider>
|
||||
<SafeAreaProvider
|
||||
initialMetrics={initialWindowMetrics}>
|
||||
<LightStatusBarProvider>
|
||||
<InnerApp />
|
||||
</LightStatusBarProvider>
|
||||
<InnerApp />
|
||||
</SafeAreaProvider>
|
||||
</StarterPackProvider>
|
||||
</BottomSheetProvider>
|
||||
|
||||
@@ -33,22 +33,22 @@ 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 {Provider as ServiceConfigProvider} from '#/state/service-config'
|
||||
import {
|
||||
Provider as SessionProvider,
|
||||
SessionAccount,
|
||||
type SessionAccount,
|
||||
useSession,
|
||||
useSessionApi,
|
||||
} from '#/state/session'
|
||||
import {readLastActiveAccount} from '#/state/session/util'
|
||||
import {Provider as ShellStateProvider} from '#/state/shell'
|
||||
import {Provider as ComposerProvider} from '#/state/shell/composer'
|
||||
import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar'
|
||||
import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
|
||||
import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
|
||||
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
||||
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
|
||||
import {Provider as TrendingConfigProvider} from '#/state/trending-config'
|
||||
import {Provider as UnstablePostSourceProvider} from '#/state/unstable-post-source'
|
||||
import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoWebContext'
|
||||
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
@@ -131,12 +131,14 @@ function InnerApp() {
|
||||
<MutedThreadsProvider>
|
||||
<SafeAreaProvider>
|
||||
<ProgressGuideProvider>
|
||||
<TrendingConfigProvider>
|
||||
<IntentDialogProvider>
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
</IntentDialogProvider>
|
||||
</TrendingConfigProvider>
|
||||
<ServiceConfigProvider>
|
||||
<UnstablePostSourceProvider>
|
||||
<IntentDialogProvider>
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
</IntentDialogProvider>
|
||||
</UnstablePostSourceProvider>
|
||||
</ServiceConfigProvider>
|
||||
</ProgressGuideProvider>
|
||||
</SafeAreaProvider>
|
||||
</MutedThreadsProvider>
|
||||
@@ -193,9 +195,7 @@ function App() {
|
||||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<StarterPackProvider>
|
||||
<LightStatusBarProvider>
|
||||
<InnerApp />
|
||||
</LightStatusBarProvider>
|
||||
<InnerApp />
|
||||
</StarterPackProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
|
||||
@@ -33,7 +33,6 @@ import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig'
|
||||
import {bskyTitle} from '#/lib/strings/headings'
|
||||
import {logger} from '#/logger'
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||
import {useSession} from '#/state/session'
|
||||
import {
|
||||
@@ -80,33 +79,37 @@ import {ProfileFeedScreen} from '#/screens/Profile/ProfileFeed'
|
||||
import {ProfileFollowersScreen} from '#/screens/Profile/ProfileFollowers'
|
||||
import {ProfileFollowsScreen} from '#/screens/Profile/ProfileFollows'
|
||||
import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
|
||||
import {ProfileSearchScreen} from '#/screens/Profile/ProfileSearch'
|
||||
import {SearchScreen} from '#/screens/Search'
|
||||
import {AboutSettingsScreen} from '#/screens/Settings/AboutSettings'
|
||||
import {AccessibilitySettingsScreen} from '#/screens/Settings/AccessibilitySettings'
|
||||
import {AccountSettingsScreen} from '#/screens/Settings/AccountSettings'
|
||||
import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings'
|
||||
import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings'
|
||||
import {AppPasswordsScreen} from '#/screens/Settings/AppPasswords'
|
||||
import {ContentAndMediaSettingsScreen} from '#/screens/Settings/ContentAndMediaSettings'
|
||||
import {ExternalMediaPreferencesScreen} from '#/screens/Settings/ExternalMediaPreferences'
|
||||
import {FollowingFeedPreferencesScreen} from '#/screens/Settings/FollowingFeedPreferences'
|
||||
import {LanguageSettingsScreen} from '#/screens/Settings/LanguageSettings'
|
||||
import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
|
||||
import {PrivacyAndSecuritySettingsScreen} from '#/screens/Settings/PrivacyAndSecuritySettings'
|
||||
import {SettingsScreen} from '#/screens/Settings/Settings'
|
||||
import {SettingsInterests} from '#/screens/Settings/SettingsInterests'
|
||||
import {ThreadPreferencesScreen} from '#/screens/Settings/ThreadPreferences'
|
||||
import {
|
||||
StarterPackScreen,
|
||||
StarterPackScreenShort,
|
||||
} from '#/screens/StarterPack/StarterPackScreen'
|
||||
import {Wizard} from '#/screens/StarterPack/Wizard'
|
||||
import TopicScreen from '#/screens/Topic'
|
||||
import {VideoFeed} from '#/screens/VideoFeed'
|
||||
import {useTheme} from '#/alf'
|
||||
import {type Theme, useTheme} from '#/alf'
|
||||
import {
|
||||
EmailDialogScreenID,
|
||||
useEmailDialogControl,
|
||||
} from '#/components/dialogs/EmailDialog'
|
||||
import {router} from '#/routes'
|
||||
import {Referrer} from '../modules/expo-bluesky-swiss-army'
|
||||
import {ProfileSearchScreen} from './screens/Profile/ProfileSearch'
|
||||
import {AboutSettingsScreen} from './screens/Settings/AboutSettings'
|
||||
import {AccessibilitySettingsScreen} from './screens/Settings/AccessibilitySettings'
|
||||
import {AccountSettingsScreen} from './screens/Settings/AccountSettings'
|
||||
import {AppPasswordsScreen} from './screens/Settings/AppPasswords'
|
||||
import {ContentAndMediaSettingsScreen} from './screens/Settings/ContentAndMediaSettings'
|
||||
import {ExternalMediaPreferencesScreen} from './screens/Settings/ExternalMediaPreferences'
|
||||
import {FollowingFeedPreferencesScreen} from './screens/Settings/FollowingFeedPreferences'
|
||||
import {LanguageSettingsScreen} from './screens/Settings/LanguageSettings'
|
||||
import {PrivacyAndSecuritySettingsScreen} from './screens/Settings/PrivacyAndSecuritySettings'
|
||||
import {SettingsScreen} from './screens/Settings/Settings'
|
||||
import {ThreadPreferencesScreen} from './screens/Settings/ThreadPreferences'
|
||||
import TopicScreen from './screens/Topic'
|
||||
|
||||
const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
|
||||
|
||||
@@ -124,7 +127,7 @@ const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
|
||||
/**
|
||||
* These "common screens" are reused across stacks.
|
||||
*/
|
||||
function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
||||
function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
|
||||
const title = (page: MessageDescriptor) =>
|
||||
bskyTitle(i18n._(page), unreadCountLabel)
|
||||
|
||||
@@ -496,6 +499,10 @@ function TabsNavigator() {
|
||||
tabBar={tabBar}>
|
||||
<Tab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} />
|
||||
<Tab.Screen name="SearchTab" getComponent={() => SearchTabNavigator} />
|
||||
<Tab.Screen
|
||||
name="MessagesTab"
|
||||
getComponent={() => MessagesTabNavigator}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="NotificationsTab"
|
||||
getComponent={() => NotificationsTabNavigator}
|
||||
@@ -504,29 +511,26 @@ function TabsNavigator() {
|
||||
name="MyProfileTab"
|
||||
getComponent={() => MyProfileTabNavigator}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="MessagesTab"
|
||||
getComponent={() => MessagesTabNavigator}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
function screenOptions(t: Theme) {
|
||||
return {
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: t.atoms.bg,
|
||||
} as const
|
||||
}
|
||||
|
||||
function HomeTabNavigator() {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<HomeTab.Navigator
|
||||
screenOptions={{
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<HomeTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Home">
|
||||
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
|
||||
<HomeTab.Screen name="Start" getComponent={() => HomeScreen} />
|
||||
{commonScreens(HomeTab)}
|
||||
{commonScreens(HomeTab as typeof Flat)}
|
||||
</HomeTab.Navigator>
|
||||
)
|
||||
}
|
||||
@@ -535,15 +539,10 @@ function SearchTabNavigator() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<SearchTab.Navigator
|
||||
screenOptions={{
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
screenOptions={screenOptions(t)}
|
||||
initialRouteName="Search">
|
||||
<SearchTab.Screen name="Search" getComponent={() => SearchScreen} />
|
||||
{commonScreens(SearchTab as typeof HomeTab)}
|
||||
{commonScreens(SearchTab as typeof Flat)}
|
||||
</SearchTab.Navigator>
|
||||
)
|
||||
}
|
||||
@@ -552,19 +551,14 @@ function NotificationsTabNavigator() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<NotificationsTab.Navigator
|
||||
screenOptions={{
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
screenOptions={screenOptions(t)}
|
||||
initialRouteName="Notifications">
|
||||
<NotificationsTab.Screen
|
||||
name="Notifications"
|
||||
getComponent={() => NotificationsScreen}
|
||||
options={{requireAuth: true}}
|
||||
/>
|
||||
{commonScreens(NotificationsTab as typeof HomeTab)}
|
||||
{commonScreens(NotificationsTab as typeof Flat)}
|
||||
</NotificationsTab.Navigator>
|
||||
)
|
||||
}
|
||||
@@ -573,23 +567,16 @@ function MyProfileTabNavigator() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MyProfileTab.Navigator
|
||||
screenOptions={{
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
screenOptions={screenOptions(t)}
|
||||
initialRouteName="MyProfile">
|
||||
<MyProfileTab.Screen
|
||||
// @ts-ignore // TODO: fix this broken type in ProfileScreen
|
||||
name="MyProfile"
|
||||
// MyProfile is not in AllNavigationParams - asserting as Profile at least
|
||||
// gives us typechecking for initialParams -sfn
|
||||
name={'MyProfile' as 'Profile'}
|
||||
getComponent={() => ProfileScreen}
|
||||
initialParams={{
|
||||
name: 'me',
|
||||
hideBackButton: true,
|
||||
}}
|
||||
initialParams={{name: 'me', hideBackButton: true}}
|
||||
/>
|
||||
{commonScreens(MyProfileTab as typeof HomeTab)}
|
||||
{commonScreens(MyProfileTab as unknown as typeof Flat)}
|
||||
</MyProfileTab.Navigator>
|
||||
)
|
||||
}
|
||||
@@ -598,13 +585,8 @@ function MessagesTabNavigator() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MessagesTab.Navigator
|
||||
screenOptions={{
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
screenOptions={screenOptions(t)}
|
||||
initialRouteName="Messages">
|
||||
<MessagesTab.Screen
|
||||
name="Messages"
|
||||
getComponent={() => MessagesScreen}
|
||||
@@ -613,7 +595,7 @@ function MessagesTabNavigator() {
|
||||
animationTypeForReplace: route.params?.animation ?? 'push',
|
||||
})}
|
||||
/>
|
||||
{commonScreens(MessagesTab as typeof HomeTab)}
|
||||
{commonScreens(MessagesTab as typeof Flat)}
|
||||
</MessagesTab.Navigator>
|
||||
)
|
||||
}
|
||||
@@ -631,13 +613,7 @@ const FlatNavigator = () => {
|
||||
return (
|
||||
<Flat.Navigator
|
||||
screenListeners={screenListeners}
|
||||
screenOptions={{
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
screenOptions={screenOptions(t)}>
|
||||
<Flat.Screen
|
||||
name="Home"
|
||||
getComponent={() => HomeScreen}
|
||||
@@ -663,7 +639,7 @@ const FlatNavigator = () => {
|
||||
getComponent={() => HomeScreen}
|
||||
options={{title: title(msg`Home`)}}
|
||||
/>
|
||||
{commonScreens(Flat as typeof HomeTab, numUnread)}
|
||||
{commonScreens(Flat, numUnread)}
|
||||
</Flat.Navigator>
|
||||
)
|
||||
}
|
||||
@@ -736,36 +712,51 @@ const LINKING = {
|
||||
function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
||||
const theme = useColorSchemeStyle(DefaultTheme, DarkTheme)
|
||||
const {currentAccount} = useSession()
|
||||
const {openModal} = useModalControls()
|
||||
const prevLoggedRouteName = React.useRef<string | undefined>(undefined)
|
||||
const emailDialogControl = useEmailDialogControl()
|
||||
|
||||
function onReady() {
|
||||
prevLoggedRouteName.current = getCurrentRouteName()
|
||||
if (currentAccount && shouldRequestEmailConfirmation(currentAccount)) {
|
||||
openModal({name: 'verify-email', showReminder: true})
|
||||
emailDialogControl.open({
|
||||
id: EmailDialogScreenID.VerificationReminder,
|
||||
})
|
||||
snoozeEmailConfirmationPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
linking={LINKING}
|
||||
theme={theme}
|
||||
onStateChange={() => {
|
||||
logger.metric('router:navigate', {
|
||||
from: prevLoggedRouteName.current,
|
||||
})
|
||||
prevLoggedRouteName.current = getCurrentRouteName()
|
||||
}}
|
||||
onReady={() => {
|
||||
attachRouteToLogEvents(getCurrentRouteName)
|
||||
logModuleInitTime()
|
||||
onReady()
|
||||
logger.metric('router:navigate', {})
|
||||
}}>
|
||||
{children}
|
||||
</NavigationContainer>
|
||||
<>
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
linking={LINKING}
|
||||
theme={theme}
|
||||
onStateChange={() => {
|
||||
logger.metric(
|
||||
'router:navigate',
|
||||
{
|
||||
from: prevLoggedRouteName.current,
|
||||
},
|
||||
{statsig: false},
|
||||
)
|
||||
prevLoggedRouteName.current = getCurrentRouteName()
|
||||
}}
|
||||
onReady={() => {
|
||||
attachRouteToLogEvents(getCurrentRouteName)
|
||||
logModuleInitTime()
|
||||
onReady()
|
||||
logger.metric('router:navigate', {}, {statsig: false})
|
||||
}}
|
||||
// WARNING: Implicit navigation to nested navigators is depreciated in React Navigation 7.x
|
||||
// However, there's a fair amount of places we do that, especially in when popping to the top of stacks.
|
||||
// See BottomBar.tsx for an example of how to handle nested navigators in the tabs correctly.
|
||||
// I'm scared of missing a spot (esp. with push notifications etc) so let's enable this legacy behaviour for now.
|
||||
// We will need to confirm we handle nested navigators correctly by the time we migrate to React Navigation 8.x
|
||||
// -sfn
|
||||
navigationInChildEnabled>
|
||||
{children}
|
||||
</NavigationContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ export const atoms = {
|
||||
relative: {
|
||||
position: 'relative',
|
||||
},
|
||||
static: {
|
||||
position: 'static',
|
||||
},
|
||||
sticky: web({
|
||||
position: 'sticky',
|
||||
}),
|
||||
@@ -204,6 +207,9 @@ export const atoms = {
|
||||
flex_grow: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
flex_grow_0: {
|
||||
flexGrow: 0,
|
||||
},
|
||||
flex_shrink: {
|
||||
flexShrink: 1,
|
||||
},
|
||||
@@ -951,8 +957,8 @@ export const atoms = {
|
||||
userSelect: 'all',
|
||||
},
|
||||
outline_inset_1: {
|
||||
outlineOffset: '-1px',
|
||||
} as StyleProp<ViewStyle>,
|
||||
outlineOffset: -1,
|
||||
},
|
||||
|
||||
/*
|
||||
* Text decoration
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, {Children} from 'react'
|
||||
import {TextProps as RNTextProps} from 'react-native'
|
||||
import {StyleProp, TextStyle} from 'react-native'
|
||||
import {Children} from 'react'
|
||||
import {type TextProps as RNTextProps} from 'react-native'
|
||||
import {type StyleProp, type TextStyle} from 'react-native'
|
||||
import {UITextView} from 'react-native-uitextview'
|
||||
import createEmojiRegex from 'emoji-regex'
|
||||
import type React from 'react'
|
||||
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {Alf, applyFonts, atoms, flatten} from '#/alf'
|
||||
import {type Alf, applyFonts, atoms, flatten} from '#/alf'
|
||||
|
||||
/**
|
||||
* Util to calculate lineHeight from a text size atom and a leading atom
|
||||
@@ -110,7 +111,10 @@ export function renderChildrenWithEmoji(
|
||||
return child.split(EMOJI).map((stringPart, index) => [
|
||||
stringPart,
|
||||
emojis[index] ? (
|
||||
<UITextView {...props} style={[props?.style, {fontFamily: 'System'}]}>
|
||||
<UITextView
|
||||
{...props}
|
||||
style={[props?.style, {fontFamily: 'System'}]}
|
||||
key={index}>
|
||||
{emojis[index]}
|
||||
</UITextView>
|
||||
) : null,
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import * as NavigationBar from 'expo-navigation-bar'
|
||||
import * as SystemUI from 'expo-system-ui'
|
||||
|
||||
import {isAndroid} from '#/platform/detection'
|
||||
import {Theme} from '../types'
|
||||
|
||||
export function setNavigationBar(themeType: 'theme' | 'lightbox', t: Theme) {
|
||||
if (isAndroid) {
|
||||
if (themeType === 'theme') {
|
||||
NavigationBar.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
|
||||
NavigationBar.setBorderColorAsync(t.atoms.bg.backgroundColor)
|
||||
NavigationBar.setButtonStyleAsync(t.name !== 'light' ? 'light' : 'dark')
|
||||
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
|
||||
} else {
|
||||
NavigationBar.setBackgroundColorAsync('black')
|
||||
NavigationBar.setBorderColorAsync('black')
|
||||
NavigationBar.setButtonStyleAsync('light')
|
||||
SystemUI.setBackgroundColorAsync('black')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import * as SystemUI from 'expo-system-ui'
|
||||
|
||||
import {isAndroid} from '#/platform/detection'
|
||||
import {Theme} from '../types'
|
||||
|
||||
export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) {
|
||||
if (isAndroid) {
|
||||
if (themeType === 'theme') {
|
||||
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
|
||||
} else {
|
||||
SystemUI.setBackgroundColorAsync('black')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {type AppBskyActorDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useActorStatus} from '#/lib/actor-status'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useProfilesQuery} from '#/state/queries/profile'
|
||||
@@ -110,6 +111,7 @@ function AccountItem({
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const verification = useSimpleVerificationState({profile})
|
||||
const {isActive: live} = useActorStatus(profile)
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
onSelect(account)
|
||||
@@ -141,6 +143,8 @@ function AccountItem({
|
||||
avatar={profile?.avatar}
|
||||
size={36}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
live={live}
|
||||
hideLiveBadge
|
||||
/>
|
||||
|
||||
<View style={[a.flex_1, a.gap_2xs, a.pr_2xl]}>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, View, ViewStyle} from 'react-native'
|
||||
import {createContext, useContext} from 'react'
|
||||
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
||||
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Button as BaseButton, ButtonProps} from '#/components/Button'
|
||||
import {Button as BaseButton, type ButtonProps} from '#/components/Button'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
|
||||
import {Eye_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/Eye'
|
||||
import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf'
|
||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||
import {Text as BaseText, TextProps} from '#/components/Typography'
|
||||
import {Text as BaseText, type TextProps} from '#/components/Typography'
|
||||
|
||||
export const colors = {
|
||||
warning: {
|
||||
@@ -20,13 +20,13 @@ type Context = {
|
||||
type: 'info' | 'tip' | 'warning' | 'error'
|
||||
}
|
||||
|
||||
const Context = React.createContext<Context>({
|
||||
const Context = createContext<Context>({
|
||||
type: 'info',
|
||||
})
|
||||
|
||||
export function Icon() {
|
||||
const t = useTheme()
|
||||
const {type} = React.useContext(Context)
|
||||
const {type} = useContext(Context)
|
||||
const Icon = {
|
||||
info: InfoIcon,
|
||||
tip: TipIcon,
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {resetPostsFeedQueries} from '#/state/queries/post-feed'
|
||||
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||
import {atoms as a, platform, useTheme} from '#/alf'
|
||||
import * as Select from '#/components/Select'
|
||||
import {Button} from './Button'
|
||||
|
||||
export function AppLanguageDropdown(_props: ViewStyleProp) {
|
||||
export function AppLanguageDropdown() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const langPrefs = useLanguagePrefs()
|
||||
@@ -19,7 +21,7 @@ export function AppLanguageDropdown(_props: ViewStyleProp) {
|
||||
const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage)
|
||||
|
||||
const onChangeAppLanguage = React.useCallback(
|
||||
(value: Parameters<PickerSelectProps['onValueChange']>[0]) => {
|
||||
(value: string) => {
|
||||
if (!value) return
|
||||
if (sanitizedLang !== value) {
|
||||
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
|
||||
@@ -32,43 +34,48 @@ export function AppLanguageDropdown(_props: ViewStyleProp) {
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={a.relative}>
|
||||
<RNPickerSelect
|
||||
darkTheme={t.scheme === 'dark'}
|
||||
placeholder={{}}
|
||||
value={sanitizedLang}
|
||||
onValueChange={onChangeAppLanguage}
|
||||
items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
|
||||
<Select.Root
|
||||
value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
|
||||
onValueChange={onChangeAppLanguage}>
|
||||
<Select.Trigger label={_(msg`Change app language`)}>
|
||||
{({props}) => (
|
||||
<Button
|
||||
{...props}
|
||||
label={props.accessibilityLabel}
|
||||
size={platform({
|
||||
web: 'tiny',
|
||||
native: 'small',
|
||||
})}
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
style={[
|
||||
a.pr_xs,
|
||||
a.pl_sm,
|
||||
platform({
|
||||
web: [{alignSelf: 'flex-start'}, a.gap_sm],
|
||||
native: [a.gap_xs],
|
||||
}),
|
||||
]}>
|
||||
<Select.ValueText
|
||||
placeholder={_(msg`Select an app language`)}
|
||||
style={[t.atoms.text_contrast_medium]}
|
||||
/>
|
||||
<Select.Icon style={[t.atoms.text_contrast_medium]} />
|
||||
</Button>
|
||||
)}
|
||||
</Select.Trigger>
|
||||
<Select.Content
|
||||
renderItem={({label, value}) => (
|
||||
<Select.Item value={value} label={label}>
|
||||
<Select.ItemIndicator />
|
||||
<Select.ItemText>{label}</Select.ItemText>
|
||||
</Select.Item>
|
||||
)}
|
||||
items={APP_LANGUAGES.map(l => ({
|
||||
label: l.name,
|
||||
value: l.code2,
|
||||
key: l.code2,
|
||||
}))}
|
||||
useNativeAndroidPickerStyle={false}
|
||||
style={{
|
||||
inputAndroid: {
|
||||
color: t.atoms.text_contrast_medium.color,
|
||||
fontSize: 16,
|
||||
paddingRight: 12 + 4,
|
||||
},
|
||||
inputIOS: {
|
||||
color: t.atoms.text.color,
|
||||
fontSize: 16,
|
||||
paddingRight: 12 + 4,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
{left: 'auto'},
|
||||
{pointerEvents: 'none'},
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
]}>
|
||||
<ChevronDown fill={t.atoms.text.color} size="xs" />
|
||||
</View>
|
||||
</View>
|
||||
</Select.Root>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {resetPostsFeedQueries} from '#/state/queries/post-feed'
|
||||
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function AppLanguageDropdown({style}: ViewStyleProp) {
|
||||
const t = useTheme()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const langPrefs = useLanguagePrefs()
|
||||
const setLangPrefs = useLanguagePrefsApi()
|
||||
|
||||
const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage)
|
||||
|
||||
const onChangeAppLanguage = React.useCallback(
|
||||
(ev: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = ev.target.value
|
||||
|
||||
if (!value) return
|
||||
if (sanitizedLang !== value) {
|
||||
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
|
||||
}
|
||||
|
||||
// reset feeds to refetch content
|
||||
resetPostsFeedQueries(queryClient)
|
||||
},
|
||||
[sanitizedLang, setLangPrefs, queryClient],
|
||||
)
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
// We don't have hitSlop here to increase the tap region,
|
||||
// alternative is negative margins.
|
||||
{height: 32, marginVertical: -((32 - 14) / 2)},
|
||||
style,
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.gap_sm,
|
||||
a.align_center,
|
||||
a.flex_shrink,
|
||||
a.h_full,
|
||||
t.atoms.bg,
|
||||
]}>
|
||||
<Text aria-hidden={true} style={t.atoms.text_contrast_medium}>
|
||||
{APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name}
|
||||
</Text>
|
||||
<ChevronDown fill={t.atoms.text.color} size="xs" style={a.flex_0} />
|
||||
</View>
|
||||
|
||||
<select
|
||||
value={sanitizedLang}
|
||||
onChange={onChangeAppLanguage}
|
||||
style={{
|
||||
fontSize: a.text_sm.fontSize,
|
||||
letterSpacing: a.text_sm.letterSpacing,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
opacity: 0,
|
||||
color: t.atoms.text.color,
|
||||
background: t.atoms.bg.backgroundColor,
|
||||
padding: 4,
|
||||
maxWidth: '100%',
|
||||
}}>
|
||||
{APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => (
|
||||
<option key={l.code2} value={l.code2}>
|
||||
{l.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
AccessibilityProps,
|
||||
GestureResponderEvent,
|
||||
MouseEvent,
|
||||
NativeSyntheticEvent,
|
||||
type AccessibilityProps,
|
||||
type GestureResponderEvent,
|
||||
type MouseEvent,
|
||||
type NativeSyntheticEvent,
|
||||
Pressable,
|
||||
PressableProps,
|
||||
StyleProp,
|
||||
type PressableProps,
|
||||
type StyleProp,
|
||||
StyleSheet,
|
||||
TargetedEvent,
|
||||
TextProps,
|
||||
TextStyle,
|
||||
type TargetedEvent,
|
||||
type TextProps,
|
||||
type TextStyle,
|
||||
View,
|
||||
ViewStyle,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {LinearGradient} from 'expo-linear-gradient'
|
||||
|
||||
import {atoms as a, flatten, select, tokens, useTheme} from '#/alf'
|
||||
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {type Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
|
||||
@@ -26,6 +26,7 @@ export type ButtonColor =
|
||||
| 'secondary'
|
||||
| 'secondary_inverted'
|
||||
| 'negative'
|
||||
| 'negative_secondary'
|
||||
| 'gradient_primary'
|
||||
| 'gradient_sky'
|
||||
| 'gradient_midnight'
|
||||
@@ -336,6 +337,57 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
||||
borderWidth: 1,
|
||||
})
|
||||
|
||||
if (!disabled) {
|
||||
baseStyles.push(a.border, {
|
||||
borderColor: t.palette.negative_500,
|
||||
})
|
||||
hoverStyles.push(a.border, {
|
||||
backgroundColor: t.palette.negative_50,
|
||||
})
|
||||
} else {
|
||||
baseStyles.push(a.border, {
|
||||
borderColor: t.palette.negative_200,
|
||||
})
|
||||
}
|
||||
} else if (variant === 'ghost') {
|
||||
if (!disabled) {
|
||||
baseStyles.push(t.atoms.bg)
|
||||
hoverStyles.push({
|
||||
backgroundColor: t.palette.negative_100,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if (color === 'negative_secondary') {
|
||||
if (variant === 'solid') {
|
||||
if (!disabled) {
|
||||
baseStyles.push({
|
||||
backgroundColor: select(t.name, {
|
||||
light: t.palette.negative_50,
|
||||
dim: t.palette.negative_100,
|
||||
dark: t.palette.negative_100,
|
||||
}),
|
||||
})
|
||||
hoverStyles.push({
|
||||
backgroundColor: select(t.name, {
|
||||
light: t.palette.negative_100,
|
||||
dim: t.palette.negative_200,
|
||||
dark: t.palette.negative_200,
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
baseStyles.push({
|
||||
backgroundColor: select(t.name, {
|
||||
light: t.palette.negative_100,
|
||||
dim: t.palette.negative_50,
|
||||
dark: t.palette.negative_50,
|
||||
}),
|
||||
})
|
||||
}
|
||||
} else if (variant === 'outline') {
|
||||
baseStyles.push(a.border, t.atoms.bg, {
|
||||
borderWidth: 1,
|
||||
})
|
||||
|
||||
if (!disabled) {
|
||||
baseStyles.push(a.border, {
|
||||
borderColor: t.palette.negative_500,
|
||||
@@ -425,6 +477,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
||||
secondary: tokens.gradients.sky,
|
||||
secondary_inverted: tokens.gradients.sky,
|
||||
negative: tokens.gradients.sky,
|
||||
negative_secondary: tokens.gradients.sky,
|
||||
gradient_primary: tokens.gradients.primary,
|
||||
gradient_sky: tokens.gradients.sky,
|
||||
gradient_midnight: tokens.gradients.midnight,
|
||||
@@ -597,7 +650,7 @@ export function useSharedButtonTextStyles() {
|
||||
if (variant === 'solid' || variant === 'gradient') {
|
||||
if (!disabled) {
|
||||
baseStyles.push({
|
||||
color: t.palette.contrast_100,
|
||||
color: t.palette.contrast_50,
|
||||
})
|
||||
} else {
|
||||
baseStyles.push({
|
||||
@@ -645,6 +698,39 @@ export function useSharedButtonTextStyles() {
|
||||
baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
|
||||
}
|
||||
}
|
||||
} else if (color === 'negative_secondary') {
|
||||
if (variant === 'solid' || variant === 'gradient') {
|
||||
if (!disabled) {
|
||||
baseStyles.push({
|
||||
color: select(t.name, {
|
||||
light: t.palette.negative_500,
|
||||
dim: t.palette.negative_950,
|
||||
dark: t.palette.negative_900,
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
baseStyles.push({
|
||||
color: select(t.name, {
|
||||
light: t.palette.negative_500,
|
||||
dim: t.palette.negative_700,
|
||||
dark: t.palette.negative_700,
|
||||
}),
|
||||
opacity: 0.5,
|
||||
})
|
||||
}
|
||||
} else if (variant === 'outline') {
|
||||
if (!disabled) {
|
||||
baseStyles.push({color: t.palette.negative_400})
|
||||
} else {
|
||||
baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
|
||||
}
|
||||
} else if (variant === 'ghost') {
|
||||
if (!disabled) {
|
||||
baseStyles.push({color: t.palette.negative_400})
|
||||
} else {
|
||||
baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!disabled) {
|
||||
baseStyles.push({color: t.palette.white})
|
||||
|
||||
@@ -556,7 +556,7 @@ export function Outer({
|
||||
// pure vibes based
|
||||
const TOP_INSET = insets.top + 80
|
||||
const BOTTOM_INSET_IOS = insets.bottom + 20
|
||||
const BOTTOM_INSET_ANDROID = 12 // TODO: revisit when edge-to-edge mode is enabled -sfn
|
||||
const BOTTOM_INSET_ANDROID = insets.bottom + 12
|
||||
|
||||
const {height} = evt.nativeEvent.layout
|
||||
const topPosition =
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useId,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
|
||||
import {useDialogStateContext} from '#/state/dialogs'
|
||||
import {
|
||||
DialogContextProps,
|
||||
DialogControlRefProps,
|
||||
DialogOuterProps,
|
||||
type DialogContextProps,
|
||||
type DialogControlRefProps,
|
||||
type DialogOuterProps,
|
||||
} from '#/components/Dialog/types'
|
||||
import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types'
|
||||
|
||||
export const Context = React.createContext<DialogContextProps>({
|
||||
export const Context = createContext<DialogContextProps>({
|
||||
close: () => {},
|
||||
isNativeDialog: false,
|
||||
nativeSnapPoint: BottomSheetSnapPoint.Hidden,
|
||||
@@ -18,18 +25,18 @@ export const Context = React.createContext<DialogContextProps>({
|
||||
})
|
||||
|
||||
export function useDialogContext() {
|
||||
return React.useContext(Context)
|
||||
return useContext(Context)
|
||||
}
|
||||
|
||||
export function useDialogControl(): DialogOuterProps['control'] {
|
||||
const id = React.useId()
|
||||
const control = React.useRef<DialogControlRefProps>({
|
||||
const id = useId()
|
||||
const control = useRef<DialogControlRefProps>({
|
||||
open: () => {},
|
||||
close: () => {},
|
||||
})
|
||||
const {activeDialogs} = useDialogStateContext()
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
activeDialogs.current.set(id, control)
|
||||
return () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -37,7 +44,7 @@ export function useDialogControl(): DialogOuterProps['control'] {
|
||||
}
|
||||
}, [id, activeDialogs])
|
||||
|
||||
return React.useMemo<DialogOuterProps['control']>(
|
||||
return useMemo<DialogOuterProps['control']>(
|
||||
() => ({
|
||||
id,
|
||||
ref: control,
|
||||
|
||||
@@ -298,9 +298,7 @@ export const InnerFlatList = React.forwardRef<
|
||||
<List
|
||||
keyboardShouldPersistTaps="handled"
|
||||
bounces={nativeSnapPoint === BottomSheetSnapPoint.Full}
|
||||
ListFooterComponent={
|
||||
<View style={{height: insets.bottom + a.pt_5xl.paddingTop + 50}} />
|
||||
}
|
||||
ListFooterComponent={<View style={{height: insets.bottom + 100}} />}
|
||||
ref={ref}
|
||||
{...props}
|
||||
style={[style]}
|
||||
@@ -309,7 +307,7 @@ export const InnerFlatList = React.forwardRef<
|
||||
)
|
||||
})
|
||||
|
||||
export function Handle() {
|
||||
export function Handle({difference = false}: {difference?: boolean}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {screenReaderEnabled} = useA11y()
|
||||
@@ -330,9 +328,19 @@ export function Handle() {
|
||||
width: 35,
|
||||
height: 5,
|
||||
alignSelf: 'center',
|
||||
backgroundColor: t.palette.contrast_975,
|
||||
opacity: 0.5,
|
||||
},
|
||||
difference
|
||||
? {
|
||||
// TODO: mixBlendMode is only available on the new architecture -sfn
|
||||
// backgroundColor: t.palette.white,
|
||||
// mixBlendMode: 'difference',
|
||||
backgroundColor: t.palette.white,
|
||||
opacity: 0.75,
|
||||
}
|
||||
: {
|
||||
backgroundColor: t.palette.contrast_975,
|
||||
opacity: 0.5,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Pressable>
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import React, {useImperativeHandle} from 'react'
|
||||
import {
|
||||
FlatList,
|
||||
FlatListProps,
|
||||
StyleProp,
|
||||
type FlatListProps,
|
||||
type StyleProp,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewStyle,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
|
||||
import {useFocusGuards} from '@radix-ui/react-focus-guards'
|
||||
import {FocusScope} from '@radix-ui/react-focus-scope'
|
||||
import {DismissableLayer, FocusGuards, FocusScope} from 'radix-ui/internal'
|
||||
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
@@ -21,9 +19,9 @@ import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {Context} from '#/components/Dialog/context'
|
||||
import {
|
||||
DialogControlProps,
|
||||
DialogInnerProps,
|
||||
DialogOuterProps,
|
||||
type DialogControlProps,
|
||||
type DialogInnerProps,
|
||||
type DialogOuterProps,
|
||||
} from '#/components/Dialog/types'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import {Portal} from '#/components/Portal'
|
||||
@@ -162,9 +160,9 @@ export function Inner({
|
||||
const {close} = React.useContext(Context)
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {reduceMotionEnabled} = useA11y()
|
||||
useFocusGuards()
|
||||
FocusGuards.useFocusGuards()
|
||||
return (
|
||||
<FocusScope loop asChild trapped>
|
||||
<FocusScope.FocusScope loop asChild trapped>
|
||||
<View
|
||||
role="dialog"
|
||||
aria-role="dialog"
|
||||
@@ -191,7 +189,7 @@ export function Inner({
|
||||
!reduceMotionEnabled && a.zoom_fade_in,
|
||||
style,
|
||||
])}>
|
||||
<DismissableLayer
|
||||
<DismissableLayer.DismissableLayer
|
||||
onInteractOutside={preventDefault}
|
||||
onFocusOutside={preventDefault}
|
||||
onDismiss={close}
|
||||
@@ -200,9 +198,9 @@ export function Inner({
|
||||
<View style={[gtMobile ? a.p_2xl : a.p_xl, contentContainerStyle]}>
|
||||
{children}
|
||||
</View>
|
||||
</DismissableLayer>
|
||||
</DismissableLayer.DismissableLayer>
|
||||
</View>
|
||||
</FocusScope>
|
||||
</FocusScope.FocusScope>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'
|
||||
import {
|
||||
type LayoutChangeEvent,
|
||||
type StyleProp,
|
||||
type TextStyle,
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import type React from 'react'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -9,15 +15,18 @@ export function Header({
|
||||
renderRight,
|
||||
children,
|
||||
style,
|
||||
onLayout,
|
||||
}: {
|
||||
renderLeft?: () => React.ReactNode
|
||||
renderRight?: () => React.ReactNode
|
||||
children?: React.ReactNode
|
||||
style?: StyleProp<ViewStyle>
|
||||
onLayout?: (event: LayoutChangeEvent) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
onLayout={onLayout}
|
||||
style={[
|
||||
a.relative,
|
||||
a.w_full,
|
||||
|
||||