Compare commits

...

43 Commits

Author SHA1 Message Date
Samuel Newman 35414b82c1 bring back the MMKV patch 2025-09-12 16:35:56 +03:00
Samuel Newman 31a74f8d31 yolo rip out sentry 2025-09-12 15:26:31 +03:00
Samuel Newman 0d4367833b try downgrading sentry 2025-09-12 14:39:01 +03:00
Samuel Newman a2005b3f27 Revert "downgrade rn-screens"
This reverts commit d6610eddd4.
2025-09-12 14:35:30 +03:00
Samuel Newman d6610eddd4 downgrade rn-screens 2025-09-12 13:59:21 +03:00
Samuel Newman 744e141703 Revert "temp rm pagerview"
This reverts commit 30c2394271.
2025-09-12 13:32:15 +03:00
Samuel Newman 30c2394271 temp rm pagerview 2025-09-12 13:11:52 +03:00
Samuel Newman 4aac5c1482 Revert "temp remove date-picker"
This reverts commit fa9e42c2fd.
2025-09-12 13:08:11 +03:00
Samuel Newman fa9e42c2fd temp remove date-picker 2025-09-12 13:05:51 +03:00
Samuel Newman fa52da8628 allow swipe dismissal on iOS 2025-09-12 11:00:30 +03:00
Samuel Newman 60d826c4f0 Delete react-native-mmkv+2.12.2.patch 2025-09-12 11:00:14 +03:00
Samuel Newman c3bb121250 rm atob from docs 2025-09-12 10:01:35 +03:00
Samuel Newman 3b4c5becda update build instructions 2025-09-12 10:00:55 +03:00
Samuel Newman 4278171dd7 bump node version in eas.json 2025-09-12 09:49:31 +03:00
Samuel Newman d1a356ceec update resolutions, dynamic-app-icon 2025-09-12 09:22:12 +03:00
Samuel Newman 5b3a47ed87 dep update 2025-09-12 08:26:18 +03:00
Samuel Newman e326694c1f revert patch, just do debugging part 2025-09-12 08:14:40 +03:00
Samuel Newman 2682989f3c bring back speculative patch, patch built JS code instead of source 2025-09-11 22:42:49 +03:00
Samuel Newman b803098228 revert patch, add temp debug info to error 2025-09-11 21:49:14 +03:00
Samuel Newman db57b5d939 add speculative react-native-mmkv patch 2025-09-11 21:01:03 +03:00
Samuel Newman 82a1cbebdf rm unused picker lib 2025-09-11 20:11:20 +03:00
Samuel Newman 371e31bd93 bump cocoapods 2025-09-11 19:37:24 +03:00
Samuel Newman 749d4c5bd4 import React in expo modules 2025-09-11 14:33:50 +03:00
Samuel Newman 7861de140c delete NativeDropdown & remove Zeego 2025-09-11 14:21:05 +03:00
Samuel Newman 3a666db699 add patch of https://github.com/bluesky-social/react-native-paste-input/pull/5 2025-09-11 14:18:16 +03:00
Samuel Newman 9833afcd1b bump rn-compressor 2025-09-11 14:14:56 +03:00
Samuel Newman 73f6d878bd fix mock 2025-09-11 13:46:53 +03:00
Samuel Newman 91b6d32c5d more type errors 2025-09-11 12:24:56 +03:00
Samuel Newman 9847f7338e rm @types/react resolution from bad rebase 2025-09-11 12:21:05 +03:00
Samuel Newman a99fae861e fix type of uint8array 2025-09-11 12:10:48 +03:00
Samuel Newman be56364733 try and upgrade types/react-dom 2025-09-11 12:09:07 +03:00
Samuel Newman 4c4644d089 use expo-file-system/legacy 2025-09-11 12:01:07 +03:00
Samuel Newman e61f1632c3 downgrade pagerview 2025-09-11 11:55:02 +03:00
Samuel Newman 0a2e459529 downgrade reanimated 2025-09-11 11:43:50 +03:00
Samuel Newman 992b9ebaff add back expo-location 2025-09-11 11:41:42 +03:00
Samuel Newman 97c5cfa9b7 update patches 2025-09-11 11:40:11 +03:00
Samuel Newman 5f98594eff fix lockfile lint 2025-09-11 11:35:08 +03:00
Samuel Newman 735a9c1e3e upgrade off of preview to release 2025-09-11 11:34:00 +03:00
Samuel Newman 01194dd32d update to preview.14 2025-09-11 11:30:23 +03:00
Samuel Newman 1e57665aee build from source, disable new arch 2025-09-11 11:30:23 +03:00
Samuel Newman 6df3dc0812 fix new arch optout 2025-09-11 11:30:23 +03:00
Samuel Newman 9c6369b4ec update patches 2025-09-11 11:30:23 +03:00
Samuel Newman 5dd417cf3f update deps 2025-09-11 11:30:23 +03:00
57 changed files with 1635 additions and 3285 deletions
+2 -2
View File
@@ -53,12 +53,12 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.4'
xcode-version: "16.4"
- name: ☕️ Setup Cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
version: 1.14.3
version: 1.16.2
- name: 💾 Cache Pods
uses: actions/cache@v3
@@ -192,12 +192,12 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2'
xcode-version: "16.2"
- name: ☕️ Setup Cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
version: 1.14.3
version: 1.16.2
- name: 💾 Cache Pods
uses: actions/cache@v3
@@ -293,8 +293,8 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
distribution: "temurin"
java-version: "17"
- name: ⚙️ Install dependencies
run: yarn install
@@ -332,7 +332,7 @@ jobs:
id: timestamp
uses: nanzm/get-time-action@master
with:
format: 'MM-DD-HH-mm-ss'
format: "MM-DD-HH-mm-ss"
- name: 🚀 Upload Artifact
id: upload-artifact
-3
View File
@@ -1,3 +0,0 @@
jest.mock('sentry-expo', () => ({
init: () => jest.fn(),
}))
+1 -1
View File
@@ -1,4 +1,4 @@
import {createDownloadResumable, deleteAsync} from 'expo-file-system'
import {createDownloadResumable, deleteAsync} from 'expo-file-system/legacy'
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
import {
+2 -12
View File
@@ -28,8 +28,6 @@ module.exports = function (_config) {
const UPDATES_ENABLED = IS_TESTFLIGHT || IS_PRODUCTION
const USE_SENTRY = Boolean(process.env.SENTRY_AUTH_TOKEN)
return {
expo: {
version: VERSION,
@@ -43,6 +41,7 @@ module.exports = function (_config) {
icon: './assets/app-icons/ios_icon_default_light.png',
userInterfaceStyle: 'automatic',
primaryColor: '#1083fe',
newArchEnabled: false,
ios: {
supportsTablet: false,
bundleIdentifier: 'xyz.blueskyweb.app',
@@ -194,26 +193,17 @@ module.exports = function (_config) {
'react-native-edge-to-edge',
{android: {enforceNavigationBarContrast: false}},
],
USE_SENTRY && [
'@sentry/react-native/expo',
{
organization: 'blueskyweb',
project: 'app',
url: 'https://sentry.io',
},
],
[
'expo-build-properties',
{
ios: {
deploymentTarget: '15.1',
newArchEnabled: false,
buildReactNativeFromSource: true,
},
android: {
compileSdkVersion: 35,
targetSdkVersion: 35,
buildToolsVersion: '35.0.0',
newArchEnabled: false,
},
},
],
+1 -2
View File
@@ -63,7 +63,7 @@ After you do `yarn ios` and `yarn android` once, you can later just run `yarn we
### Tips
- Copy the `.env.example` to `.env` and fill in any necessary tokens. (The Sentry token is NOT required; see instructions below if you want to enable Sentry.)
- To run on the device, add `--device` to the command (e.g. `yarn android --device`). To build in production mode (slower build, faster app), also add `--variant release`.
- To run on the device, add `--device` to the command (e.g. `yarn android --device`). To build in production mode (slower build, faster app), also add `--variant release` on Android or `--configuration Release` on iOS.
- If you want to use Expo EAS on your own builds without ejecting from Expo, make sure to change the `owner` and `extra.eas.projectId` properties. If you do not have an Expo account, you may remove these properties.
- `npx react-native info` Checks what has been installed.
- If the Android simulator frequently hangs or is very sluggish, [bump its memory limit](https://stackoverflow.com/a/40068396)
@@ -164,7 +164,6 @@ See [testing.md](./testing.md).
- TextEncoder / TextDecoder
- react-native-url-polyfill
- Array#findLast (on web)
- atob (on native)
### Sentry sourcemaps
+1 -1
View File
@@ -6,7 +6,7 @@
},
"build": {
"base": {
"node": "18.18.2"
"node": "20.19.4"
},
"development": {
"extends": "base",
+1 -12
View File
@@ -33,7 +33,7 @@ jest.mock('react-native-safe-area-context', () => {
}
})
jest.mock('expo-file-system', () => ({
jest.mock('expo-file-system/legacy', () => ({
getInfoAsync: jest.fn().mockResolvedValue({exists: true, size: 100}),
deleteAsync: jest.fn(),
createDownloadResumable: jest.fn(),
@@ -65,17 +65,6 @@ jest.mock('lande', () => ({
default: jest.fn().mockReturnValue([['eng']]),
}))
jest.mock('sentry-expo', () => ({
init: () => jest.fn(),
Native: {
ReactNativeTracing: jest.fn().mockImplementation(() => ({
start: jest.fn(),
stop: jest.fn(),
})),
ReactNavigationInstrumentation: jest.fn(),
},
}))
jest.mock('crypto', () => ({}))
jest.mock('expo-application', () => ({
+3 -2
View File
@@ -1,6 +1,7 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const {getSentryExpoConfig} = require('@sentry/react-native/metro')
const cfg = getSentryExpoConfig(__dirname)
const {getDefaultConfig} = require('expo/metro-config')
const cfg = getDefaultConfig(__dirname)
cfg.resolver.sourceExts = process.env.RN_SRC_EXT
? process.env.RN_SRC_EXT.split(',').concat(cfg.resolver.sourceExts)
+1
View File
@@ -1,4 +1,5 @@
import ExpoModulesCore
import React
import UIKit
class SheetView: ExpoView, UISheetPresentationControllerDelegate {
@@ -1,4 +1,5 @@
import ExpoModulesCore
import React
// This view will be used as a native component. Make sure to inherit from `ExpoView`
// to apply the proper styling (e.g. border radius and shadows).
+57 -65
View File
@@ -63,8 +63,7 @@
"intl:push": "crowdin push translations --verbose -b main",
"nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android",
"update-extensions": "bash scripts/updateExtensions.sh",
"export": "npx expo export --dump-sourcemap && yarn upload-native-sourcemaps",
"upload-native-sourcemaps": "npx sentry-expo-upload-sourcemaps dist",
"export": "npx expo export --dump-sourcemap",
"make-deploy-bundle": "bash scripts/bundleUpdate.sh",
"generate-webpack-stats-file": "EXPO_PUBLIC_GENERATE_STATS=1 yarn build-web",
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web",
@@ -92,15 +91,12 @@
"@lingui/react": "^4.14.1",
"@mattermost/react-native-paste-input": "mattermost/react-native-paste-input",
"@miblanchard/react-native-slider": "^2.6.0",
"@mozzius/expo-dynamic-app-icon": "^1.7.1",
"@react-native-async-storage/async-storage": "2.1.2",
"@react-native-menu/menu": "^1.2.3",
"@react-native-picker/picker": "2.11.0",
"@mozzius/expo-dynamic-app-icon": "^1.8.0",
"@react-native-async-storage/async-storage": "2.2.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.14.0",
"@tanstack/query-async-storage-persister": "^5.25.0",
"@tanstack/react-query": "^5.8.1",
"@tanstack/react-query-persist-client": "^5.25.0",
@@ -126,41 +122,40 @@
"bcp-47": "^2.1.0",
"bcp-47-match": "^2.0.3",
"date-fns": "^2.30.0",
"deprecated-react-native-prop-types": "^5.0.0",
"email-validator": "^2.0.4",
"emoji-mart": "^5.5.2",
"emoji-regex": "^10.4.0",
"eventemitter3": "^5.0.1",
"expo": "53.0.11",
"expo-application": "~6.1.4",
"expo-blur": "~14.1.5",
"expo-build-properties": "~0.14.6",
"expo-camera": "~16.1.8",
"expo-clipboard": "~7.1.4",
"expo-dev-client": "~5.2.0",
"expo-device": "~7.1.4",
"expo-file-system": "~18.1.10",
"expo-font": "~13.3.1",
"expo-haptics": "~14.1.4",
"expo-image": "^2.4.0",
"expo": "54.0.2",
"expo-application": "~7.0.7",
"expo-blur": "~15.0.7",
"expo-build-properties": "~1.0.8",
"expo-camera": "~17.0.7",
"expo-clipboard": "~8.0.7",
"expo-dev-client": "~6.0.12",
"expo-device": "~8.0.7",
"expo-file-system": "~19.0.12",
"expo-font": "~14.0.8",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.8",
"expo-image-crop-tool": "^0.1.8",
"expo-image-manipulator": "~13.1.7",
"expo-image-picker": "17.0.5",
"expo-intent-launcher": "^12.1.5",
"expo-linear-gradient": "~14.1.5",
"expo-linking": "~7.1.5",
"expo-localization": "~16.1.5",
"expo-location": "~18.1.6",
"expo-media-library": "~17.1.7",
"expo-notifications": "~0.31.3",
"expo-screen-orientation": "~8.1.7",
"expo-sharing": "~13.1.5",
"expo-splash-screen": "~0.30.9",
"expo-system-ui": "~5.0.8",
"expo-task-manager": "~13.1.5",
"expo-updates": "~0.28.14",
"expo-video": "~2.2.1",
"expo-web-browser": "~14.1.6",
"expo-image-manipulator": "~14.0.7",
"expo-image-picker": "~17.0.8",
"expo-intent-launcher": "~13.0.7",
"expo-linear-gradient": "~15.0.7",
"expo-linking": "~8.0.8",
"expo-localization": "~17.0.7",
"expo-location": "~19.0.7",
"expo-media-library": "~18.1.1",
"expo-notifications": "~0.32.11",
"expo-screen-orientation": "~9.0.7",
"expo-sharing": "~14.0.7",
"expo-splash-screen": "~31.0.9",
"expo-system-ui": "~6.0.7",
"expo-task-manager": "~14.0.7",
"expo-updates": "~29.0.10",
"expo-video": "~3.0.11",
"expo-web-browser": "~15.0.7",
"fast-text-encoding": "^1.0.6",
"history": "^5.3.0",
"hls.js": "^1.6.2",
@@ -178,37 +173,36 @@
"patch-package": "^6.5.1",
"postinstall-postinstall": "^2.1.0",
"psl": "^1.9.0",
"radix-ui": "^1.2.0",
"react": "19.0.0",
"radix-ui": "^1.4.3",
"react": "19.1.0",
"react-compiler-runtime": "^19.1.0-rc.1",
"react-dom": "19.0.0",
"react-dom": "19.1.0",
"react-image-crop": "^11.0.7",
"react-is": "19",
"react-keyed-flatten-children": "^5.0.0",
"react-native": "^0.79.3",
"react-native-compressor": "^1.11.0",
"react-native-date-picker": "^5.0.12",
"react-native": "0.81.4",
"react-native-compressor": "^1.12.0",
"react-native-date-picker": "^5.0.13",
"react-native-device-attest": "^0.1.6",
"react-native-drawer-layout": "^4.1.8",
"react-native-edge-to-edge": "^1.6.0",
"react-native-gesture-handler": "2.25.0",
"react-native-gesture-handler": "~2.28.0",
"react-native-get-random-values": "~1.11.0",
"react-native-ios-context-menu": "^1.15.3",
"react-native-keyboard-controller": "^1.17.5",
"react-native-keyboard-controller": "1.18.5",
"react-native-mmkv": "^2.12.2",
"react-native-pager-view": "6.8.0",
"react-native-progress": "bluesky-social/react-native-progress",
"react-native-qrcode-styled": "^0.3.3",
"react-native-reanimated": "~3.17.5",
"react-native-reanimated": "^3.19.1",
"react-native-root-siblings": "^5.0.1",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "^4.11.1",
"react-native-svg": "15.12.0",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "15.12.1",
"react-native-uitextview": "^1.4.0",
"react-native-url-polyfill": "^1.3.0",
"react-native-uuid": "^2.0.3",
"react-native-view-shot": "^4.0.3",
"react-native-web": "~0.20.0",
"react-native-web": "^0.21.0",
"react-native-web-webview": "^1.0.2",
"react-native-webview": "^13.13.5",
"react-remove-scroll-bar": "^2.3.8",
@@ -220,7 +214,6 @@
"tippy.js": "^6.3.7",
"tlds": "^1.234.0",
"tldts": "^6.1.46",
"zeego": "^1.6.2",
"zod": "^3.20.2"
},
"devDependencies": {
@@ -228,14 +221,13 @@
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@babel/runtime": "^7.26.0",
"@expo/config-plugins": "~10.0.2",
"@expo/config-plugins": "~54.0.1",
"@lingui/cli": "^4.14.1",
"@lingui/macro": "^4.14.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@react-native/babel-preset": "0.79.3",
"@react-native/eslint-config": "^0.79.3",
"@react-native/typescript-config": "^0.79.3",
"@sentry/webpack-plugin": "^3.2.2",
"@react-native/babel-preset": "0.81.4",
"@react-native/eslint-config": "^0.81.4",
"@react-native/typescript-config": "^0.81.4",
"@testing-library/jest-native": "^5.4.3",
"@testing-library/react-native": "^13.2.0",
"@types/jest": "^29.4.0",
@@ -245,14 +237,14 @@
"@types/lodash.shuffle": "^4.2.7",
"@types/psl": "^1.1.1",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.8",
"@types/react-dom": "^19.1.9",
"@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.1.0-rc.1",
"babel-preset-expo": "~13.1.11",
"babel-preset-expo": "~54.0.0",
"eslint": "^8.19.0",
"eslint-plugin-bsky-internal": "link:./eslint",
"eslint-plugin-ft-flow": "^2.0.3",
@@ -266,7 +258,7 @@
"husky": "^8.0.3",
"is-ci": "^3.0.1",
"jest": "^29.7.0",
"jest-expo": "~53.0.7",
"jest-expo": "~54.0.10",
"jest-junit": "^16.0.0",
"lint-staged": "^13.2.3",
"lockfile-lint": "^4.14.0",
@@ -276,14 +268,14 @@
"react-refresh": "^0.14.0",
"svgo": "^3.3.2",
"ts-node": "^10.9.1",
"typescript": "~5.8.3",
"typescript": "^5.9.2",
"webpack-bundle-analyzer": "^4.10.1"
},
"resolutions": {
"@expo/image-utils": "0.6.3",
"@react-native/babel-preset": "0.79.3",
"@react-native/normalize-colors": "0.79.3",
"**/expo-constants": "17.0.3",
"@react-native/babel-preset": "0.81.4",
"@react-native/normalize-colors": "0.81.4",
"**/@expo/image-utils": "0.8.7",
"**/expo-constants": "18.0.8",
"**/expo-device": "7.1.4",
"**/zod": "3.23.8",
"**/multiformats": "9.9.0"
@@ -0,0 +1,13 @@
diff --git a/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt b/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt
index 4ed2307..ede1181 100644
--- a/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt
+++ b/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt
@@ -54,7 +54,7 @@ class PasteTextInputManager(context: ReactApplicationContext) : ReactTextInputMa
}
override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
- val map = super.getExportedCustomBubblingEventTypeConstants()!!
+ val map = super.getExportedCustomBubblingEventTypeConstants().toMutableMap()
map["onPaste"] = MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onPaste")
-13
View File
@@ -1,13 +0,0 @@
diff --git a/node_modules/@sentry/react-native/scripts/expo-upload-sourcemaps.js b/node_modules/@sentry/react-native/scripts/expo-upload-sourcemaps.js
index c282ade..8777755 100755
--- a/node_modules/@sentry/react-native/scripts/expo-upload-sourcemaps.js
+++ b/node_modules/@sentry/react-native/scripts/expo-upload-sourcemaps.js
@@ -215,7 +215,7 @@ for (const [assetGroupName, assets] of Object.entries(groupedAssets)) {
const isHermes = assets.find(asset => asset.endsWith('.hbc'));
const windowsCallback = process.platform === "win32" ? 'node ' : '';
- execSync(`${windowsCallback}${sentryCliBin} sourcemaps upload ${isHermes ? '--debug-id-reference' : ''} ${assets.join(' ')}`, {
+ execSync(`${windowsCallback}${sentryCliBin} sourcemaps upload ${isHermes ? '--debug-id-reference' : ''} ${assets.join(' ')} --dist ${process.env.SENTRY_DIST}`, {
env: {
...process.env,
[SENTRY_PROJECT]: sentryProject,
-19
View File
@@ -1,19 +0,0 @@
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 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
@@ -112,11 +112,9 @@ class MediaLibraryModule : Module() {
}
AsyncFunction("createAssetAsync") { localUri: String, albumId: String?, promise: Promise ->
- throwUnlessPermissionsGranted {
- withModuleScope(promise) {
- CreateAssetWithAlbumId(context, localUri, promise, true, albumId)
- .execute()
- }
+ withModuleScope(promise) {
+ CreateAssetWithAlbumId(context, localUri, promise, true, albumId)
+ .execute()
}
}
+13
View File
@@ -0,0 +1,13 @@
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 2dc1db2..ee844e8 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
@@ -105,7 +105,7 @@ class MediaLibraryModule : Module() {
}
AsyncFunction("createAssetAsync") Coroutine { localUri: String, albumId: String? ->
- requireSystemPermissions()
+ // requireSystemPermissions()
return@Coroutine createAssetWithAlbumId(context, localUri, true, albumId)
}
@@ -15,7 +15,7 @@ diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/Scro
index d029337..0f63ea3 100644
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
@@ -1003,6 +1003,11 @@ - (void)_adjustForMaintainVisibleContentPosition
@@ -1038,6 +1038,11 @@ - (void)_adjustForMaintainVisibleContentPosition
}
}
@@ -0,0 +1,17 @@
diff --git a/node_modules/react-native-date-picker/ios/RNDatePicker.h b/node_modules/react-native-date-picker/ios/RNDatePicker.h
index 480746e..470dc3a 100644
--- a/node_modules/react-native-date-picker/ios/RNDatePicker.h
+++ b/node_modules/react-native-date-picker/ios/RNDatePicker.h
@@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_END
#else
#import "DatePicker.h"
#import <UIKit/UIKit.h>
+#include <string>
@interface RNDatePicker : DatePicker
@@ -22,4 +23,3 @@ NS_ASSUME_NONNULL_END
@end
#endif
-
@@ -1,36 +0,0 @@
diff --git a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm
index 43d11b4..5bf0d32 100644
--- a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm
+++ b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm
@@ -461,16 +461,23 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
- if ([_handlersToWaitFor count]) {
- RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
- if (handler != nil) {
- for (NSNumber *handlerTag in _handlersToWaitFor) {
- if ([handler.tag isEqual:handlerTag]) {
- return YES;
- }
- }
+ RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
+ if (handler == nil) {
+ return NO;
+ }
+
+ for (NSNumber *handlerTag in _handlersToWaitFor) {
+ if ([handler.tag isEqual:handlerTag]) {
+ return YES;
}
}
+
+ for (NSNumber *handlerTag in handler->_handlersThatShouldWait) {
+ if ([_tag isEqual:handlerTag]) {
+ return YES;
+ }
+ }
+
return NO;
}
+91
View File
@@ -0,0 +1,91 @@
diff --git a/node_modules/react-native-mmkv/lib/commonjs/createMMKV.js b/node_modules/react-native-mmkv/lib/commonjs/createMMKV.js
index b9b9d69..9ec36f9 100644
--- a/node_modules/react-native-mmkv/lib/commonjs/createMMKV.js
+++ b/node_modules/react-native-mmkv/lib/commonjs/createMMKV.js
@@ -38,8 +38,8 @@ const createMMKV = config => {
}
// Check if we are running on-device (JSI)
- if (global.nativeCallSyncHook == null || MMKVModule.install == null) {
- throw new Error('Failed to create a new MMKV instance: React Native is not running on-device. MMKV can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.');
+ if (isRemoteDebuggingInChrome() || MMKVModule.install == null) {
+ throw new Error(`Failed to create a new MMKV instance: React Native is not running on-device. MMKV can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead. isRemoteDebuggingInChrome(): ${isRemoteDebuggingInChrome()}; MMKVModule.install == null: ${MMKVModule.install == null}`);
}
// Call the synchronous blocking install() function
@@ -51,5 +51,13 @@ const createMMKV = config => {
}
return global.mmkvCreateNewInstance(config);
};
+function isRemoteDebuggingInChrome() {
+ // Remote debugging in Chrome is not supported in bridgeless
+ if ('RN$Bridgeless' in global && RN$Bridgeless === true) {
+ return false;
+ }
+
+ return __DEV__ && typeof global.nativeCallSyncHook === 'undefined';
+}
exports.createMMKV = createMMKV;
//# sourceMappingURL=createMMKV.js.map
diff --git a/node_modules/react-native-mmkv/lib/module/createMMKV.js b/node_modules/react-native-mmkv/lib/module/createMMKV.js
index 644840d..6e704d6 100644
--- a/node_modules/react-native-mmkv/lib/module/createMMKV.js
+++ b/node_modules/react-native-mmkv/lib/module/createMMKV.js
@@ -32,8 +32,8 @@ export const createMMKV = config => {
}
// Check if we are running on-device (JSI)
- if (global.nativeCallSyncHook == null || MMKVModule.install == null) {
- throw new Error('Failed to create a new MMKV instance: React Native is not running on-device. MMKV can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.');
+ if (isRemoteDebuggingInChrome() || MMKVModule.install == null) {
+ throw new Error(`Failed to create a new MMKV instance: React Native is not running on-device. MMKV can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead. isRemoteDebuggingInChrome(): ${isRemoteDebuggingInChrome()}; MMKVModule.install == null: ${MMKVModule.install == null}`);
}
// Call the synchronous blocking install() function
@@ -45,4 +45,13 @@ export const createMMKV = config => {
}
return global.mmkvCreateNewInstance(config);
};
+
+function isRemoteDebuggingInChrome() {
+ // Remote debugging in Chrome is not supported in bridgeless
+ if ('RN$Bridgeless' in global && RN$Bridgeless === true) {
+ return false;
+ }
+
+ return __DEV__ && typeof global.nativeCallSyncHook === 'undefined';
+}
//# sourceMappingURL=createMMKV.js.map
diff --git a/node_modules/react-native-mmkv/src/createMMKV.ts b/node_modules/react-native-mmkv/src/createMMKV.ts
index 15d29a0..55675fd 100644
--- a/node_modules/react-native-mmkv/src/createMMKV.ts
+++ b/node_modules/react-native-mmkv/src/createMMKV.ts
@@ -46,9 +46,9 @@ export const createMMKV = (config: MMKVConfiguration): NativeMMKV => {
}
// Check if we are running on-device (JSI)
- if (global.nativeCallSyncHook == null || MMKVModule.install == null) {
+ if (isRemoteDebuggingInChrome() || MMKVModule.install == null) {
throw new Error(
- 'Failed to create a new MMKV instance: React Native is not running on-device. MMKV can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.'
+ `Failed to create a new MMKV instance: React Native is not running on-device. MMKV can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead. isRemoteDebuggingInChrome(): ${isRemoteDebuggingInChrome()}; MMKVModule.install == null: ${MMKVModule.install == null}`
);
}
@@ -68,3 +68,16 @@ export const createMMKV = (config: MMKVConfiguration): NativeMMKV => {
return global.mmkvCreateNewInstance(config);
};
+
+declare global {
+ const RN$Bridgeless: boolean;
+}
+
+function isRemoteDebuggingInChrome() {
+ // Remote debugging in Chrome is not supported in bridgeless
+ if ('RN$Bridgeless' in global && RN$Bridgeless === true) {
+ return false;
+ }
+
+ return __DEV__ && typeof global.nativeCallSyncHook === 'undefined';
+}
@@ -0,0 +1,3 @@
# react-native-mmkv+2.12.2.patch.md
Patch of https://github.com/mrousavy/react-native-mmkv/pull/778
@@ -1,44 +0,0 @@
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
+1 -3
View File
@@ -1,4 +1,3 @@
import '#/logger/sentry/setup'
import '#/logger/bitdrift/setup'
import '#/view/icons'
@@ -14,7 +13,6 @@ import * as SplashScreen from 'expo-splash-screen'
import * as SystemUI from 'expo-system-ui'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import * as Sentry from '@sentry/react-native'
import {KeyboardControllerProvider} from '#/lib/hooks/useEnableKeyboardController'
import {Provider as HideBottomBarBorderProvider} from '#/lib/hooks/useHideBottomBarBorder'
@@ -253,4 +251,4 @@ function App() {
)
}
export default Sentry.wrap(App)
export default App
+1 -3
View File
@@ -1,4 +1,3 @@
import '#/logger/sentry/setup' // must be near top
import '#/view/icons'
import './style.css'
@@ -7,7 +6,6 @@ import {RootSiblingParent} from 'react-native-root-siblings'
import {SafeAreaProvider} from 'react-native-safe-area-context'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import * as Sentry from '@sentry/react-native'
import {QueryProvider} from '#/lib/react-query'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
@@ -221,4 +219,4 @@ function App() {
)
}
export default Sentry.wrap(App)
export default App
+3 -5
View File
@@ -2,8 +2,6 @@ import {
Children,
cloneElement,
isValidElement,
type ReactElement,
type ReactNode,
useCallback,
useEffect,
useMemo,
@@ -26,7 +24,7 @@ import {useA11y} from '#/state/a11y'
* screen reader support is enabled. THIS SHOULD BE USED SPARINGLY, only when
* no better option is available.
*/
export function FocusScope({children}: {children: ReactNode}) {
export function FocusScope({children}: {children: React.ReactNode}) {
const {screenReaderEnabled} = useA11y()
return screenReaderEnabled ? <FocusTrap>{children}</FocusTrap> : children
@@ -41,7 +39,7 @@ export function FocusScope({children}: {children: ReactNode}) {
* they have reached the start or end of the content and tell them how to
* remain within the active content section.
*/
function FocusTrap({children}: {children: ReactNode}) {
function FocusTrap({children}: {children: React.ReactNode}) {
const {_} = useLingui()
const child = useRef<View>(null)
@@ -53,7 +51,7 @@ function FocusTrap({children}: {children: ReactNode}) {
const decoratedChildren = useMemo(() => {
return Children.toArray(children).map((node, i) => {
if (i === 0 && isValidElement(node)) {
const n = node as ReactElement<any>
const n = node as React.ReactElement<any>
if (n.props.ref !== undefined) {
throw new Error(
'FocusScope needs to override the ref on its first child.',
+1 -2
View File
@@ -1,4 +1,3 @@
import {type ReactNode} from 'react'
import {FocusScope as RadixFocusScope} from 'radix-ui/internal'
/*
@@ -6,7 +5,7 @@ import {FocusScope as RadixFocusScope} from 'radix-ui/internal'
* use this in Dialogs and such already. It's here as a convenient counterpart
* to the hacky native solution.
*/
export function FocusScope({children}: {children: ReactNode}) {
export function FocusScope({children}: {children: React.ReactNode}) {
return (
<RadixFocusScope.FocusScope loop asChild trapped>
{children}
+1 -1
View File
@@ -72,9 +72,9 @@ export function ActionsWrapper({
return (
<View
// @ts-expect-error web only
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
// @ts-expect-error web only
onFocus={onFocus}
onBlur={onMouseLeave}
style={[a.flex_1, isFromSelf ? a.flex_row : a.flex_row_reverse]}
+1 -1
View File
@@ -1,4 +1,4 @@
import {copyAsync} from 'expo-file-system'
import {copyAsync} from 'expo-file-system/legacy'
import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api'
import {safeDeleteAsync} from '#/lib/media/manip'
+1 -1
View File
@@ -10,7 +10,7 @@ import {
makeDirectoryAsync,
StorageAccessFramework,
writeAsStringAsync,
} from 'expo-file-system'
} from 'expo-file-system/legacy'
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
import * as MediaLibrary from 'expo-media-library'
import * as Sharing from 'expo-sharing'
+1 -1
View File
@@ -157,7 +157,7 @@ function createResizedImage(
export async function saveBytesToDisk(
filename: string,
bytes: Uint8Array,
bytes: Uint8Array<ArrayBuffer>,
type: string,
) {
const blob = new Blob([bytes], {type})
+1 -1
View File
@@ -2,7 +2,7 @@ import {
documentDirectory,
getInfoAsync,
readDirectoryAsync,
} from 'expo-file-system'
} from 'expo-file-system/legacy'
import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool'
import {compressIfNeeded} from './manip'
+1 -1
View File
@@ -1,4 +1,4 @@
import {createUploadTask, FileSystemUploadType} from 'expo-file-system'
import {createUploadTask, FileSystemUploadType} from 'expo-file-system/legacy'
import {type AppBskyVideoDefs, type BskyAgent} from '@atproto/api'
import {type I18n} from '@lingui/core'
import {msg} from '@lingui/macro'
-481
View File
@@ -1,481 +0,0 @@
import {beforeAll, describe, expect, jest, test} from '@jest/globals'
import * as Sentry from '@sentry/react-native'
import {nanoid} from 'nanoid/non-secure'
import {Logger} from '#/logger'
import {sentryTransport} from '#/logger/transports/sentry'
import {LogLevel} from '#/logger/types'
jest.mock('@sentry/react-native', () => ({
addBreadcrumb: jest.fn(),
captureException: jest.fn(),
captureMessage: jest.fn(),
}))
beforeAll(() => {
jest.useFakeTimers()
})
describe('general functionality', () => {
test('default params', () => {
const logger = new Logger()
expect(logger.level).toEqual(LogLevel.Info)
})
test('can override default params', () => {
const logger = new Logger({
level: LogLevel.Debug,
})
expect(logger.level).toEqual(LogLevel.Debug)
})
test('contextFilter overrides level', () => {
const logger = new Logger({
level: LogLevel.Info,
contextFilter: 'test',
})
expect(logger.level).toEqual(LogLevel.Debug)
})
test('supports extra metadata', () => {
const timestamp = Date.now()
const logger = new Logger({})
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
const extra = {foo: true}
logger.warn('message', extra)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
'message',
extra,
timestamp,
)
})
test('supports nullish/falsy metadata', () => {
const timestamp = Date.now()
const logger = new Logger({})
const mockTransport = jest.fn()
const remove = logger.addTransport(mockTransport)
// @ts-expect-error testing the JS case
logger.warn('a', null)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
'a',
{},
timestamp,
)
// @ts-expect-error testing the JS case
logger.warn('b', false)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
'b',
{},
timestamp,
)
// @ts-expect-error testing the JS case
logger.warn('c', 0)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
'c',
{},
timestamp,
)
remove()
logger.addTransport((level, context, message, metadata) => {
expect(typeof metadata).toEqual('object')
})
// @ts-expect-error testing the JS case
logger.warn('message', null)
})
test('sentryTransport', () => {
const message = 'message'
const timestamp = Date.now()
const sentryTimestamp = timestamp / 1000
/*
sentryTransport(
LogLevel.Debug,
Logger.Context.Default,
message,
{},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
category: Logger.Context.Default,
message,
data: {__context__: 'logger'},
type: 'default',
level: LogLevel.Debug,
timestamp: sentryTimestamp,
})
*/
sentryTransport(
LogLevel.Info,
Logger.Context.Default,
message,
{type: 'info', prop: true},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
category: Logger.Context.Default,
message,
data: {prop: true, __context__: 'logger'},
type: 'info',
level: LogLevel.Info,
timestamp: sentryTimestamp,
})
sentryTransport(
LogLevel.Log,
Logger.Context.Default,
message,
{},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
category: Logger.Context.Default,
message,
data: {__context__: 'logger'},
type: 'default',
level: 'log',
timestamp: sentryTimestamp,
})
jest.runAllTimers()
expect(Sentry.captureMessage).toHaveBeenCalledWith(message, {
level: 'log',
tags: {category: 'logger'},
extra: {__context__: 'logger'},
})
sentryTransport(
LogLevel.Warn,
Logger.Context.Default,
message,
{},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
category: Logger.Context.Default,
message,
data: {__context__: 'logger'},
type: 'default',
level: 'warning',
timestamp: sentryTimestamp,
})
jest.runAllTimers()
expect(Sentry.captureMessage).toHaveBeenCalledWith(message, {
level: 'warning',
tags: {category: 'logger'},
extra: {__context__: 'logger'},
})
const e = new Error('error')
const tags = {
prop: 'prop',
}
sentryTransport(
LogLevel.Error,
Logger.Context.Default,
e,
{
tags,
prop: true,
},
timestamp,
)
expect(Sentry.captureException).toHaveBeenCalledWith(e, {
tags: {
...tags,
category: 'logger',
},
extra: {
prop: true,
__context__: 'logger',
},
})
})
test('sentryTransport serializes errors', () => {
const message = 'message'
const timestamp = Date.now()
const sentryTimestamp = timestamp / 1000
sentryTransport(
LogLevel.Info,
undefined,
message,
{error: new Error('foo')},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
message,
data: {error: 'Error: foo'},
type: 'default',
level: LogLevel.Info,
timestamp: sentryTimestamp,
})
})
test('add/remove transport', () => {
const timestamp = Date.now()
const logger = new Logger({})
const mockTransport = jest.fn()
const remove = logger.addTransport(mockTransport)
logger.warn('warn')
remove()
logger.warn('warn')
// only called once bc it was removed
expect(mockTransport).toHaveBeenNthCalledWith(
1,
LogLevel.Warn,
undefined,
'warn',
{},
timestamp,
)
})
})
describe('create', () => {
test('create', () => {
const mockTransport = jest.fn()
const timestamp = Date.now()
const message = nanoid()
const logger = Logger.create(Logger.Context.Default)
logger.addTransport(mockTransport)
logger.info(message, {})
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Info,
Logger.Context.Default,
message,
{},
timestamp,
)
})
})
describe('debug contexts', () => {
test('specific', () => {
const mockTransport = jest.fn()
const timestamp = Date.now()
const message = nanoid()
const logger = new Logger({
// @ts-ignore
context: 'specific',
level: LogLevel.Debug,
})
logger.addTransport(mockTransport)
logger.debug(message, {})
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Debug,
'specific',
message,
{},
timestamp,
)
})
test('namespaced', () => {
const mockTransport = jest.fn()
const timestamp = Date.now()
const message = nanoid()
const logger = new Logger({
// @ts-ignore
context: 'namespace:foo',
contextFilter: 'namespace:*',
level: LogLevel.Debug,
})
logger.addTransport(mockTransport)
logger.debug(message, {})
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Debug,
'namespace:foo',
message,
{},
timestamp,
)
})
test('ignores inactive', () => {
const mockTransport = jest.fn()
const timestamp = Date.now()
const message = nanoid()
const logger = new Logger({
// @ts-ignore
context: 'namespace:bar:baz',
contextFilter: 'namespace:foo:*',
})
logger.addTransport(mockTransport)
logger.debug(message, {})
expect(mockTransport).not.toHaveBeenCalledWith(
LogLevel.Debug,
'namespace:bar:baz',
message,
{},
timestamp,
)
})
})
describe('supports levels', () => {
test('debug', () => {
const timestamp = Date.now()
const logger = new Logger({
level: LogLevel.Debug,
})
const message = nanoid()
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
logger.debug(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Debug,
undefined,
message,
{},
timestamp,
)
logger.info(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Info,
undefined,
message,
{},
timestamp,
)
logger.warn(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
message,
{},
timestamp,
)
const e = new Error(message)
logger.error(e)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Error,
undefined,
e,
{},
timestamp,
)
})
test('info', () => {
const timestamp = Date.now()
const logger = new Logger({
level: LogLevel.Info,
})
const message = nanoid()
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
logger.debug(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.info(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Info,
undefined,
message,
{},
timestamp,
)
})
test('warn', () => {
const timestamp = Date.now()
const logger = new Logger({
level: LogLevel.Warn,
})
const message = nanoid()
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
logger.debug(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.info(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.warn(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
message,
{},
timestamp,
)
})
test('error', () => {
const timestamp = Date.now()
const logger = new Logger({
level: LogLevel.Error,
})
const message = nanoid()
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
logger.debug(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.info(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.warn(message)
expect(mockTransport).not.toHaveBeenCalled()
const e = new Error('original message')
logger.error(e)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Error,
undefined,
e,
{},
timestamp,
)
})
})
+1 -4
View File
@@ -5,7 +5,6 @@ import {add} from '#/logger/logDump'
import {type MetricEvents} from '#/logger/metrics'
import {bitdriftTransport} from '#/logger/transports/bitdrift'
import {consoleTransport} from '#/logger/transports/console'
import {sentryTransport} from '#/logger/transports/sentry'
import {
LogContext,
LogLevel,
@@ -19,9 +18,7 @@ import {ENV} from '#/env'
const TRANSPORTS: Transport[] = (function configureTransports() {
switch (ENV) {
case 'production': {
return [sentryTransport, isNative && bitdriftTransport].filter(
Boolean,
) as Transport[]
return [isNative && bitdriftTransport].filter(Boolean) as Transport[]
}
case 'test': {
return []
-1
View File
@@ -1 +0,0 @@
export * as Sentry from '@sentry/react-native'
-1
View File
@@ -1 +0,0 @@
export * as Sentry from '@sentry/react-native'
-32
View File
@@ -1,32 +0,0 @@
import {init} from '@sentry/react-native'
import * as env from '#/env'
init({
enabled: !env.IS_DEV && !!env.SENTRY_DSN,
autoSessionTracking: false,
dsn: env.SENTRY_DSN,
debug: false, // If `true`, Sentry will try to print out useful debugging information if something goes wrong with sending the event. Set it to `false` in production
environment: env.ENV,
dist: env.BUNDLE_IDENTIFIER,
release: env.RELEASE_VERSION,
ignoreErrors: [
/*
* Unknown internals errors
*/
`t is not defined`,
`Can't find variable: t`,
/*
* Un-useful errors
*/
`Network request failed`,
],
/**
* Does not affect traces of error events or other logs, just disables
* automatically attaching stack traces to events. This helps us group events
* and prevents explosions of separate issues.
*
* @see https://docs.sentry.io/platforms/react-native/configuration/options/#attach-stacktrace
*/
attachStacktrace: false,
})
+4 -27
View File
@@ -1,5 +1,4 @@
import {isNetworkError} from '#/lib/strings/errors'
import {Sentry} from '#/logger/sentry/lib'
import {LogLevel, type Transport} from '#/logger/types'
import {prepareMetadata} from '#/logger/util'
@@ -7,8 +6,7 @@ export const sentryTransport: Transport = (
level,
context,
message,
{type, tags, ...metadata},
timestamp,
{tags, ...metadata},
) => {
// Skip debug messages entirely for now - esb
if (level === LogLevel.Debug) return
@@ -38,15 +36,6 @@ export const sentryTransport: Transport = (
} as const
)[level]
Sentry.addBreadcrumb({
category: context,
message,
data: meta,
type: type || 'default',
level: severity,
timestamp: timestamp / 1000, // Sentry expects seconds
})
// We don't want to send any network errors to sentry
if (isNetworkError(message)) {
return
@@ -68,20 +57,15 @@ export const sentryTransport: Transport = (
/**
* It's otherwise an Error and should be reported with captureException
*/
Sentry.captureException(message, {
tags: _tags,
extra: meta,
})
}
}
const queuedMessages: [string, Parameters<typeof Sentry.captureMessage>[1]][] =
[]
const queuedMessages: [string, Parameters<any>[1]][] = []
let sentrySendTimeout: ReturnType<typeof setTimeout> | null = null
function queueMessageForSentry(
message: string,
captureContext: Parameters<typeof Sentry.captureMessage>[1],
captureContext: Parameters<any>[1],
) {
queuedMessages.push([message, captureContext])
if (!sentrySendTimeout) {
@@ -94,11 +78,4 @@ function queueMessageForSentry(
}
}
function sendQueuedMessages() {
while (queuedMessages.length > 0) {
const record = queuedMessages.shift()
if (record) {
Sentry.captureMessage(record[0], record[1])
}
}
}
function sendQueuedMessages() {}
@@ -333,9 +333,9 @@ function ChatListItemReady({
return (
<GestureActionView actions={actions}>
<View
// @ts-expect-error web only
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
// @ts-expect-error web only
onFocus={onFocus}
onBlur={onMouseLeave}
style={[a.relative, t.atoms.bg]}>
+1 -1
View File
@@ -1,7 +1,7 @@
import {useMemo} from 'react'
import {Platform} from 'react-native'
import {setStringAsync} from 'expo-clipboard'
import * as FileSystem from 'expo-file-system'
import * as FileSystem from 'expo-file-system/legacy'
import {Image} from 'expo-image'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+1 -1
View File
@@ -420,7 +420,7 @@ function DevOptions() {
{
style: 'default',
text: 'Apply',
onPress: channel => {
onPress: (channel?: string) => {
tryApplyUpdate(channel ?? '')
},
},
+1 -1
View File
@@ -3,7 +3,7 @@ import {
deleteAsync,
makeDirectoryAsync,
moveAsync,
} from 'expo-file-system'
} from 'expo-file-system/legacy'
import {
type Action,
type ActionCrop,
+26 -11
View File
@@ -173,8 +173,10 @@ export const ComposePost = ({
imageUris: initImageUris,
videoUri: initVideoUri,
cancelRef,
setIsDirty,
}: Props & {
cancelRef?: React.RefObject<CancelRef | null>
setIsDirty?: React.Dispatch<React.SetStateAction<boolean>>
}) => {
const {currentAccount} = useSession()
const agent = useAgent()
@@ -304,24 +306,37 @@ export const ComposePost = ({
[insets, isKeyboardVisible],
)
const onPressCancel = useCallback(() => {
if (textInput.current?.maybeClosePopup()) {
const isDirty = thread.posts.some(
post =>
post.shortenedGraphemeLength > 0 || post.embed.media || post.embed.link,
)
// very unfortunate, but we need to pass state back up to the parent on iOS
//
// WARNING - if the Modal on iOS thinks it's not dirty,
// `allowSwipeDismissal` will be true, and if we don't then close the composer
// when `onPressCancel` is called it will be bad (might even softlock)
// so we need to keep the parent state and the behaviour of onPressCancel in
// tight sync. do NOT force the modal to stay open without marking it as dirty! -sfn
useEffect(() => {
if (isIOS) {
setIsDirty?.(isDirty)
}
}, [isDirty, setIsDirty])
const onPressCancel = useNonReactiveCallback(() => {
// web only, so it's fine w.r.t. the Modal
const didCloseAutocomplete = textInput.current?.maybeClosePopup()
if (isWeb && didCloseAutocomplete) {
return
} else if (
thread.posts.some(
post =>
post.shortenedGraphemeLength > 0 ||
post.embed.media ||
post.embed.link,
)
) {
} else if (isDirty) {
closeAllDialogs()
Keyboard.dismiss()
discardPromptControl.open()
} else {
onClose()
}
}, [thread, closeAllDialogs, discardPromptControl, onClose])
})
useImperativeHandle(cancelRef, () => ({onPressCancel}))
-340
View File
@@ -1,340 +0,0 @@
import React from 'react'
import {
Platform,
Pressable,
StyleSheet,
View,
type ViewStyle,
} from 'react-native'
import {type IconProp} from '@fortawesome/fontawesome-svg-core'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import * as DropdownMenu from 'zeego/dropdown-menu'
import {type MenuItemCommonProps} from 'zeego/lib/typescript/menu'
import {usePalette} from '#/lib/hooks/usePalette'
import {useTheme} from '#/lib/ThemeContext'
import {isIOS} from '#/platform/detection'
import {Portal} from '#/components/Portal'
// Custom Dropdown Menu Components
// ==
/**
* @deprecated use Menu from `#/components/Menu.tsx` instead
*/
export const DropdownMenuRoot = DropdownMenu.Root
// export const DropdownMenuTrigger = DropdownMenu.Trigger
/**
* @deprecated use Menu from `#/components/Menu.tsx` instead
*/
export const DropdownMenuContent = DropdownMenu.Content
type TriggerProps = Omit<
React.ComponentProps<(typeof DropdownMenu)['Trigger']>,
'children'
> &
React.PropsWithChildren<{
testID?: string
accessibilityLabel?: string
accessibilityHint?: string
}>
/**
* @deprecated use Menu from `#/components/Menu.tsx` instead
*/
export const DropdownMenuTrigger = DropdownMenu.create(
(props: TriggerProps) => {
const theme = useTheme()
const defaultCtrlColor = theme.palette.default.postCtrl
return (
// This Pressable doesn't actually do anything other than
// provide the "pressed state" visual feedback.
<Pressable
testID={props.testID}
accessibilityRole="button"
accessibilityLabel={props.accessibilityLabel}
accessibilityHint={props.accessibilityHint}
style={({pressed}) => [{opacity: pressed ? 0.8 : 1}]}>
<DropdownMenu.Trigger action="press">
<View>
{props.children ? (
props.children
) : (
<FontAwesomeIcon
icon="ellipsis"
size={20}
color={defaultCtrlColor}
/>
)}
</View>
</DropdownMenu.Trigger>
</Pressable>
)
},
'Trigger',
)
type ItemProps = React.ComponentProps<(typeof DropdownMenu)['Item']>
/**
* @deprecated use Menu from `#/components/Menu.tsx` instead
*/
export const DropdownMenuItem = DropdownMenu.create(
(props: ItemProps & {testID?: string}) => {
const theme = useTheme()
const [focused, setFocused] = React.useState(false)
const backgroundColor = theme.colorScheme === 'dark' ? '#fff1' : '#0001'
return (
<DropdownMenu.Item
{...props}
style={[styles.item, focused && {backgroundColor: backgroundColor}]}
onFocus={() => {
setFocused(true)
props.onFocus && props.onFocus()
}}
onBlur={() => {
setFocused(false)
props.onBlur && props.onBlur()
}}
/>
)
},
'Item',
)
type TitleProps = React.ComponentProps<(typeof DropdownMenu)['ItemTitle']>
/**
* @deprecated use Menu from `#/components/Menu.tsx` instead
*/
export const DropdownMenuItemTitle = DropdownMenu.create(
(props: TitleProps) => {
const pal = usePalette('default')
return (
<DropdownMenu.ItemTitle
{...props}
style={[props.style, pal.text, styles.itemTitle]}
/>
)
},
'ItemTitle',
)
type IconProps = React.ComponentProps<(typeof DropdownMenu)['ItemIcon']>
/**
* @deprecated use Menu from `#/components/Menu.tsx` instead
*/
export const DropdownMenuItemIcon = DropdownMenu.create((props: IconProps) => {
return <DropdownMenu.ItemIcon {...props} />
}, 'ItemIcon')
type SeparatorProps = React.ComponentProps<(typeof DropdownMenu)['Separator']>
/**
* @deprecated use Menu from `#/components/Menu.tsx` instead
*/
export const DropdownMenuSeparator = DropdownMenu.create(
(props: SeparatorProps) => {
const pal = usePalette('default')
const theme = useTheme()
const {borderColor: separatorColor} =
theme.colorScheme === 'dark' ? pal.borderDark : pal.border
return (
<DropdownMenu.Separator
{...props}
style={[
props.style,
styles.separator,
{backgroundColor: separatorColor},
]}
/>
)
},
'Separator',
)
// Types for Dropdown Menu and Items
export type DropdownItem = {
label: string | 'separator'
onPress?: () => void
testID?: string
icon?: {
ios: MenuItemCommonProps['ios']
android: string
web: IconProp
}
}
type Props = {
items: DropdownItem[]
testID?: string
accessibilityLabel?: string
accessibilityHint?: string
triggerStyle?: ViewStyle
}
/**
* The `NativeDropdown` function uses native iOS and Android dropdown menus.
* It also creates a animated custom dropdown for web that uses
* Radix UI primitives under the hood
* @prop {DropdownItem[]} items - An array of dropdown items
* @prop {React.ReactNode} children - A custom dropdown trigger
*
* @deprecated use Menu from `#/components/Menu.tsx` instead
*/
export function NativeDropdown({
items,
children,
testID,
accessibilityLabel,
accessibilityHint,
}: React.PropsWithChildren<Props>) {
const pal = usePalette('default')
const theme = useTheme()
const [isOpen, setIsOpen] = React.useState(false)
const dropDownBackgroundColor =
theme.colorScheme === 'dark' ? pal.btn : pal.viewLight
return (
<>
{isIOS && isOpen && (
<Portal>
<Backdrop />
</Portal>
)}
<DropdownMenuRoot onOpenWillChange={setIsOpen}>
<DropdownMenuTrigger
action="press"
testID={testID}
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}>
{children}
</DropdownMenuTrigger>
{/* @ts-ignore inheriting props from Radix, which is only for web */}
<DropdownMenuContent
style={[styles.content, dropDownBackgroundColor]}
loop>
{items.map((item, index) => {
if (item.label === 'separator') {
return (
<DropdownMenuSeparator
key={getKey(item.label, index, item.testID)}
/>
)
}
if (index > 1 && items[index - 1].label === 'separator') {
return (
<DropdownMenu.Group
key={getKey(item.label, index, item.testID)}>
<DropdownMenuItem
key={getKey(item.label, index, item.testID)}
onSelect={item.onPress}>
<DropdownMenuItemTitle>{item.label}</DropdownMenuItemTitle>
{item.icon && (
<DropdownMenuItemIcon
ios={item.icon.ios}
// androidIconName={item.icon.android} TODO: Add custom android icon support, because these ones are based on https://developer.android.com/reference/android/R.drawable.html and they are ugly
>
<FontAwesomeIcon
icon={item.icon.web}
size={20}
style={[pal.text]}
/>
</DropdownMenuItemIcon>
)}
</DropdownMenuItem>
</DropdownMenu.Group>
)
}
return (
<DropdownMenuItem
key={getKey(item.label, index, item.testID)}
onSelect={item.onPress}>
<DropdownMenuItemTitle>{item.label}</DropdownMenuItemTitle>
{item.icon && (
<DropdownMenuItemIcon
ios={item.icon.ios}
// androidIconName={item.icon.android}
>
<FontAwesomeIcon
icon={item.icon.web}
size={20}
style={[pal.text]}
/>
</DropdownMenuItemIcon>
)}
</DropdownMenuItem>
)
})}
</DropdownMenuContent>
</DropdownMenuRoot>
</>
)
}
function Backdrop() {
// Not visible but it eats the click outside.
// Only necessary for iOS.
return (
<Pressable
accessibilityRole="button"
accessibilityLabel="Dialog backdrop"
accessibilityHint="Press the backdrop to close the dialog"
style={{
top: 0,
left: 0,
right: 0,
bottom: 0,
position: 'absolute',
}}
onPress={() => {
/* noop */
}}
/>
)
}
const getKey = (label: string, index: number, id?: string) => {
if (id) {
return id
}
return `${label}_${index}`
}
const styles = StyleSheet.create({
separator: {
height: 1,
marginVertical: 4,
},
content: {
backgroundColor: '#f0f0f0',
borderRadius: 8,
paddingVertical: 4,
paddingHorizontal: 4,
marginTop: 6,
...Platform.select({
web: {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
animationKeyframes: {
'0%': {opacity: 0, transform: [{scale: 0.5}]},
'100%': {opacity: 1, transform: [{scale: 1}]},
},
boxShadow:
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
transformOrigin: 'var(--radix-dropdown-menu-content-transform-origin)',
},
}),
},
item: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
columnGap: 20,
// @ts-ignore -web
cursor: 'pointer',
paddingVertical: 8,
paddingHorizontal: 12,
borderRadius: 8,
},
itemTitle: {
fontSize: 18,
},
})
@@ -1,286 +0,0 @@
import React from 'react'
import {
Pressable,
StyleSheet,
Text,
type View,
type ViewStyle,
} from 'react-native'
import {type IconProp} from '@fortawesome/fontawesome-svg-core'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {DropdownMenu} from 'radix-ui'
import {type MenuItemCommonProps} from 'zeego/lib/typescript/menu'
import {HITSLOP_10} from '#/lib/constants'
import {usePalette} from '#/lib/hooks/usePalette'
import {useTheme} from '#/lib/ThemeContext'
// Custom Dropdown Menu Components
// ==
export const DropdownMenuRoot = DropdownMenu.Root
export const DropdownMenuContent = DropdownMenu.Content
type ItemProps = React.ComponentProps<(typeof DropdownMenu)['Item']>
export const DropdownMenuItem = (props: ItemProps & {testID?: string}) => {
const theme = useTheme()
const [focused, setFocused] = React.useState(false)
const backgroundColor = theme.colorScheme === 'dark' ? '#fff1' : '#0001'
return (
<DropdownMenu.Item
className="nativeDropdown-item"
{...props}
style={StyleSheet.flatten([
styles.item,
focused && {backgroundColor: backgroundColor},
])}
onFocus={() => {
setFocused(true)
}}
onBlur={() => {
setFocused(false)
}}
/>
)
}
// Types for Dropdown Menu and Items
export type DropdownItem = {
label: string | 'separator'
onPress?: () => void
testID?: string
icon?: {
ios: MenuItemCommonProps['ios']
android: string
web: IconProp
}
}
type Props = {
items: DropdownItem[]
testID?: string
accessibilityLabel?: string
accessibilityHint?: string
triggerStyle?: ViewStyle
}
/**
* @deprecated use Menu from `#/components/Menu.tsx` instead
*/
export function NativeDropdown({
items,
children,
testID,
accessibilityLabel,
accessibilityHint,
triggerStyle,
}: React.PropsWithChildren<Props>) {
const [open, setOpen] = React.useState(false)
const buttonRef = React.useRef<HTMLButtonElement>(null)
const menuRef = React.useRef<HTMLDivElement>(null)
React.useEffect(() => {
if (!open) {
return
}
function clickHandler(e: MouseEvent) {
const t = e.target
if (!open) return
if (!t) return
if (!buttonRef.current || !menuRef.current) return
if (
t !== buttonRef.current &&
!buttonRef.current.contains(t as Node) &&
t !== menuRef.current &&
!menuRef.current.contains(t as Node)
) {
// prevent clicking through to links beneath dropdown
// only applies to mobile web
e.preventDefault()
e.stopPropagation()
// close menu
setOpen(false)
}
}
function keydownHandler(e: KeyboardEvent) {
if (e.key === 'Escape' && open) {
setOpen(false)
}
}
document.addEventListener('click', clickHandler, true)
window.addEventListener('keydown', keydownHandler, true)
return () => {
document.removeEventListener('click', clickHandler, true)
window.removeEventListener('keydown', keydownHandler, true)
}
}, [open, setOpen])
return (
<DropdownMenuRoot open={open} onOpenChange={o => setOpen(o)}>
<DropdownMenu.Trigger asChild>
<Pressable
ref={buttonRef as unknown as React.Ref<View>}
testID={testID}
accessibilityRole="button"
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}
onPointerDown={e => {
// Prevent false positive that interpret mobile scroll as a tap.
// This requires the custom onPress handler below to compensate.
// https://github.com/radix-ui/primitives/issues/1912
e.preventDefault()
}}
onPress={() => {
if (window.event instanceof KeyboardEvent) {
// The onPointerDown hack above is not relevant to this press, so don't do anything.
return
}
// Compensate for the disabled onPointerDown above by triggering it manually.
setOpen(o => !o)
}}
hitSlop={HITSLOP_10}
style={triggerStyle}>
{children}
</Pressable>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownContent items={items} menuRef={menuRef} />
</DropdownMenu.Portal>
</DropdownMenuRoot>
)
}
function DropdownContent({
items,
menuRef,
}: {
items: DropdownItem[]
menuRef: React.RefObject<HTMLDivElement | null>
}) {
const pal = usePalette('default')
const theme = useTheme()
const dropDownBackgroundColor =
theme.colorScheme === 'dark' ? pal.btn : pal.view
const {borderColor: separatorColor} =
theme.colorScheme === 'dark' ? pal.borderDark : pal.border
return (
<DropdownMenu.Content
ref={menuRef}
style={
StyleSheet.flatten([
styles.content,
dropDownBackgroundColor,
]) as React.CSSProperties
}
loop>
{items.map((item, index) => {
if (item.label === 'separator') {
return (
<DropdownMenu.Separator
key={getKey(item.label, index, item.testID)}
style={
StyleSheet.flatten([
styles.separator,
{backgroundColor: separatorColor},
]) as React.CSSProperties
}
/>
)
}
if (index > 1 && items[index - 1].label === 'separator') {
return (
<DropdownMenu.Group key={getKey(item.label, index, item.testID)}>
<DropdownMenuItem
key={getKey(item.label, index, item.testID)}
onSelect={item.onPress}>
<Text selectable={false} style={[pal.text, styles.itemTitle]}>
{item.label}
</Text>
{item.icon && (
<FontAwesomeIcon
icon={item.icon.web}
size={20}
color={pal.colors.textLight}
/>
)}
</DropdownMenuItem>
</DropdownMenu.Group>
)
}
return (
<DropdownMenuItem
key={getKey(item.label, index, item.testID)}
onSelect={item.onPress}>
<Text selectable={false} style={[pal.text, styles.itemTitle]}>
{item.label}
</Text>
{item.icon && (
<FontAwesomeIcon
icon={item.icon.web}
size={20}
color={pal.colors.textLight}
/>
)}
</DropdownMenuItem>
)
})}
</DropdownMenu.Content>
)
}
const getKey = (label: string, index: number, id?: string) => {
if (id) {
return id
}
return `${label}_${index}`
}
const styles = StyleSheet.create({
separator: {
height: 1,
marginTop: 4,
marginBottom: 4,
},
content: {
backgroundColor: '#f0f0f0',
borderRadius: 8,
paddingTop: 4,
paddingBottom: 4,
paddingLeft: 4,
paddingRight: 4,
marginTop: 6,
// @ts-ignore web only -prf
boxShadow: 'rgba(0, 0, 0, 0.3) 0px 5px 20px',
},
item: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
columnGap: 20,
cursor: 'pointer',
paddingTop: 8,
paddingBottom: 8,
paddingLeft: 12,
paddingRight: 12,
borderRadius: 8,
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif',
// @ts-expect-error web only
outline: 0,
border: 0,
},
itemTitle: {
fontSize: 16,
fontWeight: '600',
paddingRight: 10,
},
})
+12 -4
View File
@@ -1,4 +1,4 @@
import React from 'react'
import {useEffect, useRef, useState} from 'react'
import {Modal, View} from 'react-native'
import {useDialogStateControlContext} from '#/state/dialogs'
@@ -11,11 +11,17 @@ export function Composer({}: {winHeight: number}) {
const t = useTheme()
const state = useComposerState()
const ref = useComposerCancelRef()
const [isDirty, setIsDirty] = useState(
!!state?.text ||
!!state?.imageUris ||
!!state?.videoUri ||
!!state?.mention,
)
const open = !!state
const prevOpen = React.useRef(open)
const prevOpen = useRef(open)
React.useEffect(() => {
useEffect(() => {
if (open && !prevOpen.current) {
setFullyExpandedCount(c => c + 1)
} else if (!open && prevOpen.current) {
@@ -31,7 +37,8 @@ export function Composer({}: {winHeight: number}) {
visible={open}
presentationStyle="pageSheet"
animationType="slide"
onRequestClose={() => ref.current?.onPressCancel()}>
onRequestClose={() => ref.current?.onPressCancel()}
allowSwipeDismissal={!isDirty}>
<View style={[t.atoms.bg, a.flex_1]}>
<ComposePost
cancelRef={ref}
@@ -43,6 +50,7 @@ export function Composer({}: {winHeight: number}) {
text={state?.text}
imageUris={state?.imageUris}
videoUri={state?.videoUri}
setIsDirty={setIsDirty}
/>
</View>
</Modal>
+1363 -1857
View File
File diff suppressed because it is too large Load Diff