mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
95
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d71ba2da34 | ||
|
|
9c18ad85c7 | ||
|
|
e1b175f137 | ||
|
|
9cf1918d0d | ||
|
|
ca17733e2f | ||
|
|
0471f9b70f | ||
|
|
935d90023e | ||
|
|
eca45c28c3 | ||
|
|
ab3fe91e9e | ||
|
|
e217b7f513 | ||
|
|
2bba1e325b | ||
|
|
1ea2c9bad9 | ||
|
|
166cc09d75 | ||
|
|
9d5ca3647a | ||
|
|
8d6d34644a | ||
|
|
9cb7b44d4c | ||
|
|
5d7c34c3c4 | ||
|
|
138cb9187e | ||
|
|
d9d7286e8a | ||
|
|
a0cb168aa0 | ||
|
|
840db2122a | ||
|
|
49d2009add | ||
|
|
738e01430a | ||
|
|
8de71e58b6 | ||
|
|
34d7d016b4 | ||
|
|
bbecf516c0 | ||
|
|
1c09228161 | ||
|
|
fed80ad8c3 | ||
|
|
2fcda359ba | ||
|
|
d7af544bb9 | ||
|
|
dd89bfb254 | ||
|
|
414c015da3 | ||
|
|
c1c8a30cc8 | ||
|
|
bab46dd321 | ||
|
|
37abba0193 | ||
|
|
08fc618b77 | ||
|
|
c068efcdae | ||
|
|
51de9e1ad6 | ||
|
|
30b5de04d6 | ||
|
|
f3b71f4843 | ||
|
|
c92bb3c526 | ||
|
|
921bbb6cf1 | ||
|
|
6d6b6a8345 | ||
|
|
614e4109bf | ||
|
|
d3c94b42f4 | ||
|
|
e795b534aa | ||
|
|
68cd44be07 | ||
|
|
a469927c16 | ||
|
|
c5f18de6a2 | ||
|
|
1199e4764d | ||
|
|
cb30323f04 | ||
|
|
5299c60bdc | ||
|
|
341cda45ee | ||
|
|
0026f73360 | ||
|
|
392a843494 | ||
|
|
8beed6ad08 | ||
|
|
a9ec217117 | ||
|
|
d0699dc428 | ||
|
|
73837a72f6 | ||
|
|
8a493af0b9 | ||
|
|
7b2cff831e | ||
|
|
6070b58a96 | ||
|
|
3e079f0802 | ||
|
|
18137348ee | ||
|
|
14794a9946 | ||
|
|
eae08663fe | ||
|
|
1ebe10a4e5 | ||
|
|
1fc7693205 | ||
|
|
4e24223550 | ||
|
|
536ee8ba4c | ||
|
|
3447fea075 | ||
|
|
ea09a4701d | ||
|
|
cffdad69c3 | ||
|
|
a0a81a400b | ||
|
|
b6291aacf8 | ||
|
|
7700d7fd78 | ||
|
|
2443677fe0 | ||
|
|
7c9f3ab437 | ||
|
|
f5fbda5e46 | ||
|
|
05ba16aa3d | ||
|
|
9ffe82edd3 | ||
|
|
6e04b2a717 | ||
|
|
fecfd04d89 | ||
|
|
a4e390a188 | ||
|
|
17a96158aa | ||
|
|
73f37b14d6 | ||
|
|
aff2e93576 | ||
|
|
8a5ddb92f1 | ||
|
|
f9b3835f1e | ||
|
|
2c6edee68e | ||
|
|
f40f8f9ad2 | ||
|
|
c05d19102c | ||
|
|
dad96696b7 | ||
|
|
6d2805bf85 | ||
|
|
c2b182f9f5 |
@@ -37,11 +37,6 @@ ADD scripts /app/scripts
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN scripts/buck/buck_fetch.sh
|
||||
|
||||
RUN buck build packages/react-native/ReactAndroid/src/main/java/com/facebook/react
|
||||
RUN buck build packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell
|
||||
|
||||
ADD . /app
|
||||
|
||||
RUN yarn
|
||||
|
||||
+2
-57
@@ -46,7 +46,7 @@ references:
|
||||
# Dependency Anchors
|
||||
# -------------------------
|
||||
dependency_versions:
|
||||
xcode_version: &xcode_version "14.2.0"
|
||||
xcode_version: &xcode_version "14.3.0"
|
||||
nodelts_image: &nodelts_image "cimg/node:18.12.1"
|
||||
nodeprevlts_image: &nodeprevlts_image "cimg/node:16.18.1"
|
||||
|
||||
@@ -620,7 +620,7 @@ jobs:
|
||||
- run:
|
||||
name: Run Ruby Tests
|
||||
command: |
|
||||
cd scripts
|
||||
cd packages/react-native/scripts
|
||||
sh run_ruby_tests.sh
|
||||
- run_yarn
|
||||
- *attach_hermes_workspace
|
||||
@@ -719,60 +719,6 @@ jobs:
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Test Buck
|
||||
# -------------------------
|
||||
test_buck:
|
||||
executor: reactnativeandroid
|
||||
environment:
|
||||
KOTLIN_HOME=packages/react-native/third-party/kotlin
|
||||
steps:
|
||||
- checkout
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
|
||||
- run:
|
||||
name: Download Dependencies Using Buck
|
||||
command: ./scripts/buck/buck_fetch.sh
|
||||
|
||||
- run:
|
||||
name: Build & Test React Native using Buck
|
||||
command: |
|
||||
buck build packages/react-native/ReactAndroid/src/main/java/com/facebook/react
|
||||
buck build packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell
|
||||
|
||||
- run:
|
||||
name: Run Tests - Android Unit Tests with Buck
|
||||
command: buck test packages/react-native/ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS --xml ./reports/buck/all-results-raw.xml
|
||||
|
||||
- run:
|
||||
name: Build JavaScript Bundle for instrumentation tests
|
||||
working_directory: ~/react-native/packages/react-native
|
||||
command: node cli.js bundle --max-workers 2 --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
|
||||
|
||||
- run:
|
||||
name: Build Tests - Android Instrumentation Tests with Buck
|
||||
# Here, just build the instrumentation tests. There is a known issue with installing the APK to android-21+ emulator.
|
||||
command: |
|
||||
if [[ ! -e packages/react-native/ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then
|
||||
echo "JavaScript bundle missing, cannot run instrumentation tests. Verify Build JavaScript Bundle step completed successfully."; exit 1;
|
||||
fi
|
||||
source scripts/android-setup.sh && NO_BUCKD=1 scripts/retry3 timeout 300 buck build packages/react-native/ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS
|
||||
|
||||
- run:
|
||||
name: Collect Test Results
|
||||
command: |
|
||||
find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ./reports/build/ \;
|
||||
find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ./reports/outputs/ \;
|
||||
find . -type f -regex ".*/buck-out/gen/packages/react-native/ReactAndroid/src/test/.*/.*xml" -exec cp {} ./reports/buck/ \;
|
||||
if [ -f ~/react-native/reports/buck/all-results-raw.xml ]; then
|
||||
~/react-native/scripts/circleci/buckToJunit/buckToJunit.sh ~/react-native/reports/buck/all-results-raw.xml ~/react-native/reports/junit/results.xml
|
||||
fi
|
||||
when: always
|
||||
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Test Android
|
||||
# -------------------------
|
||||
@@ -1694,7 +1640,6 @@ workflows:
|
||||
architecture: ["NewArch", "OldArch"]
|
||||
jsengine: ["Hermes", "JSC"]
|
||||
flavor: ["Debug", "Release"]
|
||||
- test_buck
|
||||
- test_ios_template:
|
||||
requires:
|
||||
- build_npm_package
|
||||
|
||||
+8
-8
@@ -3,22 +3,22 @@ GEM
|
||||
specs:
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
activesupport (7.0.4.2)
|
||||
activesupport (7.0.4.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.1)
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
atomos (0.1.3)
|
||||
claide (1.1.0)
|
||||
cocoapods (1.12.0)
|
||||
cocoapods (1.12.1)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.12.0)
|
||||
cocoapods-core (= 1.12.1)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.6.0, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
@@ -33,7 +33,7 @@ GEM
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 2.3.0, < 3.0)
|
||||
xcodeproj (>= 1.21.0, < 2.0)
|
||||
cocoapods-core (1.12.0)
|
||||
cocoapods-core (1.12.1)
|
||||
activesupport (>= 5.0, < 8)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
@@ -62,7 +62,7 @@ GEM
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.12.0)
|
||||
i18n (1.13.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.6.3)
|
||||
minitest (5.18.0)
|
||||
@@ -93,7 +93,7 @@ DEPENDENCIES
|
||||
cocoapods (~> 1.12)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.2.0p0
|
||||
ruby 3.0.5p211
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.7
|
||||
2.4.10
|
||||
|
||||
+6
-6
@@ -40,8 +40,7 @@
|
||||
"test-ios": "./scripts/objc-test.sh test",
|
||||
"test-typescript": "dtslint packages/react-native/types",
|
||||
"test-typescript-offline": "dtslint --localTs node_modules/typescript/lib packages/react-native/types",
|
||||
"bump-all-updated-packages": "node ./scripts/monorepo/bump-all-updated-packages",
|
||||
"align-package-versions": "node ./scripts/monorepo/align-package-versions.js"
|
||||
"bump-all-updated-packages": "node ./scripts/monorepo/bump-all-updated-packages"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
@@ -57,10 +56,11 @@
|
||||
"@definitelytyped/dtslint": "^0.0.127",
|
||||
"@jest/create-cache-key-function": "^29.2.1",
|
||||
"@reactions/component": "^2.0.2",
|
||||
"@react-native/metro-config": "^0.72.5",
|
||||
"@types/react": "^18.0.18",
|
||||
"@typescript-eslint/parser": "^5.30.5",
|
||||
"async": "^3.2.2",
|
||||
"babel-plugin-transform-flow-enums":"^0.0.2",
|
||||
"babel-plugin-transform-flow-enums": "^0.0.2",
|
||||
"clang-format": "^1.8.0",
|
||||
"connect": "^3.6.5",
|
||||
"coveralls": "^3.1.1",
|
||||
@@ -84,9 +84,9 @@
|
||||
"jest": "^29.2.1",
|
||||
"jest-junit": "^10.0.0",
|
||||
"jscodeshift": "^0.14.0",
|
||||
"metro-babel-register": "0.75.1",
|
||||
"metro-memory-fs": "0.75.1",
|
||||
"metro-react-native-babel-transformer": "0.75.1",
|
||||
"metro-babel-register": "0.76.4",
|
||||
"metro-memory-fs": "0.76.4",
|
||||
"metro-react-native-babel-transformer": "0.76.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mock-fs": "^5.1.4",
|
||||
"prettier": "^2.4.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/eslint-plugin-specs",
|
||||
"version": "0.72.2",
|
||||
"version": "0.72.3",
|
||||
"description": "ESLint rules to validate NativeModule and Component Specs",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @noformat
|
||||
*/
|
||||
|
||||
/*:: import type {MetroConfig} from 'metro-config'; */
|
||||
|
||||
const {mergeConfig} = require('metro-config');
|
||||
|
||||
const INTERNAL_CALLSITES_REGEX = new RegExp(
|
||||
[
|
||||
'/Libraries/Renderer/implementations/.+\\.js$',
|
||||
'/Libraries/BatchedBridge/MessageQueue\\.js$',
|
||||
'/Libraries/YellowBox/.+\\.js$',
|
||||
'/Libraries/LogBox/.+\\.js$',
|
||||
'/Libraries/Core/Timers/.+\\.js$',
|
||||
'/Libraries/WebSocket/.+\\.js$',
|
||||
'/Libraries/vendor/.+\\.js$',
|
||||
'/node_modules/react-devtools-core/.+\\.js$',
|
||||
'/node_modules/react-refresh/.+\\.js$',
|
||||
'/node_modules/scheduler/.+\\.js$',
|
||||
'/node_modules/event-target-shim/.+\\.js$',
|
||||
'/node_modules/invariant/.+\\.js$',
|
||||
'/node_modules/react-native/index.js$',
|
||||
'/metro-runtime/.+\\.js$',
|
||||
'^\\[native code\\]$',
|
||||
].join('|'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the base Metro configuration for a React Native project.
|
||||
*/
|
||||
|
||||
function getDefaultConfig(
|
||||
projectRoot /*: string */
|
||||
) /*: MetroConfig */ {
|
||||
return {
|
||||
resolver: {
|
||||
resolverMainFields: ['react-native', 'browser', 'main'],
|
||||
platforms: ['android', 'ios'],
|
||||
unstable_conditionNames: ['require', 'import', 'react-native'],
|
||||
},
|
||||
serializer: {
|
||||
getPolyfills: () => require('@react-native/js-polyfills')(),
|
||||
},
|
||||
server: {
|
||||
port: Number(process.env.RCT_METRO_PORT) || 8081,
|
||||
},
|
||||
symbolicator: {
|
||||
customizeFrame: (frame /*: $ReadOnly<{file: ?string, ...}>*/) => {
|
||||
const collapse = Boolean(
|
||||
frame.file && INTERNAL_CALLSITES_REGEX.test(frame.file),
|
||||
);
|
||||
return {collapse};
|
||||
},
|
||||
},
|
||||
transformer: {
|
||||
allowOptionalDependencies: true,
|
||||
assetRegistryPath: 'react-native/Libraries/Image/AssetRegistry',
|
||||
asyncRequireModulePath: require.resolve(
|
||||
'metro-runtime/src/modules/asyncRequire',
|
||||
),
|
||||
babelTransformerPath: require.resolve(
|
||||
'metro-react-native-babel-transformer',
|
||||
),
|
||||
getTransformOptions: async () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: false,
|
||||
inlineRequires: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
watchFolders: [],
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {getDefaultConfig, mergeConfig};
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@react-native/metro-config",
|
||||
"version": "0.72.5",
|
||||
"description": "Metro configuration for React Native.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:facebook/react-native.git",
|
||||
"directory": "packages/metro-config"
|
||||
},
|
||||
"license": "MIT",
|
||||
"exports": "./index.js",
|
||||
"dependencies": {
|
||||
"@react-native/js-polyfills": "^0.72.1",
|
||||
"metro-config": "0.76.4",
|
||||
"metro-react-native-babel-transformer": "0.76.4",
|
||||
"metro-runtime": "0.76.4"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/codegen",
|
||||
"version": "0.72.3",
|
||||
"version": "0.72.4",
|
||||
"description": "⚛️ Code generation tools for React Native",
|
||||
"homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/react-native-codegen",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/gradle-plugin",
|
||||
"version": "0.72.5",
|
||||
"version": "0.72.7",
|
||||
"description": "⚛️ Gradle Plugin for React Native",
|
||||
"homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/react-native-gradle-plugin",
|
||||
"repository": {
|
||||
|
||||
+5
-3
@@ -18,7 +18,7 @@ import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts
|
||||
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
|
||||
import com.facebook.react.utils.DependencyUtils.configureDependencies
|
||||
import com.facebook.react.utils.DependencyUtils.configureRepositories
|
||||
import com.facebook.react.utils.DependencyUtils.readVersionString
|
||||
import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings
|
||||
import com.facebook.react.utils.JsonUtils
|
||||
import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk
|
||||
import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson
|
||||
@@ -54,8 +54,10 @@ class ReactPlugin : Plugin<Project> {
|
||||
project.afterEvaluate {
|
||||
val reactNativeDir = extension.reactNativeDir.get().asFile
|
||||
val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")
|
||||
val versionString = readVersionString(propertiesFile)
|
||||
configureDependencies(project, versionString)
|
||||
val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile)
|
||||
val versionString = versionAndGroupStrings.first
|
||||
val groupString = versionAndGroupStrings.second
|
||||
configureDependencies(project, versionString, groupString)
|
||||
configureRepositories(project, reactNativeDir)
|
||||
}
|
||||
|
||||
|
||||
+22
-12
@@ -13,6 +13,8 @@ import java.util.*
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
|
||||
|
||||
internal const val DEFAULT_GROUP_STRING = "com.facebook.react"
|
||||
|
||||
internal object DependencyUtils {
|
||||
|
||||
/**
|
||||
@@ -46,7 +48,11 @@ internal object DependencyUtils {
|
||||
* - Forcing the react-android/hermes-android version to the one specified in the package.json
|
||||
* - Substituting `react-native` with `react-android` and `hermes-engine` with `hermes-android`.
|
||||
*/
|
||||
fun configureDependencies(project: Project, versionString: String) {
|
||||
fun configureDependencies(
|
||||
project: Project,
|
||||
versionString: String,
|
||||
groupString: String = DEFAULT_GROUP_STRING
|
||||
) {
|
||||
if (versionString.isBlank()) return
|
||||
project.rootProject.allprojects { eachProject ->
|
||||
eachProject.configurations.all { configuration ->
|
||||
@@ -56,32 +62,36 @@ internal object DependencyUtils {
|
||||
// implementation("com.facebook.react:react-native:+") and resolve the right dependency.
|
||||
configuration.resolutionStrategy.dependencySubstitution {
|
||||
it.substitute(it.module("com.facebook.react:react-native"))
|
||||
.using(it.module("com.facebook.react:react-android:${versionString}"))
|
||||
.using(it.module("${groupString}:react-android:${versionString}"))
|
||||
.because(
|
||||
"The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210.")
|
||||
it.substitute(it.module("com.facebook.react:hermes-engine"))
|
||||
.using(it.module("com.facebook.react:hermes-android:${versionString}"))
|
||||
.using(it.module("${groupString}:hermes-android:${versionString}"))
|
||||
.because(
|
||||
"The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210.")
|
||||
}
|
||||
configuration.resolutionStrategy.force(
|
||||
"com.facebook.react:react-android:${versionString}",
|
||||
"com.facebook.react:hermes-android:${versionString}",
|
||||
"${groupString}:react-android:${versionString}",
|
||||
"${groupString}:hermes-android:${versionString}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun readVersionString(propertiesFile: File): String {
|
||||
fun readVersionAndGroupStrings(propertiesFile: File): Pair<String, String> {
|
||||
val reactAndroidProperties = Properties()
|
||||
propertiesFile.inputStream().use { reactAndroidProperties.load(it) }
|
||||
val versionString = reactAndroidProperties["VERSION_NAME"] as? String ?: ""
|
||||
val versionStringFromFile = reactAndroidProperties["VERSION_NAME"] as? String ?: ""
|
||||
// If on a nightly, we need to fetch the -SNAPSHOT artifact from Sonatype.
|
||||
return if (versionString.startsWith("0.0.0")) {
|
||||
"$versionString-SNAPSHOT"
|
||||
} else {
|
||||
versionString
|
||||
}
|
||||
val versionString =
|
||||
if (versionStringFromFile.startsWith("0.0.0")) {
|
||||
"$versionStringFromFile-SNAPSHOT"
|
||||
} else {
|
||||
versionStringFromFile
|
||||
}
|
||||
// Returns Maven group for repos using different group for Maven artifacts
|
||||
val groupString = reactAndroidProperties["GROUP"] as? String ?: DEFAULT_GROUP_STRING
|
||||
return Pair(versionString, groupString)
|
||||
}
|
||||
|
||||
fun Project.mavenRepoFromUrl(url: String): MavenArtifactRepository =
|
||||
|
||||
+56
-5
@@ -12,7 +12,7 @@ import com.facebook.react.utils.DependencyUtils.configureDependencies
|
||||
import com.facebook.react.utils.DependencyUtils.configureRepositories
|
||||
import com.facebook.react.utils.DependencyUtils.mavenRepoFromURI
|
||||
import com.facebook.react.utils.DependencyUtils.mavenRepoFromUrl
|
||||
import com.facebook.react.utils.DependencyUtils.readVersionString
|
||||
import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings
|
||||
import java.net.URI
|
||||
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
|
||||
import org.gradle.testfixtures.ProjectBuilder
|
||||
@@ -226,6 +226,24 @@ class DependencyUtilsTest {
|
||||
assertTrue(libForcedModules.any { it.toString() == "com.facebook.react:hermes-android:1.2.3" })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun configureDependencies_withVersionStringAndGroupString_appliesOnAllProjects() {
|
||||
val rootProject = ProjectBuilder.builder().build()
|
||||
val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build()
|
||||
val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build()
|
||||
appProject.plugins.apply("com.android.application")
|
||||
libProject.plugins.apply("com.android.library")
|
||||
|
||||
configureDependencies(appProject, "1.2.3", "io.github.test")
|
||||
|
||||
val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules
|
||||
val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules
|
||||
assertTrue(appForcedModules.any { it.toString() == "io.github.test:react-android:1.2.3" })
|
||||
assertTrue(appForcedModules.any { it.toString() == "io.github.test:hermes-android:1.2.3" })
|
||||
assertTrue(libForcedModules.any { it.toString() == "io.github.test:react-android:1.2.3" })
|
||||
assertTrue(libForcedModules.any { it.toString() == "io.github.test:hermes-android:1.2.3" })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readVersionString_withCorrectVersionString_returnsIt() {
|
||||
val propertiesFile =
|
||||
@@ -238,7 +256,7 @@ class DependencyUtilsTest {
|
||||
.trimIndent())
|
||||
}
|
||||
|
||||
val versionString = readVersionString(propertiesFile)
|
||||
val versionString = readVersionAndGroupStrings(propertiesFile).first
|
||||
|
||||
assertEquals("1000.0.0", versionString)
|
||||
}
|
||||
@@ -255,7 +273,7 @@ class DependencyUtilsTest {
|
||||
.trimIndent())
|
||||
}
|
||||
|
||||
val versionString = readVersionString(propertiesFile)
|
||||
val versionString = readVersionAndGroupStrings(propertiesFile).first
|
||||
|
||||
assertEquals("0.0.0-20221101-2019-cfe811ab1-SNAPSHOT", versionString)
|
||||
}
|
||||
@@ -271,7 +289,7 @@ class DependencyUtilsTest {
|
||||
.trimIndent())
|
||||
}
|
||||
|
||||
val versionString = readVersionString(propertiesFile)
|
||||
val versionString = readVersionAndGroupStrings(propertiesFile).first
|
||||
assertEquals("", versionString)
|
||||
}
|
||||
|
||||
@@ -287,10 +305,43 @@ class DependencyUtilsTest {
|
||||
.trimIndent())
|
||||
}
|
||||
|
||||
val versionString = readVersionString(propertiesFile)
|
||||
val versionString = readVersionAndGroupStrings(propertiesFile).first
|
||||
assertEquals("", versionString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readGroupString_withCorrectGroupString_returnsIt() {
|
||||
val propertiesFile =
|
||||
tempFolder.newFile("gradle.properties").apply {
|
||||
writeText(
|
||||
"""
|
||||
GROUP=io.github.test
|
||||
ANOTHER_PROPERTY=true
|
||||
"""
|
||||
.trimIndent())
|
||||
}
|
||||
|
||||
val groupString = readVersionAndGroupStrings(propertiesFile).second
|
||||
|
||||
assertEquals("io.github.test", groupString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readGroupString_withEmptyGroupString_returnsDefault() {
|
||||
val propertiesFile =
|
||||
tempFolder.newFile("gradle.properties").apply {
|
||||
writeText(
|
||||
"""
|
||||
ANOTHER_PROPERTY=true
|
||||
"""
|
||||
.trimIndent())
|
||||
}
|
||||
|
||||
val groupString = readVersionAndGroupStrings(propertiesFile).second
|
||||
|
||||
assertEquals("com.facebook.react", groupString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mavenRepoFromUrl_worksCorrectly() {
|
||||
val process = createProject()
|
||||
|
||||
@@ -46,13 +46,6 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
|
||||
flexWrap: true,
|
||||
gap: true,
|
||||
height: true,
|
||||
inset: true,
|
||||
insetBlock: true,
|
||||
insetBlockEnd: true,
|
||||
insetBlockStart: true,
|
||||
insetInline: true,
|
||||
insetInlineEnd: true,
|
||||
insetInlineStart: true,
|
||||
justifyContent: true,
|
||||
left: true,
|
||||
margin: true,
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
*/
|
||||
|
||||
exports.version = {
|
||||
major: 1000,
|
||||
minor: 0,
|
||||
major: 0,
|
||||
minor: 72,
|
||||
patch: 0,
|
||||
prerelease: null,
|
||||
prerelease: 'rc.3',
|
||||
};
|
||||
|
||||
@@ -39,8 +39,12 @@ exports.checkVersions = function checkVersions(): void {
|
||||
}
|
||||
};
|
||||
|
||||
// Note: in OSS, the prerelease version is usually 0.Y.0-rc.W, so it is a string and not a number
|
||||
// Then we need to keep supporting that object shape.
|
||||
function _formatVersion(
|
||||
version: (typeof Platform)['constants']['reactNativeVersion'],
|
||||
version:
|
||||
| (typeof Platform)['constants']['reactNativeVersion']
|
||||
| {major: number, minor: number, patch: number, prerelease: ?string},
|
||||
): string {
|
||||
return (
|
||||
`${version.major}.${version.minor}.${version.patch}` +
|
||||
|
||||
@@ -258,14 +258,6 @@ const validAttributesForNonEventProps = {
|
||||
top: true,
|
||||
bottom: true,
|
||||
|
||||
inset: true,
|
||||
insetBlock: true,
|
||||
insetBlockEnd: true,
|
||||
insetBlockStart: true,
|
||||
insetInline: true,
|
||||
insetInlineEnd: true,
|
||||
insetInlineStart: true,
|
||||
|
||||
position: true,
|
||||
|
||||
style: ReactNativeStyleAttributes,
|
||||
|
||||
@@ -236,14 +236,6 @@ const validAttributesForNonEventProps = {
|
||||
bottom: true,
|
||||
left: true,
|
||||
|
||||
inset: true,
|
||||
insetBlock: true,
|
||||
insetBlockEnd: true,
|
||||
insetBlockStart: true,
|
||||
insetInline: true,
|
||||
insetInlineEnd: true,
|
||||
insetInlineStart: true,
|
||||
|
||||
width: true,
|
||||
height: true,
|
||||
|
||||
|
||||
+27
-1
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import type {RootTag} from '../Types/RootTagTypes';
|
||||
import type {Spec as FabricUIManagerSpec} from './FabricUIManager';
|
||||
import type {Spec} from './NativeUIManager';
|
||||
|
||||
import {getFabricUIManager} from './FabricUIManager';
|
||||
@@ -175,6 +174,33 @@ const UIManager = {
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
dispatchViewManagerCommand(
|
||||
reactTag: number,
|
||||
commandName: number | string,
|
||||
commandArgs: any[],
|
||||
) {
|
||||
if (isFabricReactTag(reactTag)) {
|
||||
const FabricUIManager = nullthrows(getFabricUIManager());
|
||||
const shadowNode =
|
||||
FabricUIManager.findShadowNodeByTag_DEPRECATED(reactTag);
|
||||
if (shadowNode) {
|
||||
// Transform the accidental CommandID into a CommandName which is the stringified number.
|
||||
// The interop layer knows how to convert this number into the right method name.
|
||||
// Stringify a string is a no-op, so it's safe.
|
||||
commandName = `${commandName}`;
|
||||
FabricUIManager.dispatchCommand(shadowNode, commandName, commandArgs);
|
||||
}
|
||||
} else {
|
||||
UIManagerImpl.dispatchViewManagerCommand(
|
||||
reactTag,
|
||||
// We have some legacy components that are actually already using strings. ¯\_(ツ)_/¯
|
||||
// $FlowFixMe[incompatible-call]
|
||||
commandName,
|
||||
commandArgs,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = UIManager;
|
||||
|
||||
+26
-3
@@ -3419,6 +3419,23 @@ function mountSafeCallback_NOT_REALLY_SAFE(context, callback) {
|
||||
return callback.apply(context, arguments);
|
||||
};
|
||||
}
|
||||
function warnForStyleProps(props, validAttributes) {
|
||||
{
|
||||
for (var key in validAttributes.style) {
|
||||
if (!(validAttributes[key] || props[key] === undefined)) {
|
||||
error(
|
||||
"You are setting the style `{ %s" +
|
||||
": ... }` as a prop. You " +
|
||||
"should nest it in a style object. " +
|
||||
"E.g. `{ style: { %s" +
|
||||
": ... } }`",
|
||||
key,
|
||||
key
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modules provided by RN:
|
||||
var emptyObject = {};
|
||||
@@ -5100,7 +5117,8 @@ var _nativeFabricUIManage = nativeFabricUIManager,
|
||||
FabricDefaultPriority = _nativeFabricUIManage.unstable_DefaultEventPriority,
|
||||
FabricDiscretePriority = _nativeFabricUIManage.unstable_DiscreteEventPriority,
|
||||
fabricGetCurrentEventPriority =
|
||||
_nativeFabricUIManage.unstable_getCurrentEventPriority;
|
||||
_nativeFabricUIManage.unstable_getCurrentEventPriority,
|
||||
_setNativeProps = _nativeFabricUIManage.setNativeProps;
|
||||
var getViewConfigForType =
|
||||
ReactNativePrivateInterface.ReactNativeViewConfigRegistry.get; // Counter for uniquely identifying views.
|
||||
// % 10 === 1 means it is a rootTag.
|
||||
@@ -5199,10 +5217,15 @@ var ReactFabricHostComponent = /*#__PURE__*/ (function() {
|
||||
|
||||
_proto.setNativeProps = function setNativeProps(nativeProps) {
|
||||
{
|
||||
error("Warning: setNativeProps is not currently supported in Fabric");
|
||||
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
|
||||
}
|
||||
|
||||
return;
|
||||
var updatePayload = create(nativeProps, this.viewConfig.validAttributes);
|
||||
var stateNode = this._internalInstanceHandle.stateNode;
|
||||
|
||||
if (stateNode != null && updatePayload != null) {
|
||||
_setNativeProps(stateNode.node, updatePayload);
|
||||
}
|
||||
}; // This API (addEventListener, removeEventListener) attempts to adhere to the
|
||||
// w3 Level2 Events spec as much as possible, treating HostComponent as a DOM node.
|
||||
//
|
||||
|
||||
+13
-1
@@ -1922,6 +1922,7 @@ var _nativeFabricUIManage = nativeFabricUIManager,
|
||||
FabricDiscretePriority = _nativeFabricUIManage.unstable_DiscreteEventPriority,
|
||||
fabricGetCurrentEventPriority =
|
||||
_nativeFabricUIManage.unstable_getCurrentEventPriority,
|
||||
_setNativeProps = _nativeFabricUIManage.setNativeProps,
|
||||
getViewConfigForType =
|
||||
ReactNativePrivateInterface.ReactNativeViewConfigRegistry.get,
|
||||
nextReactTag = 2;
|
||||
@@ -1979,7 +1980,18 @@ var ReactFabricHostComponent = (function() {
|
||||
);
|
||||
}
|
||||
};
|
||||
_proto.setNativeProps = function() {};
|
||||
_proto.setNativeProps = function(nativeProps) {
|
||||
nativeProps = diffProperties(
|
||||
null,
|
||||
emptyObject,
|
||||
nativeProps,
|
||||
this.viewConfig.validAttributes
|
||||
);
|
||||
var stateNode = this._internalInstanceHandle.stateNode;
|
||||
null != stateNode &&
|
||||
null != nativeProps &&
|
||||
_setNativeProps(stateNode.node, nativeProps);
|
||||
};
|
||||
_proto.addEventListener_unstable = function(eventType, listener, options) {
|
||||
if ("string" !== typeof eventType)
|
||||
throw Error("addEventListener_unstable eventType must be a string");
|
||||
|
||||
+13
-1
@@ -1981,6 +1981,7 @@ var _nativeFabricUIManage = nativeFabricUIManager,
|
||||
FabricDiscretePriority = _nativeFabricUIManage.unstable_DiscreteEventPriority,
|
||||
fabricGetCurrentEventPriority =
|
||||
_nativeFabricUIManage.unstable_getCurrentEventPriority,
|
||||
_setNativeProps = _nativeFabricUIManage.setNativeProps,
|
||||
getViewConfigForType =
|
||||
ReactNativePrivateInterface.ReactNativeViewConfigRegistry.get,
|
||||
nextReactTag = 2;
|
||||
@@ -2038,7 +2039,18 @@ var ReactFabricHostComponent = (function() {
|
||||
);
|
||||
}
|
||||
};
|
||||
_proto.setNativeProps = function() {};
|
||||
_proto.setNativeProps = function(nativeProps) {
|
||||
nativeProps = diffProperties(
|
||||
null,
|
||||
emptyObject,
|
||||
nativeProps,
|
||||
this.viewConfig.validAttributes
|
||||
);
|
||||
var stateNode = this._internalInstanceHandle.stateNode;
|
||||
null != stateNode &&
|
||||
null != nativeProps &&
|
||||
_setNativeProps(stateNode.node, nativeProps);
|
||||
};
|
||||
_proto.addEventListener_unstable = function(eventType, listener, options) {
|
||||
if ("string" !== typeof eventType)
|
||||
throw Error("addEventListener_unstable eventType must be a string");
|
||||
|
||||
@@ -69,13 +69,6 @@ export interface FlexStyle {
|
||||
flexShrink?: number | undefined;
|
||||
flexWrap?: 'wrap' | 'nowrap' | 'wrap-reverse' | undefined;
|
||||
height?: DimensionValue | undefined;
|
||||
inset?: DimensionValue | undefined;
|
||||
insetBlock?: DimensionValue | undefined;
|
||||
insetBlockEnd?: DimensionValue | undefined;
|
||||
insetBlockStart?: DimensionValue | undefined;
|
||||
insetInline?: DimensionValue | undefined;
|
||||
insetInlineEnd?: DimensionValue | undefined;
|
||||
insetInlineStart?: DimensionValue | undefined;
|
||||
justifyContent?:
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
|
||||
@@ -134,80 +134,6 @@ type ____LayoutStyle_Internal = $ReadOnly<{
|
||||
*/
|
||||
top?: DimensionValue,
|
||||
|
||||
/** `inset` is a shorthand that corresponds to the top, right, bottom, and/or left properties.
|
||||
*
|
||||
* It works similarly to `inset` in CSS, but in React Native you
|
||||
* must use points or percentages. Ems and other units are not supported.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset
|
||||
* for more details of how `inset` affects layout.
|
||||
*/
|
||||
inset?: DimensionValue,
|
||||
|
||||
/** `insetBlock` is a shorthand that corresponds to the `insetBlockStart` and `insetBlockEnd` properties.
|
||||
*
|
||||
* It works similarly to `inset-block` in CSS, but in React Native you
|
||||
* must use points or percentages. Ems and other units are not supported.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-block
|
||||
* for more details of how `inset-block` affects layout.
|
||||
*/
|
||||
insetBlock?: DimensionValue,
|
||||
|
||||
/** `insetBlockEnd` is a logical property that sets the length that an
|
||||
* element is offset in the block direction from its ending edge.
|
||||
*
|
||||
* It works similarly to `inset-block-end` in CSS, but in React Native you
|
||||
* must use points or percentages. Ems and other units are not supported.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-block-end
|
||||
* for more details of how `inset-block-end` affects layout.
|
||||
*/
|
||||
insetBlockEnd?: DimensionValue,
|
||||
|
||||
/** `insetBlockStart` is a logical property that sets the length that an
|
||||
* element is offset in the block direction from its starting edge.
|
||||
*
|
||||
* It works similarly to `inset-block-start` in CSS, but in React Native you
|
||||
* must use points or percentages. Ems and other units are not supported.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-block-start
|
||||
* for more details of how `inset-block-start` affects layout.
|
||||
*/
|
||||
insetBlockStart?: DimensionValue,
|
||||
|
||||
/** `insetInline` is a shorthand that corresponds to the `insetInlineStart` and `insetInlineEnd` properties.
|
||||
*
|
||||
* It works similarly to `inset-inline` in CSS, but in React Native you
|
||||
* must use points or percentages. Ems and other units are not supported.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-inline
|
||||
* for more details of how `inset-inline` affects layout.
|
||||
*/
|
||||
insetInline?: DimensionValue,
|
||||
|
||||
/** `insetInlineEnd` is a logical property that sets the length that an
|
||||
* element is offset in the starting inline direction.
|
||||
*
|
||||
* It works similarly to `inset-inline-end` in CSS, but in React Native you
|
||||
* must use points or percentages. Ems and other units are not supported.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-inline-end
|
||||
* for more details of how `inset-inline-end` affects layout.
|
||||
*/
|
||||
insetInlineEnd?: DimensionValue,
|
||||
|
||||
/** `insetInlineStart` is a logical property that sets the length that an
|
||||
* element is offset in the starting inline direction.
|
||||
*
|
||||
* It works similarly to `inset-inline-start` in CSS, but in React Native you
|
||||
* must use points or percentages. Ems and other units are not supported.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-inline-start
|
||||
* for more details of how `inset-inline-start` affects layout.
|
||||
*/
|
||||
insetInlineStart?: DimensionValue,
|
||||
|
||||
/** `minWidth` is the minimum width for this component, in logical pixels.
|
||||
*
|
||||
* It works similarly to `min-width` in CSS, but in React Native you
|
||||
|
||||
@@ -256,21 +256,21 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
|
||||
|
||||
- (void)textViewDidChange:(__unused UITextView *)textView
|
||||
{
|
||||
if (_ignoreNextTextInputCall && [_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
|
||||
if (_ignoreNextTextInputCall) {
|
||||
_ignoreNextTextInputCall = NO;
|
||||
return;
|
||||
}
|
||||
_lastStringStateWasUpdatedWith = _backedTextInputView.attributedText;
|
||||
_textDidChangeIsComing = NO;
|
||||
[_backedTextInputView.textInputDelegate textInputDidChange];
|
||||
}
|
||||
|
||||
- (void)textViewDidChangeSelection:(__unused UITextView *)textView
|
||||
{
|
||||
if (![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
|
||||
if (_lastStringStateWasUpdatedWith && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
|
||||
[self textViewDidChange:_backedTextInputView];
|
||||
_ignoreNextTextInputCall = YES;
|
||||
}
|
||||
_lastStringStateWasUpdatedWith = _backedTextInputView.attributedText;
|
||||
[self textViewProbablyDidChangeSelection];
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ NSDictionary* RCTGetReactNativeVersion(void)
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^(void){
|
||||
__rnVersion = @{
|
||||
RCTVersionMajor: @(1000),
|
||||
RCTVersionMinor: @(0),
|
||||
RCTVersionMajor: @(0),
|
||||
RCTVersionMinor: @(72),
|
||||
RCTVersionPatch: @(0),
|
||||
RCTVersionPrerelease: [NSNull null],
|
||||
RCTVersionPrerelease: @"rc.3",
|
||||
};
|
||||
});
|
||||
return __rnVersion;
|
||||
|
||||
@@ -428,10 +428,10 @@ RCT_EXPORT_METHOD(show)
|
||||
? UIAlertControllerStyleActionSheet
|
||||
: UIAlertControllerStyleAlert;
|
||||
|
||||
NSString *debugMenuType = self.bridge ? @"Bridge" : @"Bridgeless";
|
||||
NSString *debugMenuTitle = [NSString stringWithFormat:@"React Native Debug Menu (%@)", debugMenuType];
|
||||
NSString *devMenuType = self.bridge ? @"Bridge" : @"Bridgeless";
|
||||
NSString *devMenuTitle = [NSString stringWithFormat:@"React Native Dev Menu (%@)", devMenuType];
|
||||
|
||||
_actionSheet = [UIAlertController alertControllerWithTitle:debugMenuTitle message:description preferredStyle:style];
|
||||
_actionSheet = [UIAlertController alertControllerWithTitle:devMenuTitle message:description preferredStyle:style];
|
||||
|
||||
NSArray<RCTDevMenuItem *> *items = [self _menuItemsToPresent];
|
||||
for (RCTDevMenuItem *item in items) {
|
||||
|
||||
+12
-1
@@ -14,6 +14,7 @@
|
||||
#import <react/renderer/components/image/ImageComponentDescriptor.h>
|
||||
#import <react/renderer/components/image/ImageEventEmitter.h>
|
||||
#import <react/renderer/components/image/ImageProps.h>
|
||||
#import <react/renderer/core/CoreFeatures.h>
|
||||
#import <react/renderer/imagemanager/ImageRequest.h>
|
||||
#import <react/renderer/imagemanager/RCTImagePrimitivesConversions.h>
|
||||
|
||||
@@ -97,8 +98,18 @@ using namespace facebook::react;
|
||||
- (void)_setStateAndResubscribeImageResponseObserver:(ImageShadowNode::ConcreteState::Shared const &)state
|
||||
{
|
||||
if (_state) {
|
||||
auto &observerCoordinator = _state->getData().getImageRequest().getObserverCoordinator();
|
||||
auto const &imageRequest = _state->getData().getImageRequest();
|
||||
auto &observerCoordinator = imageRequest.getObserverCoordinator();
|
||||
observerCoordinator.removeObserver(_imageResponseObserverProxy);
|
||||
if (CoreFeatures::cancelImageDownloadsOnRecycle) {
|
||||
// Cancelling image request because we are no longer observing it.
|
||||
// This is not 100% correct place to do this because we may want to
|
||||
// re-create RCTImageComponentView with the same image and if it
|
||||
// was cancelled before downloaded, download is not resumed.
|
||||
// This will only become issue if we decouple life cycle of a
|
||||
// ShadowNode from ComponentView, which is not something we do now.
|
||||
imageRequest.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
_state = state;
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@
|
||||
|
||||
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args
|
||||
{
|
||||
[_coordinator handleCommand:commandName args:args reactTag:_tag];
|
||||
[_coordinator handleCommand:commandName args:args reactTag:_tag paperView:self.paperView];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -285,6 +285,10 @@ static BackgroundExecutor RCTGetBackgroundExecutor()
|
||||
CoreFeatures::cacheNSTextStorage = true;
|
||||
}
|
||||
|
||||
if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:cancel_image_downloads_on_recycle")) {
|
||||
CoreFeatures::cancelImageDownloadsOnRecycle = true;
|
||||
}
|
||||
|
||||
auto componentRegistryFactory =
|
||||
[factory = wrapManagedObject(_mountingManager.componentViewRegistry.componentViewFactory)](
|
||||
EventDispatcher::Weak const &eventDispatcher, ContextContainer::Shared const &contextContainer) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION_NAME=1000.0.0
|
||||
VERSION_NAME=0.72.0-rc.3
|
||||
GROUP=com.facebook.react
|
||||
|
||||
# JVM Versions
|
||||
|
||||
+40
-1
@@ -11,6 +11,7 @@ import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.devsupport.DoubleTapReloadRecognizer;
|
||||
@@ -33,6 +34,8 @@ public class ReactDelegate {
|
||||
|
||||
private ReactNativeHost mReactNativeHost;
|
||||
|
||||
private boolean mFabricEnabled = false;
|
||||
|
||||
public ReactDelegate(
|
||||
Activity activity,
|
||||
ReactNativeHost reactNativeHost,
|
||||
@@ -45,6 +48,20 @@ public class ReactDelegate {
|
||||
mReactNativeHost = reactNativeHost;
|
||||
}
|
||||
|
||||
public ReactDelegate(
|
||||
Activity activity,
|
||||
ReactNativeHost reactNativeHost,
|
||||
@Nullable String appKey,
|
||||
@Nullable Bundle launchOptions,
|
||||
boolean fabricEnabled) {
|
||||
mActivity = activity;
|
||||
mMainComponentName = appKey;
|
||||
mLaunchOptions = composeLaunchOptions(launchOptions);
|
||||
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
|
||||
mReactNativeHost = reactNativeHost;
|
||||
mFabricEnabled = fabricEnabled;
|
||||
}
|
||||
|
||||
public void onHostResume() {
|
||||
if (getReactNativeHost().hasInstance()) {
|
||||
if (mActivity instanceof DefaultHardwareBackBtnHandler) {
|
||||
@@ -109,7 +126,9 @@ public class ReactDelegate {
|
||||
}
|
||||
|
||||
protected ReactRootView createRootView() {
|
||||
return new ReactRootView(mActivity);
|
||||
ReactRootView reactRootView = new ReactRootView(mActivity);
|
||||
reactRootView.setIsFabric(isFabricEnabled());
|
||||
return reactRootView;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,4 +163,24 @@ public class ReactDelegate {
|
||||
public ReactInstanceManager getReactInstanceManager() {
|
||||
return getReactNativeHost().getReactInstanceManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you wish to selectively toggle Fabric for a specific surface. This will
|
||||
* also control if Concurrent Root (React 18) should be enabled or not.
|
||||
*
|
||||
* @return true if Fabric is enabled for this Activity, false otherwise.
|
||||
*/
|
||||
protected boolean isFabricEnabled() {
|
||||
return mFabricEnabled;
|
||||
}
|
||||
|
||||
private @NonNull Bundle composeLaunchOptions(Bundle composedLaunchOptions) {
|
||||
if (isFabricEnabled()) {
|
||||
if (composedLaunchOptions == null) {
|
||||
composedLaunchOptions = new Bundle();
|
||||
}
|
||||
composedLaunchOptions.putBoolean("concurrentRoot", true);
|
||||
}
|
||||
return composedLaunchOptions;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-7
@@ -16,6 +16,7 @@ import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
@@ -29,6 +30,7 @@ public class ReactFragment extends Fragment implements PermissionAwareActivity {
|
||||
|
||||
protected static final String ARG_COMPONENT_NAME = "arg_component_name";
|
||||
protected static final String ARG_LAUNCH_OPTIONS = "arg_launch_options";
|
||||
protected static final String ARG_FABRIC_ENABLED = "arg_fabric_enabled";
|
||||
|
||||
private ReactDelegate mReactDelegate;
|
||||
|
||||
@@ -40,13 +42,16 @@ public class ReactFragment extends Fragment implements PermissionAwareActivity {
|
||||
|
||||
/**
|
||||
* @param componentName The name of the react native component
|
||||
* @param fabricEnabled Flag to enable Fabric for ReactFragment
|
||||
* @return A new instance of fragment ReactFragment.
|
||||
*/
|
||||
private static ReactFragment newInstance(String componentName, Bundle launchOptions) {
|
||||
private static ReactFragment newInstance(
|
||||
String componentName, Bundle launchOptions, Boolean fabricEnabled) {
|
||||
ReactFragment fragment = new ReactFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_COMPONENT_NAME, componentName);
|
||||
args.putBundle(ARG_LAUNCH_OPTIONS, launchOptions);
|
||||
args.putBoolean(ARG_FABRIC_ENABLED, fabricEnabled);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
@@ -57,15 +62,18 @@ public class ReactFragment extends Fragment implements PermissionAwareActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
String mainComponentName = null;
|
||||
Bundle launchOptions = null;
|
||||
Boolean fabricEnabled = null;
|
||||
if (getArguments() != null) {
|
||||
mainComponentName = getArguments().getString(ARG_COMPONENT_NAME);
|
||||
launchOptions = getArguments().getBundle(ARG_LAUNCH_OPTIONS);
|
||||
fabricEnabled = getArguments().getBoolean(ARG_FABRIC_ENABLED);
|
||||
}
|
||||
if (mainComponentName == null) {
|
||||
throw new IllegalStateException("Cannot loadApp if component name is null");
|
||||
}
|
||||
mReactDelegate =
|
||||
new ReactDelegate(getActivity(), getReactNativeHost(), mainComponentName, launchOptions);
|
||||
new ReactDelegate(
|
||||
getActivity(), getReactNativeHost(), mainComponentName, launchOptions, fabricEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,7 +93,7 @@ public class ReactFragment extends Fragment implements PermissionAwareActivity {
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mReactDelegate.loadApp();
|
||||
return mReactDelegate.getReactRootView();
|
||||
}
|
||||
@@ -140,7 +148,7 @@ public class ReactFragment extends Fragment implements PermissionAwareActivity {
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, String[] permissions, int[] grantResults) {
|
||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (mPermissionListener != null
|
||||
&& mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||
@@ -170,12 +178,14 @@ public class ReactFragment extends Fragment implements PermissionAwareActivity {
|
||||
/** Builder class to help instantiate a ReactFragment */
|
||||
public static class Builder {
|
||||
|
||||
String mComponentName;
|
||||
Bundle mLaunchOptions;
|
||||
@Nullable String mComponentName;
|
||||
@Nullable Bundle mLaunchOptions;
|
||||
@Nullable Boolean mFabricEnabled;
|
||||
|
||||
public Builder() {
|
||||
mComponentName = null;
|
||||
mLaunchOptions = null;
|
||||
mFabricEnabled = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +211,12 @@ public class ReactFragment extends Fragment implements PermissionAwareActivity {
|
||||
}
|
||||
|
||||
public ReactFragment build() {
|
||||
return ReactFragment.newInstance(mComponentName, mLaunchOptions);
|
||||
return ReactFragment.newInstance(mComponentName, mLaunchOptions, mFabricEnabled);
|
||||
}
|
||||
|
||||
public Builder setFabricEnabled(boolean fabricEnabled) {
|
||||
mFabricEnabled = fabricEnabled;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -295,6 +295,16 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
|
||||
mCurrentFrameNumber++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void willMountItems(UIManager uiManager) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didMountItems(UIManager uiManager) {
|
||||
// noop
|
||||
}
|
||||
|
||||
// For FabricUIManager only
|
||||
@Override
|
||||
@UiThread
|
||||
|
||||
+29
@@ -21,6 +21,7 @@ import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.ThreadConfined;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.interop.InteropModuleRegistry;
|
||||
import com.facebook.react.bridge.queue.MessageQueueThread;
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
@@ -69,6 +70,8 @@ public class ReactContext extends ContextWrapper {
|
||||
private @Nullable JSExceptionHandler mJSExceptionHandler;
|
||||
private @Nullable JSExceptionHandler mExceptionHandlerWrapper;
|
||||
private @Nullable WeakReference<Activity> mCurrentActivity;
|
||||
|
||||
private @Nullable InteropModuleRegistry mInteropModuleRegistry;
|
||||
private boolean mIsInitialized = false;
|
||||
|
||||
public ReactContext(Context base) {
|
||||
@@ -93,6 +96,7 @@ public class ReactContext extends ContextWrapper {
|
||||
|
||||
ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration();
|
||||
initializeMessageQueueThreads(queueConfig);
|
||||
initializeInteropModules();
|
||||
}
|
||||
|
||||
/** Initialize message queue threads using a ReactQueueConfiguration. */
|
||||
@@ -120,6 +124,14 @@ public class ReactContext extends ContextWrapper {
|
||||
mIsInitialized = true;
|
||||
}
|
||||
|
||||
protected void initializeInteropModules() {
|
||||
mInteropModuleRegistry = new InteropModuleRegistry();
|
||||
}
|
||||
|
||||
protected void initializeInteropModules(ReactContext reactContext) {
|
||||
mInteropModuleRegistry = reactContext.mInteropModuleRegistry;
|
||||
}
|
||||
|
||||
public void resetPerfStats() {
|
||||
if (mNativeModulesMessageQueueThread != null) {
|
||||
mNativeModulesMessageQueueThread.resetPerfStats();
|
||||
@@ -163,6 +175,10 @@ public class ReactContext extends ContextWrapper {
|
||||
}
|
||||
throw new IllegalStateException(EARLY_JS_ACCESS_EXCEPTION_MESSAGE);
|
||||
}
|
||||
if (mInteropModuleRegistry != null
|
||||
&& mInteropModuleRegistry.shouldReturnInteropModule(jsInterface)) {
|
||||
return mInteropModuleRegistry.getInteropModule(jsInterface);
|
||||
}
|
||||
return mCatalystInstance.getJSModule(jsInterface);
|
||||
}
|
||||
|
||||
@@ -546,4 +562,17 @@ public class ReactContext extends ContextWrapper {
|
||||
Assertions.assertNotNull(mCatalystInstance).registerSegment(segmentId, path);
|
||||
Assertions.assertNotNull(callback).invoke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link JavaScriptModule} within the Interop Layer so that can be consumed whenever
|
||||
* getJSModule is invoked.
|
||||
*
|
||||
* <p>This method is internal to React Native and should not be used externally.
|
||||
*/
|
||||
public <T extends JavaScriptModule> void internal_registerInteropModule(
|
||||
Class<T> interopModuleInterface, Object interopModule) {
|
||||
if (mInteropModuleRegistry != null) {
|
||||
mInteropModuleRegistry.registerInteropModule(interopModuleInterface, interopModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-2
@@ -12,10 +12,33 @@ public interface UIManagerListener {
|
||||
/**
|
||||
* Called right before view updates are dispatched at the end of a batch. This is useful if a
|
||||
* module needs to add UIBlocks to the queue before it is flushed.
|
||||
*
|
||||
* <p>This is called by Paper only.
|
||||
*/
|
||||
void willDispatchViewUpdates(UIManager uiManager);
|
||||
/* Called right after view updates are dispatched for a frame. */
|
||||
/**
|
||||
* Called on UIThread right before view updates are executed.
|
||||
*
|
||||
* <p>This is called by Fabric only.
|
||||
*/
|
||||
void willMountItems(UIManager uiManager);
|
||||
/**
|
||||
* Called on UIThread right after view updates are executed.
|
||||
*
|
||||
* <p>This is called by Fabric only.
|
||||
*/
|
||||
void didMountItems(UIManager uiManager);
|
||||
/**
|
||||
* Called on UIThread right after view updates are dispatched for a frame. Note that this will be
|
||||
* called for every frame even if there are no updates.
|
||||
*
|
||||
* <p>This is called by Fabric only.
|
||||
*/
|
||||
void didDispatchMountItems(UIManager uiManager);
|
||||
/* Called right after scheduleMountItems is called in Fabric, after a new tree is committed. */
|
||||
/**
|
||||
* Called right after scheduleMountItems is called in Fabric, after a new tree is committed.
|
||||
*
|
||||
* <p>This is called by Fabric only.
|
||||
*/
|
||||
void didScheduleMountItems(UIManager uiManager);
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.interop;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* A utility class that takes care of returning {@link JavaScriptModule} which are used for the
|
||||
* Fabric Interop Layer. This allows us to override the returned classes once the user is invoking
|
||||
* `ReactContext.getJsModule()`.
|
||||
*
|
||||
* <p>Currently we only support a `RCTEventEmitter` re-implementation, being `InteropEventEmitter`
|
||||
* but this class can support other re-implementation in the future.
|
||||
*/
|
||||
public class InteropModuleRegistry {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final HashMap<Class, Object> supportedModules;
|
||||
|
||||
public InteropModuleRegistry() {
|
||||
this.supportedModules = new HashMap<>();
|
||||
}
|
||||
|
||||
public <T extends JavaScriptModule> boolean shouldReturnInteropModule(Class<T> requestedModule) {
|
||||
return checkReactFeatureFlagsConditions() && supportedModules.containsKey(requestedModule);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T extends JavaScriptModule> T getInteropModule(Class<T> requestedModule) {
|
||||
if (checkReactFeatureFlagsConditions()) {
|
||||
//noinspection unchecked
|
||||
return (T) supportedModules.get(requestedModule);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends JavaScriptModule> void registerInteropModule(
|
||||
Class<T> interopModuleInterface, Object interopModule) {
|
||||
if (checkReactFeatureFlagsConditions()) {
|
||||
supportedModules.put(interopModuleInterface, interopModule);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkReactFeatureFlagsConditions() {
|
||||
return ReactFeatureFlags.enableFabricRenderer && ReactFeatureFlags.unstable_useFabricInterop;
|
||||
}
|
||||
}
|
||||
+11
@@ -34,6 +34,14 @@ public class ReactFeatureFlags {
|
||||
*/
|
||||
public static volatile boolean enableFabricRenderer = false;
|
||||
|
||||
/**
|
||||
* Should this application enable the Fabric Interop Layer for Android? If yes, the application
|
||||
* will behave so that it can accept non-Fabric components and render them on Fabric. This toggle
|
||||
* is controlling extra logic such as custom event dispatching that are needed for the Fabric
|
||||
* Interop Layer to work correctly.
|
||||
*/
|
||||
public static volatile boolean unstable_useFabricInterop = false;
|
||||
|
||||
/**
|
||||
* Feature flag to enable the new bridgeless architecture. Note: Enabling this will force enable
|
||||
* the following flags: `useTurboModules` & `enableFabricRenderer`.
|
||||
@@ -75,6 +83,9 @@ public class ReactFeatureFlags {
|
||||
/** Feature Flag to enable the pending event queue in fabric before mounting views */
|
||||
public static boolean enableFabricPendingEventQueue = false;
|
||||
|
||||
/** Feature Flag to enable caching mechanism of text measurement at shadow node level */
|
||||
public static boolean enableTextMeasureCachePerShadowNode = false;
|
||||
|
||||
/**
|
||||
* Feature flag that controls how turbo modules are exposed to JS
|
||||
*
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ object DefaultNewArchitectureEntryPoint {
|
||||
) {
|
||||
ReactFeatureFlags.useTurboModules = turboModulesEnabled
|
||||
ReactFeatureFlags.enableFabricRenderer = fabricEnabled
|
||||
ReactFeatureFlags.unstable_useFabricInterop = fabricEnabled
|
||||
|
||||
this.privateFabricEnabled = fabricEnabled
|
||||
this.privateTurboModulesEnabled = turboModulesEnabled
|
||||
|
||||
+4
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.facebook.react.defaults
|
||||
|
||||
import android.os.Bundle
|
||||
import com.facebook.react.ReactActivity
|
||||
import com.facebook.react.ReactActivityDelegate
|
||||
import com.facebook.react.ReactRootView
|
||||
@@ -43,4 +44,7 @@ open class DefaultReactActivityDelegate(
|
||||
|
||||
override fun createRootView(): ReactRootView =
|
||||
ReactRootView(context).apply { setIsFabric(fabricEnabled) }
|
||||
|
||||
override fun createRootView(bundle: Bundle?): ReactRootView =
|
||||
ReactRootView(context).apply { setIsFabric(fabricEnabled) }
|
||||
}
|
||||
|
||||
+26
-8
@@ -24,6 +24,7 @@ import android.util.Pair;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -553,17 +554,30 @@ public abstract class DevSupportManagerBase implements DevSupportManager {
|
||||
return;
|
||||
}
|
||||
|
||||
final TextView textView = new TextView(getApplicationContext());
|
||||
textView.setText("React Native DevMenu (" + getUniqueTag() + ")");
|
||||
textView.setPadding(0, 50, 0, 0);
|
||||
textView.setGravity(Gravity.CENTER);
|
||||
textView.setTextColor(Color.BLACK);
|
||||
textView.setTextSize(17);
|
||||
textView.setTypeface(textView.getTypeface(), Typeface.BOLD);
|
||||
final LinearLayout header = new LinearLayout(getApplicationContext());
|
||||
header.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
final TextView title = new TextView(getApplicationContext());
|
||||
title.setText("React Native Dev Menu (" + getUniqueTag() + ")");
|
||||
title.setPadding(0, 50, 0, 0);
|
||||
title.setGravity(Gravity.CENTER);
|
||||
title.setTextColor(Color.DKGRAY);
|
||||
title.setTextSize(16);
|
||||
title.setTypeface(title.getTypeface(), Typeface.BOLD);
|
||||
|
||||
final TextView jsExecutorLabel = new TextView(getApplicationContext());
|
||||
jsExecutorLabel.setText(getJSExecutorDescription());
|
||||
jsExecutorLabel.setPadding(0, 20, 0, 0);
|
||||
jsExecutorLabel.setGravity(Gravity.CENTER);
|
||||
jsExecutorLabel.setTextColor(Color.GRAY);
|
||||
jsExecutorLabel.setTextSize(14);
|
||||
|
||||
header.addView(title);
|
||||
header.addView(jsExecutorLabel);
|
||||
|
||||
mDevOptionsDialog =
|
||||
new AlertDialog.Builder(context)
|
||||
.setCustomTitle(textView)
|
||||
.setCustomTitle(header)
|
||||
.setItems(
|
||||
options.keySet().toArray(new String[0]),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@@ -587,6 +601,10 @@ public abstract class DevSupportManagerBase implements DevSupportManager {
|
||||
}
|
||||
}
|
||||
|
||||
private String getJSExecutorDescription() {
|
||||
return "Running " + getReactInstanceDevHelper().getJavaScriptExecutorFactory().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ReactInstanceDevCommandsHandler} is responsible for enabling/disabling dev support when
|
||||
* a React view is attached/detached or when application state changes (e.g. the application is
|
||||
|
||||
+21
@@ -56,6 +56,7 @@ import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.react.fabric.events.EventBeatManager;
|
||||
import com.facebook.react.fabric.events.EventEmitterWrapper;
|
||||
import com.facebook.react.fabric.events.FabricEventEmitter;
|
||||
import com.facebook.react.fabric.interop.InteropEventEmitter;
|
||||
import com.facebook.react.fabric.mounting.MountItemDispatcher;
|
||||
import com.facebook.react.fabric.mounting.MountingManager;
|
||||
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
|
||||
@@ -81,6 +82,7 @@ import com.facebook.react.uimanager.ViewManagerRegistry;
|
||||
import com.facebook.react.uimanager.events.EventCategoryDef;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.uimanager.events.EventDispatcherImpl;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.views.text.TextLayoutManager;
|
||||
import com.facebook.react.views.text.TextLayoutManagerMapBuffer;
|
||||
import java.util.HashMap;
|
||||
@@ -390,6 +392,11 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
||||
|
||||
ReactMarker.addFabricListener(mDevToolsReactPerfLogger);
|
||||
}
|
||||
if (ReactFeatureFlags.unstable_useFabricInterop) {
|
||||
InteropEventEmitter interopEventEmitter = new InteropEventEmitter(mReactApplicationContext);
|
||||
mReactApplicationContext.internal_registerInteropModule(
|
||||
RCTEventEmitter.class, interopEventEmitter);
|
||||
}
|
||||
}
|
||||
|
||||
// This is called on the JS thread (see CatalystInstanceImpl).
|
||||
@@ -1171,6 +1178,20 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
||||
}
|
||||
|
||||
private class MountItemDispatchListener implements MountItemDispatcher.ItemDispatchListener {
|
||||
@Override
|
||||
public void willMountItems() {
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.willMountItems(FabricUIManager.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didMountItems() {
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.didMountItems(FabricUIManager.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didDispatchMountItems() {
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.interop;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
|
||||
/**
|
||||
* An {@link Event} class used by the {@link InteropEventEmitter}. This class is just holding the
|
||||
* event name and the data which is received by the `receiveEvent` method and will be passed over
|
||||
* the the {@link com.facebook.react.uimanager.events.EventDispatcher}
|
||||
*/
|
||||
class InteropEvent extends Event<InteropEvent> {
|
||||
|
||||
private final String mName;
|
||||
private final WritableMap mEventData;
|
||||
|
||||
InteropEvent(String name, @Nullable WritableMap eventData, int surfaceId, int viewTag) {
|
||||
super(surfaceId, viewTag);
|
||||
mName = name;
|
||||
mEventData = eventData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
public WritableMap getEventData() {
|
||||
return mEventData;
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.interop;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.uimanager.UIManagerHelper;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
/**
|
||||
* A reimplementation of {@link RCTEventEmitter} which is using a {@link EventDispatcher} under the
|
||||
* hood.
|
||||
*
|
||||
* <p>On Fabric, you're supposed to use {@link EventDispatcher} to dispatch events. However, we
|
||||
* provide an interop layer for non-Fabric migrated components.
|
||||
*
|
||||
* <p>This instance will be returned if the user is invoking `context.getJsModule(RCTEventEmitter)
|
||||
* and is providing support for the `receiveEvent` method, so that non-Fabric ViewManagers can
|
||||
* continue to deliver events also when Fabric is turned on.
|
||||
*/
|
||||
public class InteropEventEmitter implements RCTEventEmitter {
|
||||
|
||||
private final ReactContext mReactContext;
|
||||
|
||||
private @Nullable EventDispatcher mEventDispatcherOverride;
|
||||
|
||||
public InteropEventEmitter(ReactContext reactContext) {
|
||||
mReactContext = reactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveEvent(int targetReactTag, String eventName, @Nullable WritableMap eventData) {
|
||||
EventDispatcher dispatcher;
|
||||
if (mEventDispatcherOverride != null) {
|
||||
dispatcher = mEventDispatcherOverride;
|
||||
} else {
|
||||
dispatcher = UIManagerHelper.getEventDispatcherForReactTag(mReactContext, targetReactTag);
|
||||
}
|
||||
int surfaceId = UIManagerHelper.getSurfaceId(mReactContext);
|
||||
if (dispatcher != null) {
|
||||
dispatcher.dispatchEvent(new InteropEvent(eventName, eventData, surfaceId, targetReactTag));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveTouches(
|
||||
String eventName, WritableArray touches, WritableArray changedIndices) {
|
||||
throw new UnsupportedOperationException(
|
||||
"EventEmitter#receiveTouches is not supported by the Fabric Interop Layer");
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void overrideEventDispatcher(EventDispatcher eventDispatcherOverride) {
|
||||
mEventDispatcherOverride = eventDispatcherOverride;
|
||||
}
|
||||
}
|
||||
+9
@@ -193,6 +193,8 @@ public class MountItemDispatcher {
|
||||
return false;
|
||||
}
|
||||
|
||||
mItemDispatchListener.willMountItems();
|
||||
|
||||
// As an optimization, execute all ViewCommands first
|
||||
// This should be:
|
||||
// 1) Performant: ViewCommands are often a replacement for SetNativeProps, which we've always
|
||||
@@ -299,6 +301,9 @@ public class MountItemDispatcher {
|
||||
}
|
||||
mBatchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime;
|
||||
}
|
||||
|
||||
mItemDispatchListener.didMountItems();
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
|
||||
return true;
|
||||
@@ -415,6 +420,10 @@ public class MountItemDispatcher {
|
||||
}
|
||||
|
||||
public interface ItemDispatchListener {
|
||||
void willMountItems();
|
||||
|
||||
void didMountItems();
|
||||
|
||||
void didDispatchMountItems();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -15,8 +15,8 @@ import java.util.Map;
|
||||
|
||||
public class ReactNativeVersion {
|
||||
public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
|
||||
"major", 1000,
|
||||
"minor", 0,
|
||||
"major", 0,
|
||||
"minor", 72,
|
||||
"patch", 0,
|
||||
"prerelease", null);
|
||||
"prerelease", "rc.3");
|
||||
}
|
||||
|
||||
+1
@@ -50,6 +50,7 @@ public class ThemedReactContext extends ReactContext {
|
||||
if (reactApplicationContext.hasCatalystInstance()) {
|
||||
initializeWithInstance(reactApplicationContext.getCatalystInstance());
|
||||
}
|
||||
initializeInteropModules(reactApplicationContext);
|
||||
mReactApplicationContext = reactApplicationContext;
|
||||
mModuleName = moduleName;
|
||||
mSurfaceId = surfaceId;
|
||||
|
||||
+32
@@ -10,11 +10,15 @@ package com.facebook.react.uimanager;
|
||||
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.systrace.SystraceMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helps generate constants map for {@link UIManagerModule} by collecting and merging constants from
|
||||
@@ -113,6 +117,13 @@ import java.util.Map;
|
||||
|
||||
Map viewManagerBubblingEvents = viewManager.getExportedCustomBubblingEventTypeConstants();
|
||||
if (viewManagerBubblingEvents != null) {
|
||||
|
||||
if (ReactFeatureFlags.enableFabricRenderer && ReactFeatureFlags.unstable_useFabricInterop) {
|
||||
// For Fabric, events needs to be fired with a "top" prefix.
|
||||
// For the sake of Fabric Interop, here we normalize events adding "top" in their
|
||||
// name if the user hasn't provided it.
|
||||
normalizeEventTypes(viewManagerBubblingEvents);
|
||||
}
|
||||
recursiveMerge(cumulativeBubblingEventTypes, viewManagerBubblingEvents);
|
||||
recursiveMerge(viewManagerBubblingEvents, defaultBubblingEvents);
|
||||
viewManagerConstants.put(BUBBLING_EVENTS_KEY, viewManagerBubblingEvents);
|
||||
@@ -145,6 +156,27 @@ import java.util.Map;
|
||||
return viewManagerConstants;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
/* package */ static void normalizeEventTypes(Map events) {
|
||||
if (events == null) {
|
||||
return;
|
||||
}
|
||||
Set<String> keysToNormalize = new HashSet<>();
|
||||
for (Object key : events.keySet()) {
|
||||
if (key instanceof String) {
|
||||
String keyString = (String) key;
|
||||
if (!keyString.startsWith("top")) {
|
||||
keysToNormalize.add(keyString);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String oldKey : keysToNormalize) {
|
||||
Object value = events.get(oldKey);
|
||||
String newKey = "top" + oldKey.substring(0, 1).toUpperCase() + oldKey.substring(1);
|
||||
events.put(newKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges {@param source} map into {@param dest} map recursively */
|
||||
private static void recursiveMerge(@Nullable Map dest, @Nullable Map source) {
|
||||
if (dest == null || source == null || source.isEmpty()) {
|
||||
|
||||
+19
@@ -18,6 +18,7 @@ import com.facebook.react.bridge.UIManager;
|
||||
import com.facebook.react.bridge.UIManagerListener;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.uimanager.UIManagerHelper;
|
||||
import com.facebook.react.uimanager.common.UIManagerType;
|
||||
import com.facebook.react.uimanager.common.ViewUtil;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper.HasSmoothScroll;
|
||||
import com.facebook.react.views.view.ReactViewGroup;
|
||||
@@ -89,6 +90,14 @@ public class MaintainVisibleScrollPositionHelper<ScrollViewT extends ViewGroup &
|
||||
* been updated.
|
||||
*/
|
||||
public void updateScrollPosition() {
|
||||
// On Fabric this will be called internally in `didMountItems`.
|
||||
if (ViewUtil.getUIManagerType(mScrollView.getId()) == UIManagerType.FABRIC) {
|
||||
return;
|
||||
}
|
||||
updateScrollPositionInternal();
|
||||
}
|
||||
|
||||
private void updateScrollPositionInternal() {
|
||||
if (mConfig == null || mFirstVisibleView == null || mPrevFirstVisibleFrame == null) {
|
||||
return;
|
||||
}
|
||||
@@ -169,6 +178,16 @@ public class MaintainVisibleScrollPositionHelper<ScrollViewT extends ViewGroup &
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void willMountItems(UIManager uiManager) {
|
||||
computeTargetView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didMountItems(UIManager uiManager) {
|
||||
updateScrollPositionInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didDispatchMountItems(UIManager uiManager) {
|
||||
// noop
|
||||
|
||||
+4
@@ -37,6 +37,10 @@ public class CustomLetterSpacingSpan extends MetricAffectingSpan implements Reac
|
||||
apply(paint);
|
||||
}
|
||||
|
||||
public float getSpacing() {
|
||||
return mLetterSpacing;
|
||||
}
|
||||
|
||||
private void apply(TextPaint paint) {
|
||||
if (!Float.isNaN(mLetterSpacing)) {
|
||||
paint.setLetterSpacing(mLetterSpacing);
|
||||
|
||||
+4
@@ -71,6 +71,10 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
|
||||
return mFontFamily;
|
||||
}
|
||||
|
||||
public @Nullable String getFontFeatureSettings() {
|
||||
return mFeatureSettings;
|
||||
}
|
||||
|
||||
private static void apply(
|
||||
Paint paint,
|
||||
int style,
|
||||
|
||||
+1
-5
@@ -31,8 +31,6 @@ public class ReactTextUpdate {
|
||||
private final int mSelectionEnd;
|
||||
private final int mJustificationMode;
|
||||
|
||||
public boolean mContainsMultipleFragments;
|
||||
|
||||
/**
|
||||
* @deprecated Use a non-deprecated constructor for ReactTextUpdate instead. This one remains
|
||||
* because it's being used by a unit test that isn't currently open source.
|
||||
@@ -142,13 +140,11 @@ public class ReactTextUpdate {
|
||||
int jsEventCounter,
|
||||
int textAlign,
|
||||
int textBreakStrategy,
|
||||
int justificationMode,
|
||||
boolean containsMultipleFragments) {
|
||||
int justificationMode) {
|
||||
|
||||
ReactTextUpdate reactTextUpdate =
|
||||
new ReactTextUpdate(
|
||||
text, jsEventCounter, false, textAlign, textBreakStrategy, justificationMode);
|
||||
reactTextUpdate.mContainsMultipleFragments = containsMultipleFragments;
|
||||
return reactTextUpdate;
|
||||
}
|
||||
|
||||
|
||||
+192
-136
@@ -11,6 +11,8 @@ import static com.facebook.react.uimanager.UIManagerHelper.getReactContext;
|
||||
import static com.facebook.react.views.text.TextAttributeProps.UNSET;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -50,15 +52,19 @@ import com.facebook.react.views.text.CustomLetterSpacingSpan;
|
||||
import com.facebook.react.views.text.CustomLineHeightSpan;
|
||||
import com.facebook.react.views.text.CustomStyleSpan;
|
||||
import com.facebook.react.views.text.ReactAbsoluteSizeSpan;
|
||||
import com.facebook.react.views.text.ReactBackgroundColorSpan;
|
||||
import com.facebook.react.views.text.ReactForegroundColorSpan;
|
||||
import com.facebook.react.views.text.ReactSpan;
|
||||
import com.facebook.react.views.text.ReactStrikethroughSpan;
|
||||
import com.facebook.react.views.text.ReactTextUpdate;
|
||||
import com.facebook.react.views.text.ReactTypefaceUtils;
|
||||
import com.facebook.react.views.text.ReactUnderlineSpan;
|
||||
import com.facebook.react.views.text.TextAttributes;
|
||||
import com.facebook.react.views.text.TextInlineImageSpan;
|
||||
import com.facebook.react.views.text.TextLayoutManager;
|
||||
import com.facebook.react.views.view.ReactViewBackgroundManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A wrapper around the EditText that lets us better control what happens when an EditText gets
|
||||
@@ -82,7 +88,6 @@ public class ReactEditText extends AppCompatEditText
|
||||
// *TextChanged events should be triggered. This is less expensive than removing the text
|
||||
// listeners and adding them back again after the text change is completed.
|
||||
protected boolean mIsSettingTextFromJS;
|
||||
protected boolean mIsSettingTextFromCacheUpdate = false;
|
||||
private int mDefaultGravityHorizontal;
|
||||
private int mDefaultGravityVertical;
|
||||
|
||||
@@ -362,7 +367,7 @@ public class ReactEditText extends AppCompatEditText
|
||||
}
|
||||
|
||||
super.onSelectionChanged(selStart, selEnd);
|
||||
if (!mIsSettingTextFromCacheUpdate && mSelectionWatcher != null && hasFocus()) {
|
||||
if (mSelectionWatcher != null && hasFocus()) {
|
||||
mSelectionWatcher.onSelectionChanged(selStart, selEnd);
|
||||
}
|
||||
}
|
||||
@@ -512,6 +517,14 @@ public class ReactEditText extends AppCompatEditText
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFontFeatureSettings(String fontFeatureSettings) {
|
||||
if (!Objects.equals(fontFeatureSettings, getFontFeatureSettings())) {
|
||||
super.setFontFeatureSettings(fontFeatureSettings);
|
||||
mTypefaceDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void maybeUpdateTypeface() {
|
||||
if (!mTypefaceDirty) {
|
||||
return;
|
||||
@@ -523,6 +536,17 @@ public class ReactEditText extends AppCompatEditText
|
||||
ReactTypefaceUtils.applyStyles(
|
||||
getTypeface(), mFontStyle, mFontWeight, mFontFamily, getContext().getAssets());
|
||||
setTypeface(newTypeface);
|
||||
|
||||
// Match behavior of CustomStyleSpan and enable SUBPIXEL_TEXT_FLAG when setting anything
|
||||
// nonstandard
|
||||
if (mFontStyle != UNSET
|
||||
|| mFontWeight != UNSET
|
||||
|| mFontFamily != null
|
||||
|| getFontFeatureSettings() != null) {
|
||||
setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
|
||||
} else {
|
||||
setPaintFlags(getPaintFlags() & (~Paint.SUBPIXEL_TEXT_FLAG));
|
||||
}
|
||||
}
|
||||
|
||||
// VisibleForTesting from {@link TextInputEventsTestCase}.
|
||||
@@ -584,10 +608,8 @@ public class ReactEditText extends AppCompatEditText
|
||||
SpannableStringBuilder spannableStringBuilder =
|
||||
new SpannableStringBuilder(reactTextUpdate.getText());
|
||||
|
||||
manageSpans(spannableStringBuilder, reactTextUpdate.mContainsMultipleFragments);
|
||||
|
||||
// Mitigation for https://github.com/facebook/react-native/issues/35936 (S318090)
|
||||
stripAttributeEquivalentSpans(spannableStringBuilder);
|
||||
manageSpans(spannableStringBuilder);
|
||||
stripStyleEquivalentSpans(spannableStringBuilder);
|
||||
|
||||
mContainsImages = reactTextUpdate.containsImages();
|
||||
|
||||
@@ -615,7 +637,7 @@ public class ReactEditText extends AppCompatEditText
|
||||
}
|
||||
|
||||
// Update cached spans (in Fabric only).
|
||||
updateCachedSpannable(false);
|
||||
updateCachedSpannable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,8 +646,7 @@ public class ReactEditText extends AppCompatEditText
|
||||
* will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes
|
||||
* them.
|
||||
*/
|
||||
private void manageSpans(
|
||||
SpannableStringBuilder spannableStringBuilder, boolean skipAddSpansForMeasurements) {
|
||||
private void manageSpans(SpannableStringBuilder spannableStringBuilder) {
|
||||
Object[] spans = getText().getSpans(0, length(), Object.class);
|
||||
for (int spanIdx = 0; spanIdx < spans.length; spanIdx++) {
|
||||
Object span = spans[spanIdx];
|
||||
@@ -653,58 +674,170 @@ public class ReactEditText extends AppCompatEditText
|
||||
spannableStringBuilder.setSpan(span, spanStart, spanEnd, spanFlags);
|
||||
}
|
||||
}
|
||||
|
||||
// In Fabric only, apply necessary styles to entire span
|
||||
// If the Spannable was constructed from multiple fragments, we don't apply any spans that could
|
||||
// impact the whole Spannable, because that would override "local" styles per-fragment
|
||||
if (!skipAddSpansForMeasurements) {
|
||||
addSpansForMeasurement(getText());
|
||||
}
|
||||
}
|
||||
|
||||
private void stripAttributeEquivalentSpans(SpannableStringBuilder sb) {
|
||||
// We have already set a font size on the EditText itself. We can safely remove sizing spans
|
||||
// which are the same as the set font size, and not otherwise overlapped.
|
||||
final int effectiveFontSize = mTextAttributes.getEffectiveFontSize();
|
||||
ReactAbsoluteSizeSpan[] spans = sb.getSpans(0, sb.length(), ReactAbsoluteSizeSpan.class);
|
||||
// TODO: Replace with Predicate<T> and lambdas once Java 8 builds in OSS
|
||||
interface SpanPredicate<T> {
|
||||
boolean test(T span);
|
||||
}
|
||||
|
||||
outerLoop:
|
||||
for (ReactAbsoluteSizeSpan span : spans) {
|
||||
ReactAbsoluteSizeSpan[] overlappingSpans =
|
||||
sb.getSpans(sb.getSpanStart(span), sb.getSpanEnd(span), ReactAbsoluteSizeSpan.class);
|
||||
/**
|
||||
* Remove spans from the SpannableStringBuilder which can be represented by TextAppearance
|
||||
* attributes on the underlying EditText. This works around instability on Samsung devices with
|
||||
* the presence of spans https://github.com/facebook/react-native/issues/35936 (S318090)
|
||||
*/
|
||||
private void stripStyleEquivalentSpans(SpannableStringBuilder sb) {
|
||||
stripSpansOfKind(
|
||||
sb,
|
||||
ReactAbsoluteSizeSpan.class,
|
||||
new SpanPredicate<ReactAbsoluteSizeSpan>() {
|
||||
@Override
|
||||
public boolean test(ReactAbsoluteSizeSpan span) {
|
||||
return span.getSize() == mTextAttributes.getEffectiveFontSize();
|
||||
}
|
||||
});
|
||||
|
||||
for (ReactAbsoluteSizeSpan overlappingSpan : overlappingSpans) {
|
||||
if (span.getSize() != effectiveFontSize) {
|
||||
continue outerLoop;
|
||||
}
|
||||
stripSpansOfKind(
|
||||
sb,
|
||||
ReactBackgroundColorSpan.class,
|
||||
new SpanPredicate<ReactBackgroundColorSpan>() {
|
||||
@Override
|
||||
public boolean test(ReactBackgroundColorSpan span) {
|
||||
return span.getBackgroundColor() == mReactBackgroundManager.getBackgroundColor();
|
||||
}
|
||||
});
|
||||
|
||||
stripSpansOfKind(
|
||||
sb,
|
||||
ReactForegroundColorSpan.class,
|
||||
new SpanPredicate<ReactForegroundColorSpan>() {
|
||||
@Override
|
||||
public boolean test(ReactForegroundColorSpan span) {
|
||||
return span.getForegroundColor() == getCurrentTextColor();
|
||||
}
|
||||
});
|
||||
|
||||
stripSpansOfKind(
|
||||
sb,
|
||||
ReactStrikethroughSpan.class,
|
||||
new SpanPredicate<ReactStrikethroughSpan>() {
|
||||
@Override
|
||||
public boolean test(ReactStrikethroughSpan span) {
|
||||
return (getPaintFlags() & Paint.STRIKE_THRU_TEXT_FLAG) != 0;
|
||||
}
|
||||
});
|
||||
|
||||
stripSpansOfKind(
|
||||
sb,
|
||||
ReactUnderlineSpan.class,
|
||||
new SpanPredicate<ReactUnderlineSpan>() {
|
||||
@Override
|
||||
public boolean test(ReactUnderlineSpan span) {
|
||||
return (getPaintFlags() & Paint.UNDERLINE_TEXT_FLAG) != 0;
|
||||
}
|
||||
});
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
stripSpansOfKind(
|
||||
sb,
|
||||
CustomLetterSpacingSpan.class,
|
||||
new SpanPredicate<CustomLetterSpacingSpan>() {
|
||||
@Override
|
||||
public boolean test(CustomLetterSpacingSpan span) {
|
||||
return span.getSpacing() == mTextAttributes.getEffectiveLetterSpacing();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stripSpansOfKind(
|
||||
sb,
|
||||
CustomStyleSpan.class,
|
||||
new SpanPredicate<CustomStyleSpan>() {
|
||||
@Override
|
||||
public boolean test(CustomStyleSpan span) {
|
||||
return span.getStyle() == mFontStyle
|
||||
&& Objects.equals(span.getFontFamily(), mFontFamily)
|
||||
&& span.getWeight() == mFontWeight
|
||||
&& Objects.equals(span.getFontFeatureSettings(), getFontFeatureSettings());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void stripSpansOfKind(
|
||||
SpannableStringBuilder sb, Class<T> clazz, SpanPredicate<T> shouldStrip) {
|
||||
T[] spans = sb.getSpans(0, sb.length(), clazz);
|
||||
|
||||
for (T span : spans) {
|
||||
if (shouldStrip.test(span)) {
|
||||
sb.removeSpan(span);
|
||||
}
|
||||
|
||||
sb.removeSpan(span);
|
||||
}
|
||||
}
|
||||
|
||||
private void unstripAttributeEquivalentSpans(
|
||||
SpannableStringBuilder workingText, Spannable originalText) {
|
||||
// We must add spans back for Fabric to be able to measure, at lower precedence than any
|
||||
// existing spans. Remove all spans, add the attributes, then re-add the spans over
|
||||
workingText.append(originalText);
|
||||
/**
|
||||
* Copy styles represented as attributes to the underlying span, for later measurement or other
|
||||
* usage outside the ReactEditText.
|
||||
*/
|
||||
private void addSpansFromStyleAttributes(SpannableStringBuilder workingText) {
|
||||
int spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE;
|
||||
|
||||
for (Object span : workingText.getSpans(0, workingText.length(), Object.class)) {
|
||||
workingText.removeSpan(span);
|
||||
}
|
||||
// Set all bits for SPAN_PRIORITY so that this span has the highest possible priority
|
||||
// (least precedence). This ensures the span is behind any overlapping spans.
|
||||
spanFlags |= Spannable.SPAN_PRIORITY;
|
||||
|
||||
workingText.setSpan(
|
||||
new ReactAbsoluteSizeSpan(mTextAttributes.getEffectiveFontSize()),
|
||||
0,
|
||||
workingText.length(),
|
||||
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
spanFlags);
|
||||
|
||||
for (Object span : originalText.getSpans(0, originalText.length(), Object.class)) {
|
||||
workingText.setSpan(
|
||||
new ReactForegroundColorSpan(getCurrentTextColor()), 0, workingText.length(), spanFlags);
|
||||
|
||||
int backgroundColor = mReactBackgroundManager.getBackgroundColor();
|
||||
if (backgroundColor != Color.TRANSPARENT) {
|
||||
workingText.setSpan(
|
||||
span,
|
||||
originalText.getSpanStart(span),
|
||||
originalText.getSpanEnd(span),
|
||||
originalText.getSpanFlags(span));
|
||||
new ReactBackgroundColorSpan(backgroundColor), 0, workingText.length(), spanFlags);
|
||||
}
|
||||
|
||||
if ((getPaintFlags() & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
|
||||
workingText.setSpan(new ReactStrikethroughSpan(), 0, workingText.length(), spanFlags);
|
||||
}
|
||||
|
||||
if ((getPaintFlags() & Paint.UNDERLINE_TEXT_FLAG) != 0) {
|
||||
workingText.setSpan(new ReactUnderlineSpan(), 0, workingText.length(), spanFlags);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
float effectiveLetterSpacing = mTextAttributes.getEffectiveLetterSpacing();
|
||||
if (!Float.isNaN(effectiveLetterSpacing)) {
|
||||
workingText.setSpan(
|
||||
new CustomLetterSpacingSpan(effectiveLetterSpacing),
|
||||
0,
|
||||
workingText.length(),
|
||||
spanFlags);
|
||||
}
|
||||
}
|
||||
|
||||
if (mFontStyle != UNSET
|
||||
|| mFontWeight != UNSET
|
||||
|| mFontFamily != null
|
||||
|| getFontFeatureSettings() != null) {
|
||||
workingText.setSpan(
|
||||
new CustomStyleSpan(
|
||||
mFontStyle,
|
||||
mFontWeight,
|
||||
getFontFeatureSettings(),
|
||||
mFontFamily,
|
||||
getContext().getAssets()),
|
||||
0,
|
||||
workingText.length(),
|
||||
spanFlags);
|
||||
}
|
||||
|
||||
float lineHeight = mTextAttributes.getEffectiveLineHeight();
|
||||
if (!Float.isNaN(lineHeight)) {
|
||||
workingText.setSpan(new CustomLineHeightSpan(lineHeight), 0, workingText.length(), spanFlags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,73 +857,6 @@ public class ReactEditText extends AppCompatEditText
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is hacked in for Fabric. When we delete non-Fabric code, we might be able to simplify or
|
||||
// clean this up a bit.
|
||||
private void addSpansForMeasurement(Spannable spannable) {
|
||||
if (!mFabricViewStateManager.hasStateWrapper()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean originalDisableTextDiffing = mDisableTextDiffing;
|
||||
mDisableTextDiffing = true;
|
||||
|
||||
int start = 0;
|
||||
int end = spannable.length();
|
||||
|
||||
// Remove duplicate spans we might add here
|
||||
Object[] spans = spannable.getSpans(0, length(), Object.class);
|
||||
for (Object span : spans) {
|
||||
int spanFlags = spannable.getSpanFlags(span);
|
||||
boolean isInclusive =
|
||||
(spanFlags & Spanned.SPAN_INCLUSIVE_INCLUSIVE) == Spanned.SPAN_INCLUSIVE_INCLUSIVE
|
||||
|| (spanFlags & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) == Spanned.SPAN_INCLUSIVE_EXCLUSIVE;
|
||||
if (isInclusive
|
||||
&& span instanceof ReactSpan
|
||||
&& spannable.getSpanStart(span) == start
|
||||
&& spannable.getSpanEnd(span) == end) {
|
||||
spannable.removeSpan(span);
|
||||
}
|
||||
}
|
||||
|
||||
List<TextLayoutManager.SetSpanOperation> ops = new ArrayList<>();
|
||||
|
||||
if (!Float.isNaN(mTextAttributes.getLetterSpacing())) {
|
||||
ops.add(
|
||||
new TextLayoutManager.SetSpanOperation(
|
||||
start, end, new CustomLetterSpacingSpan(mTextAttributes.getLetterSpacing())));
|
||||
}
|
||||
ops.add(
|
||||
new TextLayoutManager.SetSpanOperation(
|
||||
start, end, new ReactAbsoluteSizeSpan((int) mTextAttributes.getEffectiveFontSize())));
|
||||
if (mFontStyle != UNSET || mFontWeight != UNSET || mFontFamily != null) {
|
||||
ops.add(
|
||||
new TextLayoutManager.SetSpanOperation(
|
||||
start,
|
||||
end,
|
||||
new CustomStyleSpan(
|
||||
mFontStyle,
|
||||
mFontWeight,
|
||||
null, // TODO: do we need to support FontFeatureSettings / fontVariant?
|
||||
mFontFamily,
|
||||
getReactContext(ReactEditText.this).getAssets())));
|
||||
}
|
||||
if (!Float.isNaN(mTextAttributes.getEffectiveLineHeight())) {
|
||||
ops.add(
|
||||
new TextLayoutManager.SetSpanOperation(
|
||||
start, end, new CustomLineHeightSpan(mTextAttributes.getEffectiveLineHeight())));
|
||||
}
|
||||
|
||||
int priority = 0;
|
||||
for (TextLayoutManager.SetSpanOperation op : ops) {
|
||||
// Actual order of calling {@code execute} does NOT matter,
|
||||
// but the {@code priority} DOES matter.
|
||||
op.execute(spannable, priority);
|
||||
priority++;
|
||||
}
|
||||
|
||||
mDisableTextDiffing = originalDisableTextDiffing;
|
||||
}
|
||||
|
||||
protected boolean showSoftKeyboard() {
|
||||
return mInputMethodManager.showSoftInput(this, 0);
|
||||
}
|
||||
@@ -1060,7 +1126,9 @@ public class ReactEditText extends AppCompatEditText
|
||||
|
||||
float effectiveLetterSpacing = mTextAttributes.getEffectiveLetterSpacing();
|
||||
if (!Float.isNaN(effectiveLetterSpacing)) {
|
||||
setLetterSpacing(effectiveLetterSpacing);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setLetterSpacing(effectiveLetterSpacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1075,7 +1143,7 @@ public class ReactEditText extends AppCompatEditText
|
||||
* TextLayoutManager.java with some very minor modifications. There's some duplication between
|
||||
* here and TextLayoutManager, so there might be an opportunity for refactor.
|
||||
*/
|
||||
private void updateCachedSpannable(boolean resetStyles) {
|
||||
private void updateCachedSpannable() {
|
||||
// Noops in non-Fabric
|
||||
if (mFabricViewStateManager == null || !mFabricViewStateManager.hasStateWrapper()) {
|
||||
return;
|
||||
@@ -1085,12 +1153,6 @@ public class ReactEditText extends AppCompatEditText
|
||||
return;
|
||||
}
|
||||
|
||||
if (resetStyles) {
|
||||
mIsSettingTextFromCacheUpdate = true;
|
||||
addSpansForMeasurement(getText());
|
||||
mIsSettingTextFromCacheUpdate = false;
|
||||
}
|
||||
|
||||
Editable currentText = getText();
|
||||
boolean haveText = currentText != null && currentText.length() > 0;
|
||||
|
||||
@@ -1132,8 +1194,7 @@ public class ReactEditText extends AppCompatEditText
|
||||
// ...
|
||||
// - android.app.Activity.dispatchKeyEvent (Activity.java:3447)
|
||||
try {
|
||||
Spannable text = (Spannable) currentText.subSequence(0, currentText.length());
|
||||
unstripAttributeEquivalentSpans(sb, text);
|
||||
sb.append(currentText.subSequence(0, currentText.length()));
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
ReactSoftExceptionLogger.logSoftException(TAG, e);
|
||||
}
|
||||
@@ -1149,11 +1210,9 @@ public class ReactEditText extends AppCompatEditText
|
||||
// Measure something so we have correct height, even if there's no string.
|
||||
sb.append("I");
|
||||
}
|
||||
|
||||
// Make sure that all text styles are applied when we're measurable the hint or "blank" text
|
||||
addSpansForMeasurement(sb);
|
||||
}
|
||||
|
||||
addSpansFromStyleAttributes(sb);
|
||||
TextLayoutManager.setCachedSpannabledForTag(getId(), sb);
|
||||
}
|
||||
|
||||
@@ -1168,7 +1227,7 @@ public class ReactEditText extends AppCompatEditText
|
||||
private class TextWatcherDelegator implements TextWatcher {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
if (!mIsSettingTextFromCacheUpdate && !mIsSettingTextFromJS && mListeners != null) {
|
||||
if (!mIsSettingTextFromJS && mListeners != null) {
|
||||
for (TextWatcher listener : mListeners) {
|
||||
listener.beforeTextChanged(s, start, count, after);
|
||||
}
|
||||
@@ -1182,23 +1241,20 @@ public class ReactEditText extends AppCompatEditText
|
||||
TAG, "onTextChanged[" + getId() + "]: " + s + " " + start + " " + before + " " + count);
|
||||
}
|
||||
|
||||
if (!mIsSettingTextFromCacheUpdate) {
|
||||
if (!mIsSettingTextFromJS && mListeners != null) {
|
||||
for (TextWatcher listener : mListeners) {
|
||||
listener.onTextChanged(s, start, before, count);
|
||||
}
|
||||
if (!mIsSettingTextFromJS && mListeners != null) {
|
||||
for (TextWatcher listener : mListeners) {
|
||||
listener.onTextChanged(s, start, before, count);
|
||||
}
|
||||
|
||||
updateCachedSpannable(
|
||||
!mIsSettingTextFromJS && !mIsSettingTextFromState && start == 0 && before == 0);
|
||||
}
|
||||
|
||||
updateCachedSpannable();
|
||||
|
||||
onContentSizeChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (!mIsSettingTextFromCacheUpdate && !mIsSettingTextFromJS && mListeners != null) {
|
||||
if (!mIsSettingTextFromJS && mListeners != null) {
|
||||
for (TextWatcher listener : mListeners) {
|
||||
listener.afterTextChanged(s);
|
||||
}
|
||||
|
||||
+23
-10
@@ -13,6 +13,7 @@ import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.BlendMode;
|
||||
import android.graphics.BlendModeColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
@@ -68,6 +69,7 @@ import com.facebook.react.views.text.DefaultStyleValuesUtil;
|
||||
import com.facebook.react.views.text.ReactBaseTextShadowNode;
|
||||
import com.facebook.react.views.text.ReactTextUpdate;
|
||||
import com.facebook.react.views.text.ReactTextViewManagerCallback;
|
||||
import com.facebook.react.views.text.ReactTypefaceUtils;
|
||||
import com.facebook.react.views.text.TextAttributeProps;
|
||||
import com.facebook.react.views.text.TextInlineImageSpan;
|
||||
import com.facebook.react.views.text.TextLayoutManager;
|
||||
@@ -412,6 +414,11 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
||||
view.setFontStyle(fontStyle);
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.FONT_VARIANT)
|
||||
public void setFontVariant(ReactEditText view, @Nullable ReadableArray fontVariant) {
|
||||
view.setFontFeatureSettings(ReactTypefaceUtils.parseFontVariant(fontVariant));
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.INCLUDE_FONT_PADDING, defaultBoolean = true)
|
||||
public void setIncludeFontPadding(ReactEditText view, boolean includepad) {
|
||||
view.setIncludeFontPadding(includepad);
|
||||
@@ -935,6 +942,20 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
||||
view.setAutoFocus(autoFocus);
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.TEXT_DECORATION_LINE)
|
||||
public void setTextDecorationLine(ReactEditText view, @Nullable String textDecorationLineString) {
|
||||
view.setPaintFlags(
|
||||
view.getPaintFlags() & ~(Paint.STRIKE_THRU_TEXT_FLAG | Paint.UNDERLINE_TEXT_FLAG));
|
||||
|
||||
for (String token : textDecorationLineString.split(" ")) {
|
||||
if (token.equals("underline")) {
|
||||
view.setPaintFlags(view.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
} else if (token.equals("line-through")) {
|
||||
view.setPaintFlags(view.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactPropGroup(
|
||||
names = {
|
||||
ViewProps.BORDER_WIDTH,
|
||||
@@ -1329,9 +1350,6 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
||||
TextLayoutManager.getOrCreateSpannableForText(
|
||||
view.getContext(), attributedString, mReactTextViewManagerCallback);
|
||||
|
||||
boolean containsMultipleFragments =
|
||||
attributedString.getArray("fragments").toArrayList().size() > 1;
|
||||
|
||||
int textBreakStrategy =
|
||||
TextAttributeProps.getTextBreakStrategy(
|
||||
paragraphAttributes.getString(ViewProps.TEXT_BREAK_STRATEGY));
|
||||
@@ -1344,8 +1362,7 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
||||
TextAttributeProps.getTextAlignment(
|
||||
props, TextLayoutManager.isRTL(attributedString), view.getGravityHorizontal()),
|
||||
textBreakStrategy,
|
||||
TextAttributeProps.getJustificationMode(props, currentJustificationMode),
|
||||
containsMultipleFragments);
|
||||
TextAttributeProps.getJustificationMode(props, currentJustificationMode));
|
||||
}
|
||||
|
||||
public Object getReactTextUpdate(ReactEditText view, ReactStylesDiffMap props, MapBuffer state) {
|
||||
@@ -1366,9 +1383,6 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
||||
TextLayoutManagerMapBuffer.getOrCreateSpannableForText(
|
||||
view.getContext(), attributedString, mReactTextViewManagerCallback);
|
||||
|
||||
boolean containsMultipleFragments =
|
||||
attributedString.getMapBuffer(TextLayoutManagerMapBuffer.AS_KEY_FRAGMENTS).getCount() > 1;
|
||||
|
||||
int textBreakStrategy =
|
||||
TextAttributeProps.getTextBreakStrategy(
|
||||
paragraphAttributes.getString(TextLayoutManagerMapBuffer.PA_KEY_TEXT_BREAK_STRATEGY));
|
||||
@@ -1381,7 +1395,6 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
||||
TextAttributeProps.getTextAlignment(
|
||||
props, TextLayoutManagerMapBuffer.isRTL(attributedString), view.getGravityHorizontal()),
|
||||
textBreakStrategy,
|
||||
TextAttributeProps.getJustificationMode(props, currentJustificationMode),
|
||||
containsMultipleFragments);
|
||||
TextAttributeProps.getJustificationMode(props, currentJustificationMode));
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -19,6 +19,7 @@ public class ReactViewBackgroundManager {
|
||||
|
||||
private @Nullable ReactViewBackgroundDrawable mReactBackgroundDrawable;
|
||||
private View mView;
|
||||
private int mColor = Color.TRANSPARENT;
|
||||
|
||||
public ReactViewBackgroundManager(View view) {
|
||||
this.mView = view;
|
||||
@@ -56,6 +57,10 @@ public class ReactViewBackgroundManager {
|
||||
}
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
public void setBorderWidth(int position, float width) {
|
||||
getOrCreateReactViewBackground().setBorderWidth(position, width);
|
||||
}
|
||||
|
||||
@@ -429,6 +429,9 @@ void Binding::installFabricUIManager(
|
||||
"CalculateTransformedFramesEnabled",
|
||||
getFeatureFlagValue("calculateTransformedFramesEnabled"));
|
||||
|
||||
CoreFeatures::cacheLastTextMeasurement =
|
||||
getFeatureFlagValue("enableTextMeasureCachePerShadowNode");
|
||||
|
||||
// Props setter pattern feature
|
||||
CoreFeatures::enablePropIteratorSetter =
|
||||
getFeatureFlagValue("enableCppPropsIteratorSetter");
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.interop;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
public class FakeRCTEventEmitter implements RCTEventEmitter {
|
||||
|
||||
@Override
|
||||
public void receiveEvent(int targetReactTag, String eventName, @Nullable WritableMap event) {}
|
||||
|
||||
@Override
|
||||
public void receiveTouches(
|
||||
String eventName, WritableArray touches, WritableArray changedIndices) {}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.interop;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.react.modules.core.JSTimers;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class InteropModuleRegistryTest {
|
||||
|
||||
InteropModuleRegistry underTest;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
underTest = new InteropModuleRegistry();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnInteropModule_withFabricDisabled_returnsFalse() {
|
||||
ReactFeatureFlags.enableFabricRenderer = false;
|
||||
|
||||
assertFalse(underTest.shouldReturnInteropModule(RCTEventEmitter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnInteropModule_withFabricInteropDisabled_returnsFalse() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = false;
|
||||
|
||||
assertFalse(underTest.shouldReturnInteropModule(RCTEventEmitter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnInteropModule_withUnregisteredClass_returnsFalse() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = true;
|
||||
|
||||
assertFalse(underTest.shouldReturnInteropModule(JSTimers.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnInteropModule_withRegisteredClass_returnsTrue() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = true;
|
||||
|
||||
underTest.registerInteropModule(RCTEventEmitter.class, new FakeRCTEventEmitter());
|
||||
|
||||
assertTrue(underTest.shouldReturnInteropModule(RCTEventEmitter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInteropModule_withRegisteredClassAndInvalidFlags_returnsNull() {
|
||||
ReactFeatureFlags.enableFabricRenderer = false;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = false;
|
||||
underTest.registerInteropModule(RCTEventEmitter.class, new FakeRCTEventEmitter());
|
||||
|
||||
RCTEventEmitter interopModule = underTest.getInteropModule(RCTEventEmitter.class);
|
||||
|
||||
assertNull(interopModule);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInteropModule_withRegisteredClassAndValidFlags_returnsInteropModule() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = true;
|
||||
underTest.registerInteropModule(RCTEventEmitter.class, new FakeRCTEventEmitter());
|
||||
|
||||
RCTEventEmitter interopModule = underTest.getInteropModule(RCTEventEmitter.class);
|
||||
|
||||
assertTrue(interopModule instanceof FakeRCTEventEmitter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInteropModule_withUnregisteredClass_returnsNull() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = true;
|
||||
JSTimers missingModule = underTest.getInteropModule(JSTimers.class);
|
||||
|
||||
assertNull(missingModule);
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.interop;
|
||||
|
||||
import com.facebook.react.uimanager.events.BatchEventDispatchedListener;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.uimanager.events.EventDispatcherListener;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.uimanager.events.RCTModernEventEmitter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class FakeEventDispatcher implements EventDispatcher {
|
||||
|
||||
List<Event> recordedDispatchedEvents = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void dispatchEvent(Event event) {
|
||||
recordedDispatchedEvents.add(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchAllEvents() {}
|
||||
|
||||
@Override
|
||||
public void addListener(EventDispatcherListener listener) {}
|
||||
|
||||
@Override
|
||||
public void removeListener(EventDispatcherListener listener) {}
|
||||
|
||||
@Override
|
||||
public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) {}
|
||||
|
||||
@Override
|
||||
public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) {}
|
||||
|
||||
@Override
|
||||
public void registerEventEmitter(int uiManagerType, RCTEventEmitter eventEmitter) {}
|
||||
|
||||
@Override
|
||||
public void registerEventEmitter(int uiManagerType, RCTModernEventEmitter eventEmitter) {}
|
||||
|
||||
@Override
|
||||
public void unregisterEventEmitter(int uiManagerType) {}
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroyed() {}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.interop;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import com.facebook.react.bridge.JavaOnlyMap;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class InteropEventEmitterTest {
|
||||
|
||||
ReactContext mReactContext;
|
||||
FakeEventDispatcher mEventDispatcher;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
|
||||
mEventDispatcher = new FakeEventDispatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveEvent_dispatchesCorrectly() {
|
||||
InteropEventEmitter eventEmitter = new InteropEventEmitter(mReactContext);
|
||||
eventEmitter.overrideEventDispatcher(mEventDispatcher);
|
||||
|
||||
eventEmitter.receiveEvent(42, "onTest", null);
|
||||
|
||||
assertEquals(1, mEventDispatcher.recordedDispatchedEvents.size());
|
||||
assertEquals("onTest", mEventDispatcher.recordedDispatchedEvents.get(0).getEventName());
|
||||
assertEquals(InteropEvent.class, mEventDispatcher.recordedDispatchedEvents.get(0).getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveEvent_dataIsPreserved() {
|
||||
InteropEventEmitter eventEmitter = new InteropEventEmitter(mReactContext);
|
||||
eventEmitter.overrideEventDispatcher(mEventDispatcher);
|
||||
WritableMap eventData = JavaOnlyMap.of("color", "indigo");
|
||||
|
||||
eventEmitter.receiveEvent(42, "onTest", eventData);
|
||||
|
||||
InteropEvent event = (InteropEvent) mEventDispatcher.recordedDispatchedEvents.get(0);
|
||||
WritableMap dispatchedEventData = event.getEventData();
|
||||
assertNotNull(dispatchedEventData);
|
||||
assertEquals("indigo", dispatchedEventData.getString("color"));
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void receiveTouches_isNotSupported() {
|
||||
InteropEventEmitter eventEmitter = new InteropEventEmitter(mReactContext);
|
||||
eventEmitter.receiveTouches("a touch", null, null);
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import java.util.Map;
|
||||
import org.junit.Test;
|
||||
|
||||
public class UIManagerModuleConstantsHelperTest {
|
||||
|
||||
@Test
|
||||
public void normalizeEventTypes_withNull_doesNothing() {
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizeEventTypes_withEmptyMap_doesNothing() {
|
||||
Map<Object, Object> emptyMap = MapBuilder.builder().build();
|
||||
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(emptyMap);
|
||||
|
||||
assertTrue(emptyMap.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizeEventTypes_withOnEvent_doesNormalize() {
|
||||
Map<Object, Object> onClickMap = MapBuilder.builder().put("onClick", "¯\\_(ツ)_/¯").build();
|
||||
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap);
|
||||
|
||||
assertTrue(onClickMap.containsKey("topOnClick"));
|
||||
assertTrue(onClickMap.containsKey("onClick"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizeEventTypes_withTopEvent_doesNormalize() {
|
||||
Map<Object, Object> onClickMap = MapBuilder.builder().put("topOnClick", "¯\\_(ツ)_/¯").build();
|
||||
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap);
|
||||
|
||||
assertTrue(onClickMap.containsKey("topOnClick"));
|
||||
assertFalse(onClickMap.containsKey("onClick"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void normalizeEventTypes_withNestedObjects_doesNotLoseThem() {
|
||||
Map<String, Object> nestedObjects =
|
||||
MapBuilder.<String, Object>builder()
|
||||
.put(
|
||||
"onColorChanged",
|
||||
MapBuilder.of(
|
||||
"phasedRegistrationNames",
|
||||
MapBuilder.of(
|
||||
"bubbled", "onColorChanged", "captured", "onColorChangedCapture")))
|
||||
.build();
|
||||
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(nestedObjects);
|
||||
|
||||
assertTrue(nestedObjects.containsKey("topOnColorChanged"));
|
||||
Map<String, Object> innerMap = (Map<String, Object>) nestedObjects.get("topOnColorChanged");
|
||||
assertNotNull(innerMap);
|
||||
assertTrue(innerMap.containsKey("phasedRegistrationNames"));
|
||||
Map<String, Object> innerInnerMap =
|
||||
(Map<String, Object>) innerMap.get("phasedRegistrationNames");
|
||||
assertNotNull(innerInnerMap);
|
||||
assertEquals("onColorChanged", innerInnerMap.get("bubbled"));
|
||||
assertEquals("onColorChangedCapture", innerInnerMap.get("captured"));
|
||||
|
||||
assertTrue(nestedObjects.containsKey("onColorChanged"));
|
||||
innerMap = (Map<String, Object>) nestedObjects.get("topOnColorChanged");
|
||||
assertNotNull(innerMap);
|
||||
assertTrue(innerMap.containsKey("phasedRegistrationNames"));
|
||||
innerInnerMap = (Map<String, Object>) innerMap.get("phasedRegistrationNames");
|
||||
assertNotNull(innerInnerMap);
|
||||
assertEquals("onColorChanged", innerInnerMap.get("bubbled"));
|
||||
assertEquals("onColorChangedCapture", innerInnerMap.get("captured"));
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,15 @@ else
|
||||
source[:tag] = "v#{version}"
|
||||
end
|
||||
|
||||
react_native_path = ".."
|
||||
# We are using two different paths for react native because of the way how js_srcs_dir and output_dir options are used
|
||||
# output_dir option usage was introduced in https://github.com/facebook/react-native/pull/36210
|
||||
# React-rncore.podspec is the only podspec in the project that uses this option
|
||||
# We should rethink this approach in T148704916
|
||||
|
||||
# Relative path to react native from iOS project root (e.g. <ios-project-root>/../node_modules/react-native)
|
||||
react_native_dependency_path = ENV['REACT_NATIVE_PATH']
|
||||
# Relative path to react native from current podspec
|
||||
react_native_sources_path = '..'
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React-rncore"
|
||||
@@ -33,10 +41,10 @@ Pod::Spec.new do |s|
|
||||
|
||||
|
||||
use_react_native_codegen!(s, {
|
||||
:react_native_path => react_native_path,
|
||||
:js_srcs_dir => "#{react_native_path}/Libraries",
|
||||
:react_native_path => react_native_sources_path,
|
||||
:js_srcs_dir => "#{react_native_sources_path}/Libraries",
|
||||
:library_name => "rncore",
|
||||
:library_type => "components",
|
||||
:output_dir => "#{react_native_path}/react-native/ReactCommon"
|
||||
:output_dir => "#{react_native_dependency_path}/ReactCommon"
|
||||
})
|
||||
end
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
namespace facebook::react {
|
||||
|
||||
constexpr struct {
|
||||
int32_t Major = 1000;
|
||||
int32_t Minor = 0;
|
||||
int32_t Major = 0;
|
||||
int32_t Minor = 72;
|
||||
int32_t Patch = 0;
|
||||
std::string_view Prerelease = "";
|
||||
std::string_view Prerelease = "rc.3";
|
||||
} ReactNativeVersion;
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -24,6 +24,9 @@ bool EmptyReactNativeConfig::getBool(const std::string ¶m) const {
|
||||
if (param == "react_fabric:enabled_layout_animations_ios") {
|
||||
return true;
|
||||
}
|
||||
if (param == "react_fabric:enable_nstextstorage_caching") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -33,7 +33,10 @@ typedef void (^InterceptorBlock)(std::string eventName, folly::dynamic event);
|
||||
|
||||
- (NSString *)componentViewName;
|
||||
|
||||
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args reactTag:(NSInteger)tag;
|
||||
- (void)handleCommand:(NSString *)commandName
|
||||
args:(NSArray *)args
|
||||
reactTag:(NSInteger)tag
|
||||
paperView:(UIView *)paperView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+49
-2
@@ -100,13 +100,23 @@ using namespace facebook::react;
|
||||
return RCTDropReactPrefixes(_componentData.name);
|
||||
}
|
||||
|
||||
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args reactTag:(NSInteger)tag
|
||||
- (void)handleCommand:(NSString *)commandName
|
||||
args:(NSArray *)args
|
||||
reactTag:(NSInteger)tag
|
||||
paperView:(nonnull UIView *)paperView
|
||||
{
|
||||
Class managerClass = _componentData.managerClass;
|
||||
[self _lookupModuleMethodsIfNecessary];
|
||||
RCTModuleData *moduleData = [_bridge.batchedBridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
|
||||
id<RCTBridgeMethod> method;
|
||||
if ([commandName isKindOfClass:[NSNumber class]]) {
|
||||
|
||||
// We can't use `[NSString intValue]` as "0" is a valid command,
|
||||
// but also a falsy value. [NSNumberFormatter numberFromString] returns a
|
||||
// `NSNumber *` which is NULL when it's to be NULL
|
||||
// and it points to 0 when the string is @"0" (not a falsy value).
|
||||
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
||||
|
||||
if ([commandName isKindOfClass:[NSNumber class]] || [formatter numberFromString:commandName] != NULL) {
|
||||
method = moduleData ? moduleData.methods[[commandName intValue]] : _moduleMethods[[commandName intValue]];
|
||||
} else if ([commandName isKindOfClass:[NSString class]]) {
|
||||
method = moduleData ? moduleData.methodsByName[commandName] : _moduleMethodsByName[commandName];
|
||||
@@ -121,12 +131,14 @@ using namespace facebook::react;
|
||||
NSArray *newArgs = [@[ [NSNumber numberWithInteger:tag] ] arrayByAddingObjectsFromArray:args];
|
||||
|
||||
if (_bridge) {
|
||||
[self _addViewToRegistry:paperView withTag:tag];
|
||||
[_bridge.batchedBridge
|
||||
dispatchBlock:^{
|
||||
[method invokeWithBridge:self->_bridge module:self->_componentData.manager arguments:newArgs];
|
||||
[self->_bridge.uiManager setNeedsLayout];
|
||||
}
|
||||
queue:RCTGetUIManagerQueue()];
|
||||
[self _removeViewFromRegistryWithTag:tag];
|
||||
} else {
|
||||
// TODO T86826778 - Figure out which queue this should be dispatched to.
|
||||
[method invokeWithBridge:nil module:self->_componentData.manager arguments:newArgs];
|
||||
@@ -134,6 +146,41 @@ using namespace facebook::react;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (void)_addViewToRegistry:(UIView *)view withTag:(NSInteger)tag
|
||||
{
|
||||
[self _addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
if ([viewRegistry objectForKey:@(tag)] != NULL) {
|
||||
return;
|
||||
}
|
||||
NSMutableDictionary<NSNumber *, UIView *> *mutableViewRegistry =
|
||||
(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry;
|
||||
[mutableViewRegistry setObject:view forKey:@(tag)];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_removeViewFromRegistryWithTag:(NSInteger)tag
|
||||
{
|
||||
[self _addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
if ([viewRegistry objectForKey:@(tag)] == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary<NSNumber *, UIView *> *mutableViewRegistry =
|
||||
(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry;
|
||||
[mutableViewRegistry removeObjectForKey:@(tag)];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_addUIBlock:(RCTViewManagerUIBlock)block
|
||||
{
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
[_bridge.batchedBridge
|
||||
dispatchBlock:^{
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
[strongSelf->_bridge.uiManager addUIBlock:block];
|
||||
}
|
||||
queue:RCTGetUIManagerQueue()];
|
||||
}
|
||||
|
||||
// This is copy-pasta from RCTModuleData.
|
||||
- (void)_lookupModuleMethodsIfNecessary
|
||||
|
||||
+30
-5
@@ -15,6 +15,19 @@ TextMeasurement ParagraphLayoutManager::measure(
|
||||
AttributedString const &attributedString,
|
||||
ParagraphAttributes const ¶graphAttributes,
|
||||
LayoutConstraints layoutConstraints) const {
|
||||
bool cacheLastTextMeasurement = CoreFeatures::cacheLastTextMeasurement;
|
||||
if (cacheLastTextMeasurement &&
|
||||
(layoutConstraints.maximumSize.width == availableWidth_ ||
|
||||
layoutConstraints.maximumSize.width ==
|
||||
cachedTextMeasurement_.size.width)) {
|
||||
/* Yoga has requested measurement for this size before. Let's use cached
|
||||
* value. `TextLayoutManager` might not have cached this because it could be
|
||||
* using different width to generate cache key. This happens because Yoga
|
||||
* switches between available width and exact width but since we already
|
||||
* know exact width, it is wasteful to calculate it again.
|
||||
*/
|
||||
return cachedTextMeasurement_;
|
||||
}
|
||||
if (CoreFeatures::cacheNSTextStorage) {
|
||||
size_t newHash = folly::hash::hash_combine(
|
||||
0,
|
||||
@@ -28,11 +41,23 @@ TextMeasurement ParagraphLayoutManager::measure(
|
||||
}
|
||||
}
|
||||
|
||||
return textLayoutManager_->measure(
|
||||
AttributedStringBox(attributedString),
|
||||
paragraphAttributes,
|
||||
layoutConstraints,
|
||||
hostTextStorage_);
|
||||
if (cacheLastTextMeasurement) {
|
||||
cachedTextMeasurement_ = textLayoutManager_->measure(
|
||||
AttributedStringBox(attributedString),
|
||||
paragraphAttributes,
|
||||
layoutConstraints,
|
||||
hostTextStorage_);
|
||||
|
||||
availableWidth_ = layoutConstraints.maximumSize.width;
|
||||
|
||||
return cachedTextMeasurement_;
|
||||
} else {
|
||||
return textLayoutManager_->measure(
|
||||
AttributedStringBox(attributedString),
|
||||
paragraphAttributes,
|
||||
layoutConstraints,
|
||||
hostTextStorage_);
|
||||
}
|
||||
}
|
||||
|
||||
LinesMeasurements ParagraphLayoutManager::measureLines(
|
||||
|
||||
+12
@@ -54,6 +54,18 @@ class ParagraphLayoutManager {
|
||||
std::shared_ptr<TextLayoutManager const> mutable textLayoutManager_{};
|
||||
std::shared_ptr<void> mutable hostTextStorage_{};
|
||||
|
||||
/* The width Yoga set as maximum width.
|
||||
* Yoga sometimes calls measure twice with two
|
||||
* different maximum width. One if available space.
|
||||
* The other one is exact space needed for the string.
|
||||
* This happens when node is dirtied but its size is not affected.
|
||||
* To deal with this inefficiency, we cache `TextMeasurement` for each
|
||||
* `ParagraphShadowNode`. If Yoga tries to re-measure with available width
|
||||
* or exact width, we provide it with the cached value.
|
||||
*/
|
||||
Float mutable availableWidth_{};
|
||||
TextMeasurement mutable cachedTextMeasurement_{};
|
||||
|
||||
size_t mutable hash_{};
|
||||
};
|
||||
} // namespace facebook::react
|
||||
|
||||
-21
@@ -369,21 +369,6 @@ void YogaLayoutableShadowNode::updateYogaProps() {
|
||||
YGStyle result{baseStyle};
|
||||
|
||||
// Aliases with precedence
|
||||
if (!props.inset.isUndefined()) {
|
||||
result.position()[YGEdgeAll] = props.inset;
|
||||
}
|
||||
if (!props.insetBlock.isUndefined()) {
|
||||
result.position()[YGEdgeVertical] = props.insetBlock;
|
||||
}
|
||||
if (!props.insetInline.isUndefined()) {
|
||||
result.position()[YGEdgeHorizontal] = props.insetInline;
|
||||
}
|
||||
if (!props.insetInlineEnd.isUndefined()) {
|
||||
result.position()[YGEdgeEnd] = props.insetInlineEnd;
|
||||
}
|
||||
if (!props.insetInlineStart.isUndefined()) {
|
||||
result.position()[YGEdgeStart] = props.insetInlineStart;
|
||||
}
|
||||
if (!props.marginInline.isUndefined()) {
|
||||
result.margin()[YGEdgeHorizontal] = props.marginInline;
|
||||
}
|
||||
@@ -410,12 +395,6 @@ void YogaLayoutableShadowNode::updateYogaProps() {
|
||||
}
|
||||
|
||||
// Aliases without precedence
|
||||
if (CompactValue(result.position()[YGEdgeBottom]).isUndefined()) {
|
||||
result.position()[YGEdgeBottom] = props.insetBlockEnd;
|
||||
}
|
||||
if (CompactValue(result.position()[YGEdgeTop]).isUndefined()) {
|
||||
result.position()[YGEdgeTop] = props.insetBlockStart;
|
||||
}
|
||||
if (CompactValue(result.margin()[YGEdgeTop]).isUndefined()) {
|
||||
result.margin()[YGEdgeTop] = props.marginBlockStart;
|
||||
}
|
||||
|
||||
-49
@@ -136,13 +136,6 @@ void YogaStylableProps::setProp(
|
||||
static const auto defaults = YogaStylableProps{};
|
||||
|
||||
// Aliases
|
||||
RAW_SET_PROP_SWITCH_CASE(inset, "inset");
|
||||
RAW_SET_PROP_SWITCH_CASE(insetBlock, "insetBlock");
|
||||
RAW_SET_PROP_SWITCH_CASE(insetBlockEnd, "insetBlockEnd");
|
||||
RAW_SET_PROP_SWITCH_CASE(insetBlockStart, "insetBlockStart");
|
||||
RAW_SET_PROP_SWITCH_CASE(insetInline, "insetInline");
|
||||
RAW_SET_PROP_SWITCH_CASE(insetInlineEnd, "insetInlineEnd");
|
||||
RAW_SET_PROP_SWITCH_CASE(insetInlineStart, "insetInlineStart");
|
||||
RAW_SET_PROP_SWITCH_CASE(marginInline, "marginInline");
|
||||
RAW_SET_PROP_SWITCH_CASE(marginInlineStart, "marginInlineStart");
|
||||
RAW_SET_PROP_SWITCH_CASE(marginInlineEnd, "marginInlineEnd");
|
||||
@@ -242,48 +235,6 @@ void YogaStylableProps::convertRawPropAliases(
|
||||
const PropsParserContext &context,
|
||||
YogaStylableProps const &sourceProps,
|
||||
RawProps const &rawProps) {
|
||||
inset = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"inset",
|
||||
sourceProps.inset,
|
||||
CompactValue::ofUndefined());
|
||||
insetBlock = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"insetBlock",
|
||||
sourceProps.insetBlock,
|
||||
CompactValue::ofUndefined());
|
||||
insetBlockEnd = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"insetBlockEnd",
|
||||
sourceProps.insetBlockEnd,
|
||||
CompactValue::ofUndefined());
|
||||
insetBlockStart = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"insetBlockStart",
|
||||
sourceProps.insetBlockStart,
|
||||
CompactValue::ofUndefined());
|
||||
insetInline = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"insetInline",
|
||||
sourceProps.insetInline,
|
||||
CompactValue::ofUndefined());
|
||||
insetInlineEnd = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"insetInlineEnd",
|
||||
sourceProps.insetInlineEnd,
|
||||
CompactValue::ofUndefined());
|
||||
insetInlineStart = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"insetInlineStart",
|
||||
sourceProps.insetInlineStart,
|
||||
CompactValue::ofUndefined());
|
||||
marginInline = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
|
||||
@@ -43,11 +43,6 @@ class YogaStylableProps : public Props {
|
||||
|
||||
// Duplicates of existing properties with different names, taking
|
||||
// precedence. E.g. "marginBlock" instead of "marginVertical"
|
||||
CompactValue inset;
|
||||
CompactValue insetInline;
|
||||
CompactValue insetInlineEnd;
|
||||
CompactValue insetInlineStart;
|
||||
|
||||
CompactValue marginInline;
|
||||
CompactValue marginInlineStart;
|
||||
CompactValue marginInlineEnd;
|
||||
@@ -61,10 +56,6 @@ class YogaStylableProps : public Props {
|
||||
// BlockEnd/BlockStart map to top/bottom (no writing mode), but we preserve
|
||||
// Yoga's precedence and prefer specific edges (e.g. top) to ones which are
|
||||
// flow relative (e.g. blockStart).
|
||||
CompactValue insetBlock;
|
||||
CompactValue insetBlockEnd;
|
||||
CompactValue insetBlockStart;
|
||||
|
||||
CompactValue marginBlockStart;
|
||||
CompactValue marginBlockEnd;
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ bool CoreFeatures::enableMapBuffer = false;
|
||||
bool CoreFeatures::blockPaintForUseLayoutEffect = false;
|
||||
bool CoreFeatures::useNativeState = false;
|
||||
bool CoreFeatures::cacheNSTextStorage = false;
|
||||
bool CoreFeatures::cacheLastTextMeasurement = false;
|
||||
bool CoreFeatures::cancelImageDownloadsOnRecycle = false;
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
||||
@@ -39,6 +39,15 @@ class CoreFeatures {
|
||||
// creating it twice. Once when measuring text and once when rendering it.
|
||||
// This flag caches it inside ParagraphState.
|
||||
static bool cacheNSTextStorage;
|
||||
|
||||
// Yoga might measure multiple times the same Text with the same constraints
|
||||
// This flag enables a caching mechanism to avoid subsequents measurements
|
||||
// of the same Text with the same constrainst.
|
||||
static bool cacheLastTextMeasurement;
|
||||
|
||||
// Fabric was not cancelling image downloads when <ImageView /> was removed
|
||||
// from view hierarchy. This feature flag enables this feature.
|
||||
static bool cancelImageDownloadsOnRecycle;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
|
||||
@@ -35,20 +35,24 @@ class ImageRequest final {
|
||||
/*
|
||||
* The move constructor.
|
||||
*/
|
||||
ImageRequest(ImageRequest &&other) noexcept;
|
||||
ImageRequest(ImageRequest &&other) noexcept = default;
|
||||
|
||||
/*
|
||||
* `ImageRequest` does not support copying by design.
|
||||
*/
|
||||
ImageRequest(const ImageRequest &other) = delete;
|
||||
|
||||
~ImageRequest();
|
||||
|
||||
/**
|
||||
* Set cancelation function.
|
||||
*/
|
||||
void setCancelationFunction(std::function<void(void)> cancelationFunction);
|
||||
|
||||
/*
|
||||
* Calls cancel function if one is defined. Should be when downloading
|
||||
* image isn't needed anymore. E.g. <ImageView /> was removed.
|
||||
*/
|
||||
void cancel() const;
|
||||
|
||||
/*
|
||||
* Returns the Image Source associated with the request.
|
||||
*/
|
||||
|
||||
-11
@@ -18,17 +18,6 @@ ImageRequest::ImageRequest(
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
ImageRequest::ImageRequest(ImageRequest &&other) noexcept
|
||||
: imageSource_(std::move(other.imageSource_)),
|
||||
telemetry_(std::move(other.telemetry_)),
|
||||
coordinator_(std::move(other.coordinator_)) {
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
ImageRequest::~ImageRequest() {
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
const ImageResponseObserverCoordinator &ImageRequest::getObserverCoordinator()
|
||||
const {
|
||||
// Not implemented
|
||||
|
||||
+6
-16
@@ -17,27 +17,17 @@ ImageRequest::ImageRequest(
|
||||
coordinator_ = std::make_shared<ImageResponseObserverCoordinator>();
|
||||
}
|
||||
|
||||
ImageRequest::ImageRequest(ImageRequest &&other) noexcept
|
||||
: imageSource_(std::move(other.imageSource_)),
|
||||
telemetry_(std::move(other.telemetry_)),
|
||||
coordinator_(std::move(other.coordinator_)) {
|
||||
other.coordinator_ = nullptr;
|
||||
other.cancelRequest_ = nullptr;
|
||||
other.telemetry_ = nullptr;
|
||||
other.imageSource_ = {};
|
||||
}
|
||||
|
||||
ImageRequest::~ImageRequest() {
|
||||
if (cancelRequest_) {
|
||||
cancelRequest_();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageRequest::setCancelationFunction(
|
||||
std::function<void(void)> cancelationFunction) {
|
||||
cancelRequest_ = cancelationFunction;
|
||||
}
|
||||
|
||||
void ImageRequest::cancel() const {
|
||||
if (cancelRequest_) {
|
||||
cancelRequest_();
|
||||
}
|
||||
}
|
||||
|
||||
const ImageSource &ImageRequest::getImageSource() const {
|
||||
return imageSource_;
|
||||
}
|
||||
|
||||
+5
-1
@@ -12,6 +12,7 @@
|
||||
#include <react/common/mapbuffer/JReadableMapBuffer.h>
|
||||
#include <react/jni/ReadableNativeMap.h>
|
||||
#include <react/renderer/attributedstring/conversions.h>
|
||||
#include <react/renderer/core/CoreFeatures.h>
|
||||
#include <react/renderer/core/conversions.h>
|
||||
#include <react/renderer/mapbuffer/MapBuffer.h>
|
||||
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
|
||||
@@ -146,7 +147,10 @@ Size measureAndroidComponentMapBuffer(
|
||||
TextLayoutManager::TextLayoutManager(
|
||||
const ContextContainer::Shared &contextContainer)
|
||||
: contextContainer_(contextContainer),
|
||||
measureCache_(kSimpleThreadSafeCacheSizeCap) {}
|
||||
measureCache_(
|
||||
CoreFeatures::cacheLastTextMeasurement
|
||||
? 8096
|
||||
: kSimpleThreadSafeCacheSizeCap) {}
|
||||
|
||||
void *TextLayoutManager::getNativeTextLayoutManager() const {
|
||||
return self_;
|
||||
|
||||
+112
-83
@@ -52,47 +52,9 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi
|
||||
textStorage = [self _textStorageForNSAttributesString:attributedString
|
||||
paragraphAttributes:paragraphAttributes
|
||||
size:maximumSize];
|
||||
} else {
|
||||
textStorage.layoutManagers.firstObject.textContainers.firstObject.size = maximumSize;
|
||||
}
|
||||
|
||||
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
|
||||
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
||||
[layoutManager ensureLayoutForTextContainer:textContainer];
|
||||
|
||||
CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;
|
||||
|
||||
size = (CGSize){RCTCeilPixelValue(size.width), RCTCeilPixelValue(size.height)};
|
||||
|
||||
__block auto attachments = TextMeasurement::Attachments{};
|
||||
|
||||
[textStorage
|
||||
enumerateAttribute:NSAttachmentAttributeName
|
||||
inRange:NSMakeRange(0, textStorage.length)
|
||||
options:0
|
||||
usingBlock:^(NSTextAttachment *attachment, NSRange range, BOOL *stop) {
|
||||
if (!attachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize attachmentSize = attachment.bounds.size;
|
||||
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:range inTextContainer:textContainer];
|
||||
|
||||
UIFont *font = [textStorage attribute:NSFontAttributeName atIndex:range.location effectiveRange:nil];
|
||||
|
||||
CGRect frame = {
|
||||
{glyphRect.origin.x,
|
||||
glyphRect.origin.y + glyphRect.size.height - attachmentSize.height + font.descender},
|
||||
attachmentSize};
|
||||
|
||||
auto rect = facebook::react::Rect{
|
||||
facebook::react::Point{frame.origin.x, frame.origin.y},
|
||||
facebook::react::Size{frame.size.width, frame.size.height}};
|
||||
|
||||
attachments.push_back(TextMeasurement::Attachment{rect, false});
|
||||
}];
|
||||
|
||||
return TextMeasurement{{size.width, size.height}, attachments};
|
||||
return [self _measureTextStorage:textStorage];
|
||||
}
|
||||
|
||||
- (TextMeasurement)measureAttributedString:(AttributedString)attributedString
|
||||
@@ -100,10 +62,14 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi
|
||||
layoutConstraints:(LayoutConstraints)layoutConstraints
|
||||
textStorage:(NSTextStorage *_Nullable)textStorage
|
||||
{
|
||||
return [self measureNSAttributedString:[self _nsAttributedStringFromAttributedString:attributedString]
|
||||
paragraphAttributes:paragraphAttributes
|
||||
layoutConstraints:layoutConstraints
|
||||
textStorage:textStorage];
|
||||
if (textStorage) {
|
||||
return [self _measureTextStorage:textStorage];
|
||||
} else {
|
||||
return [self measureNSAttributedString:[self _nsAttributedStringFromAttributedString:attributedString]
|
||||
paragraphAttributes:paragraphAttributes
|
||||
layoutConstraints:layoutConstraints
|
||||
textStorage:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawAttributedString:(AttributedString)attributedString
|
||||
@@ -111,13 +77,33 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi
|
||||
frame:(CGRect)frame
|
||||
textStorage:(NSTextStorage *_Nullable)textStorage
|
||||
{
|
||||
BOOL createdStorageForFrame = NO;
|
||||
|
||||
if (!textStorage) {
|
||||
textStorage = [self textStorageForAttributesString:attributedString
|
||||
paragraphAttributes:paragraphAttributes
|
||||
size:frame.size];
|
||||
createdStorageForFrame = YES;
|
||||
}
|
||||
|
||||
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
|
||||
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
||||
CGPoint origin = frame.origin;
|
||||
|
||||
if (!createdStorageForFrame) {
|
||||
CGRect rect = [layoutManager usedRectForTextContainer:textContainer];
|
||||
static auto threshold = 1.0 / RCTScreenScale() + 0.01; // Size of a pixel plus some small threshold.
|
||||
|
||||
// `rect`'s width is stored in double precesion.
|
||||
// `frame`'s width is also in double precesion but was stored as float in Yoga previously, precesion was lost.
|
||||
if (std::abs(RCTCeilPixelValue(rect.size.width) - frame.size.width) < threshold) {
|
||||
// `textStorage` passed to this method was used to calculate size of frame. If that's the case, it's
|
||||
// width is the same as frame's width. Origin must be adjusted, otherwise glyhps will be painted in wrong
|
||||
// place.
|
||||
// We could create new `NSTextStorage` for the specific frame, but that is expensive.
|
||||
origin.x -= RCTCeilPixelValue(rect.origin.x);
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_OS_MACCATALYST
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
@@ -126,8 +112,8 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi
|
||||
#endif
|
||||
|
||||
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
||||
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin];
|
||||
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin];
|
||||
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:origin];
|
||||
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:origin];
|
||||
|
||||
#if TARGET_OS_MACCATALYST
|
||||
CGContextRestoreGState(context);
|
||||
@@ -178,35 +164,6 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi
|
||||
return paragraphLines;
|
||||
}
|
||||
|
||||
- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)attributedString
|
||||
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
|
||||
size:(CGSize)size
|
||||
{
|
||||
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size];
|
||||
|
||||
textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
|
||||
textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0
|
||||
? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode)
|
||||
: NSLineBreakByClipping;
|
||||
textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines;
|
||||
|
||||
NSLayoutManager *layoutManager = [NSLayoutManager new];
|
||||
layoutManager.usesFontLeading = NO;
|
||||
[layoutManager addTextContainer:textContainer];
|
||||
|
||||
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
|
||||
|
||||
[textStorage addLayoutManager:layoutManager];
|
||||
|
||||
if (paragraphAttributes.adjustsFontSizeToFit) {
|
||||
CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0;
|
||||
CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0;
|
||||
[textStorage scaleFontSizeToFitSize:size minimumFontSize:minimumFontSize maximumFontSize:maximumFontSize];
|
||||
}
|
||||
|
||||
return textStorage;
|
||||
}
|
||||
|
||||
- (NSTextStorage *)textStorageForAttributesString:(AttributedString)attributedString
|
||||
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
|
||||
size:(CGSize)size
|
||||
@@ -246,15 +203,6 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)_nsAttributedStringFromAttributedString:(AttributedString)attributedString
|
||||
{
|
||||
auto sharedNSAttributedString = _cache.get(attributedString, [](AttributedString attributedString) {
|
||||
return wrapManagedObject(RCTNSAttributedStringFromAttributedString(attributedString));
|
||||
});
|
||||
|
||||
return unwrapManagedObject(sharedNSAttributedString);
|
||||
}
|
||||
|
||||
- (void)getRectWithAttributedString:(AttributedString)attributedString
|
||||
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
|
||||
enumerateAttribute:(NSString *)enumerateAttribute
|
||||
@@ -294,4 +242,85 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)attributedString
|
||||
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
|
||||
size:(CGSize)size
|
||||
{
|
||||
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size];
|
||||
|
||||
textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
|
||||
textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0
|
||||
? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode)
|
||||
: NSLineBreakByClipping;
|
||||
textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines;
|
||||
|
||||
NSLayoutManager *layoutManager = [NSLayoutManager new];
|
||||
layoutManager.usesFontLeading = NO;
|
||||
[layoutManager addTextContainer:textContainer];
|
||||
|
||||
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
|
||||
|
||||
[textStorage addLayoutManager:layoutManager];
|
||||
|
||||
if (paragraphAttributes.adjustsFontSizeToFit) {
|
||||
CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0;
|
||||
CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0;
|
||||
[textStorage scaleFontSizeToFitSize:size minimumFontSize:minimumFontSize maximumFontSize:maximumFontSize];
|
||||
}
|
||||
|
||||
return textStorage;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)_nsAttributedStringFromAttributedString:(AttributedString)attributedString
|
||||
{
|
||||
auto sharedNSAttributedString = _cache.get(attributedString, [](AttributedString attributedString) {
|
||||
return wrapManagedObject(RCTNSAttributedStringFromAttributedString(attributedString));
|
||||
});
|
||||
|
||||
return unwrapManagedObject(sharedNSAttributedString);
|
||||
}
|
||||
|
||||
- (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage
|
||||
{
|
||||
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
|
||||
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
||||
[layoutManager ensureLayoutForTextContainer:textContainer];
|
||||
|
||||
CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;
|
||||
|
||||
size = (CGSize){RCTCeilPixelValue(size.width), RCTCeilPixelValue(size.height)};
|
||||
|
||||
__block auto attachments = TextMeasurement::Attachments{};
|
||||
|
||||
[textStorage
|
||||
enumerateAttribute:NSAttachmentAttributeName
|
||||
inRange:NSMakeRange(0, textStorage.length)
|
||||
options:0
|
||||
usingBlock:^(NSTextAttachment *attachment, NSRange range, BOOL *stop) {
|
||||
if (!attachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize attachmentSize = attachment.bounds.size;
|
||||
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:range inTextContainer:textContainer];
|
||||
|
||||
UIFont *font = [textStorage attribute:NSFontAttributeName atIndex:range.location effectiveRange:nil];
|
||||
|
||||
CGRect frame = {
|
||||
{glyphRect.origin.x,
|
||||
glyphRect.origin.y + glyphRect.size.height - attachmentSize.height + font.descender},
|
||||
attachmentSize};
|
||||
|
||||
auto rect = facebook::react::Rect{
|
||||
facebook::react::Point{frame.origin.x, frame.origin.y},
|
||||
facebook::react::Size{frame.size.width, frame.size.height}};
|
||||
|
||||
attachments.push_back(TextMeasurement::Attachment{rect, false});
|
||||
}];
|
||||
|
||||
return TextMeasurement{{size.width, size.height}, attachments};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// This is the top level build.gradle.kts file used when the users
|
||||
// is doing a build from source. It's triggered as the user
|
||||
// will add an `includeBuild(../node_modules/react-native)` in
|
||||
// their settings.gradle.kts file.
|
||||
// More on this here: https://reactnative.dev/contributing/how-to-build-from-source
|
||||
plugins {
|
||||
id("com.android.library") version "7.4.2" apply false
|
||||
id("com.android.application") version "7.4.2" apply false
|
||||
id("de.undercouch.download") version "5.0.1" apply false
|
||||
kotlin("android") version "1.7.22" apply false
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
android.useAndroidX=true
|
||||
@@ -9,14 +9,15 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const {getDefaultConfig} = require('@react-native/metro-config');
|
||||
const {mergeConfig} = require('metro-config');
|
||||
const path = require('path');
|
||||
const getPolyfills = require('./rn-get-polyfills');
|
||||
|
||||
/**
|
||||
* This cli config is needed for development purposes, e.g. for running
|
||||
* integration tests during local development or on CI services.
|
||||
*/
|
||||
module.exports = {
|
||||
const config = {
|
||||
// Make Metro able to resolve required packages that might be imported from /packages/react-native
|
||||
watchFolders: [
|
||||
path.resolve(__dirname, '../../node_modules'),
|
||||
@@ -31,7 +32,6 @@ module.exports = {
|
||||
'react-native': __dirname,
|
||||
},
|
||||
},
|
||||
serializer: {
|
||||
getPolyfills,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native",
|
||||
"version": "1000.0.0",
|
||||
"version": "0.72.0-rc.3",
|
||||
"bin": "./cli.js",
|
||||
"description": "A framework for building native apps using React",
|
||||
"license": "MIT",
|
||||
@@ -79,27 +79,27 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/create-cache-key-function": "^29.2.1",
|
||||
"@react-native-community/cli": "11.0.0-alpha.2",
|
||||
"@react-native-community/cli-platform-android": "11.0.0-alpha.2",
|
||||
"@react-native-community/cli-platform-ios": "11.0.0-alpha.2",
|
||||
"@react-native-community/cli": "11.2.3",
|
||||
"@react-native-community/cli-platform-android": "11.2.3",
|
||||
"@react-native-community/cli-platform-ios": "11.2.3",
|
||||
"@react-native/assets-registry": "^0.72.0",
|
||||
"@react-native/codegen": "^0.72.3",
|
||||
"@react-native/gradle-plugin": "^0.72.5",
|
||||
"@react-native/codegen": "^0.72.4",
|
||||
"@react-native/gradle-plugin": "^0.72.7",
|
||||
"@react-native/js-polyfills": "^0.72.1",
|
||||
"@react-native/normalize-colors": "^0.72.0",
|
||||
"@react-native/virtualized-lists": "^0.72.2",
|
||||
"@react-native/virtualized-lists": "^0.72.4",
|
||||
"abort-controller": "^3.0.0",
|
||||
"anser": "^1.4.9",
|
||||
"base64-js": "^1.1.2",
|
||||
"deprecated-react-native-prop-types": "^4.0.0",
|
||||
"deprecated-react-native-prop-types": "4.1.0",
|
||||
"event-target-shim": "^5.0.1",
|
||||
"flow-enums-runtime": "^0.0.5",
|
||||
"invariant": "^2.2.4",
|
||||
"jest-environment-node": "^29.2.1",
|
||||
"jsc-android": "^250231.0.0",
|
||||
"memoize-one": "^5.0.0",
|
||||
"metro-runtime": "0.75.1",
|
||||
"metro-source-map": "0.75.1",
|
||||
"metro-runtime": "0.76.4",
|
||||
"metro-source-map": "0.76.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nullthrows": "^1.1.1",
|
||||
"pretty-format": "^26.5.2",
|
||||
@@ -108,7 +108,7 @@
|
||||
"react-refresh": "^0.4.0",
|
||||
"react-shallow-renderer": "^16.15.0",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"scheduler": "^0.23.0",
|
||||
"scheduler": "0.24.0-canary-efb381bbf-20230505",
|
||||
"stacktrace-parser": "^0.1.10",
|
||||
"use-sync-external-store": "^1.0.0",
|
||||
"whatwg-fetch": "^3.0.0",
|
||||
@@ -133,4 +133,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,14 @@ require_relative "./test_utils/CodegenUtilsMock.rb"
|
||||
require_relative "./test_utils/CodegenScriptPhaseExtractorMock.rb"
|
||||
require_relative "./test_utils/FileUtilsMock.rb"
|
||||
|
||||
# mocking the min_ios_version_supported function
|
||||
# as it is not possible to require the original react_native_pod
|
||||
# without incurring in circular deps
|
||||
# TODO: move `min_ios_version_supported` to utils.rb
|
||||
def min_ios_version_supported
|
||||
return '12.4'
|
||||
end
|
||||
|
||||
class CodegenUtilsTests < Test::Unit::TestCase
|
||||
:base_path
|
||||
|
||||
@@ -527,7 +535,7 @@ class CodegenUtilsTests < Test::Unit::TestCase
|
||||
'source' => { :git => '' },
|
||||
'header_mappings_dir' => './',
|
||||
'platforms' => {
|
||||
'ios' => '11.0',
|
||||
'ios' => '12.4',
|
||||
},
|
||||
'source_files' => "**/*.{h,mm,cpp}",
|
||||
'pod_target_xcconfig' => {
|
||||
|
||||
@@ -82,7 +82,7 @@ class FlipperTests < Test::Unit::TestCase
|
||||
assert_equal(config.build_settings['SWIFT_VERSION'], '4.1')
|
||||
end
|
||||
|
||||
reactCore_target = installer.target_with_name("React-Core")
|
||||
reactCore_target = installer.target_with_name("React-RCTAppDelegate")
|
||||
reactCore_target.build_configurations.each do |config|
|
||||
if config.name == 'Debug' || config.name == 'CustomConfig' then
|
||||
assert_equal(config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'], ['$(inherited)', 'FB_SONARKIT_ENABLED=1'])
|
||||
@@ -144,6 +144,14 @@ class FlipperTests < Test::Unit::TestCase
|
||||
BuildConfigurationMock.new("Release", is_debug: false),
|
||||
BuildConfigurationMock.new("CustomConfig", is_debug: true),
|
||||
]
|
||||
),
|
||||
TargetMock.new(
|
||||
"React-RCTAppDelegate",
|
||||
[
|
||||
BuildConfigurationMock.new("Debug", is_debug: true),
|
||||
BuildConfigurationMock.new("Release", is_debug: false),
|
||||
BuildConfigurationMock.new("CustomConfig", is_debug: true),
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -105,7 +105,9 @@ class JSEngineTests < Test::Unit::TestCase
|
||||
assert_equal($podInvocation["React-jsi"][:path], "../../ReactCommon/jsi")
|
||||
assert_equal($podInvocation["React-hermes"][:path], "../../ReactCommon/hermes")
|
||||
assert_equal($podInvocation["libevent"][:version], "~> 2.1.12")
|
||||
assert_equal($podInvocation["hermes-engine"][:podspec], "../../sdks/hermes/hermes-engine.podspec")
|
||||
hermes_engine_pod_invocation = $podInvocation["hermes-engine"]
|
||||
assert_equal(hermes_engine_pod_invocation[:podspec], "../../sdks/hermes-engine/hermes-engine.podspec")
|
||||
assert_equal(hermes_engine_pod_invocation[:tag], "")
|
||||
end
|
||||
|
||||
def test_setupHermes_installsPods_installsFabricSubspecWhenFabricEnabled
|
||||
@@ -118,7 +120,9 @@ class JSEngineTests < Test::Unit::TestCase
|
||||
# Assert
|
||||
assert_equal($podInvocationCount, 4)
|
||||
assert_equal($podInvocation["React-jsi"][:path], "../../ReactCommon/jsi")
|
||||
assert_equal($podInvocation["hermes-engine"][:podspec], "../../sdks/hermes/hermes-engine.podspec")
|
||||
hermes_engine_pod_invocation = $podInvocation["hermes-engine"]
|
||||
assert_equal(hermes_engine_pod_invocation[:podspec], "../../sdks/hermes-engine/hermes-engine.podspec")
|
||||
assert_equal(hermes_engine_pod_invocation[:tag], "")
|
||||
assert_equal($podInvocation["React-hermes"][:path], "../../ReactCommon/hermes")
|
||||
assert_equal($podInvocation["libevent"][:version], "~> 2.1.12")
|
||||
end
|
||||
|
||||
@@ -26,7 +26,7 @@ def podSpy_cleanUp
|
||||
$podInvocationCount = 0
|
||||
end
|
||||
|
||||
def pod(name, version = nil, path: nil, configurations: nil, modular_headers: nil, podspec: nil)
|
||||
def pod(name, version = nil, path: nil, configurations: nil, modular_headers: nil, podspec: nil, tag: nil)
|
||||
$podInvocationCount += 1
|
||||
params = {}
|
||||
if version != nil then params[:version] = version end
|
||||
@@ -34,5 +34,6 @@ def pod(name, version = nil, path: nil, configurations: nil, modular_headers: ni
|
||||
if configurations != nil then params[:configurations] = configurations end
|
||||
if modular_headers != nil then params[:modular_headers] = modular_headers end
|
||||
if podspec != nil then params[:podspec] = podspec end
|
||||
if tag != nil then params[:tag] = tag end
|
||||
$podInvocation[name] = params
|
||||
end
|
||||
|
||||
@@ -677,74 +677,6 @@ class UtilsTests < Test::Unit::TestCase
|
||||
assert_equal(config.build_settings["OTHER_CFLAGS"], "$(inherited)")
|
||||
end
|
||||
end
|
||||
|
||||
# ============================= #
|
||||
# Test - Enable Hermes Profiler #
|
||||
# ============================= #
|
||||
|
||||
def test_enableHermesProfiler_whenEnableHermesProfileIsTrue_setsFlagsInRelease
|
||||
# Arrange
|
||||
first_target = prepare_target("FirstTarget")
|
||||
second_target = prepare_target("SecondTarget")
|
||||
third_target = prepare_target("ThirdTarget", "com.apple.product-type.bundle")
|
||||
user_project_mock = UserProjectMock.new("a/path", [
|
||||
prepare_config("Debug"),
|
||||
prepare_config("Release"),
|
||||
],
|
||||
:native_targets => [
|
||||
first_target,
|
||||
second_target
|
||||
]
|
||||
)
|
||||
pods_projects_mock = PodsProjectMock.new([third_target], {"hermes-engine" => {}})
|
||||
installer = InstallerMock.new(pods_projects_mock, [
|
||||
AggregatedProjectMock.new(user_project_mock)
|
||||
])
|
||||
|
||||
# Act
|
||||
ReactNativePodsUtils.enable_hermes_profiler(installer, enable_hermes_profiler: true)
|
||||
|
||||
# Assert
|
||||
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
|
||||
target_installation_result.native_target.build_configurations.each do |config|
|
||||
if config.name != "Release"
|
||||
assert_nil(config.build_settings["OTHER_CFLAGS"])
|
||||
else
|
||||
assert_equal(config.build_settings["OTHER_CFLAGS"], "$(inherited) -DRCT_REMOTE_PROFILE=1")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_enableHermesProfiler_whenEnableHermesProfileIsFalse_doesNothing
|
||||
# Arrange
|
||||
first_target = prepare_target("FirstTarget")
|
||||
second_target = prepare_target("SecondTarget")
|
||||
third_target = prepare_target("ThirdTarget", "com.apple.product-type.bundle")
|
||||
user_project_mock = UserProjectMock.new("a/path", [
|
||||
prepare_config("Debug"),
|
||||
prepare_config("Release"),
|
||||
],
|
||||
:native_targets => [
|
||||
first_target,
|
||||
second_target
|
||||
]
|
||||
)
|
||||
pods_projects_mock = PodsProjectMock.new([third_target], {"hermes-engine" => {}})
|
||||
installer = InstallerMock.new(pods_projects_mock, [
|
||||
AggregatedProjectMock.new(user_project_mock)
|
||||
])
|
||||
|
||||
# Act
|
||||
ReactNativePodsUtils.enable_hermes_profiler(installer)
|
||||
|
||||
# Assert
|
||||
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
|
||||
target_installation_result.native_target.build_configurations.each do |config|
|
||||
assert_nil(config.build_settings["OTHER_CFLAGS"])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ===== #
|
||||
|
||||
@@ -111,7 +111,7 @@ class CodegenUtils
|
||||
'source' => { :git => '' },
|
||||
'header_mappings_dir' => './',
|
||||
'platforms' => {
|
||||
'ios' => '11.0',
|
||||
'ios' => min_ios_version_supported,
|
||||
},
|
||||
'source_files' => "**/*.{h,mm,cpp}",
|
||||
'pod_target_xcconfig' => {
|
||||
|
||||
@@ -77,8 +77,8 @@ def flipper_post_install(installer)
|
||||
end
|
||||
end
|
||||
|
||||
# Enable flipper for React-Core Debug configuration
|
||||
if target.name == 'React-Core'
|
||||
# Enable flipper for React-RCTAppDelegate Debug configuration
|
||||
if target.name == 'React-RCTAppDelegate'
|
||||
target.build_configurations.each do |config|
|
||||
if config.debug?
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = ['$(inherited)', 'FB_SONARKIT_ENABLED=1']
|
||||
|
||||
@@ -30,7 +30,12 @@ def setup_hermes!(react_native_path: "../node_modules/react-native", fabric_enab
|
||||
abort unless prep_status == 0
|
||||
|
||||
pod 'React-jsi', :path => "#{react_native_path}/ReactCommon/jsi"
|
||||
pod 'hermes-engine', :podspec => "#{react_native_path}/sdks/hermes/hermes-engine.podspec"
|
||||
# This `:tag => hermestag` below is only to tell CocoaPods to update hermes-engine when React Native version changes.
|
||||
# We have custom logic to compute the source for hermes-engine. See sdks/hermes-engine/*
|
||||
hermestag_file = File.join(react_native_path, "sdks", ".hermesversion")
|
||||
hermestag = File.exist?(hermestag_file) ? File.read(hermestag_file).strip : ''
|
||||
|
||||
pod 'hermes-engine', :podspec => "#{react_native_path}/sdks/hermes-engine/hermes-engine.podspec", :tag => hermestag
|
||||
pod 'React-hermes', :path => "#{react_native_path}/ReactCommon/hermes"
|
||||
pod 'libevent', '~> 2.1.12'
|
||||
end
|
||||
|
||||
@@ -220,13 +220,6 @@ class ReactNativePodsUtils
|
||||
end
|
||||
end
|
||||
|
||||
def self.enable_hermes_profiler(installer, enable_hermes_profiler: false)
|
||||
return if !enable_hermes_profiler
|
||||
|
||||
Pod::UI.puts "[Hermes Profiler] Enable Hermes Sample profiler"
|
||||
self.add_compiler_flag_to_pods(installer, "-DRCT_REMOTE_PROFILE=1", configuration: "Release")
|
||||
end
|
||||
|
||||
# ========= #
|
||||
# Utilities #
|
||||
# ========= #
|
||||
|
||||
@@ -222,11 +222,10 @@ end
|
||||
# - mac_catalyst_enabled: whether we are running the Pod on a Mac Catalyst project or not.
|
||||
# - enable_hermes_profiler: whether the hermes profiler should be turned on in Release mode
|
||||
def react_native_post_install(
|
||||
installer, react_native_path = "../node_modules/react-native",
|
||||
mac_catalyst_enabled: false,
|
||||
enable_hermes_profiler: false
|
||||
installer,
|
||||
react_native_path = "../node_modules/react-native",
|
||||
mac_catalyst_enabled: false
|
||||
)
|
||||
enable_hermes_profiler = enable_hermes_profiler || ENV["ENABLE_HERMES_PROFILER"] == "1"
|
||||
ReactNativePodsUtils.turn_off_resource_bundle_react_core(installer)
|
||||
|
||||
ReactNativePodsUtils.apply_mac_catalyst_patches(installer) if mac_catalyst_enabled
|
||||
@@ -242,13 +241,11 @@ def react_native_post_install(
|
||||
ReactNativePodsUtils.update_search_paths(installer)
|
||||
ReactNativePodsUtils.set_node_modules_user_settings(installer, react_native_path)
|
||||
ReactNativePodsUtils.apply_flags_for_fabric(installer, fabric_enabled: fabric_enabled)
|
||||
ReactNativePodsUtils.enable_hermes_profiler(installer, enable_hermes_profiler: enable_hermes_profiler)
|
||||
|
||||
NewArchitectureHelper.set_clang_cxx_language_standard_if_needed(installer)
|
||||
is_new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == "1"
|
||||
NewArchitectureHelper.modify_flags_for_new_architecture(installer, is_new_arch_enabled)
|
||||
|
||||
|
||||
Pod::UI.puts "Pod install took #{Time.now.to_i - $START_TIME} [s] to run".green
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
hermes-2023-03-20-RNv0.72.0-49794cfc7c81fb8f69fd60c3bbf85a7480cc5a77
|
||||
@@ -1 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// This is the settings.gradle.kts file used when the users
|
||||
// is doing a build from source. It's triggered as the user
|
||||
// will add an `includeBuild(../node_modules/react-native)` in
|
||||
// their settings.gradle.kts file.
|
||||
// More on this here: https://reactnative.dev/contributing/how-to-build-from-source
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "react-native-github"
|
||||
|
||||
include(":packages:react-native:ReactAndroid")
|
||||
|
||||
project(":packages:react-native:ReactAndroid").projectDir = file("ReactAndroid/")
|
||||
|
||||
include(":packages:react-native:ReactAndroid:hermes-engine")
|
||||
|
||||
project(":packages:react-native:ReactAndroid:hermes-engine").projectDir =
|
||||
file("ReactAndroid/hermes-engine/")
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "com.facebook.react"
|
||||
|
||||
@@ -107,8 +114,6 @@ dependencies {
|
||||
// The version of react-native is set by the React Native Gradle Plugin
|
||||
implementation("com.facebook.react:react-android")
|
||||
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.squareup.okhttp3', module:'okhttp'
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
/**
|
||||
* Metro configuration for React Native
|
||||
* https://github.com/facebook/react-native
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
|
||||
|
||||
module.exports = {
|
||||
transformer: {
|
||||
getTransformOptions: async () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: false,
|
||||
inlineRequires: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Metro configuration
|
||||
* https://facebook.github.io/metro/docs/configuration
|
||||
*
|
||||
* @type {import('metro-config').MetroConfig}
|
||||
*/
|
||||
const config = {};
|
||||
|
||||
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
|
||||
|
||||
@@ -11,20 +11,22 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-native": "1000.0.0"
|
||||
"react-native": "0.72.0-rc.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/preset-env": "^7.20.0",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@react-native/eslint-config": "^0.72.1",
|
||||
"@tsconfig/react-native": "^2.0.2",
|
||||
"@react-native/metro-config": "^0.72.5",
|
||||
"@tsconfig/react-native": "^3.0.0",
|
||||
"@types/metro-config": "^0.76.3",
|
||||
"@types/react": "^18.0.24",
|
||||
"@types/react-test-renderer": "^18.0.0",
|
||||
"babel-jest": "^29.2.1",
|
||||
"eslint": "^8.19.0",
|
||||
"jest": "^29.2.1",
|
||||
"metro-react-native-babel-preset": "0.75.1",
|
||||
"metro-react-native-babel-preset": "0.76.4",
|
||||
"prettier": "^2.4.1",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "4.8.4"
|
||||
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
DrawerLayoutAndroid,
|
||||
DrawerSlideEvent,
|
||||
DynamicColorIOS,
|
||||
EmitterSubscription,
|
||||
EventSubscription,
|
||||
FlatList,
|
||||
FlatListProps,
|
||||
GestureResponderEvent,
|
||||
|
||||
+4
@@ -147,6 +147,10 @@ export * from '../Libraries/Utilities/Platform';
|
||||
export * from '../Libraries/Vibration/Vibration';
|
||||
export * from '../Libraries/YellowBox/YellowBoxDeprecated';
|
||||
export * from '../Libraries/vendor/core/ErrorUtils';
|
||||
export {
|
||||
EmitterSubscription,
|
||||
EventSubscription,
|
||||
} from '../Libraries/vendor/emitter/EventEmitter';
|
||||
|
||||
export * from './public/DeprecatedPropertiesAlias';
|
||||
export * from './public/Insets';
|
||||
|
||||
@@ -30,9 +30,41 @@ RCT_REMAP_VIEW_PROPERTY(opacity, alpha, CGFloat)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(onColorChanged, RCTBubblingEventBlock)
|
||||
|
||||
RCT_CUSTOM_VIEW_PROPERTY(cornerRadius, CGFloat, RNTLegacyView)
|
||||
{
|
||||
view.clipsToBounds = true;
|
||||
NSNumber *cornerRadius = (NSNumber *)json;
|
||||
view.layer.cornerRadius = [cornerRadius floatValue];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(changeBackgroundColor : (nonnull NSNumber *)reactTag color : (NSString *)color)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if (!view || ![view isKindOfClass:[RNTLegacyView class]]) {
|
||||
RCTLogError(@"Cannot find RNTLegacyView with tag #%@", reactTag);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned rgbValue = 0;
|
||||
NSString *colorString = [NSString stringWithCString:std::string([color UTF8String]).c_str()
|
||||
encoding:[NSString defaultCStringEncoding]];
|
||||
NSScanner *scanner = [NSScanner scannerWithString:colorString];
|
||||
[scanner setScanLocation:1]; // bypass '#' character
|
||||
[scanner scanHexInt:&rgbValue];
|
||||
|
||||
UIColor *newColor = [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0
|
||||
green:((rgbValue & 0xFF00) >> 8) / 255.0
|
||||
blue:(rgbValue & 0xFF) / 255.0
|
||||
alpha:1.0];
|
||||
view.backgroundColor = newColor;
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
RNTLegacyView *view = [[RNTLegacyView alloc] init];
|
||||
view.backgroundColor = UIColor.redColor;
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user