Compare commits

..
Author SHA1 Message Date
Simek 04ae39e603 fix C++ in-code docs comments to silence warnings 2025-09-18 10:50:03 +02:00
189 changed files with 2558 additions and 4492 deletions
+1 -1
View File
@@ -98,4 +98,4 @@ untyped-import
untyped-type-import
[version]
^0.285.0
^0.281.0
-3
View File
@@ -17,9 +17,6 @@ runs:
- name: Install node dependencies
uses: ./.github/actions/yarn-install
- name: Set React Native Version
# We don't want to set the version for stable branches, because this has been
# already set from the 'create release' commits on the release branch.
if: ${{ !endsWith(github.ref_name, '-stable') }}
shell: bash
run: node ./scripts/releases/set-rn-artifacts-version.js --build-type ${{ inputs.release-type }}
- name: Setup gradle
+2 -1
View File
@@ -383,7 +383,8 @@ jobs:
runs-on: 8-core-ubuntu
needs: [set_release_type]
container:
image: reactnativecommunity/react-native-android:latest
# Version is pinned to v18.0 to unblock `run_fantom_tests` - see https://github.com/react-native-community/docker-android/pull/242#issuecomment-3280029122
image: reactnativecommunity/react-native-android:v18.0
env:
TERM: "dumb"
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
-1
View File
@@ -133,7 +133,6 @@ vendor/
# Swift Package build folder
/packages/react-native/.build
/packages/react-native/.swiftpm
/packages/react-native/React/includes/
# @react-native/codegen
/packages/react-native/React/FBReactNativeSpec/
+2 -38
View File
@@ -1,40 +1,5 @@
# Changelog
## v0.81.4
### Fixed
#### iOS specific
- Fix regression that skips artifacts code generation ([f6f5ea0b2e](https://github.com/facebook/react-native/commit/f6f5ea0b2eed894725d8f6e8bd045c99c3182026) by [@kitten](https://github.com/kitten))
- Reapplied "Use autolinking-generated react-native-config output in second step of cocoapods linking that generates artifacts and generated source" ([bb73315a3f](https://github.com/facebook/react-native/commit/bb73315a3fd8cd203c4ddb3ffa10bfec307c7e92) by [@kitten](https://github.com/kitten))
## v0.82.0-rc.4
### Fixed
- **Hermes V1:** Fixed coordinates for Hermes V1 artifacts ([674fb77d93](https://github.com/facebook/react-native/commit/674fb77d93dfe67a5fe90449dc71b195b1851e13) by [@j-piasecki](https://github.com/j-piasecki))
#### iOS specific
- **Switch:** Fix Switch layout to work with iOS26 ([91d427fe52](https://github.com/facebook/react-native/commit/91d427fe52a307f9aa8579912ef956d61808dad2) by [@cipolleschi](https://github.com/cipolleschi))
## v0.82.0-rc.3
### Added
- **Hermes V1:** Added a dependency on hermes-compiler ([0caf8e70d5](https://github.com/facebook/react-native/commit/0caf8e70d53f611a73ad9b55879dfe0a5f036371) by [@j-piasecki](https://github.com/j-piasecki))
### Fixed
#### Android specific
- **Hermes V1:** Changed the coordinates of hermes artifacts when using Hermes V1 ([d0fb33822d](https://github.com/facebook/react-native/commit/d0fb33822de38183895c8b3421f46966909beaf3) by [@j-piasecki](https://github.com/j-piasecki))
#### iOS specific
- **Codegen:** Fix regression that skips artifacts code generation ([f6f5ea0b2e](https://github.com/facebook/react-native/commit/f6f5ea0b2eed894725d8f6e8bd045c99c3182026) by [@kitten](https://github.com/kitten))
## v0.81.3
### Fixed
@@ -101,7 +66,6 @@
- **Error Handling:** Unhandled promises are now handled by ExceptionsManager.handleException, instead of being swallowed as Logbox Warnings. ([c4082c9ce2](https://github.com/facebook/react-native/commit/c4082c9ce208a324c2d011823ca2ba432411aafc) by [@krystofwoldrich](https://github.com/krystofwoldrich))
- **`shouldEmitW3CPointerEvents`:** Migrate `shouldPressibilityUseW3CPointerEventsForHover` to common private feature flags and remove `shouldEmitW3CPointerEvents` flag. ([fb4587780e](https://github.com/facebook/react-native/commit/fb4587780e8d6111139d73598a9a26ff392dee28) by [@coado](https://github.com/coado))
- **TurboModuleUtils:** Remove unused ReactCommon/TurboModuleUtils functions #deepCopyJSIObject and #deepCopyJSIArray ([ead669ade3](https://github.com/facebook/react-native/commit/ead669ade31ee703c407f96c0ce98d8f2991bdc8) by [@christophpurrer](https://github.com/christophpurrer))
- **TurboModules:** Delete old location of CallbackWrapper.h / LongLivedObject.h by ([cb94e71](https://github.com/facebook/react-native/commit/cb94e71845259ae7fa823eb4d21cde74fccd7435) [@christophpurrer](https://github.com/christophpurrer))
#### Android specific
@@ -116,7 +80,7 @@
### Added
- **Animated:** `Animated.CompositeAnimation` is now exposed when using `"react-native-strict-api"` ([024d25794a](https://github.com/facebook/react-native/commit/024d25794a51c94c877c1dfa115a82ebbf559614) by [@huntie](https://github.com/huntie))
- **Animated:** `Animated.CompositeAnomation` is now exposed when using `"react-native-strict-api"` ([024d25794a](https://github.com/facebook/react-native/commit/024d25794a51c94c877c1dfa115a82ebbf559614) by [@huntie](https://github.com/huntie))
- **Animated:** Allow calling createAnimatedNode without batching ([d9d9a49e18](https://github.com/facebook/react-native/commit/d9d9a49e18f3c51caa18cf7da0a1fcd62f1ecf18) by [@zeyap](https://github.com/zeyap))
- **Animated:** Allow filter usage with native animated driver. ([138d0eb01d](https://github.com/facebook/react-native/commit/138d0eb01dbe597261459a37d364d1780c3ef228) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
- **API:** Expose NativeComponentRegistry API as JavaScript root export ([f936780cd5](https://github.com/facebook/react-native/commit/f936780cd5c0c17797f9d2bbc8f5cee81c2eefce) by [@zhongwuzw](https://github.com/zhongwuzw))
@@ -153,7 +117,7 @@
- **AGP:** AGP to 8.12.0 ([742ef3d661](https://github.com/facebook/react-native/commit/742ef3d6615c8c1202e9f683e6127ac97d7a9e23) by [@cortinico](https://github.com/cortinico))
- **DevSupportManager:** DevSupport `openDebugger()` methods now accept a `panel: String?` param. Frameworks directly implementing `DevSupportManager` will need to adjust call signatures. ([9dba7112cf](https://github.com/facebook/react-native/commit/9dba7112cfd09b02300869a77dba3dca16f49a28) by [@huntie](https://github.com/huntie))
- **Kotlin:** Migrated TextAttributeProps to Kotlin. You might need to update your property access to use camelCase instead of Hungarian notation. ([fa921b3c7b](https://github.com/facebook/react-native/commit/fa921b3c7b289800a79196468f993a0eb0bf693f) by [@mateoguzmana](https://github.com/mateoguzmana))
- **Kotlin:**Migrated TextAttributeProps to Kotlin. You might need to update your property access to use camelCase instead of Hungarian notation. ([fa921b3c7b](https://github.com/facebook/react-native/commit/fa921b3c7b289800a79196468f993a0eb0bf693f) by [@mateoguzmana](https://github.com/mateoguzmana))
- **Kotlin:** Migrated ReactBaseTextShadowNode to Kotlin. You might need to update your property access to use camelCase instead of Hungarian notation. ([8ccfff9a46](https://github.com/facebook/react-native/commit/8ccfff9a46f317fd78f478c8b3f180441535d1ca) by [@mateoguzmana](https://github.com/mateoguzmana))
- **Kotlin:** Migrated com.facebook.react.bridge.Arguments to Kotlin. ([2534aeaddb](https://github.com/facebook/react-native/commit/2534aeaddb0490b69dfaba6b8d316616c7e10a9c) by [@mateoguzmana](https://github.com/mateoguzmana))
- **Kotlin:** Migrate `YogaConfig` to Kotlin ([4d5caef76b](https://github.com/facebook/react-native/commit/4d5caef76b83eb7e983364ecc81abb6027e5f98e) by [@mateoguzmana](https://github.com/mateoguzmana))
+16 -19
View File
@@ -3057,30 +3057,30 @@ type util$InspectOptions = {
};
declare type util$ParseArgsOption =
| $ReadOnly<{|
| {|
type: 'boolean',
multiple?: false,
short?: string,
default?: boolean,
|}>
| $ReadOnly<{|
|}
| {|
type: 'boolean',
multiple: true,
short?: string,
default?: Array<boolean>,
|}>
| $ReadOnly<{|
|}
| {|
type: 'string',
multiple?: false,
short?: string,
default?: string,
|}>
| $ReadOnly<{|
|}
| {|
type: 'string',
multiple: true,
short?: string,
default?: Array<string>,
|}>;
|};
type util$ParseArgsOptionToValue<TOption> = TOption['type'] extends 'boolean'
? TOption['multiple'] extends true
@@ -3137,7 +3137,7 @@ declare module 'util' {
declare function stripVTControlCharacters(str: string): string;
declare function parseArgs<
TOptions: {+[string]: util$ParseArgsOption} = {||},
TOptions: {[string]: util$ParseArgsOption} = {||},
>(config: {|
args?: Array<string>,
options?: TOptions,
@@ -3995,16 +3995,13 @@ declare class Process extends events$EventEmitter {
initgroups?: (user: number | string, extra_group: number | string) => void;
kill(pid: number, signal?: string | number): void;
mainModule: Object;
memoryUsage: {
(): {
arrayBuffers: number,
rss: number,
heapTotal: number,
heapUsed: number,
external: number,
...
},
rss: () => number,
memoryUsage(): {
arrayBuffers: number,
rss: number,
heapTotal: number,
heapUsed: number,
external: number,
...
};
nextTick: <T>(cb: (...T) => mixed, ...T) => void;
pid: number;
+41
View File
@@ -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.
*
* @flow
* @format
*/
declare module 'electron-store' {
declare export type Schema = any;
declare export type Options = {
+name?: string,
defaults?: Object,
schema?: any,
migrations?: any,
beforeEachMigration?: any,
clearInvalidConfig?: boolean,
serialize?: any,
deserialize?: any,
accessPropertiesByDotNotation?: boolean,
watch?: boolean,
encryptionKey?: string | Buffer | $ReadOnlyArray<number>,
...
};
declare class ElectronStore {
constructor(options?: Options): this;
get(key: string): any;
get(key: string, defaultValue: any): any;
set(key: string, value: any): void;
set(object: Object): void;
has(key: string): boolean;
delete(key: string): void;
clear(): void;
}
declare module.exports: Class<ElectronStore>;
}
+1 -1
View File
@@ -86,7 +86,7 @@
"eslint-plugin-relay": "^1.8.3",
"fb-dotslash": "0.5.8",
"flow-api-translator": "0.32.0",
"flow-bin": "^0.285.0",
"flow-bin": "^0.281.0",
"glob": "^7.1.1",
"hermes-eslint": "0.32.0",
"hermes-transform": "0.32.0",
+2 -2
View File
@@ -1,5 +1,5 @@
@generated SignedSource<<d090ed9a9e781c82884f22944254ed65>>
Git revision: 8b84a77b2c82c670886ef064dfc92d6bce83e579
@generated SignedSource<<0b54f75686e4893a5444839bf621a317>>
Git revision: 5a792db1225adda206313e9bf751198a1ca7851a
Built with --nohooks: false
Is local checkout: false
Remote URL: https://github.com/facebook/react-native-devtools-frontend
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,71 +1,71 @@
#!/usr/bin/env dotslash
// @generated SignedSource<<80974ea6ac4427cf5fd9f14f6779b5c6>>
// @generated SignedSource<<9df662721aea6e3774a8677e2a6065e4>>
{
"name": "React Native DevTools",
"platforms": {
"linux-aarch64": {
"size": 116072551,
"size": 116056584,
"hash": "sha256",
"digest": "3fd69eff02f4a41818a7ecb252ab0de449fa1f88fe0dd1de474bd15dd2e63856",
"digest": "fac3912f10e3c373c874be6c4696f11e05cbaa754c116db6ea72189afae5efe6",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQOBrLd2GhnOLBe4XfcroGD8Uh8odfhSuE5y5f6WSUCPAD93HJyyUbNxvU8rneUBXRi6SjftdBfU9ggRGlEdLVoTt3bp1bS8C370rWXH5oZIPkX87SovgSPmlCQygx8OQIUmShnoHVMSa3xArpSdXRdOvqNrwiOsmBkmxJXm_NycW502xfhcL6A6d38"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQPdlYV_w4y_wxd5Rs8c_F-soLMbY1zwXJjD-JM4TkOtOELyxZGyreTb6dTehp_X6e3qVFZUT05ETRs0MbvqYr4mqdb6dkG9DqNQEVv3TBoHwl7TYSygtHJEs3gXTAtMLYEKW9xXODJlVF-9II7fdTdHU7x7Pyf6NR6S3nv7sKUVD-zKpxF50L2TwUQ"
}
],
"format": "tar.gz",
"path": "React Native DevTools-linux-arm64/React Native DevTools"
},
"linux-x86_64": {
"size": 115924495,
"size": 115929093,
"hash": "sha256",
"digest": "d154ffed0d8a66849da78baebf78e1323ecb8bbeda30d27b82a9b507196266c1",
"digest": "06bbaeb62ae2e0081d184eba42b9f15be0d0b3f4901142b08a8a1430ff83e722",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQPp7lTIJZPh1_Bz1ONneTDc_i5ScY8TLzgHe4DzvYz6e7z-QHdBKhUNbNVBInrYrJaPKPum8FOGcvFW9JsvPNpsdMiAbjivdKQPAEkGOZ-unSOqL1dPPjW-yZYSBGucXmcgQiXVaDhqG3Wcu04JLsFKlKb9QvAZZ-tyN72E8ndAQ0mgDyhzEhwB"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQPENbrxrg0RF_TiyxSY_YVYqC1UfRKj_bMfKb3qHUsIhBlya8n3FkbfJqTXTNdqL4riFRulXS5ecXZprtvk_9kao-zY59r3kiTRwobzF0jnjM507_9UOnEHWzG5ZJYzQyOtS3kQmT0HwZabVj9qw38OB9mk-MPVCZIPZay2PRnIThBzpKDoOOP9"
}
],
"format": "tar.gz",
"path": "React Native DevTools-linux-x64/React Native DevTools"
},
"macos-aarch64": {
"size": 110891525,
"size": 110891603,
"hash": "sha256",
"digest": "b05e1ae5528dcfcff5178d943d90128c9bf28ecd89da4dc303fd4ca748e5fc88",
"digest": "eeb9cc1399c0c38c429848dbf622f1b46e88d7d97788dcdc4c30a9fcce37705c",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQMfoVd8CCoqS0ETntAOKhks2FtIDsyoPEeJHLIc-bxPESxTSHf02xDSSM-vjCG9gBitObq3iZECz1LGwbYS9qrjqXdrf7oljIWzK-eszvQwDe0wzCsywqRXBtLlDBb5h8a1WxjA_scRqoGCDbvk7-XovnJNdmTX4vq0CFb8QUUcEz31sVQ_v5IRjDdF"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQN9bd7nbXi-uF_MooJEiUL2jJVifMqgQxZ236aqbSdiJe5Uzjud5ANf0LDAl8GDVVRShd6x4B-gsZ-qRowH0qJikoGatQIkBgyzp4i8ors52etGOIIwdAxdNIng5Vtp441j2_N__btkJqlHcMMbbqvO8fg9oeYTlhMpOx3MLLWXIlG7ix5IFUDiYChf"
}
],
"format": "tar.gz",
"path": "React Native DevTools.app/Contents/MacOS/React Native DevTools"
},
"macos-x86_64": {
"size": 117767014,
"size": 117770388,
"hash": "sha256",
"digest": "6aa15f6dc13bea46503fcad1b9ba93b3007ea659abb63a2f6bf9b34c3abf834a",
"digest": "a65e446e526502b267cbe6800ae031e4e1b5b0aca21412152a66ffe1d3a29410",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQM57KwrYx0XeV3M8JrX_StgCFVbMHB5L-ShQfzIdoG4jfz71Z7U7CtK5FZ72Uwzm9wmzx5n0-w_3P8DRd24gvMWUSaN8QNdyxIUxd1n0BqJRYsvsbc8w2YpNeWadcce701zV5dYgQg7PIjPzDSuhYWM_7ih_QtZIBIHUUjCtr0sJBUYZVaqBQag6Q"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQN29ukKLZS48QcCU-gms06YFnG-fxxDfrWTup-Z6KoIAP-QEu3sFzrpqeZz4g-jFsh3o-IIoHJsdbu4a2U7ROjPfWGguGJrPK8fdc3iV67Qw1_VAPU13dm0dI1DbSY50ah05wh43jdBG3LGCAYYJ-W8_mZMC3HqM2v-8KVd-DoPT4hOh1s9CCFgEQ"
}
],
"format": "tar.gz",
"path": "React Native DevTools.app/Contents/MacOS/React Native DevTools"
},
"windows-x86_64": {
"size": 125530089,
"size": 125526370,
"hash": "sha256",
"digest": "da1747672dd222b1389f6ad9e1c67778ea0bc4e8859ddee23222a92bcaaed48f",
"digest": "26b190c0f85249dee91999e020b8fe7ffd5c007458a2103ed3822558861dbe87",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQN_2xTT04YUkfSPrA0LaBIHmjUTCCxBkiTgUpb3TUn02lpaFas2x3asL1LeHpZHlUmb_NiLE0PLZYg3T232fdIbmLaquPST7lQpsXDDMG-ykGK7-xxfK5CMU608Pd5Crw5YZBPF3O2PCtiIIcR85tWSqmyWYuoX-PnIhLnBWHjbpNcMcj9RkVqB"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQO0mdIJx1eYiSDJNiAZ2soaeECILayWnvOeiey-fpS4ydDTreAMfwL4QZ5GZo4-AsGwOXCSdF3hBMM2ufnvw6BDFg5UEiTc4DALJu7o6YBBG4wlVZbI-2kkaXd7u8RhBQPjmhsdcX3sDmCGmSr3WK9EkTRKStE2lb6ilOn-969h4dYWekwUUCSX"
}
],
"format": "tar.gz",
+1
View File
@@ -35,6 +35,7 @@
},
"devDependencies": {
"electron": "37.2.6",
"electron-store": "^8.2.0",
"semver": "^7.1.3"
},
"files": [
@@ -10,9 +10,11 @@
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
const {BrowserWindow, Menu, app, shell, ipcMain} = require('electron') as any;
const Store = require('electron-store');
const path = require('path');
const util = require('util');
const appSettings = new Store();
const windowMetadata = new WeakMap<
typeof BrowserWindow,
$ReadOnly<{
@@ -53,10 +55,11 @@ function handleLaunchArgs(argv: string[]) {
}, 1000);
}
} else {
// Create the browser window.
frontendWindow = new BrowserWindow({
width: 1200,
height: 600,
...(getSavedWindowPosition(windowKey) ?? {
width: 1200,
height: 600,
}),
webPreferences: {
partition: 'persist:react-native-devtools',
preload: require.resolve('./preload.js'),
@@ -66,6 +69,8 @@ function handleLaunchArgs(argv: string[]) {
});
// Auto-hide the Windows/Linux menu bar
frontendWindow.setMenuBarVisibility(false);
// Observe and update saved window position
setupWindowResizeListeners(frontendWindow, windowKey);
}
// Open links in the default browser instead of in new Electron windows.
@@ -119,6 +124,37 @@ function configureAppMenu() {
Menu.setApplicationMenu(menu);
}
function getSavedWindowPosition(
windowKey: string,
): ?{width: number, height: number, x?: number, y?: number} {
return appSettings.get('windowArrangements', {})[windowKey];
}
function saveWindowPosition(
windowKey: string,
position: {x: number, y: number, width: number, height: number},
) {
const windowArrangements = appSettings.get('windowArrangements', {});
windowArrangements[windowKey] = position;
appSettings.set('windowArrangements', windowArrangements);
}
function setupWindowResizeListeners(
browserWindow: typeof BrowserWindow,
windowKey: string,
) {
const savePosition = () => {
if (!browserWindow.isDestroyed()) {
const [x, y] = browserWindow.getPosition();
const [width, height] = browserWindow.getSize();
saveWindowPosition(windowKey, {x, y, width, height});
}
};
browserWindow.on('moved', savePosition);
browserWindow.on('resized', savePosition);
browserWindow.on('closed', savePosition);
}
app.whenReady().then(() => {
handleLaunchArgs(process.argv.slice(app.isPackaged ? 1 : 2));
configureAppMenu();
@@ -25,9 +25,7 @@ app.setVersion(pkg.version + '-' + buildInfo.revision);
const {
values: {version = false},
} = util.parseArgs({
options: {
version: {type: 'boolean'},
},
options: {version: {type: 'boolean'}},
args: process.argv.slice(app.isPackaged ? 1 : 2),
strict: false,
});
+6 -18
View File
@@ -30,7 +30,6 @@ async function unstable_spawnDebuggerShellWithArgs(
{
mode = 'detached',
flavor = 'prebuilt',
prebuiltBinaryPath,
}: $ReadOnly<{
// In 'syncAndExit' mode, the current process will block until the spawned process exits, and then it will exit
// with the same exit code as the spawned process.
@@ -38,13 +37,9 @@ async function unstable_spawnDebuggerShellWithArgs(
// continue to run normally.
mode?: 'syncThenExit' | 'detached',
flavor?: DebuggerShellFlavor,
prebuiltBinaryPath?: string,
}> = {},
): Promise<void> {
const [binaryPath, baseArgs] = getShellBinaryAndArgs(
flavor,
prebuiltBinaryPath,
);
const [binaryPath, baseArgs] = getShellBinaryAndArgs(flavor);
return new Promise((resolve, reject) => {
const child = spawn(binaryPath, [...baseArgs, ...args], {
@@ -114,18 +109,15 @@ export type DebuggerShellPreparationResult = $ReadOnly<{
*/
async function unstable_prepareDebuggerShell(
flavor: DebuggerShellFlavor,
{prebuiltBinaryPath}: {prebuiltBinaryPath?: string} = {},
): Promise<DebuggerShellPreparationResult> {
const [binaryPath, baseArgs] = getShellBinaryAndArgs(
flavor,
prebuiltBinaryPath,
);
const [binaryPath, baseArgs] = getShellBinaryAndArgs(flavor);
try {
switch (flavor) {
case 'prebuilt':
const prebuiltResult =
await prepareDebuggerShellFromDotSlashFile(binaryPath);
const prebuiltResult = await prepareDebuggerShellFromDotSlashFile(
DEVTOOLS_BINARY_DOTSLASH_FILE,
);
if (prebuiltResult.code !== 'success') {
return prebuiltResult;
}
@@ -157,14 +149,10 @@ async function unstable_prepareDebuggerShell(
function getShellBinaryAndArgs(
flavor: DebuggerShellFlavor,
prebuiltBinaryPath: ?string,
): [string, Array<string>] {
switch (flavor) {
case 'prebuilt':
return [
require('fb-dotslash'),
[prebuiltBinaryPath ?? DEVTOOLS_BINARY_DOTSLASH_FILE],
];
return [require('fb-dotslash'), [DEVTOOLS_BINARY_DOTSLASH_FILE]];
case 'dev':
return [
// NOTE: Internally at Meta, this is aliased to a workspace that is
@@ -372,6 +372,12 @@ export default class InspectorProxy implements InspectorProxyQueries {
this.#devices.set(deviceId, newDevice);
this.#logger?.info(
"Connection established to app='%s' on device='%s'.",
appName,
deviceName,
);
debug(
"Got new device connection: name='%s', app=%s, device=%s, via=%s",
deviceName,
@@ -444,7 +450,7 @@ export default class InspectorProxy implements InspectorProxyQueries {
);
socket.on('close', (code: number, reason: string) => {
debug(
this.#logger?.info(
"Connection closed to device='%s' for app='%s' with code='%s' and reason='%s'.",
deviceName,
appName,
@@ -520,7 +526,7 @@ export default class InspectorProxy implements InspectorProxyQueries {
throw new Error(INTERNAL_ERROR_MESSAGES.UNREGISTERED_DEVICE);
}
debug(
this.#logger?.info(
"Connection established to DevTools for app='%s' on device='%s'.",
device.getApp() || 'unknown',
device.getName() || 'unknown',
@@ -588,7 +594,7 @@ export default class InspectorProxy implements InspectorProxyQueries {
});
socket.on('close', (code: number, reason: string) => {
debug(
this.#logger?.info(
"Connection closed to DevTools for app='%s' on device='%s' with code='%s' and reason='%s'.",
device.getApp() || 'unknown',
device.getName() || 'unknown',
@@ -191,14 +191,6 @@ internal object DependencyUtils {
"The hermes-android dependency was modified to use the correct Maven group.",
)
)
} else if (hermesV1Enabled) {
dependencySubstitution.add(
Triple(
"com.facebook.react:hermes-android",
hermesVersionString,
"The hermes-android dependency was modified to use Hermes V1.",
)
)
}
return dependencySubstitution
}
@@ -242,7 +242,7 @@ internal fun readPackageJsonFile(
return packageJson?.let { JsonUtils.fromPackageJson(it) }
}
private const val HERMES_COMPILER_NPM_DIR = "node_modules/hermes-compiler/hermesc/%OS-BIN%/"
private const val HERMES_COMPILER_NPM_DIR = "node_modules/hermes-compiler/%OS-BIN%/"
private const val HERMESC_IN_REACT_NATIVE_DIR = "node_modules/react-native/sdks/hermesc/%OS-BIN%/"
private const val HERMESC_BUILT_FROM_SOURCE_DIR =
"node_modules/react-native/ReactAndroid/hermes-engine/build/hermes/bin/"
@@ -165,8 +165,8 @@ class PathUtilsTest {
@Test
@WithOs(OS.MAC)
fun detectOSAwareHermesCommand_withHermesV1Enabled() {
tempFolder.newFolder("node_modules/hermes-compiler/hermesc/osx-bin/")
val expected = tempFolder.newFile("node_modules/hermes-compiler/hermesc/osx-bin/hermesc")
tempFolder.newFolder("node_modules/hermes-compiler/osx-bin/")
val expected = tempFolder.newFile("node_modules/hermes-compiler/osx-bin/hermesc")
assertThat(detectOSAwareHermesCommand(tempFolder.root, "", hermesV1Enabled = true))
.isEqualTo(expected.toString())
@@ -47,7 +47,7 @@ function combineSchemas(
}
return merged;
},
{modules: {} /*:: as SchemaType['modules'] */},
{modules: {}},
);
return {
@@ -540,7 +540,7 @@ function buildEventEmitterSchema(
return {
name: eventemitterName,
optional: Boolean(property.optional),
optional: false,
typeAnnotation: {
type: 'EventEmitterTypeAnnotation',
typeAnnotation: eventTypeAnnotation,
@@ -873,6 +873,56 @@ describe('Animated', () => {
});
});
describe('Animated Interactions', () => {
let Animated; // eslint-disable-line no-shadow
let InteractionManager;
beforeEach(() => {
jest.mock('../../Interaction/InteractionManager');
Animated = require('../Animated').default;
InteractionManager =
require('../../Interaction/InteractionManager').default;
});
afterEach(() => {
jest.unmock('../../Interaction/InteractionManager');
});
it('registers an interaction by default', () => {
// $FlowFixMe[prop-missing]
InteractionManager.createInteractionHandle.mockReturnValue(777);
const value = new Animated.Value(0);
const callback = jest.fn();
Animated.timing(value, {
toValue: 100,
duration: 100,
useNativeDriver: false,
}).start(callback);
jest.runAllTimers();
expect(InteractionManager.createInteractionHandle).toBeCalled();
expect(InteractionManager.clearInteractionHandle).toBeCalledWith(777);
expect(callback).toBeCalledWith({finished: true});
});
it('does not register an interaction when specified', () => {
const value = new Animated.Value(0);
const callback = jest.fn();
Animated.timing(value, {
toValue: 100,
duration: 100,
isInteraction: false,
useNativeDriver: false,
}).start(callback);
jest.runAllTimers();
expect(InteractionManager.createInteractionHandle).not.toBeCalled();
expect(InteractionManager.clearInteractionHandle).not.toBeCalled();
expect(callback).toBeCalledWith({finished: true});
});
});
describe('Animated Tracking', () => {
it('should track values', () => {
const value1 = new Animated.Value(0);
@@ -14,10 +14,8 @@ import FlatList, {type FlatListProps} from '../../Lists/FlatList';
import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
export default createAnimatedComponent(FlatList) as $FlowFixMe as component<
// $FlowExpectedError[unclear-type]
ItemT = any,
>(
// $FlowExpectedError[unclear-type]
export default (createAnimatedComponent(FlatList): component<ItemT = any>(
ref?: React.RefSetter<FlatList<ItemT>>,
...props: AnimatedProps<FlatListProps<ItemT>>
);
));
@@ -14,10 +14,9 @@ import Image from '../../Image/Image';
import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
export default (createAnimatedComponent<
$FlowFixMe,
React.ElementRef<typeof Image>,
>((Image: $FlowFixMe)): AnimatedComponentType<
export default (createAnimatedComponent(
(Image: $FlowFixMe),
): AnimatedComponentType<
React.ElementConfig<typeof Image>,
React.ElementRef<typeof Image>,
>);
@@ -15,7 +15,7 @@ import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
// $FlowFixMe[incompatible-type]
export default createAnimatedComponent(SectionList) as $FlowFixMe as component<
export default (createAnimatedComponent(SectionList): component<
// $FlowExpectedError[unclear-type]
ItemT = any,
// $FlowExpectedError[unclear-type]
@@ -23,4 +23,4 @@ export default createAnimatedComponent(SectionList) as $FlowFixMe as component<
>(
ref?: React.RefSetter<SectionList<ItemT, SectionT>>,
...props: AnimatedProps<SectionListProps<ItemT, SectionT>>
);
));
@@ -14,10 +14,6 @@ import Text, {type TextProps} from '../../Text/Text';
import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
export default (createAnimatedComponent<
$FlowFixMe,
React.ElementRef<typeof Text>,
>((Text: $FlowFixMe)): AnimatedComponentType<
TextProps,
React.ElementRef<typeof Text>,
>);
export default (createAnimatedComponent(
(Text: $FlowFixMe),
): AnimatedComponentType<TextProps, React.ElementRef<typeof Text>>);
@@ -15,8 +15,7 @@ import View from '../../Components/View/View';
import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
// $FlowFixMe[incompatible-type]
export default createAnimatedComponent(View) as AnimatedComponentType<
export default (createAnimatedComponent(View): AnimatedComponentType<
ViewProps,
React.ElementRef<typeof View>,
>;
>);
@@ -63,29 +63,17 @@ type PassThroughProps = $ReadOnly<{
passthroughAnimatedPropExplicitValues?: ViewProps | null,
}>;
type LooseOmit<O: interface {}, K: $Keys<$FlowFixMe>> = Pick<
O,
Exclude<$Keys<O>, K>,
>;
export type AnimatedProps<Props: {...}> = {
[K in keyof Props]: K extends NonAnimatedProps
? Props[K]
: WithAnimatedValue<Props[K]>,
} & PassThroughProps;
export type AnimatedProps<Props: {...}> = LooseOmit<
{
[K in keyof Props]: K extends NonAnimatedProps
? Props[K]
: WithAnimatedValue<Props[K]>,
},
'ref',
> &
PassThroughProps;
export type AnimatedBaseProps<Props: {...}> = LooseOmit<
{
[K in keyof Props]: K extends NonAnimatedProps
? Props[K]
: WithAnimatedValue<Props[K]>,
},
'ref',
>;
export type AnimatedBaseProps<Props: {...}> = {
[K in keyof Props]: K extends NonAnimatedProps
? Props[K]
: WithAnimatedValue<Props[K]>,
};
export type AnimatedComponentType<Props: {...}, +Instance = mixed> = component(
ref?: React.RefSetter<Instance>,
@@ -18,6 +18,7 @@ import type {AnimatedNodeConfig} from './AnimatedNode';
import type AnimatedTracking from './AnimatedTracking';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import InteractionManager from '../../Interaction/InteractionManager';
import AnimatedInterpolation from './AnimatedInterpolation';
import AnimatedWithChildren from './AnimatedWithChildren';
@@ -311,6 +312,10 @@ export default class AnimatedValue extends AnimatedWithChildren {
* See https://reactnative.dev/docs/animatedvalue#animate
*/
animate(animation: Animation, callback: ?EndCallback): void {
let handle = null;
if (animation.__isInteraction) {
handle = InteractionManager.createInteractionHandle();
}
const previousAnimation = this._animation;
this._animation && this._animation.stop();
this._animation = animation;
@@ -323,6 +328,9 @@ export default class AnimatedValue extends AnimatedWithChildren {
},
result => {
this._animation = null;
if (handle !== null) {
InteractionManager.clearInteractionHandle(handle);
}
callback && callback(result);
},
previousAnimation,
@@ -1690,7 +1690,6 @@ class ScrollView extends React.Component<ScrollViewProps, ScrollViewState> {
return (
<StickyHeaderComponent
key={key}
/* $FlowFixMe[incompatible-type] */
ref={ref => this._setStickyHeaderRef(key, ref)}
nextHeaderLayoutY={this._headerLayoutYs.get(
this._getKeyForIndex(nextIndex, children),
@@ -44,7 +44,7 @@ interface Instance extends React.ElementRef<typeof Animated.View> {
}
const ScrollViewStickyHeader: component(
ref?: React.RefSetter<Instance>,
ref: React.RefSetter<Instance>,
...props: ScrollViewStickyHeaderProps
) = function ScrollViewStickyHeader({
ref: forwardedRef,
@@ -282,7 +282,7 @@ const ScrollViewStickyHeader: component(
const passthroughAnimatedPropExplicitValues =
isFabric && translateY != null
? {
style: {transform: [{translateY}]},
style: {transform: [{translateY: translateY}]},
}
: null;
@@ -303,20 +303,20 @@ const ScrollViewStickyHeader: component(
passthroughAnimatedPropExplicitValues
}>
{cloneElement(child, {
onLayout: undefined, // we call this manually through our this._onLayout
style: styles.fill, // We transfer the child style to the wrapper.
onLayout: undefined, // we call this manually through our this._onLayout
})}
</Animated.View>
);
};
const styles = StyleSheet.create({
fill: {
flex: 1,
},
header: {
zIndex: 10,
},
fill: {
flex: 1,
},
});
export default ScrollViewStickyHeader;
@@ -449,12 +449,6 @@ function InternalTextInput(props: TextInputProps): React.Node {
before we can get to the long term breaking change.
*/
if (instance != null) {
// Register the input immediately when the ref is set so that focus()
// can be called from ref callbacks
// Double registering during useLayoutEffect is fine, because the underlying
// state is a Set.
TextInputState.registerInput(instance);
// $FlowFixMe[prop-missing] - See the explanation above.
// $FlowFixMe[unsafe-object-assign]
Object.assign(instance, {
@@ -47,14 +47,15 @@ class TouchableBounce extends React.Component<
_createPressabilityConfig(): PressabilityConfig {
return {
android_disableSound: this.props.touchSoundDisabled,
cancelable: !this.props.rejectResponderTermination,
disabled: this.props.disabled,
hitSlop: this.props.hitSlop,
delayLongPress: this.props.delayLongPress,
delayPressIn: this.props.delayPressIn,
delayPressOut: this.props.delayPressOut,
disabled: this.props.disabled,
hitSlop: this.props.hitSlop,
minPressDuration: 0,
pressRectOffset: this.props.pressRetentionOffset,
android_disableSound: this.props.touchSoundDisabled,
onBlur: event => {
if (Platform.isTV) {
this._bounceTo(1, 0.4, 0);
@@ -112,7 +113,6 @@ class TouchableBounce extends React.Component<
this.props.onPressOut(event);
}
},
pressRectOffset: this.props.pressRetentionOffset,
};
}
@@ -123,10 +123,10 @@ class TouchableBounce extends React.Component<
callback?: ?() => void,
) {
Animated.spring(this.state.scale, {
bounciness,
toValue,
useNativeDriver: true,
velocity,
bounciness,
useNativeDriver: true,
}).start(callback);
}
@@ -230,6 +230,6 @@ export default (function TouchableBounceWrapper({
}) {
return <TouchableBounce {...props} hostRef={hostRef} />;
} as component(
ref?: React.RefSetter<mixed>,
ref: React.RefSetter<mixed>,
...props: $ReadOnly<Omit<TouchableBounceProps, 'hostRef'>>
));
@@ -21,14 +21,12 @@ const ViewNativeComponent: HostComponent<Props> =
}));
interface NativeCommands {
+focus: (viewRef: HostInstance) => void;
+blur: (viewRef: HostInstance) => void;
+hotspotUpdate: (viewRef: HostInstance, x: number, y: number) => void;
+setPressed: (viewRef: HostInstance, pressed: boolean) => void;
}
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['focus', 'blur', 'hotspotUpdate', 'setPressed'],
supportedCommands: ['hotspotUpdate', 'setPressed'],
});
/**
+21 -21
View File
@@ -39,24 +39,6 @@ function DebuggingOverlay({
useImperativeHandle(
ref,
() => ({
clearElementsHighlight() {
if (!isNativeComponentReady) {
return;
}
if (nativeComponentRef.current != null) {
Commands.clearElementsHighlights(nativeComponentRef.current);
}
},
highlightElements(elements) {
if (!isNativeComponentReady) {
return;
}
if (nativeComponentRef.current != null) {
Commands.highlightElements(nativeComponentRef.current, elements);
}
},
highlightTraceUpdates(updates) {
if (!isNativeComponentReady) {
return;
@@ -73,6 +55,24 @@ function DebuggingOverlay({
);
}
},
highlightElements(elements) {
if (!isNativeComponentReady) {
return;
}
if (nativeComponentRef.current != null) {
Commands.highlightElements(nativeComponentRef.current, elements);
}
},
clearElementsHighlight() {
if (!isNativeComponentReady) {
return;
}
if (nativeComponentRef.current != null) {
Commands.clearElementsHighlights(nativeComponentRef.current);
}
},
}),
[],
);
@@ -95,14 +95,14 @@ function DebuggingOverlay({
const styles = StyleSheet.create({
overlay: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
},
});
export default DebuggingOverlay as component(
ref?: React.RefSetter<DebuggingOverlayHandle>,
ref: React.RefSetter<DebuggingOverlayHandle>,
);
+6 -3
View File
@@ -8,8 +8,11 @@
* @format
*/
import type {HostInstance} from '../..';
import type {AbstractImageAndroid, AbstractImageIOS} from './ImageTypes.flow';
import type {
AbstractImageAndroid,
AbstractImageIOS,
ImageType as ImageComponent,
} from './ImageTypes.flow';
import useMergeRefs from '../Utilities/useMergeRefs';
import * as React from 'react';
@@ -30,7 +33,7 @@ export function unstable_getImageComponentDecorator(): ?ImageComponentDecorator
return injectedImageComponentDecorator;
}
type ImageInstance = HostInstance;
type ImageInstance = React.ElementRef<ImageComponent>;
type ImageAttachedCallback = (
imageInstance: ImageInstance,
@@ -8,11 +8,12 @@
* @format
*/
import type {HostInstance} from '../..';
import type {RootTag} from '../Types/RootTagTypes';
import type {ResolvedAssetSource} from './AssetSourceResolver';
import type {ImageProps as ImagePropsType} from './ImageProps';
import type {ImageSource} from './ImageSource';
import typeof ImageViewNativeComponent from './ImageViewNativeComponent';
import typeof TextInlineImageNativeComponent from './TextInlineImageNativeComponent';
import * as React from 'react';
@@ -66,14 +67,17 @@ type ImageComponentStaticsAndroid = $ReadOnly<{
}>;
export type AbstractImageAndroid = component(
ref?: React.RefSetter<HostInstance>,
ref?: React.RefSetter<
| React.ElementRef<TextInlineImageNativeComponent>
| React.ElementRef<ImageViewNativeComponent>,
>,
...props: ImagePropsType
);
export type ImageAndroid = AbstractImageAndroid & ImageComponentStaticsAndroid;
export type AbstractImageIOS = component(
ref?: React.RefSetter<HostInstance>,
ref?: React.RefSetter<React.ElementRef<ImageViewNativeComponent>>,
...props: ImagePropsType
);
+51 -6
View File
@@ -12,6 +12,7 @@
import type {GestureResponderEvent} from '../Types/CoreEventTypes';
const InteractionManager = require('./InteractionManager').default;
const TouchHistoryMath = require('./TouchHistoryMath').default;
const currentCentroidXOfTouchesChangedAfter =
@@ -30,6 +31,9 @@ const currentCentroidY = TouchHistoryMath.currentCentroidY;
* single-touch gestures resilient to extra touches, and can be used to
* recognize simple multi-touch gestures.
*
* By default, `PanResponder` holds an `InteractionManager` handle to block
* long-running JS events from interrupting active gestures.
*
* It provides a predictable wrapper of the responder handlers provided by the
* [gesture responder system](docs/gesture-responder-system.html).
* For each handler, it provides a new `gestureState` object alongside the
@@ -401,6 +405,9 @@ const PanResponder = {
getInteractionHandle: () => ?number,
panHandlers: GestureResponderHandlerMethods,
} {
const interactionState = {
handle: (null: ?number),
};
const gestureState: PanResponderGestureState = {
// Useful for debugging
stateID: Math.random(),
@@ -457,6 +464,10 @@ const PanResponder = {
},
onResponderGrant(event: GestureResponderEvent): boolean {
if (!interactionState.handle) {
interactionState.handle =
InteractionManager.createInteractionHandle();
}
gestureState.x0 = currentCentroidX(event.touchHistory);
gestureState.y0 = currentCentroidY(event.touchHistory);
gestureState.dx = 0;
@@ -471,11 +482,21 @@ const PanResponder = {
},
onResponderReject(event: GestureResponderEvent): void {
config.onPanResponderReject?.call(undefined, event, gestureState);
clearInteractionHandle(
interactionState,
config.onPanResponderReject,
event,
gestureState,
);
},
onResponderRelease(event: GestureResponderEvent): void {
config.onPanResponderRelease?.call(undefined, event, gestureState);
clearInteractionHandle(
interactionState,
config.onPanResponderRelease,
event,
gestureState,
);
PanResponder._initializeGestureState(gestureState);
},
@@ -508,11 +529,21 @@ const PanResponder = {
onResponderEnd(event: GestureResponderEvent): void {
const touchHistory = event.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
config.onPanResponderEnd?.call(undefined, event, gestureState);
clearInteractionHandle(
interactionState,
config.onPanResponderEnd,
event,
gestureState,
);
},
onResponderTerminate(event: GestureResponderEvent): void {
config.onPanResponderTerminate?.call(undefined, event, gestureState);
clearInteractionHandle(
interactionState,
config.onPanResponderTerminate,
event,
gestureState,
);
PanResponder._initializeGestureState(gestureState);
},
@@ -525,13 +556,27 @@ const PanResponder = {
return {
panHandlers,
getInteractionHandle(): ?number {
// TODO: Deprecate and delete this method.
return null;
return interactionState.handle;
},
};
},
};
function clearInteractionHandle(
interactionState: {handle: ?number, ...},
callback: ?(ActiveCallback | PassiveCallback),
event: GestureResponderEvent,
gestureState: PanResponderGestureState,
) {
if (interactionState.handle) {
InteractionManager.clearInteractionHandle(interactionState.handle);
interactionState.handle = null;
}
if (callback) {
callback(event, gestureState);
}
}
export type PanResponderInstance = ReturnType<(typeof PanResponder)['create']>;
export default PanResponder;
@@ -129,13 +129,11 @@ export function getWithFallback_DEPRECATED<Config: {...}>(
// `getRuntimeConfig == null` when static view configs are disabled
// If `setRuntimeConfigProvider` is not configured, use native reflection.
if (hasNativeViewConfig(name)) {
/* $FlowFixMe[incompatible-type] Extra ref prop */
return get<Config>(name, viewConfigProvider);
}
} else {
// If there is no runtime config, then the native component is unavailable.
if (getRuntimeConfig(name) != null) {
/* $FlowFixMe[incompatible-type] Extra ref prop */
return get<Config>(name, viewConfigProvider);
}
}
@@ -14,7 +14,6 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type {HostInstance} from 'react-native';
import ReactNativeElement from '../../../../src/private/webapis/dom/nodes/ReactNativeElement';
import TextInput from '../../../Components/TextInput/TextInput';
import TextInputState from '../../../Components/TextInput/TextInputState';
import View from '../../../Components/View/View';
import ReactFabricHostComponent from '../ReactFabricHostComponent';
@@ -49,7 +48,7 @@ describe('ReactFabricPublicInstance', () => {
const nodeRef = createRef<HostInstance>();
Fantom.runTask(() => {
root.render(<TextInput ref={nodeRef} />);
root.render(<View ref={nodeRef} />);
});
const node = nullthrows(nodeRef.current);
@@ -74,7 +73,7 @@ describe('ReactFabricPublicInstance', () => {
const ref = createRef<HostInstance>();
Fantom.runTask(() => {
root.render(<TextInput ref={ref} />);
root.render(<View ref={ref} />);
});
const node = nullthrows(ref.current);
+7 -15
View File
@@ -14,37 +14,29 @@ import NativeDevLoadingView from './NativeDevLoadingView';
const COLOR_SCHEME = {
dark: {
refresh: {
backgroundColor: '#2584e8',
textColor: '#ffffff',
},
load: {
backgroundColor: '#fafafa',
textColor: '#242526',
},
refresh: {
backgroundColor: '#2584e8',
textColor: '#ffffff',
},
error: {
backgroundColor: '#1065AF',
textColor: '#ffffff',
},
},
default: {
load: {
backgroundColor: '#404040',
textColor: '#ffffff',
},
refresh: {
backgroundColor: '#2584e8',
textColor: '#ffffff',
},
error: {
backgroundColor: '#1065AF',
load: {
backgroundColor: '#404040',
textColor: '#ffffff',
},
},
};
export default {
showMessage(message: string, type: 'load' | 'refresh' | 'error') {
showMessage(message: string, type: 'load' | 'refresh') {
if (NativeDevLoadingView) {
const colorScheme =
getColorScheme() === 'dark' ? COLOR_SCHEME.dark : COLOR_SCHEME.default;
+4 -7
View File
@@ -232,6 +232,8 @@ Error: ${e.message}`;
});
client.on('error', data => {
DevLoadingView.hide();
if (data.type === 'GraphNotFoundError') {
client.close();
setHMRUnavailableReason(
@@ -251,6 +253,8 @@ Error: ${e.message}`;
});
client.on('close', closeEvent => {
DevLoadingView.hide();
// https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
// https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5
const isNormalOrUnsetCloseReason =
@@ -292,17 +296,10 @@ function setHMRUnavailableReason(reason: string) {
}
hmrUnavailableReason = reason;
const DevLoadingView = require('./DevLoadingView').default;
DevLoadingView.hide();
// We only want to show a warning if Fast Refresh is on *and* if we ever
// previously managed to connect successfully. We don't want to show
// the warning to native engineers who use cached bundles without Metro.
if (hmrClient.isEnabled() && didConnect) {
DevLoadingView.showMessage(
'Fast Refresh disconnected. Reload app to reconnect.',
'error',
);
console.warn(reason);
// (Not using the `warning` module to prevent a Buck cycle.)
}
+1 -1
View File
@@ -161,7 +161,7 @@ let reactJsInspectorTracing = RNTarget(
name: .reactJsInspectorTracing,
path: "ReactCommon/jsinspector-modern/tracing",
excludedPaths: ["tests"],
dependencies: [.reactNativeDependencies, .reactFeatureFlags, .reactJsInspectorNetwork, .jsi, .reactOSCompat]
dependencies: [.reactNativeDependencies, .reactFeatureFlags, .jsi, .reactOSCompat]
)
/// React-jsinspectornetwork.podspec
@@ -380,11 +380,6 @@ static RCTBridge *RCTCurrentBridgeInstance = nil;
moduleProvider:(RCTBridgeModuleListProvider)block
launchOptions:(NSDictionary *)launchOptions
{
// Only enabld this assertion in OSS
#if COCOAPODS
[RCTBridge throwIfOnLegacyArch];
#endif
if (self = [super init]) {
RCTEnforceNewArchitectureValidation(RCTNotAllowedInBridgeless, self, nil);
_delegate = delegate;
@@ -398,17 +393,6 @@ static RCTBridge *RCTCurrentBridgeInstance = nil;
return self;
}
// Wrap the exception throwing in a static method to avoid the warning: "The code following the exception will never be
// executed". This might create build failures internally where we treat warnings as errors.
+ (void)throwIfOnLegacyArch
{
@throw [NSException
exceptionWithName:NSInternalInconsistencyException
reason:
@"You are trying to initialize the legacy architecture. This is not supported anymore and will be removed in the next version of React Native. Please use the New Architecture instead."
userInfo:nil];
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (void)dealloc
@@ -12,7 +12,7 @@
#import <React/RCTEventEmitter.h>
RCT_EXTERN void RCTEnableAppearancePreference(BOOL enabled);
RCT_EXTERN void RCTOverrideAppearancePreference(NSString *);
RCT_EXTERN void RCTOverrideAppearancePreference(NSString *const);
RCT_EXTERN void RCTUseKeyWindowForSystemStyle(BOOL useMainScreen);
RCT_EXTERN NSString *RCTCurrentOverrideAppearancePreference(void);
RCT_EXTERN NSString *RCTColorSchemePreference(UITraitCollection *traitCollection);
@@ -85,6 +85,14 @@ RCT_EXPORT_MODULE()
dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), self->_initialMessageBlock);
}
- (void)hideBannerAfter:(CGFloat)delay
{
// Cancel previous hide call after the delay.
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hide) object:nil];
// Set new hide call after a delay.
[self performSelector:@selector(hide) withObject:nil afterDelay:delay];
}
- (void)showMessage:(NSString *)message color:(UIColor *)color backgroundColor:(UIColor *)backgroundColor
{
if (!RCTDevLoadingViewGetEnabled() || _hiding) {
@@ -120,9 +128,6 @@ RCT_EXPORT_MODULE()
self->_container = [[UIView alloc] init];
self->_container.backgroundColor = backgroundColor;
self->_container.translatesAutoresizingMaskIntoConstraints = NO;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hide)];
[self->_container addGestureRecognizer:tapGesture];
self->_container.userInteractionEnabled = YES;
self->_label = [[UILabel alloc] init];
self->_label.translatesAutoresizingMaskIntoConstraints = NO;
@@ -153,6 +158,8 @@ RCT_EXPORT_MODULE()
[self->_label.centerXAnchor constraintEqualToAnchor:self->_container.centerXAnchor],
[self->_label.bottomAnchor constraintEqualToAnchor:self->_container.bottomAnchor constant:-5],
]];
[self hideBannerAfter:15.0];
});
}
@@ -365,14 +365,11 @@ RCT_EXPORT_MODULE()
handler:^(__unused UIAlertAction *action) {
[weakSelf setDefaultJSBundle];
}]];
UIAlertAction *configCancelAction =
[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:^(__unused UIAlertAction *action) {
return;
}];
[configCancelAction setValue:[UIColor systemRedColor] forKey:@"titleTextColor"];
[alertController addAction:configCancelAction];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:^(__unused UIAlertAction *action) {
return;
}]];
[RCTPresentedViewController() presentViewController:alertController animated:YES completion:NULL];
}]];
@@ -393,11 +390,6 @@ RCT_EXPORT_METHOD(show)
_actionSheet = [UIAlertController alertControllerWithTitle:@"React Native Dev Menu" message:nil preferredStyle:style];
NSAttributedString *title =
[[NSAttributedString alloc] initWithString:@"React Native Dev Menu"
attributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:17]}];
[_actionSheet setValue:title forKey:@"_attributedTitle"];
NSArray<RCTDevMenuItem *> *items = [self _menuItemsToPresent];
for (RCTDevMenuItem *item in items) {
UIAlertAction *action = [UIAlertAction actionWithTitle:item.title
@@ -407,11 +399,9 @@ RCT_EXPORT_METHOD(show)
[_actionSheet addAction:action];
}
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:[self alertActionHandlerForDevItem:nil]];
[cancelAction setValue:[UIColor systemRedColor] forKey:@"titleTextColor"];
[_actionSheet addAction:cancelAction];
[_actionSheet addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:[self alertActionHandlerForDevItem:nil]]];
_presentedItems = items;
[RCTPresentedViewController() presentViewController:_actionSheet animated:YES completion:nil];
@@ -650,9 +650,7 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
BOOL isPointInside = [self pointInside:point withEvent:event];
UIView *currentContainerView = self.currentContainerView;
BOOL clipsToBounds = currentContainerView.clipsToBounds;
BOOL clipsToBounds = self.currentContainerView.clipsToBounds;
clipsToBounds = clipsToBounds || _layoutMetrics.overflowInset == EdgeInsets{};
@@ -660,8 +658,8 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
return nil;
}
for (UIView *subview = nullptr in [currentContainerView.subviews reverseObjectEnumerator]) {
UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:currentContainerView] withEvent:event];
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
if (hitView) {
return hitView;
}
@@ -861,7 +859,7 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
if (_useCustomContainerView) {
if (!_containerView) {
_containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
_containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
for (UIView *subview = nullptr in effectiveContentView.subviews) {
[_containerView addSubview:subview];
}
@@ -1542,62 +1540,6 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
}
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
if ([commandName isEqualToString:@"focus"]) {
[self focus];
return;
}
if ([commandName isEqualToString:@"blur"]) {
[self blur];
return;
}
}
- (void)focus
{
[self becomeFirstResponder];
}
- (void)blur
{
[self resignFirstResponder];
}
#pragma mark - Focus Events
- (BOOL)becomeFirstResponder
{
if (![super becomeFirstResponder]) {
return NO;
}
if (_eventEmitter && ReactNativeFeatureFlags::enableImperativeFocus()) {
_eventEmitter->onFocus();
}
return YES;
}
- (BOOL)resignFirstResponder
{
if (![super resignFirstResponder]) {
return NO;
}
if (_eventEmitter && ReactNativeFeatureFlags::enableImperativeFocus()) {
_eventEmitter->onBlur();
}
return YES;
}
@end
#ifdef __cplusplus
@@ -61,7 +61,7 @@ RCT_EXTERN CGPathRef RCTPathCreateWithRoundedRect(
CGRect bounds,
RCTCornerInsets cornerInsets,
const CGAffineTransform *transform,
BOOL inverted);
const BOOL inverted);
/**
* Draw a CSS-compliant border as an image. You can determine if it's scalable
@@ -1892,7 +1892,6 @@ public final class com/facebook/react/devsupport/DefaultDevLoadingViewImplementa
public fun <init> (Lcom/facebook/react/devsupport/ReactInstanceDevHelper;)V
public fun hide ()V
public fun showMessage (Ljava/lang/String;)V
public fun showMessage (Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;)V
public fun updateProgress (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;)V
}
@@ -2120,7 +2119,6 @@ public abstract interface class com/facebook/react/devsupport/interfaces/DevBund
public abstract interface class com/facebook/react/devsupport/interfaces/DevLoadingViewManager {
public abstract fun hide ()V
public abstract fun showMessage (Ljava/lang/String;)V
public abstract fun showMessage (Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;)V
public abstract fun updateProgress (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;)V
}
@@ -5566,7 +5564,7 @@ public final class com/facebook/react/views/scroll/ReactHorizontalScrollContaine
public final class com/facebook/react/views/scroll/ReactHorizontalScrollContainerViewManager$Companion {
}
public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android/widget/HorizontalScrollView, android/view/View$OnLayoutChangeListener, android/view/ViewGroup$OnHierarchyChangeListener, com/facebook/react/uimanager/ReactClippingViewGroup, com/facebook/react/uimanager/ReactOverflowViewWithInset, com/facebook/react/views/scroll/ReactAccessibleScrollView, com/facebook/react/views/scroll/ReactScrollViewHelper$HasFlingAnimator, com/facebook/react/views/scroll/ReactScrollViewHelper$HasScrollEventThrottle, com/facebook/react/views/scroll/ReactScrollViewHelper$HasScrollState, com/facebook/react/views/scroll/ReactScrollViewHelper$HasSmoothScroll, com/facebook/react/views/scroll/ReactScrollViewHelper$HasStateWrapper, com/facebook/react/views/scroll/VirtualViewContainer {
public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android/widget/HorizontalScrollView, android/view/View$OnLayoutChangeListener, android/view/ViewGroup$OnHierarchyChangeListener, com/facebook/react/uimanager/ReactClippingViewGroup, com/facebook/react/uimanager/ReactOverflowViewWithInset, com/facebook/react/views/scroll/ReactAccessibleScrollView, com/facebook/react/views/scroll/ReactScrollViewHelper$HasFlingAnimator, com/facebook/react/views/scroll/ReactScrollViewHelper$HasScrollEventThrottle, com/facebook/react/views/scroll/ReactScrollViewHelper$HasScrollState, com/facebook/react/views/scroll/ReactScrollViewHelper$HasSmoothScroll, com/facebook/react/views/scroll/ReactScrollViewHelper$HasStateWrapper {
public fun <init> (Landroid/content/Context;)V
public fun <init> (Landroid/content/Context;Lcom/facebook/react/views/scroll/FpsListener;)V
public fun abortAnimation ()V
@@ -5596,7 +5594,6 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android
public fun getScrollEnabled ()Z
public fun getScrollEventThrottle ()I
public fun getStateWrapper ()Lcom/facebook/react/uimanager/StateWrapper;
public fun getVirtualViewContainerState ()Lcom/facebook/react/views/scroll/VirtualViewContainerState;
protected fun handleInterceptedTouchEvent (Landroid/view/MotionEvent;)V
public fun isPartiallyScrolledInView (Landroid/view/View;)Z
protected fun onAttachedToWindow ()V
@@ -328,15 +328,6 @@ public class ReactInstanceManager {
}
registerCxxErrorHandlerFunc();
// Using `if (true)` just to prevent tests / lint errors.
if (true) {
// Legacy architecture of React Native is deprecated and can't be initialized anymore.
// More details on:
// https://github.com/react-native-community/discussions-and-proposals/blob/nc/legacy-arch-removal/proposals/0929-legacy-architecture-removal.md
throw new UnsupportedOperationException(
"ReactInstanceManager.createReactContext is unsupported.");
}
}
private ReactInstanceDevHelper createDevHelperInterface() {
@@ -1455,7 +1446,6 @@ public class ReactInstanceManager {
*/
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
FLog.d(ReactConstants.TAG, "ReactInstanceManager.createReactContext()");
ReactMarker.logMarker(CREATE_REACT_CONTEXT_START, jsExecutor.getName());
@@ -196,11 +196,9 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
private val nodesManagerRef = AtomicReference<NativeAnimatedNodesManager?>()
private var batchingControlledByJS = false // TODO T71377544: delete
@Volatile private var currentFrameNumber: Long = 0
@Volatile private var currentFrameNumber: Long = 0 // TODO T71377544: delete
@Volatile private var currentBatchNumber: Long = 0
@Volatile private var currentBatchNumber: Long = 0 // frame number at last operations dispatch
private var initializedForFabric = false
private var initializedForNonFabric = false
@@ -282,22 +280,19 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
var batchNumber = currentBatchNumber - 1
// TODO T71377544: delete this when the JS method is confirmed safe
if (!batchingControlledByJS) {
// The problem we're trying to solve here: we could be in the middle of queueing
// a batch of related animation operations when Fabric flushes a batch of MountItems.
// It's visually bad if we execute half of the animation ops and then wait another frame
// (or more) to execute the rest.
// See mFrameNumber. If the dispatchedFrameNumber drifts too far - that
// is, if no MountItems are scheduled for a while, which can happen if a tree
// is committed but there are no changes - bring these counts back in sync and
// execute any queued operations. This number is arbitrary, but we want it low
// enough that the user shouldn't be able to see this delay in most cases.
currentFrameNumber++
if ((currentFrameNumber - currentBatchNumber) > 2) {
currentBatchNumber = currentFrameNumber
batchNumber = currentBatchNumber
}
// The problem we're trying to solve here: we could be in the middle of queueing
// a batch of related animation operations when Fabric flushes a batch of MountItems.
// It's visually bad if we execute half of the animation ops and then wait another frame
// (or more) to execute the rest.
// See mFrameNumber. If the dispatchedFrameNumber drifts too far - that
// is, if no MountItems are scheduled for a while, which can happen if a tree
// is committed but there are no changes - bring these counts back in sync and
// execute any queued operations. This number is arbitrary, but we want it low
// enough that the user shouldn't be able to see this delay in most cases.
currentFrameNumber++
if ((currentFrameNumber - currentBatchNumber) > 2) {
currentBatchNumber = currentFrameNumber
batchNumber = currentBatchNumber
}
preOperations.executeBatch(batchNumber, nodesManager)
@@ -484,14 +479,14 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
}
}
@Suppress("DEPRECATION")
override fun startOperationBatch() {
batchingControlledByJS = true
currentBatchNumber++
// no-op
}
@Suppress("DEPRECATION")
override fun finishOperationBatch() {
batchingControlledByJS = false
currentBatchNumber++
// no-op
}
override fun createAnimatedNode(tagDouble: Double, config: ReadableMap) {
@@ -774,12 +774,8 @@ public class NativeAnimatedNodesManager(
("Looks like animated nodes graph has ${reason}, there are $activeNodesCount but toposort visited only $updatedNodesCount")
)
if (eventListenerInitializedForFabric && cyclesDetected == 0) {
// TODO T71377544: investigate these SoftExceptions and see if we can remove entirely
// or fix the root cause
ReactSoftExceptionLogger.logSoftException(TAG, ReactNoCrashSoftException(ex))
} else if (eventListenerInitializedForFabric) {
// TODO T71377544: investigate these SoftExceptions and see if we can remove entirely
// or fix the root cause
ReactSoftExceptionLogger.logSoftException(TAG, ReactNoCrashSoftException(ex))
} else {
throw ex
@@ -98,7 +98,13 @@ public object DefaultNewArchitectureEntryPoint {
ReactNativeFeatureFlags.override(ReactNativeFeatureFlagsOverrides_RNOSS_Canary_Android())
}
ReleaseLevel.STABLE -> {
ReactNativeFeatureFlags.override(ReactNativeFeatureFlagsOverrides_RNOSS_Stable_Android())
ReactNativeFeatureFlags.override(
ReactNativeFeatureFlagsOverrides_RNOSS_Stable_Android(
fabricEnabled,
bridgelessEnabled,
turboModulesEnabled,
)
)
}
}
@@ -119,16 +125,6 @@ public object DefaultNewArchitectureEntryPoint {
privateConcurrentReactEnabled = featureFlags.enableFabricRenderer()
privateBridgelessEnabled = featureFlags.enableBridgelessArchitecture()
val (isValid, errorMessage) =
isConfigurationValid(
privateTurboModulesEnabled,
privateFabricEnabled,
privateBridgelessEnabled,
)
if (!isValid) {
error(errorMessage)
}
DefaultSoLoader.maybeLoadSoLibrary()
}
@@ -162,13 +158,13 @@ public object DefaultNewArchitectureEntryPoint {
fabricEnabled: Boolean,
bridgelessEnabled: Boolean,
): Pair<Boolean, String> =
if (!turboModulesEnabled || !fabricEnabled || !bridgelessEnabled) {
false to
"You cannot load React Native with the New Architecture disabled. " +
"Please use DefaultNewArchitectureEntryPoint.load() instead of " +
"DefaultNewArchitectureEntryPoint.load(turboModulesEnabled=$turboModulesEnabled, " +
"fabricEnabled=$fabricEnabled, bridgelessEnabled=$bridgelessEnabled)"
} else {
true to ""
when {
fabricEnabled && !turboModulesEnabled ->
false to
"fabricEnabled=true requires turboModulesEnabled=true (is now false) - Please update your DefaultNewArchitectureEntryPoint.load() parameters."
bridgelessEnabled && (!turboModulesEnabled || !fabricEnabled) ->
false to
"bridgelessEnabled=true requires (turboModulesEnabled=true AND fabricEnabled=true) - Please update your DefaultNewArchitectureEntryPoint.load() parameters."
else -> true to ""
}
}
@@ -33,14 +33,10 @@ public class DefaultDevLoadingViewImplementation(
private var devLoadingPopup: PopupWindow? = null
override fun showMessage(message: String) {
showMessage(message, color = null, backgroundColor = null)
}
override fun showMessage(message: String, color: Double?, backgroundColor: Double?) {
if (!isEnabled) {
return
}
UiThreadUtil.runOnUiThread { showInternal(message, color, backgroundColor) }
UiThreadUtil.runOnUiThread { showInternal(message) }
}
override fun updateProgress(status: String?, done: Int?, total: Int?) {
@@ -63,7 +59,7 @@ public class DefaultDevLoadingViewImplementation(
}
}
private fun showInternal(message: String, color: Double?, backgroundColor: Double?) {
private fun showInternal(message: String) {
if (devLoadingPopup?.isShowing == true) {
// already showing
return
@@ -88,19 +84,13 @@ public class DefaultDevLoadingViewImplementation(
currentActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.dev_loading_view, null) as TextView
view.text = message
if (color != null) {
view.setTextColor(color.toInt())
}
if (backgroundColor != null) {
view.setBackgroundColor(backgroundColor.toInt())
}
view.setOnClickListener { hideInternal() }
val popup =
PopupWindow(
view,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
)
popup.isTouchable = false
popup.showAtLocation(currentActivity.window.decorView, Gravity.NO_GRAVITY, 0, topOffset)
devLoadingView = view
devLoadingPopup = popup
@@ -78,6 +78,7 @@ import java.io.File
import java.net.MalformedURLException
import java.net.URL
import java.util.Locale
import javax.inject.Provider
public abstract class DevSupportManagerBase(
protected val applicationContext: Context,
@@ -184,7 +185,6 @@ public abstract class DevSupportManagerBase(
}
private var perfMonitorOverlayManager: PerfMonitorOverlayManager? = null
private var perfMonitorInitialized = false
private var tracingStateProvider: TracingStateProvider? = null
init {
@@ -222,6 +222,13 @@ public abstract class DevSupportManagerBase(
perfMonitorOverlayManager =
PerfMonitorOverlayManager(
reactInstanceDevHelper,
Provider {
val context = reactInstanceDevHelper.currentActivity
if (context == null || context.isFinishing) {
return@Provider null
}
context
},
{ openDebugger(DebuggerFrontendPanelName.PERFORMANCE.toString()) },
)
}
@@ -545,16 +552,14 @@ public abstract class DevSupportManagerBase(
}
override fun onNewReactContextCreated(reactContext: ReactContext) {
if (!perfMonitorInitialized && reactInstanceDevHelper is PerfMonitorDevHelper) {
resetCurrentContext(reactContext)
if (reactInstanceDevHelper is PerfMonitorDevHelper) {
perfMonitorOverlayManager?.let { manager ->
reactInstanceDevHelper.inspectorTarget?.addPerfMonitorListener(manager)
}
perfMonitorOverlayManager?.enable()
perfMonitorOverlayManager?.startBackgroundTrace()
perfMonitorInitialized = true
}
resetCurrentContext(reactContext)
}
override fun onReactInstanceDestroyed(reactContext: ReactContext) {
@@ -872,6 +877,8 @@ public abstract class DevSupportManagerBase(
devLoadingViewManager?.showMessage("Reloading...")
}
perfMonitorOverlayManager?.reset()
devServerHelper.openPackagerConnection(
javaClass.simpleName,
object : PackagerCommandListener {
@@ -921,7 +928,7 @@ public abstract class DevSupportManagerBase(
hideRedboxDialog()
hideDevOptionsDialog()
devLoadingViewManager?.hide()
perfMonitorOverlayManager?.disable()
perfMonitorOverlayManager?.reset()
devServerHelper.closePackagerConnection()
}
@@ -11,8 +11,6 @@ package com.facebook.react.devsupport.interfaces
public interface DevLoadingViewManager {
public fun showMessage(message: String)
public fun showMessage(message: String, color: Double?, backgroundColor: Double?)
public fun updateProgress(status: String?, done: Int?, total: Int?)
public fun hide()
@@ -7,15 +7,11 @@
package com.facebook.react.devsupport.perfmonitor
import android.app.Activity
/**
* Interface implemented by [com.facebook.react.runtime.ReactHostImplDevHelper] exposing additional
* hooks used to implement the V2 Perf Monitor overlay (experimental).
*/
internal interface PerfMonitorDevHelper {
public val currentActivity: Activity?
/**
* The inspector target object. Matches the lifetime of the ReactHost. May be null if modern JS
* debugging is disabled.
@@ -7,24 +7,42 @@
package com.facebook.react.devsupport.perfmonitor
import android.content.Context
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.devsupport.interfaces.TracingState
import javax.inject.Provider
internal class PerfMonitorOverlayManager(
private val devHelper: PerfMonitorDevHelper,
private val contextProvider: Provider<Context?>,
private val onRequestOpenDevTools: () -> Unit,
) : PerfMonitorUpdateListener {
private var enabled: Boolean = false
private var initialized: Boolean = false
private var view: PerfMonitorOverlayView? = null
private var tracingState: TracingState = TracingState.ENABLEDINCDPMODE
/** Enable the Perf Monitor overlay. */
private fun init() {
if (initialized || !enabled) {
return
}
UiThreadUtil.runOnUiThread {
val context = contextProvider.get() ?: return@runOnUiThread
view = PerfMonitorOverlayView(context, ::handleRecordingButtonPress)
// Start background tracing
devHelper.inspectorTarget?.resumeBackgroundTrace()
view?.show()
initialized = true
}
}
/** Enable the Perf Monitor overlay. Will be shown when updates are received. */
fun enable() {
enabled = true
UiThreadUtil.runOnUiThread {
val context = devHelper.currentActivity ?: return@runOnUiThread
view = PerfMonitorOverlayView(context, ::handleRecordingButtonPress)
}
init()
}
/** Disable the Perf Monitor overlay. Will remain hidden when updates are received. */
@@ -34,31 +52,24 @@ internal class PerfMonitorOverlayManager(
enabled = false
}
/** Start background trace recording. */
fun startBackgroundTrace() {
if (!enabled) {
return
}
devHelper.inspectorTarget?.let { target ->
target.resumeBackgroundTrace()
onRecordingStateChanged(target.getTracingState())
}
/** Reset the Perf Monitor overlay, e.g. after a reload. */
fun reset() {
// Update with current recording state
onRecordingStateChanged(
devHelper.inspectorTarget?.getTracingState() ?: TracingState.ENABLEDINCDPMODE
)
}
override fun onRecordingStateChanged(state: TracingState) {
tracingState = state
UiThreadUtil.runOnUiThread {
view?.updateRecordingState(state)
view?.show()
}
view?.updateRecordingState(state)
}
private fun handleRecordingButtonPress() {
when (tracingState) {
TracingState.ENABLEDINBACKGROUNDMODE -> {
devHelper.inspectorTarget?.let { target ->
if (!target.pauseAndAnalyzeBackgroundTrace()) {
devHelper.inspectorTarget?.let {
if (!it.pauseAndAnalyzeBackgroundTrace()) {
onRequestOpenDevTools()
}
}
@@ -55,11 +55,11 @@ internal class PerfMonitorOverlayView(
if (state == TracingState.ENABLEDINBACKGROUNDMODE) {
(statusIndicator.background as GradientDrawable).setColor(Color.RED)
statusLabel.text = "Profiling Active"
statusLabel.text = "Background Profiling Active"
tooltipLabel.text = "Press ☰ to open"
} else {
(statusIndicator.background as GradientDrawable).setColor(Color.GRAY)
statusLabel.text = "Profiling Stopped"
statusLabel.text = "Background Profiling Stopped"
tooltipLabel.text = "Press ☰ to restart"
}
dialog.show()
@@ -164,7 +164,7 @@ internal class PerfMonitorOverlayView(
shape = GradientDrawable.RECTANGLE
setColor(Color.BLACK)
cornerRadius = dpToPx(14.5f)
alpha = (0.4 * 255).toInt()
alpha = (0.8 * 255).toInt()
setStroke(dpToPx(1f).toInt(), COLOR_OVERLAY_BORDER)
}
showDividers = LinearLayout.SHOW_DIVIDER_MIDDLE
@@ -821,9 +821,6 @@ public class SurfaceMountingManager {
@UnstableReactNativeAPI
public void experimental_prefetchResources(
int surfaceId, String componentName, MapBuffer params) {
if (isStopped()) {
return;
}
mViewManagerRegistry
.get(componentName)
.experimental_prefetchResources(
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<6b632ac7553ae149fa42a67efde5acfa>>
* @generated SignedSource<<f089964c958dfcac00f83c3371ccbefc>>
*/
/**
@@ -180,12 +180,6 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableImagePrefetchingAndroid(): Boolean = accessor.enableImagePrefetchingAndroid()
/**
* When enabled, Android will build and initiate image prefetch requests on ImageShadowNode::layout and batch them together in a single JNI call
*/
@JvmStatic
public fun enableImagePrefetchingJNIBatchingAndroid(): Boolean = accessor.enableImagePrefetchingJNIBatchingAndroid()
/**
* When enabled, Android will initiate image prefetch requested on ImageShadowNode::layout on the UI thread
*/
@@ -198,12 +192,6 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = accessor.enableImmediateUpdateModeForContentOffsetChanges()
/**
* Enable ref.focus() and ref.blur() for all views, not just TextInput.
*/
@JvmStatic
public fun enableImperativeFocus(): Boolean = accessor.enableImperativeFocus()
/**
* This is to fix the issue with interop view manager where component descriptor lookup is causing ViewManager to preload.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<c228bdbf6120b4715f49e03c04918a7c>>
* @generated SignedSource<<912dec895495052328e7e52b94a6738a>>
*/
/**
@@ -45,10 +45,8 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableImagePrefetchingAndroidCache: Boolean? = null
private var enableImagePrefetchingJNIBatchingAndroidCache: Boolean? = null
private var enableImagePrefetchingOnUiThreadAndroidCache: Boolean? = null
private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null
private var enableImperativeFocusCache: Boolean? = null
private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
@@ -321,15 +319,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableImagePrefetchingJNIBatchingAndroid(): Boolean {
var cached = enableImagePrefetchingJNIBatchingAndroidCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableImagePrefetchingJNIBatchingAndroid()
enableImagePrefetchingJNIBatchingAndroidCache = cached
}
return cached
}
override fun enableImagePrefetchingOnUiThreadAndroid(): Boolean {
var cached = enableImagePrefetchingOnUiThreadAndroidCache
if (cached == null) {
@@ -348,15 +337,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableImperativeFocus(): Boolean {
var cached = enableImperativeFocusCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableImperativeFocus()
enableImperativeFocusCache = cached
}
return cached
}
override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean {
var cached = enableInteropViewManagerClassLookUpOptimizationIOSCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<26dce1945df5641ab84cbca9eaf2b10f>>
* @generated SignedSource<<ba1e53d93b9fdaf298a034543bc44a57>>
*/
/**
@@ -78,14 +78,10 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableImagePrefetchingAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun enableImagePrefetchingJNIBatchingAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun enableImagePrefetchingOnUiThreadAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean
@DoNotStrip @JvmStatic public external fun enableImperativeFocus(): Boolean
@DoNotStrip @JvmStatic public external fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnAndroid(): Boolean
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<11051ece1b61fd4bf4ca003a3b7fc4f9>>
* @generated SignedSource<<7601cbcde75ff19ef0fe67f6faef2549>>
*/
/**
@@ -73,14 +73,10 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableImagePrefetchingAndroid(): Boolean = false
override fun enableImagePrefetchingJNIBatchingAndroid(): Boolean = false
override fun enableImagePrefetchingOnUiThreadAndroid(): Boolean = false
override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = false
override fun enableImperativeFocus(): Boolean = false
override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean = false
override fun enableLayoutAnimationsOnAndroid(): Boolean = false
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<66fa583c37021750123a483ab0ccb030>>
* @generated SignedSource<<3a13f8e35423b634ed4ef46fbac3c1e9>>
*/
/**
@@ -49,10 +49,8 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableImagePrefetchingAndroidCache: Boolean? = null
private var enableImagePrefetchingJNIBatchingAndroidCache: Boolean? = null
private var enableImagePrefetchingOnUiThreadAndroidCache: Boolean? = null
private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null
private var enableImperativeFocusCache: Boolean? = null
private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
@@ -350,16 +348,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableImagePrefetchingJNIBatchingAndroid(): Boolean {
var cached = enableImagePrefetchingJNIBatchingAndroidCache
if (cached == null) {
cached = currentProvider.enableImagePrefetchingJNIBatchingAndroid()
accessedFeatureFlags.add("enableImagePrefetchingJNIBatchingAndroid")
enableImagePrefetchingJNIBatchingAndroidCache = cached
}
return cached
}
override fun enableImagePrefetchingOnUiThreadAndroid(): Boolean {
var cached = enableImagePrefetchingOnUiThreadAndroidCache
if (cached == null) {
@@ -380,16 +368,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableImperativeFocus(): Boolean {
var cached = enableImperativeFocusCache
if (cached == null) {
cached = currentProvider.enableImperativeFocus()
accessedFeatureFlags.add("enableImperativeFocus")
enableImperativeFocusCache = cached
}
return cached
}
override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean {
var cached = enableInteropViewManagerClassLookUpOptimizationIOSCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<dffc5db0f80501f6246f9d6087c6fb4a>>
* @generated SignedSource<<4feeb3c1789d8169dd93bee7ae1ff1ac>>
*/
/**
@@ -25,7 +25,5 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android :
override fun enableAccessibilityOrder(): Boolean = true
override fun enableSwiftUIBasedFilters(): Boolean = true
override fun preventShadowTreeCommitExhaustion(): Boolean = true
}
@@ -7,10 +7,16 @@
package com.facebook.react.internal.featureflags
public class ReactNativeFeatureFlagsOverrides_RNOSS_Stable_Android() :
ReactNativeNewArchitectureFeatureFlagsDefaults() {
public class ReactNativeFeatureFlagsOverrides_RNOSS_Stable_Android(
private val fabricEnabled: Boolean,
private val bridgelessEnabled: Boolean,
private val turboModulesEnabled: Boolean,
) : ReactNativeNewArchitectureFeatureFlagsDefaults(bridgelessEnabled) {
override fun useFabricInterop(): Boolean = bridgelessEnabled || fabricEnabled
override fun useFabricInterop(): Boolean = true
override fun enableFabricRenderer(): Boolean = bridgelessEnabled || fabricEnabled
override fun useTurboModules(): Boolean = bridgelessEnabled || turboModulesEnabled
override fun useShadowNodeStateOnClone(): Boolean = true
}
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<f26998daf87c8b90c2ec822c8316b134>>
* @generated SignedSource<<143b568248e68033efaefc3f178ff6db>>
*/
/**
@@ -73,14 +73,10 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableImagePrefetchingAndroid(): Boolean
@DoNotStrip public fun enableImagePrefetchingJNIBatchingAndroid(): Boolean
@DoNotStrip public fun enableImagePrefetchingOnUiThreadAndroid(): Boolean
@DoNotStrip public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean
@DoNotStrip public fun enableImperativeFocus(): Boolean
@DoNotStrip public fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean
@DoNotStrip public fun enableLayoutAnimationsOnAndroid(): Boolean
@@ -20,11 +20,9 @@ package com.facebook.react.internal.featureflags
* When the new architecture is enabled, we want to set the default values of the flags for Fabric,
* TurboModules and Bridgeless as enabled by default.
*/
public open class ReactNativeNewArchitectureFeatureFlagsDefaults() :
ReactNativeFeatureFlagsDefaults() {
private val newArchitectureEnabled: Boolean = true
public open class ReactNativeNewArchitectureFeatureFlagsDefaults(
private val newArchitectureEnabled: Boolean = true
) : ReactNativeFeatureFlagsDefaults() {
override fun enableBridgelessArchitecture(): Boolean = newArchitectureEnabled
override fun enableFabricRenderer(): Boolean = newArchitectureEnabled
@@ -31,9 +31,7 @@ internal class DevLoadingModule(reactContext: ReactApplicationContext) :
}
override fun showMessage(message: String, color: Double?, backgroundColor: Double?) {
UiThreadUtil.runOnUiThread {
devLoadingViewManager?.showMessage(message, color, backgroundColor)
}
UiThreadUtil.runOnUiThread { devLoadingViewManager?.showMessage(message) }
}
override fun hide() {
@@ -0,0 +1,137 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.AccelerateInterpolator
import android.view.animation.Animation
import android.view.animation.BaseInterpolator
import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator
import android.view.animation.LinearInterpolator
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.common.annotations.VisibleForTesting
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
import com.facebook.react.uimanager.IllegalViewOperationException
/**
* Class responsible for parsing and converting layout animation data into native [Animation] in
* order to animate layout when a valid configuration has been supplied by the application.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal abstract class AbstractLayoutAnimation {
var interpolator: Interpolator? = null
var delayMs: Int = 0
var animatedProperty: AnimatedPropertyType? = null
var durationMs: Int = 0
internal abstract fun isValid(): Boolean
/**
* Create an animation object for the current animation type, based on the view and final screen
* coordinates. If the application-supplied configuration does not specify an animation definition
* for this types, or if the animation definition is invalid, returns null.
*/
internal abstract fun createAnimationImpl(
view: View,
x: Int,
y: Int,
width: Int,
height: Int,
): Animation?
fun reset() {
animatedProperty = null
durationMs = 0
delayMs = 0
interpolator = null
}
fun initializeFromConfig(data: ReadableMap, globalDuration: Int) {
animatedProperty =
if (data.hasKey("property")) {
AnimatedPropertyType.fromString(data.getString("property").orEmpty())
} else {
null
}
durationMs = if (data.hasKey("duration")) data.getInt("duration") else globalDuration
delayMs = if (data.hasKey("delay")) data.getInt("delay") else 0
require(data.hasKey("type")) { "Missing interpolation type." }
interpolator =
getInterpolator(InterpolatorType.fromString(data.getString("type").orEmpty()), data)
if (!isValid()) {
throw IllegalViewOperationException("Invalid layout animation : $data")
}
}
/**
* Create an animation object to be used to animate the view, based on the animation config
* supplied at initialization time and the new view position and size.
*
* @param view the view to create the animation for
* @param x the new X position for the view
* @param y the new Y position for the view
* @param width the new width value for the view
* @param height the new height value for the view
*/
fun createAnimation(view: View, x: Int, y: Int, width: Int, height: Int): Animation? {
if (!isValid()) {
return null
}
return createAnimationImpl(view, x, y, width, height)?.apply {
val slowdownFactor = if (SLOWDOWN_ANIMATION_MODE) 10 else 1
duration = (durationMs * slowdownFactor).toLong()
startOffset = (delayMs * slowdownFactor).toLong()
interpolator = this@AbstractLayoutAnimation.interpolator
}
}
companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"AbstractLayoutAnimation",
LegacyArchitectureLogLevel.ERROR,
)
}
// Forces animation to be playing 10x slower, used for debug purposes.
private const val SLOWDOWN_ANIMATION_MODE = false
private val INTERPOLATOR: Map<InterpolatorType, BaseInterpolator> =
mapOf(
InterpolatorType.LINEAR to LinearInterpolator(),
InterpolatorType.EASE_IN to AccelerateInterpolator(),
InterpolatorType.EASE_OUT to DecelerateInterpolator(),
InterpolatorType.EASE_IN_EASE_OUT to AccelerateDecelerateInterpolator(),
)
@VisibleForTesting
fun getInterpolator(type: InterpolatorType, params: ReadableMap): Interpolator {
val interpolator =
if (type == InterpolatorType.SPRING) {
SimpleSpringInterpolator(SimpleSpringInterpolator.getSpringDamping(params))
} else {
INTERPOLATOR[type]
}
requireNotNull(interpolator) { "Missing interpolator for type : $type" }
return interpolator
}
}
}
@@ -0,0 +1,42 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
/**
* Enum representing the different view properties that can be used when animating layout for view
* creation.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal enum class AnimatedPropertyType {
OPACITY,
SCALE_X,
SCALE_Y,
SCALE_XY;
companion object {
@JvmStatic
fun fromString(name: String): AnimatedPropertyType {
return when (name) {
"opacity" -> OPACITY
"scaleX" -> SCALE_X
"scaleY" -> SCALE_Y
"scaleXY" -> SCALE_XY
else -> throw IllegalArgumentException("Unsupported animated property: $name")
}
}
}
}
@@ -0,0 +1,96 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
import android.view.animation.Animation
import android.view.animation.ScaleAnimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
import com.facebook.react.uimanager.IllegalViewOperationException
/** Class responsible for default layout animation, i.e animation of view creation and deletion. */
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal abstract class BaseLayoutAnimation : AbstractLayoutAnimation() {
abstract fun isReverse(): Boolean
override fun isValid(): Boolean = durationMs > 0 && animatedProperty != null
override fun createAnimationImpl(view: View, x: Int, y: Int, width: Int, height: Int): Animation {
animatedProperty?.let {
return when (it) {
AnimatedPropertyType.OPACITY -> {
val fromValue = if (isReverse()) view.alpha else 0.0f
val toValue = if (isReverse()) 0.0f else view.alpha
OpacityAnimation(view, fromValue, toValue)
}
AnimatedPropertyType.SCALE_XY -> {
val fromValue = if (isReverse()) 1.0f else 0.0f
val toValue = if (isReverse()) 0.0f else 1.0f
ScaleAnimation(
fromValue,
toValue,
fromValue,
toValue,
Animation.RELATIVE_TO_SELF,
.5f,
Animation.RELATIVE_TO_SELF,
.5f,
)
}
AnimatedPropertyType.SCALE_X -> {
val fromValue = if (isReverse()) 1.0f else 0.0f
val toValue = if (isReverse()) 0.0f else 1.0f
ScaleAnimation(
fromValue,
toValue,
1f,
1f,
Animation.RELATIVE_TO_SELF,
.5f,
Animation.RELATIVE_TO_SELF,
0f,
)
}
AnimatedPropertyType.SCALE_Y -> {
val fromValue = if (isReverse()) 1.0f else 0.0f
val toValue = if (isReverse()) 0.0f else 1.0f
ScaleAnimation(
1f,
1f,
fromValue,
toValue,
Animation.RELATIVE_TO_SELF,
0f,
Animation.RELATIVE_TO_SELF,
.5f,
)
}
}
} ?: throw IllegalViewOperationException("Missing animated property from animation config")
}
private companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"BaseLayoutAnimation",
LegacyArchitectureLogLevel.ERROR,
)
}
}
}
@@ -0,0 +1,43 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
/**
* Enum representing the different interpolators that can be used in layout animation configuration.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal enum class InterpolatorType {
LINEAR,
EASE_IN,
EASE_OUT,
EASE_IN_EASE_OUT,
SPRING;
companion object {
@JvmStatic
fun fromString(name: String): InterpolatorType {
return when (name.lowercase()) {
"linear" -> LINEAR
"easein" -> EASE_IN
"easeout" -> EASE_OUT
"easeineaseout" -> EASE_IN_EASE_OUT
"spring" -> SPRING
else -> throw IllegalArgumentException("Unsupported interpolation type : $name")
}
}
}
}
@@ -9,12 +9,19 @@
package com.facebook.react.uimanager.layoutanimation
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.Animation.AnimationListener
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.UiThreadUtil.assertOnUiThread
import com.facebook.react.bridge.UiThreadUtil.getUiThreadHandler
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger.assertLegacyArchitecture
import com.facebook.react.uimanager.layoutanimation.LayoutAnimationType.Companion.toString
import javax.annotation.concurrent.NotThreadSafe
/**
@@ -29,27 +36,75 @@ import javax.annotation.concurrent.NotThreadSafe
level = DeprecationLevel.WARNING,
)
public open class LayoutAnimationController {
private val layoutCreateAnimation: AbstractLayoutAnimation = LayoutCreateAnimation()
private val layoutUpdateAnimation: AbstractLayoutAnimation = LayoutUpdateAnimation()
private val layoutDeleteAnimation: AbstractLayoutAnimation = LayoutDeleteAnimation()
private val layoutHandlers = SparseArray<LayoutHandlingAnimation?>(0)
private var shouldAnimateLayout = false
private var maxAnimationDuration: Long = -1
private var completionRunnable: Runnable? = null
public fun initializeFromConfig(config: ReadableMap?, completionCallback: Callback?) {
LegacyArchitectureLogger.assertLegacyArchitecture(
"LayoutAnimationController",
LegacyArchitectureLogLevel.ERROR,
)
if (config == null) {
reset()
return
}
shouldAnimateLayout = false
val globalDuration = if (config.hasKey("duration")) config.getInt("duration") else 0
if (config.hasKey(toString(LayoutAnimationType.CREATE))) {
layoutCreateAnimation.initializeFromConfig(
config.getMap(toString(LayoutAnimationType.CREATE))!!,
globalDuration,
)
shouldAnimateLayout = true
}
if (config.hasKey(toString(LayoutAnimationType.UPDATE))) {
layoutUpdateAnimation.initializeFromConfig(
config.getMap(toString(LayoutAnimationType.UPDATE))!!,
globalDuration,
)
shouldAnimateLayout = true
}
if (config.hasKey(toString(LayoutAnimationType.DELETE))) {
layoutDeleteAnimation.initializeFromConfig(
config.getMap(toString(LayoutAnimationType.DELETE))!!,
globalDuration,
)
shouldAnimateLayout = true
}
if (shouldAnimateLayout && completionCallback != null) {
completionRunnable = Runnable { completionCallback.invoke(java.lang.Boolean.TRUE) }
}
}
public open fun reset() {
LegacyArchitectureLogger.assertLegacyArchitecture(
"LayoutAnimationController",
LegacyArchitectureLogLevel.ERROR,
)
layoutCreateAnimation.reset()
layoutUpdateAnimation.reset()
layoutDeleteAnimation.reset()
completionRunnable = null
shouldAnimateLayout = false
maxAnimationDuration = -1
for (i in layoutHandlers.size() - 1 downTo 0) {
val animation = layoutHandlers.valueAt(i)
if (!animation!!.isValid()) {
layoutHandlers.removeAt(i)
}
}
}
public open fun shouldAnimateLayout(viewToAnimate: View?): Boolean {
LegacyArchitectureLogger.assertLegacyArchitecture(
"LayoutAnimationController",
LegacyArchitectureLogLevel.ERROR,
)
return false
// if view parent is null, skip animation: view have been clipped, we don't want animation to
// resume when view is re-attached to parent, which is the standard android animation behavior.
// If there's a layout handling animation going on, it should be animated nonetheless since the
// ongoing animation needs to be updated.
if (viewToAnimate == null) {
return false
}
return ((shouldAnimateLayout && viewToAnimate.parent != null) ||
layoutHandlers[viewToAnimate.id] != null)
}
/**
@@ -64,10 +119,57 @@ public open class LayoutAnimationController {
* @param height the new height value for the view
*/
public open fun applyLayoutUpdate(view: View, x: Int, y: Int, width: Int, height: Int) {
LegacyArchitectureLogger.assertLegacyArchitecture(
"LayoutAnimationController",
LegacyArchitectureLogLevel.ERROR,
)
assertOnUiThread()
val reactTag = view.id
// Update an ongoing animation if possible, otherwise the layout update would be ignored as
// the existing animation would still animate to the old layout.
val existingAnimation = layoutHandlers[reactTag]
if (existingAnimation != null) {
if (!existingAnimation.isValid()) {
layoutHandlers.remove(reactTag)
} else {
existingAnimation.onLayoutUpdate(x, y, width, height)
return
}
}
// Determine which animation to use : if view is initially invisible, use create animation,
// otherwise use update animation. This approach is easier than maintaining a list of tags
// for recently created views.
val layoutAnimation =
if ((view.width == 0 || view.height == 0)) layoutCreateAnimation else layoutUpdateAnimation
val animation = layoutAnimation.createAnimation(view, x, y, width, height)
if (animation is LayoutHandlingAnimation) {
animation.setAnimationListener(
object : AnimationListener {
override fun onAnimationStart(animation: Animation) {
layoutHandlers.put(reactTag, animation as LayoutHandlingAnimation)
}
override fun onAnimationEnd(animation: Animation) {
layoutHandlers.remove(reactTag)
}
override fun onAnimationRepeat(animation: Animation) = Unit
}
)
} else {
view.layout(x, y, x + width, y + height)
}
if (animation != null) {
val animationDuration = animation.duration
if (animationDuration > maxAnimationDuration) {
maxAnimationDuration = animationDuration
scheduleCompletionCallback(animationDuration)
}
view.startAnimation(animation)
}
}
/**
@@ -79,9 +181,59 @@ public open class LayoutAnimationController {
* view.
*/
public open fun deleteView(view: View, listener: LayoutAnimationListener) {
LegacyArchitectureLogger.assertLegacyArchitecture(
"LayoutAnimationController",
LegacyArchitectureLogLevel.ERROR,
)
assertOnUiThread()
val animation =
layoutDeleteAnimation.createAnimation(view, view.left, view.top, view.width, view.height)
if (animation != null) {
disableUserInteractions(view)
animation.setAnimationListener(
object : AnimationListener {
override fun onAnimationStart(anim: Animation) = Unit
override fun onAnimationRepeat(anim: Animation) = Unit
override fun onAnimationEnd(anim: Animation) {
listener.onAnimationEnd()
}
}
)
val animationDuration = animation.duration
if (animationDuration > maxAnimationDuration) {
scheduleCompletionCallback(animationDuration)
maxAnimationDuration = animationDuration
}
view.startAnimation(animation)
} else {
listener.onAnimationEnd()
}
}
/** Disables user interactions for a view and all it's subviews. */
private fun disableUserInteractions(view: View) {
view.isClickable = false
if (view is ViewGroup) {
for (i in 0 until view.childCount) {
disableUserInteractions(view.getChildAt(i))
}
}
}
private fun scheduleCompletionCallback(delayMillis: Long) {
if (completionRunnable != null) {
val completionHandler = getUiThreadHandler()
completionHandler.removeCallbacks(completionRunnable!!)
completionHandler.postDelayed(completionRunnable!!, delayMillis)
}
}
private companion object {
init {
assertLegacyArchitecture("LayoutAnimationController", LegacyArchitectureLogLevel.ERROR)
}
}
}
@@ -0,0 +1,46 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
/**
* Enum representing the different animation type that can be specified in layout animation config.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal enum class LayoutAnimationType {
CREATE,
UPDATE,
DELETE;
companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"LayoutAnimationType",
LegacyArchitectureLogLevel.ERROR,
)
}
@JvmStatic
fun toString(type: LayoutAnimationType): String {
return when (type) {
CREATE -> "create"
UPDATE -> "update"
DELETE -> "delete"
}
}
}
}
@@ -0,0 +1,33 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
/**
* Class responsible for handling layout view creation animation, applied to view whenever a valid
* config was supplied for the layout animation of CREATE type.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
internal class LayoutCreateAnimation : BaseLayoutAnimation() {
override fun isReverse(): Boolean = false
private companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"LayoutCreateAnimation",
LegacyArchitectureLogLevel.ERROR,
)
}
}
}
@@ -0,0 +1,37 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
/**
* Class responsible for handling layout view deletion animation, applied to view whenever a valid
* config was supplied for the layout animation of DELETE type.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal class LayoutDeleteAnimation : BaseLayoutAnimation() {
override fun isReverse(): Boolean = true
private companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"LayoutDeleteAnimation",
LegacyArchitectureLogLevel.ERROR,
)
}
}
}
@@ -0,0 +1,42 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
/** Interface for an animation type that takes care of updating the view layout. */
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal interface LayoutHandlingAnimation {
/**
* Notifies the animation of a layout update in case one occurs during the animation. This avoids
* animating the view to the old layout since it's no longer correct; instead the animation should
* update and do whatever it can so that the final layout is correct.
*
* @param x the new X position for the view
* @param y the new Y position for the view
* @param width the new width value for the view
* @param height the new height value for the view
*/
fun onLayoutUpdate(x: Int, y: Int, width: Int, height: Int)
/**
* Returns whether the animation is valid and should be used. Because layout animations generally
* hold {@link java.lang.ref.WeakReference<android.view.View>} objects, it's possible that the
* view has been garbage collected. In this case, the animation should not be used.
*
* @return true if the animation is valid and can be used, false otherwise
*/
fun isValid(): Boolean
}
@@ -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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
import android.view.animation.Animation
import android.view.animation.TranslateAnimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
/**
* Class responsible for handling layout update animation, applied to view whenever a valid config
* was supplied for the layout animation of UPDATE type.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal class LayoutUpdateAnimation : AbstractLayoutAnimation() {
override fun isValid(): Boolean = durationMs > 0
override fun createAnimationImpl(
view: View,
x: Int,
y: Int,
width: Int,
height: Int,
): Animation? {
val animateLocation = view.x.toInt() != x || view.y.toInt() != y
val animateSize = view.width != width || view.height != height
return if (!animateLocation && !animateSize) {
null
} else if (animateLocation && !animateSize && USE_TRANSLATE_ANIMATION) {
// Use GPU-accelerated animation, however we loose the ability to resume interrupted
// animation where it was left off. We may be able to listen to animation interruption
// and set the layout manually in this case, so that next animation kicks off smoothly.
TranslateAnimation(view.x - x, 0f, view.y - y, 0f)
} else {
// Animation is sub-optimal for perf, but scale transformation can't be use in this case.
PositionAndSizeAnimation(view, x, y, width, height)
}
}
private companion object {
// We are currently not enabling translation GPU-accelerated animated, as it creates odd
// artifacts with native react scrollview. This needs to be investigated.
private const val USE_TRANSLATE_ANIMATION = false
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"LayoutUpdateAnimation",
LegacyArchitectureLogLevel.ERROR,
)
}
}
}
@@ -0,0 +1,77 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
import android.view.animation.Animation
import android.view.animation.Transformation
import com.facebook.react.common.annotations.VisibleForTesting
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
import java.lang.ref.WeakReference
/**
* Animation responsible for updating opacity of a view. It should ideally use hardware texture to
* optimize rendering performances.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal class OpacityAnimation(view: View, private val startOpacity: Float, endOpacity: Float) :
Animation() {
private val viewRef = WeakReference(view)
private val deltaOpacity = endOpacity - startOpacity
init {
setAnimationListener(OpacityAnimationListener(view))
LegacyArchitectureLogger.assertLegacyArchitecture(
"OpacityAnimation",
LegacyArchitectureLogLevel.ERROR,
)
}
class OpacityAnimationListener(view: View) : Animation.AnimationListener {
private val viewRef = WeakReference(view)
private var layerTypeChanged = false
override fun onAnimationStart(animation: Animation) {
viewRef.get()?.let { view ->
if (view.hasOverlappingRendering() && view.layerType == View.LAYER_TYPE_NONE) {
layerTypeChanged = true
view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
}
}
}
override fun onAnimationEnd(animation: Animation) {
viewRef.get()?.let { view ->
if (layerTypeChanged) {
view.setLayerType(View.LAYER_TYPE_NONE, null)
}
}
}
override fun onAnimationRepeat(animation: Animation) {
// do nothing
}
}
@VisibleForTesting
public override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
viewRef.get()?.let { view -> view.alpha = startOpacity + deltaOpacity * interpolatedTime }
}
override fun willChangeBounds(): Boolean {
return false
}
}
@@ -0,0 +1,98 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
import android.view.animation.Animation
import android.view.animation.Transformation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
import java.lang.ref.WeakReference
/**
* Animation responsible for updating size and position of a view. We can't use scaling as view
* content may not necessarily stretch. As a result, this approach is inefficient because of layout
* passes occurring on every frame. What we might want to try to do instead is use a combined
* ScaleAnimation and TranslateAnimation.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal class PositionAndSizeAnimation(view: View, x: Int, y: Int, width: Int, height: Int) :
Animation(), LayoutHandlingAnimation {
private val viewRef = WeakReference(view)
private var startX = 0f
private var startY = 0f
private var deltaX = 0f
private var deltaY = 0f
private var startWidth = 0
private var startHeight = 0
private var deltaWidth = 0
private var deltaHeight = 0
init {
calculateAnimation(x, y, width, height)
}
override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
viewRef.get()?.let { view ->
val newX = startX + deltaX * interpolatedTime
val newY = startY + deltaY * interpolatedTime
val newWidth = startWidth + deltaWidth * interpolatedTime
val newHeight = startHeight + deltaHeight * interpolatedTime
view.layout(
Math.round(newX),
Math.round(newY),
Math.round(newX + newWidth),
Math.round(newY + newHeight),
)
}
}
override fun onLayoutUpdate(x: Int, y: Int, width: Int, height: Int) {
// Layout changed during the animation, we should update our values so that the final layout
// is correct.
calculateAnimation(x, y, width, height)
}
override fun isValid(): Boolean {
return viewRef.get() != null
}
override fun willChangeBounds(): Boolean {
return true
}
private fun calculateAnimation(x: Int, y: Int, width: Int, height: Int) {
viewRef.get()?.let { view ->
startX = view.x - view.translationX
startY = view.y - view.translationY
startWidth = view.width
startHeight = view.height
deltaX = x - startX
deltaY = y - startY
deltaWidth = width - startWidth
deltaHeight = height - startHeight
}
}
private companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"PositionAndSizeAnimation",
LegacyArchitectureLogLevel.ERROR,
)
}
}
}
@@ -0,0 +1,62 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.animation.Interpolator
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
import kotlin.math.pow
import kotlin.math.sin
/** Simple spring interpolator */
// TODO(7613736): Improve spring interpolator with friction and damping variable support
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal class SimpleSpringInterpolator @JvmOverloads constructor(springDamping: Float = FACTOR) :
Interpolator {
private val _springDamping: Float = springDamping
override fun getInterpolation(input: Float): Float =
// Using mSpringDamping in this equation is not really the exact mathematical springDamping,
// but a good approximation
// We need to replace this equation with the right Factor that accounts for damping and
// friction
(1 +
2.0.pow((-10 * input).toDouble()) *
sin((input - _springDamping / 4) * Math.PI * 2 / _springDamping))
.toFloat()
companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"SimpleSpringInterpolator",
LegacyArchitectureLogLevel.ERROR,
)
}
private const val FACTOR = 0.5f
const val PARAM_SPRING_DAMPING: String = "springDamping"
@JvmStatic
fun getSpringDamping(params: ReadableMap): Float =
if (params.getType(PARAM_SPRING_DAMPING) == ReadableType.Number) {
params.getDouble(PARAM_SPRING_DAMPING).toFloat()
} else {
FACTOR
}
}
}
@@ -79,8 +79,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
HasStateWrapper,
HasFlingAnimator,
HasScrollEventThrottle,
HasSmoothScroll,
VirtualViewContainer {
HasSmoothScroll {
private static boolean DEBUG_MODE = false && ReactBuildConfig.DEBUG;
private static String TAG = ReactHorizontalScrollView.class.getSimpleName();
@@ -101,7 +100,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
private final ValueAnimator DEFAULT_FLING_ANIMATOR = ObjectAnimator.ofInt(this, "scrollX", 0, 0);
private Rect mOverflowInset = new Rect();
private @Nullable VirtualViewContainerState mVirtualViewContainerState;
private boolean mActivelyScrolling;
private @Nullable Rect mClippingRect;
private Overflow mOverflow = Overflow.SCROLL;
@@ -158,15 +156,9 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
*/
private void initView() {
mOverflowInset = new Rect();
mVirtualViewContainerState = null;
mActivelyScrolling = false;
mClippingRect = null;
// The default value for `overflow` is set to `Visible` in the Yoga style props.
mOverflow =
ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()
? Overflow.VISIBLE
: Overflow.SCROLL;
mOverflow = Overflow.SCROLL;
mDragging = false;
mPagingEnabled = false;
mPostTouchRunnable = null;
@@ -212,15 +204,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
private void updateView() {}
@Override
public VirtualViewContainerState getVirtualViewContainerState() {
if (mVirtualViewContainerState == null) {
mVirtualViewContainerState = new VirtualViewContainerState(this);
}
return mVirtualViewContainerState;
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
@@ -398,12 +381,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
mOverflow = Overflow.SCROLL;
} else {
@Nullable Overflow parsedOverflow = Overflow.fromString(overflow);
mOverflow =
parsedOverflow == null
? (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()
? Overflow.VISIBLE
: Overflow.SCROLL)
: parsedOverflow;
mOverflow = parsedOverflow == null ? Overflow.SCROLL : parsedOverflow;
}
invalidate();
@@ -524,9 +502,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
}
ReactScrollViewHelper.emitLayoutEvent(this);
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.updateState();
}
}
/**
@@ -620,11 +595,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
this,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity());
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.updateState();
}
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT);
}
@@ -875,9 +846,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
if (mRemoveClippedSubviews) {
updateClippingRect();
}
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.updateState();
}
}
@Override
@@ -897,9 +865,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
if (mMaintainVisibleContentPositionHelper != null) {
mMaintainVisibleContentPositionHelper.stop();
}
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.cleanup();
}
}
@Override
@@ -159,19 +159,14 @@ public class ReactScrollView extends ScrollView
mVirtualViewContainerState = null;
mActivelyScrolling = false;
mClippingRect = null;
// The default value for `overflow` is set to `Visible` in the Yoga style props.
mOverflow =
ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()
? Overflow.VISIBLE
: Overflow.SCROLL;
mOverflow = Overflow.SCROLL;
mDragging = false;
mPagingEnabled = false;
mPostTouchRunnable = null;
mRemoveClippedSubviews = false;
mScrollEnabled = true;
mSendMomentumEvents = false;
mFpsListener = null;
mScrollPerfTag = null;
mEndBackground = null;
mEndFillColor = Color.TRANSPARENT;
@@ -368,12 +363,7 @@ public class ReactScrollView extends ScrollView
mOverflow = Overflow.SCROLL;
} else {
@Nullable Overflow parsedOverflow = Overflow.fromString(overflow);
mOverflow =
parsedOverflow == null
? (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()
? Overflow.VISIBLE
: Overflow.SCROLL)
: parsedOverflow;
mOverflow = parsedOverflow == null ? Overflow.SCROLL : parsedOverflow;
}
invalidate();
@@ -34,6 +34,7 @@ import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontVariant
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
import com.facebook.react.views.text.TextTransform.Companion.apply
import com.facebook.react.views.text.internal.ReactTextInlineImageShadowNode
import com.facebook.react.views.text.internal.span.CustomLetterSpacingSpan
import com.facebook.react.views.text.internal.span.CustomLineHeightSpan
import com.facebook.react.views.text.internal.span.CustomStyleSpan
@@ -522,7 +523,6 @@ public constructor(
while (i < length) {
val child: ReactShadowNode<*> = textShadowNode.getChildAt(i)
@Suppress("DEPRECATION")
if (child is ReactRawTextShadowNode) {
child.text?.let { sb.append(apply(it, textAttributes.textTransform)) }
} else if (child is ReactBaseTextShadowNode) {
@@ -535,7 +535,7 @@ public constructor(
inlineViews,
sb.length,
)
} else if (child is com.facebook.react.views.text.internal.ReactTextInlineImageShadowNode) {
} else if (child is ReactTextInlineImageShadowNode) {
// We make the image take up 1 character in the span and put a corresponding character
// into the text so that the image doesn't run over any following text.
sb.append(INLINE_VIEW_PLACEHOLDER)
@@ -5,8 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.views.text.frescosupport
import android.content.Context
@@ -9,18 +9,11 @@
package com.facebook.react.views.text.internal
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.uimanager.LayoutShadowNode
import com.facebook.react.views.text.internal.span.TextInlineImageSpan
import com.facebook.yoga.YogaNode
/** Base class for [YogaNode]s that represent inline images. */
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING,
)
internal abstract class ReactTextInlineImageShadowNode : LayoutShadowNode() {
/**
* Build a [TextInlineImageSpan] from this node. This will be added to the TextView in place of
@@ -416,14 +416,6 @@ public open class ReactViewGroup public constructor(context: Context?) :
updateClippingToRect(clippingRect, excludedViews)
}
internal fun requestFocusFromJS() {
super.requestFocus(FOCUS_DOWN, null)
}
internal fun clearFocusFromJS() {
super.clearFocus()
}
override fun endViewTransition(view: View) {
super.endViewTransition(view)
childrenRemovedWhileTransitioning?.remove(view.id)
@@ -404,8 +404,6 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
when (commandId) {
HOTSPOT_UPDATE_KEY -> handleHotspotUpdate(root, args)
"setPressed" -> handleSetPressed(root, args)
"focus" -> handleFocus(root)
"blur" -> handleBlur(root)
else -> {}
}
}
@@ -430,16 +428,4 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
val y = args.getDouble(1).dpToPx()
root.drawableHotspotChanged(x, y)
}
private fun handleFocus(root: ReactViewGroup) {
if (ReactNativeFeatureFlags.enableImperativeFocus()) {
root.requestFocusFromJS()
}
}
private fun handleBlur(root: ReactViewGroup) {
if (ReactNativeFeatureFlags.enableImperativeFocus()) {
root.clearFocusFromJS()
}
}
}
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<8e1821e9a153a4b725890cb4a7f262ee>>
* @generated SignedSource<<92c9aa29df580c6d12faa14e60b00e4f>>
*/
/**
@@ -189,12 +189,6 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool enableImagePrefetchingJNIBatchingAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableImagePrefetchingJNIBatchingAndroid");
return method(javaProvider_);
}
bool enableImagePrefetchingOnUiThreadAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableImagePrefetchingOnUiThreadAndroid");
@@ -207,12 +201,6 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool enableImperativeFocus() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableImperativeFocus");
return method(javaProvider_);
}
bool enableInteropViewManagerClassLookUpOptimizationIOS() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableInteropViewManagerClassLookUpOptimizationIOS");
@@ -618,11 +606,6 @@ bool JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingAndroid(
return ReactNativeFeatureFlags::enableImagePrefetchingAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingJNIBatchingAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableImagePrefetchingJNIBatchingAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingOnUiThreadAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid();
@@ -633,11 +616,6 @@ bool JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffs
return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges();
}
bool JReactNativeFeatureFlagsCxxInterop::enableImperativeFocus(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableImperativeFocus();
}
bool JReactNativeFeatureFlagsCxxInterop::enableInteropViewManagerClassLookUpOptimizationIOS(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableInteropViewManagerClassLookUpOptimizationIOS();
@@ -974,18 +952,12 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"enableImagePrefetchingAndroid",
JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingAndroid),
makeNativeMethod(
"enableImagePrefetchingJNIBatchingAndroid",
JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingJNIBatchingAndroid),
makeNativeMethod(
"enableImagePrefetchingOnUiThreadAndroid",
JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingOnUiThreadAndroid),
makeNativeMethod(
"enableImmediateUpdateModeForContentOffsetChanges",
JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges),
makeNativeMethod(
"enableImperativeFocus",
JReactNativeFeatureFlagsCxxInterop::enableImperativeFocus),
makeNativeMethod(
"enableInteropViewManagerClassLookUpOptimizationIOS",
JReactNativeFeatureFlagsCxxInterop::enableInteropViewManagerClassLookUpOptimizationIOS),
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<5be2ec52bda638a1eac837c402149b9f>>
* @generated SignedSource<<124d1378990fdd09a4d97f9103f81a6a>>
*/
/**
@@ -105,18 +105,12 @@ class JReactNativeFeatureFlagsCxxInterop
static bool enableImagePrefetchingAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableImagePrefetchingJNIBatchingAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableImagePrefetchingOnUiThreadAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableImmediateUpdateModeForContentOffsetChanges(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableImperativeFocus(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableInteropViewManagerClassLookUpOptimizationIOS(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -24,18 +24,20 @@ class JReactMarker : public facebook::jni::JavaClass<JReactMarker> {
private:
static void logMarker(const std::string& marker);
static void logMarker(const std::string& marker, const std::string& tag);
static void
logMarker(const std::string& marker, const std::string& tag, int instanceKey);
static void logMarker(
const std::string& marker,
const std::string& tag,
const int instanceKey);
static void logPerfMarker(
ReactMarker::ReactMarkerId markerId,
const ReactMarker::ReactMarkerId markerId,
const char* tag);
static void logPerfMarkerBridgeless(
ReactMarker::ReactMarkerId markerId,
const ReactMarker::ReactMarkerId markerId,
const char* tag);
static void logPerfMarkerWithInstanceKey(
ReactMarker::ReactMarkerId markerId,
const ReactMarker::ReactMarkerId markerId,
const char* tag,
int instanceKey);
const int instanceKey);
static void nativeLogMarker(
jni::alias_ref<jclass> /* unused */,
const std::string& markerNameStr,
@@ -13,48 +13,36 @@ import org.junit.Test
class DefaultNewArchitectureEntryPointTest {
@Test
fun isConfigurationValid_withEverythingOff_returnsFalse() {
val (isValid, errorMessage) =
fun isConfigurationValid_withEverythingOff_returnsTrue() {
val (isValid, _) =
DefaultNewArchitectureEntryPoint.isConfigurationValid(
turboModulesEnabled = false,
fabricEnabled = false,
bridgelessEnabled = false,
)
assertThat(isValid).isFalse()
assertThat(errorMessage)
.isEqualTo(
"You cannot load React Native with the New Architecture disabled. Please use DefaultNewArchitectureEntryPoint.load() instead of DefaultNewArchitectureEntryPoint.load(turboModulesEnabled=false, fabricEnabled=false, bridgelessEnabled=false)"
)
assertThat(isValid).isTrue()
}
@Test
fun isConfigurationValid_withNewArchOnlyOn_returnsFalse() {
val (isValid, errorMessage) =
fun isConfigurationValid_withNewArchOn_returnsTrue() {
val (isValid, _) =
DefaultNewArchitectureEntryPoint.isConfigurationValid(
turboModulesEnabled = true,
fabricEnabled = true,
bridgelessEnabled = false,
)
assertThat(isValid).isFalse()
assertThat(errorMessage)
.isEqualTo(
"You cannot load React Native with the New Architecture disabled. Please use DefaultNewArchitectureEntryPoint.load() instead of DefaultNewArchitectureEntryPoint.load(turboModulesEnabled=true, fabricEnabled=true, bridgelessEnabled=false)"
)
assertThat(isValid).isTrue()
}
@Test
fun isConfigurationValid_withTurboModulesOnlyOn_returnsFalse() {
val (isValid, errorMessage) =
fun isConfigurationValid_withTurboModulesOnlyOn_returnsTrue() {
val (isValid, _) =
DefaultNewArchitectureEntryPoint.isConfigurationValid(
turboModulesEnabled = true,
fabricEnabled = false,
bridgelessEnabled = false,
)
assertThat(isValid).isFalse()
assertThat(errorMessage)
.isEqualTo(
"You cannot load React Native with the New Architecture disabled. Please use DefaultNewArchitectureEntryPoint.load() instead of DefaultNewArchitectureEntryPoint.load(turboModulesEnabled=true, fabricEnabled=false, bridgelessEnabled=false)"
)
assertThat(isValid).isTrue()
}
@Test
@@ -79,7 +67,7 @@ class DefaultNewArchitectureEntryPointTest {
assertThat(isValid).isFalse()
assertThat(errorMessage)
.isEqualTo(
"You cannot load React Native with the New Architecture disabled. Please use DefaultNewArchitectureEntryPoint.load() instead of DefaultNewArchitectureEntryPoint.load(turboModulesEnabled=false, fabricEnabled=true, bridgelessEnabled=false)"
"fabricEnabled=true requires turboModulesEnabled=true (is now false) - Please update your DefaultNewArchitectureEntryPoint.load() parameters."
)
}
@@ -94,7 +82,7 @@ class DefaultNewArchitectureEntryPointTest {
assertThat(isValid).isFalse()
assertThat(errorMessage)
.isEqualTo(
"You cannot load React Native with the New Architecture disabled. Please use DefaultNewArchitectureEntryPoint.load() instead of DefaultNewArchitectureEntryPoint.load(turboModulesEnabled=false, fabricEnabled=true, bridgelessEnabled=true)"
"fabricEnabled=true requires turboModulesEnabled=true (is now false) - Please update your DefaultNewArchitectureEntryPoint.load() parameters."
)
}
@@ -109,7 +97,7 @@ class DefaultNewArchitectureEntryPointTest {
assertThat(isValid).isFalse()
assertThat(errorMessage)
.isEqualTo(
"You cannot load React Native with the New Architecture disabled. Please use DefaultNewArchitectureEntryPoint.load() instead of DefaultNewArchitectureEntryPoint.load(turboModulesEnabled=true, fabricEnabled=false, bridgelessEnabled=true)"
"bridgelessEnabled=true requires (turboModulesEnabled=true AND fabricEnabled=true) - Please update your DefaultNewArchitectureEntryPoint.load() parameters."
)
}
}
@@ -0,0 +1,157 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.AccelerateInterpolator
import android.view.animation.Animation
import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.IllegalViewOperationException
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.mockConstruction
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
class AbstractLayoutAnimationTest {
private lateinit var view: View
private lateinit var config: ReadableMap
private lateinit var animation: AbstractLayoutAnimation
private lateinit var mockedConstructors: List<AutoCloseable>
@Before
fun setUp() {
view = mock()
config = mock()
mockedConstructors =
listOf(
mockConstruction(LinearInterpolator::class.java),
mockConstruction(AccelerateInterpolator::class.java),
mockConstruction(DecelerateInterpolator::class.java),
mockConstruction(AccelerateDecelerateInterpolator::class.java),
)
animation =
object : AbstractLayoutAnimation() {
override fun isValid(): Boolean = true
override fun createAnimationImpl(
view: View,
x: Int,
y: Int,
width: Int,
height: Int,
): Animation = mock()
}
}
@After
fun tearDown() {
mockedConstructors.forEach { it.close() }
}
@Test
fun reset_clearsAnimationProperties() {
animation.reset()
assertThat(animation.animatedProperty).isNull()
assertThat(animation.durationMs).isEqualTo(0)
assertThat(animation.delayMs).isEqualTo(0)
assertThat(animation.interpolator).isNull()
}
@Test
fun createAnimation_returnsValidAnimation() {
val result = animation.createAnimation(view, 0, 0, 100, 100)
assertThat(result).isNotNull
}
@Test
fun initializeFromConfig_throwsIfTypeMissing() {
whenever(config.hasKey("type")).thenReturn(false)
val exception =
assertThrows(IllegalArgumentException::class.java) {
animation.initializeFromConfig(config, 300)
}
assertThat(exception.message).isEqualTo("Missing interpolation type.")
}
@Test
fun createAnimation_returnsNullWhenInvalid() {
val invalidAnimation =
object : AbstractLayoutAnimation() {
override fun isValid(): Boolean = false
override fun createAnimationImpl(
view: View,
x: Int,
y: Int,
width: Int,
height: Int,
): Animation = mock()
}
val result = invalidAnimation.createAnimation(view, 0, 0, 100, 100)
assertThat(result).isNull()
}
@Test
fun initializeFromConfig_throwsIfInvalidAnimation() {
whenever(config.hasKey("type")).thenReturn(true)
whenever(config.getString("type")).thenReturn("linear")
whenever(config.hasKey("duration")).thenReturn(true)
whenever(config.getInt("duration")).thenReturn(300)
val invalidAnimation =
object : AbstractLayoutAnimation() {
override fun isValid(): Boolean = false
override fun createAnimationImpl(
view: View,
x: Int,
y: Int,
width: Int,
height: Int,
): Animation = mock()
}
assertThatThrownBy { invalidAnimation.initializeFromConfig(config, 300) }
.isInstanceOf(IllegalViewOperationException::class.java)
.hasMessageContaining("Invalid layout animation")
}
@Test
fun getInterpolator_returnsSimpleSpringInterpolator() {
val type = InterpolatorType.SPRING
val params = mock<ReadableMap>()
whenever(params.hasKey("damping")).thenReturn(true)
whenever(params.getDouble("damping")).thenReturn(0.5)
val interpolator = AbstractLayoutAnimation.getInterpolator(type, params)
assertThat(interpolator).isInstanceOf(SimpleSpringInterpolator::class.java)
}
@Test
fun getInterpolator_returnsDefaultInterpolator() {
val type = InterpolatorType.LINEAR
val params = mock<ReadableMap>()
val interpolator = AbstractLayoutAnimation.getInterpolator(type, params)
assertThat(interpolator).isInstanceOf(LinearInterpolator::class.java)
}
}
@@ -0,0 +1,47 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import java.util.Locale
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class InterpolatorTypeTest {
@Test
fun testCamelCase() {
assertThat(InterpolatorType.fromString("linear")).isEqualTo(InterpolatorType.LINEAR)
assertThat(InterpolatorType.fromString("easeIn")).isEqualTo(InterpolatorType.EASE_IN)
assertThat(InterpolatorType.fromString("easeOut")).isEqualTo(InterpolatorType.EASE_OUT)
assertThat(InterpolatorType.fromString("easeInEaseOut"))
.isEqualTo(InterpolatorType.EASE_IN_EASE_OUT)
assertThat(InterpolatorType.fromString("spring")).isEqualTo(InterpolatorType.SPRING)
}
@Test
fun testOtherCases() {
assertThat(InterpolatorType.fromString("EASEIN")).isEqualTo(InterpolatorType.EASE_IN)
assertThat(InterpolatorType.fromString("easeout")).isEqualTo(InterpolatorType.EASE_OUT)
assertThat(InterpolatorType.fromString("easeineaseout"))
.isEqualTo(InterpolatorType.EASE_IN_EASE_OUT)
}
@Test
fun testLocales() {
Locale.setDefault(Locale.forLanguageTag("tr-TR"))
assertThat(InterpolatorType.fromString("easeInEaseOut"))
.isEqualTo(InterpolatorType.EASE_IN_EASE_OUT)
}
@Test(expected = IllegalArgumentException::class)
@Throws(IllegalArgumentException::class)
fun testInvalidInterpolatorTypes() {
InterpolatorType.fromString("ease_in_ease_out")
}
}
@@ -0,0 +1,105 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
import android.view.animation.Transformation
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
@RunWith(RobolectricTestRunner::class)
class OpacityAnimationTest {
private lateinit var view: View
private lateinit var animation: OpacityAnimation
@Before
fun setUp() {
view = View(RuntimeEnvironment.getApplication())
}
@Test
fun applyTransformation_updatesAlphaCorrectly() {
val startOpacity = 0.5f
val endOpacity = 1.0f
animation = OpacityAnimation(view, startOpacity, endOpacity)
val transformation = Transformation()
animation.applyTransformation(0.0f, transformation)
assertThat(view.alpha).isEqualTo(0.5f)
animation.applyTransformation(0.5f, transformation)
assertThat(view.alpha).isEqualTo(0.75f)
animation.applyTransformation(1.0f, transformation)
assertThat(view.alpha).isEqualTo(1.0f)
}
@Test
fun willChangeBounds_returnsFalse() {
animation = OpacityAnimation(view, 0.5f, 1.0f)
assertThat(animation.willChangeBounds()).isFalse()
}
@Test
fun onAnimationStart_setsLayerTypeHardwareIfOverlappingRendering() {
val spyView: View = mock {
on { hasOverlappingRendering() } doReturn true
on { layerType } doReturn View.LAYER_TYPE_NONE
}
val listener = OpacityAnimation.OpacityAnimationListener(spyView)
listener.onAnimationStart(mock())
verify(spyView).setLayerType(View.LAYER_TYPE_HARDWARE, null)
}
@Test
fun onAnimationStart_doesNotChangeLayerTypeIfAlreadySet() {
val spyView: View = mock {
on { hasOverlappingRendering() } doReturn true
on { layerType } doReturn View.LAYER_TYPE_HARDWARE
}
val listener = OpacityAnimation.OpacityAnimationListener(spyView)
listener.onAnimationStart(mock())
verify(spyView, never()).setLayerType(View.LAYER_TYPE_HARDWARE, null)
}
@Test
fun onAnimationEnd_resetsLayerTypeIfChanged() {
val spyView: View = mock {
on { hasOverlappingRendering() } doReturn true
on { layerType } doReturn View.LAYER_TYPE_NONE
}
val listener = OpacityAnimation.OpacityAnimationListener(spyView)
listener.onAnimationStart(mock())
listener.onAnimationEnd(mock())
verify(spyView).setLayerType(View.LAYER_TYPE_NONE, null)
}
@Test
fun onAnimationRepeat_doesNothing() {
val listener = OpacityAnimation.OpacityAnimationListener(mock())
listener.onAnimationRepeat(mock())
// No assertions needed, just verifying it runs without errors
}
}
@@ -106,7 +106,7 @@ class RN_EXPORT [[deprecated(
* necessary native modules methods.
*/
virtual void invokeCallback(
double callbackId,
const double callbackId,
const folly::dynamic& arguments) = 0;
virtual void setGlobalVariable(

Some files were not shown because too many files have changed in this diff Show More