Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae7e0c90fb | |||
| 912b1a2b4a | |||
| c6dd0d09e4 | |||
| 1b5cef3d1a | |||
| a0e37e93a0 | |||
| 67038d929d | |||
| 5bea24906e | |||
| 0cba15b833 | |||
| 5ad61d39e7 | |||
| fca34e27b9 | |||
| 73e82f3177 | |||
| d13bcff613 | |||
| 54a3a350e2 | |||
| f61a4fe7a0 | |||
| 82e63b5189 | |||
| 1bf9a2e578 | |||
| 0e9c186d4f | |||
| 1b2ec14ffd | |||
| c96c0bb72b | |||
| cee61feb67 | |||
| 5944074504 | |||
| c1c4f07be1 | |||
| ec831163d4 | |||
| e2c7978953 | |||
| 937edf8826 | |||
| 2397832bd5 | |||
| e9b1330249 | |||
| 4cc6824745 | |||
| 8a5fc05700 | |||
| 103c23517e | |||
| 5d95a91437 | |||
| 3a08e1a488 | |||
| b4563b6b26 | |||
| 6d1cb5ffd9 | |||
| 614a5a3183 | |||
| 18aa91c802 | |||
| 4b1ce6c9f9 | |||
| 1ee9950227 | |||
| cd9874b26f | |||
| 0a116d1d51 | |||
| fe338d33c2 | |||
| d61208a63e | |||
| 035b7a54be |
@@ -196,14 +196,7 @@ jobs:
|
||||
with:
|
||||
header: fingerprint-diff
|
||||
message: |
|
||||
The Pull Request introduced fingerprint changes against the base commit:
|
||||
<details><summary>Fingerprint diff</summary>
|
||||
|
||||
```json
|
||||
${{ steps.fingerprint.outputs.diff }}
|
||||
```
|
||||
|
||||
</details>
|
||||
The Pull Request introduced native fingerprint changes against the base commit.
|
||||
|
||||
---
|
||||
*Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
|
||||
|
||||
+1
-5
@@ -184,10 +184,6 @@ module.exports = function (_config) {
|
||||
androidStatusBar: {
|
||||
barStyle: 'light-content',
|
||||
},
|
||||
// Dark nav bar in light mode is better than light nav bar in dark mode
|
||||
androidNavigationBar: {
|
||||
barStyle: 'light-content',
|
||||
},
|
||||
android: {
|
||||
icon: './assets/app-icons/android_icon_default_next.png',
|
||||
adaptiveIcon: {
|
||||
@@ -261,7 +257,7 @@ module.exports = function (_config) {
|
||||
'expo-build-properties',
|
||||
{
|
||||
ios: {
|
||||
deploymentTarget: '15.1',
|
||||
deploymentTarget: '16.4',
|
||||
buildReactNativeFromSource: true,
|
||||
ccacheEnabled: IS_DEV,
|
||||
cxxLanguageStandard: 'c++23',
|
||||
|
||||
+22
-13
@@ -21,9 +21,10 @@ jest.mock('react-native-safe-area-context', () => {
|
||||
const inset = {top: 0, right: 0, bottom: 0, left: 0}
|
||||
return {
|
||||
SafeAreaProvider: jest.fn().mockImplementation(({children}) => children),
|
||||
SafeAreaConsumer: jest
|
||||
.fn()
|
||||
.mockImplementation(({children}) => children(inset)),
|
||||
SafeAreaConsumer: jest.fn().mockImplementation(
|
||||
/** @param {{children: (i: typeof inset) => unknown}} props */
|
||||
({children}) => children(inset),
|
||||
),
|
||||
useSafeAreaInsets: jest.fn().mockImplementation(() => inset),
|
||||
}
|
||||
})
|
||||
@@ -85,18 +86,26 @@ jest.mock('expo-application', () => ({
|
||||
}))
|
||||
|
||||
jest.mock('expo-modules-core', () => ({
|
||||
requireNativeModule: jest.fn().mockImplementation(moduleName => {
|
||||
if (moduleName === 'ExpoPlatformInfo') {
|
||||
return {
|
||||
getIsReducedMotionEnabled: () => false,
|
||||
requireNativeModule: jest.fn().mockImplementation(
|
||||
/** @param {string} moduleName */
|
||||
moduleName => {
|
||||
if (moduleName === 'ExpoPlatformInfo') {
|
||||
return {
|
||||
getIsReducedMotionEnabled: () => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (moduleName === 'BottomSheet') {
|
||||
return {
|
||||
dismissAll: () => {},
|
||||
if (moduleName === 'BottomSheet') {
|
||||
return {
|
||||
dismissAll: () => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
const expoModules = /** @type {Record<string, unknown> | undefined} */ (
|
||||
globalThis.expo?.modules
|
||||
)
|
||||
return expoModules?.[moduleName]
|
||||
},
|
||||
),
|
||||
requireNativeViewManager: jest.fn().mockImplementation(_ => {
|
||||
return () => null
|
||||
}),
|
||||
|
||||
@@ -1,49 +1,20 @@
|
||||
apply plugin: 'com.android.library'
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
group = 'expo.modules.bottomsheet'
|
||||
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.modules.bottomsheet"
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "0.1.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':expo-modules-core')
|
||||
implementation 'com.google.android.material:material:1.13.0'
|
||||
implementation "com.facebook.react:react-native:+"
|
||||
}
|
||||
|
||||
@@ -1,93 +1,19 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'maven-publish'
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
group = 'expo.modules.backgroundnotificationhandler'
|
||||
version = '0.5.0'
|
||||
|
||||
buildscript {
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
if (expoModulesCorePlugin.exists()) {
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Ensures backward compatibility
|
||||
ext.getKotlinVersion = {
|
||||
if (ext.has("kotlinVersion")) {
|
||||
ext.kotlinVersion()
|
||||
} else {
|
||||
ext.safeExtGet("kotlinVersion", "1.8.10")
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
from components.release
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url = mavenLocal().url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 33)
|
||||
|
||||
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
|
||||
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
||||
}
|
||||
}
|
||||
|
||||
namespace "expo.modules.backgroundnotificationhandler"
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet("minSdkVersion", 21)
|
||||
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
||||
versionCode 1
|
||||
versionName "0.5.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
publishing {
|
||||
singleVariant("release") {
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':expo-modules-core')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
||||
implementation 'com.google.firebase:firebase-messaging-ktx:24.0.0'
|
||||
}
|
||||
|
||||
@@ -1,97 +1,21 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'maven-publish'
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
group = 'expo.modules.blueskygifview'
|
||||
version = '0.5.0'
|
||||
|
||||
buildscript {
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
if (expoModulesCorePlugin.exists()) {
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Ensures backward compatibility
|
||||
ext.getKotlinVersion = {
|
||||
if (ext.has("kotlinVersion")) {
|
||||
ext.kotlinVersion()
|
||||
} else {
|
||||
ext.safeExtGet("kotlinVersion", "1.8.10")
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
from components.release
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url = mavenLocal().url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 33)
|
||||
|
||||
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
|
||||
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
||||
}
|
||||
}
|
||||
|
||||
namespace "expo.modules.blueskygifview"
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet("minSdkVersion", 21)
|
||||
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
||||
versionCode 1
|
||||
versionName "0.5.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
publishing {
|
||||
singleVariant("release") {
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
def GLIDE_VERSION = "4.16.0"
|
||||
|
||||
implementation project(':expo-modules-core')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
||||
|
||||
// Keep glide version up to date with expo-image so that we don't have duplicate deps
|
||||
implementation 'com.github.bumptech.glide:glide:4.13.2'
|
||||
|
||||
@@ -1,45 +1,17 @@
|
||||
apply plugin: 'com.android.library'
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
group = 'expo.modules.blueskyswissarmy'
|
||||
version = '0.6.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.modules.blueskyswissarmy"
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "0.6.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -10,7 +10,7 @@ Pod::Spec.new do |s|
|
||||
s.static_framework = true
|
||||
|
||||
s.dependency 'ExpoModulesCore'
|
||||
s.dependency 'EXNotifications'
|
||||
s.dependency 'ExpoNotifications'
|
||||
|
||||
# Swift/Objective-C compatibility
|
||||
s.pod_target_xcconfig = {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import EXNotifications
|
||||
import ExpoNotifications
|
||||
import ExpoModulesCore
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
|
||||
@@ -1,34 +1,15 @@
|
||||
apply plugin: 'com.android.library'
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
group = 'expo.modules.blueskyvideocompress'
|
||||
version = '1.0.0'
|
||||
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
useCoreDependencies()
|
||||
useExpoPublishing()
|
||||
|
||||
buildscript {
|
||||
ext.safeExtGet = { prop, fallback ->
|
||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
||||
|
||||
namespace "expo.modules.blueskyvideocompress"
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet("minSdkVersion", 21)
|
||||
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
}
|
||||
|
||||
@@ -1,46 +1,19 @@
|
||||
apply plugin: 'com.android.library'
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.emoji2:emoji2-emojipicker:1.5.0"
|
||||
}
|
||||
|
||||
@@ -1,92 +1,15 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'maven-publish'
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'expo-module-gradle-plugin'
|
||||
}
|
||||
|
||||
group = 'xyz.blueskyweb.app.exporeceiveandroidintents'
|
||||
version = '0.4.1'
|
||||
|
||||
buildscript {
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
if (expoModulesCorePlugin.exists()) {
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Ensures backward compatibility
|
||||
ext.getKotlinVersion = {
|
||||
if (ext.has("kotlinVersion")) {
|
||||
ext.kotlinVersion()
|
||||
} else {
|
||||
ext.safeExtGet("kotlinVersion", "1.8.10")
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
from components.release
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url = mavenLocal().url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 33)
|
||||
|
||||
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
|
||||
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
||||
}
|
||||
}
|
||||
|
||||
namespace "xyz.blueskyweb.app.exporeceiveandroidintents"
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet("minSdkVersion", 21)
|
||||
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
||||
versionCode 1
|
||||
versionName "0.4.1"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
publishing {
|
||||
singleVariant("release") {
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':expo-modules-core')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
||||
}
|
||||
|
||||
@@ -1502,7 +1502,7 @@
|
||||
},
|
||||
"src/view/com/composer/drafts/state/storage.ts": {
|
||||
"typescript/require-await": {
|
||||
"count": 3
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/view/com/composer/photos/EditImageDialog.web.tsx": {
|
||||
|
||||
+54
-52
@@ -165,43 +165,44 @@
|
||||
"emoji-mart": "^5.6.0",
|
||||
"emoji-regex": "^10.4.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"expo": "54.0.35",
|
||||
"expo-age-range": "0.2.18",
|
||||
"expo-application": "~7.0.8",
|
||||
"expo-asset": "~12.0.13",
|
||||
"expo-blur": "~15.0.8",
|
||||
"expo-build-properties": "~1.0.10",
|
||||
"expo-camera": "~17.0.10",
|
||||
"expo-clipboard": "~8.0.8",
|
||||
"expo-contacts": "^15.0.10",
|
||||
"expo-dev-client": "~6.0.20",
|
||||
"expo-device": "~8.0.10",
|
||||
"expo-file-system": "~19.0.21",
|
||||
"expo-font": "~14.0.11",
|
||||
"expo-glass-effect": "0.1.10",
|
||||
"expo-haptics": "~15.0.8",
|
||||
"expo-image": "~3.0.11",
|
||||
"expo-image-manipulator": "~14.0.8",
|
||||
"expo-image-picker": "~17.0.10",
|
||||
"expo-intent-launcher": "~13.0.8",
|
||||
"expo-keep-awake": "~15.0.8",
|
||||
"expo-linear-gradient": "~15.0.8",
|
||||
"expo-linking": "~8.0.11",
|
||||
"expo-localization": "~17.0.8",
|
||||
"expo-location": "~19.0.8",
|
||||
"expo-media-library": "~18.2.1",
|
||||
"expo-notifications": "~0.32.17",
|
||||
"expo": "57.0.8",
|
||||
"expo-age-range": "57.0.2",
|
||||
"expo-application": "~57.0.2",
|
||||
"expo-asset": "~57.0.7",
|
||||
"expo-blur": "~57.0.2",
|
||||
"expo-build-properties": "~57.0.7",
|
||||
"expo-camera": "~57.0.3",
|
||||
"expo-clipboard": "~57.0.1",
|
||||
"expo-contacts": "^57.0.2",
|
||||
"expo-dev-client": "~57.0.9",
|
||||
"expo-device": "~57.0.1",
|
||||
"expo-file-system": "~57.0.1",
|
||||
"expo-font": "~57.0.1",
|
||||
"expo-glass-effect": "57.0.1",
|
||||
"expo-haptics": "~57.0.1",
|
||||
"expo-image": "57.0.1",
|
||||
"expo-image-manipulator": "~57.0.6",
|
||||
"expo-image-picker": "~57.0.6",
|
||||
"expo-intent-launcher": "~57.0.1",
|
||||
"expo-keep-awake": "~57.0.1",
|
||||
"expo-linear-gradient": "~57.0.1",
|
||||
"expo-linking": "~57.0.4",
|
||||
"expo-localization": "~57.0.1",
|
||||
"expo-location": "~57.0.6",
|
||||
"expo-media-library": "57.0.3",
|
||||
"expo-modules-core": "57.0.8",
|
||||
"expo-notifications": "57.0.7",
|
||||
"expo-paste-input": "^0.2.1",
|
||||
"expo-privacy-sensitive": "^0.2.0",
|
||||
"expo-screen-orientation": "~9.0.8",
|
||||
"expo-sharing": "~14.0.8",
|
||||
"expo-sms": "^14.0.7",
|
||||
"expo-splash-screen": "~31.0.13",
|
||||
"expo-system-ui": "~6.0.9",
|
||||
"expo-updates": "~29.0.17",
|
||||
"expo-video": "~3.0.16",
|
||||
"expo-video-thumbnails": "^10.0.8",
|
||||
"expo-web-browser": "~15.0.10",
|
||||
"expo-screen-orientation": "~57.0.1",
|
||||
"expo-sharing": "~57.0.7",
|
||||
"expo-sms": "^57.0.1",
|
||||
"expo-splash-screen": "~57.0.5",
|
||||
"expo-system-ui": "~57.0.1",
|
||||
"expo-updates": "57.0.10",
|
||||
"expo-video": "~57.0.2",
|
||||
"expo-video-thumbnails": "^57.0.1",
|
||||
"expo-web-browser": "~57.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-text-encoding": "^1.0.6",
|
||||
"fuse.js": "^7.1.0",
|
||||
@@ -221,36 +222,36 @@
|
||||
"normalize-url": "^8.0.0",
|
||||
"psl": "1.9.0",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "19.1.0",
|
||||
"react": "19.2.3",
|
||||
"react-compiler-runtime": "19.1.0-rc.3",
|
||||
"react-dom": "19.1.0",
|
||||
"react-dom": "19.2.3",
|
||||
"react-hotkeys-hook": "5.2.4",
|
||||
"react-image-crop": "^11.0.7",
|
||||
"react-is": "19",
|
||||
"react-keyed-flatten-children": "^5.0.0",
|
||||
"react-native": "0.81.5",
|
||||
"react-native": "0.86.0",
|
||||
"react-native-compressor": "1.13.0",
|
||||
"react-native-date-picker": "^5.0.13",
|
||||
"react-native-device-attest": "^0.1.6",
|
||||
"react-native-drawer-layout": "^4.2.3",
|
||||
"react-native-edge-to-edge": "^1.8.1",
|
||||
"react-native-gesture-handler": "~2.30.0",
|
||||
"react-native-keyboard-controller": "^1.21.8",
|
||||
"react-native-gesture-handler": "~2.32.0",
|
||||
"react-native-keyboard-controller": "1.21.9",
|
||||
"react-native-mmkv": "^3.3.3",
|
||||
"react-native-pager-view": "6.8.0",
|
||||
"react-native-progress": "^5.0.1",
|
||||
"react-native-qrcode-styled": "^0.3.3",
|
||||
"react-native-reanimated": "~4.3.2",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "4.24.0",
|
||||
"react-native-reanimated": "~4.5.3",
|
||||
"react-native-safe-area-context": "~5.7.0",
|
||||
"react-native-screens": "4.26.2",
|
||||
"react-native-scroll-forwarder": "link:./modules/react-native-scroll-forwarder",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-svg": "15.15.4",
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-view-shot": "^4.0.3",
|
||||
"react-native-view-shot": "^5.1.0",
|
||||
"react-native-web": "^0.21.0",
|
||||
"react-native-web-webview": "^1.0.2",
|
||||
"react-native-webview": "^13.15.0",
|
||||
"react-native-worklets": "0.8.3",
|
||||
"react-native-webview": "^13.16.1",
|
||||
"react-native-worklets": "0.11.3",
|
||||
"react-remove-scroll-bar": "^2.3.8",
|
||||
"react-responsive": "^10.0.1",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
@@ -272,7 +273,8 @@
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.9.2",
|
||||
"@lingui/cli": "^5.9.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||
"@react-native/babel-preset": "0.81.5",
|
||||
"@react-native/babel-preset": "0.86.0",
|
||||
"@react-native/jest-preset": "0.86.0",
|
||||
"@react-native/typescript-config": "^0.81.5",
|
||||
"@sentry/webpack-plugin": "^3.2.2",
|
||||
"@testing-library/react-native": "^13.2.0",
|
||||
@@ -281,13 +283,13 @@
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/lodash.shuffle": "^4.2.7",
|
||||
"@types/psl": "1.1.1",
|
||||
"@types/react": "^19.1.17",
|
||||
"@types/react-dom": "^19.1.11",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript/native": "npm:typescript@^7.0.2",
|
||||
"babel-jest": "^29.7.0",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"babel-plugin-react-compiler": "19.1.0-rc.3",
|
||||
"babel-preset-expo": "~54.0.10",
|
||||
"babel-preset-expo": "~57.0.4",
|
||||
"eslint-plugin-bsky-internal": "link:lint-rules",
|
||||
"eslint-plugin-react-native-a11y": "^3.5.1",
|
||||
"eslint-plugin-simple-import-sort": "^13.0.0",
|
||||
@@ -296,7 +298,7 @@
|
||||
"husky": "^9.1.7",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~54.0.17",
|
||||
"jest-expo": "~57.0.2",
|
||||
"jest-junit": "^16.0.0",
|
||||
"lint-staged": "^17.0.8",
|
||||
"oxlint": "^1.73.0",
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
diff --git a/ios/MediaHandler.swift b/ios/MediaHandler.swift
|
||||
index 6e4fbe1b3921173a1cc4e6eff626b0749e8bab14..e334abb680a8965acb1f6bbdfbc1325ad35edf3a 100644
|
||||
--- a/ios/MediaHandler.swift
|
||||
+++ b/ios/MediaHandler.swift
|
||||
@@ -310,10 +310,12 @@ internal struct MediaHandler {
|
||||
let fileExtension = getFileExtension(from: originalFilename)
|
||||
let destinationUrl = try generateUrl(withFileExtension: fileExtension)
|
||||
|
||||
+ let resourceOptions = PHAssetResourceRequestOptions()
|
||||
+ resourceOptions.isNetworkAccessAllowed = true
|
||||
try await PHAssetResourceManager.default().writeData(
|
||||
for: resource,
|
||||
toFile: destinationUrl,
|
||||
- options: nil
|
||||
+ options: resourceOptions
|
||||
)
|
||||
|
||||
let mimeType = getMimeType(from: destinationUrl.pathExtension)
|
||||
@@ -389,7 +391,9 @@ internal struct MediaHandler {
|
||||
|
||||
// Stream the resource into our cache directory. This API is asynchronous but doesn't require
|
||||
// a temporary file like `loadFileRepresentation`.
|
||||
- try await PHAssetResourceManager.default().writeData(for: resource, toFile: destinationUrl, options: nil)
|
||||
+ let resourceOptions = PHAssetResourceRequestOptions()
|
||||
+ resourceOptions.isNetworkAccessAllowed = true
|
||||
+ try await PHAssetResourceManager.default().writeData(for: resource, toFile: destinationUrl, options: resourceOptions)
|
||||
|
||||
// Build and return the result using the helper.
|
||||
let mimeType = getMimeType(from: destinationUrl.pathExtension)
|
||||
@@ -1,5 +0,0 @@
|
||||
# `expo-image-picker` patch
|
||||
|
||||
Patches this issue: https://github.com/expo/expo/issues/39937
|
||||
|
||||
Source: https://github.com/expo/expo/issues/39937#issuecomment-3342082239
|
||||
@@ -1,103 +0,0 @@
|
||||
diff --git a/build/Image.types.d.ts b/build/Image.types.d.ts
|
||||
index 022ae487e65ae9d624f8a4be262b01944928f250..416504fe18ecd00fdc713faca23485fb292caf2c 100644
|
||||
--- a/build/Image.types.d.ts
|
||||
+++ b/build/Image.types.d.ts
|
||||
@@ -152,6 +152,16 @@ export interface ImageProps extends Omit<ViewProps, 'style' | 'children'> {
|
||||
* @default 'normal'
|
||||
*/
|
||||
priority?: 'low' | 'normal' | 'high' | null;
|
||||
+ /**
|
||||
+ * The loading behavior for the image. Maps to the native HTML `loading` attribute on web.
|
||||
+ *
|
||||
+ * - `'lazy'` - Defers loading until the image is near the viewport.
|
||||
+ * - `'eager'` - Loads the image immediately.
|
||||
+ *
|
||||
+ * @default undefined
|
||||
+ * @platform web
|
||||
+ */
|
||||
+ loading?: 'lazy' | 'eager' | null;
|
||||
/**
|
||||
* Determines whether to cache the image and where: on the disk, in the memory or both.
|
||||
*
|
||||
diff --git a/src/ExpoImage.web.tsx b/src/ExpoImage.web.tsx
|
||||
index 2a49ff00649b374b86fa780653a4b0d6f13a4a57..1c3de93ef280bf6f3c27bed34c794b8ff59e3db3 100644
|
||||
--- a/src/ExpoImage.web.tsx
|
||||
+++ b/src/ExpoImage.web.tsx
|
||||
@@ -70,6 +70,7 @@ export default function ExpoImage({
|
||||
onLoadEnd,
|
||||
onDisplay,
|
||||
priority,
|
||||
+ loading,
|
||||
blurRadius,
|
||||
recyclingKey,
|
||||
style,
|
||||
@@ -118,6 +119,7 @@ export default function ExpoImage({
|
||||
accessibilityLabel={accessibilityLabel ?? alt}
|
||||
cachePolicy={cachePolicy}
|
||||
priority={priority}
|
||||
+ loading={loading}
|
||||
tintColor={tintColor}
|
||||
/>
|
||||
),
|
||||
@@ -149,6 +151,7 @@ export default function ExpoImage({
|
||||
className={className}
|
||||
cachePolicy={cachePolicy}
|
||||
priority={priority}
|
||||
+ loading={loading}
|
||||
contentPosition={selectedSource ? contentPosition : { top: '50%', left: '50%' }}
|
||||
hashPlaceholderContentPosition={contentPosition}
|
||||
hashPlaceholderStyle={imageHashStyle}
|
||||
diff --git a/src/Image.types.ts b/src/Image.types.ts
|
||||
index 9dec0e7aee61dfaa73a3cfa535d08259c9dad209..61c162114977dff35b633ac6b1f3d59e373bbbfe 100644
|
||||
--- a/src/Image.types.ts
|
||||
+++ b/src/Image.types.ts
|
||||
@@ -178,6 +178,17 @@ export interface ImageProps extends Omit<ViewProps, 'style' | 'children'> {
|
||||
*/
|
||||
priority?: 'low' | 'normal' | 'high' | null;
|
||||
|
||||
+ /**
|
||||
+ * The loading behavior for the image. Maps to the native HTML `loading` attribute on web.
|
||||
+ *
|
||||
+ * - `'lazy'` - Defers loading until the image is near the viewport.
|
||||
+ * - `'eager'` - Loads the image immediately.
|
||||
+ *
|
||||
+ * @default undefined
|
||||
+ * @platform web
|
||||
+ */
|
||||
+ loading?: 'lazy' | 'eager' | null;
|
||||
+
|
||||
/**
|
||||
* Determines whether to cache the image and where: on the disk, in the memory or both.
|
||||
*
|
||||
diff --git a/src/web/ImageWrapper.tsx b/src/web/ImageWrapper.tsx
|
||||
index e8f891d525892f8d3668e34cf96cefccfbfc49f6..89a5cb1e3a8574fc3bd1d0361895a610e3165dfb 100644
|
||||
--- a/src/web/ImageWrapper.tsx
|
||||
+++ b/src/web/ImageWrapper.tsx
|
||||
@@ -30,6 +30,7 @@ const ImageWrapper = React.forwardRef(
|
||||
contentPosition,
|
||||
hashPlaceholderContentPosition,
|
||||
priority,
|
||||
+ loading,
|
||||
style,
|
||||
hashPlaceholderStyle,
|
||||
tintColor,
|
||||
@@ -82,6 +83,7 @@ const ImageWrapper = React.forwardRef(
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line react/no-unknown-property
|
||||
fetchPriority={getFetchPriorityFromImagePriority(priority || 'normal')}
|
||||
+ loading={loading || undefined}
|
||||
{...getImageWrapperEventHandler(events, sourceWithHeaders)}
|
||||
{...getImgPropsFromSource(source)}
|
||||
{...props}
|
||||
diff --git a/src/web/ImageWrapper.types.ts b/src/web/ImageWrapper.types.ts
|
||||
index 19bbe2f15999124cda0ca7c81b3ebcf289f522f8..179837f803a3d315c85075895f4191631b37eda7 100644
|
||||
--- a/src/web/ImageWrapper.types.ts
|
||||
+++ b/src/web/ImageWrapper.types.ts
|
||||
@@ -29,6 +29,7 @@ export type ImageWrapperProps = {
|
||||
contentPosition?: ImageContentPositionObject;
|
||||
hashPlaceholderContentPosition?: ImageContentPositionObject;
|
||||
priority?: string | null;
|
||||
+ loading?: 'lazy' | 'eager' | null;
|
||||
style: CSSProperties;
|
||||
tintColor?: string | null;
|
||||
hashPlaceholderStyle?: CSSProperties;
|
||||
@@ -1,3 +0,0 @@
|
||||
## Expo Image
|
||||
|
||||
Patches in https://github.com/expo/expo/pull/41442
|
||||
@@ -1,51 +0,0 @@
|
||||
diff --git a/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
index 47c4d15f6b10bbd77858cfff425cda9a618735b9..afe138d22d566244482498a7be0e14b8454eab96 100644
|
||||
--- a/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
+++ b/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
@@ -125,6 +125,10 @@ internal fun peekResponseBody(
|
||||
}
|
||||
|
||||
internal fun shouldParseBody(response: Response): Boolean {
|
||||
+ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") {
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
// Check for Content-Type
|
||||
val skipContentTypes = listOf(
|
||||
"text/event-stream", // Server Sent Events
|
||||
diff --git a/ios/Core/ExpoBridgeModule.mm b/ios/Core/ExpoBridgeModule.mm
|
||||
index 2ed1c00f47406e109750cc27ace7e0d88e42c00e..99d0d140eddf95a8db7beb57c61dfcb12c1424b4 100644
|
||||
--- a/ios/Core/ExpoBridgeModule.mm
|
||||
+++ b/ios/Core/ExpoBridgeModule.mm
|
||||
@@ -7,6 +7,9 @@
|
||||
// The runtime executor is included as of React Native 0.74 in bridgeless mode.
|
||||
#if __has_include(<ReactCommon/RCTRuntimeExecutor.h>)
|
||||
#import <ReactCommon/RCTRuntimeExecutor.h>
|
||||
+#else // React Native <0.74
|
||||
+// dispatchBlock:queue: is declared in RCTBridge+Private.h, not the public header.
|
||||
+#import <React/RCTBridge+Private.h>
|
||||
#endif // React Native >=0.74
|
||||
|
||||
@implementation ExpoBridgeModule
|
||||
@@ -46,7 +49,20 @@ - (void)setBridge:(RCTBridge *)bridge
|
||||
_appContext.reactBridge = bridge;
|
||||
|
||||
#if !__has_include(<ReactCommon/RCTRuntimeExecutor.h>)
|
||||
- _appContext._runtime = [EXJavaScriptRuntimeManager runtimeFromBridge:bridge];
|
||||
+ // Hop the runtime install (and the prepareRuntime() chain it triggers via
|
||||
+ // _runtime.didSet) onto RCTJSThread. The original line ran synchronously
|
||||
+ // on whatever thread called setBridge: - typically the main thread - and
|
||||
+ // raced JSIExecutor::initializeRuntime() on the JS thread, corrupting
|
||||
+ // Hermes' Hades GC (HadesGC::writeBarrierSlow EXC_BAD_ACCESS).
|
||||
+ __weak EXAppContext *weakAppContext = _appContext;
|
||||
+ __weak RCTBridge *weakBridge = bridge;
|
||||
+ [bridge dispatchBlock:^{
|
||||
+ EXAppContext *strongAppContext = weakAppContext;
|
||||
+ RCTBridge *strongBridge = weakBridge;
|
||||
+ if (strongAppContext != nil && strongBridge != nil && strongAppContext._runtime == nil) {
|
||||
+ strongAppContext._runtime = [EXJavaScriptRuntimeManager runtimeFromBridge:strongBridge];
|
||||
+ }
|
||||
+ } queue:RCTJSThread];
|
||||
#endif // React Native <0.74
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
## expo-modules-core Patch
|
||||
|
||||
This patch contains two unrelated fixes:
|
||||
|
||||
### Android: bitdrift interceptor
|
||||
|
||||
Fixes an issue where bitdrift's API stream gets blocked by the Expo interceptor used to power the devtools.
|
||||
|
||||
### iOS: Hermes startup race in `ExpoBridgeModule.setBridge:`
|
||||
|
||||
On the legacy bridge (old architecture, where `RCTRuntimeExecutor.h` is
|
||||
absent), `setBridge:` installed the Expo runtime synchronously on whatever
|
||||
thread called it - typically the main thread, since RN's lazy module-load
|
||||
path ignores `+requiresMainQueueSetup`. The `_runtime.didSet` then ran
|
||||
`prepareRuntime()` (JSI mutations) on the main thread while the JS thread was
|
||||
concurrently inside `JSIExecutor::initializeRuntime()`. Two threads mutating
|
||||
the same Hermes runtime corrupted Hades GC, producing intermittent
|
||||
`EXC_BAD_ACCESS` launch crashes (e.g. `HadesGC::writeBarrierSlow`,
|
||||
`prepareRuntime` / `bindNativePerformanceNow`).
|
||||
|
||||
The fix hops the runtime install onto `RCTJSThread` so all JSI mutation is
|
||||
serialized on the JS thread. This backports the upstream fix discussed in
|
||||
expo/expo#45374; the racy `ExpoBridgeModule` is removed entirely in SDK 55+
|
||||
(expo/expo#44351), so this patch can be dropped on that upgrade.
|
||||
|
||||
Refs: expo/expo#43003, expo/expo#45374, expo/expo#44351
|
||||
@@ -0,0 +1,15 @@
|
||||
diff --git a/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
index 47c4d15f6b10bbd77858cfff425cda9a618735b9..afe138d22d566244482498a7be0e14b8454eab96 100644
|
||||
--- a/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
+++ b/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
@@ -125,6 +125,10 @@ internal fun peekResponseBody(
|
||||
}
|
||||
|
||||
internal fun shouldParseBody(response: Response): Boolean {
|
||||
+ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") {
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
// Check for Content-Type
|
||||
val skipContentTypes = listOf(
|
||||
"text/event-stream", // Server Sent Events
|
||||
@@ -0,0 +1,5 @@
|
||||
## expo-modules-core Patch
|
||||
|
||||
### Android: bitdrift interceptor
|
||||
|
||||
Fixes an issue where bitdrift's API stream gets blocked by the Expo interceptor used to power the devtools.
|
||||
@@ -1,9 +1,9 @@
|
||||
diff --git a/android/build.gradle b/android/build.gradle
|
||||
index 7db47bdf190b0790c7bf867fbcfeb594005861be..0f868153edd6ec557730531f61dba7bf26a71742 100644
|
||||
index 18a1c56507a1c0da7eb3b6e1f80a1f25a0a8f171..305e10998b99d0c0256930de669e51b5ba4c98c4 100644
|
||||
--- a/android/build.gradle
|
||||
+++ b/android/build.gradle
|
||||
@@ -42,6 +42,7 @@ dependencies {
|
||||
implementation 'com.google.firebase:firebase-messaging:24.0.1'
|
||||
@@ -43,6 +43,7 @@ dependencies {
|
||||
implementation 'com.google.firebase:firebase-messaging:25.0.1'
|
||||
|
||||
implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
|
||||
+ implementation project(':expo-background-notification-handler')
|
||||
@@ -124,7 +124,7 @@ index 610d3039cefd589647538ad8ba14587d29fab338..3655fc3121ebc0a97820d9653b61a90b
|
||||
builder.setContentText(content.text)
|
||||
builder.setSubText(content.subText)
|
||||
diff --git a/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt b/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
|
||||
index 90ca4ff35132b33dcccb80b90d68572506fef603..9d4cb09b35844805d543acff54772380c732c02c 100644
|
||||
index eecdae82e99d2997687e3f3e199c94c3aeffbfe0..216891213fb2eac5a9a382b4f075eadccdcfeb5a 100644
|
||||
--- a/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
|
||||
+++ b/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
|
||||
@@ -3,6 +3,9 @@ package expo.modules.notifications.service.delegates
|
||||
@@ -137,16 +137,16 @@ index 90ca4ff35132b33dcccb80b90d68572506fef603..9d4cb09b35844805d543acff54772380
|
||||
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
|
||||
@@ -17,7 +20,7 @@ import expo.modules.notifications.service.interfaces.FirebaseMessagingDelegate
|
||||
import expo.modules.notifications.tokens.interfaces.FirebaseTokenListener
|
||||
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
|
||||
// than by static properties.
|
||||
@@ -109,8 +112,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)
|
||||
@@ -0,0 +1,97 @@
|
||||
diff --git a/build/winter/runtime.native.d.ts b/build/winter/runtime.native.d.ts
|
||||
index 85c4227e4e37003dd3769a590e73a69874de5fac..e389ca8edd2310df9ce29684991465c275e804c5 100644
|
||||
--- a/build/winter/runtime.native.d.ts
|
||||
+++ b/build/winter/runtime.native.d.ts
|
||||
@@ -1,3 +1,2 @@
|
||||
import 'react-native/Libraries/Core/InitializeCore';
|
||||
-import '../../types';
|
||||
//# sourceMappingURL=runtime.native.d.ts.map
|
||||
\ No newline at end of file
|
||||
diff --git a/src/winter/fetch/RequestUtils.ts b/src/winter/fetch/RequestUtils.ts
|
||||
index a93473fd2beaf2ab0f3880951d292e58e7288d10..c27911bd5cb56ba6051fb8ebf4194cced43bc0fd 100644
|
||||
--- a/src/winter/fetch/RequestUtils.ts
|
||||
+++ b/src/winter/fetch/RequestUtils.ts
|
||||
@@ -50,7 +50,11 @@ function isBlob(obj: any): obj is Blob {
|
||||
*/
|
||||
export async function normalizeBodyInitAsync(
|
||||
body: BodyInit | null | undefined
|
||||
-): Promise<{ body: Uint8Array | null; overriddenHeaders?: NativeHeadersType }> {
|
||||
+): Promise<{
|
||||
+ body: Uint8Array | null;
|
||||
+ overriddenHeaders?: NativeHeadersType;
|
||||
+ fallbackHeaders?: NativeHeadersType;
|
||||
+}> {
|
||||
if (body == null) {
|
||||
return { body: null };
|
||||
}
|
||||
@@ -71,7 +75,12 @@ export async function normalizeBodyInitAsync(
|
||||
if (body instanceof Blob || isBlob(body)) {
|
||||
return {
|
||||
body: new Uint8Array(await blobToArrayBufferAsync(body)),
|
||||
- overriddenHeaders: [['Content-Type', body.type]],
|
||||
+ /*
|
||||
+ * Per the fetch spec, a blob's type is only a default for Content-Type:
|
||||
+ * it must not replace a header the caller set explicitly, and an empty
|
||||
+ * type contributes no header at all.
|
||||
+ */
|
||||
+ fallbackHeaders: body.type ? [['Content-Type', body.type]] : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,6 +146,25 @@ export function overrideHeaders(
|
||||
return result;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * Create a new header array by adding new headers only for keys not already
|
||||
+ * present (by case-insensitive header key). Used for body-derived defaults
|
||||
+ * that must not replace caller-provided headers.
|
||||
+ */
|
||||
+export function fillMissingHeaders(
|
||||
+ headers: NativeHeadersType,
|
||||
+ newHeaders: NativeHeadersType
|
||||
+): NativeHeadersType {
|
||||
+ const existingKeySet = new Set(headers.map(([key]) => key.toLocaleLowerCase()));
|
||||
+ const result: NativeHeadersType = [...headers];
|
||||
+ for (const [key, value] of newHeaders) {
|
||||
+ if (!existingKeySet.has(key.toLocaleLowerCase())) {
|
||||
+ result.push([key, value]);
|
||||
+ }
|
||||
+ }
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
/** Normalizes known HTTP methods to uppercase */
|
||||
export function normalizeMethod(method: string): string {
|
||||
const normalized = method.toUpperCase();
|
||||
diff --git a/src/winter/fetch/fetch.ts b/src/winter/fetch/fetch.ts
|
||||
index ac789c024c96379e239bf44903c47e6a8e540d63..4f953b845c145de02976e8162ba2f63f8bd4dfd5 100644
|
||||
--- a/src/winter/fetch/fetch.ts
|
||||
+++ b/src/winter/fetch/fetch.ts
|
||||
@@ -3,6 +3,7 @@ import { FetchError } from './FetchErrors';
|
||||
import { FetchResponse, type AbortSubscriptionCleanupFunction } from './FetchResponse';
|
||||
import type { NativeRequest, NativeRequestInit } from './NativeRequest';
|
||||
import {
|
||||
+ fillMissingHeaders,
|
||||
normalizeBodyInitAsync,
|
||||
normalizeHeadersInit,
|
||||
overrideHeaders,
|
||||
@@ -62,10 +63,17 @@ export async function fetch(
|
||||
|
||||
const request = new ExpoFetchModule.NativeRequest(response) as NativeRequest;
|
||||
|
||||
- const { body: requestBody, overriddenHeaders } = await normalizeBodyInitAsync(body);
|
||||
+ const {
|
||||
+ body: requestBody,
|
||||
+ overriddenHeaders,
|
||||
+ fallbackHeaders,
|
||||
+ } = await normalizeBodyInitAsync(body);
|
||||
if (overriddenHeaders) {
|
||||
headers = overrideHeaders(headers, overriddenHeaders);
|
||||
}
|
||||
+ if (fallbackHeaders) {
|
||||
+ headers = fillMissingHeaders(headers, fallbackHeaders);
|
||||
+ }
|
||||
|
||||
const nativeRequestInit: NativeRequestInit = {
|
||||
credentials: credentials ?? 'include',
|
||||
@@ -0,0 +1,56 @@
|
||||
# expo
|
||||
|
||||
## build/winter/runtime.native.d.ts
|
||||
|
||||
Type-check-only change; no runtime impact (only a `.d.ts` is modified).
|
||||
|
||||
Expo 57 added `import '../../types'` to `build/winter/runtime.native.d.ts`
|
||||
(in Expo 54 the file was an empty `export {}`). That pulls
|
||||
`expo/types/react-native-web.d.ts` into every native type-check pass via the
|
||||
chain `expo/build/Expo.fx.d.ts -> ./winter -> runtime.native.d.ts ->
|
||||
expo/types/index.d.ts`.
|
||||
|
||||
`react-native-web.d.ts` augments react-native's `TextStyle` with web-only
|
||||
props, including `cursor?: string`, which conflicts with react-native 0.86's
|
||||
own `cursor?: CursorValue`. The merged declaration makes `TextStyle` no
|
||||
longer assignable to `ViewStyle`, which in turn poisons `StyleSheet.create`
|
||||
inference (values widen to `ViewStyle | TextStyle | ImageStyle`) and produced
|
||||
~60 errors in `pnpm typecheck:ios` / `typecheck:android`.
|
||||
|
||||
The patch drops the `import '../../types'` line so the web-only augmentation
|
||||
stays out of the native passes, matching Expo 54 behavior. The web pass is
|
||||
unaffected: it resolves `runtime.d.ts` (not `.native`), which never had this
|
||||
import.
|
||||
|
||||
Can be removed if Expo stops referencing `./react-native-web` from the types
|
||||
loaded by the native winter runtime, or guards the augmentation to web.
|
||||
|
||||
## src/winter/fetch/RequestUtils.ts + fetch.ts - Blob body must not clobber an explicit Content-Type
|
||||
|
||||
Expo 57 installs `expo/fetch` as the global `fetch` on native
|
||||
(`src/winter/runtime.native.ts`), replacing React Native's whatwg-fetch. When
|
||||
the request body is a Blob, `normalizeBodyInitAsync` returned
|
||||
`overriddenHeaders: [['Content-Type', blob.type]]`, which `fetch.ts` applied
|
||||
*over* the caller's headers (introduced in expo/expo#33405). This is backwards
|
||||
per the fetch spec: a blob's type is only a default, used when no Content-Type
|
||||
was provided, and an empty type must contribute no header at all.
|
||||
|
||||
In this app it broke publishing posts with any image blob on Android. The
|
||||
composer uploads via `agent.uploadBlob(blob, {encoding})`; `@atproto/xrpc`
|
||||
sets `content-type: <mime>` explicitly, but the blob comes from an XHR
|
||||
`file://` read of a `.bin`-renamed jpeg (the RN#27099 workaround in
|
||||
`src/lib/api/upload-blob.ts`), for which Android's BlobModule returns an empty
|
||||
mime. expo/fetch replaced the good header with the empty blob type and the PDS
|
||||
rejected the upload with "Request encoding (Content-Type) required but not
|
||||
provided". Also silently rewrote the intended mime on every other blob upload
|
||||
(avatars, banners, caption files) even when the blob type was non-empty.
|
||||
|
||||
The patch splits body-derived headers into two channels: FormData keeps
|
||||
`overriddenHeaders` (its boundary header must win), while the Blob branch
|
||||
returns new `fallbackHeaders` (skipped entirely when `blob.type` is empty)
|
||||
that `fetch.ts` applies via `fillMissingHeaders` only for header keys the
|
||||
caller did not set. This matches browser behavior.
|
||||
|
||||
Upstream: expo/expo#33405 introduced the override; the SDK 58 Request rewrite
|
||||
(expo/expo#46630) is expected to make this spec-compliant, so re-evaluate on
|
||||
the next SDK bump. Worth filing an issue against expo/expo referencing this.
|
||||
@@ -1,31 +0,0 @@
|
||||
diff --git a/apple/RNGestureHandler.mm b/apple/RNGestureHandler.mm
|
||||
index c4f760c41a9965245edcfa5e7cb781f8d2c7b66a..bf7d1fb092e22cbcedf787e606876e533879f810 100644
|
||||
--- a/apple/RNGestureHandler.mm
|
||||
+++ b/apple/RNGestureHandler.mm
|
||||
@@ -470,15 +470,19 @@ + (RNGestureHandler *)findGestureHandlerByRecognizer:(UIGestureRecognizer *)reco
|
||||
|
||||
// We may try to extract "DummyGestureHandler" in case when "otherGestureRecognizer" belongs to
|
||||
// a native view being wrapped with "NativeViewGestureHandler"
|
||||
- RNGHUIView *reactView = recognizer.view;
|
||||
- while (reactView != nil && reactView.reactTag == nil) {
|
||||
- reactView = reactView.superview;
|
||||
- }
|
||||
+ RNGHUIView *view = recognizer.view;
|
||||
+ while (view != nil) {
|
||||
+ for (UIGestureRecognizer *candidateRecognizer in view.gestureRecognizers) {
|
||||
+ if ([candidateRecognizer isKindOfClass:[RNDummyGestureRecognizer class]]) {
|
||||
+ return candidateRecognizer.gestureHandler;
|
||||
+ }
|
||||
+ }
|
||||
|
||||
- for (UIGestureRecognizer *recognizer in reactView.gestureRecognizers) {
|
||||
- if ([recognizer isKindOfClass:[RNDummyGestureRecognizer class]]) {
|
||||
- return recognizer.gestureHandler;
|
||||
+ if ([view isKindOfClass:[RCTViewComponentView class]]) {
|
||||
+ return nil;
|
||||
}
|
||||
+
|
||||
+ view = view.superview;
|
||||
}
|
||||
|
||||
return nil;
|
||||
@@ -1,5 +0,0 @@
|
||||
# react-native-gesture-handler.patch
|
||||
|
||||
Updated `findGestureHandlerByRecognizer:` in `apple/RNGestureHandler.mm` to the version from RN GH 2.32.0
|
||||
|
||||
This fixes `UIContextMenuInteraction` from `ExpoBlueskyPeekMenuView.swift`. https://github.com/software-mansion/react-native-gesture-handler/commit/fba4dcc06d71dce08b10b2afc738a2af5b01e86a
|
||||
@@ -1,65 +0,0 @@
|
||||
# react-native-reanimated@4.3.2.patch
|
||||
|
||||
Backports of two merged upstream PRs:
|
||||
|
||||
1. PR 9901 (`LayoutAnimation.configureNext` compatibility)
|
||||
2. PR 9971 (stale `settledProps` on worklet re-animation / after app resume)
|
||||
|
||||
## 1. Backport of PR 9901
|
||||
|
||||
Backport of https://github.com/software-mansion/react-native-reanimated/pull/9901
|
||||
("refactor(LayoutAnimations): stop taking over UIManagerAnimationDelegate").
|
||||
|
||||
Reanimated's legacy `LayoutAnimationsProxy_Legacy` registered itself as the
|
||||
`UIManagerAnimationDelegate` only to receive `stopSurface`. Occupying that slot
|
||||
overwrites the `LayoutAnimationDriver` that React Native installs there, which
|
||||
silently breaks `LayoutAnimation.configureNext` for the whole app.
|
||||
|
||||
The patch makes the proxy detect surface teardown itself via a
|
||||
`UIManagerCommitHook` (a commit with an empty root marks the surface in
|
||||
`surfacesToRemove_`), frees the animation-delegate slot, and drops final
|
||||
keyframe `Update` mutations for views deleted in the same transaction (a
|
||||
deterministic `configureNext` delete-animation crash found in this app).
|
||||
`uiManager` moves from Android-only to shared constructor args since the hook
|
||||
registration needs it on both platforms.
|
||||
|
||||
Only the `packages/react-native-reanimated` part of the PR is included (the
|
||||
`apps/fabric-example` hunk is not part of the published package), and the
|
||||
include hunk in `LayoutAnimationsProxy_Legacy.cpp` was adjusted to the 4.3.2
|
||||
release sources.
|
||||
|
||||
## 2. Backport of PR 9971 (stale `settledProps`)
|
||||
|
||||
Verbatim application of
|
||||
https://github.com/software-mansion/react-native-reanimated/pull/9971, the
|
||||
4.3-stable cherry-pick of
|
||||
https://github.com/software-mansion/react-native-reanimated/pull/9527
|
||||
("Fix stale settledProps on worklet re-animation"). Fixes the Android DM
|
||||
composer "phantom jump"
|
||||
(https://github.com/software-mansion/react-native-reanimated/issues/9574).
|
||||
|
||||
Background: with `FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS`, once an
|
||||
animation settles its final props are handed to JS (polled every 500 ms by
|
||||
`PropsRegistryGarbageCollector`) and stored in React component state
|
||||
(`settledProps`), after which the React-side snapshot becomes the sole owner
|
||||
of the value.
|
||||
|
||||
The PR replaces `getUpdatesOlderThanTimestamp` (which evicted registry
|
||||
entries on a wall-clock 1 s/2 s window) with `collectSettledUpdates`:
|
||||
|
||||
- `syncedTags_` / `invalidatedTags_` track which tags React already has a
|
||||
snapshot for; when a previously-synced view re-animates, its stale snapshot
|
||||
is refreshed on the next GC tick instead of waiting for the new value to
|
||||
settle.
|
||||
- Eviction is no longer time-based. An entry is only evicted on the tick
|
||||
*after* it was returned to JS (once its `settledProps` commit is
|
||||
guaranteed), so a missed timer window (app backgrounded, JS thread blocked)
|
||||
can no longer destroy a settled value before it reaches React. This
|
||||
replaces the ad-hoc eviction guard an earlier version of this patch added
|
||||
on top of the pre-merge PR 9527.
|
||||
- `PropsRegistryGarbageCollector` drops the separate `viewsCount` counter
|
||||
(which could desync when nested animated components unregister a tag that
|
||||
was never registered, stopping the GC interval while views remain) in favor
|
||||
of `viewsMap.size`. Only `src/` is touched, matching the PR; Metro bundles
|
||||
the app from `src/` via the package's `react-native` field, and the stale
|
||||
`lib/` copy is unreachable (the feature is native-only).
|
||||
+17
-234
@@ -1,160 +1,8 @@
|
||||
diff --git a/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp b/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp
|
||||
index 531f0dc7b4eeb9b29cb2255d8444da02a74c35b7..534f419fce55c39a09a7eebfb7ab3c53f8a16637 100644
|
||||
--- a/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp
|
||||
+++ b/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <reanimated/Fabric/updates/AnimatedPropsRegistry.h>
|
||||
#include <reanimated/Tools/FeatureFlags.h>
|
||||
|
||||
+#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
+#include <vector>
|
||||
|
||||
namespace reanimated {
|
||||
|
||||
@@ -25,25 +27,59 @@ void AnimatedPropsRegistry::update(jsi::Runtime &rt, const jsi::Value &operation
|
||||
addUpdatesToBatch(shadowNode, jsi::dynamicFromValue(rt, updates));
|
||||
|
||||
if constexpr (StaticFeatureFlags::getFlag("FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS")) {
|
||||
- timestampMap_[shadowNode->getTag()] = timestamp;
|
||||
+ const auto tag = shadowNode->getTag();
|
||||
+ timestampMap_[tag] = timestamp;
|
||||
+ // If JS already has a `settledProps` snapshot for this tag, it is now
|
||||
+ // stale — schedule a refresh on the next `collectSettledUpdates`.
|
||||
+ if (syncedTags_.erase(tag) > 0) {
|
||||
+ invalidatedTags_.insert(tag);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-jsi::Value AnimatedPropsRegistry::getUpdatesOlderThanTimestamp(
|
||||
- jsi::Runtime &rt,
|
||||
- const double timestamp,
|
||||
- const double cleanupTimestamp) {
|
||||
+jsi::Value AnimatedPropsRegistry::collectSettledUpdates(jsi::Runtime &rt, const double settledTimestamp) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
- removeUpdatesOlderThanTimestamp(cleanupTimestamp);
|
||||
|
||||
std::vector<std::pair<Tag, std::reference_wrapper<const folly::dynamic>>> updates;
|
||||
|
||||
- for (const auto &[viewTag, pair] : updatesRegistry_) {
|
||||
- auto it = timestampMap_.find(viewTag);
|
||||
- if (it != timestampMap_.end() && it->second < timestamp) {
|
||||
- updates.emplace_back(viewTag, std::cref(pair.second));
|
||||
+ for (auto it = updatesRegistry_.begin(); it != updatesRegistry_.end();) {
|
||||
+ const auto viewTag = it->first;
|
||||
+
|
||||
+ if (syncedTags_.contains(viewTag)) {
|
||||
+ // React already has the latest value for this tag (synced on a previous
|
||||
+ // call, so the `settledProps` state is committed by now) — the registry
|
||||
+ // entry is redundant. `syncedTags_` is intentionally retained to detect
|
||||
+ // re-animation staleness. Note that `syncedTags_` and `invalidatedTags_`
|
||||
+ // are disjoint — `update()` moves tags from the former to the latter.
|
||||
+ timestampMap_.erase(viewTag);
|
||||
+ it = updatesRegistry_.erase(it);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ const auto timestampIt = timestampMap_.find(viewTag);
|
||||
+ if (timestampIt == timestampMap_.end()) {
|
||||
+ ++it;
|
||||
+ continue;
|
||||
+ }
|
||||
+ const bool isSettled = timestampIt->second < settledTimestamp;
|
||||
+ const auto invalidatedIt = invalidatedTags_.find(viewTag);
|
||||
+ const bool isInvalidated = invalidatedIt != invalidatedTags_.end();
|
||||
+ if (isSettled || isInvalidated) {
|
||||
+ updates.emplace_back(viewTag, std::cref(it->second.second));
|
||||
+ if (isSettled) {
|
||||
+ // Only settled-path tags are tracked as "synced" so that an ongoing
|
||||
+ // animation doesn't re-trigger an invalidation/sync on every GC tick.
|
||||
+ syncedTags_.insert(viewTag);
|
||||
+ }
|
||||
+ if (isInvalidated) {
|
||||
+ // Only erase serviced invalidations; if a tag was invalidated but the
|
||||
+ // matching update batch hasn't been flushed into updatesRegistry_ yet,
|
||||
+ // we leave the entry so the next sync picks it up.
|
||||
+ invalidatedTags_.erase(invalidatedIt);
|
||||
+ }
|
||||
}
|
||||
+ ++it;
|
||||
}
|
||||
|
||||
const jsi::Array array(rt, updates.size());
|
||||
@@ -58,22 +94,11 @@ jsi::Value AnimatedPropsRegistry::getUpdatesOlderThanTimestamp(
|
||||
return jsi::Value(rt, array);
|
||||
}
|
||||
|
||||
-void AnimatedPropsRegistry::removeUpdatesOlderThanTimestamp(const double timestamp) {
|
||||
- for (auto it = timestampMap_.begin(); it != timestampMap_.end();) {
|
||||
- const auto viewTag = it->first;
|
||||
- const auto viewTimestamp = it->second;
|
||||
- if (viewTimestamp < timestamp) {
|
||||
- it = timestampMap_.erase(it);
|
||||
- updatesRegistry_.erase(viewTag);
|
||||
- } else {
|
||||
- it++;
|
||||
- }
|
||||
- }
|
||||
-}
|
||||
-
|
||||
void AnimatedPropsRegistry::removeTag(const Tag tag) {
|
||||
updatesRegistry_.erase(tag);
|
||||
timestampMap_.erase(tag);
|
||||
+ syncedTags_.erase(tag);
|
||||
+ invalidatedTags_.erase(tag);
|
||||
}
|
||||
|
||||
} // namespace reanimated
|
||||
diff --git a/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h b/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h
|
||||
index 2c6c0e13604c9421e147d7eea7f4a4752288011c..8cd67f118501c2786b94d76541aea29a14ba8c16 100644
|
||||
--- a/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h
|
||||
+++ b/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h
|
||||
@@ -4,10 +4,8 @@
|
||||
|
||||
#include <react/renderer/uimanager/UIManager.h>
|
||||
|
||||
-#include <memory>
|
||||
-#include <string>
|
||||
#include <unordered_map>
|
||||
-#include <vector>
|
||||
+#include <unordered_set>
|
||||
|
||||
namespace reanimated {
|
||||
|
||||
@@ -15,13 +13,22 @@ class AnimatedPropsRegistry : public UpdatesRegistry {
|
||||
public:
|
||||
void update(jsi::Runtime &rt, const jsi::Value &operations, double timestamp);
|
||||
|
||||
- /// Also removes updates older than `cleanupTimestamp` from the registry.
|
||||
- jsi::Value getUpdatesOlderThanTimestamp(jsi::Runtime &rt, double timestamp, double cleanupTimestamp);
|
||||
+ /// Returns updates that settled (received no update since `settledTimestamp`)
|
||||
+ /// or whose synced `settledProps` snapshot was invalidated by a fresh update.
|
||||
+ /// Also evicts entries that have already been synced to React — by the time
|
||||
+ /// of the next call, the corresponding `settledProps` state is guaranteed to
|
||||
+ /// be committed, so the registry entries are redundant.
|
||||
+ jsi::Value collectSettledUpdates(jsi::Runtime &rt, double settledTimestamp);
|
||||
|
||||
private:
|
||||
std::unordered_map<Tag, double> timestampMap_; // viewTag -> timestamp, protected by `mutex_`
|
||||
+ // Tags whose latest values have already been pushed to React `settledProps`.
|
||||
+ // Intentionally retained after eviction to detect re-animation staleness.
|
||||
+ std::unordered_set<Tag> syncedTags_;
|
||||
+ // Tags that were synced to React but received a fresh worklet update since;
|
||||
+ // their `settledProps` are stale and need to be refreshed on the next sync.
|
||||
+ std::unordered_set<Tag> invalidatedTags_;
|
||||
|
||||
- void removeUpdatesOlderThanTimestamp(double timestamp);
|
||||
void removeTag(Tag tag) override;
|
||||
};
|
||||
|
||||
diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h
|
||||
index 096218ab9659955cd6272c97181bce3c893ed591..1a8e25fc8295b3ac943130709bf063ea41a50585 100644
|
||||
index 8603591..20d042b 100644
|
||||
--- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h
|
||||
+++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h
|
||||
@@ -57,11 +57,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
||||
@@ -62,11 +62,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
||||
const SharedComponentDescriptorRegistry &componentDescriptorRegistry,
|
||||
const std::shared_ptr<const ContextContainer> &contextContainer,
|
||||
jsi::Runtime &uiRuntime,
|
||||
@@ -168,7 +16,7 @@ index 096218ab9659955cd6272c97181bce3c893ed591..1a8e25fc8295b3ac943130709bf063ea
|
||||
const std::shared_ptr<facebook::react::CallInvoker> &jsInvoker
|
||||
#endif
|
||||
)
|
||||
@@ -69,11 +69,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
||||
@@ -74,11 +74,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
||||
contextContainer_(contextContainer),
|
||||
componentDescriptorRegistry_(componentDescriptorRegistry),
|
||||
uiRuntime_(uiRuntime),
|
||||
@@ -182,7 +30,7 @@ index 096218ab9659955cd6272c97181bce3c893ed591..1a8e25fc8295b3ac943130709bf063ea
|
||||
jsInvoker_(jsInvoker)
|
||||
#endif
|
||||
{
|
||||
@@ -93,10 +93,10 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
||||
@@ -98,10 +98,10 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
||||
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
|
||||
jsi::Runtime &uiRuntime_;
|
||||
const std::shared_ptr<UIScheduler> uiScheduler_;
|
||||
@@ -195,10 +43,10 @@ index 096218ab9659955cd6272c97181bce3c893ed591..1a8e25fc8295b3ac943130709bf063ea
|
||||
|
||||
void restoreOpacityInCaseOfFlakyEnteringAnimation(SurfaceId surfaceId) const;
|
||||
diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h
|
||||
index eca44e4cf651d16e9741806004ce9119c85d07d6..e39c79a5d7b52659106ed6fe6fbcbbc048bf4787 100644
|
||||
index fcc677f..115971a 100644
|
||||
--- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h
|
||||
+++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h
|
||||
@@ -66,11 +66,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon,
|
||||
@@ -67,11 +67,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon,
|
||||
const SharedComponentDescriptorRegistry &componentDescriptorRegistry,
|
||||
const std::shared_ptr<const ContextContainer> &contextContainer,
|
||||
jsi::Runtime &uiRuntime,
|
||||
@@ -212,7 +60,7 @@ index eca44e4cf651d16e9741806004ce9119c85d07d6..e39c79a5d7b52659106ed6fe6fbcbbc0
|
||||
const std::shared_ptr<CallInvoker> &jsInvoker
|
||||
#endif
|
||||
)
|
||||
@@ -79,11 +79,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon,
|
||||
@@ -80,11 +80,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon,
|
||||
componentDescriptorRegistry,
|
||||
contextContainer,
|
||||
uiRuntime,
|
||||
@@ -227,18 +75,18 @@ index eca44e4cf651d16e9741806004ce9119c85d07d6..e39c79a5d7b52659106ed6fe6fbcbbc0
|
||||
#endif
|
||||
),
|
||||
diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp
|
||||
index 83ef7430b923b6b3b99368ee8072168769110cd0..2affd12822ab19bdc90963d3ce8ca1e6bb0d43b7 100644
|
||||
index df53d8d..735f138 100644
|
||||
--- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp
|
||||
+++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <reanimated/NativeModules/ReanimatedModuleProxy.h>
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h>
|
||||
|
||||
#include <react/renderer/animations/utils.h>
|
||||
#include <react/debug/react_native_assert.h>
|
||||
+#include <react/renderer/mounting/ShadowTree.h>
|
||||
#include <react/renderer/mounting/ShadowViewMutation.h>
|
||||
|
||||
#include <memory>
|
||||
@@ -53,14 +54,37 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
|
||||
@@ -60,14 +61,37 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
|
||||
|
||||
parseRemoveMutations(movedViews, mutations, roots);
|
||||
|
||||
@@ -278,7 +126,7 @@ index 83ef7430b923b6b3b99368ee8072168769110cd0..2affd12822ab19bdc90963d3ce8ca1e6
|
||||
return MountingTransaction{surfaceId, transactionNumber, std::move(filteredMutations), telemetry};
|
||||
}
|
||||
|
||||
@@ -947,23 +971,22 @@ inline bool MutationNode::isMutationNode() {
|
||||
@@ -998,23 +1022,22 @@ inline bool MutationNode::isMutationNode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -318,7 +166,7 @@ index 83ef7430b923b6b3b99368ee8072168769110cd0..2affd12822ab19bdc90963d3ce8ca1e6
|
||||
|
||||
} // namespace reanimated
|
||||
diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h
|
||||
index e9a5e9959e89ec33cee179ddb907c17f6dfbd3de..a2c89041518cd71e8ba5ac62ef89c0022d197c9b 100644
|
||||
index 57cc134..1a2966c 100644
|
||||
--- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h
|
||||
+++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h
|
||||
@@ -3,8 +3,8 @@
|
||||
@@ -376,7 +224,7 @@ index e9a5e9959e89ec33cee179ddb907c17f6dfbd3de..a2c89041518cd71e8ba5ac62ef89c002
|
||||
}
|
||||
|
||||
void startEnteringAnimation(const int tag, ShadowViewMutation &mutation) const;
|
||||
@@ -202,19 +207,15 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
|
||||
@@ -206,19 +211,15 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
|
||||
const TransactionTelemetry &telemetry,
|
||||
ShadowViewMutationList mutations) const override;
|
||||
|
||||
@@ -404,43 +252,10 @@ index e9a5e9959e89ec33cee179ddb907c17f6dfbd3de..a2c89041518cd71e8ba5ac62ef89c002
|
||||
|
||||
} // namespace reanimated
|
||||
diff --git a/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
|
||||
index 9ade22bf773005613048a00c47b35767628e86c6..f3415e824da1a8da5c83762415ca54646bd6429f 100644
|
||||
index 2b68ff7..d08b1ae 100644
|
||||
--- a/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
|
||||
+++ b/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
|
||||
@@ -524,15 +524,13 @@ jsi::Value ReanimatedModuleProxy::getSettledUpdates(jsi::Runtime &rt) {
|
||||
StaticFeatureFlags::getFlag("FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS") &&
|
||||
"getSettledUpdates requires FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS static feature flag to be enabled");
|
||||
|
||||
+ constexpr double SETTLED_ANIMATION_THRESHOLD_MS = 1000;
|
||||
+
|
||||
// TODO(future): use unified timestamp
|
||||
const auto currentTimestamp = getAnimationTimestamp_();
|
||||
|
||||
- // TODO: fix bug when threshold difference is smaller than 1 second
|
||||
// TODO(future): flush updates from CSS animations and CSS transitions registries
|
||||
- // TODO(future): find a better way to obtain timestamp for removing updates
|
||||
- // TODO(future): move removing old updates to separate method
|
||||
- return animatedPropsRegistry_->getUpdatesOlderThanTimestamp(
|
||||
- rt, currentTimestamp - 1000 /* 1 second */, currentTimestamp - 2000 /* 2 seconds */);
|
||||
+ return animatedPropsRegistry_->collectSettledUpdates(rt, currentTimestamp - SETTLED_ANIMATION_THRESHOLD_MS);
|
||||
}
|
||||
|
||||
bool ReanimatedModuleProxy::handleEvent(
|
||||
@@ -1306,11 +1304,11 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxy() {
|
||||
componentDescriptorRegistry,
|
||||
scheduler->getContextContainer(),
|
||||
getJSIRuntimeFromWorkletRuntime(uiRuntime_),
|
||||
- uiScheduler_
|
||||
+ uiScheduler_,
|
||||
+ uiManager_
|
||||
#ifdef ANDROID
|
||||
,
|
||||
filterUnmountedTagsFunction_,
|
||||
- uiManager_,
|
||||
jsInvoker_
|
||||
#endif
|
||||
);
|
||||
@@ -1319,22 +1317,19 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxy() {
|
||||
@@ -1235,22 +1235,19 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxy() {
|
||||
#endif
|
||||
layoutAnimationsProxy_ = std::move(layoutAnimationsProxyExperimental);
|
||||
} else {
|
||||
@@ -466,35 +281,3 @@ index 9ade22bf773005613048a00c47b35767628e86c6..f3415e824da1a8da5c83762415ca5464
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/src/PropsRegistryGarbageCollector.ts b/src/PropsRegistryGarbageCollector.ts
|
||||
index f917ce5a8586c02855f1d8d9ae73154592d22510..32148fbac8a9224ffec6edc784b48938da9585fb 100644
|
||||
--- a/src/PropsRegistryGarbageCollector.ts
|
||||
+++ b/src/PropsRegistryGarbageCollector.ts
|
||||
@@ -11,7 +11,6 @@ import { ReanimatedModule } from './ReanimatedModule';
|
||||
const FLUSH_INTERVAL_MS = 500;
|
||||
|
||||
export const PropsRegistryGarbageCollector = {
|
||||
- viewsCount: 0,
|
||||
viewsMap: new Map<number, IAnimatedComponentInternal>(),
|
||||
intervalId: null as NodeJS.Timeout | null,
|
||||
|
||||
@@ -25,16 +24,14 @@ export const PropsRegistryGarbageCollector = {
|
||||
return;
|
||||
}
|
||||
this.viewsMap.set(viewTag, component);
|
||||
- this.viewsCount++;
|
||||
- if (this.viewsCount === 1) {
|
||||
+ if (this.viewsMap.size === 1) {
|
||||
this.registerInterval();
|
||||
}
|
||||
},
|
||||
|
||||
unregisterView(viewTag: number) {
|
||||
- this.viewsMap.delete(viewTag);
|
||||
- this.viewsCount--;
|
||||
- if (this.viewsCount === 0) {
|
||||
+ const deleted = this.viewsMap.delete(viewTag);
|
||||
+ if (deleted && this.viewsMap.size === 0) {
|
||||
this.unregisterInterval();
|
||||
}
|
||||
},
|
||||
@@ -0,0 +1,27 @@
|
||||
# react-native-reanimated@4.5.3.patch
|
||||
|
||||
Backport of https://github.com/software-mansion/react-native-reanimated/pull/9901
|
||||
("refactor(LayoutAnimations): stop taking over UIManagerAnimationDelegate").
|
||||
|
||||
Reanimated's legacy `LayoutAnimationsProxy_Legacy` registered itself as the
|
||||
`UIManagerAnimationDelegate` only to receive `stopSurface`. Occupying that slot
|
||||
overwrites the `LayoutAnimationDriver` that React Native installs there, which
|
||||
silently breaks `LayoutAnimation.configureNext` for the whole app.
|
||||
|
||||
The patch makes the proxy detect surface teardown itself via a
|
||||
`UIManagerCommitHook` (a commit with an empty root marks the surface in
|
||||
`surfacesToRemove_`), frees the animation-delegate slot, and drops final
|
||||
keyframe `Update` mutations for views deleted in the same transaction (a
|
||||
deterministic `configureNext` delete-animation crash found in this app).
|
||||
`uiManager` moves from Android-only to shared constructor args since the hook
|
||||
registration needs it on both platforms.
|
||||
|
||||
Only the `packages/react-native-reanimated` part of the PR is included (the
|
||||
`apps/fabric-example` hunk is not part of the published package), and the hunks
|
||||
were rebased onto the 4.5.3 release sources.
|
||||
|
||||
Note that upstream's own `pullTransaction` rework in 4.5.3 (the new
|
||||
`reconcileContradictedRemovals`) covers a different case - a `Create`/`Insert`
|
||||
contradicting a *withheld* exit removal - and does not subsume the deleted-tag
|
||||
`Update` filter here, which guards against the `LayoutAnimationDriver` final
|
||||
keyframe. That driver only runs at all once this patch frees the delegate slot.
|
||||
+7
-7
@@ -1,28 +1,28 @@
|
||||
diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt
|
||||
index c99ca362df5baee81fa48d1c9ef09b40a2c23f07..725c07aa0ed516f9dd09f81e522f2d3191d9b5d0 100644
|
||||
index 76bb694854b29d7f779f38244cd48113b429ea1f..fd402ac938862e8d39c5467c1b5c86c4e7eb0c83 100644
|
||||
--- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt
|
||||
+++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt
|
||||
@@ -17,6 +17,7 @@ import androidx.annotation.RequiresApi
|
||||
@@ -16,6 +16,7 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.children
|
||||
import androidx.fragment.app.Fragment
|
||||
+import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.facebook.react.bridge.GuardedRunnable
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
@@ -637,7 +638,7 @@ class Screen(
|
||||
import com.facebook.react.uimanager.PixelUtil
|
||||
@@ -462,7 +463,7 @@ class Screen(
|
||||
endTransitionRecursive(childView.toolbar)
|
||||
}
|
||||
|
||||
|
||||
- if (childView is ViewGroup) {
|
||||
+ if (childView is ViewGroup && childView !is RecyclerView) {
|
||||
endTransitionRecursive(childView)
|
||||
}
|
||||
}
|
||||
@@ -666,7 +667,10 @@ class Screen(
|
||||
@@ -491,7 +492,10 @@ class Screen(
|
||||
startTransitionRecursive(child.toolbar)
|
||||
}
|
||||
|
||||
|
||||
- if (child is ViewGroup) {
|
||||
+ // Transition a RecyclerView as one unit. Marking its recyclable children as
|
||||
+ // transitioning keeps their parent set after removal, so RecyclerView crashes
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# react-native-screens+4.24.0.patch
|
||||
# react-native-screens+4.26.2.patch
|
||||
|
||||
## Android: do not transition RecyclerView children individually
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
diff --git a/src/index.js b/src/index.js
|
||||
index fa76d7e1272e7fbe4bbd153104db127f1f6eecad..018b6860b7fa02d498d73b5fd06028bae99abedb 100644
|
||||
--- a/src/index.js
|
||||
+++ b/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) {
|
||||
@@ -1,3 +0,0 @@
|
||||
## react-native-view-shot patch
|
||||
|
||||
Temporary patch for web, where `view`'s type has changed.
|
||||
+8
-6
@@ -1,16 +1,16 @@
|
||||
diff --git a/lib/module/threads.js b/lib/module/threads.js
|
||||
index dd3a7f1ab12e5a8030af7f17b6a7a891e0b645d2..7d48ccbf95f00724db6b083a7155f25aadd6b8f5 100644
|
||||
index c17e314..71f3cf7 100644
|
||||
--- a/lib/module/threads.js
|
||||
+++ b/lib/module/threads.js
|
||||
@@ -2,7 +2,6 @@
|
||||
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import { WorkletsError } from './debug/WorkletsError';
|
||||
import { IS_JEST } from './platformChecker';
|
||||
-import { mockedRequestAnimationFrame } from './runLoop/uiRuntime/mockedRequestAnimationFrame';
|
||||
-import { mockedRequestAnimationFrame } from "./runLoop/uiRuntime/mockedRequestAnimationFrame.js";
|
||||
export function scheduleOnUI(worklet, ...args) {
|
||||
enqueueUI(worklet, args);
|
||||
}
|
||||
@@ -24,38 +23,50 @@ export function scheduleOnRN(fun, ...args) {
|
||||
@@ -23,38 +22,50 @@ export function scheduleOnRN(fun, ...args) {
|
||||
queueMicrotask(args.length ? () => fun(...args) : fun);
|
||||
}
|
||||
export function runOnUIAsync(worklet, ...args) {
|
||||
@@ -73,6 +73,8 @@ index dd3a7f1ab12e5a8030af7f17b6a7a891e0b645d2..7d48ccbf95f00724db6b083a7155f25a
|
||||
});
|
||||
}
|
||||
-const requestAnimationFrameImpl = !globalThis.requestAnimationFrame ? mockedRequestAnimationFrame : globalThis.requestAnimationFrame;
|
||||
-//# sourceMappingURL=threads.js.map
|
||||
\ No newline at end of file
|
||||
+function drainUIQueue(queue) {
|
||||
+ while (queue.length > offset) {
|
||||
+ const [workletFunction, workletArgs, jobResolve] = queue[offset];
|
||||
@@ -83,4 +85,4 @@ index dd3a7f1ab12e5a8030af7f17b6a7a891e0b645d2..7d48ccbf95f00724db6b083a7155f25a
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
//# sourceMappingURL=threads.js.map
|
||||
+//# sourceMappingURL=threads.js.map
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# react-native-worklets@0.8.3.patch
|
||||
# react-native-worklets@0.11.3.patch
|
||||
|
||||
Backport of https://github.com/software-mansion/react-native-reanimated/pull/10167
|
||||
("fix(Worklets): web scheduleOnUI implementation on errors").
|
||||
@@ -1,239 +0,0 @@
|
||||
diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm
|
||||
index c593d9ee2155a826352ebca34845aa5792b2eec3..3c26cd737f21116ff0aa48190e97e6c0649b5fac 100644
|
||||
--- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm
|
||||
+++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm
|
||||
@@ -101,6 +101,20 @@ - (void)setContentOffset:(CGPoint)contentOffset
|
||||
RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
|
||||
}
|
||||
|
||||
+- (void)setCenterContent:(BOOL)centerContent
|
||||
+{
|
||||
+ if (_centerContent != centerContent) {
|
||||
+ _centerContent = centerContent;
|
||||
+ [self centerContentIfNeeded];
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+- (void)setContentSize:(CGSize)contentSize
|
||||
+{
|
||||
+ [super setContentSize:contentSize];
|
||||
+ [self centerContentIfNeeded];
|
||||
+}
|
||||
+
|
||||
- (void)setFrame:(CGRect)frame
|
||||
{
|
||||
[super setFrame:frame];
|
||||
diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm
|
||||
index 0d231bc8aa938da296eb3b981e8ac9595a43b87f..be0a10d9c4de1892fa00bcbf8d63d739b66d8ffe 100644
|
||||
--- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm
|
||||
+++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm
|
||||
@@ -76,7 +76,17 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
|
||||
return;
|
||||
}
|
||||
|
||||
- const auto &oldConcreteProps = static_cast<const PullToRefreshViewProps &>(*_props);
|
||||
+ /*
|
||||
+ * TODO: Remove after upgrading React Native to 0.82+ (fixed upstream by
|
||||
+ * facebook/react-native#52615, #52584 and #53231).
|
||||
+ * Diff against oldProps instead of _props. During the initial-layout replay
|
||||
+ * from layoutSubviews, _props already holds the new props, so diffing
|
||||
+ * against it is a no-op and tintColor/progressViewOffset are never applied
|
||||
+ * on mount (facebook/react-native#56343). oldProps is null-guarded because
|
||||
+ * the create-mutation path passes nullptr.
|
||||
+ */
|
||||
+ const auto &oldConcreteProps = static_cast<const PullToRefreshViewProps &>(
|
||||
+ oldProps ? *oldProps : *PullToRefreshViewShadowNode::defaultSharedProps());
|
||||
const auto &newConcreteProps = static_cast<const PullToRefreshViewProps &>(*props);
|
||||
|
||||
if (newConcreteProps.tintColor != oldConcreteProps.tintColor) {
|
||||
diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
||||
index 1494fd225aff1fa0429e917404d6b4ca5fc961c5..d0cce700090245444f8ce51e517d5ceca09526f6 100644
|
||||
--- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
||||
+++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
||||
@@ -380,7 +380,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
|
||||
|
||||
MAP_SCROLL_VIEW_PROP(zoomScale);
|
||||
|
||||
- if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset) {
|
||||
+ // When disabling centerContent, reset inset to prop value
|
||||
+ // (enabling is handled automatically by the setCenterContent: setter)
|
||||
+ if (oldScrollViewProps.centerContent && !newScrollViewProps.centerContent) {
|
||||
+ _scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset);
|
||||
+ }
|
||||
+
|
||||
+ // Only apply contentInset from props if centerContent is disabled
|
||||
+ // When centerContent is enabled, the inset is calculated by centerContentIfNeeded
|
||||
+ if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset && !newScrollViewProps.centerContent) {
|
||||
_scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset);
|
||||
}
|
||||
|
||||
@@ -507,7 +515,7 @@ - (UIView *)betterHitTest:(CGPoint)point withEvent:(UIEvent *)event
|
||||
}
|
||||
}
|
||||
|
||||
- return isPointInside ? self : nil;
|
||||
+ return isPointInside ? _scrollView : nil;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1038,6 +1046,11 @@ - (void)_adjustForMaintainVisibleContentPosition
|
||||
}
|
||||
}
|
||||
|
||||
++ (BOOL)shouldBeRecycled
|
||||
+{
|
||||
+ return NO;
|
||||
+}
|
||||
+
|
||||
@end
|
||||
|
||||
Class<RCTComponentViewProtocol> RCTScrollViewCls(void)
|
||||
diff --git a/React/Views/RefreshControl/RCTRefreshControl.h b/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
index e9b330fa7c29c42653a3b0191d0f8a1b13b2d3de..5fbb2e05cadfc06fd7a18bf52b81bc399e92f3ca 100644
|
||||
--- a/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
+++ b/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
@@ -15,5 +15,6 @@
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
|
||||
@property (nonatomic, weak) UIScrollView *scrollView;
|
||||
+@property (nonatomic, copy) UIColor *customTintColor;
|
||||
|
||||
@end
|
||||
diff --git a/React/Views/RefreshControl/RCTRefreshControl.m b/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
index 53bfd04703502d5b8e932c47a528bb03cd79d330..e2e0c9f4e5d1a3a3b178a7ec69aa63e3039b6dec 100644
|
||||
--- a/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
+++ b/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
@@ -23,6 +23,7 @@ @implementation RCTRefreshControl {
|
||||
UIColor *_titleColor;
|
||||
CGFloat _progressViewOffset;
|
||||
BOOL _hasMovedToWindow;
|
||||
+ UIColor *_customTintColor;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
@@ -58,6 +59,12 @@ - (void)layoutSubviews
|
||||
_isInitialRender = false;
|
||||
}
|
||||
|
||||
+- (void)didMoveToSuperview
|
||||
+{
|
||||
+ [super didMoveToSuperview];
|
||||
+ [self setTintColor:_customTintColor];
|
||||
+}
|
||||
+
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
@@ -221,4 +228,16 @@ - (void)refreshControlValueChanged
|
||||
}
|
||||
}
|
||||
|
||||
+// 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];
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
@end
|
||||
diff --git a/React/Views/RefreshControl/RCTRefreshControlManager.m b/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
index 40aaf9c51ebda9fedb1d1db2e9aacec84b4c39c8..1c60164b69762997b3369b46609a07768a06bad3 100644
|
||||
--- a/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
+++ b/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
@@ -22,11 +22,12 @@ - (UIView *)view
|
||||
|
||||
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/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt b/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt
|
||||
index 8b6571698fc5dd091a0d8980a33bb40295faf305..27c97bfeb6f13907c89f1d85f2bb8b8af7bdfb43 100644
|
||||
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt
|
||||
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt
|
||||
@@ -313,8 +313,9 @@ public open class JavaTimerManager(
|
||||
// We also capture the idleCallbackRunnable to tentatively fix:
|
||||
// https://github.com/facebook/react-native/issues/44842
|
||||
currentIdleCallbackRunnable?.cancel()
|
||||
- currentIdleCallbackRunnable = IdleCallbackRunnable(frameTimeNanos)
|
||||
- reactApplicationContext.runOnJSQueueThread(currentIdleCallbackRunnable)
|
||||
+ val idleCallbackRunnable = IdleCallbackRunnable(frameTimeNanos)
|
||||
+ currentIdleCallbackRunnable = idleCallbackRunnable
|
||||
+ reactApplicationContext.runOnJSQueueThread(idleCallbackRunnable)
|
||||
reactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.IDLE_EVENT, this)
|
||||
}
|
||||
}
|
||||
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
|
||||
index 89b666dcf0258df0702c812600b685463128294c..2b1c3971f0c31a0d7a592b90170e4cc53a8a69dd 100644
|
||||
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
|
||||
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
|
||||
@@ -431,6 +431,13 @@ public open class ReactViewGroup public constructor(context: Context?) :
|
||||
inSubviewClippingLoop = true
|
||||
var clippedSoFar = 0
|
||||
for (i in 0..<allChildrenCount) {
|
||||
+ // Reentrant child removal during this loop can compact allChildren and leave a null at
|
||||
+ // an index below allChildrenCount. A null entry means the view is already detached, so
|
||||
+ // treat it as clipped instead of crashing.
|
||||
+ if (childArray[i] == null) {
|
||||
+ clippedSoFar++
|
||||
+ continue
|
||||
+ }
|
||||
try {
|
||||
updateSubviewClipStatus(clippingRect, i, clippedSoFar, excludedViewsSet)
|
||||
} catch (ex: IndexOutOfBoundsException) {
|
||||
@@ -466,7 +473,9 @@ public open class ReactViewGroup public constructor(context: Context?) :
|
||||
) {
|
||||
assertOnUiThread()
|
||||
|
||||
- val child = checkNotNull(allChildren?.get(idx))
|
||||
+ // allChildren can be mutated reentrantly while a clipping pass is running, so a stale
|
||||
+ // index can point at a null slot. Skip it instead of crashing.
|
||||
+ val child = allChildren?.get(idx) ?: return
|
||||
val intersects = clippingRect.intersects(child.left, child.top, child.right, child.bottom)
|
||||
var needUpdateClippingRecursive = false
|
||||
|
||||
diff --git a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm
|
||||
index 216bb23beb023ef6c3ae814c17e05bccbda7fc91..6ad5cc1d9ed5b8cd2df08ad77adca56c6bb58ff4 100644
|
||||
--- a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm
|
||||
+++ b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm
|
||||
@@ -386,9 +386,10 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage
|
||||
size.height = enumeratedLinesHeight;
|
||||
}
|
||||
|
||||
+ CGFloat epsilon = 0.001;
|
||||
size = (CGSize){
|
||||
- ceil(size.width * layoutContext.pointScaleFactor) / layoutContext.pointScaleFactor,
|
||||
- ceil(size.height * layoutContext.pointScaleFactor) / layoutContext.pointScaleFactor};
|
||||
+ ceil((size.width + epsilon) * layoutContext.pointScaleFactor) / layoutContext.pointScaleFactor,
|
||||
+ ceil((size.height + epsilon) * layoutContext.pointScaleFactor) / layoutContext.pointScaleFactor};
|
||||
|
||||
__block auto attachments = TextMeasurement::Attachments{};
|
||||
|
||||
diff --git a/third-party-podspecs/fmt.podspec b/third-party-podspecs/fmt.podspec
|
||||
index 2f38990e226c13f483aaf1b986302d4094243814..9b02e481e290299be20a6f09c42056ff51695e9b 100644
|
||||
--- a/third-party-podspecs/fmt.podspec
|
||||
+++ b/third-party-podspecs/fmt.podspec
|
||||
@@ -26,4 +26,11 @@ Pod::Spec.new do |spec|
|
||||
spec.public_header_files = "include/fmt/*.h"
|
||||
spec.header_mappings_dir = "include"
|
||||
spec.source_files = ["include/fmt/*.h", "src/format.cc"]
|
||||
+
|
||||
+ # TODO: Remove after upgrading React Native past 0.83.x
|
||||
+ # Fix fmt 11.0.2 consteval build error with Xcode 26.4 (facebook/react-native#55601)
|
||||
+ # Fixed in RN 0.84+ which bumps fmt to a compatible version.
|
||||
+ spec.prepare_command = <<~SCRIPT
|
||||
+ perl -i -pe 's/^# define FMT_USE_CONSTEVAL 1$/# define FMT_USE_CONSTEVAL 0/' include/fmt/base.h
|
||||
+ SCRIPT
|
||||
end
|
||||
@@ -0,0 +1,373 @@
|
||||
diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm
|
||||
index 1b02e8b2d39672063551411d5c403a69b671a869..b3481c1b98b45dea769035140dc2fd8d9b088b24 100644
|
||||
--- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm
|
||||
+++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm
|
||||
@@ -102,6 +102,20 @@ - (void)setContentOffset:(CGPoint)contentOffset
|
||||
RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
|
||||
}
|
||||
|
||||
+- (void)setCenterContent:(BOOL)centerContent
|
||||
+{
|
||||
+ if (_centerContent != centerContent) {
|
||||
+ _centerContent = centerContent;
|
||||
+ [self centerContentIfNeeded];
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+- (void)setContentSize:(CGSize)contentSize
|
||||
+{
|
||||
+ [super setContentSize:contentSize];
|
||||
+ [self centerContentIfNeeded];
|
||||
+}
|
||||
+
|
||||
- (void)setFrame:(CGRect)frame
|
||||
{
|
||||
[super setFrame:frame];
|
||||
diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
||||
index a087536f3af0d33b13fe38d8abd1bc6d7935def2..01f5c884ea4772350c0ebe6263723d97632f2b74 100644
|
||||
--- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
||||
+++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
||||
@@ -396,7 +396,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
|
||||
|
||||
MAP_SCROLL_VIEW_PROP(zoomScale);
|
||||
|
||||
- if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset) {
|
||||
+ // When disabling centerContent, reset inset to prop value
|
||||
+ // (enabling is handled automatically by the setCenterContent: setter)
|
||||
+ if (oldScrollViewProps.centerContent && !newScrollViewProps.centerContent) {
|
||||
+ _scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset);
|
||||
+ }
|
||||
+
|
||||
+ // Only apply contentInset from props if centerContent is disabled
|
||||
+ // When centerContent is enabled, the inset is calculated by centerContentIfNeeded
|
||||
+ if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset && !newScrollViewProps.centerContent) {
|
||||
_scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset);
|
||||
}
|
||||
|
||||
@@ -523,7 +531,7 @@ - (UIView *)betterHitTest:(CGPoint)point withEvent:(UIEvent *)event
|
||||
}
|
||||
}
|
||||
|
||||
- return isPointInside ? self : nil;
|
||||
+ return isPointInside ? _scrollView : nil;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1133,6 +1141,11 @@ - (RCTVirtualViewContainerState *)virtualViewContainerState
|
||||
return _virtualViewContainerState;
|
||||
}
|
||||
|
||||
++ (BOOL)shouldBeRecycled
|
||||
+{
|
||||
+ return NO;
|
||||
+}
|
||||
+
|
||||
@end
|
||||
|
||||
Class<RCTComponentViewProtocol> RCTScrollViewCls(void)
|
||||
diff --git a/React/Views/RefreshControl/RCTRefreshControl.h b/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
index ed306d7cadbf36a2fed79be8bd9d68b5dca135bd..d447dad534fefa9fcbdbbde6dcbbdcddadd5a824 100644
|
||||
--- a/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
+++ b/React/Views/RefreshControl/RCTRefreshControl.h
|
||||
@@ -18,6 +18,7 @@ __attribute__((deprecated("This API will be removed along with the legacy archit
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
|
||||
@property (nonatomic, weak) UIScrollView *scrollView;
|
||||
+@property (nonatomic, copy) UIColor *customTintColor;
|
||||
|
||||
@end
|
||||
|
||||
diff --git a/React/Views/RefreshControl/RCTRefreshControl.m b/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
index 2dc86e464264c9450eef18d7b153d35bf6a5cc55..6661dc69a04766afa0284d6e83839b219e98cf57 100644
|
||||
--- a/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
+++ b/React/Views/RefreshControl/RCTRefreshControl.m
|
||||
@@ -25,6 +25,7 @@ @implementation RCTRefreshControl {
|
||||
UIColor *_titleColor;
|
||||
CGFloat _progressViewOffset;
|
||||
BOOL _hasMovedToWindow;
|
||||
+ UIColor *_customTintColor;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
@@ -60,6 +61,12 @@ - (void)layoutSubviews
|
||||
_isInitialRender = false;
|
||||
}
|
||||
|
||||
+- (void)didMoveToSuperview
|
||||
+{
|
||||
+ [super didMoveToSuperview];
|
||||
+ [self setTintColor:_customTintColor];
|
||||
+}
|
||||
+
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
@@ -225,6 +232,18 @@ - (void)refreshControlValueChanged
|
||||
}
|
||||
}
|
||||
|
||||
+// 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];
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
@end
|
||||
|
||||
#endif // RCT_REMOVE_LEGACY_ARCH
|
||||
diff --git a/React/Views/RefreshControl/RCTRefreshControlManager.m b/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
index 1e9ff527f4e6691d716da624031113a397876981..44329c5422c6f24d8a437fa35c6f2bad6bf8622b 100644
|
||||
--- a/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
+++ b/React/Views/RefreshControl/RCTRefreshControlManager.m
|
||||
@@ -24,11 +24,12 @@ - (UIView *)view
|
||||
|
||||
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/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
|
||||
index 59775241c80bec99ad3ec080f2425aacc8900c24..426de3aa77cda2032d7b0991e2ca3f8482a438d3 100644
|
||||
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
|
||||
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
|
||||
@@ -459,6 +459,13 @@ public open class ReactViewGroup public constructor(context: Context?) :
|
||||
inSubviewClippingLoop = true
|
||||
var clippedSoFar = 0
|
||||
for (i in 0..<allChildrenCount) {
|
||||
+ // Reentrant child removal during this loop can compact allChildren and leave a null at
|
||||
+ // an index below allChildrenCount. A null entry means the view is already detached, so
|
||||
+ // treat it as clipped instead of crashing.
|
||||
+ if (childArray[i] == null) {
|
||||
+ clippedSoFar++
|
||||
+ continue
|
||||
+ }
|
||||
try {
|
||||
updateSubviewClipStatus(clippingRect, i, clippedSoFar, excludedViewsSet)
|
||||
} catch (ex: IndexOutOfBoundsException) {
|
||||
@@ -496,7 +503,9 @@ public open class ReactViewGroup public constructor(context: Context?) :
|
||||
) {
|
||||
assertOnUiThread()
|
||||
|
||||
- val child = checkNotNull(allChildren?.get(idx))
|
||||
+ // allChildren can be mutated reentrantly while a clipping pass is running, so a stale
|
||||
+ // index can point at a null slot. Skip it instead of crashing.
|
||||
+ val child = allChildren?.get(idx) ?: return
|
||||
val intersects = clippingRect.intersects(child.left, child.top, child.right, child.bottom)
|
||||
var needUpdateClippingRecursive = false
|
||||
|
||||
diff --git a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm
|
||||
index 9b04cadc22f5ae7b105f9f9875a242b53188cf03..b2b27626edc46625ac2372a13977d700948835b6 100644
|
||||
--- a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm
|
||||
+++ b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm
|
||||
@@ -361,7 +361,7 @@ static UIFontDescriptorSystemDesign RCTGetFontDescriptorSystemDesign(NSString *f
|
||||
font = [UIFont fontWithName:fontProperties.family size:effectiveFontSize];
|
||||
if (font != nullptr) {
|
||||
fontNames = [UIFont fontNamesForFamilyName:font.familyName];
|
||||
- fontWeight = (fontWeight != 0.0) ?: RCTGetFontWeight(font);
|
||||
+ fontWeight = (fontWeight != 0.0) ? fontWeight : RCTGetFontWeight(font);
|
||||
} else {
|
||||
// Failback to system font.
|
||||
font = RCTDefaultFontWithFontProperties(fontProperties);
|
||||
diff --git a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm
|
||||
index ac553045a9c0ce77e288277912538d9e131ebc01..d99c8f4db5a07f1e4ffe7e03ff23adce9c63137b 100644
|
||||
--- a/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm
|
||||
+++ b/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm
|
||||
@@ -389,8 +389,9 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage
|
||||
size.height = enumeratedLinesHeight;
|
||||
}
|
||||
|
||||
- size = (CGSize){ceil(size.width * layoutContext.pointScaleFactor) / layoutContext.pointScaleFactor,
|
||||
- ceil(size.height * layoutContext.pointScaleFactor) / layoutContext.pointScaleFactor};
|
||||
+ CGFloat epsilon = 0.001;
|
||||
+ size = (CGSize){ceil((size.width + epsilon) * layoutContext.pointScaleFactor) / layoutContext.pointScaleFactor,
|
||||
+ ceil((size.height + epsilon) * layoutContext.pointScaleFactor) / layoutContext.pointScaleFactor};
|
||||
|
||||
NSRange visibleGlyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
||||
|
||||
diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm
|
||||
index 60160efb163d91813fa2ca7ca758b51afcf261e1..fb646fe945ffe4aa4a386f80a1e42a90180691f1 100644
|
||||
--- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm
|
||||
+++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.mm
|
||||
@@ -42,6 +42,32 @@ - (void)setRefreshing:(BOOL)refreshing
|
||||
@implementation RCTPullToRefreshViewComponentView {
|
||||
UIRefreshControl *_refreshControl;
|
||||
RCTScrollViewComponentView *__weak _scrollViewComponentView;
|
||||
+ /*
|
||||
+ * Deferred props: updateProps runs during the Create mount mutation, before
|
||||
+ * _attach puts the control on the scroll view, and writes to a detached
|
||||
+ * UIRefreshControl are hazardous:
|
||||
+ *
|
||||
+ * - tintColor: writing it to a detached control permanently suppresses the
|
||||
+ * pull-to-refresh trigger haptic on iOS 17.4+
|
||||
+ * (https://github.com/facebook/react-native/issues/43388).
|
||||
+ *
|
||||
+ * - progressViewOffset (the bounds.origin shift): on iOS 26 the control's
|
||||
+ * _UIRefreshControlModernContentView positions itself at whatever
|
||||
+ * bounds.origin it observes when it is CREATED - at insertion into the
|
||||
+ * scroll view, or earlier if a pre-attach property write materializes it -
|
||||
+ * and keeps that y forever (width tracks, y never re-pins; verified via
|
||||
+ * on-device frame logging, Aug 2026). A pre-attach shift is therefore
|
||||
+ * baked into the content view's own frame and cancelled exactly, hiding
|
||||
+ * the spinner. Applied post-attach, the content view has already been
|
||||
+ * created at origin 0 and the same bounds shift works as intended.
|
||||
+ *
|
||||
+ * Both props are parked here and applied only once the control is inside
|
||||
+ * the scroll view.
|
||||
+ */
|
||||
+ UIColor *_pendingTintColor;
|
||||
+ BOOL _hasPendingTintColor;
|
||||
+ CGFloat _pendingProgressViewOffset;
|
||||
+ BOOL _hasPendingProgressViewOffset;
|
||||
// This variable keeps track of whether the view is recycled or not. Once the view is recycled, the component
|
||||
// creates a new instance of UIRefreshControl, resetting the native props to the default values.
|
||||
// However, when recycling, we are keeping around the old _props. The flag is used to force the application
|
||||
@@ -79,10 +105,25 @@ + (ComponentDescriptorProvider)componentDescriptorProvider
|
||||
return concreteComponentDescriptorProvider<PullToRefreshViewComponentDescriptor>();
|
||||
}
|
||||
|
||||
+// Recycled instances get all props force-applied in updateProps, which runs
|
||||
+// before the new UIRefreshControl is inserted into the scroll view hierarchy;
|
||||
+// touching the control that early suppresses the pull-to-refresh haptic on
|
||||
+// iOS 17.4+ (react-native#43388). Opting out of recycling keeps every mount on
|
||||
+// the untouched-before-attach path. Refresh controls are rare and cheap, so
|
||||
+// losing recycling for them is negligible.
|
||||
++ (BOOL)shouldBeRecycled
|
||||
+{
|
||||
+ return NO;
|
||||
+}
|
||||
+
|
||||
- (void)prepareForRecycle
|
||||
{
|
||||
[super prepareForRecycle];
|
||||
_scrollViewComponentView = nil;
|
||||
+ _pendingTintColor = nil;
|
||||
+ _hasPendingTintColor = NO;
|
||||
+ _pendingProgressViewOffset = 0;
|
||||
+ _hasPendingProgressViewOffset = NO;
|
||||
[self _initializeUIRefreshControl];
|
||||
_recycled = YES;
|
||||
}
|
||||
@@ -93,7 +134,8 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
|
||||
const auto &newConcreteProps = static_cast<const PullToRefreshViewProps &>(*props);
|
||||
|
||||
if (_recycled || newConcreteProps.tintColor != oldConcreteProps.tintColor) {
|
||||
- _refreshControl.tintColor = RCTUIColorFromSharedColor(newConcreteProps.tintColor);
|
||||
+ // Deferred until the control is inside the scroll view (#43388).
|
||||
+ [self _updateTintColor:RCTUIColorFromSharedColor(newConcreteProps.tintColor)];
|
||||
}
|
||||
|
||||
if (_recycled || newConcreteProps.progressViewOffset != oldConcreteProps.progressViewOffset) {
|
||||
@@ -141,11 +183,50 @@ - (void)handleUIControlEventValueChanged
|
||||
|
||||
- (void)_updateProgressViewOffset:(Float)progressViewOffset
|
||||
{
|
||||
+ _pendingProgressViewOffset = progressViewOffset;
|
||||
+ _hasPendingProgressViewOffset = YES;
|
||||
+ // Applies immediately for runtime changes while the control is attached;
|
||||
+ // pre-attach sets wait until the control is inside the scroll view (see the
|
||||
+ // _pendingProgressViewOffset declaration).
|
||||
+ [self _applyPendingProgressViewOffsetIfPossible];
|
||||
+ if (_hasPendingProgressViewOffset) {
|
||||
+ [self setNeedsLayout];
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+- (void)_applyPendingProgressViewOffsetIfPossible
|
||||
+{
|
||||
+ if (!_hasPendingProgressViewOffset || ![_refreshControl.superview isKindOfClass:[UIScrollView class]]) {
|
||||
+ return;
|
||||
+ }
|
||||
_refreshControl.bounds = CGRectMake(
|
||||
_refreshControl.bounds.origin.x,
|
||||
- -progressViewOffset,
|
||||
+ -_pendingProgressViewOffset,
|
||||
_refreshControl.bounds.size.width,
|
||||
_refreshControl.bounds.size.height);
|
||||
+ _hasPendingProgressViewOffset = NO;
|
||||
+}
|
||||
+
|
||||
+- (void)_updateTintColor:(UIColor *)tintColor
|
||||
+{
|
||||
+ _pendingTintColor = tintColor;
|
||||
+ _hasPendingTintColor = YES;
|
||||
+ // Applies immediately for runtime changes while the control is attached;
|
||||
+ // pre-attach sets wait until the control is inside the scroll view.
|
||||
+ [self _applyPendingTintColorIfPossible];
|
||||
+ if (_hasPendingTintColor) {
|
||||
+ [self setNeedsLayout];
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+- (void)_applyPendingTintColorIfPossible
|
||||
+{
|
||||
+ if (!_hasPendingTintColor || ![_refreshControl.superview isKindOfClass:[UIScrollView class]]) {
|
||||
+ return;
|
||||
+ }
|
||||
+ _refreshControl.tintColor = _pendingTintColor;
|
||||
+ _pendingTintColor = nil;
|
||||
+ _hasPendingTintColor = NO;
|
||||
}
|
||||
|
||||
- (void)_updateTitle
|
||||
@@ -153,7 +234,12 @@ - (void)_updateTitle
|
||||
const auto &concreteProps = static_cast<const PullToRefreshViewProps &>(*_props);
|
||||
|
||||
if (concreteProps.title.empty()) {
|
||||
- _refreshControl.attributedTitle = nil;
|
||||
+ // Avoid touching the control when there is nothing to clear - writing
|
||||
+ // attributedTitle (even nil) before the control is in the scroll view
|
||||
+ // hierarchy can suppress the pull-to-refresh haptic (#43388).
|
||||
+ if (_refreshControl.attributedTitle != nil) {
|
||||
+ _refreshControl.attributedTitle = nil;
|
||||
+ }
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,6 +258,18 @@ - (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
+ /*
|
||||
+ * Fallback for the pending props: _attach applies them right after the
|
||||
+ * refreshControl assignment (insertion is synchronous there on current iOS),
|
||||
+ * but should UIKit ever defer the insertion to a later layout pass, re-arm
|
||||
+ * and retry until the control is actually inside the scroll view.
|
||||
+ */
|
||||
+ [self _applyPendingTintColorIfPossible];
|
||||
+ [self _applyPendingProgressViewOffsetIfPossible];
|
||||
+ if ((_hasPendingTintColor || _hasPendingProgressViewOffset) && _scrollViewComponentView != nil) {
|
||||
+ [self setNeedsLayout];
|
||||
+ }
|
||||
+
|
||||
// Attempts to begin refreshing before the initial layout are ignored by _refreshControl. So if the control is
|
||||
// refreshing when mounted, we need to call beginRefreshing in layoutSubviews or it won't work.
|
||||
if (self.window) {
|
||||
@@ -209,6 +307,15 @@ - (void)_attach
|
||||
|
||||
// This ensures that layoutSubviews is called. Without this, recycled instances won't refresh on mount
|
||||
[self setNeedsLayout];
|
||||
+
|
||||
+ /*
|
||||
+ * The assignment above inserts the control (and creates its content view)
|
||||
+ * synchronously on current iOS - verified via frame logging - so the
|
||||
+ * pending props can be applied immediately. layoutSubviews is the fallback
|
||||
+ * if insertion is ever deferred.
|
||||
+ */
|
||||
+ [self _applyPendingTintColorIfPossible];
|
||||
+ [self _applyPendingProgressViewOffsetIfPossible];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,62 @@ Patching `RCTRefreshControl.mm` temporarily to play an impact haptic on refresh
|
||||
17.4, there has been a regression somewhere causing haptics to not play on iOS on refresh. Should monitor for an update
|
||||
in the RN repo: https://github.com/facebook/react-native/issues/43388
|
||||
|
||||
## RCTPullToRefreshViewComponentView.mm Patch - RefreshControl initial props dropped on New Arch
|
||||
## RCTPullToRefreshViewComponentView.mm Patch - iOS 17.4+ haptic regression and iOS 26 progressViewOffset cancellation on New Arch
|
||||
|
||||
**TODO: Remove after bumping React Native to 0.82+** (fixed upstream by facebook/react-native#52615, #52584
|
||||
and #53231).
|
||||
Both bugs share one root cause, established by instrumented frame-logging runs on the iOS 26
|
||||
simulator (Aug 2026): **writes to a detached `UIRefreshControl` are hazardous, because the
|
||||
control's `_UIRefreshControlModernContentView` bakes in the state it observes at its own
|
||||
creation.** Facts proven by the logs:
|
||||
|
||||
On Fabric, `updateProps` diffs against `_props`, but the initial-layout replay in `layoutSubviews` passes
|
||||
`_props` as the new props too, so the diff is a no-op and `tintColor`/`progressViewOffset`/`title` are never
|
||||
applied on mount. This hides the pull-to-refresh spinner behind the floating home header (it stays at offset
|
||||
0 instead of `headerOffset`). We diff against the `oldProps` argument instead, null-guarded with default
|
||||
props for the create-mutation path.
|
||||
- `scrollView.refreshControl` assignment inserts the control and creates its content view
|
||||
**synchronously** on iOS 26 (the "UIKit inserts lazily on a later layout pass" folklore is
|
||||
false there).
|
||||
- The content view can also be materialized **earlier** by a pre-attach property write (observed
|
||||
with `tintColor`) while the control is still detached.
|
||||
- The content view positions itself at whatever `bounds.origin` exists at its creation and keeps
|
||||
that y forever - width tracks on later layouts, y never re-pins.
|
||||
|
||||
Issue: https://github.com/facebook/react-native/issues/56343
|
||||
Consequences:
|
||||
|
||||
**1. progressViewOffset.** Stock Fabric writes the offset as a `bounds.origin` shift in
|
||||
`updateProps`, pre-attach. The content view is then created (at insertion) already inside the
|
||||
shifted bounds, pins to it, and cancels the shift exactly - spinner hidden behind the floating
|
||||
home header (home is the only screen passing a non-zero offset). Stock RN appeared to work only
|
||||
by accident: its own pre-attach `tintColor` write materialized the content view at origin 0
|
||||
*before* the offset write. Possibly related upstream: react-native#54183.
|
||||
|
||||
**2. Haptic (react-native#43388).** The Paper fix above does not cover Fabric: `updateProps`
|
||||
writes `tintColor` pre-attach, and a tint write on a detached control materializes the content
|
||||
view outside the scroll view, permanently suppressing the trigger haptic on iOS 17.4+ (the
|
||||
creation-time-state story likely explains this too, though the haptic wiring itself is not
|
||||
observable in logs).
|
||||
|
||||
**The fix**: both `tintColor` and `progressViewOffset` are parked in the component view
|
||||
(`_pendingTintColor` / `_pendingProgressViewOffset`, no `UIRefreshControl` subclass) and applied
|
||||
only once `_refreshControl.superview` is the scroll view - by then the content view exists,
|
||||
was created at origin 0, and a bounds shift lands visibly. Application points: immediately in
|
||||
`_updateX` for runtime changes while attached; in `_attach` right after the assignment (insertion
|
||||
is synchronous); and from `layoutSubviews` with a `setNeedsLayout` re-arm as a fallback should
|
||||
insertion ever be deferred.
|
||||
|
||||
Supporting changes:
|
||||
|
||||
- `shouldBeRecycled = NO`: recycled instances get all props force-applied in `updateProps` before
|
||||
the new control is attached, which would re-trigger the pre-attach hazards; opting out keeps
|
||||
every mount on the untouched-before-attach path.
|
||||
- `_updateTitle` no longer writes `attributedTitle = nil` when there is nothing to clear - even a
|
||||
nil write before attach suppresses the haptic.
|
||||
|
||||
History: an earlier iteration fixed the offset by porting Paper's frame-offset trick into an
|
||||
`RCTHapticCompatibleRefreshControl` subclass (worked, verified on device) - replaced by the
|
||||
deferral once the root cause was understood. The control's `didMoveToSuperview` appeared broken
|
||||
as a tint application point in early non-rigorous testing; unproven, not disproven.
|
||||
|
||||
Upstream issue #43388 still open as of Aug 2026. Haptics cannot be verified on the simulator -
|
||||
physical device only. Spinner position verified via frame logs; haptic on this variant NOT yet
|
||||
device-verified.
|
||||
|
||||
Opened issue in RN repo: https://github.com/react/react-native/issues/57843
|
||||
|
||||
## RCTEnhancedScrollView.mm / RCTScrollViewComponentView.mm Patch - centerContent insets stale after content resize on New Arch
|
||||
|
||||
@@ -86,6 +130,16 @@ the prebuilt AAR from Maven Central instead, where this hunk (like any ReactAndr
|
||||
source change) has no effect - do not expect to see the fix in a local debug build unless
|
||||
you prebuild with EXPO_PUBLIC_ENV=production or add the substitution block manually.
|
||||
|
||||
## RCTFontUtils.mm Patch - Custom font weights render as the heaviest face on New Arch
|
||||
|
||||
**TODO: Remove after bumping React Native to a release that contains facebook/react-native#57483**
|
||||
(commit 918fb15bfe5f, on `main`; not in 0.86 and not yet released).
|
||||
|
||||
Backport of the upstream one-liner: use a real ternary so the numeric weight is returned instead of
|
||||
the boolean. For a double, `(A != 0.0) ? A : B` is exactly equivalent to the original `A ?: B`.
|
||||
|
||||
PR: https://github.com/facebook/react-native/pull/57483
|
||||
|
||||
## RCTTextLayoutManager.mm Patch - Text overflows instead of wrapping on the last line
|
||||
|
||||
Issue: https://github.com/react/react-native/issues/53450#issuecomment-3298157830
|
||||
@@ -56,7 +56,7 @@ const withXcodeTarget = (config, {targetName}) => {
|
||||
buildSettingsObj.SWIFT_VERSION = '5.0'
|
||||
buildSettingsObj.TARGETED_DEVICE_FAMILY = `"1"`
|
||||
buildSettingsObj.DEVELOPMENT_TEAM = 'B3LX46C5HS'
|
||||
buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = '15.1'
|
||||
buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = '16.4'
|
||||
buildSettingsObj.ASSETCATALOG_COMPILER_APPICON_NAME = 'AppIcon'
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+2157
-2212
File diff suppressed because it is too large
Load Diff
+18
-21
@@ -13,16 +13,16 @@ packageExtensions:
|
||||
trustPolicy: 'no-downgrade' # default: off
|
||||
trustPolicyIgnoreAfter: 10080 # 7 days, default: undefined
|
||||
overrides:
|
||||
'@react-native/babel-preset': '0.81.5'
|
||||
'@react-native/normalize-colors': '0.81.5'
|
||||
'@react-native/babel-preset': '0.86.0'
|
||||
'@react-native/normalize-colors': '0.86.0'
|
||||
'@expo/image-utils': '0.8.12'
|
||||
'@types/estree': '1.0.6'
|
||||
'react-native-compressor': '1.13.0'
|
||||
'react-native-reanimated': '4.3.2'
|
||||
'react-native-worklets': '0.8.3'
|
||||
'react-native-reanimated': '4.5.3'
|
||||
'react-native-worklets': '0.11.3'
|
||||
'psl': '1.9.0'
|
||||
'@types/psl': '1.1.1'
|
||||
'react-native-screens': '4.24.0'
|
||||
'react-native-screens': '4.26.2'
|
||||
allowBuilds:
|
||||
'@sentry/cli': true
|
||||
'core-js': false
|
||||
@@ -31,26 +31,23 @@ allowBuilds:
|
||||
'unrs-resolver': true
|
||||
patchedDependencies:
|
||||
'@sentry/expo-upload-sourcemaps@8.18.0': patches/@sentry__expo-upload-sourcemaps@8.18.0.patch
|
||||
'expo-age-range@0.2.18': patches/expo-age-range@0.2.18.patch
|
||||
'expo-haptics@15.0.8': patches/expo-haptics@15.0.8.patch
|
||||
'expo-image-picker@17.0.11': patches/expo-image-picker@17.0.11.patch
|
||||
'expo-image@3.0.11': patches/expo-image@3.0.11.patch
|
||||
'expo-media-library@18.2.1': patches/expo-media-library@18.2.1.patch
|
||||
'expo-modules-core@3.0.30': patches/expo-modules-core@3.0.30.patch
|
||||
'expo-notifications@0.32.17': patches/expo-notifications@0.32.17.patch
|
||||
'expo-updates@29.0.17': patches/expo-updates@29.0.17.patch
|
||||
'expo-age-range@57.0.2': patches/expo-age-range@57.0.2.patch
|
||||
'expo-haptics@57.0.1': patches/expo-haptics@57.0.1.patch
|
||||
'expo-media-library@57.0.3': patches/expo-media-library@57.0.3.patch
|
||||
'expo-modules-core@57.0.8': patches/expo-modules-core@57.0.8.patch
|
||||
'expo-notifications@57.0.7': patches/expo-notifications@57.0.7.patch
|
||||
'expo-updates@57.0.10': patches/expo-updates@57.0.10.patch
|
||||
expo@57.0.8: patches/expo@57.0.8.patch
|
||||
'react-native-compressor@1.13.0': patches/react-native-compressor@1.13.0.patch
|
||||
'react-native-date-picker@5.0.13': patches/react-native-date-picker@5.0.13.patch
|
||||
'react-native-drawer-layout@4.2.3': patches/react-native-drawer-layout@4.2.3.patch
|
||||
'react-native-gesture-handler': patches/react-native-gesture-handler.patch
|
||||
'react-native-keyboard-controller@1.21.8': patches/react-native-keyboard-controller@1.21.8.patch
|
||||
'react-native-keyboard-controller@1.21.9': patches/react-native-keyboard-controller@1.21.9.patch
|
||||
'react-native-pager-view@6.8.0': patches/react-native-pager-view@6.8.0.patch
|
||||
'react-native-reanimated@4.3.2': patches/react-native-reanimated@4.3.2.patch
|
||||
react-native-screens@4.24.0: patches/react-native-screens@4.24.0.patch
|
||||
'react-native-svg@15.12.1': patches/react-native-svg@15.12.1.patch
|
||||
'react-native-view-shot@4.0.3': patches/react-native-view-shot@4.0.3.patch
|
||||
react-native-worklets@0.8.3: patches/react-native-worklets@0.8.3.patch
|
||||
'react-native@0.81.5': patches/react-native@0.81.5.patch
|
||||
'react-native-reanimated@4.5.3': patches/react-native-reanimated@4.5.3.patch
|
||||
'react-native-screens@4.26.2': patches/react-native-screens@4.26.2.patch
|
||||
'react-native-svg@15.15.4': patches/react-native-svg@15.15.4.patch
|
||||
react-native-worklets@0.11.3: patches/react-native-worklets@0.11.3.patch
|
||||
'react-native@0.86.0': patches/react-native@0.86.0.patch
|
||||
minimumReleaseAgeExclude:
|
||||
- '@atproto/*'
|
||||
- '@atproto-labs/*'
|
||||
|
||||
@@ -14,7 +14,7 @@ SETTINGS_GRADLE="$ANDROID_DIR/settings.gradle"
|
||||
# Guard against building with the wrong app identity. The New Arch build must
|
||||
# use a distinct rootProject.name so it installs alongside the store app rather
|
||||
# than overwriting it.
|
||||
EXPECTED_APP_NAME="rootProject.name = 'Bluesky (New Arch)'"
|
||||
EXPECTED_APP_NAME="rootProject.name = 'Bluesky'"
|
||||
if ! grep -qF "$EXPECTED_APP_NAME" "$SETTINGS_GRADLE"; then
|
||||
echo "Error: expected \"$EXPECTED_APP_NAME\" in $SETTINGS_GRADLE" >&2
|
||||
echo "(Set the app name in settings.gradle before building the New Arch release.)" >&2
|
||||
|
||||
+5
-4
@@ -2,7 +2,6 @@ import {forwardRef, useCallback, useEffect, useState} from 'react'
|
||||
import {
|
||||
AccessibilityInfo,
|
||||
Image as RNImage,
|
||||
StyleSheet,
|
||||
useColorScheme,
|
||||
View,
|
||||
} from 'react-native'
|
||||
@@ -20,6 +19,7 @@ import {Image} from 'expo-image'
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
|
||||
import {Logotype} from '#/view/icons/Logotype'
|
||||
import {atoms as a} from '#/alf'
|
||||
// @ts-ignore
|
||||
import splashImagePointer from '../assets/splash/splash.png'
|
||||
// @ts-ignore
|
||||
@@ -170,12 +170,12 @@ export function Splash(props: React.PropsWithChildren<Props>) {
|
||||
return (
|
||||
<View style={{flex: 1}} onLayout={onLayout}>
|
||||
{!isAnimationComplete && (
|
||||
<View style={StyleSheet.absoluteFillObject}>
|
||||
<View style={[a.absolute, a.inset_0]}>
|
||||
<Image
|
||||
accessibilityIgnoresInvertColors
|
||||
onLoadEnd={onLoadEnd}
|
||||
source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}}
|
||||
style={StyleSheet.absoluteFillObject}
|
||||
style={[a.absolute, a.inset_0]}
|
||||
/>
|
||||
|
||||
<Animated.View
|
||||
@@ -205,7 +205,8 @@ export function Splash(props: React.PropsWithChildren<Props>) {
|
||||
{!isAnimationComplete && (
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFillObject,
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
logoAnimation,
|
||||
{
|
||||
flex: 1,
|
||||
|
||||
@@ -2,7 +2,7 @@ import {lazy, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
// @ts-expect-error missing types
|
||||
import QRCode from 'react-native-qrcode-styled'
|
||||
import type ViewShot from 'react-native-view-shot'
|
||||
import {type ViewShotRef} from 'react-native-view-shot'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {Logo} from '#/view/icons/Logo'
|
||||
@@ -14,10 +14,7 @@ import {IS_WEB} from '#/env'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
const LazyViewShot = lazy(
|
||||
// @ts-expect-error dynamic import
|
||||
() => import('react-native-view-shot/src/index'),
|
||||
)
|
||||
const LazyViewShot = lazy(() => import('react-native-view-shot'))
|
||||
|
||||
export function QrCode({
|
||||
starterPack,
|
||||
@@ -26,7 +23,7 @@ export function QrCode({
|
||||
}: {
|
||||
starterPack: app.bsky.graph.defs.StarterPackView
|
||||
link: string
|
||||
ref: React.Ref<ViewShot>
|
||||
ref: React.Ref<ViewShotRef>
|
||||
}) {
|
||||
const {record} = starterPack
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import {Suspense, useRef, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import type ViewShot from 'react-native-view-shot'
|
||||
import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library'
|
||||
import {type ViewShotRef} from 'react-native-view-shot'
|
||||
import {
|
||||
requestPermissionsAsync,
|
||||
saveToLibraryAsync,
|
||||
} from 'expo-media-library/legacy'
|
||||
import * as Sharing from 'expo-sharing'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -38,7 +41,7 @@ export function QrCodeDialog({
|
||||
const [isSaveProcessing, setIsSaveProcessing] = useState(false)
|
||||
const [isCopyProcessing, setIsCopyProcessing] = useState(false)
|
||||
|
||||
const ref = useRef<ViewShot>(null)
|
||||
const ref = useRef<ViewShotRef>(null)
|
||||
|
||||
const getCanvas = (base64: string): Promise<HTMLCanvasElement> => {
|
||||
return new Promise(resolve => {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {Suspense, useRef} from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import type ViewShot from 'react-native-view-shot'
|
||||
import {type ViewShotRef} from 'react-native-view-shot'
|
||||
import {setStringAsync} from 'expo-clipboard'
|
||||
import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library'
|
||||
import {
|
||||
requestPermissionsAsync,
|
||||
saveToLibraryAsync,
|
||||
} from 'expo-media-library/legacy'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
@@ -41,7 +44,7 @@ export function InviteFriendsDialogInner({
|
||||
const {currentAccount} = useSession()
|
||||
const profileQuery = useProfileQuery({did: currentAccount?.did})
|
||||
const [themeKey, setThemeKey] = useInviteThemeKey()
|
||||
const captureRef = useRef<ViewShot>(null)
|
||||
const captureRef = useRef<ViewShotRef>(null)
|
||||
|
||||
const theme = getInviteTheme(themeKey)
|
||||
const variant = t.name === 'light' ? theme.light : theme.dark
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useCallback, useState} from 'react'
|
||||
import {Pressable, StyleSheet, useWindowDimensions, View} from 'react-native'
|
||||
import {Pressable, useWindowDimensions, View} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import Svg, {Path} from 'react-native-svg'
|
||||
import {
|
||||
@@ -125,7 +125,7 @@ export function InviteScannerScreen() {
|
||||
noInsetTop
|
||||
style={{backgroundColor: t.palette.black}}>
|
||||
<CameraView
|
||||
style={StyleSheet.absoluteFill}
|
||||
style={[a.absolute, a.inset_0]}
|
||||
facing="back"
|
||||
barcodeScannerSettings={{barcodeTypes: ['qr']}}
|
||||
onBarcodeScanned={scannerEnabled ? onBarcodeScanned : undefined}
|
||||
@@ -254,7 +254,7 @@ function ScannerScrim() {
|
||||
`V${y + r} A${r} ${r} 0 0 1 ${x + r} ${y} Z`
|
||||
return (
|
||||
<Svg
|
||||
style={StyleSheet.absoluteFill}
|
||||
style={[a.absolute, a.inset_0]}
|
||||
width={width}
|
||||
height={height}
|
||||
pointerEvents="none">
|
||||
|
||||
@@ -2,7 +2,7 @@ import {lazy} from 'react'
|
||||
import {View} from 'react-native'
|
||||
// @ts-expect-error missing types
|
||||
import QRCode from 'react-native-qrcode-styled'
|
||||
import type ViewShot from 'react-native-view-shot'
|
||||
import {type ViewShotRef} from 'react-native-view-shot'
|
||||
import {Image} from 'expo-image'
|
||||
import {LinearGradient} from 'expo-linear-gradient'
|
||||
|
||||
@@ -12,10 +12,7 @@ import {hexToRgb, rgbToHex} from '#/alf/util/colorGeneration'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {type InviteThemeVariant} from '../themes'
|
||||
|
||||
const LazyViewShot = lazy(
|
||||
// @ts-expect-error dynamic import
|
||||
() => import('react-native-view-shot/src/index'),
|
||||
)
|
||||
const LazyViewShot = lazy(() => import('react-native-view-shot'))
|
||||
|
||||
const CARD_WIDTH = 278
|
||||
const CARD_GRADIENT_PADDING = 12
|
||||
@@ -37,7 +34,7 @@ export function ThemedQrCard({
|
||||
shareUrl: string
|
||||
handle: string
|
||||
avatarUri?: string
|
||||
captureRef: React.Ref<ViewShot>
|
||||
captureRef: React.Ref<ViewShotRef>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useMemo, useState} from 'react'
|
||||
import {type ColorValue, Dimensions, StyleSheet, View} from 'react-native'
|
||||
import {type ColorValue, Dimensions, View} from 'react-native'
|
||||
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
|
||||
import Animated, {
|
||||
clamp,
|
||||
@@ -16,6 +16,7 @@ import Animated, {
|
||||
import {scheduleOnRN} from 'react-native-worklets'
|
||||
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {type GestureActions} from './GestureActionView.shared'
|
||||
|
||||
const MAX_WIDTH = Dimensions.get('screen').width
|
||||
@@ -287,8 +288,7 @@ export function GestureActionView({
|
||||
return (
|
||||
<GestureDetector gesture={composedGesture}>
|
||||
<View>
|
||||
<Animated.View
|
||||
style={[StyleSheet.absoluteFill, animatedBackgroundStyle]}>
|
||||
<Animated.View style={[a.absolute, a.inset_0, animatedBackgroundStyle]}>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Linking} from 'react-native'
|
||||
import {useCameraPermissions as useExpoCameraPermissions} from 'expo-camera'
|
||||
import * as MediaLibrary from 'expo-media-library'
|
||||
import * as MediaLibrary from 'expo-media-library/legacy'
|
||||
|
||||
import {Alert} from '#/view/com/util/Alert'
|
||||
import {IS_WEB} from '#/env'
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
writeAsStringAsync,
|
||||
} from 'expo-file-system/legacy'
|
||||
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
||||
import * as MediaLibrary from 'expo-media-library'
|
||||
import * as MediaLibrary from 'expo-media-library/legacy'
|
||||
import * as Sharing from 'expo-sharing'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useCallback} from 'react'
|
||||
import * as MediaLibrary from 'expo-media-library'
|
||||
import * as MediaLibrary from 'expo-media-library/legacy'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
|
||||
Vendored
+40
-8
@@ -18,27 +18,42 @@ declare module '*.css'
|
||||
/*
|
||||
* expo-file-system's declarations build File/Directory on top of
|
||||
* `./ExpoFileSystem`, which remaps to a web shim whose classes are empty.
|
||||
* The fully-typed base classes live in ExpoFileSystem.types (no .web
|
||||
* sibling), so mirror the FileSystem.d.ts wrapper classes on top of those.
|
||||
* As of SDK 57 the fully-typed base classes live in
|
||||
* internal/NativeFileSystem.types (no .web sibling), so mirror the File.d.ts
|
||||
* and Directory.d.ts wrapper classes on top of those.
|
||||
*/
|
||||
declare module 'expo-file-system' {
|
||||
import {
|
||||
Directory as ExpoFileSystemDirectory,
|
||||
File as ExpoFileSystemFile,
|
||||
} from 'expo-file-system/build/ExpoFileSystem.types'
|
||||
NativeFileSystemDirectory as ExpoFileSystemDirectory,
|
||||
NativeFileSystemFile as ExpoFileSystemFile,
|
||||
} from 'expo-file-system/build/internal/NativeFileSystem.types'
|
||||
export {
|
||||
type DirectoryCreateOptions,
|
||||
type DirectoryInfo,
|
||||
type DownloadOptions,
|
||||
EncodingType,
|
||||
type FileCreateOptions,
|
||||
FileHandle,
|
||||
type FileHandle,
|
||||
type FileInfo,
|
||||
type FileWriteOptions,
|
||||
type InfoOptions,
|
||||
type PathInfo,
|
||||
} from 'expo-file-system/build/ExpoFileSystem.types'
|
||||
import {type PathInfo as ExpoPathInfo} from 'expo-file-system/build/ExpoFileSystem.types'
|
||||
} from 'expo-file-system/build/FileSystem.types'
|
||||
import {type PathInfo as ExpoPathInfo} from 'expo-file-system/build/FileSystem.types'
|
||||
import {
|
||||
type WatchEvent,
|
||||
type WatchOptions,
|
||||
type WatchSubscription,
|
||||
} from 'expo-file-system/build/FileSystemWatcher.types'
|
||||
import {
|
||||
type DownloadTask,
|
||||
type UploadTask,
|
||||
} from 'expo-file-system/build/NetworkTasks'
|
||||
import {
|
||||
type DownloadTaskOptions,
|
||||
type UploadOptions,
|
||||
type UploadResult,
|
||||
} from 'expo-file-system/build/NetworkTasks.types'
|
||||
import {PathUtilities} from 'expo-file-system/build/pathUtilities'
|
||||
|
||||
export class Paths extends PathUtilities {
|
||||
@@ -59,8 +74,21 @@ declare module 'expo-file-system' {
|
||||
readableStream(): ReadableStream<Uint8Array<ArrayBuffer>>
|
||||
writableStream(): WritableStream<Uint8Array<ArrayBufferLike>>
|
||||
arrayBuffer(): Promise<ArrayBuffer>
|
||||
json(): Promise<unknown>
|
||||
formData(): ReturnType<Response['formData']>
|
||||
stream(): ReadableStream<Uint8Array<ArrayBuffer>>
|
||||
slice(start?: number, end?: number, contentType?: string): Blob
|
||||
upload(url: string, options?: UploadOptions): Promise<UploadResult>
|
||||
createUploadTask(url: string, options?: UploadOptions): UploadTask
|
||||
static createDownloadTask(
|
||||
url: string,
|
||||
destination: File | Directory,
|
||||
options?: DownloadTaskOptions,
|
||||
): DownloadTask
|
||||
watch(
|
||||
callback: (event: WatchEvent<File>) => void,
|
||||
options?: WatchOptions,
|
||||
): WatchSubscription
|
||||
}
|
||||
|
||||
export class Directory extends ExpoFileSystemDirectory {
|
||||
@@ -70,6 +98,10 @@ declare module 'expo-file-system' {
|
||||
get name(): string
|
||||
createFile(name: string, mimeType: string | null): File
|
||||
createDirectory(name: string): Directory
|
||||
watch(
|
||||
callback: (event: WatchEvent<File | Directory>) => void,
|
||||
options?: WatchOptions,
|
||||
): WatchSubscription
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,12 @@ import {
|
||||
useRef,
|
||||
} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import type ViewShot from 'react-native-view-shot'
|
||||
import {type ViewShotRef} from 'react-native-view-shot'
|
||||
|
||||
import {useAvatar} from '#/screens/Onboarding/StepProfile/index'
|
||||
import {atoms as a} from '#/alf'
|
||||
|
||||
const LazyViewShot = lazy(
|
||||
// @ts-expect-error dynamic import
|
||||
() => import('react-native-view-shot/src/index'),
|
||||
)
|
||||
const LazyViewShot = lazy(() => import('react-native-view-shot'))
|
||||
|
||||
const SIZE_MULTIPLIER = 5
|
||||
|
||||
@@ -28,7 +25,7 @@ export interface PlaceholderCanvasRef {
|
||||
export const PlaceholderCanvas = forwardRef<PlaceholderCanvasRef, {}>(
|
||||
function PlaceholderCanvas({}, ref) {
|
||||
const {avatar} = useAvatar()
|
||||
const viewshotRef = useRef<ViewShot>(null)
|
||||
const viewshotRef = useRef<ViewShotRef>(null)
|
||||
const Icon = avatar.placeholder.component
|
||||
|
||||
const styles = useMemo(
|
||||
|
||||
@@ -107,6 +107,7 @@ export function StepProfile() {
|
||||
const response = await sheetWrapper(
|
||||
launchImageLibraryAsync({
|
||||
exif: false,
|
||||
shouldDownloadFromNetwork: true,
|
||||
mediaTypes: ['images'],
|
||||
quality: 1,
|
||||
...opts,
|
||||
|
||||
@@ -95,8 +95,7 @@ export function StepInfo({
|
||||
tldtsRef.current = tldts
|
||||
})
|
||||
// This will get used in the avatar creator a few steps later, so lets preload it now
|
||||
// @ts-expect-error - valid path
|
||||
void import('react-native-view-shot/src/index')
|
||||
void import('react-native-view-shot')
|
||||
}, [])
|
||||
|
||||
const onNextPress = () => {
|
||||
|
||||
@@ -567,7 +567,7 @@ export const ComposePost = ({
|
||||
FileSystem.Paths.cache,
|
||||
tempFileName,
|
||||
)
|
||||
sourceFile.copy(tempFile)
|
||||
await sourceFile.copy(tempFile)
|
||||
logger.debug('restoreVideo: copied to temp file', {
|
||||
source: videoInfo.uri,
|
||||
temp: tempFile.uri,
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function saveMediaToLocal(
|
||||
|
||||
try {
|
||||
const sourceFile = new File(normalizedSource)
|
||||
sourceFile.copy(destFile)
|
||||
await sourceFile.copy(destFile)
|
||||
// Update cache after successful save
|
||||
mediaExistsCache.set(localRefPath, true)
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useCallback} from 'react'
|
||||
import * as MediaLibrary from 'expo-media-library'
|
||||
import * as MediaLibrary from 'expo-media-library/legacy'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
|
||||
@@ -369,7 +369,7 @@ export function TabBar({
|
||||
</Animated.View>
|
||||
</ScrollView>
|
||||
</BlockDrawerGesture>
|
||||
<View style={[t.atoms.border_contrast_low, styles.outerBottomBorder]} />
|
||||
<View style={[t.atoms.bg_contrast_100, styles.outerBottomBorder]} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -470,6 +470,6 @@ const styles = StyleSheet.create({
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: '100%',
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
height: StyleSheet.hairlineWidth,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -12,6 +12,21 @@
|
||||
*/
|
||||
"expo-file-system/legacy": [
|
||||
"./node_modules/expo-file-system/build/legacy/index.d.ts"
|
||||
],
|
||||
/*
|
||||
* Mirrors the root tsconfig mapping (paths does not merge across
|
||||
* extends): expo-file-system 57's `exports` map blocks the deep type
|
||||
* imports in src/platform/misc.web-check.d.ts.
|
||||
*/
|
||||
"expo-file-system/build/*": ["./node_modules/expo-file-system/build/*"],
|
||||
/*
|
||||
* The react-native exports condition resolves to the package's raw
|
||||
* TypeScript source, whose findNodeHandle usage breaks under the web
|
||||
* pass. Point it at the compiled declarations, where skipLibCheck
|
||||
* applies.
|
||||
*/
|
||||
"react-native-view-shot": [
|
||||
"./node_modules/react-native-view-shot/lib/index.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
"paths": {
|
||||
"#/*": ["./src/*"],
|
||||
"crypto": ["./src/platform/crypto.ts"],
|
||||
/*
|
||||
* expo-file-system 57 ships an `exports` map without `./build/*`, which
|
||||
* blocks the deep type imports in src/platform/misc.web-check.d.ts
|
||||
* under `moduleResolution: "bundler"`. Type-check-only escape hatch.
|
||||
*/
|
||||
"expo-file-system/build/*": ["./node_modules/expo-file-system/build/*"],
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Replacement for react-native-reanimated's js-reanimated/webUtils.web.js,
|
||||
* wired up via a webpack alias in webpack.config.js.
|
||||
*
|
||||
* The original file declares ES module exports but populates them with bare
|
||||
* CommonJS `require()` calls wrapped in try/catch. Webpack parses the file as
|
||||
* ESM and therefore leaves those `require`s untouched, so in the browser they
|
||||
* throw ReferenceError, the try/catch swallows it, and createReactDOMStyle &
|
||||
* co. stay undefined. That silently disables reanimated's DOM update path and
|
||||
* _updatePropsJS later crashes with "Cannot convert undefined or null to
|
||||
* object" on Object.keys(component.props). Importing the same react-native-web
|
||||
* internals statically fixes the resolution.
|
||||
*/
|
||||
import createReactDOMStyle from 'react-native-web/dist/exports/StyleSheet/compiler/createReactDOMStyle'
|
||||
import {
|
||||
createTextShadowValue,
|
||||
createTransformValue,
|
||||
} from 'react-native-web/dist/exports/StyleSheet/preprocess'
|
||||
|
||||
export {createReactDOMStyle, createTextShadowValue, createTransformValue}
|
||||
@@ -1,6 +1,7 @@
|
||||
const path = require('path')
|
||||
|
||||
const createExpoWebpackConfigAsync = require('@expo/webpack-config')
|
||||
const webpack = require('webpack')
|
||||
const {withAlias} = require('@expo/webpack-config/addons')
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
|
||||
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
|
||||
@@ -65,6 +66,13 @@ module.exports = async function (env, argv) {
|
||||
'react-native-webview': 'react-native-web-webview',
|
||||
'react-native-gesture-handler': false, // RNGH should not be used on web, so let's cause a build error if it sneaks in
|
||||
'@sentry-internal/replay': false, // not used, ~300kb of dead weight
|
||||
/*
|
||||
* @sentry/react-native's tracing integration probes for expo-router via a
|
||||
* try/catch require(). We don't use expo-router, so the module can't
|
||||
* resolve and webpack warns on every build. Stubbing it to an empty
|
||||
* module makes the probe return null (`mod?.store ?? null`) silently.
|
||||
*/
|
||||
'expo-router/build/global-state/router-store': false,
|
||||
/*
|
||||
* react-native-svg's fetchData util imports the ~55KB `buffer` polyfill,
|
||||
* but is only needed by SvgUri/SvgXml remote loading, which we don't use.
|
||||
@@ -76,8 +84,37 @@ module.exports = async function (env, argv) {
|
||||
__dirname,
|
||||
'node_modules/react-native-svg/lib/module/utils/fetchData',
|
||||
)]: false,
|
||||
/*
|
||||
* reanimated's webUtils.web.js mixes ESM exports with bare CommonJS
|
||||
* require() calls in try/catch, which webpack leaves untranspiled - they
|
||||
* throw at runtime and createReactDOMStyle & co. silently stay undefined,
|
||||
* making _updatePropsJS crash on every animated style update. The shim
|
||||
* imports the same react-native-web internals statically. See the shim
|
||||
* file for details.
|
||||
*/
|
||||
[path.join(
|
||||
__dirname,
|
||||
'node_modules/react-native-reanimated/lib/module/ReanimatedModule/js-reanimated/webUtils',
|
||||
)]: path.join(__dirname, 'web/reanimatedWebUtilsShim.js'),
|
||||
})
|
||||
|
||||
/*
|
||||
* expo-font's serverContext.web.js imports `node:async_hooks` for SSR-only
|
||||
* font collection, but webpack can't resolve `node:` URIs for web targets.
|
||||
* Every call site is guarded by `typeof window === 'undefined'`, so in the
|
||||
* browser bundle the module is dead code - strip the scheme prefix and stub
|
||||
* the builtin out with an empty module.
|
||||
*/
|
||||
config.plugins.push(
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/^node:async_hooks$/,
|
||||
resource => {
|
||||
resource.request = 'async_hooks'
|
||||
},
|
||||
),
|
||||
)
|
||||
config.resolve.fallback = {...config.resolve.fallback, async_hooks: false}
|
||||
|
||||
// react-native-uuid ships sourceMappingURL comments but no .map files.
|
||||
patchSourceMapFilter(config.module.rules, /react-native-uuid/)
|
||||
config.module.rules = [
|
||||
|
||||
Reference in New Issue
Block a user