mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c863979abc | ||
|
|
27d152cd82 | ||
|
|
b7b0013348 | ||
|
|
325217371f | ||
|
|
6f1e48d34d | ||
|
|
0aa79897e6 | ||
|
|
08a6a1ae65 | ||
|
|
ad732b84ab | ||
|
|
284a20b0de | ||
|
|
742c205e52 | ||
|
|
93dc62ac08 | ||
|
|
e0b07568fe | ||
|
|
5adfe16ad2 | ||
|
|
7b456c0e3f | ||
|
|
0e91eadc12 | ||
|
|
1e8c9639a0 | ||
|
|
269456cf55 |
@@ -14,8 +14,8 @@ jobs:
|
||||
with:
|
||||
repo-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
|
||||
days-before-stale: 180
|
||||
stale-issue-message: 'This issue is stale because it has been open for 180 days with no activity. It will be closed in 7 days unless you comment on it or remove the "Stale" label.'
|
||||
stale-pr-message: 'This PR is stale because it has been open for 180 days with no activity. It will be closed in 7 days unless you comment on it or remove the "Stale" label.'
|
||||
stale-issue-message: 'This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
|
||||
stale-pr-message: 'This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
|
||||
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'
|
||||
exempt-issue-labels: 'Help Wanted :octocat:, Good first issue, Never gets stale, Issue: Author Provided Repro'
|
||||
|
||||
+1
-1
@@ -124,7 +124,7 @@ if (project.findProperty("react.internal.useHermesNightly")?.toString()?.toBoole
|
||||
configurations.all {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute(project(":packages:react-native:ReactAndroid:hermes-engine"))
|
||||
.using(module("com.facebook.react:hermes-android:0.+"))
|
||||
.using(module("com.facebook.react:hermes-android:0.0.0-+"))
|
||||
.because("Users opted to use hermes from nightly")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@generated SignedSource<<cf5be4596d1f07e220b710fc286d02fa>>
|
||||
Git revision: d95ac13bf0ab64a5e6c2eb18eb138587063b9c34
|
||||
@generated SignedSource<<9ada78dff12dcfc7937c8934261f47f4>>
|
||||
Git revision: 68cfd0ae84acb0ed8e47b421afd64ae3b0b5b727
|
||||
Built with --nohooks: false
|
||||
Is local checkout: false
|
||||
Remote URL: https://github.com/facebook/react-native-devtools-frontend
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -9349,11 +9349,6 @@ export type {
|
||||
PublicRootInstance,
|
||||
PublicTextInstance,
|
||||
} from \\"./Libraries/ReactPrivate/ReactNativePrivateInterface\\";
|
||||
export {
|
||||
default as unstable_VirtualView,
|
||||
VirtualViewMode,
|
||||
} from \\"./src/private/components/virtualview/VirtualView\\";
|
||||
export type { ModeChangeEvent } from \\"./src/private/components/virtualview/VirtualView\\";
|
||||
"
|
||||
`;
|
||||
|
||||
|
||||
@@ -397,7 +397,6 @@ let reactFabric = RNTarget(
|
||||
"components/textinput",
|
||||
"components/textinput/platform/ios/",
|
||||
"components/unimplementedview",
|
||||
"components/virtualview",
|
||||
"components/root/tests",
|
||||
],
|
||||
dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga],
|
||||
@@ -416,8 +415,6 @@ let reactFabricComponents = RNTarget(
|
||||
name: .reactFabricComponents,
|
||||
path: "ReactCommon/react/renderer",
|
||||
excludedPaths: [
|
||||
"components/modal/platform/android",
|
||||
"components/modal/platform/cxx",
|
||||
"components/view/platform/android",
|
||||
"components/view/platform/windows",
|
||||
"components/view/platform/macos",
|
||||
@@ -433,7 +430,7 @@ let reactFabricComponents = RNTarget(
|
||||
"conponents/rncore", // this was the old folder where RN Core Components were generated. If you ran codegen in the past, you might have some files in it that might make the build fail.
|
||||
],
|
||||
dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging],
|
||||
sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "components/virtualview", "textlayoutmanager", "textlayoutmanager/platform/ios"]
|
||||
sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "textlayoutmanager", "textlayoutmanager/platform/ios"]
|
||||
)
|
||||
|
||||
/// React-FabricImage.podspec
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# 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.
|
||||
|
||||
require "json"
|
||||
|
||||
begin
|
||||
react_native_path = File.dirname(Pod::Executable.execute_command('node', ['-p',
|
||||
'require.resolve(
|
||||
"react-native",
|
||||
{paths: [process.argv[1]]},
|
||||
)', __dir__]).strip
|
||||
)
|
||||
rescue => e
|
||||
# Fallback to the parent directory if the above command fails (e.g when building locally in OOT Platform)
|
||||
react_native_path = File.join(__dir__, "..", "..")
|
||||
end
|
||||
|
||||
# package.json
|
||||
package = JSON.parse(File.read(File.join(react_native_path, "package.json")))
|
||||
version = package['version']
|
||||
|
||||
source = ReactNativeCoreUtils.resolve_podspec_source()
|
||||
|
||||
header_search_paths = [
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/RCTDeprecation",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/ReactCommon/yoga",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/ReactCommon",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/Libraries/AppDelegate",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/Libraries",
|
||||
|
||||
"$(REACT_NATIVE_PATH)/React/Base",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon",
|
||||
"$(REACT_NATIVE_PATH)/Libraries",
|
||||
"$(REACT_NATIVE_PATH)/ReactApple",
|
||||
"$(REACT_NATIVE_PATH)/ReactCxxPlatform",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon/react/runtime/platform/ios",
|
||||
"${REACT_NATIVE_PATH}/ReactCommon/jsi",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon/jsiexecutor/",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon/react/nativemodule/samples/platform/ios",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon/react/nativemodule/samples",
|
||||
]
|
||||
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'React-Core-prebuilt'
|
||||
spec.version = version
|
||||
spec.summary = "Prebuilt core of React Native."
|
||||
spec.homepage = "https://reactnative.dev/"
|
||||
spec.description = 'Prebuilt React Native Core libraries and headers'
|
||||
spec.homepage = 'https://github.com/facebook/react-native'
|
||||
spec.license = package['license']
|
||||
spec.authors = 'meta'
|
||||
spec.platforms = min_supported_versions
|
||||
spec.source = source
|
||||
|
||||
spec.vendored_frameworks = "React.xcframework"
|
||||
|
||||
spec.preserve_paths = '**/*.*'
|
||||
spec.header_mappings_dir = 'React.xcframework/Headers'
|
||||
spec.source_files = 'React.xcframework/Headers/**/*.{h,hpp}'
|
||||
|
||||
spec.module_name = 'React'
|
||||
spec.module_map = 'React.xcframework/Modules/module.modulemap'
|
||||
spec.public_header_files = 'React.xcframework/Headers/**/*.h'
|
||||
|
||||
# Setup the consuming project's search paths
|
||||
spec.user_target_xcconfig = {
|
||||
"HEADER_SEARCH_PATHS" => header_search_paths,
|
||||
"SWIFT_INCLUDE_PATHS" => "${PODS_ROOT}/Headers/Private/React-Core-prebuilt",
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
'CLANG_ENABLE_MODULES' => 'YES',
|
||||
}
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'WARNING_CFLAGS' => '-Wno-comma -Wno-shorten-64-to-32',
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
'CLANG_ENABLE_MODULES' => 'YES',
|
||||
}
|
||||
|
||||
# We need to make sure that the React.xcframework is copied correctly - in the downloaded tarball
|
||||
# the root directory is the framework, but when using it we need to have it in a subdirectory
|
||||
# called React.xcframework, so we need to move the contents of the tarball into that directory.
|
||||
# This is done in the prepare_command.
|
||||
spec.prepare_command = <<~'CMD'
|
||||
CURRENT_PATH=$(pwd)
|
||||
XCFRAMEWORK_PATH="${CURRENT_PATH}/React.xcframework"
|
||||
mkdir -p "${XCFRAMEWORK_PATH}"
|
||||
find "$CURRENT_PATH" -mindepth 1 -maxdepth 1 ! -name "$(basename "$XCFRAMEWORK_PATH")" -exec mv {} "$XCFRAMEWORK_PATH" \;
|
||||
CMD
|
||||
|
||||
# If we are passing a local tarball, we don't want to switch between Debug and Release
|
||||
if !ENV["RCT_USE_LOCAL_RN_CORE"]
|
||||
script_phase = {
|
||||
:name => "[RNDeps] Replace React Native Core for the right configuration, if needed",
|
||||
:execution_position => :before_compile,
|
||||
:script => <<-EOS
|
||||
. "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh"
|
||||
|
||||
CONFIG="Release"
|
||||
if echo $GCC_PREPROCESSOR_DEFINITIONS | grep -q "DEBUG=1"; then
|
||||
CONFIG="Debug"
|
||||
fi
|
||||
|
||||
# TODO(T228219721): Add this for React Native Core as well
|
||||
##### "$NODE_BINARY" "$REACT_NATIVE_PATH/third-party-podspecs/replace_dependencies_version.js" -c "$CONFIG" -r "#{version}" -p "$PODS_ROOT"
|
||||
EOS
|
||||
}
|
||||
|
||||
|
||||
# :always_out_of_date is only available in CocoaPods 1.13.0 and later
|
||||
if Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.13.0')
|
||||
# always run the script without warning
|
||||
script_phase[:always_out_of_date] = "1"
|
||||
end
|
||||
|
||||
spec.script_phase = script_phase
|
||||
end
|
||||
|
||||
end
|
||||
+1
-1
@@ -1215,7 +1215,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||
result = [NSMutableString string];
|
||||
}
|
||||
if (result.length > 0) {
|
||||
[result appendString:@", "];
|
||||
[result appendString:@" "];
|
||||
}
|
||||
[result appendString:label];
|
||||
}
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <React/RCTViewComponentView.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTVirtualViewComponentView : RCTViewComponentView
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
-295
@@ -1,295 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTVirtualViewComponentView.h"
|
||||
|
||||
#import <React/RCTAssert.h>
|
||||
#import <React/RCTConversions.h>
|
||||
#import <React/RCTScrollViewComponentView.h>
|
||||
#import <React/RCTScrollableProtocol.h>
|
||||
#import <React/UIView+React.h>
|
||||
#import <jsi/jsi.h>
|
||||
|
||||
#import <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#import <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
|
||||
#import <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
|
||||
#import <react/renderer/components/FBReactNativeSpec/Props.h>
|
||||
#import <react/renderer/components/virtualview/VirtualViewComponentDescriptor.h>
|
||||
#import <react/renderer/components/virtualview/VirtualViewShadowNode.h>
|
||||
|
||||
#import "RCTFabricComponentsPlugins.h"
|
||||
|
||||
using namespace facebook;
|
||||
using namespace facebook::react;
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTVirtualViewMode) {
|
||||
RCTVirtualViewModeVisible = 0,
|
||||
RCTVirtualViewModePrerender = 1,
|
||||
RCTVirtualViewModeHidden = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether one CGRect overlaps with another CGRect.
|
||||
*
|
||||
* This is different from CGRectIntersectsRect because a CGRect representing
|
||||
* a line or a point is considered to overlap with another CGRect if the line
|
||||
* or point is within the rect bounds. However, two CGRects are not considered
|
||||
* to overlap if they only share a boundary.
|
||||
*/
|
||||
static BOOL CGRectOverlaps(CGRect rect1, CGRect rect2)
|
||||
{
|
||||
CGFloat minY1 = CGRectGetMinY(rect1);
|
||||
CGFloat maxY1 = CGRectGetMaxY(rect1);
|
||||
CGFloat minY2 = CGRectGetMinY(rect2);
|
||||
CGFloat maxY2 = CGRectGetMaxY(rect2);
|
||||
if (minY1 >= maxY2 || minY2 >= maxY1) {
|
||||
// No overlap on the y-axis.
|
||||
return NO;
|
||||
}
|
||||
CGFloat minX1 = CGRectGetMinX(rect1);
|
||||
CGFloat maxX1 = CGRectGetMaxX(rect1);
|
||||
CGFloat minX2 = CGRectGetMinX(rect2);
|
||||
CGFloat maxX2 = CGRectGetMaxX(rect2);
|
||||
if (minX1 >= maxX2 || minX2 >= maxX1) {
|
||||
// No overlap on the x-axis.
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
@interface RCTVirtualViewComponentView () <UIScrollViewDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTVirtualViewComponentView {
|
||||
RCTScrollViewComponentView *_lastParentScrollViewComponentView;
|
||||
std::optional<enum RCTVirtualViewMode> _mode;
|
||||
std::optional<CGRect> _targetRect;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_props = VirtualViewShadowNode::defaultSharedProps();
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
|
||||
{
|
||||
if (!_mode.has_value()) {
|
||||
const auto &newViewProps = static_cast<const VirtualViewProps &>(*props);
|
||||
_mode = newViewProps.initialHidden ? RCTVirtualViewModeHidden : RCTVirtualViewModeVisible;
|
||||
}
|
||||
|
||||
[super updateProps:props oldProps:oldProps];
|
||||
}
|
||||
|
||||
- (RCTScrollViewComponentView *)getParentScrollViewComponentView
|
||||
{
|
||||
UIView *view = self.superview;
|
||||
while (view != nil) {
|
||||
if ([view isKindOfClass:[RCTScrollViewComponentView class]]) {
|
||||
return (RCTScrollViewComponentView *)view;
|
||||
}
|
||||
view = view.superview;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)prepareForRecycle
|
||||
{
|
||||
[super prepareForRecycle];
|
||||
|
||||
// No need to remove the scroll listener here since the view is always removed from window before being recycled and
|
||||
// we do that in didMoveToWindow, which gets called when the view is removed from window.
|
||||
RCTAssert(
|
||||
_lastParentScrollViewComponentView == nil,
|
||||
@"_lastParentScrollViewComponentView should already have been cleared in didMoveToWindow.");
|
||||
|
||||
_mode.reset();
|
||||
_targetRect.reset();
|
||||
}
|
||||
|
||||
// Handles case when sibling changes size.
|
||||
// TODO(T202601695): This doesn't yet handle the case of elements in the ScrollView outside a VirtualColumn changing
|
||||
// size.
|
||||
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
|
||||
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
|
||||
{
|
||||
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_layoutMetrics];
|
||||
|
||||
[self dispatchOnModeChangeIfNeeded:YES];
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
|
||||
if (_lastParentScrollViewComponentView) {
|
||||
[_lastParentScrollViewComponentView removeScrollListener:self];
|
||||
_lastParentScrollViewComponentView = nil;
|
||||
}
|
||||
|
||||
if (RCTScrollViewComponentView *parentScrollViewComponentView = [self getParentScrollViewComponentView]) {
|
||||
if (self.window) {
|
||||
// TODO(T202601695): We also want the ScrollView to emit layout changes from didLayoutSubviews so that any event
|
||||
// that may affect visibily of this view notifies the listeners.
|
||||
[parentScrollViewComponentView addScrollListener:self];
|
||||
_lastParentScrollViewComponentView = parentScrollViewComponentView;
|
||||
|
||||
// We want to dispatch the event immediately when the view is added to the window before any scrolling occurs.
|
||||
[self dispatchOnModeChangeIfNeeded:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
[self dispatchOnModeChangeIfNeeded:NO];
|
||||
}
|
||||
|
||||
- (void)dispatchOnModeChangeIfNeeded:(BOOL)checkForTargetRectChange
|
||||
{
|
||||
if (!_lastParentScrollViewComponentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIScrollView *scrollView = _lastParentScrollViewComponentView.scrollView;
|
||||
CGRect targetRect = [self convertRect:self.bounds toView:scrollView];
|
||||
|
||||
// While scrolling, the `targetRect` does not change, so we don't check for changed `targetRect` in that case.
|
||||
if (checkForTargetRectChange) {
|
||||
if (_targetRect.has_value() && CGRectEqualToRect(targetRect, _targetRect.value())) {
|
||||
return;
|
||||
}
|
||||
_targetRect = targetRect;
|
||||
}
|
||||
|
||||
enum RCTVirtualViewMode newMode;
|
||||
CGRect thresholdRect = CGRectMake(
|
||||
scrollView.contentOffset.x,
|
||||
scrollView.contentOffset.y,
|
||||
scrollView.frame.size.width,
|
||||
scrollView.frame.size.height);
|
||||
if (CGRectOverlaps(targetRect, thresholdRect)) {
|
||||
newMode = RCTVirtualViewModeVisible;
|
||||
} else {
|
||||
auto prerender = false;
|
||||
const CGFloat prerenderRatio = ReactNativeFeatureFlags::virtualViewPrerenderRatio();
|
||||
if (prerenderRatio > 0) {
|
||||
thresholdRect = CGRectInset(
|
||||
thresholdRect, -thresholdRect.size.width * prerenderRatio, -thresholdRect.size.height * prerenderRatio);
|
||||
prerender = CGRectOverlaps(targetRect, thresholdRect);
|
||||
}
|
||||
if (prerender) {
|
||||
newMode = RCTVirtualViewModePrerender;
|
||||
} else {
|
||||
newMode = RCTVirtualViewModeHidden;
|
||||
thresholdRect = CGRectZero;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mode.has_value() && newMode == _mode.value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: Make sure to keep these props in sync with dispatchSyncModeChange below where we have to explicitly copy all
|
||||
// props.
|
||||
VirtualViewEventEmitter::OnModeChange event = {
|
||||
.mode = (int)newMode,
|
||||
.targetRect =
|
||||
{.x = targetRect.origin.x,
|
||||
.y = targetRect.origin.y,
|
||||
.width = targetRect.size.width,
|
||||
.height = targetRect.size.height},
|
||||
.thresholdRect =
|
||||
{.x = thresholdRect.origin.x,
|
||||
.y = thresholdRect.origin.y,
|
||||
.width = thresholdRect.size.width,
|
||||
.height = thresholdRect.size.height},
|
||||
};
|
||||
|
||||
const std::optional<enum RCTVirtualViewMode> oldMode = _mode;
|
||||
_mode = newMode;
|
||||
|
||||
switch (newMode) {
|
||||
case RCTVirtualViewModeVisible:
|
||||
[self dispatchSyncModeChange:event];
|
||||
break;
|
||||
case RCTVirtualViewModePrerender:
|
||||
if (!oldMode.has_value() || oldMode != RCTVirtualViewModeVisible) {
|
||||
[self dispatchAsyncModeChange:event];
|
||||
}
|
||||
break;
|
||||
case RCTVirtualViewModeHidden:
|
||||
[self dispatchAsyncModeChange:event];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dispatchAsyncModeChange:(VirtualViewEventEmitter::OnModeChange &)event
|
||||
{
|
||||
if (!_eventEmitter) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<const VirtualViewEventEmitter> emitter =
|
||||
std::static_pointer_cast<const VirtualViewEventEmitter>(_eventEmitter);
|
||||
emitter->onModeChange(event);
|
||||
}
|
||||
|
||||
- (void)dispatchSyncModeChange:(VirtualViewEventEmitter::OnModeChange &)event
|
||||
{
|
||||
if (!_eventEmitter) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<const VirtualViewEventEmitter> emitter =
|
||||
std::static_pointer_cast<const VirtualViewEventEmitter>(_eventEmitter);
|
||||
|
||||
// TODO: Move this into a custom event emitter. We had to duplicate the codegen code here from onModeChange in order
|
||||
// to dispatch synchronously and discrete.
|
||||
emitter->experimental_flushSync([&emitter, &event]() {
|
||||
emitter->dispatchEvent(
|
||||
"modeChange",
|
||||
[event](jsi::Runtime &runtime) {
|
||||
auto payload = jsi::Object(runtime);
|
||||
payload.setProperty(runtime, "mode", event.mode);
|
||||
{
|
||||
auto targetRect = jsi::Object(runtime);
|
||||
targetRect.setProperty(runtime, "x", event.targetRect.x);
|
||||
targetRect.setProperty(runtime, "y", event.targetRect.y);
|
||||
targetRect.setProperty(runtime, "width", event.targetRect.width);
|
||||
targetRect.setProperty(runtime, "height", event.targetRect.height);
|
||||
payload.setProperty(runtime, "targetRect", targetRect);
|
||||
}
|
||||
{
|
||||
auto thresholdRect = jsi::Object(runtime);
|
||||
thresholdRect.setProperty(runtime, "x", event.thresholdRect.x);
|
||||
thresholdRect.setProperty(runtime, "y", event.thresholdRect.y);
|
||||
thresholdRect.setProperty(runtime, "width", event.thresholdRect.width);
|
||||
thresholdRect.setProperty(runtime, "height", event.thresholdRect.height);
|
||||
payload.setProperty(runtime, "thresholdRect", thresholdRect);
|
||||
}
|
||||
return payload;
|
||||
},
|
||||
RawEvent::Category::Discrete);
|
||||
});
|
||||
}
|
||||
|
||||
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
||||
{
|
||||
return concreteComponentDescriptorProvider<VirtualViewComponentDescriptor>();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Class<RCTComponentViewProtocol> VirtualViewCls(void)
|
||||
{
|
||||
return RCTVirtualViewComponentView.class;
|
||||
}
|
||||
@@ -16,12 +16,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
+ (std::vector<facebook::react::ProcessedColorStop>)getFixedColorStops:
|
||||
(const std::vector<facebook::react::ColorStop> &)colorStops
|
||||
gradientLineLength:(CGFloat)gradientLineLength;
|
||||
// CAGradientLayer linear gradient squishes the non-square gradient to square gradient.
|
||||
// This function fixes the "squished" effect.
|
||||
// See https://stackoverflow.com/a/43176174 for more information.
|
||||
+ (std::pair<CGPoint, CGPoint>)pointsForCAGradientLayerLinearGradient:(CGPoint)startPoint
|
||||
endPoint:(CGPoint)endPoint
|
||||
bounds:(CGSize)bounds;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -9,140 +9,10 @@
|
||||
#import <React/RCTAnimationUtils.h>
|
||||
#import <React/RCTConversions.h>
|
||||
#import <react/utils/FloatComparison.h>
|
||||
#include <optional>
|
||||
#import <vector>
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Line;
|
||||
|
||||
struct LineSegment {
|
||||
CGPoint p1;
|
||||
CGPoint p2;
|
||||
|
||||
LineSegment(CGPoint p1, CGPoint p2) : p1(p1), p2(p2) {}
|
||||
|
||||
LineSegment(CGPoint p1, CGFloat m, CGFloat distance);
|
||||
|
||||
CGFloat getLength() const
|
||||
{
|
||||
CGFloat dx = p2.x - p1.x;
|
||||
CGFloat dy = p2.y - p1.y;
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
CGFloat getDistance() const
|
||||
{
|
||||
return p1.x <= p2.x ? getLength() : -getLength();
|
||||
}
|
||||
|
||||
CGPoint getMidpoint() const
|
||||
{
|
||||
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
||||
}
|
||||
|
||||
CGFloat getSlope() const
|
||||
{
|
||||
CGFloat dx = p2.x - p1.x;
|
||||
if (floatEquality(dx, 0.0)) {
|
||||
return std::numeric_limits<CGFloat>::infinity();
|
||||
}
|
||||
return (p2.y - p1.y) / dx;
|
||||
}
|
||||
|
||||
CGFloat getPerpendicularSlope() const
|
||||
{
|
||||
CGFloat slope = getSlope();
|
||||
if (std::isinf(slope)) {
|
||||
return 0.0;
|
||||
}
|
||||
if (floatEquality(slope, 0.0)) {
|
||||
return -std::numeric_limits<CGFloat>::infinity();
|
||||
}
|
||||
return -1 / slope;
|
||||
}
|
||||
|
||||
Line toLine() const;
|
||||
|
||||
LineSegment perpendicularBisector() const
|
||||
{
|
||||
CGPoint midpoint = getMidpoint();
|
||||
CGFloat perpSlope = getPerpendicularSlope();
|
||||
CGFloat dist = getDistance();
|
||||
return {LineSegment(midpoint, perpSlope, -dist / 2).p2, LineSegment(midpoint, perpSlope, dist / 2).p2};
|
||||
}
|
||||
|
||||
LineSegment multiplied(CGSize multipliers) const
|
||||
{
|
||||
return {
|
||||
CGPointMake(p1.x * multipliers.width, p1.y * multipliers.height),
|
||||
CGPointMake(p2.x * multipliers.width, p2.y * multipliers.height)};
|
||||
}
|
||||
|
||||
LineSegment divided(CGSize divisors) const
|
||||
{
|
||||
return multiplied(CGSizeMake(1 / divisors.width, 1 / divisors.height));
|
||||
}
|
||||
};
|
||||
|
||||
struct Line {
|
||||
CGFloat m;
|
||||
CGFloat b;
|
||||
|
||||
Line(CGFloat m, CGFloat b) : m(m), b(b) {}
|
||||
Line(CGFloat m, CGPoint p) : m(m), b(p.y - m * p.x) {}
|
||||
Line(CGPoint p1, CGPoint p2) : m(LineSegment(p1, p2).getSlope()), b(p1.y - m * p1.x) {}
|
||||
|
||||
CGFloat y(CGFloat x) const
|
||||
{
|
||||
return m * x + b;
|
||||
}
|
||||
|
||||
CGPoint point(CGFloat x) const
|
||||
{
|
||||
return CGPointMake(x, y(x));
|
||||
}
|
||||
|
||||
std::optional<CGPoint> intersection(const Line &other) const
|
||||
{
|
||||
CGFloat n = other.m;
|
||||
CGFloat c = other.b;
|
||||
if (floatEquality(m, n)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
CGFloat x = (c - b) / (m - n);
|
||||
return point(x);
|
||||
}
|
||||
};
|
||||
|
||||
LineSegment::LineSegment(CGPoint p1, CGFloat m, CGFloat distance) : p1(p1)
|
||||
{
|
||||
Line line(m, p1);
|
||||
CGPoint measuringPoint = line.point(p1.x + 1);
|
||||
LineSegment measuringSegment(p1, measuringPoint);
|
||||
CGFloat measuringDeltaH = measuringSegment.getDistance();
|
||||
CGFloat deltaX = !floatEquality(measuringDeltaH, 0.0) ? distance / measuringDeltaH : 0.0;
|
||||
p2 = line.point(p1.x + deltaX);
|
||||
}
|
||||
|
||||
Line LineSegment::toLine() const
|
||||
{
|
||||
return {p1, p2};
|
||||
}
|
||||
|
||||
CGSize calculateMultipliers(CGSize bounds)
|
||||
{
|
||||
if (bounds.height <= bounds.width) {
|
||||
return CGSizeMake(1, bounds.width / bounds.height);
|
||||
} else {
|
||||
return CGSizeMake(bounds.height / bounds.width, 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static std::optional<Float> resolveColorStopPosition(ValueUnit position, CGFloat gradientLineLength)
|
||||
{
|
||||
if (position.unit == UnitType::Point) {
|
||||
@@ -182,21 +52,21 @@ static std::vector<ProcessedColorStop> processColorTransitionHints(const std::ve
|
||||
auto leftDist = offset - offsetLeft;
|
||||
auto rightDist = offsetRight - offset;
|
||||
auto totalDist = offsetRight - offsetLeft;
|
||||
const SharedColor &leftSharedColor = colorStops[x - 1].color;
|
||||
const SharedColor &rightSharedColor = colorStops[x + 1].color;
|
||||
SharedColor leftSharedColor = colorStops[x - 1].color;
|
||||
SharedColor rightSharedColor = colorStops[x + 1].color;
|
||||
|
||||
if (floatEquality(leftDist, rightDist)) {
|
||||
if (facebook::react::floatEquality(leftDist, rightDist)) {
|
||||
colorStops.erase(colorStops.begin() + x);
|
||||
--indexOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (floatEquality(leftDist, 0.0)) {
|
||||
if (facebook::react::floatEquality(leftDist, .0f)) {
|
||||
colorStops[x].color = rightSharedColor;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (floatEquality(rightDist, 0.0)) {
|
||||
if (facebook::react::floatEquality(rightDist, .0f)) {
|
||||
colorStops[x].color = leftSharedColor;
|
||||
continue;
|
||||
}
|
||||
@@ -250,7 +120,7 @@ static std::vector<ProcessedColorStop> processColorTransitionHints(const std::ve
|
||||
auto green = (interpolatedColor >> 8) & 0xFF;
|
||||
auto blue = interpolatedColor & 0xFF;
|
||||
|
||||
newStop.color = colorFromRGBA(red, green, blue, alpha);
|
||||
newStop.color = facebook::react::colorFromRGBA(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
// Replace the color hint with new color stops
|
||||
@@ -331,47 +201,4 @@ static std::vector<ProcessedColorStop> processColorTransitionHints(const std::ve
|
||||
}
|
||||
return processColorTransitionHints(fixedColorStops);
|
||||
}
|
||||
|
||||
// CAGradientLayer linear gradient squishes the non-square gradient to square gradient.
|
||||
// This function fixes the "squished" effect.
|
||||
// See https://stackoverflow.com/a/43176174 for more information.
|
||||
+ (std::pair<CGPoint, CGPoint>)pointsForCAGradientLayerLinearGradient:(CGPoint)startPoint
|
||||
endPoint:(CGPoint)endPoint
|
||||
bounds:(CGSize)bounds
|
||||
{
|
||||
if (floatEquality(startPoint.x, endPoint.x) || floatEquality(startPoint.y, endPoint.y)) {
|
||||
// Apple's implementation of horizontal and vertical gradients works just fine
|
||||
return {startPoint, endPoint};
|
||||
}
|
||||
|
||||
LineSegment startEnd(startPoint, endPoint);
|
||||
LineSegment ab = startEnd.multiplied({bounds.width, bounds.height});
|
||||
const CGPoint a = ab.p1;
|
||||
const CGPoint b = ab.p2;
|
||||
|
||||
LineSegment cd = ab.perpendicularBisector();
|
||||
|
||||
CGSize multipliers = calculateMultipliers(bounds);
|
||||
LineSegment lineSegmentCD = cd.multiplied(multipliers);
|
||||
|
||||
LineSegment lineSegmentEF = lineSegmentCD.perpendicularBisector();
|
||||
|
||||
LineSegment ef = lineSegmentEF.divided(multipliers);
|
||||
|
||||
Line efLine = ef.toLine();
|
||||
|
||||
Line aParallelLine(cd.getSlope(), a);
|
||||
Line bParallelLine(cd.getSlope(), b);
|
||||
|
||||
std::optional<CGPoint> g_opt = efLine.intersection(aParallelLine);
|
||||
std::optional<CGPoint> h_opt = efLine.intersection(bParallelLine);
|
||||
|
||||
if (g_opt && h_opt) {
|
||||
LineSegment gh(*g_opt, *h_opt);
|
||||
LineSegment result = gh.divided({bounds.width, bounds.height});
|
||||
return {result.p1, result.p2};
|
||||
}
|
||||
|
||||
return {startPoint, endPoint};
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -19,52 +19,54 @@ using namespace facebook::react;
|
||||
|
||||
+ (CALayer *)gradientLayerWithSize:(CGSize)size gradient:(const LinearGradient &)gradient
|
||||
{
|
||||
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
|
||||
const auto &direction = gradient.direction;
|
||||
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
|
||||
CGPoint startPoint;
|
||||
CGPoint endPoint;
|
||||
UIImage *gradientImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
|
||||
CGPoint startPoint;
|
||||
CGPoint endPoint;
|
||||
|
||||
if (direction.type == GradientDirectionType::Angle) {
|
||||
CGFloat angle = std::get<Float>(direction.value);
|
||||
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
|
||||
} else if (direction.type == GradientDirectionType::Keyword) {
|
||||
auto keyword = std::get<GradientKeyword>(direction.value);
|
||||
CGFloat angle = getAngleForKeyword(keyword, size);
|
||||
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
|
||||
} else {
|
||||
// Default to top-to-bottom gradient
|
||||
CGFloat centerX = size.width / 2;
|
||||
startPoint = CGPointMake(centerX, 0.0);
|
||||
endPoint = CGPointMake(centerX, size.height);
|
||||
}
|
||||
if (direction.type == GradientDirectionType::Angle) {
|
||||
CGFloat angle = std::get<Float>(direction.value);
|
||||
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
|
||||
} else if (direction.type == GradientDirectionType::Keyword) {
|
||||
auto keyword = std::get<GradientKeyword>(direction.value);
|
||||
CGFloat angle = getAngleForKeyword(keyword, size);
|
||||
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
|
||||
} else {
|
||||
// Default to top-to-bottom gradient
|
||||
startPoint = CGPointMake(0.0, 0.0);
|
||||
endPoint = CGPointMake(0.0, size.height);
|
||||
}
|
||||
|
||||
CGFloat dx = endPoint.x - startPoint.x;
|
||||
CGFloat dy = endPoint.y - startPoint.y;
|
||||
CGFloat gradientLineLength = sqrt(dx * dx + dy * dy);
|
||||
const auto colorStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops
|
||||
gradientLineLength:gradientLineLength];
|
||||
NSMutableArray<id> *colors = [NSMutableArray array];
|
||||
NSMutableArray<NSNumber *> *locations = [NSMutableArray array];
|
||||
CGPoint relativeStartPoint = CGPointMake(startPoint.x / size.width, startPoint.y / size.height);
|
||||
CGPoint relativeEndPoint = CGPointMake(endPoint.x / size.width, endPoint.y / size.height);
|
||||
CGFloat dx = endPoint.x - startPoint.x;
|
||||
CGFloat dy = endPoint.y - startPoint.y;
|
||||
CGFloat gradientLineLength = sqrt(dx * dx + dy * dy);
|
||||
const auto colorStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops
|
||||
gradientLineLength:gradientLineLength];
|
||||
|
||||
CGPoint fixedStartPoint;
|
||||
CGPoint fixedEndPoint;
|
||||
CGContextRef context = rendererContext.CGContext;
|
||||
NSMutableArray *colors = [NSMutableArray array];
|
||||
CGFloat locations[colorStops.size()];
|
||||
|
||||
std::tie(fixedStartPoint, fixedEndPoint) = [RCTGradientUtils pointsForCAGradientLayerLinearGradient:relativeStartPoint
|
||||
endPoint:relativeEndPoint
|
||||
bounds:size];
|
||||
for (size_t i = 0; i < colorStops.size(); ++i) {
|
||||
const auto &colorStop = colorStops[i];
|
||||
CGColorRef cgColor = RCTCreateCGColorRefFromSharedColor(colorStop.color);
|
||||
[colors addObject:(__bridge id)cgColor];
|
||||
locations[i] = std::max(std::min(colorStop.position.value(), 1.0), 0.0);
|
||||
}
|
||||
|
||||
gradientLayer.startPoint = fixedStartPoint;
|
||||
gradientLayer.endPoint = fixedEndPoint;
|
||||
CGGradientRef cgGradient = CGGradientCreateWithColors(NULL, (__bridge CFArrayRef)colors, locations);
|
||||
|
||||
for (const auto &colorStop : colorStops) {
|
||||
[colors addObject:(id)RCTUIColorFromSharedColor(colorStop.color).CGColor];
|
||||
[locations addObject:@(std::max(std::min(colorStop.position.value(), 1.0), 0.0))];
|
||||
}
|
||||
CGContextDrawLinearGradient(context, cgGradient, startPoint, endPoint, 0);
|
||||
|
||||
gradientLayer.colors = colors;
|
||||
gradientLayer.locations = locations;
|
||||
for (id color in colors) {
|
||||
CGColorRelease((__bridge CGColorRef)color);
|
||||
}
|
||||
CGGradientRelease(cgGradient);
|
||||
}];
|
||||
|
||||
CALayer *gradientLayer = [CALayer layer];
|
||||
gradientLayer.contents = (__bridge id)gradientImage.CGImage;
|
||||
|
||||
return gradientLayer;
|
||||
}
|
||||
|
||||
@@ -148,29 +148,29 @@ public class com/facebook/react/ReactDelegate {
|
||||
public fun <init> (Landroid/app/Activity;Lcom/facebook/react/ReactNativeHost;Ljava/lang/String;Landroid/os/Bundle;)V
|
||||
public fun <init> (Landroid/app/Activity;Lcom/facebook/react/ReactNativeHost;Ljava/lang/String;Landroid/os/Bundle;Z)V
|
||||
protected fun createRootView ()Lcom/facebook/react/ReactRootView;
|
||||
public final fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
|
||||
public final fun getReactHost ()Lcom/facebook/react/ReactHost;
|
||||
public final fun getReactInstanceManager ()Lcom/facebook/react/ReactInstanceManager;
|
||||
public final fun getReactRootView ()Lcom/facebook/react/ReactRootView;
|
||||
protected final fun isFabricEnabled ()Z
|
||||
public final fun loadApp ()V
|
||||
public final fun loadApp (Ljava/lang/String;)V
|
||||
public final fun onActivityResult (IILandroid/content/Intent;Z)V
|
||||
public final fun onBackPressed ()Z
|
||||
public final fun onConfigurationChanged (Landroid/content/res/Configuration;)V
|
||||
public final fun onHostDestroy ()V
|
||||
public final fun onHostPause ()V
|
||||
public final fun onHostResume ()V
|
||||
public final fun onKeyDown (ILandroid/view/KeyEvent;)Z
|
||||
public final fun onKeyLongPress (I)Z
|
||||
public final fun onNewIntent (Landroid/content/Intent;)Z
|
||||
public final fun onUserLeaveHint ()V
|
||||
public final fun onWindowFocusChanged (Z)V
|
||||
public final fun reload ()V
|
||||
public final fun setReactRootView (Lcom/facebook/react/ReactRootView;)V
|
||||
public final fun setReactSurface (Lcom/facebook/react/interfaces/fabric/ReactSurface;)V
|
||||
public final fun shouldShowDevMenuOrReload (ILandroid/view/KeyEvent;)Z
|
||||
public final fun unloadApp ()V
|
||||
public fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
|
||||
public fun getReactHost ()Lcom/facebook/react/ReactHost;
|
||||
public fun getReactInstanceManager ()Lcom/facebook/react/ReactInstanceManager;
|
||||
public fun getReactRootView ()Lcom/facebook/react/ReactRootView;
|
||||
protected fun isFabricEnabled ()Z
|
||||
public fun loadApp ()V
|
||||
public fun loadApp (Ljava/lang/String;)V
|
||||
public fun onActivityResult (IILandroid/content/Intent;Z)V
|
||||
public fun onBackPressed ()Z
|
||||
public fun onConfigurationChanged (Landroid/content/res/Configuration;)V
|
||||
public fun onHostDestroy ()V
|
||||
public fun onHostPause ()V
|
||||
public fun onHostResume ()V
|
||||
public fun onKeyDown (ILandroid/view/KeyEvent;)Z
|
||||
public fun onKeyLongPress (I)Z
|
||||
public fun onNewIntent (Landroid/content/Intent;)Z
|
||||
public fun onUserLeaveHint ()V
|
||||
public fun onWindowFocusChanged (Z)V
|
||||
public fun reload ()V
|
||||
public fun setReactRootView (Lcom/facebook/react/ReactRootView;)V
|
||||
public fun setReactSurface (Lcom/facebook/react/interfaces/fabric/ReactSurface;)V
|
||||
public fun shouldShowDevMenuOrReload (ILandroid/view/KeyEvent;)Z
|
||||
public fun unloadApp ()V
|
||||
}
|
||||
|
||||
public class com/facebook/react/ReactFragment : androidx/fragment/app/Fragment, com/facebook/react/modules/core/PermissionAwareActivity {
|
||||
@@ -259,7 +259,6 @@ public class com/facebook/react/ReactInstanceManager {
|
||||
public fun createViewManager (Ljava/lang/String;)Lcom/facebook/react/uimanager/ViewManager;
|
||||
public fun destroy ()V
|
||||
public fun detachRootView (Lcom/facebook/react/uimanager/ReactRoot;)V
|
||||
public fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
|
||||
public fun getDevSupportManager ()Lcom/facebook/react/devsupport/interfaces/DevSupportManager;
|
||||
public fun getJsExecutorName ()Ljava/lang/String;
|
||||
public fun getLifecycleState ()Lcom/facebook/react/common/LifecycleState;
|
||||
@@ -601,14 +600,12 @@ public abstract interface class com/facebook/react/bridge/CatalystInstance : com
|
||||
public abstract fun getSourceURL ()Ljava/lang/String;
|
||||
public abstract fun hasNativeModule (Ljava/lang/Class;)Z
|
||||
public abstract fun hasRunJSBundle ()Z
|
||||
public abstract fun initialize ()V
|
||||
public abstract fun invokeCallback (ILcom/facebook/react/bridge/NativeArrayInterface;)V
|
||||
public abstract fun isDestroyed ()Z
|
||||
public abstract fun registerSegment (ILjava/lang/String;)V
|
||||
public abstract fun removeBridgeIdleDebugListener (Lcom/facebook/react/bridge/NotThreadSafeBridgeIdleDebugListener;)V
|
||||
public abstract fun runJSBundle ()V
|
||||
public abstract fun setFabricUIManager (Lcom/facebook/react/bridge/UIManager;)V
|
||||
public abstract fun setGlobalVariable (Ljava/lang/String;Ljava/lang/String;)V
|
||||
public abstract fun setTurboModuleRegistry (Lcom/facebook/react/internal/turbomodule/core/interfaces/TurboModuleRegistry;)V
|
||||
}
|
||||
|
||||
@@ -2375,6 +2372,24 @@ public final class com/facebook/react/fabric/FabricUIManagerProviderImpl : com/f
|
||||
public fun createUIManager (Lcom/facebook/react/bridge/ReactApplicationContext;)Lcom/facebook/react/bridge/UIManager;
|
||||
}
|
||||
|
||||
public class com/facebook/react/fabric/mounting/MountItemDispatcher {
|
||||
public fun <init> (Lcom/facebook/react/fabric/mounting/MountingManager;Lcom/facebook/react/fabric/mounting/MountItemDispatcher$ItemDispatchListener;)V
|
||||
public fun addMountItem (Lcom/facebook/react/fabric/mounting/mountitems/MountItem;)V
|
||||
public fun addPreAllocateMountItem (Lcom/facebook/react/fabric/mounting/mountitems/MountItem;)V
|
||||
public fun addViewCommandMountItem (Lcom/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem;)V
|
||||
public fun dispatchMountItems (Ljava/util/Queue;)V
|
||||
public fun dispatchPreMountItems (J)V
|
||||
public fun getBatchedExecutionTime ()J
|
||||
public fun getRunStartTime ()J
|
||||
public fun tryDispatchMountItems ()V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/fabric/mounting/MountItemDispatcher$ItemDispatchListener {
|
||||
public abstract fun didDispatchMountItems ()V
|
||||
public abstract fun didMountItems (Ljava/util/List;)V
|
||||
public abstract fun willMountItems (Ljava/util/List;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/fabric/mounting/SurfaceMountingManager {
|
||||
public static final field TAG Ljava/lang/String;
|
||||
public fun <init> (ILcom/facebook/react/touch/JSResponderHandler;Lcom/facebook/react/uimanager/ViewManagerRegistry;Lcom/facebook/react/uimanager/RootViewManager;Lcom/facebook/react/fabric/mounting/MountingManager$MountItemExecutor;Lcom/facebook/react/uimanager/ThemedReactContext;)V
|
||||
@@ -3300,7 +3315,6 @@ public abstract class com/facebook/react/uimanager/BaseViewManager : com/faceboo
|
||||
public fun getExportedCustomBubblingEventTypeConstants ()Ljava/util/Map;
|
||||
public fun getExportedCustomDirectEventTypeConstants ()Ljava/util/Map;
|
||||
protected fun onAfterUpdateTransaction (Landroid/view/View;)V
|
||||
public fun onDropViewInstance (Landroid/view/View;)V
|
||||
public fun onLayoutChange (Landroid/view/View;IIIIIIII)V
|
||||
protected fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
|
||||
public fun setAccessibilityActions (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
@@ -4890,6 +4904,32 @@ public final class com/facebook/react/uimanager/events/NativeGestureUtil {
|
||||
public static final fun notifyNativeGestureStarted (Landroid/view/View;Landroid/view/MotionEvent;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/uimanager/events/PointerEvent : com/facebook/react/uimanager/events/Event {
|
||||
public fun dispatch (Lcom/facebook/react/uimanager/events/RCTEventEmitter;)V
|
||||
public fun dispatchModern (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;)V
|
||||
public fun getCoalescingKey ()S
|
||||
public fun getEventAnimationDriverMatchSpec ()Lcom/facebook/react/uimanager/events/Event$EventAnimationDriverMatchSpec;
|
||||
public fun getEventName ()Ljava/lang/String;
|
||||
public static fun obtain (Ljava/lang/String;ILcom/facebook/react/uimanager/events/PointerEvent$PointerEventState;Landroid/view/MotionEvent;)Lcom/facebook/react/uimanager/events/PointerEvent;
|
||||
public static fun obtain (Ljava/lang/String;ILcom/facebook/react/uimanager/events/PointerEvent$PointerEventState;Landroid/view/MotionEvent;S)Lcom/facebook/react/uimanager/events/PointerEvent;
|
||||
public fun onDispose ()V
|
||||
}
|
||||
|
||||
public class com/facebook/react/uimanager/events/PointerEvent$PointerEventState {
|
||||
public fun <init> (IIIILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Set;)V
|
||||
public fun getActivePointerId ()I
|
||||
public final fun getEventCoordinatesByPointerId ()Ljava/util/Map;
|
||||
public final fun getHitPathByPointerId ()Ljava/util/Map;
|
||||
public final fun getHitPathForActivePointer ()Ljava/util/List;
|
||||
public fun getHoveringPointerIds ()Ljava/util/Set;
|
||||
public fun getLastButtonState ()I
|
||||
public final fun getOffsetByPointerId ()Ljava/util/Map;
|
||||
public fun getPrimaryPointerId ()I
|
||||
public final fun getScreenCoordinatesByPointerId ()Ljava/util/Map;
|
||||
public fun getSurfaceId ()I
|
||||
public fun supportsHover (I)Z
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/uimanager/events/RCTEventEmitter : com/facebook/react/bridge/JavaScriptModule {
|
||||
public abstract fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
|
||||
public abstract fun receiveTouches (Ljava/lang/String;Lcom/facebook/react/bridge/WritableArray;Lcom/facebook/react/bridge/WritableArray;)V
|
||||
@@ -5296,15 +5336,6 @@ public abstract interface class com/facebook/react/viewmanagers/UnimplementedNat
|
||||
public abstract fun setName (Landroid/view/View;Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/viewmanagers/VirtualViewManagerDelegate : com/facebook/react/uimanager/BaseViewManagerDelegate {
|
||||
public fun <init> (Lcom/facebook/react/uimanager/BaseViewManager;)V
|
||||
public fun setProperty (Landroid/view/View;Ljava/lang/String;Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/viewmanagers/VirtualViewManagerInterface : com/facebook/react/uimanager/ViewManagerWithGeneratedInterface {
|
||||
public abstract fun setInitialHidden (Landroid/view/View;Z)V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/views/drawer/ReactDrawerLayout : androidx/drawerlayout/widget/DrawerLayout {
|
||||
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
|
||||
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
|
||||
@@ -6132,17 +6163,16 @@ public final class com/facebook/react/views/text/ReactFontManager$Companion {
|
||||
public final fun getInstance ()Lcom/facebook/react/views/text/ReactFontManager;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/views/text/ReactTextShadowNode : com/facebook/react/views/text/ReactBaseTextShadowNode {
|
||||
public class com/facebook/react/views/text/ReactTextShadowNode : com/facebook/react/views/text/ReactBaseTextShadowNode {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Lcom/facebook/react/views/text/ReactTextViewManagerCallback;)V
|
||||
public synthetic fun <init> (Lcom/facebook/react/views/text/ReactTextViewManagerCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun calculateLayoutOnChildren ()Ljava/lang/Iterable;
|
||||
public fun hoistNativeChildren ()Z
|
||||
public fun isVirtualAnchor ()Z
|
||||
public fun markUpdated ()V
|
||||
public fun onBeforeLayout (Lcom/facebook/react/uimanager/NativeViewHierarchyOptimizer;)V
|
||||
public fun onCollectExtraUpdates (Lcom/facebook/react/uimanager/UIViewOperationQueue;)V
|
||||
public final fun setShouldNotifyOnTextLayout (Z)V
|
||||
public fun setShouldNotifyOnTextLayout (Z)V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/views/text/ReactTextUpdate {
|
||||
|
||||
+460
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
|
||||
import com.facebook.react.devsupport.DoubleTapReloadRecognizer;
|
||||
import com.facebook.react.devsupport.ReleaseDevSupportManager;
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager;
|
||||
import com.facebook.react.interfaces.fabric.ReactSurface;
|
||||
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A delegate for handling React Application support. This delegate is unaware whether it is used in
|
||||
* an {@link Activity} or a {@link android.app.Fragment}.
|
||||
*/
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public class ReactDelegate {
|
||||
|
||||
private final Activity mActivity;
|
||||
|
||||
@Nullable private ReactRootView mReactRootView;
|
||||
|
||||
@Nullable private final String mMainComponentName;
|
||||
|
||||
@Nullable private Bundle mLaunchOptions;
|
||||
|
||||
@Nullable private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
|
||||
|
||||
@Nullable private ReactNativeHost mReactNativeHost;
|
||||
|
||||
@Nullable private ReactHost mReactHost;
|
||||
|
||||
@Nullable private ReactSurface mReactSurface;
|
||||
|
||||
private boolean mFabricEnabled = ReactNativeNewArchitectureFeatureFlags.enableFabricRenderer();
|
||||
|
||||
/**
|
||||
* Do not use this constructor as it's not accounting for New Architecture at all. You should use
|
||||
* {@link ReactDelegate#ReactDelegate(Activity, ReactNativeHost, String, Bundle, boolean)} as it's
|
||||
* the constructor used for New Architecture.
|
||||
*
|
||||
* @deprecated Use one of the other constructors instead to account for New Architecture.
|
||||
*/
|
||||
@Deprecated(since = "0.75.0")
|
||||
public ReactDelegate(
|
||||
Activity activity,
|
||||
@Nullable ReactNativeHost reactNativeHost,
|
||||
@Nullable String appKey,
|
||||
@Nullable Bundle launchOptions) {
|
||||
mActivity = activity;
|
||||
mMainComponentName = appKey;
|
||||
mLaunchOptions = launchOptions;
|
||||
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
|
||||
mReactNativeHost = reactNativeHost;
|
||||
}
|
||||
|
||||
public ReactDelegate(
|
||||
Activity activity,
|
||||
@Nullable ReactHost reactHost,
|
||||
@Nullable String appKey,
|
||||
@Nullable Bundle launchOptions) {
|
||||
mActivity = activity;
|
||||
mMainComponentName = appKey;
|
||||
mLaunchOptions = launchOptions;
|
||||
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
|
||||
mReactHost = reactHost;
|
||||
}
|
||||
|
||||
@Deprecated(since = "0.81.0")
|
||||
public ReactDelegate(
|
||||
Activity activity,
|
||||
@Nullable ReactNativeHost reactNativeHost,
|
||||
@Nullable String appKey,
|
||||
@Nullable Bundle launchOptions,
|
||||
boolean fabricEnabled) {
|
||||
mFabricEnabled = fabricEnabled;
|
||||
mActivity = activity;
|
||||
mMainComponentName = appKey;
|
||||
mLaunchOptions = launchOptions;
|
||||
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
|
||||
mReactNativeHost = reactNativeHost;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DevSupportManager getDevSupportManager() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null
|
||||
&& mReactHost.getDevSupportManager() != null) {
|
||||
return mReactHost.getDevSupportManager();
|
||||
} else if (getReactNativeHost() != null
|
||||
&& getReactNativeHost().hasInstance()
|
||||
&& getReactNativeHost().getReactInstanceManager() != null) {
|
||||
return getReactNativeHost().getReactInstanceManager().getDevSupportManager();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void onHostResume() {
|
||||
if (!(mActivity instanceof DefaultHardwareBackBtnHandler)) {
|
||||
throw new ClassCastException(
|
||||
"Host Activity does not implement DefaultHardwareBackBtnHandler");
|
||||
}
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost
|
||||
.getReactInstanceManager()
|
||||
.onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onUserLeaveHint() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onHostLeaveHint(mActivity);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onUserLeaveHint(mActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onHostPause() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onHostPause(mActivity);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onHostPause(mActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onHostDestroy() {
|
||||
unloadApp();
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onHostDestroy(mActivity);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onHostDestroy(mActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onBackPressed();
|
||||
return true;
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onNewIntent(Intent intent) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onNewIntent(intent);
|
||||
return true;
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onNewIntent(intent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onActivityResult(
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
@Nullable Intent data,
|
||||
boolean shouldForwardToReactInstance) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onActivityResult(mActivity, requestCode, resultCode, data);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null
|
||||
&& reactNativeHost.hasInstance()
|
||||
&& shouldForwardToReactInstance) {
|
||||
reactNativeHost
|
||||
.getReactInstanceManager()
|
||||
.onActivityResult(mActivity, requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onWindowFocusChange(hasFocus);
|
||||
} else {
|
||||
if (getReactNativeHost() != null && getReactNativeHost().hasInstance()) {
|
||||
getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onConfigurationChanged(Assertions.assertNotNull(mActivity));
|
||||
} else {
|
||||
if (getReactNativeHost() != null && getReactNativeHost().hasInstance()) {
|
||||
getReactInstanceManager()
|
||||
.onConfigurationChanged(Assertions.assertNotNull(mActivity), newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
|
||||
&& ((ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null
|
||||
&& mReactHost.getDevSupportManager() != null)
|
||||
|| (getReactNativeHost() != null
|
||||
&& getReactNativeHost().hasInstance()
|
||||
&& getReactNativeHost().getUseDeveloperSupport()))) {
|
||||
event.startTracking();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onKeyLongPress(int keyCode) {
|
||||
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
DevSupportManager devSupportManager = mReactHost.getDevSupportManager();
|
||||
// onKeyLongPress is a Dev API and not supported in RELEASE mode.
|
||||
if (devSupportManager != null && !(devSupportManager instanceof ReleaseDevSupportManager)) {
|
||||
devSupportManager.showDevOptionsDialog();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null
|
||||
&& reactNativeHost.hasInstance()
|
||||
&& reactNativeHost.getUseDeveloperSupport()) {
|
||||
reactNativeHost.getReactInstanceManager().showDevOptionsDialog();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
DevSupportManager devSupportManager = getDevSupportManager();
|
||||
if (devSupportManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload in RELEASE mode
|
||||
if (devSupportManager instanceof ReleaseDevSupportManager) {
|
||||
// Do not reload the bundle from JS as there is no bundler running in release mode.
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactHost != null) {
|
||||
mReactHost.reload("ReactDelegate.reload()");
|
||||
}
|
||||
} else {
|
||||
UiThreadUtil.runOnUiThread(
|
||||
() -> {
|
||||
if (mReactNativeHost != null
|
||||
&& mReactNativeHost.hasInstance()
|
||||
&& mReactNativeHost.getReactInstanceManager() != null) {
|
||||
mReactNativeHost.getReactInstanceManager().recreateReactContextInBackground();
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload in DEBUG mode
|
||||
devSupportManager.handleReloadJS();
|
||||
}
|
||||
|
||||
/** Start the React surface with the app key supplied in the {@link ReactDelegate} constructor. */
|
||||
public void loadApp() {
|
||||
if (mMainComponentName == null) {
|
||||
throw new IllegalStateException("Cannot loadApp without a main component name.");
|
||||
} else {
|
||||
loadApp(mMainComponentName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the React surface for the given app key.
|
||||
*
|
||||
* @param appKey The ID of the app to load into the surface.
|
||||
*/
|
||||
public void loadApp(String appKey) {
|
||||
// With Bridgeless enabled, create and start the surface
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactSurface == null && mReactHost != null) {
|
||||
mReactSurface = mReactHost.createSurface(mActivity, appKey, mLaunchOptions);
|
||||
}
|
||||
Objects.requireNonNull(mReactSurface).start();
|
||||
} else {
|
||||
if (mReactRootView != null) {
|
||||
throw new IllegalStateException("Cannot loadApp while app is already running.");
|
||||
}
|
||||
mReactRootView = createRootView();
|
||||
if (getReactNativeHost() != null) {
|
||||
mReactRootView.startReactApplication(
|
||||
getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop the React surface started with {@link ReactDelegate#loadApp()}. */
|
||||
public void unloadApp() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactSurface != null) {
|
||||
mReactSurface.stop();
|
||||
mReactSurface = null;
|
||||
}
|
||||
} else {
|
||||
if (mReactRootView != null) {
|
||||
mReactRootView.unmountReactApplication();
|
||||
mReactRootView = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setReactSurface(ReactSurface reactSurface) {
|
||||
mReactSurface = reactSurface;
|
||||
}
|
||||
|
||||
public void setReactRootView(ReactRootView reactRootView) {
|
||||
mReactRootView = reactRootView;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ReactRootView getReactRootView() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactSurface != null) {
|
||||
return (ReactRootView) mReactSurface.getView();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return mReactRootView;
|
||||
}
|
||||
}
|
||||
|
||||
// Not used in bridgeless
|
||||
protected ReactRootView createRootView() {
|
||||
ReactRootView reactRootView = new ReactRootView(mActivity);
|
||||
reactRootView.setIsFabric(isFabricEnabled());
|
||||
return reactRootView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles delegating the {@link Activity#onKeyUp(int, KeyEvent)} method to determine whether the
|
||||
* application should show the developer menu or should reload the React Application.
|
||||
*
|
||||
* @return true if we consume the event and either shoed the develop menu or reloaded the
|
||||
* application.
|
||||
*/
|
||||
public boolean shouldShowDevMenuOrReload(int keyCode, KeyEvent event) {
|
||||
DevSupportManager devSupportManager = getDevSupportManager();
|
||||
// shouldShowDevMenuOrReload is a Dev API and not supported in RELEASE mode.
|
||||
if (devSupportManager == null || devSupportManager instanceof ReleaseDevSupportManager) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
devSupportManager.showDevOptionsDialog();
|
||||
return true;
|
||||
}
|
||||
boolean didDoubleTapR =
|
||||
Objects.requireNonNull(mDoubleTapReloadRecognizer)
|
||||
.didDoubleTapR(keyCode, mActivity.getCurrentFocus());
|
||||
if (didDoubleTapR) {
|
||||
devSupportManager.handleReloadJS();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Get the {@link ReactNativeHost} used by this app. */
|
||||
@DeprecatedInNewArchitecture(message = "Use getReactHost()")
|
||||
private @Nullable ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@DeprecatedInNewArchitecture(message = "Use getReactHost()")
|
||||
public ReactInstanceManager getReactInstanceManager() {
|
||||
if (getReactNativeHost() == null) {
|
||||
throw new IllegalStateException("Cannot get ReactInstanceManager without a ReactNativeHost.");
|
||||
}
|
||||
return getReactNativeHost().getReactInstanceManager();
|
||||
}
|
||||
|
||||
public @Nullable ReactHost getReactHost() {
|
||||
return mReactHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current {@link ReactContext} from ReactHost or ReactInstanceManager
|
||||
*
|
||||
* <p>Do not store a reference to this, if the React instance is reloaded or destroyed, this
|
||||
* context will no longer be valid.
|
||||
*/
|
||||
public @Nullable ReactContext getCurrentReactContext() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactHost != null) {
|
||||
return mReactHost.getCurrentReactContext();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return getReactInstanceManager().getCurrentReactContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you wish to selectively toggle Fabric for a specific surface. This will
|
||||
* also control if Concurrent Root (React 18) should be enabled or not.
|
||||
*
|
||||
* @return true if Fabric is enabled for this Activity, false otherwise.
|
||||
*/
|
||||
protected boolean isFabricEnabled() {
|
||||
return mFabricEnabled;
|
||||
}
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
|
||||
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture
|
||||
import com.facebook.react.devsupport.DoubleTapReloadRecognizer
|
||||
import com.facebook.react.devsupport.ReleaseDevSupportManager
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager
|
||||
import com.facebook.react.interfaces.fabric.ReactSurface
|
||||
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
|
||||
|
||||
/**
|
||||
* A delegate for handling React Application support. This delegate is unaware whether it is used in
|
||||
* an [Activity] or a [android.app.Fragment].
|
||||
*/
|
||||
public open class ReactDelegate {
|
||||
private val activity: Activity
|
||||
private var internalReactRootView: ReactRootView? = null
|
||||
private val mainComponentName: String?
|
||||
private var launchOptions: Bundle?
|
||||
private var doubleTapReloadRecognizer: DoubleTapReloadRecognizer?
|
||||
private var reactNativeHost: ReactNativeHost? = null
|
||||
public var reactHost: ReactHost? = null
|
||||
private set
|
||||
|
||||
private var reactSurface: ReactSurface? = null
|
||||
|
||||
/**
|
||||
* Override this method if you wish to selectively toggle Fabric for a specific surface. This will
|
||||
* also control if Concurrent Root (React 18) should be enabled or not.
|
||||
*
|
||||
* @return true if Fabric is enabled for this Activity, false otherwise.
|
||||
*/
|
||||
protected var isFabricEnabled: Boolean =
|
||||
ReactNativeNewArchitectureFeatureFlags.enableFabricRenderer()
|
||||
private set
|
||||
|
||||
/**
|
||||
* Do not use this constructor as it's not accounting for New Architecture at all. You should use
|
||||
* [ReactDelegate(Activity, ReactNativeHost, String, Bundle, boolean)] as it's the constructor
|
||||
* used for New Architecture.
|
||||
*/
|
||||
@Deprecated(
|
||||
"Use one of the other constructors instead to account for New Architecture. Deprecated since 0.75.0")
|
||||
public constructor(
|
||||
activity: Activity,
|
||||
reactNativeHost: ReactNativeHost?,
|
||||
appKey: String?,
|
||||
launchOptions: Bundle?
|
||||
) {
|
||||
this.activity = activity
|
||||
mainComponentName = appKey
|
||||
this.launchOptions = launchOptions
|
||||
doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
|
||||
this.reactNativeHost = reactNativeHost
|
||||
}
|
||||
|
||||
public constructor(
|
||||
activity: Activity,
|
||||
reactHost: ReactHost?,
|
||||
appKey: String?,
|
||||
launchOptions: Bundle?
|
||||
) {
|
||||
this.activity = activity
|
||||
mainComponentName = appKey
|
||||
this.launchOptions = launchOptions
|
||||
doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
|
||||
this.reactHost = reactHost
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated since 0.81.0, use one of the other constructors instead.")
|
||||
public constructor(
|
||||
activity: Activity,
|
||||
reactNativeHost: ReactNativeHost?,
|
||||
appKey: String?,
|
||||
launchOptions: Bundle?,
|
||||
fabricEnabled: Boolean
|
||||
) {
|
||||
isFabricEnabled = fabricEnabled
|
||||
this.activity = activity
|
||||
mainComponentName = appKey
|
||||
this.launchOptions = launchOptions
|
||||
doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
|
||||
this.reactNativeHost = reactNativeHost
|
||||
}
|
||||
|
||||
private val devSupportManager: DevSupportManager?
|
||||
get() =
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost?.devSupportManager != null) {
|
||||
reactHost?.devSupportManager
|
||||
} else if (reactNativeHost?.hasInstance() == true &&
|
||||
reactNativeHost?.reactInstanceManager != null) {
|
||||
reactNativeHost?.reactInstanceManager?.devSupportManager
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
public fun onHostResume() {
|
||||
if (activity !is DefaultHardwareBackBtnHandler) {
|
||||
throw ClassCastException("Host Activity does not implement DefaultHardwareBackBtnHandler")
|
||||
}
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onHostResume(activity, activity as DefaultHardwareBackBtnHandler)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost
|
||||
?.reactInstanceManager
|
||||
?.onHostResume(activity, activity as DefaultHardwareBackBtnHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onUserLeaveHint() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onHostLeaveHint(activity)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onUserLeaveHint(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onHostPause() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onHostPause(activity)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onHostPause(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onHostDestroy() {
|
||||
unloadApp()
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onHostDestroy(activity)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onHostDestroy(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onBackPressed(): Boolean {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onBackPressed()
|
||||
return true
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onBackPressed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public fun onNewIntent(intent: Intent): Boolean {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onNewIntent(intent)
|
||||
return true
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onNewIntent(intent)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public fun onActivityResult(
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
data: Intent?,
|
||||
shouldForwardToReactInstance: Boolean
|
||||
) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null &&
|
||||
shouldForwardToReactInstance) {
|
||||
reactHost?.onActivityResult(activity, requestCode, resultCode, data)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true && shouldForwardToReactInstance) {
|
||||
reactNativeHost
|
||||
?.reactInstanceManager
|
||||
?.onActivityResult(activity, requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onWindowFocusChange(hasFocus)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onWindowFocusChange(hasFocus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onConfigurationChanged(checkNotNull(activity))
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
getReactInstanceManager().onConfigurationChanged(checkNotNull(activity), newConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD &&
|
||||
((ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost?.devSupportManager != null) ||
|
||||
(reactNativeHost?.hasInstance() == true &&
|
||||
reactNativeHost?.useDeveloperSupport == true))) {
|
||||
event.startTracking()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public fun onKeyLongPress(keyCode: Int): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
val devSupportManager = reactHost?.devSupportManager
|
||||
// onKeyLongPress is a Dev API and not supported in RELEASE mode.
|
||||
if (devSupportManager != null && devSupportManager !is ReleaseDevSupportManager) {
|
||||
devSupportManager.showDevOptionsDialog()
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true &&
|
||||
reactNativeHost?.useDeveloperSupport == true) {
|
||||
reactNativeHost?.reactInstanceManager?.showDevOptionsDialog()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public fun reload() {
|
||||
val devSupportManager = devSupportManager ?: return
|
||||
|
||||
// Reload in RELEASE mode
|
||||
if (devSupportManager is ReleaseDevSupportManager) {
|
||||
// Do not reload the bundle from JS as there is no bundler running in release mode.
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
reactHost?.reload("ReactDelegate.reload()")
|
||||
} else {
|
||||
runOnUiThread {
|
||||
if (reactNativeHost?.hasInstance() == true &&
|
||||
reactNativeHost?.reactInstanceManager != null) {
|
||||
reactNativeHost?.reactInstanceManager?.recreateReactContextInBackground()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Reload in DEBUG mode
|
||||
devSupportManager.handleReloadJS()
|
||||
}
|
||||
|
||||
/** Start the React surface with the app key supplied in the [ReactDelegate] constructor. */
|
||||
public fun loadApp() {
|
||||
val name = requireNotNull(mainComponentName) { "Cannot loadApp without a main component name." }
|
||||
loadApp(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the React surface for the given app key.
|
||||
*
|
||||
* @param appKey The ID of the app to load into the surface.
|
||||
*/
|
||||
public fun loadApp(appKey: String) {
|
||||
// With Bridgeless enabled, create and start the surface
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
val reactHost = reactHost
|
||||
if (reactSurface == null && reactHost != null) {
|
||||
reactSurface = reactHost.createSurface(activity, appKey, launchOptions)
|
||||
}
|
||||
reactSurface?.start()
|
||||
} else {
|
||||
check(internalReactRootView == null) { "Cannot loadApp while app is already running." }
|
||||
internalReactRootView = createRootView()
|
||||
if (reactNativeHost != null) {
|
||||
internalReactRootView?.startReactApplication(
|
||||
reactNativeHost?.reactInstanceManager, appKey, launchOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop the React surface started with [ReactDelegate.loadApp]. */
|
||||
public fun unloadApp() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
reactSurface?.stop()
|
||||
reactSurface = null
|
||||
} else {
|
||||
if (internalReactRootView != null) {
|
||||
internalReactRootView?.unmountReactApplication()
|
||||
internalReactRootView = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun setReactSurface(reactSurface: ReactSurface?) {
|
||||
this.reactSurface = reactSurface
|
||||
}
|
||||
|
||||
public var reactRootView: ReactRootView?
|
||||
get() {
|
||||
return if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (reactSurface != null) {
|
||||
reactSurface?.view as ReactRootView?
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
internalReactRootView
|
||||
}
|
||||
}
|
||||
set(reactRootView) {
|
||||
internalReactRootView = reactRootView
|
||||
}
|
||||
|
||||
// Not used in bridgeless
|
||||
protected open fun createRootView(): ReactRootView? {
|
||||
val reactRootView = ReactRootView(activity)
|
||||
reactRootView.setIsFabric(isFabricEnabled)
|
||||
return reactRootView
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles delegating the [Activity.onKeyUp] method to determine whether the application should
|
||||
* show the developer menu or should reload the React Application.
|
||||
*
|
||||
* @return true if we consume the event and either show the develop menu or reloaded the
|
||||
* application.
|
||||
*/
|
||||
public fun shouldShowDevMenuOrReload(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
val devSupportManager = devSupportManager
|
||||
// shouldShowDevMenuOrReload is a Dev API and not supported in RELEASE mode.
|
||||
if (devSupportManager == null || devSupportManager is ReleaseDevSupportManager) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
devSupportManager.showDevOptionsDialog()
|
||||
return true
|
||||
}
|
||||
val didDoubleTapR = doubleTapReloadRecognizer?.didDoubleTapR(keyCode, activity.currentFocus)
|
||||
if (didDoubleTapR == true) {
|
||||
devSupportManager.handleReloadJS()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@DeprecatedInNewArchitecture(message = "Use reactHost")
|
||||
public fun getReactInstanceManager(): ReactInstanceManager {
|
||||
val nonNullReactNativeHost =
|
||||
checkNotNull(reactNativeHost) {
|
||||
"Cannot get ReactInstanceManager without a ReactNativeHost."
|
||||
}
|
||||
return nonNullReactNativeHost.reactInstanceManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current [ReactContext] from [ReactHost] or [ReactInstanceManager]
|
||||
*
|
||||
* Do not store a reference to this, if the React instance is reloaded or destroyed, this context
|
||||
* will no longer be valid.
|
||||
*/
|
||||
public val currentReactContext: ReactContext?
|
||||
get() {
|
||||
return if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (reactHost != null) {
|
||||
reactHost?.currentReactContext
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
getReactInstanceManager().currentReactContext
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -74,6 +74,7 @@ import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.SurfaceDelegateFactory;
|
||||
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;
|
||||
@@ -1118,6 +1119,7 @@ public class ReactInstanceManager {
|
||||
/**
|
||||
* @return current ReactApplicationContext
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public @Nullable ReactContext getCurrentReactContext() {
|
||||
synchronized (mReactContextLock) {
|
||||
return mCurrentReactContext;
|
||||
|
||||
+3
-2
@@ -9,6 +9,7 @@ package com.facebook.react.bridge
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfiguration
|
||||
import com.facebook.react.common.annotations.VisibleForTesting
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
import com.facebook.react.internal.turbomodule.core.interfaces.TurboModuleRegistry
|
||||
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder
|
||||
@@ -52,7 +53,7 @@ public interface CatalystInstance : MemoryPressureListener, JSInstance, JSBundle
|
||||
public val isDestroyed: Boolean
|
||||
|
||||
/** Initialize all the native modules */
|
||||
public fun initialize()
|
||||
@VisibleForTesting public fun initialize()
|
||||
|
||||
public val reactQueueConfiguration: ReactQueueConfiguration
|
||||
|
||||
@@ -89,7 +90,7 @@ public interface CatalystInstance : MemoryPressureListener, JSInstance, JSBundle
|
||||
/** This method registers the file path of an additional JS segment by its ID. */
|
||||
public fun registerSegment(segmentId: Int, path: String)
|
||||
|
||||
public fun setGlobalVariable(propName: String, jsonValue: String)
|
||||
@VisibleForTesting public fun setGlobalVariable(propName: String, jsonValue: String)
|
||||
|
||||
/**
|
||||
* Do not use this anymore. Use [runtimeExecutor] instead. Get the C pointer (as a long) to the
|
||||
|
||||
+2
-2
@@ -36,9 +36,9 @@ private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
fun create(
|
||||
public fun create(
|
||||
spec: ReactQueueConfigurationSpec,
|
||||
exceptionHandler: QueueThreadExceptionHandler
|
||||
): ReactQueueConfigurationImpl {
|
||||
|
||||
+2
-2
@@ -1317,7 +1317,7 @@ public class FabricUIManager
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
@Override
|
||||
public void willMountItems(@Nullable List<? extends MountItem> mountItems) {
|
||||
public void willMountItems(@Nullable List<MountItem> mountItems) {
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.willMountItems(FabricUIManager.this);
|
||||
}
|
||||
@@ -1326,7 +1326,7 @@ public class FabricUIManager
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
@Override
|
||||
public void didMountItems(@Nullable List<? extends MountItem> mountItems) {
|
||||
public void didMountItems(@Nullable List<MountItem> mountItems) {
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.didMountItems(FabricUIManager.this);
|
||||
}
|
||||
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.mounting;
|
||||
|
||||
import static com.facebook.infer.annotation.ThreadConfined.UI;
|
||||
import static com.facebook.react.fabric.FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.view.View;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.infer.annotation.ThreadConfined;
|
||||
import com.facebook.react.bridge.ReactIgnorableMountingException;
|
||||
import com.facebook.react.bridge.ReactNoCrashSoftException;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem;
|
||||
import com.facebook.react.fabric.mounting.mountitems.MountItem;
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public class MountItemDispatcher {
|
||||
|
||||
private static final String TAG = "MountItemDispatcher";
|
||||
|
||||
private static final long FRAME_TIME_NS = 1_000_000_000 / 60;
|
||||
|
||||
private final MountingManager mMountingManager;
|
||||
private final ItemDispatchListener mItemDispatchListener;
|
||||
|
||||
private final ConcurrentLinkedQueue<DispatchCommandMountItem> mViewCommandMountItems =
|
||||
new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final ConcurrentLinkedQueue<MountItem> mMountItems = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final ConcurrentLinkedQueue<MountItem> mPreMountItems = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private boolean mInDispatch = false;
|
||||
private long mBatchedExecutionTime = 0L;
|
||||
private long mRunStartTime = 0L;
|
||||
|
||||
private long mLastFrameTimeNanos = 0L;
|
||||
|
||||
public MountItemDispatcher(MountingManager mountingManager, ItemDispatchListener listener) {
|
||||
mMountingManager = mountingManager;
|
||||
mItemDispatchListener = listener;
|
||||
}
|
||||
|
||||
public void addViewCommandMountItem(DispatchCommandMountItem mountItem) {
|
||||
mViewCommandMountItems.add(mountItem);
|
||||
}
|
||||
|
||||
public void addMountItem(MountItem mountItem) {
|
||||
mMountItems.add(mountItem);
|
||||
}
|
||||
|
||||
public void addPreAllocateMountItem(MountItem mountItem) {
|
||||
// We do this check only for PreAllocateViewMountItem - and not DispatchMountItem or regular
|
||||
// MountItem - because PreAllocateViewMountItems are not batched, and is relatively more
|
||||
// expensive
|
||||
// both to queue, to drain, and to execute.
|
||||
if (!mMountingManager.surfaceIsStopped(mountItem.getSurfaceId())) {
|
||||
mPreMountItems.add(mountItem);
|
||||
} else if (IS_DEVELOPMENT_ENVIRONMENT) {
|
||||
FLog.e(
|
||||
TAG,
|
||||
"Not queueing PreAllocateMountItem: surfaceId stopped: [%d] - %s",
|
||||
mountItem.getSurfaceId(),
|
||||
mountItem.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to dispatch MountItems. In case of the exception, we will retry 10 times before giving up.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
public void tryDispatchMountItems() {
|
||||
// If we're already dispatching, don't reenter.
|
||||
// Reentrance can potentially happen a lot on Android in Fabric because
|
||||
// `updateState` from the
|
||||
// mounting layer causes mount items to be dispatched synchronously. We want to 1) make sure
|
||||
// we don't reenter in those cases, but 2) still execute those queued instructions
|
||||
// synchronously.
|
||||
// This is a pretty blunt tool, but we might not have better options since we really don't want
|
||||
// to execute anything out-of-order.
|
||||
if (mInDispatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
mInDispatch = true;
|
||||
|
||||
try {
|
||||
dispatchMountItems();
|
||||
} finally {
|
||||
// Clean up after running dispatchMountItems - even if an exception was thrown
|
||||
mInDispatch = false;
|
||||
}
|
||||
|
||||
// We call didDispatchMountItems regardless of whether we actually dispatched anything, since
|
||||
// NativeAnimatedModule relies on this for executing any animations that may have been
|
||||
// scheduled
|
||||
mItemDispatchListener.didDispatchMountItems();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
public void dispatchMountItems(Queue<MountItem> mountItems) {
|
||||
while (!mountItems.isEmpty()) {
|
||||
MountItem item = mountItems.poll();
|
||||
Assertions.assertNotNull(item);
|
||||
try {
|
||||
item.execute(mMountingManager);
|
||||
} catch (RetryableMountingLayerException e) {
|
||||
if (item instanceof DispatchCommandMountItem) {
|
||||
// Only DispatchCommandMountItem supports retries
|
||||
DispatchCommandMountItem mountItem = (DispatchCommandMountItem) item;
|
||||
// Retrying exactly once
|
||||
if (mountItem.getRetries() == 0) {
|
||||
mountItem.incrementRetries();
|
||||
// In case we haven't retried executing this item yet, execute in the next batch of
|
||||
// items
|
||||
addViewCommandMountItem(mountItem);
|
||||
}
|
||||
} else {
|
||||
printMountItem(
|
||||
item, "dispatchExternalMountItems: mounting failed with " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes view commands, pre mount items and mount items in the respective order:
|
||||
* 1. View commands.
|
||||
* 2. Pre mount items.
|
||||
* 3. Regular mount items.
|
||||
*
|
||||
* Does nothing if `viewCommandMountItemsToDispatch` and `mountItemsToDispatch` are empty.
|
||||
* Nothing should call this directly except for `tryDispatchMountItems`.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private void dispatchMountItems() {
|
||||
mBatchedExecutionTime = 0;
|
||||
|
||||
mRunStartTime = SystemClock.uptimeMillis();
|
||||
|
||||
List<DispatchCommandMountItem> viewCommandMountItemsToDispatch =
|
||||
getAndResetViewCommandMountItems();
|
||||
List<MountItem> mountItemsToDispatch = getAndResetMountItems();
|
||||
|
||||
if (mountItemsToDispatch == null && viewCommandMountItemsToDispatch == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mItemDispatchListener.willMountItems(mountItemsToDispatch);
|
||||
|
||||
// As an optimization, execute all ViewCommands first
|
||||
// This should be:
|
||||
// 1) Performant: ViewCommands are often a replacement for SetNativeProps, which we've always
|
||||
// wanted to be as "synchronous" as possible.
|
||||
// 2) Safer: ViewCommands are inherently disconnected from the tree commit/diff/mount process.
|
||||
// JS imperatively queues these commands.
|
||||
// If JS has queued a command, it's reasonable to assume that the more time passes, the more
|
||||
// likely it is that the view disappears.
|
||||
// Thus, by executing ViewCommands early, we should actually avoid a category of
|
||||
// errors/glitches.
|
||||
if (viewCommandMountItemsToDispatch != null) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews viewCommandMountItems");
|
||||
for (DispatchCommandMountItem command : viewCommandMountItemsToDispatch) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem");
|
||||
}
|
||||
try {
|
||||
executeOrEnqueue(command);
|
||||
} catch (RetryableMountingLayerException e) {
|
||||
// If the exception is marked as Retryable, we retry the viewcommand exactly once, after
|
||||
// the current batch of mount items has finished executing.
|
||||
if (command.getRetries() == 0) {
|
||||
command.incrementRetries();
|
||||
addViewCommandMountItem(command);
|
||||
} else {
|
||||
// It's very common for commands to be executed on views that no longer exist - for
|
||||
// example, a blur event on TextInput being fired because of a navigation event away
|
||||
// from the current screen. If the exception is marked as Retryable, we log a soft
|
||||
// exception but never crash in debug.
|
||||
// It's not clear that logging this is even useful, because these events are very
|
||||
// common, mundane, and there's not much we can do about them currently.
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
new ReactNoCrashSoftException(
|
||||
"Caught exception executing ViewCommand: " + command.toString(), e));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// Non-Retryable exceptions are logged as soft exceptions in prod, but crash in Debug.
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
new RuntimeException(
|
||||
"Caught exception executing ViewCommand: " + command.toString(), e));
|
||||
}
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
|
||||
// If there are MountItems to dispatch, we make sure all the "pre mount items" are executed
|
||||
// first
|
||||
List<MountItem> preMountItemsToDispatch = getAndResetPreMountItems();
|
||||
if (preMountItemsToDispatch != null) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews preMountItems");
|
||||
for (MountItem preMountItem : preMountItemsToDispatch) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(preMountItem, "dispatchMountItems: Executing preMountItem");
|
||||
}
|
||||
executeOrEnqueue(preMountItem);
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
|
||||
if (mountItemsToDispatch != null) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews mountItems to execute");
|
||||
|
||||
long batchedExecutionStartTime = SystemClock.uptimeMillis();
|
||||
|
||||
for (MountItem mountItem : mountItemsToDispatch) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(mountItem, "dispatchMountItems: Executing mountItem");
|
||||
}
|
||||
|
||||
try {
|
||||
executeOrEnqueue(mountItem);
|
||||
} catch (Throwable e) {
|
||||
// If there's an exception, we want to log diagnostics in prod and rethrow.
|
||||
FLog.e(TAG, "dispatchMountItems: caught exception, displaying mount state", e);
|
||||
for (MountItem m : mountItemsToDispatch) {
|
||||
if (m == mountItem) {
|
||||
// We want to mark the mount item that caused exception
|
||||
FLog.e(TAG, "dispatchMountItems: mountItem: next mountItem triggered exception!");
|
||||
}
|
||||
printMountItem(m, "dispatchMountItems: mountItem");
|
||||
}
|
||||
if (mountItem.getSurfaceId() != View.NO_ID) {
|
||||
SurfaceMountingManager surfaceManager =
|
||||
mMountingManager.getSurfaceManager(mountItem.getSurfaceId());
|
||||
if (surfaceManager != null) {
|
||||
surfaceManager.printSurfaceState();
|
||||
}
|
||||
}
|
||||
|
||||
if (ReactIgnorableMountingException.isIgnorable(e)) {
|
||||
ReactSoftExceptionLogger.logSoftException(TAG, e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
mBatchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime;
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
|
||||
mItemDispatchListener.didMountItems(mountItemsToDispatch);
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes pre mount items. Pre mount items are operations that can be executed before the mount items come. For example view preallocation.
|
||||
* This is a performance optimisation to do as much work ahead of time as possible.
|
||||
*
|
||||
* `tryDispatchMountItems` will also execute pre mount items, but only if there are mount items to be executed.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
public void dispatchPreMountItems(long frameTimeNanos) {
|
||||
mLastFrameTimeNanos = frameTimeNanos;
|
||||
|
||||
if (mPreMountItems.isEmpty()) {
|
||||
// Avoid starting systrace if there are no pre mount items.
|
||||
return;
|
||||
}
|
||||
|
||||
long deadline = mLastFrameTimeNanos + FRAME_TIME_NS / 2;
|
||||
dispatchPreMountItemsImpl(deadline);
|
||||
}
|
||||
|
||||
private void dispatchPreMountItemsImpl(long deadline) {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "MountItemDispatcher::premountViews");
|
||||
|
||||
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems from
|
||||
// reentering during dispatchPreMountItems
|
||||
mInDispatch = true;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (System.nanoTime() > deadline) {
|
||||
break;
|
||||
}
|
||||
|
||||
MountItem preMountItemToDispatch = mPreMountItems.poll();
|
||||
// If list is empty, `poll` will return null, or var will never be set
|
||||
if (preMountItemToDispatch == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(preMountItemToDispatch, "dispatchPreMountItems");
|
||||
}
|
||||
executeOrEnqueue(preMountItemToDispatch);
|
||||
}
|
||||
} finally {
|
||||
mInDispatch = false;
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
|
||||
private void executeOrEnqueue(MountItem item) {
|
||||
if (mMountingManager.isWaitingForViewAttach(item.getSurfaceId())) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
FLog.e(
|
||||
TAG,
|
||||
"executeOrEnqueue: Item execution delayed, surface %s is not ready yet",
|
||||
item.getSurfaceId());
|
||||
}
|
||||
SurfaceMountingManager surfaceMountingManager =
|
||||
mMountingManager.getSurfaceManagerEnforced(
|
||||
item.getSurfaceId(), "MountItemDispatcher::executeOrEnqueue");
|
||||
surfaceMountingManager.scheduleMountItemOnViewAttach(item);
|
||||
} else {
|
||||
item.execute(mMountingManager);
|
||||
}
|
||||
}
|
||||
|
||||
private static <E> @Nullable List<E> drainConcurrentItemQueue(ConcurrentLinkedQueue<E> queue) {
|
||||
if (queue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<E> result = new ArrayList<>();
|
||||
do {
|
||||
E item = queue.poll();
|
||||
if (item != null) {
|
||||
result.add(item);
|
||||
}
|
||||
} while (!queue.isEmpty());
|
||||
if (result.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private @Nullable List<DispatchCommandMountItem> getAndResetViewCommandMountItems() {
|
||||
return drainConcurrentItemQueue(mViewCommandMountItems);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private @Nullable List<MountItem> getAndResetMountItems() {
|
||||
return drainConcurrentItemQueue(mMountItems);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private @Nullable List<MountItem> getAndResetPreMountItems() {
|
||||
return drainConcurrentItemQueue(mPreMountItems);
|
||||
}
|
||||
|
||||
public long getBatchedExecutionTime() {
|
||||
return mBatchedExecutionTime;
|
||||
}
|
||||
|
||||
public long getRunStartTime() {
|
||||
return mRunStartTime;
|
||||
}
|
||||
|
||||
private static void printMountItem(MountItem mountItem, String prefix) {
|
||||
// If a MountItem description is split across multiple lines, it's because it's a
|
||||
// compound MountItem. Log each line separately.
|
||||
String[] mountItemLines = mountItem.toString().split("\n");
|
||||
for (String m : mountItemLines) {
|
||||
FLog.e(TAG, prefix + ": " + m);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ItemDispatchListener {
|
||||
void willMountItems(@Nullable List<MountItem> mountItems);
|
||||
|
||||
void didMountItems(@Nullable List<MountItem> mountItems);
|
||||
|
||||
void didDispatchMountItems();
|
||||
}
|
||||
}
|
||||
-365
@@ -1,365 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.mounting
|
||||
|
||||
import android.os.SystemClock
|
||||
import android.view.View
|
||||
import androidx.annotation.UiThread
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.infer.annotation.ThreadConfined
|
||||
import com.facebook.infer.annotation.ThreadConfined.UI
|
||||
import com.facebook.react.bridge.ReactIgnorableMountingException
|
||||
import com.facebook.react.bridge.ReactNoCrashSoftException
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException
|
||||
import com.facebook.react.fabric.FabricUIManager
|
||||
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem
|
||||
import com.facebook.react.fabric.mounting.mountitems.MountItem
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
||||
import com.facebook.systrace.Systrace
|
||||
import java.util.Queue
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
internal class MountItemDispatcher(
|
||||
private val mountingManager: MountingManager,
|
||||
private val itemDispatchListener: ItemDispatchListener
|
||||
) {
|
||||
private val viewCommandMountItems: Queue<DispatchCommandMountItem> = ConcurrentLinkedQueue()
|
||||
private val mountItems: Queue<MountItem> = ConcurrentLinkedQueue()
|
||||
private val preMountItems: Queue<MountItem> = ConcurrentLinkedQueue()
|
||||
|
||||
private var inDispatch: Boolean = false
|
||||
var batchedExecutionTime: Long = 0L
|
||||
private set
|
||||
|
||||
var runStartTime: Long = 0L
|
||||
private set
|
||||
|
||||
private var lastFrameTimeNanos: Long = 0L
|
||||
|
||||
fun addViewCommandMountItem(mountItem: DispatchCommandMountItem) {
|
||||
viewCommandMountItems.add(mountItem)
|
||||
}
|
||||
|
||||
fun addMountItem(mountItem: MountItem) {
|
||||
mountItems.add(mountItem)
|
||||
}
|
||||
|
||||
fun addPreAllocateMountItem(mountItem: MountItem) {
|
||||
// We do this check only for PreAllocateViewMountItem - and not DispatchMountItem or regular
|
||||
// MountItem - because PreAllocateViewMountItems are not batched, and is relatively more
|
||||
// expensive
|
||||
// both to queue, to drain, and to execute.
|
||||
if (!mountingManager.surfaceIsStopped(mountItem.getSurfaceId())) {
|
||||
preMountItems.add(mountItem)
|
||||
} else if (FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT) {
|
||||
FLog.e(
|
||||
TAG,
|
||||
"Not queueing PreAllocateMountItem: surfaceId stopped: [%d] - %s",
|
||||
mountItem.getSurfaceId(),
|
||||
mountItem.toString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to dispatch MountItems. In case of the exception, we will retry 10 times before giving up.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
fun tryDispatchMountItems() {
|
||||
// If we're already dispatching, don't reenter.
|
||||
// Reentrance can potentially happen a lot on Android in Fabric because `updateState` from the
|
||||
// mounting layer causes mount items to be dispatched synchronously. We want to 1) make sure we
|
||||
// don't reenter in those cases, but 2) still execute those queued instructions synchronously.
|
||||
// This is a pretty blunt tool, but we might not have better options since we really don't want
|
||||
// to execute anything out-of-order.
|
||||
if (inDispatch) {
|
||||
return
|
||||
}
|
||||
|
||||
inDispatch = true
|
||||
|
||||
try {
|
||||
dispatchMountItems()
|
||||
} finally {
|
||||
// Clean up after running dispatchMountItems - even if an exception was thrown
|
||||
inDispatch = false
|
||||
}
|
||||
|
||||
// We call didDispatchMountItems regardless of whether we actually dispatched anything, since
|
||||
// NativeAnimatedModule relies on this for executing any animations that may have been
|
||||
// scheduled
|
||||
itemDispatchListener.didDispatchMountItems()
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
fun dispatchMountItems(mountItems: Queue<MountItem?>) {
|
||||
while (!mountItems.isEmpty()) {
|
||||
val item = requireNotNull(mountItems.poll()) { "MountItem should not be null" }
|
||||
try {
|
||||
item.execute(mountingManager)
|
||||
} catch (e: RetryableMountingLayerException) {
|
||||
if (item is DispatchCommandMountItem) {
|
||||
// Only DispatchCommandMountItem supports retries
|
||||
val mountItem: DispatchCommandMountItem = item as DispatchCommandMountItem
|
||||
// Retrying exactly once
|
||||
if (mountItem.getRetries() == 0) {
|
||||
mountItem.incrementRetries()
|
||||
// In case we haven't retried executing this item yet, execute in the next batch of
|
||||
// items
|
||||
addViewCommandMountItem(mountItem)
|
||||
}
|
||||
} else {
|
||||
printMountItem(item, "dispatchExternalMountItems: mounting failed with ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes view commands, pre mount items and mount items in the respective order:
|
||||
* 1. View commands.
|
||||
* 2. Pre mount items.
|
||||
* 3. Regular mount items.
|
||||
*
|
||||
* Does nothing if `viewCommandMountItemsToDispatch` and `mountItemsToDispatch` are empty.
|
||||
* Nothing should call this directly except for `tryDispatchMountItems`.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private fun dispatchMountItems() {
|
||||
batchedExecutionTime = 0
|
||||
runStartTime = SystemClock.uptimeMillis()
|
||||
|
||||
val viewCommandMountItemsToDispatch = getAndResetViewCommandMountItems()
|
||||
val mountItemsToDispatch = getAndResetMountItems()
|
||||
|
||||
if (mountItemsToDispatch == null && viewCommandMountItemsToDispatch == null) {
|
||||
return
|
||||
}
|
||||
|
||||
itemDispatchListener.willMountItems(mountItemsToDispatch)
|
||||
|
||||
// As an optimization, execute all ViewCommands first
|
||||
// This should be:
|
||||
// 1) Performant: ViewCommands are often a replacement for SetNativeProps, which we've always
|
||||
// wanted to be as "synchronous" as possible.
|
||||
// 2) Safer: ViewCommands are inherently disconnected from the tree commit/diff/mount process.
|
||||
// JS imperatively queues these commands.
|
||||
// If JS has queued a command, it's reasonable to assume that the more time passes, the more
|
||||
// likely it is that the view disappears.
|
||||
// Thus, by executing ViewCommands early, we should actually avoid a category of
|
||||
// errors/glitches.
|
||||
viewCommandMountItemsToDispatch?.let { commands ->
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews viewCommandMountItems")
|
||||
|
||||
for (command in commands) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem")
|
||||
}
|
||||
try {
|
||||
executeOrEnqueue(command)
|
||||
} catch (e: RetryableMountingLayerException) {
|
||||
// If the exception is marked as Retryable, we retry the viewcommand exactly once, after
|
||||
// the current batch of mount items has finished executing.
|
||||
if (command.getRetries() == 0) {
|
||||
command.incrementRetries()
|
||||
addViewCommandMountItem(command)
|
||||
} else {
|
||||
// It's very common for commands to be executed on views that no longer exist - for
|
||||
// example, a blur event on TextInput being fired because of a navigation event away
|
||||
// from the current screen. If the exception is marked as Retryable, we log a soft
|
||||
// exception but never crash in debug.
|
||||
// It's not clear that logging this is even useful, because these events are very
|
||||
// common, mundane, and there's not much we can do about them currently.
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
ReactNoCrashSoftException("Caught exception executing ViewCommand: $command", e))
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// Non-retryable exceptions are logged as soft exceptions in prod, but crash in Debug.
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG, RuntimeException("Caught exception executing ViewCommand: $command", e))
|
||||
}
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
|
||||
// If there are MountItems to dispatch, we make sure all the "pre mount items" are executed
|
||||
// first
|
||||
getAndResetPreMountItems()?.let { preMountItems ->
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews preMountItems")
|
||||
for (preMountItem in preMountItems) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(preMountItem, "dispatchMountItems: Executing preMountItem")
|
||||
}
|
||||
executeOrEnqueue(preMountItem)
|
||||
}
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
|
||||
mountItemsToDispatch?.let { items ->
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews mountItems to execute")
|
||||
val batchedExecutionStartTime = SystemClock.uptimeMillis()
|
||||
|
||||
for (mountItem in items) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
|
||||
}
|
||||
|
||||
try {
|
||||
executeOrEnqueue(mountItem)
|
||||
} catch (e: Throwable) {
|
||||
// If there's an exception, we want to log diagnostics in prod and rethrow.
|
||||
FLog.e(TAG, "dispatchMountItems: caught exception, displaying mount state", e)
|
||||
for (m in items) {
|
||||
if (m === mountItem) {
|
||||
// We want to mark the mount item that caused exception
|
||||
FLog.e(TAG, "dispatchMountItems: mountItem: next mountItem triggered exception!")
|
||||
}
|
||||
printMountItem(m, "dispatchMountItems: mountItem")
|
||||
}
|
||||
|
||||
if (mountItem.getSurfaceId() != View.NO_ID) {
|
||||
mountingManager.getSurfaceManager(mountItem.getSurfaceId())?.printSurfaceState()
|
||||
}
|
||||
|
||||
if (ReactIgnorableMountingException.isIgnorable(e)) {
|
||||
ReactSoftExceptionLogger.logSoftException(TAG, e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
batchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
|
||||
itemDispatchListener.didMountItems(mountItemsToDispatch)
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes pre-mount items. Pre-mount items are operations that can be executed before the mount
|
||||
* items come, for example view pre-allocation. This is a performance optimisation to do as much
|
||||
* work ahead of time as possible.
|
||||
*
|
||||
* `tryDispatchMountItems` will also execute pre mount items, but only if there are mount items to be executed.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
fun dispatchPreMountItems(frameTimeNanos: Long) {
|
||||
lastFrameTimeNanos = frameTimeNanos
|
||||
|
||||
if (preMountItems.isEmpty()) {
|
||||
// Avoid starting systrace if there are no pre mount items.
|
||||
return
|
||||
}
|
||||
|
||||
val deadline = lastFrameTimeNanos + FRAME_TIME_NS / 2
|
||||
dispatchPreMountItemsImpl(deadline)
|
||||
}
|
||||
|
||||
private fun dispatchPreMountItemsImpl(deadline: Long) {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "MountItemDispatcher::premountViews")
|
||||
|
||||
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems from
|
||||
// reentering during dispatchPreMountItems
|
||||
inDispatch = true
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (System.nanoTime() > deadline) {
|
||||
break
|
||||
}
|
||||
|
||||
// If list is empty, `poll` will return null, or var will never be set
|
||||
val preMountItemToDispatch = preMountItems.poll() ?: break
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(preMountItemToDispatch, "dispatchPreMountItems")
|
||||
}
|
||||
executeOrEnqueue(preMountItemToDispatch)
|
||||
}
|
||||
} finally {
|
||||
inDispatch = false
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
|
||||
private fun executeOrEnqueue(item: MountItem) {
|
||||
if (mountingManager.isWaitingForViewAttach(item.getSurfaceId())) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
FLog.e(
|
||||
TAG,
|
||||
"executeOrEnqueue: Item execution delayed, surface %s is not ready yet",
|
||||
item.getSurfaceId())
|
||||
}
|
||||
val surfaceMountingManager: SurfaceMountingManager =
|
||||
mountingManager.getSurfaceManagerEnforced(
|
||||
item.getSurfaceId(), "MountItemDispatcher::executeOrEnqueue")
|
||||
surfaceMountingManager.scheduleMountItemOnViewAttach(item)
|
||||
} else {
|
||||
item.execute(mountingManager)
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadConfined(UI)
|
||||
@UiThread
|
||||
private fun getAndResetViewCommandMountItems(): List<DispatchCommandMountItem>? =
|
||||
drainConcurrentItemQueue(viewCommandMountItems)
|
||||
|
||||
@ThreadConfined(UI)
|
||||
@UiThread
|
||||
private fun getAndResetMountItems(): List<MountItem>? = drainConcurrentItemQueue(mountItems)
|
||||
|
||||
@ThreadConfined(UI)
|
||||
@UiThread
|
||||
private fun getAndResetPreMountItems(): List<MountItem>? = drainConcurrentItemQueue(preMountItems)
|
||||
|
||||
interface ItemDispatchListener {
|
||||
fun willMountItems(mountItems: List<MountItem>?)
|
||||
|
||||
fun didMountItems(mountItems: List<MountItem>?)
|
||||
|
||||
fun didDispatchMountItems()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TAG: String = "MountItemDispatcher"
|
||||
|
||||
private const val FRAME_TIME_NS: Long = (1000000000 / 60).toLong()
|
||||
|
||||
private fun <E> drainConcurrentItemQueue(queue: Queue<E>): List<E>? {
|
||||
if (queue.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return buildList {
|
||||
do {
|
||||
queue.poll()?.let { add(it) }
|
||||
} while (queue.isNotEmpty())
|
||||
}
|
||||
.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
private fun printMountItem(mountItem: MountItem, prefix: String) {
|
||||
// If a MountItem description is split across multiple lines, it's because it's a
|
||||
// compound MountItem. Log each line separately.
|
||||
val mountItemLines = mountItem.toString().split("\n").dropLastWhile { it.isEmpty() }
|
||||
|
||||
for (line in mountItemLines) {
|
||||
FLog.e(TAG, "$prefix: $line")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -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<<dee97293bfeab942c9cd0ba8622a5f86>>
|
||||
* @generated SignedSource<<d8013c4b4ec45f241908e7f8f23f7dd1>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -156,6 +156,12 @@ public object ReactNativeFeatureFlags {
|
||||
@JvmStatic
|
||||
public fun enableIOSViewClipToPaddingBox(): Boolean = accessor.enableIOSViewClipToPaddingBox()
|
||||
|
||||
/**
|
||||
* Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun enableIntersectionObserverEventLoopIntegration(): Boolean = accessor.enableIntersectionObserverEventLoopIntegration()
|
||||
|
||||
/**
|
||||
* When enabled, LayoutAnimations API will animate state changes on Android.
|
||||
*/
|
||||
|
||||
+11
-1
@@ -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<<c46464d2d10ac0c8a6b7869ae5b3cd2b>>
|
||||
* @generated SignedSource<<15b001b69d9ea8bedbce2ce0f70bfa8e>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -41,6 +41,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
|
||||
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
|
||||
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
|
||||
private var enableIntersectionObserverEventLoopIntegrationCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
|
||||
private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null
|
||||
@@ -262,6 +263,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableIntersectionObserverEventLoopIntegration(): Boolean {
|
||||
var cached = enableIntersectionObserverEventLoopIntegrationCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.enableIntersectionObserverEventLoopIntegration()
|
||||
enableIntersectionObserverEventLoopIntegrationCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableLayoutAnimationsOnAndroid(): Boolean {
|
||||
var cached = enableLayoutAnimationsOnAndroidCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -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<<62c548a9bfbf777a40b1b9e9d52b1de2>>
|
||||
* @generated SignedSource<<dc8ac9241675deb1324d8ff0572f2fad>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -70,6 +70,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableIOSViewClipToPaddingBox(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableIntersectionObserverEventLoopIntegration(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnAndroid(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnIOS(): Boolean
|
||||
|
||||
+3
-1
@@ -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<<de8775978e15479830fb215c6f15b144>>
|
||||
* @generated SignedSource<<56835f26710665a11a05879a0c90e577>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -65,6 +65,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun enableIOSViewClipToPaddingBox(): Boolean = false
|
||||
|
||||
override fun enableIntersectionObserverEventLoopIntegration(): Boolean = true
|
||||
|
||||
override fun enableLayoutAnimationsOnAndroid(): Boolean = false
|
||||
|
||||
override fun enableLayoutAnimationsOnIOS(): Boolean = true
|
||||
|
||||
+12
-1
@@ -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<<220b0cff47d2eadddf8a36646852d619>>
|
||||
* @generated SignedSource<<a6444cccfff813fe0e96c20855496560>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -45,6 +45,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
|
||||
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
|
||||
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
|
||||
private var enableIntersectionObserverEventLoopIntegrationCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
|
||||
private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null
|
||||
@@ -287,6 +288,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableIntersectionObserverEventLoopIntegration(): Boolean {
|
||||
var cached = enableIntersectionObserverEventLoopIntegrationCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.enableIntersectionObserverEventLoopIntegration()
|
||||
accessedFeatureFlags.add("enableIntersectionObserverEventLoopIntegration")
|
||||
enableIntersectionObserverEventLoopIntegrationCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableLayoutAnimationsOnAndroid(): Boolean {
|
||||
var cached = enableLayoutAnimationsOnAndroidCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -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<<d2ab95a5f7bf0dfc52428a90bee2fc10>>
|
||||
* @generated SignedSource<<e671a1bb72c4b5afcd6f364f05666e89>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -65,6 +65,8 @@ public interface ReactNativeFeatureFlagsProvider {
|
||||
|
||||
@DoNotStrip public fun enableIOSViewClipToPaddingBox(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableIntersectionObserverEventLoopIntegration(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableLayoutAnimationsOnAndroid(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableLayoutAnimationsOnIOS(): Boolean
|
||||
|
||||
+20
-4
@@ -13,6 +13,8 @@ import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.WindowInsetsController
|
||||
import android.view.WindowManager
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.fbreact.specs.NativeStatusBarManagerAndroidSpec
|
||||
import com.facebook.react.bridge.GuardedRunnable
|
||||
@@ -21,7 +23,6 @@ import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.UiThreadUtil
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.module.annotations.ReactModule
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
|
||||
import com.facebook.react.uimanager.PixelUtil
|
||||
import com.facebook.react.views.view.setStatusBarTranslucency
|
||||
import com.facebook.react.views.view.setStatusBarVisibility
|
||||
@@ -33,17 +34,32 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun getTypedExportedConstants(): Map<String, Any> {
|
||||
val currentActivity = reactApplicationContext.currentActivity
|
||||
val statusBarColor =
|
||||
currentActivity?.window?.statusBarColor?.let { color ->
|
||||
reactApplicationContext.getCurrentActivity()?.window?.statusBarColor?.let { color ->
|
||||
String.format("#%06X", 0xFFFFFF and color)
|
||||
} ?: "black"
|
||||
return mapOf(
|
||||
HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx(currentActivity).toFloat()),
|
||||
HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx()),
|
||||
DEFAULT_BACKGROUND_COLOR_KEY to statusBarColor,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStatusBarHeightPx(): Float {
|
||||
val windowInsets =
|
||||
reactApplicationContext
|
||||
.getCurrentActivity()
|
||||
?.window
|
||||
?.decorView
|
||||
?.let(ViewCompat::getRootWindowInsets) ?: return 0f
|
||||
return windowInsets
|
||||
.getInsets(
|
||||
WindowInsetsCompat.Type.statusBars() or
|
||||
WindowInsetsCompat.Type.navigationBars() or
|
||||
WindowInsetsCompat.Type.displayCutout())
|
||||
.top
|
||||
.toFloat()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun setColor(colorDouble: Double, animated: Boolean) {
|
||||
val color = colorDouble.toInt()
|
||||
|
||||
+1
@@ -188,6 +188,7 @@ internal constructor(
|
||||
internal val eventDispatcher: EventDispatcher?
|
||||
get() = reactHost?.eventDispatcher
|
||||
|
||||
@get:VisibleForTesting
|
||||
internal val isAttached: Boolean
|
||||
get() = reactHost != null
|
||||
|
||||
|
||||
+24
-58
@@ -173,19 +173,30 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull T view) {
|
||||
super.addEventEmitters(reactContext, view);
|
||||
|
||||
BaseVMFocusChangeListener focusChangeListener =
|
||||
new BaseVMFocusChangeListener(view.getOnFocusChangeListener());
|
||||
focusChangeListener.attach(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDropViewInstance(@NonNull T view) {
|
||||
super.onDropViewInstance(view);
|
||||
|
||||
@Nullable OnFocusChangeListener focusChangeListener = view.getOnFocusChangeListener();
|
||||
if (focusChangeListener instanceof BaseVMFocusChangeListener) {
|
||||
((BaseVMFocusChangeListener) focusChangeListener).detach(view);
|
||||
}
|
||||
@Nullable OnFocusChangeListener originalFocusChangeListener = view.getOnFocusChangeListener();
|
||||
view.setOnFocusChangeListener(
|
||||
(v, hasFocus) -> {
|
||||
if (originalFocusChangeListener != null) {
|
||||
originalFocusChangeListener.onFocusChange(v, hasFocus);
|
||||
}
|
||||
int surfaceId = UIManagerHelper.getSurfaceId(v.getContext());
|
||||
if (surfaceId == View.NO_ID) {
|
||||
return;
|
||||
}
|
||||
if (view.getContext() instanceof ThemedReactContext) {
|
||||
ThemedReactContext themedReactContext = (ThemedReactContext) v.getContext();
|
||||
@Nullable
|
||||
EventDispatcher eventDispatcher =
|
||||
UIManagerHelper.getEventDispatcherForReactTag(themedReactContext, view.getId());
|
||||
if (eventDispatcher != null) {
|
||||
if (hasFocus) {
|
||||
eventDispatcher.dispatchEvent(new FocusEvent(surfaceId, view.getId()));
|
||||
} else {
|
||||
eventDispatcher.dispatchEvent(new BlurEvent(surfaceId, view.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Currently, layout listener is only attached when transform or transformOrigin is set.
|
||||
@@ -1041,49 +1052,4 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
}
|
||||
|
||||
// Please add new props to BaseViewManagerDelegate as well!
|
||||
|
||||
/**
|
||||
* A helper class to keep track of the original focus change listener if one is set. This is
|
||||
* especially helpful for views that are recycled so we can retain and restore the original
|
||||
* listener upon recycling (onDropViewInstance).
|
||||
*/
|
||||
private class BaseVMFocusChangeListener<V extends View> implements OnFocusChangeListener {
|
||||
private @Nullable OnFocusChangeListener mOriginalFocusChangeListener;
|
||||
|
||||
public BaseVMFocusChangeListener(@Nullable OnFocusChangeListener originalFocusChangeListener) {
|
||||
mOriginalFocusChangeListener = originalFocusChangeListener;
|
||||
}
|
||||
|
||||
public void attach(T view) {
|
||||
view.setOnFocusChangeListener(this);
|
||||
}
|
||||
|
||||
public void detach(T view) {
|
||||
view.setOnFocusChangeListener(mOriginalFocusChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View view, boolean hasFocus) {
|
||||
if (mOriginalFocusChangeListener != null) {
|
||||
mOriginalFocusChangeListener.onFocusChange(view, hasFocus);
|
||||
}
|
||||
int surfaceId = UIManagerHelper.getSurfaceId(view.getContext());
|
||||
if (surfaceId == View.NO_ID) {
|
||||
return;
|
||||
}
|
||||
if (view.getContext() instanceof ThemedReactContext) {
|
||||
ThemedReactContext themedReactContext = (ThemedReactContext) view.getContext();
|
||||
@Nullable
|
||||
EventDispatcher eventDispatcher =
|
||||
UIManagerHelper.getEventDispatcherForReactTag(themedReactContext, view.getId());
|
||||
if (eventDispatcher != null) {
|
||||
if (hasFocus) {
|
||||
eventDispatcher.dispatchEvent(new FocusEvent(surfaceId, view.getId()));
|
||||
} else {
|
||||
eventDispatcher.dispatchEvent(new BlurEvent(surfaceId, view.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-13
@@ -7,12 +7,9 @@
|
||||
|
||||
package com.facebook.react.uimanager
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.WindowManager
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.bridge.WritableNativeMap
|
||||
|
||||
@@ -101,14 +98,4 @@ public object DisplayMetricsHolder {
|
||||
putDouble("fontScale", fontScale)
|
||||
putDouble("densityDpi", displayMetrics.densityDpi.toDouble())
|
||||
}
|
||||
|
||||
internal fun getStatusBarHeightPx(activity: Activity?): Int {
|
||||
val windowInsets = activity?.window?.decorView?.let(ViewCompat::getRootWindowInsets) ?: return 0
|
||||
return windowInsets
|
||||
.getInsets(
|
||||
WindowInsetsCompat.Type.statusBars() or
|
||||
WindowInsetsCompat.Type.navigationBars() or
|
||||
WindowInsetsCompat.Type.displayCutout())
|
||||
.top
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ private object ReactAxOrderHelper {
|
||||
* @return an array of views following the accessibility order
|
||||
*/
|
||||
@JvmStatic
|
||||
fun processAxOrderTree(
|
||||
public fun processAxOrderTree(
|
||||
root: View,
|
||||
axOrderIds: MutableList<String?>,
|
||||
axOrderSet: MutableSet<String?>
|
||||
@@ -123,7 +123,7 @@ private object ReactAxOrderHelper {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getVirtualViewBounds(host: View, virtualView: View): Rect {
|
||||
public fun getVirtualViewBounds(host: View, virtualView: View): Rect {
|
||||
var currentView: View = virtualView
|
||||
val viewBoundsInParent =
|
||||
Rect(virtualView.left, virtualView.top, virtualView.right, virtualView.bottom)
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ internal class ShadowNodeRegistry {
|
||||
inner class SingleThreadAsserter {
|
||||
private var thread: Thread? = null
|
||||
|
||||
fun assertNow() {
|
||||
public fun assertNow() {
|
||||
val currentThread = Thread.currentThread()
|
||||
if (thread == null) {
|
||||
thread = currentThread
|
||||
|
||||
+2
@@ -32,6 +32,7 @@ import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.util.Preconditions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.modules.i18nmanager.I18nUtil;
|
||||
import com.facebook.react.uimanager.FloatUtil;
|
||||
import com.facebook.react.uimanager.LengthPercentage;
|
||||
@@ -345,6 +346,7 @@ public class CSSBackgroundDrawable extends Drawable {
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
+417
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager.events;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pools;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.TouchTargetHelper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class PointerEvent extends Event<PointerEvent> {
|
||||
private static final String TAG = PointerEvent.class.getSimpleName();
|
||||
private static final int POINTER_EVENTS_POOL_SIZE = 6;
|
||||
private static final Pools.SynchronizedPool<PointerEvent> EVENTS_POOL =
|
||||
new Pools.SynchronizedPool<>(POINTER_EVENTS_POOL_SIZE);
|
||||
private static final short UNSET_COALESCING_KEY = -1;
|
||||
|
||||
public static PointerEvent obtain(
|
||||
String eventName,
|
||||
int targetTag,
|
||||
PointerEventState eventState,
|
||||
MotionEvent motionEventToCopy) {
|
||||
PointerEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new PointerEvent();
|
||||
}
|
||||
event.init(
|
||||
eventName, targetTag, eventState, Assertions.assertNotNull(motionEventToCopy), (short) 0);
|
||||
return event;
|
||||
}
|
||||
|
||||
public static PointerEvent obtain(
|
||||
String eventName,
|
||||
int targetTag,
|
||||
PointerEventState eventState,
|
||||
MotionEvent motionEventToCopy,
|
||||
short coalescingKey) {
|
||||
PointerEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new PointerEvent();
|
||||
}
|
||||
event.init(
|
||||
eventName,
|
||||
targetTag,
|
||||
eventState,
|
||||
Assertions.assertNotNull(motionEventToCopy),
|
||||
coalescingKey);
|
||||
return event;
|
||||
}
|
||||
|
||||
private @Nullable MotionEvent mMotionEvent;
|
||||
private @Nullable String mEventName;
|
||||
private short mCoalescingKey = UNSET_COALESCING_KEY;
|
||||
private @Nullable List<WritableMap> mPointersEventData;
|
||||
private PointerEventState mEventState;
|
||||
private @Nullable Event.EventAnimationDriverMatchSpec mEventAnimationDriverMatchSpec;
|
||||
|
||||
private void init(
|
||||
String eventName,
|
||||
int targetTag,
|
||||
PointerEventState eventState,
|
||||
MotionEvent motionEventToCopy,
|
||||
short coalescingKey) {
|
||||
|
||||
super.init(eventState.getSurfaceId(), targetTag, motionEventToCopy.getEventTime());
|
||||
mEventName = eventName;
|
||||
mMotionEvent = MotionEvent.obtain(motionEventToCopy);
|
||||
mCoalescingKey = coalescingKey;
|
||||
mEventState = eventState;
|
||||
}
|
||||
|
||||
private PointerEvent() {}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return mEventName;
|
||||
}
|
||||
|
||||
private boolean isClickEvent() {
|
||||
return mEventName.equals(PointerEventHelper.CLICK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
if (mMotionEvent == null) {
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
new IllegalStateException(
|
||||
"Cannot dispatch a Pointer that has no MotionEvent; the PointerEvehas been"
|
||||
+ " recycled"));
|
||||
return;
|
||||
}
|
||||
if (mPointersEventData == null) {
|
||||
mPointersEventData = createPointersEventData();
|
||||
}
|
||||
|
||||
if (mPointersEventData == null) {
|
||||
// No relevant MotionEvent to dispatch
|
||||
return;
|
||||
}
|
||||
|
||||
boolean shouldCopy = mPointersEventData.size() > 1;
|
||||
for (WritableMap pointerEventData : mPointersEventData) {
|
||||
WritableMap eventData = shouldCopy ? pointerEventData.copy() : pointerEventData;
|
||||
rctEventEmitter.receiveEvent(getViewTag(), mEventName, eventData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event.EventAnimationDriverMatchSpec getEventAnimationDriverMatchSpec() {
|
||||
if (mEventAnimationDriverMatchSpec == null) {
|
||||
mEventAnimationDriverMatchSpec =
|
||||
new EventAnimationDriverMatchSpec() {
|
||||
@Override
|
||||
public boolean match(int viewTag, String eventName) {
|
||||
if (!eventName.equals(mEventName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PointerEventHelper.isBubblingEvent(eventName)) {
|
||||
for (TouchTargetHelper.ViewTarget viewTarget :
|
||||
mEventState.getHitPathForActivePointer()) {
|
||||
if (viewTarget.getViewId() == viewTag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return getViewTag() == viewTag;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return mEventAnimationDriverMatchSpec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDispose() {
|
||||
mPointersEventData = null;
|
||||
MotionEvent motionEvent = mMotionEvent;
|
||||
mMotionEvent = null;
|
||||
if (motionEvent != null) {
|
||||
motionEvent.recycle();
|
||||
}
|
||||
|
||||
// Either `this` is in the event pool, or motionEvent
|
||||
// is null. It is in theory not possible for a PointerEvent to
|
||||
// be in the EVENTS_POOL but for motionEvent to be null. However,
|
||||
// out of an abundance of caution and to avoid memory leaks or
|
||||
// other crashes at all costs, we attempt to release here and log
|
||||
// a soft exception here if release throws an IllegalStateException
|
||||
// due to `this` being over-released. This may indicate that there is
|
||||
// a logic error in our events system or pooling mechanism.
|
||||
try {
|
||||
EVENTS_POOL.release(this);
|
||||
} catch (IllegalStateException e) {
|
||||
ReactSoftExceptionLogger.logSoftException(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<WritableMap> createW3CPointerEvents() {
|
||||
|
||||
ArrayList<WritableMap> w3cPointerEvents = new ArrayList<>();
|
||||
for (int index = 0; index < mMotionEvent.getPointerCount(); index++) {
|
||||
w3cPointerEvents.add(createW3CPointerEvent(index));
|
||||
}
|
||||
|
||||
return w3cPointerEvents;
|
||||
}
|
||||
|
||||
private void addModifierKeyData(WritableMap pointerEvent, int modifierKeyMask) {
|
||||
pointerEvent.putBoolean("ctrlKey", (modifierKeyMask & KeyEvent.META_CTRL_ON) != 0);
|
||||
pointerEvent.putBoolean("shiftKey", (modifierKeyMask & KeyEvent.META_SHIFT_ON) != 0);
|
||||
pointerEvent.putBoolean("altKey", (modifierKeyMask & KeyEvent.META_ALT_ON) != 0);
|
||||
pointerEvent.putBoolean("metaKey", (modifierKeyMask & KeyEvent.META_META_ON) != 0);
|
||||
}
|
||||
|
||||
private WritableMap createW3CPointerEvent(int index) {
|
||||
WritableMap pointerEvent = Arguments.createMap();
|
||||
int pointerId = mMotionEvent.getPointerId(index);
|
||||
|
||||
// https://www.w3.org/TR/pointerevents/#pointerevent-interface
|
||||
pointerEvent.putDouble("pointerId", pointerId);
|
||||
|
||||
String pointerType = PointerEventHelper.getW3CPointerType(mMotionEvent.getToolType(index));
|
||||
pointerEvent.putString("pointerType", pointerType);
|
||||
|
||||
boolean isPrimary =
|
||||
!isClickEvent() // compatibility click events should not be considered primary
|
||||
&& (mEventState.supportsHover(pointerId) || pointerId == mEventState.mPrimaryPointerId);
|
||||
pointerEvent.putBoolean("isPrimary", isPrimary);
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
|
||||
// Client refers to upper left edge of the content area (viewport)
|
||||
// We define the viewport to be ReactRootView
|
||||
float[] eventCoords = mEventState.getEventCoordinatesByPointerId().get(pointerId);
|
||||
double clientX = PixelUtil.toDIPFromPixel(eventCoords[0]);
|
||||
double clientY = PixelUtil.toDIPFromPixel(eventCoords[1]);
|
||||
pointerEvent.putDouble("clientX", clientX);
|
||||
pointerEvent.putDouble("clientY", clientY);
|
||||
|
||||
float[] screenCoords = mEventState.getScreenCoordinatesByPointerId().get(pointerId);
|
||||
double screenX = PixelUtil.toDIPFromPixel(screenCoords[0]);
|
||||
double screenY = PixelUtil.toDIPFromPixel(screenCoords[1]);
|
||||
pointerEvent.putDouble("screenX", screenX);
|
||||
pointerEvent.putDouble("screenY", screenY);
|
||||
|
||||
// x,y values are aliases of clientX, clientY
|
||||
pointerEvent.putDouble("x", clientX);
|
||||
pointerEvent.putDouble("y", clientY);
|
||||
|
||||
// page values in react-native are equivalent to client values since rootview is not scrollable
|
||||
pointerEvent.putDouble("pageX", clientX);
|
||||
pointerEvent.putDouble("pageY", clientY);
|
||||
|
||||
// Offset refers to upper left edge of the target view
|
||||
float[] offsetCoords = mEventState.getOffsetByPointerId().get(pointerId);
|
||||
pointerEvent.putDouble("offsetX", PixelUtil.toDIPFromPixel(offsetCoords[0]));
|
||||
pointerEvent.putDouble("offsetY", PixelUtil.toDIPFromPixel(offsetCoords[1]));
|
||||
|
||||
pointerEvent.putInt("target", getViewTag());
|
||||
pointerEvent.putDouble("timestamp", getTimestampMs());
|
||||
|
||||
pointerEvent.putInt("detail", 0);
|
||||
pointerEvent.putDouble("tiltX", 0);
|
||||
pointerEvent.putDouble("tiltY", 0);
|
||||
|
||||
pointerEvent.putInt("twist", 0);
|
||||
// note: click events should have width = height = 1
|
||||
if (pointerType.equals(PointerEventHelper.POINTER_TYPE_MOUSE) || isClickEvent()) {
|
||||
pointerEvent.putDouble("width", 1);
|
||||
pointerEvent.putDouble("height", 1);
|
||||
} else {
|
||||
float majorAxis = PixelUtil.toDIPFromPixel(mMotionEvent.getTouchMajor(index));
|
||||
pointerEvent.putDouble("width", majorAxis);
|
||||
pointerEvent.putDouble("height", majorAxis);
|
||||
}
|
||||
|
||||
int buttonState = mMotionEvent.getButtonState();
|
||||
pointerEvent.putInt(
|
||||
"button",
|
||||
PointerEventHelper.getButtonChange(
|
||||
pointerType, mEventState.getLastButtonState(), buttonState));
|
||||
pointerEvent.putInt(
|
||||
"buttons", PointerEventHelper.getButtons(mEventName, pointerType, buttonState));
|
||||
|
||||
final double pressure =
|
||||
isClickEvent() // click events need pressure=0
|
||||
? 0
|
||||
: PointerEventHelper.getPressure(pointerEvent.getInt("buttons"), mEventName);
|
||||
|
||||
pointerEvent.putDouble("pressure", pressure);
|
||||
pointerEvent.putDouble("tangentialPressure", 0.0);
|
||||
|
||||
addModifierKeyData(pointerEvent, mMotionEvent.getMetaState());
|
||||
|
||||
return pointerEvent;
|
||||
}
|
||||
|
||||
private List<WritableMap> createPointersEventData() {
|
||||
int activePointerIndex = mMotionEvent.getActionIndex();
|
||||
List<WritableMap> pointersEventData = null;
|
||||
switch (mEventName) {
|
||||
// Cases where all pointer info is relevant
|
||||
case PointerEventHelper.POINTER_MOVE:
|
||||
case PointerEventHelper.POINTER_CANCEL:
|
||||
pointersEventData = createW3CPointerEvents();
|
||||
break;
|
||||
// Cases where only the "active" pointer info is relevant
|
||||
case PointerEventHelper.POINTER_ENTER:
|
||||
case PointerEventHelper.POINTER_DOWN:
|
||||
case PointerEventHelper.POINTER_UP:
|
||||
case PointerEventHelper.POINTER_LEAVE:
|
||||
case PointerEventHelper.POINTER_OUT:
|
||||
case PointerEventHelper.POINTER_OVER:
|
||||
case PointerEventHelper.CLICK:
|
||||
pointersEventData = Arrays.asList(createW3CPointerEvent(activePointerIndex));
|
||||
break;
|
||||
}
|
||||
|
||||
return pointersEventData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return mCoalescingKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchModern(RCTModernEventEmitter rctEventEmitter) {
|
||||
if (mMotionEvent == null) {
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
new IllegalStateException(
|
||||
"Cannot dispatch a Pointer that has no MotionEvent; the PointerEvehas been"
|
||||
+ " recycled"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPointersEventData == null) {
|
||||
mPointersEventData = createPointersEventData();
|
||||
}
|
||||
|
||||
if (mPointersEventData == null) {
|
||||
// No relevant MotionEvent to dispatch
|
||||
return;
|
||||
}
|
||||
|
||||
boolean shouldCopy = mPointersEventData.size() > 1;
|
||||
for (WritableMap pointerEventData : mPointersEventData) {
|
||||
WritableMap eventData = shouldCopy ? pointerEventData.copy() : pointerEventData;
|
||||
rctEventEmitter.receiveEvent(
|
||||
getSurfaceId(),
|
||||
getViewTag(),
|
||||
mEventName,
|
||||
mCoalescingKey != UNSET_COALESCING_KEY,
|
||||
mCoalescingKey,
|
||||
eventData,
|
||||
PointerEventHelper.getEventCategory(mEventName));
|
||||
}
|
||||
}
|
||||
|
||||
public static class PointerEventState {
|
||||
private int mPrimaryPointerId;
|
||||
private int mActivePointerId;
|
||||
private int mLastButtonState;
|
||||
private int mSurfaceId;
|
||||
|
||||
private Map<Integer, float[]> mOffsetByPointerId;
|
||||
private Map<Integer, List<TouchTargetHelper.ViewTarget>> mHitPathByPointerId;
|
||||
private Map<Integer, float[]> mEventCoordinatesByPointerId;
|
||||
private Map<Integer, float[]> mScreenCoordinatesByPointerId;
|
||||
private Set<Integer> mHoveringPointerIds;
|
||||
|
||||
public PointerEventState(
|
||||
int primaryPointerId,
|
||||
int activePointerId,
|
||||
int lastButtonState,
|
||||
int surfaceId,
|
||||
Map<Integer, float[]> offsetByPointerId,
|
||||
Map<Integer, List<TouchTargetHelper.ViewTarget>> hitPathByPointerId,
|
||||
Map<Integer, float[]> eventCoordinatesByPointerId,
|
||||
Map<Integer, float[]> screenCoordinatesByPointerId,
|
||||
Set<Integer> hoveringPointerIds) {
|
||||
mPrimaryPointerId = primaryPointerId;
|
||||
mActivePointerId = activePointerId;
|
||||
mLastButtonState = lastButtonState;
|
||||
mSurfaceId = surfaceId;
|
||||
mOffsetByPointerId = offsetByPointerId;
|
||||
mHitPathByPointerId = hitPathByPointerId;
|
||||
mEventCoordinatesByPointerId = eventCoordinatesByPointerId;
|
||||
mScreenCoordinatesByPointerId = screenCoordinatesByPointerId;
|
||||
mHoveringPointerIds = new HashSet<>(hoveringPointerIds);
|
||||
}
|
||||
|
||||
public int getLastButtonState() {
|
||||
return mLastButtonState;
|
||||
}
|
||||
|
||||
public int getPrimaryPointerId() {
|
||||
return mPrimaryPointerId;
|
||||
}
|
||||
|
||||
public int getSurfaceId() {
|
||||
return mSurfaceId;
|
||||
}
|
||||
|
||||
public int getActivePointerId() {
|
||||
return mActivePointerId;
|
||||
}
|
||||
|
||||
public boolean supportsHover(int pointerId) {
|
||||
return mHoveringPointerIds.contains(pointerId);
|
||||
}
|
||||
|
||||
public Set<Integer> getHoveringPointerIds() {
|
||||
return mHoveringPointerIds;
|
||||
}
|
||||
|
||||
public final Map<Integer, float[]> getOffsetByPointerId() {
|
||||
return mOffsetByPointerId;
|
||||
}
|
||||
|
||||
public final Map<Integer, List<TouchTargetHelper.ViewTarget>> getHitPathByPointerId() {
|
||||
return mHitPathByPointerId;
|
||||
}
|
||||
|
||||
public final Map<Integer, float[]> getEventCoordinatesByPointerId() {
|
||||
return mEventCoordinatesByPointerId;
|
||||
}
|
||||
|
||||
public final Map<Integer, float[]> getScreenCoordinatesByPointerId() {
|
||||
return mScreenCoordinatesByPointerId;
|
||||
}
|
||||
|
||||
public final List<TouchTargetHelper.ViewTarget> getHitPathForActivePointer() {
|
||||
return mHitPathByPointerId.get(mActivePointerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
-340
@@ -1,340 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager.events
|
||||
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import androidx.core.util.Pools.SynchronizedPool
|
||||
import com.facebook.infer.annotation.Assertions
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger.logSoftException
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel
|
||||
import com.facebook.react.uimanager.TouchTargetHelper.ViewTarget
|
||||
import com.facebook.react.uimanager.events.Event.EventAnimationDriverMatchSpec
|
||||
import com.facebook.react.uimanager.events.PointerEventHelper.getButtonChange
|
||||
import com.facebook.react.uimanager.events.PointerEventHelper.getButtons
|
||||
import com.facebook.react.uimanager.events.PointerEventHelper.getEventCategory
|
||||
import com.facebook.react.uimanager.events.PointerEventHelper.getPressure
|
||||
import com.facebook.react.uimanager.events.PointerEventHelper.getW3CPointerType
|
||||
import com.facebook.react.uimanager.events.PointerEventHelper.isBubblingEvent
|
||||
|
||||
internal class PointerEvent private constructor() : Event<PointerEvent>() {
|
||||
private var motionEvent: MotionEvent? = null
|
||||
private lateinit var _eventName: String
|
||||
private var coalescingKey = UNSET_COALESCING_KEY
|
||||
private var pointersEventData: List<WritableMap>? = null
|
||||
private lateinit var eventState: PointerEventState
|
||||
|
||||
private fun init(
|
||||
eventName: String,
|
||||
targetTag: Int,
|
||||
eventState: PointerEventState,
|
||||
motionEventToCopy: MotionEvent,
|
||||
coalescingKey: Short
|
||||
) {
|
||||
super.init(eventState.getSurfaceId(), targetTag, motionEventToCopy.eventTime)
|
||||
this._eventName = eventName
|
||||
this.motionEvent = MotionEvent.obtain(motionEventToCopy)
|
||||
this.coalescingKey = coalescingKey
|
||||
this.eventState = eventState
|
||||
}
|
||||
|
||||
override fun getEventName(): String = _eventName
|
||||
|
||||
private val isClickEvent: Boolean
|
||||
get() = _eventName == PointerEventHelper.CLICK
|
||||
|
||||
@Deprecated("Prefer to override getEventData instead")
|
||||
@Suppress("DEPRECATION")
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
||||
if (motionEvent == null) {
|
||||
logSoftException(
|
||||
TAG,
|
||||
IllegalStateException(
|
||||
"Cannot dispatch a Pointer that has no MotionEvent; the PointerEvent has been" +
|
||||
" recycled"))
|
||||
return
|
||||
}
|
||||
if (pointersEventData == null) {
|
||||
pointersEventData = createPointersEventData()
|
||||
}
|
||||
|
||||
val data = pointersEventData ?: return // No relevant MotionEvent to dispatch
|
||||
|
||||
val shouldCopy = data.size > 1
|
||||
for (pointerEventData in data) {
|
||||
val eventData = if (shouldCopy) pointerEventData.copy() else pointerEventData
|
||||
rctEventEmitter.receiveEvent(viewTag, _eventName, eventData)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
override val eventAnimationDriverMatchSpec: EventAnimationDriverMatchSpec by
|
||||
lazy(LazyThreadSafetyMode.NONE) {
|
||||
EventAnimationDriverMatchSpec { viewTag, eventName ->
|
||||
if (eventName != _eventName) {
|
||||
return@EventAnimationDriverMatchSpec false
|
||||
}
|
||||
if (isBubblingEvent(eventName)) {
|
||||
for (viewTarget in eventState.hitPathForActivePointer) {
|
||||
if (viewTarget.getViewId() == viewTag) {
|
||||
return@EventAnimationDriverMatchSpec true
|
||||
}
|
||||
}
|
||||
false
|
||||
} else {
|
||||
this.viewTag == viewTag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDispose() {
|
||||
pointersEventData = null
|
||||
val motionEvent = motionEvent
|
||||
this.motionEvent = null
|
||||
motionEvent?.recycle()
|
||||
|
||||
// Either `this` is in the event pool, or motionEvent
|
||||
// is null. It is in theory not possible for a PointerEvent to
|
||||
// be in the EVENTS_POOL but for motionEvent to be null. However,
|
||||
// out of an abundance of caution and to avoid memory leaks or
|
||||
// other crashes at all costs, we attempt to release here and log
|
||||
// a soft exception here if release throws an IllegalStateException
|
||||
// due to `this` being over-released. This may indicate that there is
|
||||
// a logic error in our events system or pooling mechanism.
|
||||
try {
|
||||
EVENTS_POOL.release(this)
|
||||
} catch (e: IllegalStateException) {
|
||||
logSoftException(TAG, e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createW3CPointerEvents(): List<WritableMap> {
|
||||
val w3cPointerEvents = ArrayList<WritableMap>()
|
||||
for (index in 0..<checkNotNull(motionEvent).pointerCount) {
|
||||
w3cPointerEvents.add(createW3CPointerEvent(index))
|
||||
}
|
||||
|
||||
return w3cPointerEvents
|
||||
}
|
||||
|
||||
private fun addModifierKeyData(pointerEvent: WritableMap, modifierKeyMask: Int) {
|
||||
pointerEvent.putBoolean("ctrlKey", (modifierKeyMask and KeyEvent.META_CTRL_ON) != 0)
|
||||
pointerEvent.putBoolean("shiftKey", (modifierKeyMask and KeyEvent.META_SHIFT_ON) != 0)
|
||||
pointerEvent.putBoolean("altKey", (modifierKeyMask and KeyEvent.META_ALT_ON) != 0)
|
||||
pointerEvent.putBoolean("metaKey", (modifierKeyMask and KeyEvent.META_META_ON) != 0)
|
||||
}
|
||||
|
||||
private fun createW3CPointerEvent(index: Int): WritableMap {
|
||||
val pointerEvent = Arguments.createMap()
|
||||
val motionEvent = checkNotNull(motionEvent)
|
||||
val pointerId = motionEvent.getPointerId(index)
|
||||
|
||||
// https://www.w3.org/TR/pointerevents/#pointerevent-interface
|
||||
pointerEvent.putDouble("pointerId", pointerId.toDouble())
|
||||
|
||||
val pointerType = getW3CPointerType(motionEvent.getToolType(index))
|
||||
pointerEvent.putString("pointerType", pointerType)
|
||||
|
||||
val isPrimary =
|
||||
!isClickEvent // compatibility click events should not be considered primary
|
||||
&& (eventState.supportsHover(pointerId) || pointerId == eventState.primaryPointerId)
|
||||
pointerEvent.putBoolean("isPrimary", isPrimary)
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
|
||||
// Client refers to upper left edge of the content area (viewport)
|
||||
// We define the viewport to be ReactRootView
|
||||
val eventCoords = checkNotNull(eventState.eventCoordinatesByPointerId[pointerId])
|
||||
val clientX = toDIPFromPixel(eventCoords[0]).toDouble()
|
||||
val clientY = toDIPFromPixel(eventCoords[1]).toDouble()
|
||||
pointerEvent.putDouble("clientX", clientX)
|
||||
pointerEvent.putDouble("clientY", clientY)
|
||||
|
||||
val screenCoords = checkNotNull(eventState.screenCoordinatesByPointerId[pointerId])
|
||||
val screenX = toDIPFromPixel(screenCoords[0]).toDouble()
|
||||
val screenY = toDIPFromPixel(screenCoords[1]).toDouble()
|
||||
pointerEvent.putDouble("screenX", screenX)
|
||||
pointerEvent.putDouble("screenY", screenY)
|
||||
|
||||
// x,y values are aliases of clientX, clientY
|
||||
pointerEvent.putDouble("x", clientX)
|
||||
pointerEvent.putDouble("y", clientY)
|
||||
|
||||
// page values in react-native are equivalent to client values since rootview is not scrollable
|
||||
pointerEvent.putDouble("pageX", clientX)
|
||||
pointerEvent.putDouble("pageY", clientY)
|
||||
|
||||
// Offset refers to upper left edge of the target view
|
||||
val offsetCoords = checkNotNull(eventState.offsetByPointerId[pointerId])
|
||||
pointerEvent.putDouble("offsetX", toDIPFromPixel(offsetCoords[0]).toDouble())
|
||||
pointerEvent.putDouble("offsetY", toDIPFromPixel(offsetCoords[1]).toDouble())
|
||||
|
||||
pointerEvent.putInt("target", viewTag)
|
||||
pointerEvent.putDouble("timestamp", timestampMs.toDouble())
|
||||
|
||||
pointerEvent.putInt("detail", 0)
|
||||
pointerEvent.putDouble("tiltX", 0.0)
|
||||
pointerEvent.putDouble("tiltY", 0.0)
|
||||
|
||||
pointerEvent.putInt("twist", 0)
|
||||
// note: click events should have width = height = 1
|
||||
if (pointerType == PointerEventHelper.POINTER_TYPE_MOUSE || isClickEvent) {
|
||||
pointerEvent.putDouble("width", 1.0)
|
||||
pointerEvent.putDouble("height", 1.0)
|
||||
} else {
|
||||
val majorAxis = toDIPFromPixel(motionEvent.getTouchMajor(index))
|
||||
pointerEvent.putDouble("width", majorAxis.toDouble())
|
||||
pointerEvent.putDouble("height", majorAxis.toDouble())
|
||||
}
|
||||
|
||||
val buttonState = motionEvent.buttonState
|
||||
pointerEvent.putInt(
|
||||
"button", getButtonChange(pointerType, eventState.lastButtonState, buttonState))
|
||||
pointerEvent.putInt("buttons", getButtons(_eventName, pointerType, buttonState))
|
||||
|
||||
val pressure =
|
||||
if (isClickEvent) {
|
||||
0.0 // click events need pressure=0
|
||||
} else {
|
||||
getPressure(pointerEvent.getInt("buttons"), _eventName)
|
||||
}
|
||||
|
||||
pointerEvent.putDouble("pressure", pressure)
|
||||
pointerEvent.putDouble("tangentialPressure", 0.0)
|
||||
|
||||
addModifierKeyData(pointerEvent, motionEvent.metaState)
|
||||
|
||||
return pointerEvent
|
||||
}
|
||||
|
||||
private fun createPointersEventData(): List<WritableMap>? {
|
||||
val activePointerIndex = checkNotNull(motionEvent).actionIndex
|
||||
var pointersEventData: List<WritableMap>? = null
|
||||
when (_eventName) {
|
||||
PointerEventHelper.POINTER_MOVE,
|
||||
PointerEventHelper.POINTER_CANCEL -> {
|
||||
pointersEventData = createW3CPointerEvents()
|
||||
}
|
||||
PointerEventHelper.POINTER_ENTER,
|
||||
PointerEventHelper.POINTER_DOWN,
|
||||
PointerEventHelper.POINTER_UP,
|
||||
PointerEventHelper.POINTER_LEAVE,
|
||||
PointerEventHelper.POINTER_OUT,
|
||||
PointerEventHelper.POINTER_OVER,
|
||||
PointerEventHelper.CLICK -> {
|
||||
pointersEventData = listOf(createW3CPointerEvent(activePointerIndex))
|
||||
}
|
||||
}
|
||||
return pointersEventData
|
||||
}
|
||||
|
||||
override fun getCoalescingKey(): Short = coalescingKey
|
||||
|
||||
override fun dispatchModern(rctEventEmitter: RCTModernEventEmitter) {
|
||||
if (motionEvent == null) {
|
||||
logSoftException(
|
||||
TAG,
|
||||
IllegalStateException(
|
||||
"Cannot dispatch a Pointer that has no MotionEvent; the PointerEvent has been recycled"))
|
||||
return
|
||||
}
|
||||
|
||||
if (pointersEventData == null) {
|
||||
pointersEventData = createPointersEventData()
|
||||
}
|
||||
|
||||
if (pointersEventData == null) {
|
||||
// No relevant MotionEvent to dispatch
|
||||
return
|
||||
}
|
||||
|
||||
val pointersEventData = checkNotNull(pointersEventData)
|
||||
val shouldCopy = pointersEventData.size > 1
|
||||
for (singleData in pointersEventData) {
|
||||
val eventData = if (shouldCopy) singleData.copy() else singleData
|
||||
rctEventEmitter.receiveEvent(
|
||||
surfaceId,
|
||||
viewTag,
|
||||
_eventName,
|
||||
coalescingKey != UNSET_COALESCING_KEY,
|
||||
coalescingKey.toInt(),
|
||||
eventData,
|
||||
getEventCategory(_eventName))
|
||||
}
|
||||
}
|
||||
|
||||
class PointerEventState(
|
||||
val primaryPointerId: Int,
|
||||
val activePointerId: Int,
|
||||
val lastButtonState: Int,
|
||||
private val surfaceId: Int,
|
||||
val offsetByPointerId: Map<Int, FloatArray>,
|
||||
val hitPathByPointerId: Map<Int, List<ViewTarget>>,
|
||||
val eventCoordinatesByPointerId: Map<Int, FloatArray>,
|
||||
val screenCoordinatesByPointerId: Map<Int, FloatArray>,
|
||||
hoveringPointerIds: Set<Int>
|
||||
) {
|
||||
val hoveringPointerIds: Set<Int> = HashSet(hoveringPointerIds)
|
||||
|
||||
fun getSurfaceId(): Int = surfaceId
|
||||
|
||||
fun supportsHover(pointerId: Int): Boolean = hoveringPointerIds.contains(pointerId)
|
||||
|
||||
val hitPathForActivePointer: List<ViewTarget>
|
||||
get() = checkNotNull(hitPathByPointerId[activePointerId])
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG: String = PointerEvent::class.java.simpleName
|
||||
private const val POINTER_EVENTS_POOL_SIZE = 6
|
||||
private val EVENTS_POOL = SynchronizedPool<PointerEvent>(POINTER_EVENTS_POOL_SIZE)
|
||||
private const val UNSET_COALESCING_KEY: Short = -1
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(
|
||||
eventName: String,
|
||||
targetTag: Int,
|
||||
eventState: PointerEventState,
|
||||
motionEventToCopy: MotionEvent?
|
||||
): PointerEvent {
|
||||
var event = EVENTS_POOL.acquire()
|
||||
if (event == null) {
|
||||
event = PointerEvent()
|
||||
}
|
||||
event.init(
|
||||
eventName,
|
||||
targetTag,
|
||||
eventState,
|
||||
Assertions.assertNotNull(motionEventToCopy),
|
||||
0.toShort())
|
||||
return event
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(
|
||||
eventName: String,
|
||||
targetTag: Int,
|
||||
eventState: PointerEventState,
|
||||
motionEventToCopy: MotionEvent?,
|
||||
coalescingKey: Short
|
||||
): PointerEvent {
|
||||
var event = EVENTS_POOL.acquire()
|
||||
if (event == null) {
|
||||
event = PointerEvent()
|
||||
}
|
||||
event.init(
|
||||
eventName,
|
||||
targetTag,
|
||||
eventState,
|
||||
Assertions.assertNotNull(motionEventToCopy),
|
||||
coalescingKey)
|
||||
return event
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -86,7 +86,7 @@ internal object TouchesHelper {
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
@JvmStatic
|
||||
fun sendTouchesLegacy(rctEventEmitter: RCTEventEmitter, touchEvent: TouchEvent) {
|
||||
public fun sendTouchesLegacy(rctEventEmitter: RCTEventEmitter, touchEvent: TouchEvent) {
|
||||
val type = touchEvent.getTouchEventType()
|
||||
|
||||
val pointers = getWritableArray(/* copyObjects */ false, createPointersArray(touchEvent))
|
||||
@@ -122,7 +122,7 @@ internal object TouchesHelper {
|
||||
* @param event the touch event to extract data from
|
||||
*/
|
||||
@JvmStatic
|
||||
fun sendTouchEvent(eventEmitter: RCTModernEventEmitter, event: TouchEvent) {
|
||||
public fun sendTouchEvent(eventEmitter: RCTModernEventEmitter, event: TouchEvent) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT,
|
||||
"TouchesHelper.sentTouchEventModern(" + event.getEventName() + ")")
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ internal class DrawerClosedEvent : Event<DrawerClosedEvent> {
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun getEventData(): WritableMap = Arguments.createMap()
|
||||
protected override fun getEventData(): WritableMap = Arguments.createMap()
|
||||
|
||||
companion object {
|
||||
const val EVENT_NAME: String = "topDrawerClose"
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ internal class DrawerOpenedEvent : Event<DrawerOpenedEvent> {
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun getEventData(): WritableMap = Arguments.createMap()
|
||||
protected override fun getEventData(): WritableMap = Arguments.createMap()
|
||||
|
||||
companion object {
|
||||
const val EVENT_NAME: String = "topDrawerOpen"
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ internal class DrawerSlideEvent : Event<DrawerSlideEvent> {
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun getEventData(): WritableMap {
|
||||
protected override fun getEventData(): WritableMap {
|
||||
val eventData: WritableMap = Arguments.createMap()
|
||||
eventData.putDouble("offset", getOffset().toDouble())
|
||||
return eventData
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ internal class DrawerStateChangedEvent : Event<DrawerStateChangedEvent> {
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun getEventData(): WritableMap {
|
||||
protected override fun getEventData(): WritableMap {
|
||||
val eventData: WritableMap = Arguments.createMap()
|
||||
eventData.putInt("drawerState", getDrawerState())
|
||||
return eventData
|
||||
|
||||
+6
-3
@@ -29,7 +29,7 @@ internal class ReactModalHostManager :
|
||||
|
||||
override fun getName(): String = REACT_CLASS
|
||||
|
||||
override fun createViewInstance(reactContext: ThemedReactContext): ReactModalHostView =
|
||||
protected override fun createViewInstance(reactContext: ThemedReactContext): ReactModalHostView =
|
||||
ReactModalHostView(reactContext)
|
||||
|
||||
override fun onDropViewInstance(view: ReactModalHostView) {
|
||||
@@ -93,7 +93,10 @@ internal class ReactModalHostManager :
|
||||
view.setDialogRootViewGroupTestId(value)
|
||||
}
|
||||
|
||||
override fun addEventEmitters(reactContext: ThemedReactContext, view: ReactModalHostView) {
|
||||
protected override fun addEventEmitters(
|
||||
reactContext: ThemedReactContext,
|
||||
view: ReactModalHostView
|
||||
) {
|
||||
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
|
||||
if (dispatcher != null) {
|
||||
view.onRequestCloseListener = OnRequestCloseListener {
|
||||
@@ -115,7 +118,7 @@ internal class ReactModalHostManager :
|
||||
put("topOrientationChange", mapOf("registrationName" to "onOrientationChange"))
|
||||
}
|
||||
|
||||
override fun onAfterUpdateTransaction(view: ReactModalHostView) {
|
||||
protected override fun onAfterUpdateTransaction(view: ReactModalHostView) {
|
||||
super.onAfterUpdateTransaction(view)
|
||||
view.showOrUpdate()
|
||||
}
|
||||
|
||||
-25
@@ -40,8 +40,6 @@ import com.facebook.react.bridge.WritableNativeMap
|
||||
import com.facebook.react.common.annotations.VisibleForTesting
|
||||
import com.facebook.react.common.build.ReactBuildConfig
|
||||
import com.facebook.react.config.ReactFeatureFlags
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
|
||||
import com.facebook.react.uimanager.JSPointerDispatcher
|
||||
import com.facebook.react.uimanager.JSTouchDispatcher
|
||||
import com.facebook.react.uimanager.PixelUtil.pxToDp
|
||||
@@ -55,7 +53,6 @@ import com.facebook.react.views.modal.ReactModalHostView.DialogRootViewGroup
|
||||
import com.facebook.react.views.view.ReactViewGroup
|
||||
import com.facebook.react.views.view.setStatusBarTranslucency
|
||||
import com.facebook.react.views.view.setSystemBarsTranslucency
|
||||
import com.facebook.yoga.annotations.DoNotStrip
|
||||
|
||||
/**
|
||||
* ReactModalHostView is a view that sits in the view hierarchy representing a Modal view.
|
||||
@@ -125,7 +122,6 @@ public class ReactModalHostView(context: ThemedReactContext) :
|
||||
private var createNewDialog = false
|
||||
|
||||
init {
|
||||
initStatusBarHeight(context)
|
||||
dialogRootViewGroup = DialogRootViewGroup(context)
|
||||
}
|
||||
|
||||
@@ -465,26 +461,6 @@ public class ReactModalHostView(context: ThemedReactContext) :
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "ReactModalHost"
|
||||
|
||||
// We store the status bar height to be able to properly position
|
||||
// the modal on the first render.
|
||||
private var statusBarHeight = 0
|
||||
|
||||
private fun initStatusBarHeight(reactContext: ReactContext) {
|
||||
statusBarHeight = getStatusBarHeightPx(reactContext.currentActivity)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@DoNotStrip
|
||||
private fun getScreenDisplayMetricsWithoutInsets(): Long {
|
||||
val displayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics()
|
||||
return encodeFloatsToLong(
|
||||
displayMetrics.widthPixels.toFloat().pxToDp(),
|
||||
(displayMetrics.heightPixels - statusBarHeight).toFloat().pxToDp())
|
||||
}
|
||||
|
||||
private fun encodeFloatsToLong(width: Float, height: Float): Long =
|
||||
(width.toRawBits().toLong()) shl 32 or (height.toRawBits().toLong())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -500,7 +476,6 @@ public class ReactModalHostView(context: ThemedReactContext) :
|
||||
*/
|
||||
public class DialogRootViewGroup internal constructor(context: Context) :
|
||||
ReactViewGroup(context), RootView {
|
||||
|
||||
internal var stateWrapper: StateWrapper? = null
|
||||
internal var eventDispatcher: EventDispatcher? = null
|
||||
|
||||
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.BoringLayout;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.view.Gravity;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactNoCrashSoftException;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel;
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyOptimizer;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.views.text.internal.span.ReactAbsoluteSizeSpan;
|
||||
import com.facebook.react.views.text.internal.span.TextInlineViewPlaceholderSpan;
|
||||
import com.facebook.yoga.YogaBaselineFunction;
|
||||
import com.facebook.yoga.YogaConstants;
|
||||
import com.facebook.yoga.YogaDirection;
|
||||
import com.facebook.yoga.YogaMeasureFunction;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
import com.facebook.yoga.YogaMeasureOutput;
|
||||
import com.facebook.yoga.YogaNode;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* {@link ReactBaseTextShadowNode} concrete class for anchor {@code Text} node.
|
||||
*
|
||||
* <p>The class measures text in {@code <Text>} view and feeds native {@link TextView} using {@code
|
||||
* Spannable} object constructed in superclass.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public class ReactTextShadowNode extends ReactBaseTextShadowNode {
|
||||
|
||||
// It's important to pass the ANTI_ALIAS_FLAG flag to the constructor rather than setting it
|
||||
// later by calling setFlags. This is because the latter approach triggers a bug on Android 4.4.2.
|
||||
// The bug is that unicode emoticons aren't measured properly which causes text to be clipped.
|
||||
private static final TextPaint sTextPaintInstance = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private @Nullable Spannable mPreparedSpannableText;
|
||||
|
||||
private boolean mShouldNotifyOnTextLayout;
|
||||
|
||||
private final YogaMeasureFunction mTextMeasureFunction =
|
||||
new YogaMeasureFunction() {
|
||||
@Override
|
||||
public long measure(
|
||||
YogaNode node,
|
||||
float width,
|
||||
YogaMeasureMode widthMode,
|
||||
float height,
|
||||
YogaMeasureMode heightMode) {
|
||||
Spannable text =
|
||||
Assertions.assertNotNull(
|
||||
mPreparedSpannableText,
|
||||
"Spannable element has not been prepared in onBeforeLayout");
|
||||
|
||||
Layout layout = measureSpannedText(text, width, widthMode);
|
||||
|
||||
if (mAdjustsFontSizeToFit) {
|
||||
int initialFontSize = mTextAttributes.getEffectiveFontSize();
|
||||
int currentFontSize = mTextAttributes.getEffectiveFontSize();
|
||||
// Minimum font size is 4pts to match the iOS implementation.
|
||||
int minimumFontSize =
|
||||
(int) Math.max(mMinimumFontScale * initialFontSize, PixelUtil.toPixelFromDIP(4));
|
||||
while (currentFontSize > minimumFontSize
|
||||
&& (mNumberOfLines != ReactConstants.UNSET && layout.getLineCount() > mNumberOfLines
|
||||
|| heightMode != YogaMeasureMode.UNDEFINED && layout.getHeight() > height)) {
|
||||
// TODO: We could probably use a smarter algorithm here. This will require 0(n)
|
||||
// measurements
|
||||
// based on the number of points the font size needs to be reduced by.
|
||||
currentFontSize -= Math.max(1, (int) PixelUtil.toPixelFromDIP(1));
|
||||
|
||||
float ratio = (float) currentFontSize / (float) initialFontSize;
|
||||
ReactAbsoluteSizeSpan[] sizeSpans =
|
||||
text.getSpans(0, text.length(), ReactAbsoluteSizeSpan.class);
|
||||
for (ReactAbsoluteSizeSpan span : sizeSpans) {
|
||||
text.setSpan(
|
||||
new ReactAbsoluteSizeSpan(
|
||||
(int) Math.max((span.getSize() * ratio), minimumFontSize)),
|
||||
text.getSpanStart(span),
|
||||
text.getSpanEnd(span),
|
||||
text.getSpanFlags(span));
|
||||
text.removeSpan(span);
|
||||
}
|
||||
layout = measureSpannedText(text, width, widthMode);
|
||||
}
|
||||
}
|
||||
|
||||
if (mShouldNotifyOnTextLayout) {
|
||||
ThemedReactContext themedReactContext = getThemedContext();
|
||||
WritableArray lines = FontMetricsUtil.getFontMetrics(text, layout, themedReactContext);
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putArray("lines", lines);
|
||||
if (themedReactContext.hasActiveReactInstance()) {
|
||||
themedReactContext
|
||||
.getJSModule(RCTEventEmitter.class)
|
||||
.receiveEvent(getReactTag(), "topTextLayout", event);
|
||||
} else {
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
"ReactTextShadowNode",
|
||||
new ReactNoCrashSoftException("Cannot get RCTEventEmitter, no CatalystInstance"));
|
||||
}
|
||||
}
|
||||
|
||||
final int lineCount =
|
||||
mNumberOfLines == ReactConstants.UNSET
|
||||
? layout.getLineCount()
|
||||
: Math.min(mNumberOfLines, layout.getLineCount());
|
||||
|
||||
// Instead of using `layout.getWidth()` (which may yield a significantly larger width for
|
||||
// text that is wrapping), compute width using the longest line.
|
||||
float layoutWidth = 0;
|
||||
if (widthMode == YogaMeasureMode.EXACTLY) {
|
||||
layoutWidth = width;
|
||||
} else {
|
||||
for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
|
||||
boolean endsWithNewLine =
|
||||
text.length() > 0 && text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n';
|
||||
float lineWidth =
|
||||
endsWithNewLine ? layout.getLineMax(lineIndex) : layout.getLineWidth(lineIndex);
|
||||
if (lineWidth > layoutWidth) {
|
||||
layoutWidth = lineWidth;
|
||||
}
|
||||
}
|
||||
if (widthMode == YogaMeasureMode.AT_MOST && layoutWidth > width) {
|
||||
layoutWidth = width;
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
|
||||
layoutWidth = (float) Math.ceil(layoutWidth);
|
||||
}
|
||||
float layoutHeight = height;
|
||||
if (heightMode != YogaMeasureMode.EXACTLY) {
|
||||
layoutHeight = layout.getLineBottom(lineCount - 1);
|
||||
if (heightMode == YogaMeasureMode.AT_MOST && layoutHeight > height) {
|
||||
layoutHeight = height;
|
||||
}
|
||||
}
|
||||
|
||||
return YogaMeasureOutput.make(layoutWidth, layoutHeight);
|
||||
}
|
||||
};
|
||||
|
||||
private final YogaBaselineFunction mTextBaselineFunction =
|
||||
new YogaBaselineFunction() {
|
||||
@Override
|
||||
public float baseline(YogaNode node, float width, float height) {
|
||||
Spannable text =
|
||||
Assertions.assertNotNull(
|
||||
mPreparedSpannableText,
|
||||
"Spannable element has not been prepared in onBeforeLayout");
|
||||
|
||||
Layout layout = measureSpannedText(text, width, YogaMeasureMode.EXACTLY);
|
||||
return layout.getLineBaseline(layout.getLineCount() - 1);
|
||||
}
|
||||
};
|
||||
|
||||
public ReactTextShadowNode() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public ReactTextShadowNode(@Nullable ReactTextViewManagerCallback reactTextViewManagerCallback) {
|
||||
super(reactTextViewManagerCallback);
|
||||
initMeasureFunction();
|
||||
}
|
||||
|
||||
private void initMeasureFunction() {
|
||||
if (!isVirtual()) {
|
||||
setMeasureFunction(mTextMeasureFunction);
|
||||
setBaselineFunction(mTextBaselineFunction);
|
||||
}
|
||||
}
|
||||
|
||||
private Layout measureSpannedText(Spannable text, float width, YogaMeasureMode widthMode) {
|
||||
// TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic)
|
||||
TextPaint textPaint = sTextPaintInstance;
|
||||
textPaint.setTextSize(mTextAttributes.getEffectiveFontSize());
|
||||
Layout layout;
|
||||
BoringLayout.Metrics boring = BoringLayout.isBoring(text, textPaint);
|
||||
float desiredWidth = boring == null ? Layout.getDesiredWidth(text, textPaint) : Float.NaN;
|
||||
|
||||
// technically, width should never be negative, but there is currently a bug in
|
||||
boolean unconstrainedWidth = widthMode == YogaMeasureMode.UNDEFINED || width < 0;
|
||||
|
||||
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
|
||||
switch (getTextAlign()) {
|
||||
case Gravity.LEFT:
|
||||
alignment = Layout.Alignment.ALIGN_NORMAL;
|
||||
break;
|
||||
case Gravity.RIGHT:
|
||||
alignment = Layout.Alignment.ALIGN_OPPOSITE;
|
||||
break;
|
||||
case Gravity.CENTER_HORIZONTAL:
|
||||
alignment = Layout.Alignment.ALIGN_CENTER;
|
||||
break;
|
||||
}
|
||||
|
||||
if (boring == null
|
||||
&& (unconstrainedWidth
|
||||
|| (!YogaConstants.isUndefined(desiredWidth) && desiredWidth <= width))) {
|
||||
// Is used when the width is not known and the text is not boring, ie. if it contains
|
||||
// unicode characters.
|
||||
int hintWidth = (int) Math.ceil(desiredWidth);
|
||||
StaticLayout.Builder builder =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth)
|
||||
.setAlignment(alignment)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(mIncludeFontPadding)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder.setJustificationMode(mJustificationMode);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setUseLineSpacingFromFallbacks(true);
|
||||
}
|
||||
layout = builder.build();
|
||||
|
||||
} else if (boring != null && (unconstrainedWidth || boring.width <= width)) {
|
||||
// Is used for single-line, boring text when the width is either unknown or bigger
|
||||
// than the width of the text.
|
||||
layout =
|
||||
BoringLayout.make(
|
||||
text,
|
||||
textPaint,
|
||||
Math.max(boring.width, 0),
|
||||
alignment,
|
||||
1.f,
|
||||
0.f,
|
||||
boring,
|
||||
mIncludeFontPadding);
|
||||
} else {
|
||||
// Is used for multiline, boring text and the width is known.
|
||||
// Android 11+ introduces changes in text width calculation which leads to cases
|
||||
// where the container is measured smaller than text. Math.ceil prevents it
|
||||
// See T136756103 for investigation
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
|
||||
width = (float) Math.ceil(width);
|
||||
}
|
||||
|
||||
StaticLayout.Builder builder =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, (int) width)
|
||||
.setAlignment(alignment)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(mIncludeFontPadding)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder.setJustificationMode(mJustificationMode);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setUseLineSpacingFromFallbacks(true);
|
||||
}
|
||||
layout = builder.build();
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
// Return text alignment according to LTR or RTL style
|
||||
private int getTextAlign() {
|
||||
int textAlign = mTextAlign;
|
||||
if (getLayoutDirection() == YogaDirection.RTL) {
|
||||
if (textAlign == Gravity.RIGHT) {
|
||||
textAlign = Gravity.LEFT;
|
||||
} else if (textAlign == Gravity.LEFT) {
|
||||
textAlign = Gravity.RIGHT;
|
||||
}
|
||||
}
|
||||
return textAlign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeforeLayout(NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) {
|
||||
mPreparedSpannableText =
|
||||
spannedFromShadowNode(
|
||||
this,
|
||||
/* text (e.g. from `value` prop): */ null,
|
||||
/* supportsInlineViews: */ true,
|
||||
nativeViewHierarchyOptimizer);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtualAnchor() {
|
||||
// Text's descendants aren't necessarily all virtual nodes. Text can contain a combination of
|
||||
// virtual and non-virtual (e.g. inline views) nodes. Therefore it's not a virtual anchor
|
||||
// by the doc comment on {@link ReactShadowNode#isVirtualAnchor}.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hoistNativeChildren() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markUpdated() {
|
||||
super.markUpdated();
|
||||
// Telling to Yoga that the node should be remeasured on next layout pass.
|
||||
super.dirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
|
||||
super.onCollectExtraUpdates(uiViewOperationQueue);
|
||||
|
||||
if (mPreparedSpannableText != null) {
|
||||
ReactTextUpdate reactTextUpdate =
|
||||
new ReactTextUpdate(
|
||||
mPreparedSpannableText,
|
||||
ReactConstants.UNSET,
|
||||
mContainsImages,
|
||||
getPadding(Spacing.START),
|
||||
getPadding(Spacing.TOP),
|
||||
getPadding(Spacing.END),
|
||||
getPadding(Spacing.BOTTOM),
|
||||
getTextAlign(),
|
||||
mTextBreakStrategy,
|
||||
mJustificationMode);
|
||||
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "onTextLayout")
|
||||
public void setShouldNotifyOnTextLayout(boolean shouldNotifyOnTextLayout) {
|
||||
mShouldNotifyOnTextLayout = shouldNotifyOnTextLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Iterable<? extends ReactShadowNode> calculateLayoutOnChildren() {
|
||||
// Run flexbox on and return the descendants which are inline views.
|
||||
|
||||
if (mInlineViews == null || mInlineViews.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Spanned text =
|
||||
Assertions.assertNotNull(
|
||||
mPreparedSpannableText, "Spannable element has not been prepared in onBeforeLayout");
|
||||
TextInlineViewPlaceholderSpan[] placeholders =
|
||||
text.getSpans(0, text.length(), TextInlineViewPlaceholderSpan.class);
|
||||
ArrayList<ReactShadowNode> shadowNodes = new ArrayList<>(placeholders.length);
|
||||
|
||||
for (TextInlineViewPlaceholderSpan placeholder : placeholders) {
|
||||
ReactShadowNode child = mInlineViews.get(placeholder.getReactTag());
|
||||
Assertions.assertNotNull(child);
|
||||
child.calculateLayout();
|
||||
shadowNodes.add(child);
|
||||
}
|
||||
|
||||
return shadowNodes;
|
||||
}
|
||||
}
|
||||
-349
@@ -1,349 +0,0 @@
|
||||
/*
|
||||
* 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") // silents "interface RCTEventEmitter : JavaScriptModule' is deprecated."
|
||||
|
||||
package com.facebook.react.views.text
|
||||
|
||||
import android.os.Build
|
||||
import android.text.BoringLayout
|
||||
import android.text.Layout
|
||||
import android.text.Spannable
|
||||
import android.text.Spanned
|
||||
import android.text.StaticLayout
|
||||
import android.text.TextPaint
|
||||
import android.view.Gravity
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReactNoCrashSoftException
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger.logSoftException
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyOptimizer
|
||||
import com.facebook.react.uimanager.PixelUtil
|
||||
import com.facebook.react.uimanager.ReactShadowNode
|
||||
import com.facebook.react.uimanager.Spacing
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue
|
||||
import com.facebook.react.uimanager.annotations.ReactProp
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
import com.facebook.react.views.text.FontMetricsUtil.getFontMetrics
|
||||
import com.facebook.react.views.text.internal.span.ReactAbsoluteSizeSpan
|
||||
import com.facebook.react.views.text.internal.span.TextInlineViewPlaceholderSpan
|
||||
import com.facebook.yoga.YogaBaselineFunction
|
||||
import com.facebook.yoga.YogaConstants
|
||||
import com.facebook.yoga.YogaDirection
|
||||
import com.facebook.yoga.YogaMeasureFunction
|
||||
import com.facebook.yoga.YogaMeasureMode
|
||||
import com.facebook.yoga.YogaMeasureOutput
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* [ReactBaseTextShadowNode] concrete class for anchor `Text` node.
|
||||
*
|
||||
* The class measures text in `<Text>` view and feeds native [TextView] using [Spannable] object
|
||||
* constructed in superclass.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
public class ReactTextShadowNode
|
||||
@JvmOverloads
|
||||
public constructor(reactTextViewManagerCallback: ReactTextViewManagerCallback? = null) :
|
||||
ReactBaseTextShadowNode(reactTextViewManagerCallback) {
|
||||
private var preparedSpannableText: Spannable? = null
|
||||
|
||||
private var shouldNotifyOnTextLayout = false
|
||||
|
||||
private val textMeasureFunction = YogaMeasureFunction { _, width, widthMode, height, heightMode ->
|
||||
val text =
|
||||
requireNotNull(preparedSpannableText) {
|
||||
"Spannable element has not been prepared in onBeforeLayout"
|
||||
}
|
||||
var layout = measureSpannedText(text, width, widthMode)
|
||||
|
||||
if (mAdjustsFontSizeToFit) {
|
||||
val initialFontSize = mTextAttributes.effectiveFontSize
|
||||
var currentFontSize = mTextAttributes.effectiveFontSize
|
||||
// Minimum font size is 4pts to match the iOS implementation.
|
||||
val minimumFontSize =
|
||||
max((mMinimumFontScale * initialFontSize), PixelUtil.toPixelFromDIP(4f)).toInt()
|
||||
while (currentFontSize > minimumFontSize &&
|
||||
(mNumberOfLines != ReactConstants.UNSET && layout.lineCount > mNumberOfLines ||
|
||||
heightMode != YogaMeasureMode.UNDEFINED && layout.height > height)) {
|
||||
// TODO: We could probably use a smarter algorithm here. This will require 0(n)
|
||||
// measurements
|
||||
// based on the number of points the font size needs to be reduced by.
|
||||
currentFontSize -= max(1, PixelUtil.toPixelFromDIP(1f).toInt())
|
||||
|
||||
val ratio = currentFontSize.toFloat() / initialFontSize.toFloat()
|
||||
val sizeSpans = text.getSpans(0, text.length, ReactAbsoluteSizeSpan::class.java)
|
||||
for (span in sizeSpans) {
|
||||
text.setSpan(
|
||||
ReactAbsoluteSizeSpan(
|
||||
max((span.size * ratio).toDouble(), minimumFontSize.toDouble()).toInt()),
|
||||
text.getSpanStart(span),
|
||||
text.getSpanEnd(span),
|
||||
text.getSpanFlags(span))
|
||||
text.removeSpan(span)
|
||||
}
|
||||
layout = measureSpannedText(text, width, widthMode)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldNotifyOnTextLayout) {
|
||||
val themedReactContext = themedContext
|
||||
val lines = getFontMetrics(text, layout, themedReactContext)
|
||||
val event = Arguments.createMap().apply { putArray("lines", lines) }
|
||||
if (themedReactContext.hasActiveReactInstance()) {
|
||||
themedReactContext
|
||||
.getJSModule(RCTEventEmitter::class.java)
|
||||
.receiveEvent(reactTag, "topTextLayout", event)
|
||||
} else {
|
||||
logSoftException(
|
||||
"ReactTextShadowNode",
|
||||
ReactNoCrashSoftException("Cannot get RCTEventEmitter, no CatalystInstance"))
|
||||
}
|
||||
}
|
||||
|
||||
val lineCount =
|
||||
if (mNumberOfLines == ReactConstants.UNSET) layout.lineCount
|
||||
else min(mNumberOfLines.toDouble(), layout.lineCount.toDouble()).toInt()
|
||||
|
||||
// Instead of using `layout.getWidth()` (which may yield a significantly larger width
|
||||
// for
|
||||
// text that is wrapping), compute width using the longest line.
|
||||
var layoutWidth = 0f
|
||||
if (widthMode == YogaMeasureMode.EXACTLY) {
|
||||
layoutWidth = width
|
||||
} else {
|
||||
for (lineIndex in 0 until lineCount) {
|
||||
val endsWithNewLine = text.length > 0 && text[layout.getLineEnd(lineIndex) - 1] == '\n'
|
||||
val lineWidth =
|
||||
if (endsWithNewLine) layout.getLineMax(lineIndex) else layout.getLineWidth(lineIndex)
|
||||
if (lineWidth > layoutWidth) {
|
||||
layoutWidth = lineWidth
|
||||
}
|
||||
}
|
||||
if (widthMode == YogaMeasureMode.AT_MOST && layoutWidth > width) {
|
||||
layoutWidth = width
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
|
||||
layoutWidth = ceil(layoutWidth.toDouble()).toFloat()
|
||||
}
|
||||
var layoutHeight = height
|
||||
if (heightMode != YogaMeasureMode.EXACTLY) {
|
||||
layoutHeight = layout.getLineBottom(lineCount - 1).toFloat()
|
||||
if (heightMode == YogaMeasureMode.AT_MOST && layoutHeight > height) {
|
||||
layoutHeight = height
|
||||
}
|
||||
}
|
||||
YogaMeasureOutput.make(layoutWidth, layoutHeight)
|
||||
}
|
||||
|
||||
private val mTextBaselineFunction = YogaBaselineFunction { node, width, height ->
|
||||
val text =
|
||||
checkNotNull(preparedSpannableText) {
|
||||
"Spannable element has not been prepared in onBeforeLayout"
|
||||
}
|
||||
val layout = measureSpannedText(text, width, YogaMeasureMode.EXACTLY)
|
||||
layout.getLineBaseline(layout.lineCount - 1).toFloat()
|
||||
}
|
||||
|
||||
init {
|
||||
initMeasureFunction()
|
||||
}
|
||||
|
||||
private fun initMeasureFunction() {
|
||||
if (!isVirtual) {
|
||||
setMeasureFunction(textMeasureFunction)
|
||||
setBaselineFunction(mTextBaselineFunction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun measureSpannedText(
|
||||
text: Spannable,
|
||||
width: Float,
|
||||
widthMode: YogaMeasureMode
|
||||
): Layout {
|
||||
// TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic)
|
||||
var width = width
|
||||
val textPaint = textPaintInstance
|
||||
textPaint.textSize = mTextAttributes.effectiveFontSize.toFloat()
|
||||
val layout: Layout
|
||||
val boring = BoringLayout.isBoring(text, textPaint)
|
||||
val desiredWidth = if (boring == null) Layout.getDesiredWidth(text, textPaint) else Float.NaN
|
||||
|
||||
// technically, width should never be negative, but there is currently a bug in
|
||||
val unconstrainedWidth = widthMode == YogaMeasureMode.UNDEFINED || width < 0
|
||||
|
||||
val alignment =
|
||||
when (textAlign) {
|
||||
Gravity.LEFT -> Layout.Alignment.ALIGN_NORMAL
|
||||
Gravity.RIGHT -> Layout.Alignment.ALIGN_OPPOSITE
|
||||
Gravity.CENTER_HORIZONTAL -> Layout.Alignment.ALIGN_CENTER
|
||||
else -> Layout.Alignment.ALIGN_NORMAL
|
||||
}
|
||||
|
||||
if (boring == null &&
|
||||
(unconstrainedWidth ||
|
||||
(!YogaConstants.isUndefined(desiredWidth) && desiredWidth <= width))) {
|
||||
// Is used when the width is not known and the text is not boring, ie. if it contains
|
||||
// unicode characters.
|
||||
val hintWidth = ceil(desiredWidth.toDouble()).toInt()
|
||||
val builder =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length, textPaint, hintWidth)
|
||||
.setAlignment(alignment)
|
||||
.setLineSpacing(0f, 1f)
|
||||
.setIncludePad(mIncludeFontPadding)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder.setJustificationMode(mJustificationMode)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setUseLineSpacingFromFallbacks(true)
|
||||
}
|
||||
layout = builder.build()
|
||||
} else if (boring != null && (unconstrainedWidth || boring.width <= width)) {
|
||||
// Is used for single-line, boring text when the width is either unknown or bigger
|
||||
// than the width of the text.
|
||||
layout =
|
||||
BoringLayout.make(
|
||||
text,
|
||||
textPaint,
|
||||
max(boring.width.toDouble(), 0.0).toInt(),
|
||||
alignment,
|
||||
1f,
|
||||
0f,
|
||||
boring,
|
||||
mIncludeFontPadding)
|
||||
} else {
|
||||
// Is used for multiline, boring text and the width is known.
|
||||
// Android 11+ introduces changes in text width calculation which leads to cases
|
||||
// where the container is measured smaller than text. Math.ceil prevents it
|
||||
// See T136756103 for investigation
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
|
||||
width = ceil(width.toDouble()).toFloat()
|
||||
}
|
||||
|
||||
val builder =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length, textPaint, width.toInt())
|
||||
.setAlignment(alignment)
|
||||
.setLineSpacing(0f, 1f)
|
||||
.setIncludePad(mIncludeFontPadding)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder.setJustificationMode(mJustificationMode)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setUseLineSpacingFromFallbacks(true)
|
||||
}
|
||||
layout = builder.build()
|
||||
}
|
||||
return layout
|
||||
}
|
||||
|
||||
// Return text alignment according to LTR or RTL style
|
||||
private val textAlign: Int
|
||||
get() {
|
||||
var textAlign = mTextAlign
|
||||
if (layoutDirection == YogaDirection.RTL) {
|
||||
if (textAlign == Gravity.RIGHT) {
|
||||
textAlign = Gravity.LEFT
|
||||
} else if (textAlign == Gravity.LEFT) {
|
||||
textAlign = Gravity.RIGHT
|
||||
}
|
||||
}
|
||||
return textAlign
|
||||
}
|
||||
|
||||
override fun onBeforeLayout(nativeViewHierarchyOptimizer: NativeViewHierarchyOptimizer) {
|
||||
preparedSpannableText =
|
||||
spannedFromShadowNode(
|
||||
this, /* text (e.g. from `value` prop): */
|
||||
null, /* supportsInlineViews: */
|
||||
true,
|
||||
nativeViewHierarchyOptimizer)
|
||||
markUpdated()
|
||||
}
|
||||
|
||||
// Text's descendants aren't necessarily all virtual nodes. Text can contain a combination
|
||||
// of
|
||||
// virtual and non-virtual (e.g. inline views) nodes. Therefore it's not a virtual anchor
|
||||
// by the doc comment on [ReactShadowNode.isVirtualAnchor].
|
||||
override fun isVirtualAnchor(): Boolean = false
|
||||
|
||||
override fun hoistNativeChildren(): Boolean = true
|
||||
|
||||
override fun markUpdated() {
|
||||
super.markUpdated()
|
||||
// Telling to Yoga that the node should be remeasured on next layout pass.
|
||||
super.dirty()
|
||||
}
|
||||
|
||||
override fun onCollectExtraUpdates(uiViewOperationQueue: UIViewOperationQueue) {
|
||||
super.onCollectExtraUpdates(uiViewOperationQueue)
|
||||
|
||||
val text = preparedSpannableText ?: return
|
||||
val reactTextUpdate =
|
||||
ReactTextUpdate(
|
||||
text,
|
||||
ReactConstants.UNSET,
|
||||
mContainsImages,
|
||||
getPadding(Spacing.START),
|
||||
getPadding(Spacing.TOP),
|
||||
getPadding(Spacing.END),
|
||||
getPadding(Spacing.BOTTOM),
|
||||
textAlign,
|
||||
mTextBreakStrategy,
|
||||
mJustificationMode)
|
||||
uiViewOperationQueue.enqueueUpdateExtraData(reactTag, reactTextUpdate)
|
||||
}
|
||||
|
||||
@ReactProp(name = "onTextLayout")
|
||||
public fun setShouldNotifyOnTextLayout(shouldNotifyOnTextLayout: Boolean) {
|
||||
this.shouldNotifyOnTextLayout = shouldNotifyOnTextLayout
|
||||
}
|
||||
|
||||
override fun calculateLayoutOnChildren(): Iterable<ReactShadowNode<*>?>? {
|
||||
// Run flexbox on and return the descendants which are inline views.
|
||||
|
||||
if (mInlineViews.isNullOrEmpty()) return null
|
||||
|
||||
val text: Spanned =
|
||||
checkNotNull(preparedSpannableText) {
|
||||
"Spannable element has not been prepared in onBeforeLayout"
|
||||
}
|
||||
val placeholders = text.getSpans(0, text.length, TextInlineViewPlaceholderSpan::class.java)
|
||||
val shadowNodes = mutableListOf<ReactShadowNode<*>?>()
|
||||
|
||||
for (placeholder in placeholders) {
|
||||
val child = mInlineViews?.get(placeholder.reactTag)
|
||||
checkNotNull(child) { "Child is null" }
|
||||
child.calculateLayout()
|
||||
shadowNodes.add(child)
|
||||
}
|
||||
|
||||
return shadowNodes
|
||||
}
|
||||
|
||||
private companion object {
|
||||
// It's important to pass the ANTI_ALIAS_FLAG flag to the constructor rather than setting it
|
||||
// later by calling setFlags. This is because the latter approach triggers a bug on Android
|
||||
// 4.4.2.
|
||||
// The bug is that unicode emoticons aren't measured properly which causes text to be
|
||||
// clipped.
|
||||
private val textPaintInstance = TextPaint(TextPaint.ANTI_ALIAS_FLAG)
|
||||
}
|
||||
}
|
||||
+21
-13
@@ -22,7 +22,7 @@ import com.facebook.react.uimanager.ReactAccessibilityDelegate
|
||||
import com.facebook.react.views.text.internal.span.ReactClickableSpan
|
||||
|
||||
internal class ReactTextViewAccessibilityDelegate : ReactAccessibilityDelegate {
|
||||
constructor(
|
||||
public constructor(
|
||||
view: View,
|
||||
originalFocus: Boolean,
|
||||
originalImportantForAccessibility: Int
|
||||
@@ -32,8 +32,12 @@ internal class ReactTextViewAccessibilityDelegate : ReactAccessibilityDelegate {
|
||||
|
||||
private var accessibilityLinks: AccessibilityLinks? = null
|
||||
|
||||
companion object {
|
||||
fun setDelegate(view: View, originalFocus: Boolean, originalImportantForAccessibility: Int) {
|
||||
public companion object {
|
||||
public fun setDelegate(
|
||||
view: View,
|
||||
originalFocus: Boolean,
|
||||
originalImportantForAccessibility: Int
|
||||
) {
|
||||
// if a view already has an accessibility delegate, replacing it could cause
|
||||
// problems,so leave it alone.
|
||||
if (!ViewCompat.hasAccessibilityDelegate(view) &&
|
||||
@@ -51,7 +55,11 @@ internal class ReactTextViewAccessibilityDelegate : ReactAccessibilityDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fun resetDelegate(view: View, originalFocus: Boolean, originalImportantForAccessibility: Int) {
|
||||
public fun resetDelegate(
|
||||
view: View,
|
||||
originalFocus: Boolean,
|
||||
originalImportantForAccessibility: Int
|
||||
) {
|
||||
ViewCompat.setAccessibilityDelegate(
|
||||
view,
|
||||
ReactTextViewAccessibilityDelegate(
|
||||
@@ -272,7 +280,7 @@ internal class ReactTextViewAccessibilityDelegate : ReactAccessibilityDelegate {
|
||||
return null
|
||||
}
|
||||
|
||||
class AccessibilityLinks(text: Spanned) {
|
||||
public class AccessibilityLinks(text: Spanned) {
|
||||
private val links: List<AccessibleLink>
|
||||
|
||||
init {
|
||||
@@ -298,7 +306,7 @@ internal class ReactTextViewAccessibilityDelegate : ReactAccessibilityDelegate {
|
||||
links = accessibleLinks
|
||||
}
|
||||
|
||||
fun getLinkById(id: Int): AccessibleLink? {
|
||||
public fun getLinkById(id: Int): AccessibleLink? {
|
||||
for (link in links) {
|
||||
if (link.id == id) {
|
||||
return link
|
||||
@@ -308,7 +316,7 @@ internal class ReactTextViewAccessibilityDelegate : ReactAccessibilityDelegate {
|
||||
return null
|
||||
}
|
||||
|
||||
fun getLinkBySpanPos(start: Int, end: Int): AccessibleLink? {
|
||||
public fun getLinkBySpanPos(start: Int, end: Int): AccessibleLink? {
|
||||
for (link in links) {
|
||||
if (link.start == start && link.end == end) {
|
||||
return link
|
||||
@@ -318,15 +326,15 @@ internal class ReactTextViewAccessibilityDelegate : ReactAccessibilityDelegate {
|
||||
return null
|
||||
}
|
||||
|
||||
fun size(): Int {
|
||||
public fun size(): Int {
|
||||
return links.size
|
||||
}
|
||||
|
||||
class AccessibleLink {
|
||||
var description: String? = null
|
||||
var start: Int = 0
|
||||
var end: Int = 0
|
||||
var id: Int = 0
|
||||
public class AccessibleLink {
|
||||
public var description: String? = null
|
||||
public var start: Int = 0
|
||||
public var end: Int = 0
|
||||
public var id: Int = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -175,7 +175,7 @@ public open class ReactViewGroup public constructor(context: Context?) :
|
||||
childrenRemovedWhileTransitioning = null
|
||||
}
|
||||
|
||||
internal open fun recycleView() {
|
||||
internal fun recycleView() {
|
||||
recycleCount++
|
||||
// Remove dangling listeners
|
||||
if (allChildren != null && childrenLayoutChangeListener != null) {
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ internal class ViewGroupClickEvent(surfaceId: Int, viewId: Int) :
|
||||
|
||||
override fun canCoalesce(): Boolean = false
|
||||
|
||||
override fun getEventData(): WritableMap = Arguments.createMap()
|
||||
protected override fun getEventData(): WritableMap = Arguments.createMap()
|
||||
|
||||
private companion object {
|
||||
private const val EVENT_NAME: String = "topClick"
|
||||
|
||||
-349
@@ -1,349 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.virtualview
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewParent
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.R
|
||||
import com.facebook.react.common.build.ReactBuildConfig
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
||||
import com.facebook.react.uimanager.ReactRoot
|
||||
import com.facebook.react.views.scroll.ReactHorizontalScrollView
|
||||
import com.facebook.react.views.scroll.ReactScrollView
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper
|
||||
import com.facebook.react.views.scroll.ScrollEventType
|
||||
import com.facebook.react.views.view.ReactViewGroup
|
||||
import com.facebook.systrace.Systrace
|
||||
|
||||
internal class ReactVirtualView(context: Context) :
|
||||
ReactViewGroup(context),
|
||||
ReactScrollViewHelper.ScrollListener,
|
||||
ReactScrollViewHelper.LayoutChangeListener,
|
||||
View.OnLayoutChangeListener {
|
||||
|
||||
internal var mode: VirtualViewMode? = null
|
||||
internal var modeChangeEmitter: ModeChangeEmitter? = null
|
||||
internal var prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
|
||||
internal val debugLogEnabled: Boolean = ReactNativeFeatureFlags.enableVirtualViewDebugFeatures()
|
||||
|
||||
private var parentScrollView: View? = null
|
||||
|
||||
// preallocate Rects to avoid allocation during layout
|
||||
private val lastRect: Rect = Rect()
|
||||
private val targetRect: Rect = Rect()
|
||||
private val thresholdRect: Rect = Rect()
|
||||
|
||||
/** Cumulative offset of parents' `left` values within the scroll view */
|
||||
private var offsetX: Int = 0
|
||||
/** Cumulative offset of parents' `top` values within the scroll view */
|
||||
private var offsetY: Int = 0
|
||||
private var offsetChanged: Boolean = false
|
||||
|
||||
internal val nativeId: String?
|
||||
get() = getTag(R.id.view_tag_native_id) as? String
|
||||
|
||||
override internal fun recycleView() {
|
||||
ReactScrollViewHelper.removeScrollListener(this)
|
||||
ReactScrollViewHelper.removeLayoutChangeListener(this)
|
||||
cleanupLayoutListeners()
|
||||
mode = null
|
||||
modeChangeEmitter = null
|
||||
lastRect.setEmpty()
|
||||
parentScrollView = null
|
||||
offsetX = 0
|
||||
offsetY = 0
|
||||
offsetChanged = false
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
doAttachedToWindow()
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun doAttachedToWindow() {
|
||||
parentScrollView =
|
||||
getParentScrollView()?.also {
|
||||
offsetChanged = true
|
||||
ReactScrollViewHelper.addScrollListener(this)
|
||||
ReactScrollViewHelper.addLayoutChangeListener(this)
|
||||
}
|
||||
debugLog("onAttachedToWindow")
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
ReactScrollViewHelper.removeScrollListener(this)
|
||||
ReactScrollViewHelper.removeLayoutChangeListener(this)
|
||||
cleanupLayoutListeners()
|
||||
}
|
||||
|
||||
/** From [View#onLayout] */
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
if (changed) {
|
||||
offsetChanged = true
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLayoutChange(
|
||||
v: View?,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int
|
||||
) {
|
||||
offsetChanged = offsetChanged || oldLeft != left || oldTop != top
|
||||
dispatchOnModeChangeIfNeeded(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* From ReactScrollViewHelper#onScroll, triggered by [ReactScrollView] and
|
||||
* [ReactHorizontalScrollView]
|
||||
*/
|
||||
override fun onScroll(
|
||||
scrollView: ViewGroup?,
|
||||
scrollEventType: ScrollEventType?,
|
||||
xVelocity: Float,
|
||||
yVelocity: Float
|
||||
) {
|
||||
if (scrollView == parentScrollView) {
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From ReactScrollViewHelper#onLayout, triggered by [ReactScrollView] and
|
||||
* [ReactHorizontalScrollView]
|
||||
*/
|
||||
override fun onLayout(scrollView: ViewGroup?) {
|
||||
if (scrollView == parentScrollView) {
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From ReactScrollViewHelper#onLayoutChange, triggered by [ReactScrollView] and
|
||||
* [ReactHorizontalScrollView]
|
||||
*/
|
||||
override fun onLayoutChange(scrollView: ViewGroup) {
|
||||
if (scrollView == parentScrollView) {
|
||||
offsetChanged = true
|
||||
dispatchOnModeChangeIfNeeded(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dispatchOnModeChangeIfNeeded(checkRectChange: Boolean) {
|
||||
modeChangeEmitter ?: return
|
||||
val scrollView = parentScrollView ?: return
|
||||
|
||||
if (offsetChanged) {
|
||||
updateParentOffset()
|
||||
}
|
||||
targetRect.set(
|
||||
left + offsetX,
|
||||
top + offsetY,
|
||||
right + offsetX,
|
||||
bottom + offsetY,
|
||||
)
|
||||
scrollView.getDrawingRect(thresholdRect)
|
||||
|
||||
// TODO: Validate whether this is still the case and whether these checks are still needed.
|
||||
// updateRects will initially get called before the targetRect has any dimensions set, so if
|
||||
// it's both zero width and height, we need to skip dispatching an incorrect mode change.
|
||||
// The correct mode change will be dispatched later. We can't use targetRect.isEmpty because it
|
||||
// will return true if either there's a zero width or height, but that case is valid.
|
||||
if ((targetRect.width() == 0 && targetRect.height() == 0) || thresholdRect.isEmpty) {
|
||||
debugLog("dispatchOnModeChangeIfNeeded") {
|
||||
"empty rects target=${targetRect.toShortString()} threshold=${thresholdRect.toShortString()}"
|
||||
}
|
||||
return
|
||||
}
|
||||
if (checkRectChange) {
|
||||
if (!lastRect.isEmpty && lastRect == targetRect) {
|
||||
debugLog("dispatchOnModeChangeIfNeeded") { "no rect change" }
|
||||
return
|
||||
}
|
||||
lastRect.set(targetRect)
|
||||
}
|
||||
|
||||
val newMode: VirtualViewMode
|
||||
if (rectsOverlap(targetRect, thresholdRect)) {
|
||||
newMode = VirtualViewMode.Visible
|
||||
} else {
|
||||
var prerender = false
|
||||
if (prerenderRatio > 0.0) {
|
||||
thresholdRect.inset(
|
||||
(-thresholdRect.width() * prerenderRatio).toInt(),
|
||||
(-thresholdRect.height() * prerenderRatio).toInt())
|
||||
prerender = rectsOverlap(targetRect, thresholdRect)
|
||||
}
|
||||
if (prerender) {
|
||||
newMode = VirtualViewMode.Prerender
|
||||
} else {
|
||||
newMode = VirtualViewMode.Hidden
|
||||
thresholdRect.setEmpty()
|
||||
}
|
||||
}
|
||||
debugLog("dispatchOnModeChangeIfNeeded") {
|
||||
"mode=$mode target=${targetRect.toShortString()} threshold=${thresholdRect.toShortString()}"
|
||||
}
|
||||
|
||||
if (newMode == mode) {
|
||||
return
|
||||
}
|
||||
val oldMode = mode
|
||||
mode = newMode
|
||||
maybeEmitModeChanges(oldMode, newMode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether one Rect overlaps with another Rect.
|
||||
*
|
||||
* This is different from [Rect.intersects] because a Rect representing a line or a point is
|
||||
* considered to overlap with another Rect if the line or point is within the rect bounds.
|
||||
* However, two Rects are not considered to overlap if they only share a boundary.
|
||||
*/
|
||||
private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
|
||||
if (rect1.top >= rect2.bottom || rect2.top >= rect1.bottom) {
|
||||
// No overlap on the y-axis.
|
||||
return false
|
||||
}
|
||||
if (rect1.left >= rect2.right || rect2.left >= rect1.right) {
|
||||
// No overlap on the x-axis.
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the mode change and emit 0, 1, or 2 mode change events depending on the type of
|
||||
* transition, [noActivity], and [asyncPrerender]
|
||||
*/
|
||||
private fun maybeEmitModeChanges(
|
||||
oldMode: VirtualViewMode?,
|
||||
newMode: VirtualViewMode,
|
||||
) {
|
||||
debugLog("Mode change") { "$oldMode->$newMode" }
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT,
|
||||
"VirtualView::mode change $oldMode -> $newMode, nativeID=$nativeId")
|
||||
when (newMode) {
|
||||
VirtualViewMode.Visible -> {
|
||||
emitSyncModeChange(VirtualViewMode.Visible)
|
||||
}
|
||||
VirtualViewMode.Prerender -> {
|
||||
if (oldMode != VirtualViewMode.Visible) {
|
||||
emitAsyncModeChange(VirtualViewMode.Prerender)
|
||||
}
|
||||
}
|
||||
VirtualViewMode.Hidden -> {
|
||||
emitAsyncModeChange(VirtualViewMode.Hidden)
|
||||
}
|
||||
}
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
|
||||
private fun emitAsyncModeChange(mode: VirtualViewMode) {
|
||||
modeChangeEmitter?.emitModeChange(mode, targetRect, thresholdRect, synchronous = false)
|
||||
}
|
||||
|
||||
private fun emitSyncModeChange(mode: VirtualViewMode) {
|
||||
modeChangeEmitter?.emitModeChange(mode, targetRect, thresholdRect, synchronous = true)
|
||||
}
|
||||
|
||||
private fun getParentScrollView(): ViewGroup? = traverseParentStack(true)
|
||||
|
||||
private fun cleanupLayoutListeners() {
|
||||
traverseParentStack(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate up through the view hierarchy until we reach the scroll view or root view, and
|
||||
* maintain layout change listeners on any intermediate views.
|
||||
*
|
||||
* @param addListeners Whether to call [View.addOnLayoutChangeListener] to views in the hierarchy.
|
||||
* If false, existing listeners will be removed.
|
||||
*/
|
||||
private fun traverseParentStack(addListeners: Boolean): ViewGroup? {
|
||||
var parent: ViewParent? = parent
|
||||
while (parent != null) {
|
||||
if (parent is ReactScrollView) {
|
||||
return parent
|
||||
}
|
||||
if (parent is ReactHorizontalScrollView) {
|
||||
return parent
|
||||
}
|
||||
if (parent is ReactRoot) {
|
||||
// don't look past the root - it could traverse into a separate hierarchy
|
||||
return null
|
||||
}
|
||||
if (parent is View) {
|
||||
// always remove, to ensure listeners aren't added more than once
|
||||
parent.removeOnLayoutChangeListener(this)
|
||||
if (addListeners) {
|
||||
parent.addOnLayoutChangeListener(this)
|
||||
}
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** Navigate up the view hierarchy to record parents' offsets within the scroll view */
|
||||
private fun updateParentOffset() {
|
||||
val scrollView = parentScrollView ?: return
|
||||
offsetX = 0
|
||||
offsetY = 0
|
||||
offsetChanged = false
|
||||
var parent: ViewParent? = parent
|
||||
while (parent != null && parent != scrollView) {
|
||||
if (parent is View) {
|
||||
offsetX += parent.left
|
||||
offsetY += parent.top
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun debugLog(subtag: String, block: () -> String = { "" }) {
|
||||
if (debugLogEnabled) {
|
||||
if (IS_DEBUG_BUILD) {
|
||||
FLog.d("$DEBUG_TAG:$subtag", "${block()} [$id][$nativeId]")
|
||||
} else {
|
||||
// production builds only log warnings/errors
|
||||
FLog.w("$DEBUG_TAG:$subtag", "${block()} [$id][$nativeId]")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun interface ModeChangeEmitter {
|
||||
fun emitModeChange(
|
||||
mode: VirtualViewMode,
|
||||
targetRect: Rect,
|
||||
thresholdRect: Rect,
|
||||
synchronous: Boolean,
|
||||
)
|
||||
}
|
||||
|
||||
private const val DEBUG_TAG: String = "ReactVirtualView"
|
||||
|
||||
private val IS_DEBUG_BUILD =
|
||||
ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD || ReactBuildConfig.ENABLE_PERFETTO
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.virtualview
|
||||
|
||||
import android.graphics.Rect
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.facebook.react.module.annotations.ReactModule
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.UIManagerHelper
|
||||
import com.facebook.react.uimanager.ViewGroupManager
|
||||
import com.facebook.react.uimanager.ViewManagerDelegate
|
||||
import com.facebook.react.uimanager.annotations.ReactProp
|
||||
import com.facebook.react.uimanager.events.EventDispatcher
|
||||
import com.facebook.react.viewmanagers.VirtualViewManagerDelegate
|
||||
import com.facebook.react.viewmanagers.VirtualViewManagerInterface
|
||||
|
||||
@ReactModule(name = ReactVirtualViewManager.REACT_CLASS)
|
||||
internal class ReactVirtualViewManager :
|
||||
ViewGroupManager<ReactVirtualView>(), VirtualViewManagerInterface<ReactVirtualView> {
|
||||
|
||||
private val _delegate = VirtualViewManagerDelegate(this)
|
||||
|
||||
override fun getDelegate(): ViewManagerDelegate<ReactVirtualView> = _delegate
|
||||
|
||||
override fun getName(): String = REACT_CLASS
|
||||
|
||||
override fun createViewInstance(reactContext: ThemedReactContext): ReactVirtualView =
|
||||
ReactVirtualView(reactContext)
|
||||
|
||||
@ReactProp(name = "initialHidden")
|
||||
override fun setInitialHidden(view: ReactVirtualView, value: Boolean) {
|
||||
if (view.mode == null) {
|
||||
view.mode = if (value) VirtualViewMode.Hidden else VirtualViewMode.Visible
|
||||
}
|
||||
}
|
||||
|
||||
override fun setNativeId(view: ReactVirtualView, nativeId: String?) {
|
||||
super.setNativeId(view, nativeId)
|
||||
view.debugLog("setNativeId") { "${view.id}" }
|
||||
}
|
||||
|
||||
override fun addEventEmitters(reactContext: ThemedReactContext, view: ReactVirtualView) {
|
||||
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id) ?: return
|
||||
view.modeChangeEmitter =
|
||||
VirtualViewEventEmitter(view.id, UIManagerHelper.getSurfaceId(reactContext), dispatcher)
|
||||
}
|
||||
|
||||
override fun prepareToRecycleView(
|
||||
reactContext: ThemedReactContext,
|
||||
view: ReactVirtualView,
|
||||
): ReactVirtualView? {
|
||||
view.recycleView()
|
||||
return super.prepareToRecycleView(reactContext, view)
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public const val REACT_CLASS: String = "VirtualView"
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal class VirtualViewEventEmitter(
|
||||
private val viewId: Int,
|
||||
private val surfaceId: Int,
|
||||
private val dispatcher: EventDispatcher
|
||||
) : ModeChangeEmitter {
|
||||
override fun emitModeChange(
|
||||
mode: VirtualViewMode,
|
||||
targetRect: Rect,
|
||||
thresholdRect: Rect,
|
||||
synchronous: Boolean,
|
||||
) {
|
||||
dispatcher.dispatchEvent(
|
||||
VirtualViewModeChangeEvent(
|
||||
surfaceId,
|
||||
viewId,
|
||||
mode,
|
||||
targetRect,
|
||||
thresholdRect,
|
||||
synchronous,
|
||||
))
|
||||
}
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.virtualview
|
||||
|
||||
internal enum class VirtualViewMode(val value: Int) {
|
||||
Visible(0),
|
||||
Prerender(1),
|
||||
Hidden(2),
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.virtualview
|
||||
|
||||
import android.graphics.Rect
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.uimanager.PixelUtil.pxToDp
|
||||
import com.facebook.react.uimanager.events.Event
|
||||
|
||||
internal class VirtualViewModeChangeEvent(
|
||||
surfaceId: Int,
|
||||
viewTag: Int,
|
||||
private val mode: VirtualViewMode,
|
||||
targetRect: Rect,
|
||||
thresholdRect: Rect,
|
||||
private val synchronous: Boolean,
|
||||
) : Event<VirtualViewModeChangeEvent>(surfaceId, viewTag) {
|
||||
private val targetRectAsMap: ReadableMap = targetRect.toReadableMap()
|
||||
private val thresholdRectAsMap: ReadableMap = thresholdRect.toReadableMap()
|
||||
|
||||
override fun getEventName(): String = "modeChange"
|
||||
|
||||
@VisibleForTesting
|
||||
public override fun getEventData(): WritableMap {
|
||||
return Arguments.createMap().apply {
|
||||
putInt("mode", mode.value)
|
||||
putMap("targetRect", targetRectAsMap)
|
||||
putMap("thresholdRect", thresholdRectAsMap)
|
||||
}
|
||||
}
|
||||
|
||||
override fun experimental_isSynchronous(): Boolean = synchronous
|
||||
}
|
||||
|
||||
private fun Rect.toReadableMap(): ReadableMap =
|
||||
Arguments.createMap().apply {
|
||||
putDouble("x", left.toFloat().pxToDp().toDouble())
|
||||
putDouble("y", top.toFloat().pxToDp().toDouble())
|
||||
putDouble("width", width().toFloat().pxToDp().toDouble())
|
||||
putDouble("height", height().toFloat().pxToDp().toDouble())
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import kotlin.text.StringBuilder
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
internal object Systrace {
|
||||
|
||||
const val TRACE_TAG_REACT: Long = 0L
|
||||
public const val TRACE_TAG_REACT: Long = 0L
|
||||
|
||||
@JvmStatic fun registerListener(listener: TraceListener?): Unit = Unit
|
||||
|
||||
|
||||
-3
@@ -24,7 +24,6 @@
|
||||
#include <react/renderer/components/text/TextComponentDescriptor.h>
|
||||
#include <react/renderer/components/view/LayoutConformanceComponentDescriptor.h>
|
||||
#include <react/renderer/components/view/ViewComponentDescriptor.h>
|
||||
#include <react/renderer/components/virtualview/VirtualViewComponentDescriptor.h>
|
||||
|
||||
namespace facebook::react::CoreComponentsRegistry {
|
||||
|
||||
@@ -70,8 +69,6 @@ sharedProviderRegistry() {
|
||||
DebuggingOverlayComponentDescriptor>());
|
||||
providerRegistry->add(concreteComponentDescriptorProvider<
|
||||
LayoutConformanceComponentDescriptor>());
|
||||
providerRegistry->add(
|
||||
concreteComponentDescriptorProvider<VirtualViewComponentDescriptor>());
|
||||
|
||||
return providerRegistry;
|
||||
}();
|
||||
|
||||
+15
-1
@@ -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<<b8d441d64e8e04ebbfe611edb4a96b97>>
|
||||
* @generated SignedSource<<b9aef65ab7a1dffc1646519619dedc58>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -165,6 +165,12 @@ class ReactNativeFeatureFlagsJavaProvider
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool enableIntersectionObserverEventLoopIntegration() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableIntersectionObserverEventLoopIntegration");
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool enableLayoutAnimationsOnAndroid() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableLayoutAnimationsOnAndroid");
|
||||
@@ -460,6 +466,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox(
|
||||
return ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::enableIntersectionObserverEventLoopIntegration(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::enableLayoutAnimationsOnAndroid(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid();
|
||||
@@ -709,6 +720,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
|
||||
makeNativeMethod(
|
||||
"enableIOSViewClipToPaddingBox",
|
||||
JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox),
|
||||
makeNativeMethod(
|
||||
"enableIntersectionObserverEventLoopIntegration",
|
||||
JReactNativeFeatureFlagsCxxInterop::enableIntersectionObserverEventLoopIntegration),
|
||||
makeNativeMethod(
|
||||
"enableLayoutAnimationsOnAndroid",
|
||||
JReactNativeFeatureFlagsCxxInterop::enableLayoutAnimationsOnAndroid),
|
||||
|
||||
+4
-1
@@ -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<<30859b9c649e432263ff53fa04890837>>
|
||||
* @generated SignedSource<<f2c3bfb76a9efd7d5bc6c01ab1cccb2c>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -93,6 +93,9 @@ class JReactNativeFeatureFlagsCxxInterop
|
||||
static bool enableIOSViewClipToPaddingBox(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool enableIntersectionObserverEventLoopIntegration(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool enableLayoutAnimationsOnAndroid(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
|
||||
-17
@@ -93,21 +93,4 @@ class BaseViewManagerTest {
|
||||
view.onFocusChangeListener.onFocusChange(view, true)
|
||||
verify(originalFocusListener, times(1)).onFocusChange(view, true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDroppingViewInstanceRestoresFocusChangeListener() {
|
||||
val originalFocusListener = mock<OnFocusChangeListener>()
|
||||
view.onFocusChangeListener = originalFocusListener
|
||||
viewManager.addEventEmitters(themedReactContext, view)
|
||||
Assertions.assertThat(view.onFocusChangeListener).isNotEqualTo(originalFocusListener)
|
||||
|
||||
view.onFocusChangeListener.onFocusChange(view, true)
|
||||
verify(originalFocusListener, times(1)).onFocusChange(view, true)
|
||||
Assertions.assertThat(originalFocusListener).isNotEqualTo(view.onFocusChangeListener)
|
||||
|
||||
viewManager.onDropViewInstance(view)
|
||||
view.onFocusChangeListener.onFocusChange(view, true)
|
||||
verify(originalFocusListener, times(2)).onFocusChange(view, true)
|
||||
Assertions.assertThat(originalFocusListener).isEqualTo(view.onFocusChangeListener)
|
||||
}
|
||||
}
|
||||
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.virtualview
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.util.DisplayMetrics
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.JavaOnlyMap
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder
|
||||
import com.facebook.react.uimanager.events.Event
|
||||
import com.facebook.react.uimanager.events.EventDispatcher
|
||||
import com.facebook.react.views.scroll.ReactScrollView
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.mockStatic
|
||||
import org.mockito.kotlin.argumentCaptor
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.times
|
||||
import org.mockito.kotlin.verify
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
/** Tests [ReactVirtualView] */
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ReactVirtualViewTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
ReactNativeFeatureFlagsForTests.setUp()
|
||||
|
||||
context = Robolectric.buildActivity(Activity::class.java).create().get()
|
||||
|
||||
val arguments = mockStatic(Arguments::class.java)
|
||||
arguments.`when`<WritableMap> { Arguments.createMap() }.thenAnswer { JavaOnlyMap() }
|
||||
|
||||
val displayMetricsHolder = mockStatic(DisplayMetricsHolder::class.java)
|
||||
displayMetricsHolder
|
||||
.`when`<DisplayMetrics> { DisplayMetricsHolder.getWindowDisplayMetrics() }
|
||||
.thenAnswer { DisplayMetrics().apply { density = 1f } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure mode change from visible to hidden`() {
|
||||
val dispatcher = mock<EventDispatcher>()
|
||||
val view = ReactVirtualView(context)
|
||||
view.layout(0, 0, 100, 50)
|
||||
view.modeChangeEmitter = VirtualViewEventEmitter(1, 1, dispatcher)
|
||||
val newScrollView = TestScrollView(context)
|
||||
newScrollView.addView(view)
|
||||
|
||||
// initial layout with visible view
|
||||
newScrollView.drawingRect.set(0, 0, 100, 200)
|
||||
// trigger first event
|
||||
view.doAttachedToWindow()
|
||||
|
||||
// "scroll" to make view "hidden"
|
||||
// Set offset >= prerenderRatio (default 5) * newScrollView.height + height of view
|
||||
// Note: boundaries are not considered in overlapping
|
||||
newScrollView.drawingRect.offsetTo(0, 1050)
|
||||
// trigger second event
|
||||
view.onScroll(newScrollView, null, 0f, 0f)
|
||||
|
||||
// change view rect to verify this does not affect previous events
|
||||
view.layout(0, 20, 100, 40)
|
||||
|
||||
argumentCaptor<Event<*>>().apply {
|
||||
verify(dispatcher, times(2)).dispatchEvent(capture())
|
||||
verifyEvent(
|
||||
allValues[0],
|
||||
expectedMode = VirtualViewMode.Visible,
|
||||
expectedTargetY = 0,
|
||||
expectedTargetHeight = 50,
|
||||
expectedThresholdY = 0,
|
||||
expectedThresholdHeight = 200)
|
||||
verifyEvent(
|
||||
allValues[1],
|
||||
expectedMode = VirtualViewMode.Hidden,
|
||||
expectedTargetY = 0,
|
||||
expectedTargetHeight = 50,
|
||||
expectedThresholdY = 0,
|
||||
expectedThresholdHeight = 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyEvent(
|
||||
event: Event<*>,
|
||||
expectedMode: VirtualViewMode,
|
||||
expectedTargetY: Int,
|
||||
expectedTargetHeight: Int,
|
||||
expectedThresholdY: Int,
|
||||
expectedThresholdHeight: Int,
|
||||
) {
|
||||
val modeChangeEvent = event as VirtualViewModeChangeEvent
|
||||
val mode = checkNotNull(modeChangeEvent.getEventData().getInt("mode"))
|
||||
assertThat(mode).isEqualTo(expectedMode.value)
|
||||
val targetRect = checkNotNull(modeChangeEvent.getEventData().getMap("targetRect"))
|
||||
assertThat(targetRect.getDouble("y").toInt()).isEqualTo(expectedTargetY)
|
||||
assertThat(targetRect.getDouble("height").toInt()).isEqualTo(expectedTargetHeight)
|
||||
val thresholdRect = checkNotNull(modeChangeEvent.getEventData().getMap("thresholdRect"))
|
||||
assertThat(thresholdRect.getDouble("y").toInt()).isEqualTo(expectedThresholdY)
|
||||
assertThat(thresholdRect.getDouble("height").toInt()).isEqualTo(expectedThresholdHeight)
|
||||
}
|
||||
}
|
||||
|
||||
private class TestScrollView(context: Context) : ReactScrollView(context) {
|
||||
val drawingRect: Rect = Rect()
|
||||
|
||||
/** Used by [ReactVirtualView.updateRects] to determine the parent rect. */
|
||||
override fun getDrawingRect(outRect: Rect?) {
|
||||
outRect?.set(drawingRect)
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ Pod::Spec.new do |s|
|
||||
end
|
||||
|
||||
ss.subspec "modal" do |sss|
|
||||
sss.source_files = "react/renderer/components/modal/*.{m,mm,cpp,h}"
|
||||
sss.source_files = "react/renderer/components/modal/**/*.{m,mm,cpp,h}"
|
||||
sss.exclude_files = "react/renderer/components/modal/tests"
|
||||
sss.header_dir = "react/renderer/components/modal"
|
||||
end
|
||||
@@ -130,12 +130,6 @@ Pod::Spec.new do |s|
|
||||
sss.header_dir = "react/renderer/components/unimplementedview"
|
||||
end
|
||||
|
||||
ss.subspec "virtualview" do |sss|
|
||||
sss.source_files = "react/renderer/components/virtualview/**/*.{m,mm,cpp,h}"
|
||||
sss.exclude_files = "react/renderer/components/virtualview/tests"
|
||||
sss.header_dir = "react/renderer/components/virtualview"
|
||||
end
|
||||
|
||||
# Legacy header paths for backwards compat
|
||||
ss.subspec "rncore" do |sss|
|
||||
sss.source_files = "react/renderer/components/rncore/**/*.h"
|
||||
|
||||
@@ -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<<6b6a9adef957abb86845ab3720fd8968>>
|
||||
* @generated SignedSource<<f0d437a484030567e820239e1b854a51>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -110,6 +110,10 @@ bool ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox() {
|
||||
return getAccessor().enableIOSViewClipToPaddingBox();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration() {
|
||||
return getAccessor().enableIntersectionObserverEventLoopIntegration();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid() {
|
||||
return getAccessor().enableLayoutAnimationsOnAndroid();
|
||||
}
|
||||
|
||||
@@ -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<<c80472950cb864b029873b4e64acc976>>
|
||||
* @generated SignedSource<<8112c62255be23bde3fc558953dc8b67>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -144,6 +144,11 @@ class ReactNativeFeatureFlags {
|
||||
*/
|
||||
RN_EXPORT static bool enableIOSViewClipToPaddingBox();
|
||||
|
||||
/**
|
||||
* Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.
|
||||
*/
|
||||
RN_EXPORT static bool enableIntersectionObserverEventLoopIntegration();
|
||||
|
||||
/**
|
||||
* When enabled, LayoutAnimations API will animate state changes on Android.
|
||||
*/
|
||||
|
||||
+50
-32
@@ -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<<4e10fb6339175cd4566f796fcdd1ae3b>>
|
||||
* @generated SignedSource<<4ccd3294168a0c4f5d9916368db06d62>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -407,6 +407,24 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSViewClipToPaddingBox() {
|
||||
return flagValue.value();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlagsAccessor::enableIntersectionObserverEventLoopIntegration() {
|
||||
auto flagValue = enableIntersectionObserverEventLoopIntegration_.load();
|
||||
|
||||
if (!flagValue.has_value()) {
|
||||
// This block is not exclusive but it is not necessary.
|
||||
// If multiple threads try to initialize the feature flag, we would only
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(21, "enableIntersectionObserverEventLoopIntegration");
|
||||
|
||||
flagValue = currentProvider_->enableIntersectionObserverEventLoopIntegration();
|
||||
enableIntersectionObserverEventLoopIntegration_ = flagValue;
|
||||
}
|
||||
|
||||
return flagValue.value();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() {
|
||||
auto flagValue = enableLayoutAnimationsOnAndroid_.load();
|
||||
|
||||
@@ -416,7 +434,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(21, "enableLayoutAnimationsOnAndroid");
|
||||
markFlagAsAccessed(22, "enableLayoutAnimationsOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->enableLayoutAnimationsOnAndroid();
|
||||
enableLayoutAnimationsOnAndroid_ = flagValue;
|
||||
@@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnIOS() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(22, "enableLayoutAnimationsOnIOS");
|
||||
markFlagAsAccessed(23, "enableLayoutAnimationsOnIOS");
|
||||
|
||||
flagValue = currentProvider_->enableLayoutAnimationsOnIOS();
|
||||
enableLayoutAnimationsOnIOS_ = flagValue;
|
||||
@@ -452,7 +470,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMainQueueCoordinatorOnIOS() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(23, "enableMainQueueCoordinatorOnIOS");
|
||||
markFlagAsAccessed(24, "enableMainQueueCoordinatorOnIOS");
|
||||
|
||||
flagValue = currentProvider_->enableMainQueueCoordinatorOnIOS();
|
||||
enableMainQueueCoordinatorOnIOS_ = flagValue;
|
||||
@@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMainQueueModulesOnIOS() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(24, "enableMainQueueModulesOnIOS");
|
||||
markFlagAsAccessed(25, "enableMainQueueModulesOnIOS");
|
||||
|
||||
flagValue = currentProvider_->enableMainQueueModulesOnIOS();
|
||||
enableMainQueueModulesOnIOS_ = flagValue;
|
||||
@@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enableModuleArgumentNSNullConversionIOS()
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(25, "enableModuleArgumentNSNullConversionIOS");
|
||||
markFlagAsAccessed(26, "enableModuleArgumentNSNullConversionIOS");
|
||||
|
||||
flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS();
|
||||
enableModuleArgumentNSNullConversionIOS_ = flagValue;
|
||||
@@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(26, "enableNativeCSSParsing");
|
||||
markFlagAsAccessed(27, "enableNativeCSSParsing");
|
||||
|
||||
flagValue = currentProvider_->enableNativeCSSParsing();
|
||||
enableNativeCSSParsing_ = flagValue;
|
||||
@@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(27, "enableNetworkEventReporting");
|
||||
markFlagAsAccessed(28, "enableNetworkEventReporting");
|
||||
|
||||
flagValue = currentProvider_->enableNetworkEventReporting();
|
||||
enableNetworkEventReporting_ = flagValue;
|
||||
@@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNewBackgroundAndBorderDrawables() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(28, "enableNewBackgroundAndBorderDrawables");
|
||||
markFlagAsAccessed(29, "enableNewBackgroundAndBorderDrawables");
|
||||
|
||||
flagValue = currentProvider_->enableNewBackgroundAndBorderDrawables();
|
||||
enableNewBackgroundAndBorderDrawables_ = flagValue;
|
||||
@@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(29, "enablePreparedTextLayout");
|
||||
markFlagAsAccessed(30, "enablePreparedTextLayout");
|
||||
|
||||
flagValue = currentProvider_->enablePreparedTextLayout();
|
||||
enablePreparedTextLayout_ = flagValue;
|
||||
@@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(30, "enablePropsUpdateReconciliationAndroid");
|
||||
markFlagAsAccessed(31, "enablePropsUpdateReconciliationAndroid");
|
||||
|
||||
flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid();
|
||||
enablePropsUpdateReconciliationAndroid_ = flagValue;
|
||||
@@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResourceTimingAPI() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(31, "enableResourceTimingAPI");
|
||||
markFlagAsAccessed(32, "enableResourceTimingAPI");
|
||||
|
||||
flagValue = currentProvider_->enableResourceTimingAPI();
|
||||
enableResourceTimingAPI_ = flagValue;
|
||||
@@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSynchronousStateUpdates() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(32, "enableSynchronousStateUpdates");
|
||||
markFlagAsAccessed(33, "enableSynchronousStateUpdates");
|
||||
|
||||
flagValue = currentProvider_->enableSynchronousStateUpdates();
|
||||
enableSynchronousStateUpdates_ = flagValue;
|
||||
@@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(33, "enableViewCulling");
|
||||
markFlagAsAccessed(34, "enableViewCulling");
|
||||
|
||||
flagValue = currentProvider_->enableViewCulling();
|
||||
enableViewCulling_ = flagValue;
|
||||
@@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(34, "enableViewRecycling");
|
||||
markFlagAsAccessed(35, "enableViewRecycling");
|
||||
|
||||
flagValue = currentProvider_->enableViewRecycling();
|
||||
enableViewRecycling_ = flagValue;
|
||||
@@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(35, "enableViewRecyclingForText");
|
||||
markFlagAsAccessed(36, "enableViewRecyclingForText");
|
||||
|
||||
flagValue = currentProvider_->enableViewRecyclingForText();
|
||||
enableViewRecyclingForText_ = flagValue;
|
||||
@@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(36, "enableViewRecyclingForView");
|
||||
markFlagAsAccessed(37, "enableViewRecyclingForView");
|
||||
|
||||
flagValue = currentProvider_->enableViewRecyclingForView();
|
||||
enableViewRecyclingForView_ = flagValue;
|
||||
@@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewDebugFeatures() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(37, "enableVirtualViewDebugFeatures");
|
||||
markFlagAsAccessed(38, "enableVirtualViewDebugFeatures");
|
||||
|
||||
flagValue = currentProvider_->enableVirtualViewDebugFeatures();
|
||||
enableVirtualViewDebugFeatures_ = flagValue;
|
||||
@@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(38, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
|
||||
markFlagAsAccessed(39, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
|
||||
|
||||
flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact();
|
||||
fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue;
|
||||
@@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(39, "fuseboxEnabledRelease");
|
||||
markFlagAsAccessed(40, "fuseboxEnabledRelease");
|
||||
|
||||
flagValue = currentProvider_->fuseboxEnabledRelease();
|
||||
fuseboxEnabledRelease_ = flagValue;
|
||||
@@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(40, "fuseboxNetworkInspectionEnabled");
|
||||
markFlagAsAccessed(41, "fuseboxNetworkInspectionEnabled");
|
||||
|
||||
flagValue = currentProvider_->fuseboxNetworkInspectionEnabled();
|
||||
fuseboxNetworkInspectionEnabled_ = flagValue;
|
||||
@@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(41, "traceTurboModulePromiseRejectionsOnAndroid");
|
||||
markFlagAsAccessed(42, "traceTurboModulePromiseRejectionsOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid();
|
||||
traceTurboModulePromiseRejectionsOnAndroid_ = flagValue;
|
||||
@@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit(
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(42, "updateRuntimeShadowNodeReferencesOnCommit");
|
||||
markFlagAsAccessed(43, "updateRuntimeShadowNodeReferencesOnCommit");
|
||||
|
||||
flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit();
|
||||
updateRuntimeShadowNodeReferencesOnCommit_ = flagValue;
|
||||
@@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(43, "useAlwaysAvailableJSErrorHandling");
|
||||
markFlagAsAccessed(44, "useAlwaysAvailableJSErrorHandling");
|
||||
|
||||
flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling();
|
||||
useAlwaysAvailableJSErrorHandling_ = flagValue;
|
||||
@@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(44, "useFabricInterop");
|
||||
markFlagAsAccessed(45, "useFabricInterop");
|
||||
|
||||
flagValue = currentProvider_->useFabricInterop();
|
||||
useFabricInterop_ = flagValue;
|
||||
@@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(45, "useNativeViewConfigsInBridgelessMode");
|
||||
markFlagAsAccessed(46, "useNativeViewConfigsInBridgelessMode");
|
||||
|
||||
flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode();
|
||||
useNativeViewConfigsInBridgelessMode_ = flagValue;
|
||||
@@ -866,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(46, "useOptimizedEventBatchingOnAndroid");
|
||||
markFlagAsAccessed(47, "useOptimizedEventBatchingOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid();
|
||||
useOptimizedEventBatchingOnAndroid_ = flagValue;
|
||||
@@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(47, "useRawPropsJsiValue");
|
||||
markFlagAsAccessed(48, "useRawPropsJsiValue");
|
||||
|
||||
flagValue = currentProvider_->useRawPropsJsiValue();
|
||||
useRawPropsJsiValue_ = flagValue;
|
||||
@@ -902,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(48, "useShadowNodeStateOnClone");
|
||||
markFlagAsAccessed(49, "useShadowNodeStateOnClone");
|
||||
|
||||
flagValue = currentProvider_->useShadowNodeStateOnClone();
|
||||
useShadowNodeStateOnClone_ = flagValue;
|
||||
@@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(49, "useTurboModuleInterop");
|
||||
markFlagAsAccessed(50, "useTurboModuleInterop");
|
||||
|
||||
flagValue = currentProvider_->useTurboModuleInterop();
|
||||
useTurboModuleInterop_ = flagValue;
|
||||
@@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(50, "useTurboModules");
|
||||
markFlagAsAccessed(51, "useTurboModules");
|
||||
|
||||
flagValue = currentProvider_->useTurboModules();
|
||||
useTurboModules_ = flagValue;
|
||||
@@ -956,7 +974,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(51, "virtualViewPrerenderRatio");
|
||||
markFlagAsAccessed(52, "virtualViewPrerenderRatio");
|
||||
|
||||
flagValue = currentProvider_->virtualViewPrerenderRatio();
|
||||
virtualViewPrerenderRatio_ = flagValue;
|
||||
|
||||
+4
-2
@@ -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<<8c6910a889ca561af1b8ba3ef761cfde>>
|
||||
* @generated SignedSource<<c457c10861a505c16b0f8e1a8d9094bc>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -53,6 +53,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
bool enableFontScaleChangesUpdatingLayout();
|
||||
bool enableIOSTextBaselineOffsetPerLine();
|
||||
bool enableIOSViewClipToPaddingBox();
|
||||
bool enableIntersectionObserverEventLoopIntegration();
|
||||
bool enableLayoutAnimationsOnAndroid();
|
||||
bool enableLayoutAnimationsOnIOS();
|
||||
bool enableMainQueueCoordinatorOnIOS();
|
||||
@@ -95,7 +96,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
|
||||
bool wasOverridden_;
|
||||
|
||||
std::array<std::atomic<const char*>, 52> accessedFeatureFlags_;
|
||||
std::array<std::atomic<const char*>, 53> accessedFeatureFlags_;
|
||||
|
||||
std::atomic<std::optional<bool>> commonTestFlag_;
|
||||
std::atomic<std::optional<bool>> animatedShouldSignalBatch_;
|
||||
@@ -118,6 +119,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
std::atomic<std::optional<bool>> enableFontScaleChangesUpdatingLayout_;
|
||||
std::atomic<std::optional<bool>> enableIOSTextBaselineOffsetPerLine_;
|
||||
std::atomic<std::optional<bool>> enableIOSViewClipToPaddingBox_;
|
||||
std::atomic<std::optional<bool>> enableIntersectionObserverEventLoopIntegration_;
|
||||
std::atomic<std::optional<bool>> enableLayoutAnimationsOnAndroid_;
|
||||
std::atomic<std::optional<bool>> enableLayoutAnimationsOnIOS_;
|
||||
std::atomic<std::optional<bool>> enableMainQueueCoordinatorOnIOS_;
|
||||
|
||||
+5
-1
@@ -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<<77750d9d71424a12867ade6d627b957c>>
|
||||
* @generated SignedSource<<a01e55b01488b4db58e0a53481ed06ef>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -111,6 +111,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool enableIntersectionObserverEventLoopIntegration() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enableLayoutAnimationsOnAndroid() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
+10
-1
@@ -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<<72e5c7b050f76d427ca8ddada1283b98>>
|
||||
* @generated SignedSource<<0e532705f336a7e8fb21ae8056a17083>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -234,6 +234,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
|
||||
return ReactNativeFeatureFlagsDefaults::enableIOSViewClipToPaddingBox();
|
||||
}
|
||||
|
||||
bool enableIntersectionObserverEventLoopIntegration() override {
|
||||
auto value = values_["enableIntersectionObserverEventLoopIntegration"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableIntersectionObserverEventLoopIntegration();
|
||||
}
|
||||
|
||||
bool enableLayoutAnimationsOnAndroid() override {
|
||||
auto value = values_["enableLayoutAnimationsOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
|
||||
+2
-1
@@ -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<<404f7457739c9a7fa6c24fd50754836d>>
|
||||
* @generated SignedSource<<678646247ccb523a194255a7fabf45ba>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -46,6 +46,7 @@ class ReactNativeFeatureFlagsProvider {
|
||||
virtual bool enableFontScaleChangesUpdatingLayout() = 0;
|
||||
virtual bool enableIOSTextBaselineOffsetPerLine() = 0;
|
||||
virtual bool enableIOSViewClipToPaddingBox() = 0;
|
||||
virtual bool enableIntersectionObserverEventLoopIntegration() = 0;
|
||||
virtual bool enableLayoutAnimationsOnAndroid() = 0;
|
||||
virtual bool enableLayoutAnimationsOnIOS() = 0;
|
||||
virtual bool enableMainQueueCoordinatorOnIOS() = 0;
|
||||
|
||||
+6
-1
@@ -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<<9f462bca84c5fc361a92b288155b491b>>
|
||||
* @generated SignedSource<<bd98e634e772fee0ba1bb4b79b261eef>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -149,6 +149,11 @@ bool NativeReactNativeFeatureFlags::enableIOSViewClipToPaddingBox(
|
||||
return ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
|
||||
}
|
||||
|
||||
bool NativeReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration();
|
||||
}
|
||||
|
||||
bool NativeReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid();
|
||||
|
||||
+3
-1
@@ -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<<ee714ff865ecd52f3644451aaa1e1d2a>>
|
||||
* @generated SignedSource<<fa8ed2b8df27a93af45d70bef48a6060>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -78,6 +78,8 @@ class NativeReactNativeFeatureFlags
|
||||
|
||||
bool enableIOSViewClipToPaddingBox(jsi::Runtime& runtime);
|
||||
|
||||
bool enableIntersectionObserverEventLoopIntegration(jsi::Runtime& runtime);
|
||||
|
||||
bool enableLayoutAnimationsOnAndroid(jsi::Runtime& runtime);
|
||||
|
||||
bool enableLayoutAnimationsOnIOS(jsi::Runtime& runtime);
|
||||
|
||||
@@ -8,10 +8,7 @@ set(CMAKE_VERBOSE_MAKEFILE on)
|
||||
|
||||
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
|
||||
|
||||
file(GLOB rrc_modal_SRC CONFIGURE_DEPENDS
|
||||
*.cpp
|
||||
platform/android/*.cpp)
|
||||
|
||||
file(GLOB rrc_modal_SRC CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(rrc_modal STATIC ${rrc_modal_SRC})
|
||||
|
||||
target_include_directories(rrc_modal PUBLIC ${REACT_COMMON_DIR})
|
||||
|
||||
+2
-3
@@ -30,9 +30,8 @@ class ModalHostViewComponentDescriptor final
|
||||
*shadowNode.getState())
|
||||
.getData();
|
||||
|
||||
layoutableShadowNode.setSize(Size{
|
||||
.width = stateData.screenSize.width,
|
||||
.height = stateData.screenSize.height});
|
||||
layoutableShadowNode.setSize(
|
||||
Size{stateData.screenSize.width, stateData.screenSize.height});
|
||||
layoutableShadowNode.setPositionType(YGPositionTypeAbsolute);
|
||||
|
||||
ConcreteComponentDescriptor::adopt(shadowNode);
|
||||
|
||||
+12
-4
@@ -9,12 +9,15 @@
|
||||
|
||||
#include <react/renderer/core/graphicsConversions.h>
|
||||
#include <react/renderer/graphics/Float.h>
|
||||
#include "ModalHostViewUtils.h"
|
||||
|
||||
#ifdef RN_SERIALIZABLE_STATE
|
||||
#include <folly/dynamic.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && TARGET_OS_IOS
|
||||
#include "ModalHostViewUtils.h"
|
||||
#endif
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/*
|
||||
@@ -24,7 +27,12 @@ class ModalHostViewState final {
|
||||
public:
|
||||
using Shared = std::shared_ptr<const ModalHostViewState>;
|
||||
|
||||
ModalHostViewState() : screenSize(ModalHostViewScreenSize()) {}
|
||||
#if defined(__APPLE__) && TARGET_OS_IOS
|
||||
ModalHostViewState() : screenSize(RCTModalHostViewScreenSize()) {
|
||||
#else
|
||||
ModalHostViewState(){
|
||||
#endif
|
||||
};
|
||||
ModalHostViewState(Size screenSize_) : screenSize(screenSize_){};
|
||||
|
||||
#ifdef RN_SERIALIZABLE_STATE
|
||||
@@ -32,8 +40,8 @@ class ModalHostViewState final {
|
||||
const ModalHostViewState& previousState,
|
||||
folly::dynamic data)
|
||||
: screenSize(Size{
|
||||
.width = (Float)data["screenWidth"].getDouble(),
|
||||
.height = (Float)data["screenHeight"].getDouble()}){};
|
||||
(Float)data["screenWidth"].getDouble(),
|
||||
(Float)data["screenHeight"].getDouble()}){};
|
||||
#endif
|
||||
|
||||
const Size screenSize{};
|
||||
|
||||
+2
-2
@@ -7,10 +7,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/renderer/graphics/Size.h>
|
||||
#include <react/renderer/core/graphicsConversions.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
Size ModalHostViewScreenSize(void);
|
||||
Size RCTModalHostViewScreenSize(void);
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
Size ModalHostViewScreenSize(void)
|
||||
Size RCTModalHostViewScreenSize(void)
|
||||
{
|
||||
CGSize screenSize = RCTScreenSize();
|
||||
return {screenSize.width, screenSize.height};
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <react/renderer/graphics/Size.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class JReactModalHostView
|
||||
: public facebook::jni::JavaClass<JReactModalHostView> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor =
|
||||
"Lcom/facebook/react/views/modal/ReactModalHostView;";
|
||||
|
||||
static Size getDisplayMetrics() {
|
||||
static auto method =
|
||||
JReactModalHostView::javaClassStatic()->getStaticMethod<jlong()>(
|
||||
"getScreenDisplayMetricsWithoutInsets");
|
||||
auto result = method(javaClassStatic());
|
||||
|
||||
// Inspired from yogaMeassureToSize from conversions.h
|
||||
int32_t wBits = 0xFFFFFFFF & (result >> 32);
|
||||
int32_t hBits = 0xFFFFFFFF & result;
|
||||
|
||||
auto* measuredWidth = reinterpret_cast<float*>(&wBits);
|
||||
auto* measuredHeight = reinterpret_cast<float*>(&hBits);
|
||||
|
||||
return Size{.width = *measuredWidth, .height = *measuredHeight};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <react/renderer/components/modal/ModalHostViewUtils.h>
|
||||
#include <react/renderer/graphics/Size.h>
|
||||
#include "JReactModalHostView.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
Size ModalHostViewScreenSize() {
|
||||
return JReactModalHostView::getDisplayMetrics();
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <react/renderer/components/modal/ModalHostViewUtils.h>
|
||||
#include <react/renderer/graphics/Size.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
Size ModalHostViewScreenSize() {
|
||||
return Size{0, 0};
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+1
-1
@@ -56,7 +56,7 @@ class AndroidTextInputComponentDescriptor final
|
||||
if (getThemeData(
|
||||
fabricUIManager, surfaceId, defaultTextInputPaddingArray)) {
|
||||
jfloat* defaultTextInputPadding =
|
||||
env->GetFloatArrayElements(defaultTextInputPaddingArray, nullptr);
|
||||
env->GetFloatArrayElements(defaultTextInputPaddingArray, 0);
|
||||
theme.start = defaultTextInputPadding[0];
|
||||
theme.end = defaultTextInputPadding[1];
|
||||
theme.top = defaultTextInputPadding[2];
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/renderer/components/virtualview/VirtualViewShadowNode.h>
|
||||
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/*
|
||||
* Descriptor for <VirtualView> component.
|
||||
*/
|
||||
using VirtualViewComponentDescriptor =
|
||||
ConcreteComponentDescriptor<VirtualViewShadowNode>;
|
||||
} // namespace facebook::react
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
|
||||
#include <react/renderer/components/FBReactNativeSpec/Props.h>
|
||||
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
constexpr const char VirtualViewComponentName[] = "VirtualView";
|
||||
|
||||
/*
|
||||
* `ShadowNode` for <VirtualView> component.
|
||||
*/
|
||||
class VirtualViewShadowNode final : public ConcreteViewShadowNode<
|
||||
VirtualViewComponentName,
|
||||
VirtualViewProps,
|
||||
VirtualViewEventEmitter> {
|
||||
public:
|
||||
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
||||
|
||||
static ShadowNodeTraits BaseTraits() {
|
||||
auto traits = ConcreteViewShadowNode::BaseTraits();
|
||||
// <VirtualView> has a side effect: it listens to scroll events.
|
||||
// It must not be culled, otherwise Fling will not work.
|
||||
traits.set(ShadowNodeTraits::Trait::Unstable_uncullableView);
|
||||
return traits;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
-5
@@ -915,10 +915,6 @@ TEST(LayoutableShadowNodeTest, relativeLayoutMetricsOnClonedNode) {
|
||||
* │ └──────────────────────┘│
|
||||
* └─────────────────────────┘
|
||||
*/
|
||||
// We're not running this on Android as ModalHostViewShadowNode
|
||||
// need to go through JNI to resolve the initial screen size
|
||||
// in order to position the modal.
|
||||
#ifndef ANDROID
|
||||
TEST(
|
||||
LayoutableShadowNodeTest,
|
||||
relativeLayoutMetricsOnNodesCrossingRootKindNode) {
|
||||
@@ -955,7 +951,6 @@ TEST(
|
||||
EXPECT_EQ(relativeLayoutMetrics.frame.origin.x, 10);
|
||||
EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 10);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(LayoutableShadowNodeTest, includeViewportOffset) {
|
||||
auto builder = simpleComponentBuilder();
|
||||
|
||||
@@ -302,11 +302,11 @@ Transform Transform::Interpolate(
|
||||
}
|
||||
|
||||
bool Transform::isVerticalInversion(const Transform& transform) {
|
||||
return floatEquality(transform.at(1, 1), static_cast<Float>(-1.0f));
|
||||
return facebook::react::floatEquality(transform.at(1, 1), -1.0f);
|
||||
}
|
||||
|
||||
bool Transform::isHorizontalInversion(const Transform& transform) {
|
||||
return floatEquality(transform.at(0, 0), static_cast<Float>(-1.0f));
|
||||
return facebook::react::floatEquality(transform.at(0, 0), -1.0f);
|
||||
}
|
||||
|
||||
bool Transform::operator==(const Transform& rhs) const {
|
||||
|
||||
+80
-24
@@ -49,18 +49,65 @@ void IntersectionObserverManager::observe(
|
||||
|
||||
auto surfaceId = shadowNodeFamily->getSurfaceId();
|
||||
|
||||
// The actual observer lives in the array, so we need to create it there and
|
||||
// then get a reference. Otherwise we only update its state in a copy.
|
||||
IntersectionObserver* observer = nullptr;
|
||||
|
||||
// Register observer
|
||||
std::unique_lock lock(observersMutex_);
|
||||
{
|
||||
std::unique_lock lock(observersMutex_);
|
||||
|
||||
auto& observers = observersBySurfaceId_[surfaceId];
|
||||
observers.emplace_back(std::make_unique<IntersectionObserver>(
|
||||
intersectionObserverId,
|
||||
observationRootShadowNodeFamily,
|
||||
shadowNodeFamily,
|
||||
std::move(thresholds),
|
||||
std::move(rootThresholds)));
|
||||
auto& observers = observersBySurfaceId_[surfaceId];
|
||||
observers.emplace_back(std::make_unique<IntersectionObserver>(
|
||||
intersectionObserverId,
|
||||
observationRootShadowNodeFamily,
|
||||
shadowNodeFamily,
|
||||
std::move(thresholds),
|
||||
std::move(rootThresholds)));
|
||||
|
||||
observersPendingInitialization_.emplace_back(observers.back().get());
|
||||
if (ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
observersPendingInitialization_.emplace_back(observers.back().get());
|
||||
}
|
||||
|
||||
observer = observers.back().get();
|
||||
}
|
||||
|
||||
if (!ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
// Notification of initial state.
|
||||
// Ideally, we'd have well defined event loop step to notify observers
|
||||
// (like on the Web) and we'd send the initial notification there, but as
|
||||
// we don't have it we have to run this check once and manually dispatch.
|
||||
auto& shadowTreeRegistry = uiManager.getShadowTreeRegistry();
|
||||
std::shared_ptr<const MountingCoordinator> mountingCoordinator = nullptr;
|
||||
RootShadowNode::Shared rootShadowNode = nullptr;
|
||||
shadowTreeRegistry.visit(surfaceId, [&](const ShadowTree& shadowTree) {
|
||||
mountingCoordinator = shadowTree.getMountingCoordinator();
|
||||
rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
|
||||
});
|
||||
|
||||
// If the surface doesn't exist for some reason, we skip initial
|
||||
// notification.
|
||||
if (!rootShadowNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto hasPendingTransactions = mountingCoordinator != nullptr &&
|
||||
mountingCoordinator->hasPendingTransactions();
|
||||
|
||||
if (!hasPendingTransactions) {
|
||||
auto entry = observer->updateIntersectionObservation(
|
||||
*rootShadowNode, HighResTimeStamp::now());
|
||||
if (entry) {
|
||||
{
|
||||
std::unique_lock lock(pendingEntriesMutex_);
|
||||
pendingEntries_.push_back(std::move(entry).value());
|
||||
}
|
||||
notifyObserversIfNecessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntersectionObserverManager::unobserve(
|
||||
@@ -68,19 +115,22 @@ void IntersectionObserverManager::unobserve(
|
||||
const ShadowNodeFamily::Shared& shadowNodeFamily) {
|
||||
TraceSection s("IntersectionObserverManager::unobserve");
|
||||
|
||||
// This doesn't need to be protected by the mutex because it is only
|
||||
// accessed and modified from the JS thread.
|
||||
observersPendingInitialization_.erase(
|
||||
std::remove_if(
|
||||
observersPendingInitialization_.begin(),
|
||||
observersPendingInitialization_.end(),
|
||||
[intersectionObserverId, &shadowNodeFamily](const auto& observer) {
|
||||
return (
|
||||
observer->getIntersectionObserverId() ==
|
||||
intersectionObserverId &&
|
||||
observer->getTargetShadowNodeFamily() == shadowNodeFamily);
|
||||
}),
|
||||
observersPendingInitialization_.end());
|
||||
if (ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
// This doesn't need to be protected by the mutex because it is only
|
||||
// accessed and modified from the JS thread.
|
||||
observersPendingInitialization_.erase(
|
||||
std::remove_if(
|
||||
observersPendingInitialization_.begin(),
|
||||
observersPendingInitialization_.end(),
|
||||
[intersectionObserverId, &shadowNodeFamily](const auto& observer) {
|
||||
return (
|
||||
observer->getIntersectionObserverId() ==
|
||||
intersectionObserverId &&
|
||||
observer->getTargetShadowNodeFamily() == shadowNodeFamily);
|
||||
}),
|
||||
observersPendingInitialization_.end());
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock(observersMutex_);
|
||||
@@ -138,7 +188,10 @@ void IntersectionObserverManager::connect(
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeScheduler.setIntersectionObserverDelegate(this);
|
||||
if (ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
runtimeScheduler.setIntersectionObserverDelegate(this);
|
||||
}
|
||||
uiManager.registerMountHook(*this);
|
||||
shadowTreeRegistry_ = &uiManager.getShadowTreeRegistry();
|
||||
mountHookRegistered_ = true;
|
||||
@@ -154,7 +207,10 @@ void IntersectionObserverManager::disconnect(
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeScheduler.setIntersectionObserverDelegate(nullptr);
|
||||
if (ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
runtimeScheduler.setIntersectionObserverDelegate(nullptr);
|
||||
}
|
||||
uiManager.unregisterMountHook(*this);
|
||||
shadowTreeRegistry_ = nullptr;
|
||||
mountHookRegistered_ = false;
|
||||
|
||||
@@ -7,17 +7,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
constexpr float kDefaultEpsilon = 0.005f;
|
||||
|
||||
template <typename T>
|
||||
inline bool
|
||||
floatEquality(T a, T b, T epsilon = static_cast<T>(kDefaultEpsilon)) {
|
||||
inline bool floatEquality(float a, float b, float epsilon = kDefaultEpsilon) {
|
||||
return (std::isnan(a) && std::isnan(b)) ||
|
||||
(!std::isnan(a) && !std::isnan(b) && std::abs(a - b) < epsilon);
|
||||
(!std::isnan(a) && !std::isnan(b) && fabs(a - b) < epsilon);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
Vendored
-7
@@ -142,9 +142,6 @@ module.exports = {
|
||||
get VirtualizedSectionList() {
|
||||
return require('./Libraries/Lists/VirtualizedSectionList').default;
|
||||
},
|
||||
get unstable_VirtualView() {
|
||||
return require('./src/private/components/virtualview/VirtualView').default;
|
||||
},
|
||||
// #endregion
|
||||
// #region APIs
|
||||
get AccessibilityInfo() {
|
||||
@@ -329,10 +326,6 @@ module.exports = {
|
||||
get Vibration() {
|
||||
return require('./Libraries/Vibration/Vibration').default;
|
||||
},
|
||||
get VirtualViewMode() {
|
||||
return require('./src/private/components/virtualview/VirtualView')
|
||||
.VirtualViewMode;
|
||||
},
|
||||
// #endregion
|
||||
} as ReactNativePublicAPI;
|
||||
|
||||
|
||||
@@ -444,10 +444,4 @@ export type {
|
||||
PublicTextInstance,
|
||||
} from './Libraries/ReactPrivate/ReactNativePrivateInterface';
|
||||
|
||||
export {
|
||||
default as unstable_VirtualView,
|
||||
VirtualViewMode,
|
||||
} from './src/private/components/virtualview/VirtualView';
|
||||
export type {ModeChangeEvent} from './src/private/components/virtualview/VirtualView';
|
||||
|
||||
// #endregion
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
# 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.
|
||||
|
||||
require "json"
|
||||
require 'net/http'
|
||||
require 'rexml/document'
|
||||
|
||||
require_relative './utils.rb'
|
||||
|
||||
## - RCT_USE_PREBUILT_RNCORE: If set to 1, it will use the release tarball from Maven instead of building from source.
|
||||
## - RCT_USE_LOCAL_RNCORE: **TEST ONLY** If set, it will use a local tarball of RNCore if it exists.
|
||||
## - RCT_RNCORE_VERSION: **TEST ONLY** If set, it will override the version of RNCore to be used.
|
||||
|
||||
class ReactNativeCoreUtils
|
||||
@@build_from_source = true
|
||||
@@react_native_path = ""
|
||||
@@react_native_version = ""
|
||||
@@use_nightly = false
|
||||
|
||||
## Sets up wether ReactNative Core should be built from source or not.
|
||||
## If RCT_USE_PREBUILT_RNCORE is set to 1 and the artifacts exists on Maven, it will
|
||||
## not build from source. Otherwise, it will build from source.
|
||||
def self.setup_rncore(react_native_path, react_native_version)
|
||||
# We don't want setup to be called multiple times, so we check if the variables are already set.
|
||||
if @@react_native_version == ""
|
||||
rncore_log("Setting up ReactNativeCore...")
|
||||
@@react_native_path = react_native_path
|
||||
@@react_native_version = ENV["RCT_RNCORE_VERSION"] == nil ? react_native_version : ENV["RCT_RNCORE_VERSION"]
|
||||
|
||||
if @@react_native_version.include? 'nightly'
|
||||
@@use_nightly = true
|
||||
if ENV["RCT_RNCORE_VERSION"] == "nightly"
|
||||
@@react_native_version = ReactNativeDependenciesUtils.get_nightly_npm_version()
|
||||
rncore_log("Using nightly version from npm: #{@@react_native_version}")
|
||||
else
|
||||
rncore_log("Using nightly build #{@@react_native_version}")
|
||||
end
|
||||
end
|
||||
|
||||
if ENV["RCT_USE_LOCAL_RNCORE"]
|
||||
abort_if_use_local_rncore_with_no_file()
|
||||
end
|
||||
|
||||
use_local_xcframework = ENV["RCT_USE_LOCAL_RNCORE"] && File.exist?(ENV["RCT_USE_LOCAL_RNCORE"])
|
||||
artifacts_exists = ENV["RCT_USE_PREBUILT_RNCORE"] == "1" && (@@use_nightly ? nightly_artifact_exists(@@react_native_version) : release_artifact_exists(@@react_native_version))
|
||||
@@build_from_source = !use_local_xcframework && !artifacts_exists
|
||||
|
||||
if @@build_from_source && ENV["RCT_USE_LOCAL_RNCORE"] && !use_local_xcframework
|
||||
rncore_log("No local xcframework found, reverting to building from source.")
|
||||
end
|
||||
if @@build_from_source && ENV["RCT_USE_PREBUILT_RNCORE"] && !artifacts_exists
|
||||
rncore_log("No prebuilt artifacts found, reverting to building from source.")
|
||||
end
|
||||
rncore_log("Building from source: #{@@build_from_source}")
|
||||
rncore_log("Source: #{self.resolve_podspec_source()}")
|
||||
end
|
||||
end
|
||||
|
||||
def self.abort_if_use_local_rncore_with_no_file()
|
||||
if !File.exist?(ENV["RCT_USE_LOCAL_RNCORE"])
|
||||
abort("RCT_USE_LOCAL_RNCORE is set to #{ENV["RCT_USE_LOCAL_RNCORE"]} but the file does not exist!")
|
||||
end
|
||||
end
|
||||
|
||||
def self.build_rncore_from_source()
|
||||
return @@build_from_source
|
||||
end
|
||||
|
||||
def self.resolve_podspec_source()
|
||||
if ENV["RCT_USE_LOCAL_RNCORE"]
|
||||
abort_if_use_local_rncore_with_no_file()
|
||||
rncore_log("Using local xcframework at #{ENV["RCT_USE_LOCAL_RNCORE"]}")
|
||||
return {:http => "file://#{ENV["RCT_USE_LOCAL_RNCORE"]}" }
|
||||
end
|
||||
|
||||
if ENV["RCT_USE_PREBUILT_RNCORE"] == "1"
|
||||
if @@use_nightly
|
||||
rncore_log("Using nightly tarball")
|
||||
begin
|
||||
return self.podspec_source_download_prebuilt_nightly_tarball(@@react_native_version)
|
||||
rescue => e
|
||||
rncore_log("Failed to download nightly tarball: #{e.message}", :error)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
return self.podspec_source_download_prebuild_stable_tarball()
|
||||
rescue => e
|
||||
rncore_log("Failed to download release tarball: #{e.message}", :error)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def self.podspec_source_download_prebuild_stable_tarball()
|
||||
# Warn if @@react_native_path is not set
|
||||
if @@react_native_path == ""
|
||||
rncore_log("react_native_path is not set", :error)
|
||||
return
|
||||
end
|
||||
|
||||
# Warn if @@react_native_version is not set
|
||||
if @@react_native_version == ""
|
||||
rncore_log("react_native_version is not set", :error)
|
||||
return
|
||||
end
|
||||
|
||||
if @@build_from_source
|
||||
return
|
||||
end
|
||||
|
||||
url = stable_tarball_url(@@react_native_version, :debug)
|
||||
rncore_log("Using tarball from URL: #{url}")
|
||||
download_stable_rndeps(@@react_native_path, @@react_native_version, :debug)
|
||||
download_stable_rndeps(@@react_native_path, @@react_native_version, :release)
|
||||
return {:http => url}
|
||||
end
|
||||
|
||||
def self.stable_tarball_url(version, build_type)
|
||||
maven_repo_url = "https://repo1.maven.org/maven2"
|
||||
group = "com/facebook/react"
|
||||
# Sample url from Maven:
|
||||
return "#{maven_repo_url}/#{group}/react-native-artifacts/#{version}/react-native-artifacts-#{version}-reactnative-core-debug.tar.gz-#{build_type.to_s}.tar.gz"
|
||||
end
|
||||
|
||||
def self.nightly_tarball_url(version)
|
||||
artefact_coordinate = "react-native-artifacts"
|
||||
artefact_name = "reactnative-core-debug.tar.gz"
|
||||
xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml"
|
||||
|
||||
response = Net::HTTP.get(URI(xml_url))
|
||||
if response.kind_of? Net::HTTPSuccess
|
||||
xml = REXML::Document.new(response)
|
||||
timestamp = xml.elements['metadata/versioning/snapshot/timestamp'].text
|
||||
build_number = xml.elements['metadata/versioning/snapshot/buildNumber'].text
|
||||
full_version = "#{version}-#{timestamp}-#{build_number}"
|
||||
|
||||
final_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/#{artefact_coordinate}-#{full_version}-#{artefact_name}"
|
||||
return final_url
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
def self.download_stable_rndeps(react_native_path, version, configuration)
|
||||
tarball_url = stable_tarball_url(version, configuration)
|
||||
download_rndeps_tarball(react_native_path, tarball_url, version, configuration)
|
||||
end
|
||||
|
||||
def self.podspec_source_download_prebuilt_nightly_tarball(version)
|
||||
url = nightly_tarball_url(version)
|
||||
rncore_log("Using nightly tarball from URL: #{url}")
|
||||
return {:http => url}
|
||||
end
|
||||
|
||||
def self.download_rndeps_tarball(react_native_path, tarball_url, version, configuration)
|
||||
destination_path = configuration == nil ?
|
||||
"#{artifacts_dir()}/reactnative-core-debug.tar.gz-#{version}.tar.gz" :
|
||||
"#{artifacts_dir()}/reactnative-core-debug.tar.gz-#{version}-#{configuration}.tar.gz"
|
||||
|
||||
unless File.exist?(destination_path)
|
||||
# Download to a temporary file first so we don't cache incomplete downloads.
|
||||
tmp_file = "#{artifacts_dir()}/reactnative-core-debug.tar.gz.download"
|
||||
`mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
|
||||
end
|
||||
|
||||
return destination_path
|
||||
end
|
||||
|
||||
def self.release_artifact_exists(version)
|
||||
return artifact_exists(stable_tarball_url(version, :debug))
|
||||
end
|
||||
|
||||
def self.nightly_artifact_exists(version)
|
||||
return artifact_exists(nightly_tarball_url(version).gsub("\\", ""))
|
||||
end
|
||||
|
||||
def self.artifacts_dir()
|
||||
return File.join(Pod::Config.instance.project_pods_root, "ReactNativeCore-artifacts")
|
||||
end
|
||||
|
||||
# This function checks that ReactNativeCore artifact exists on the maven repo
|
||||
def self.artifact_exists(tarball_url)
|
||||
# -L is used to follow redirects, useful for the nightlies
|
||||
# I also needed to wrap the url in quotes to avoid escaping & and ?.
|
||||
return (`curl -o /dev/null --silent -Iw '%{http_code}' -L "#{tarball_url}"` == "200")
|
||||
end
|
||||
|
||||
def self.rncore_log(message, level = :info)
|
||||
if !Object.const_defined?("Pod::UI")
|
||||
return
|
||||
end
|
||||
log_message = '[ReactNativeCore] ' + message
|
||||
case level
|
||||
when :info
|
||||
Pod::UI.puts log_message.green
|
||||
when :error
|
||||
Pod::UI.puts log_message.red
|
||||
else
|
||||
Pod::UI.puts log_message.yellow
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_nightly_npm_version()
|
||||
uri = URI('https://registry.npmjs.org/react-native/nightly')
|
||||
response = Net::HTTP.get_response(uri)
|
||||
|
||||
unless response.is_a?(Net::HTTPSuccess)
|
||||
raise "Couldn't get an answer from NPM: #{response.code} #{response.message}"
|
||||
end
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
latest_nightly = json['version']
|
||||
return latest_nightly
|
||||
end
|
||||
|
||||
end
|
||||
@@ -67,46 +67,78 @@ class ReactNativeDependenciesUtils
|
||||
end
|
||||
|
||||
def self.resolve_podspec_source()
|
||||
if ENV["RCT_USE_RN_DEP"] && ENV["RCT_USE_RN_DEP"] == "1"
|
||||
if @@use_nightly
|
||||
rndeps_log("Using nightly tarball")
|
||||
return self.podspec_source_download_prebuilt_nightly_tarball(@@react_native_version)
|
||||
end
|
||||
|
||||
rndeps_log("Using release tarball")
|
||||
return self.podspec_source_download_prebuild_release_tarball()
|
||||
end
|
||||
|
||||
if ENV["RCT_USE_LOCAL_RN_DEP"] && File.exist?(ENV["RCT_USE_LOCAL_RN_DEP"])
|
||||
if ENV["RCT_USE_LOCAL_RN_DEP"]
|
||||
abort_if_use_local_rndeps_with_no_file()
|
||||
rndeps_log("Using local xcframework at #{ENV["RCT_USE_LOCAL_RN_DEP"]}")
|
||||
return {:http => "file://#{ENV["RCT_USE_LOCAL_RN_DEP"]}" }
|
||||
end
|
||||
|
||||
if ENV["RCT_USE_RN_DEP"] && ENV["RCT_USE_RN_DEP"] == "1"
|
||||
if @@use_nightly
|
||||
rndeps_log("Using nightly tarball")
|
||||
begin
|
||||
return self.podspec_source_download_prebuilt_nightly_tarball(@@react_native_version)
|
||||
rescue => e
|
||||
rndeps_log("Failed to download nightly tarball: #{e.message}", :error)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
rndeps_log("Using release tarball")
|
||||
begin
|
||||
return self.podspec_source_download_prebuild_release_tarball()
|
||||
rescue => e
|
||||
rndeps_log("Failed to download release tarball: #{e.message}", :error)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
## Sets up wether react-native-dependencies should be built from source or not.
|
||||
## If RCT_USE_RN_DEP is set to 1 and the artifacts exists on Maven, it will
|
||||
## not build from source. Otherwise, it will build from source.
|
||||
def self.setup_react_native_dependencies(react_native_path, react_native_version)
|
||||
@@react_native_path = react_native_path
|
||||
@@react_native_version = ENV["RCT_DEPS_VERSION"] == nil ? react_native_version : ENV["RCT_DEPS_VERSION"]
|
||||
# We don't want setup to be called multiple times, so we check if the variables are already set.
|
||||
if @@react_native_version == ""
|
||||
rndeps_log("Setting up ReactNativeDependencies...")
|
||||
@@react_native_path = react_native_path
|
||||
@@react_native_version = ENV["RCT_DEPS_VERSION"] == nil ? react_native_version : ENV["RCT_DEPS_VERSION"]
|
||||
|
||||
if @@react_native_version.include? 'nightly'
|
||||
rndeps_log("Using nightly build")
|
||||
@@use_nightly = true
|
||||
end
|
||||
|
||||
artifacts_exists = ENV["RCT_USE_RN_DEP"] == "1" && (@@use_nightly ? nightly_artifact_exists(@@react_native_version) : release_artifact_exists(@@react_native_version))
|
||||
use_local_xcframework = ENV["RCT_USE_LOCAL_RN_DEP"] && File.exist?(ENV["RCT_USE_LOCAL_RN_DEP"])
|
||||
|
||||
if ENV["RCT_USE_LOCAL_RN_DEP"]
|
||||
if !File.exist?(ENV["RCT_USE_LOCAL_RN_DEP"])
|
||||
abort("RCT_USE_LOCAL_RN_DEP is set to #{ENV["RCT_USE_LOCAL_RN_DEP"]} but the file does not exist!")
|
||||
if @@react_native_version.include? 'nightly'
|
||||
@@use_nightly = true
|
||||
if ENV["RCT_DEPS_VERSION"] == "nightly"
|
||||
@@react_native_version = ReactNativeDependenciesUtils.get_nightly_npm_version()
|
||||
rndeps_log("Using nightly version from npm: #{@@react_native_version}")
|
||||
else
|
||||
rndeps_log("Using nightly build #{@@react_native_version}")
|
||||
end
|
||||
end
|
||||
|
||||
if ENV["RCT_USE_LOCAL_RN_DEP"]
|
||||
abort_if_use_local_rndeps_with_no_file()
|
||||
end
|
||||
|
||||
artifacts_exists = ENV["RCT_USE_RN_DEP"] == "1" && (@@use_nightly ? nightly_artifact_exists(@@react_native_version) : release_artifact_exists(@@react_native_version))
|
||||
use_local_xcframework = ENV["RCT_USE_LOCAL_RN_DEP"] && File.exist?(ENV["RCT_USE_LOCAL_RN_DEP"])
|
||||
|
||||
@@build_from_source = !use_local_xcframework && !artifacts_exists
|
||||
|
||||
if @@build_from_source && ENV["RCT_USE_LOCAL_RNCORE"] && !use_local_xcframework
|
||||
rndeps_log("No local xcframework found, reverting to building from source.")
|
||||
end
|
||||
if @@build_from_source && ENV["RCT_USE_PREBUILT_RNCORE"] && !artifacts_exists
|
||||
rndeps_log("No prebuilt artifacts found, reverting to building from source.")
|
||||
end
|
||||
rndeps_log("Building from source: #{@@build_from_source}")
|
||||
rndeps_log("Source: #{self.resolve_podspec_source()}")
|
||||
end
|
||||
end
|
||||
|
||||
@@build_from_source = !use_local_xcframework && !artifacts_exists
|
||||
|
||||
rndeps_log("Building from source: #{@@build_from_source}")
|
||||
def self.abort_if_use_local_rndeps_with_no_file()
|
||||
if !File.exist?(ENV["RCT_USE_LOCAL_RN_DEP"])
|
||||
abort("RCT_USE_LOCAL_RN_DEP is set to #{ENV["RCT_USE_LOCAL_RN_DEP"]} but the file does not exist!")
|
||||
end
|
||||
end
|
||||
|
||||
def self.podspec_source_download_prebuild_release_tarball()
|
||||
@@ -205,7 +237,7 @@ class ReactNativeDependenciesUtils
|
||||
return (`curl -o /dev/null --silent -Iw '%{http_code}' -L "#{tarball_url}"` == "200")
|
||||
end
|
||||
|
||||
def self.rndeps_log(message, level = :warning)
|
||||
def self.rndeps_log(message, level = :info)
|
||||
if !Object.const_defined?("Pod::UI")
|
||||
return
|
||||
end
|
||||
@@ -220,7 +252,16 @@ class ReactNativeDependenciesUtils
|
||||
end
|
||||
end
|
||||
|
||||
def self.resolve_url_redirects(url)
|
||||
return (`curl -Ls -o /dev/null -w %{url_effective} \"#{url}\"`)
|
||||
def self.get_nightly_npm_version()
|
||||
uri = URI('https://registry.npmjs.org/react-native/nightly')
|
||||
response = Net::HTTP.get_response(uri)
|
||||
|
||||
unless response.is_a?(Net::HTTPSuccess)
|
||||
raise "Couldn't get an answer from NPM: #{response.code} #{response.message}"
|
||||
end
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
latest_nightly = json['version']
|
||||
return latest_nightly
|
||||
end
|
||||
end
|
||||
|
||||
+140
-80
@@ -412,30 +412,53 @@ use_frameworks = ENV['USE_FRAMEWORKS'] != nil
|
||||
folly_compiler_flags = Helpers::Constants.folly_config[:compiler_flags]
|
||||
boost_compiler_flags = Helpers::Constants.boost_config[:compiler_flags]
|
||||
|
||||
header_search_paths = [
|
||||
\\"\\\\\\"$(PODS_ROOT)/ReactNativeDependencies\\\\\\"\\",
|
||||
\\"\\\\\\"\${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-Fabric\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/Yoga\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_TARGET_SRCROOT)\\\\\\"\\",
|
||||
]
|
||||
header_search_paths = []
|
||||
framework_search_paths = []
|
||||
|
||||
if use_frameworks
|
||||
ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-Fabric\\", \\"React_Fabric\\", [\\"react/renderer/components/view/platform/cxx\\"])
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-FabricImage\\", \\"React_FabricImage\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-graphics\\", \\"React_graphics\\", [\\"react/renderer/graphics/platform/ios\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"ReactCommon\\", \\"ReactCommon\\", [\\"react/nativemodule/core\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-NativeModulesApple\\", \\"React_NativeModulesApple\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-RCTFabric\\", \\"RCTFabric\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-debug\\", \\"React_debug\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-rendererdebug\\", \\"React_rendererdebug\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-utils\\", \\"React_utils\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-featureflags\\", \\"React_featureflags\\", []))
|
||||
.each { |search_path|
|
||||
header_search_paths << \\"\\\\\\"#{search_path}\\\\\\"\\"
|
||||
}
|
||||
if ReactNativeCoreUtils.build_rncore_from_source()
|
||||
|
||||
header_search_paths = [
|
||||
\\"\\\\\\"$(PODS_ROOT)/ReactNativeDependencies\\\\\\"\\",
|
||||
\\"\\\\\\"\${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-Fabric\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/Yoga\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_TARGET_SRCROOT)\\\\\\"\\",
|
||||
]
|
||||
|
||||
if use_frameworks
|
||||
ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-Fabric\\", \\"React_Fabric\\", [\\"react/renderer/components/view/platform/cxx\\"])
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-FabricImage\\", \\"React_FabricImage\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-graphics\\", \\"React_graphics\\", [\\"react/renderer/graphics/platform/ios\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"ReactCommon\\", \\"ReactCommon\\", [\\"react/nativemodule/core\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-NativeModulesApple\\", \\"React_NativeModulesApple\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-RCTFabric\\", \\"RCTFabric\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-debug\\", \\"React_debug\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-rendererdebug\\", \\"React_rendererdebug\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-utils\\", \\"React_utils\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-featureflags\\", \\"React_featureflags\\", []))
|
||||
.each { |search_path|
|
||||
header_search_paths << \\"\\\\\\"#{search_path}\\\\\\"\\"
|
||||
}
|
||||
end
|
||||
else
|
||||
header_search_paths = [
|
||||
\\"$(PODS_ROOT)/ReactNativeDependencies\\",
|
||||
\\"$(PODS_ROOT)/Headers/Public/React-Core-prebuilt/ReactCommon\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/react/nativemodule/core\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/RCTDeprecation\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/ReactCommon/yoga\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/ReactCommon/jsi\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/ReactCommon\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/Libraries/AppDelegate\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/Libraries\\",
|
||||
\\"\${REACT_NATIVE_PATH}/ReactCommon/yoga\\",
|
||||
\\"\${REACT_NATIVE_PATH}/ReactCommon/jsi\\",
|
||||
\\"$(REACT_NATIVE_PATH)/Libraries\\",
|
||||
\\"$(REACT_NATIVE_PATH)/Libraries/FBLazyVector\\",
|
||||
]
|
||||
end
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
@@ -457,25 +480,32 @@ Pod::Spec.new do |s|
|
||||
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\"
|
||||
}
|
||||
|
||||
s.dependency \\"React-jsiexecutor\\"
|
||||
s.dependency \\"RCTRequired\\"
|
||||
s.dependency \\"RCTTypeSafety\\"
|
||||
s.dependency \\"React-Core\\"
|
||||
s.dependency \\"React-jsi\\"
|
||||
s.dependency \\"ReactCommon/turbomodule/bridging\\"
|
||||
s.dependency \\"ReactCommon/turbomodule/core\\"
|
||||
s.dependency \\"React-NativeModulesApple\\"
|
||||
s.dependency 'React-graphics'
|
||||
s.dependency 'React-rendererdebug'
|
||||
s.dependency 'React-Fabric'
|
||||
s.dependency 'React-FabricImage'
|
||||
s.dependency 'React-debug'
|
||||
s.dependency 'React-utils'
|
||||
s.dependency 'React-featureflags'
|
||||
s.dependency 'React-RCTAppDelegate'
|
||||
if ReactNativeCoreUtils.build_rncore_from_source()
|
||||
|
||||
depend_on_js_engine(s)
|
||||
add_rn_third_party_dependencies(s)
|
||||
s.dependency \\"React-jsiexecutor\\"
|
||||
s.dependency \\"RCTRequired\\"
|
||||
s.dependency \\"RCTTypeSafety\\"
|
||||
s.dependency \\"React-Core\\"
|
||||
s.dependency \\"React-jsi\\"
|
||||
s.dependency \\"ReactCommon/turbomodule/bridging\\"
|
||||
s.dependency \\"ReactCommon/turbomodule/core\\"
|
||||
s.dependency \\"React-NativeModulesApple\\"
|
||||
s.dependency 'React-graphics'
|
||||
s.dependency 'React-rendererdebug'
|
||||
s.dependency 'React-Fabric'
|
||||
s.dependency 'React-FabricImage'
|
||||
s.dependency 'React-debug'
|
||||
s.dependency 'React-utils'
|
||||
s.dependency 'React-featureflags'
|
||||
s.dependency 'React-RCTAppDelegate'
|
||||
|
||||
depend_on_js_engine(s)
|
||||
add_rn_third_party_dependencies(s)
|
||||
|
||||
else
|
||||
s.dependency 'React-Core-prebuilt'
|
||||
s.dependency 'ReactNativeDependencies'
|
||||
end
|
||||
|
||||
s.script_phases = {
|
||||
'name' => 'Generate Specs',
|
||||
@@ -887,30 +917,53 @@ use_frameworks = ENV['USE_FRAMEWORKS'] != nil
|
||||
folly_compiler_flags = Helpers::Constants.folly_config[:compiler_flags]
|
||||
boost_compiler_flags = Helpers::Constants.boost_config[:compiler_flags]
|
||||
|
||||
header_search_paths = [
|
||||
\\"\\\\\\"$(PODS_ROOT)/ReactNativeDependencies\\\\\\"\\",
|
||||
\\"\\\\\\"\${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-Fabric\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/Yoga\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_TARGET_SRCROOT)\\\\\\"\\",
|
||||
]
|
||||
header_search_paths = []
|
||||
framework_search_paths = []
|
||||
|
||||
if use_frameworks
|
||||
ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-Fabric\\", \\"React_Fabric\\", [\\"react/renderer/components/view/platform/cxx\\"])
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-FabricImage\\", \\"React_FabricImage\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-graphics\\", \\"React_graphics\\", [\\"react/renderer/graphics/platform/ios\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"ReactCommon\\", \\"ReactCommon\\", [\\"react/nativemodule/core\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-NativeModulesApple\\", \\"React_NativeModulesApple\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-RCTFabric\\", \\"RCTFabric\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-debug\\", \\"React_debug\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-rendererdebug\\", \\"React_rendererdebug\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-utils\\", \\"React_utils\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-featureflags\\", \\"React_featureflags\\", []))
|
||||
.each { |search_path|
|
||||
header_search_paths << \\"\\\\\\"#{search_path}\\\\\\"\\"
|
||||
}
|
||||
if ReactNativeCoreUtils.build_rncore_from_source()
|
||||
|
||||
header_search_paths = [
|
||||
\\"\\\\\\"$(PODS_ROOT)/ReactNativeDependencies\\\\\\"\\",
|
||||
\\"\\\\\\"\${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-Fabric\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_ROOT)/Headers/Private/Yoga\\\\\\"\\",
|
||||
\\"\\\\\\"$(PODS_TARGET_SRCROOT)\\\\\\"\\",
|
||||
]
|
||||
|
||||
if use_frameworks
|
||||
ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-Fabric\\", \\"React_Fabric\\", [\\"react/renderer/components/view/platform/cxx\\"])
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-FabricImage\\", \\"React_FabricImage\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-graphics\\", \\"React_graphics\\", [\\"react/renderer/graphics/platform/ios\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"ReactCommon\\", \\"ReactCommon\\", [\\"react/nativemodule/core\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-NativeModulesApple\\", \\"React_NativeModulesApple\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-RCTFabric\\", \\"RCTFabric\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-debug\\", \\"React_debug\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-rendererdebug\\", \\"React_rendererdebug\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-utils\\", \\"React_utils\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-featureflags\\", \\"React_featureflags\\", []))
|
||||
.each { |search_path|
|
||||
header_search_paths << \\"\\\\\\"#{search_path}\\\\\\"\\"
|
||||
}
|
||||
end
|
||||
else
|
||||
header_search_paths = [
|
||||
\\"$(PODS_ROOT)/ReactNativeDependencies\\",
|
||||
\\"$(PODS_ROOT)/Headers/Public/React-Core-prebuilt/ReactCommon\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/react/nativemodule/core\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/RCTDeprecation\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/ReactCommon/yoga\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/ReactCommon/jsi\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/ReactCommon\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/Libraries/AppDelegate\\",
|
||||
\\"\${PODS_ROOT}/Headers/Public/React-Core-prebuilt/Libraries\\",
|
||||
\\"\${REACT_NATIVE_PATH}/ReactCommon/yoga\\",
|
||||
\\"\${REACT_NATIVE_PATH}/ReactCommon/jsi\\",
|
||||
\\"$(REACT_NATIVE_PATH)/Libraries\\",
|
||||
\\"$(REACT_NATIVE_PATH)/Libraries/FBLazyVector\\",
|
||||
]
|
||||
end
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
@@ -932,25 +985,32 @@ Pod::Spec.new do |s|
|
||||
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\"
|
||||
}
|
||||
|
||||
s.dependency \\"React-jsiexecutor\\"
|
||||
s.dependency \\"RCTRequired\\"
|
||||
s.dependency \\"RCTTypeSafety\\"
|
||||
s.dependency \\"React-Core\\"
|
||||
s.dependency \\"React-jsi\\"
|
||||
s.dependency \\"ReactCommon/turbomodule/bridging\\"
|
||||
s.dependency \\"ReactCommon/turbomodule/core\\"
|
||||
s.dependency \\"React-NativeModulesApple\\"
|
||||
s.dependency 'React-graphics'
|
||||
s.dependency 'React-rendererdebug'
|
||||
s.dependency 'React-Fabric'
|
||||
s.dependency 'React-FabricImage'
|
||||
s.dependency 'React-debug'
|
||||
s.dependency 'React-utils'
|
||||
s.dependency 'React-featureflags'
|
||||
s.dependency 'React-RCTAppDelegate'
|
||||
if ReactNativeCoreUtils.build_rncore_from_source()
|
||||
|
||||
depend_on_js_engine(s)
|
||||
add_rn_third_party_dependencies(s)
|
||||
s.dependency \\"React-jsiexecutor\\"
|
||||
s.dependency \\"RCTRequired\\"
|
||||
s.dependency \\"RCTTypeSafety\\"
|
||||
s.dependency \\"React-Core\\"
|
||||
s.dependency \\"React-jsi\\"
|
||||
s.dependency \\"ReactCommon/turbomodule/bridging\\"
|
||||
s.dependency \\"ReactCommon/turbomodule/core\\"
|
||||
s.dependency \\"React-NativeModulesApple\\"
|
||||
s.dependency 'React-graphics'
|
||||
s.dependency 'React-rendererdebug'
|
||||
s.dependency 'React-Fabric'
|
||||
s.dependency 'React-FabricImage'
|
||||
s.dependency 'React-debug'
|
||||
s.dependency 'React-utils'
|
||||
s.dependency 'React-featureflags'
|
||||
s.dependency 'React-RCTAppDelegate'
|
||||
|
||||
depend_on_js_engine(s)
|
||||
add_rn_third_party_dependencies(s)
|
||||
|
||||
else
|
||||
s.dependency 'React-Core-prebuilt'
|
||||
s.dependency 'ReactNativeDependencies'
|
||||
end
|
||||
|
||||
s.script_phases = {
|
||||
'name' => 'Generate Specs',
|
||||
|
||||
@@ -16,30 +16,53 @@ use_frameworks = ENV['USE_FRAMEWORKS'] != nil
|
||||
folly_compiler_flags = Helpers::Constants.folly_config[:compiler_flags]
|
||||
boost_compiler_flags = Helpers::Constants.boost_config[:compiler_flags]
|
||||
|
||||
header_search_paths = [
|
||||
"\"$(PODS_ROOT)/ReactNativeDependencies\"",
|
||||
"\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"",
|
||||
"\"$(PODS_ROOT)/Headers/Private/React-Fabric\"",
|
||||
"\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\"",
|
||||
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
|
||||
"\"$(PODS_TARGET_SRCROOT)\"",
|
||||
]
|
||||
header_search_paths = []
|
||||
framework_search_paths = []
|
||||
|
||||
if use_frameworks
|
||||
ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"])
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-FabricImage", "React_FabricImage", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-graphics", "React_graphics", ["react/renderer/graphics/platform/ios"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-debug", "React_debug", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-rendererdebug", "React_rendererdebug", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-utils", "React_utils", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-featureflags", "React_featureflags", []))
|
||||
.each { |search_path|
|
||||
header_search_paths << "\"#{search_path}\""
|
||||
}
|
||||
if ReactNativeCoreUtils.build_rncore_from_source()
|
||||
|
||||
header_search_paths = [
|
||||
"\"$(PODS_ROOT)/ReactNativeDependencies\"",
|
||||
"\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"",
|
||||
"\"$(PODS_ROOT)/Headers/Private/React-Fabric\"",
|
||||
"\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\"",
|
||||
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
|
||||
"\"$(PODS_TARGET_SRCROOT)\"",
|
||||
]
|
||||
|
||||
if use_frameworks
|
||||
ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"])
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-FabricImage", "React_FabricImage", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-graphics", "React_graphics", ["react/renderer/graphics/platform/ios"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-debug", "React_debug", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-rendererdebug", "React_rendererdebug", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-utils", "React_utils", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-featureflags", "React_featureflags", []))
|
||||
.each { |search_path|
|
||||
header_search_paths << "\"#{search_path}\""
|
||||
}
|
||||
end
|
||||
else
|
||||
header_search_paths = [
|
||||
"$(PODS_ROOT)/ReactNativeDependencies",
|
||||
"$(PODS_ROOT)/Headers/Public/React-Core-prebuilt/ReactCommon",
|
||||
"${PODS_ROOT}/Headers/Public/React-Core-prebuilt/react/nativemodule/core",
|
||||
"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components",
|
||||
"${PODS_ROOT}/Headers/Public/React-Core-prebuilt",
|
||||
"${PODS_ROOT}/Headers/Public/React-Core-prebuilt/RCTDeprecation",
|
||||
"${PODS_ROOT}/Headers/Public/React-Core-prebuilt/ReactCommon/yoga",
|
||||
"${PODS_ROOT}/Headers/Public/React-Core-prebuilt/ReactCommon/jsi",
|
||||
"${PODS_ROOT}/Headers/Public/React-Core-prebuilt/ReactCommon",
|
||||
"${PODS_ROOT}/Headers/Public/React-Core-prebuilt/Libraries/AppDelegate",
|
||||
"${PODS_ROOT}/Headers/Public/React-Core-prebuilt/Libraries",
|
||||
"${REACT_NATIVE_PATH}/ReactCommon/yoga",
|
||||
"${REACT_NATIVE_PATH}/ReactCommon/jsi",
|
||||
"$(REACT_NATIVE_PATH)/Libraries",
|
||||
"$(REACT_NATIVE_PATH)/Libraries/FBLazyVector",
|
||||
]
|
||||
end
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
@@ -61,25 +84,32 @@ Pod::Spec.new do |s|
|
||||
"OTHER_CPLUSPLUSFLAGS" => "$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}"
|
||||
}
|
||||
|
||||
s.dependency "React-jsiexecutor"
|
||||
s.dependency "RCTRequired"
|
||||
s.dependency "RCTTypeSafety"
|
||||
s.dependency "React-Core"
|
||||
s.dependency "React-jsi"
|
||||
s.dependency "ReactCommon/turbomodule/bridging"
|
||||
s.dependency "ReactCommon/turbomodule/core"
|
||||
s.dependency "React-NativeModulesApple"
|
||||
s.dependency 'React-graphics'
|
||||
s.dependency 'React-rendererdebug'
|
||||
s.dependency 'React-Fabric'
|
||||
s.dependency 'React-FabricImage'
|
||||
s.dependency 'React-debug'
|
||||
s.dependency 'React-utils'
|
||||
s.dependency 'React-featureflags'
|
||||
s.dependency 'React-RCTAppDelegate'
|
||||
if ReactNativeCoreUtils.build_rncore_from_source()
|
||||
|
||||
depend_on_js_engine(s)
|
||||
add_rn_third_party_dependencies(s)
|
||||
s.dependency "React-jsiexecutor"
|
||||
s.dependency "RCTRequired"
|
||||
s.dependency "RCTTypeSafety"
|
||||
s.dependency "React-Core"
|
||||
s.dependency "React-jsi"
|
||||
s.dependency "ReactCommon/turbomodule/bridging"
|
||||
s.dependency "ReactCommon/turbomodule/core"
|
||||
s.dependency "React-NativeModulesApple"
|
||||
s.dependency 'React-graphics'
|
||||
s.dependency 'React-rendererdebug'
|
||||
s.dependency 'React-Fabric'
|
||||
s.dependency 'React-FabricImage'
|
||||
s.dependency 'React-debug'
|
||||
s.dependency 'React-utils'
|
||||
s.dependency 'React-featureflags'
|
||||
s.dependency 'React-RCTAppDelegate'
|
||||
|
||||
depend_on_js_engine(s)
|
||||
add_rn_third_party_dependencies(s)
|
||||
|
||||
else
|
||||
s.dependency 'React-Core-prebuilt'
|
||||
s.dependency 'ReactNativeDependencies'
|
||||
end
|
||||
|
||||
s.script_phases = {
|
||||
'name' => 'Generate Specs',
|
||||
|
||||
@@ -269,6 +269,17 @@ const definitions: FeatureFlagDefinitions = {
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
enableIntersectionObserverEventLoopIntegration: {
|
||||
defaultValue: true,
|
||||
metadata: {
|
||||
dateAdded: '2025-04-16',
|
||||
description:
|
||||
'Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'experimentation',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
enableLayoutAnimationsOnAndroid: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
|
||||
+112
-5
@@ -69,6 +69,8 @@ const HEADERFILE_IGNORE_LIST = [
|
||||
'JsArgumentHelpers-inl.h',
|
||||
'RCTJscInstance.h',
|
||||
'RCTJSThreadManager.h',
|
||||
'YGEnums.h',
|
||||
'YGNode.h',
|
||||
];
|
||||
|
||||
function buildXCFrameworks(
|
||||
@@ -106,8 +108,9 @@ function buildXCFrameworks(
|
||||
} catch (error) {
|
||||
frameworkLog(
|
||||
`Error building XCFramework: ${error.message}. Check if the build was successful.`,
|
||||
'warning',
|
||||
'error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy header files from the headers folder that we used to build the swift package
|
||||
@@ -124,7 +127,22 @@ function buildXCFrameworks(
|
||||
);
|
||||
|
||||
// Create the module map file
|
||||
createModuleMapFile(outputPath, umbrellaHeaderFile);
|
||||
const moduleMapFile = createModuleMapFile(outputPath, umbrellaHeaderFile);
|
||||
if (!moduleMapFile) {
|
||||
frameworkLog(
|
||||
'Failed to create module map file. The XCFramework may not work correctly. Stopping.',
|
||||
'error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the platforms in the framework folder and copy modulemaps and headers into each platform folder
|
||||
linkArchFolders(
|
||||
outputPath,
|
||||
moduleMapFile,
|
||||
umbrellaHeaderFile,
|
||||
outputHeaderFiles,
|
||||
);
|
||||
|
||||
// Copy Symbols to symbols folder
|
||||
const symbolPaths = frameworkFolders.map(framework =>
|
||||
@@ -144,6 +162,91 @@ function buildXCFrameworks(
|
||||
}
|
||||
}
|
||||
|
||||
function linkArchFolders(
|
||||
outputPath /*:string*/,
|
||||
moduleMapFile /*:string*/,
|
||||
umbrellaHeaderFile /*:string*/,
|
||||
outputHeaderFiles /*: Array<string> */,
|
||||
) {
|
||||
frameworkLog('Linking modules and headers to platform folders...');
|
||||
const headerRootFolder = path.dirname(umbrellaHeaderFile);
|
||||
|
||||
fs.readdirSync(outputPath)
|
||||
.filter(folder => {
|
||||
const folderPath = path.join(outputPath, folder);
|
||||
return (
|
||||
fs.statSync(folderPath).isDirectory() &&
|
||||
folder !== 'Headers' &&
|
||||
folder !== 'Modules'
|
||||
);
|
||||
})
|
||||
.forEach(folder => {
|
||||
// Get full platform folder path
|
||||
const platformFolder = path.join(outputPath, folder);
|
||||
// Link the Modules folder into the platform folder
|
||||
const targetModulesFolder = path.join(
|
||||
platformFolder,
|
||||
'React.Framework',
|
||||
'Modules',
|
||||
);
|
||||
createFolderIfNotExists(targetModulesFolder);
|
||||
try {
|
||||
fs.linkSync(
|
||||
moduleMapFile,
|
||||
path.join(targetModulesFolder, path.basename(moduleMapFile)),
|
||||
);
|
||||
} catch (error) {
|
||||
frameworkLog(
|
||||
`Error copying module map file: ${error.message}. Check if the file exists.`,
|
||||
'error',
|
||||
);
|
||||
}
|
||||
// Copy headers folder into the platform folder
|
||||
const targetHeadersFolder = path.join(
|
||||
platformFolder,
|
||||
'React.Framework',
|
||||
'Headers',
|
||||
);
|
||||
// Link header files into the platform folder
|
||||
outputHeaderFiles.forEach(headerFile => {
|
||||
// Get the relative path of the header file based on the root folder
|
||||
const relativePath = path.relative(headerRootFolder, headerFile);
|
||||
// Create the target folder for the header file
|
||||
const targetFolder = path.join(
|
||||
targetHeadersFolder,
|
||||
path.dirname(relativePath),
|
||||
);
|
||||
// Create the target folder if it doesn't exist
|
||||
createFolderIfNotExists(targetFolder);
|
||||
// Link the header file to the target folder
|
||||
try {
|
||||
fs.linkSync(
|
||||
headerFile,
|
||||
path.join(targetFolder, path.basename(headerFile)),
|
||||
);
|
||||
} catch (error) {
|
||||
frameworkLog(
|
||||
`Error linking header file: ${error.message}. Check if the file exists.`,
|
||||
'error',
|
||||
);
|
||||
}
|
||||
});
|
||||
// Link the umbrella header file to the target headers folder
|
||||
try {
|
||||
const targetUmbrellaPath = path.join(
|
||||
targetHeadersFolder,
|
||||
'React-umbrella.h',
|
||||
);
|
||||
fs.linkSync(umbrellaHeaderFile, targetUmbrellaPath);
|
||||
} catch (error) {
|
||||
frameworkLog(
|
||||
`Error linking umbrella header file: ${error.message}. Check if the file exists.`,
|
||||
'error',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function copyHeaderFiles(
|
||||
headersSourceFolder /*: string */,
|
||||
outputPath /*: string */,
|
||||
@@ -249,7 +352,8 @@ function createUmbrellaHeaderFile(
|
||||
return umbrellaHeaderFile;
|
||||
}
|
||||
|
||||
const cppHeaderRegex = /(#include|#import)\s*<[^.>]+>|\bnamespace\s+[\w:]+::/;
|
||||
const cppHeaderRegex =
|
||||
/(#include|#import)\s*<[^.>]+>|\bnamespace\s+[\w:]+::|NS_ENUM\s*\([^)]*\)|NS_OPTIONS\s*\([^)]*\)|typedef\s+enum|static\s+const|@interface|static\s+inline/;
|
||||
|
||||
function isCppHeaderFile(headerFilePath /*: string */) /*: boolean */ {
|
||||
// Check if there is a cpp or mm file with the same name
|
||||
@@ -299,21 +403,24 @@ function createModuleMapFile(
|
||||
// Create the module map file
|
||||
const moduleMapFile = path.join(moduleMapFolder, 'module.modulemap');
|
||||
frameworkLog('Creating module map file: ' + moduleMapFile);
|
||||
const moduleMapContent = `module React {
|
||||
umbrella header "../Headers/${path.basename(umbrellaPath)}"
|
||||
const moduleMapContent = `framework module React {
|
||||
umbrella header "${path.basename(umbrellaPath)}"
|
||||
export *
|
||||
module * { export * }
|
||||
}`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(moduleMapFile, moduleMapContent);
|
||||
return moduleMapFile;
|
||||
} catch (error) {
|
||||
frameworkLog(
|
||||
`Error creating module map file: ${error.message}. Check if the file exists.`,
|
||||
'warning',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanPlatformFolders(outputPath /*:string*/) {
|
||||
if (!fs.existsSync(outputPath)) {
|
||||
return;
|
||||
|
||||
@@ -19,6 +19,7 @@ require_relative './cocoapods/runtime.rb'
|
||||
require_relative './cocoapods/helpers.rb'
|
||||
require_relative './cocoapods/privacy_manifest_utils.rb'
|
||||
require_relative './cocoapods/spm.rb'
|
||||
require_relative './cocoapods/rncore.rb'
|
||||
# Importing to expose use_native_modules!
|
||||
require_relative './cocoapods/autolinking.rb'
|
||||
|
||||
@@ -105,80 +106,96 @@ def use_react_native! (
|
||||
# Update ReactNativeDependencies so that we can easily switch between source and prebuilt
|
||||
ReactNativeDependenciesUtils.setup_react_native_dependencies(prefix, react_native_version)
|
||||
|
||||
# Update ReactNativeCoreUtils so that we can easily switch between source and prebuilt
|
||||
ReactNativeCoreUtils.setup_rncore(prefix, react_native_version)
|
||||
|
||||
Pod::UI.puts "Configuring the target with the #{new_arch_enabled ? "New" : "Legacy"} Architecture\n"
|
||||
|
||||
# The Pods which should be included in all projects
|
||||
pod 'FBLazyVector', :path => "#{prefix}/Libraries/FBLazyVector"
|
||||
pod 'RCTRequired', :path => "#{prefix}/Libraries/Required"
|
||||
pod 'RCTTypeSafety', :path => "#{prefix}/Libraries/TypeSafety", :modular_headers => true
|
||||
pod 'React', :path => "#{prefix}/"
|
||||
pod 'React-Core', :path => "#{prefix}/"
|
||||
pod 'React-CoreModules', :path => "#{prefix}/React/CoreModules"
|
||||
pod 'React-RCTRuntime', :path => "#{prefix}/React/Runtime"
|
||||
pod 'React-RCTAppDelegate', :path => "#{prefix}/Libraries/AppDelegate"
|
||||
pod 'React-RCTActionSheet', :path => "#{prefix}/Libraries/ActionSheetIOS"
|
||||
pod 'React-RCTAnimation', :path => "#{prefix}/Libraries/NativeAnimation"
|
||||
pod 'React-RCTBlob', :path => "#{prefix}/Libraries/Blob"
|
||||
pod 'React-RCTImage', :path => "#{prefix}/Libraries/Image"
|
||||
pod 'React-RCTLinking', :path => "#{prefix}/Libraries/LinkingIOS"
|
||||
pod 'React-RCTNetwork', :path => "#{prefix}/Libraries/Network"
|
||||
pod 'React-RCTSettings', :path => "#{prefix}/Libraries/Settings"
|
||||
pod 'React-RCTText', :path => "#{prefix}/Libraries/Text"
|
||||
pod 'React-RCTVibration', :path => "#{prefix}/Libraries/Vibration"
|
||||
pod 'React-Core/RCTWebSocket', :path => "#{prefix}/"
|
||||
pod 'React-cxxreact', :path => "#{prefix}/ReactCommon/cxxreact"
|
||||
pod 'React-debug', :path => "#{prefix}/ReactCommon/react/debug"
|
||||
pod 'React-utils', :path => "#{prefix}/ReactCommon/react/utils"
|
||||
pod 'React-featureflags', :path => "#{prefix}/ReactCommon/react/featureflags"
|
||||
pod 'React-featureflagsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/featureflags"
|
||||
pod 'React-microtasksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/microtasks"
|
||||
pod 'React-idlecallbacksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/idlecallbacks"
|
||||
pod 'React-domnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/dom"
|
||||
pod 'React-defaultsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/defaults"
|
||||
pod 'React-Mapbuffer', :path => "#{prefix}/ReactCommon"
|
||||
pod 'React-jserrorhandler', :path => "#{prefix}/ReactCommon/jserrorhandler"
|
||||
pod 'RCTDeprecation', :path => "#{prefix}/ReactApple/Libraries/RCTFoundation/RCTDeprecation"
|
||||
pod 'React-RCTFBReactNativeSpec', :path => "#{prefix}/React"
|
||||
pod 'React-jsi', :path => "#{prefix}/ReactCommon/jsi"
|
||||
if ReactNativeCoreUtils.build_rncore_from_source()
|
||||
# The Pods which should be included in all projects
|
||||
pod 'FBLazyVector', :path => "#{prefix}/Libraries/FBLazyVector"
|
||||
pod 'RCTRequired', :path => "#{prefix}/Libraries/Required"
|
||||
pod 'RCTTypeSafety', :path => "#{prefix}/Libraries/TypeSafety", :modular_headers => true
|
||||
pod 'React', :path => "#{prefix}/"
|
||||
pod 'React-Core', :path => "#{prefix}/"
|
||||
pod 'React-CoreModules', :path => "#{prefix}/React/CoreModules"
|
||||
pod 'React-RCTRuntime', :path => "#{prefix}/React/Runtime"
|
||||
pod 'React-RCTAppDelegate', :path => "#{prefix}/Libraries/AppDelegate"
|
||||
pod 'React-RCTActionSheet', :path => "#{prefix}/Libraries/ActionSheetIOS"
|
||||
pod 'React-RCTAnimation', :path => "#{prefix}/Libraries/NativeAnimation"
|
||||
pod 'React-RCTBlob', :path => "#{prefix}/Libraries/Blob"
|
||||
pod 'React-RCTImage', :path => "#{prefix}/Libraries/Image"
|
||||
pod 'React-RCTLinking', :path => "#{prefix}/Libraries/LinkingIOS"
|
||||
pod 'React-RCTNetwork', :path => "#{prefix}/Libraries/Network"
|
||||
pod 'React-RCTSettings', :path => "#{prefix}/Libraries/Settings"
|
||||
pod 'React-RCTText', :path => "#{prefix}/Libraries/Text"
|
||||
pod 'React-RCTVibration', :path => "#{prefix}/Libraries/Vibration"
|
||||
pod 'React-Core/RCTWebSocket', :path => "#{prefix}/"
|
||||
pod 'React-cxxreact', :path => "#{prefix}/ReactCommon/cxxreact"
|
||||
pod 'React-debug', :path => "#{prefix}/ReactCommon/react/debug"
|
||||
pod 'React-utils', :path => "#{prefix}/ReactCommon/react/utils"
|
||||
pod 'React-featureflags', :path => "#{prefix}/ReactCommon/react/featureflags"
|
||||
pod 'React-featureflagsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/featureflags"
|
||||
pod 'React-microtasksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/microtasks"
|
||||
pod 'React-idlecallbacksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/idlecallbacks"
|
||||
pod 'React-domnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/dom"
|
||||
pod 'React-defaultsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/defaults"
|
||||
pod 'React-Mapbuffer', :path => "#{prefix}/ReactCommon"
|
||||
pod 'React-jserrorhandler', :path => "#{prefix}/ReactCommon/jserrorhandler"
|
||||
pod 'RCTDeprecation', :path => "#{prefix}/ReactApple/Libraries/RCTFoundation/RCTDeprecation"
|
||||
pod 'React-RCTFBReactNativeSpec', :path => "#{prefix}/React"
|
||||
pod 'React-jsi', :path => "#{prefix}/ReactCommon/jsi"
|
||||
|
||||
if hermes_enabled
|
||||
setup_hermes!(:react_native_path => prefix)
|
||||
end
|
||||
if hermes_enabled
|
||||
setup_hermes!(:react_native_path => prefix)
|
||||
end
|
||||
|
||||
pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor"
|
||||
pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector-modern"
|
||||
pod 'React-jsitooling', :path => "#{prefix}/ReactCommon/jsitooling"
|
||||
pod 'React-jsinspectorcdp', :path => "#{prefix}/ReactCommon/jsinspector-modern/cdp"
|
||||
pod 'React-jsinspectornetwork', :path => "#{prefix}/ReactCommon/jsinspector-modern/network"
|
||||
pod 'React-jsinspectortracing', :path => "#{prefix}/ReactCommon/jsinspector-modern/tracing"
|
||||
pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor"
|
||||
pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector-modern"
|
||||
pod 'React-jsitooling', :path => "#{prefix}/ReactCommon/jsitooling"
|
||||
pod 'React-jsinspectorcdp', :path => "#{prefix}/ReactCommon/jsinspector-modern/cdp"
|
||||
pod 'React-jsinspectornetwork', :path => "#{prefix}/ReactCommon/jsinspector-modern/network"
|
||||
pod 'React-jsinspectortracing', :path => "#{prefix}/ReactCommon/jsinspector-modern/tracing"
|
||||
|
||||
pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker"
|
||||
pod 'React-performancetimeline', :path => "#{prefix}/ReactCommon/react/performance/timeline"
|
||||
pod 'React-timing', :path => "#{prefix}/ReactCommon/react/timing"
|
||||
pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor"
|
||||
pod 'React-runtimescheduler', :path => "#{prefix}/ReactCommon/react/renderer/runtimescheduler"
|
||||
pod 'React-renderercss', :path => "#{prefix}/ReactCommon/react/renderer/css"
|
||||
pod 'React-rendererdebug', :path => "#{prefix}/ReactCommon/react/renderer/debug"
|
||||
pod 'React-rendererconsistency', :path => "#{prefix}/ReactCommon/react/renderer/consistency"
|
||||
pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger"
|
||||
pod 'React-oscompat', :path => "#{prefix}/ReactCommon/oscompat"
|
||||
pod 'React-logger', :path => "#{prefix}/ReactCommon/logger"
|
||||
pod 'ReactCommon/turbomodule/core', :path => "#{prefix}/ReactCommon", :modular_headers => true
|
||||
pod 'React-NativeModulesApple', :path => "#{prefix}/ReactCommon/react/nativemodule/core/platform/ios", :modular_headers => true
|
||||
pod 'Yoga', :path => "#{prefix}/ReactCommon/yoga", :modular_headers => true
|
||||
pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker"
|
||||
pod 'React-performancetimeline', :path => "#{prefix}/ReactCommon/react/performance/timeline"
|
||||
pod 'React-timing', :path => "#{prefix}/ReactCommon/react/timing"
|
||||
pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor"
|
||||
pod 'React-runtimescheduler', :path => "#{prefix}/ReactCommon/react/renderer/runtimescheduler"
|
||||
pod 'React-renderercss', :path => "#{prefix}/ReactCommon/react/renderer/css"
|
||||
pod 'React-rendererdebug', :path => "#{prefix}/ReactCommon/react/renderer/debug"
|
||||
pod 'React-rendererconsistency', :path => "#{prefix}/ReactCommon/react/renderer/consistency"
|
||||
pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger"
|
||||
pod 'React-oscompat', :path => "#{prefix}/ReactCommon/oscompat"
|
||||
pod 'React-logger', :path => "#{prefix}/ReactCommon/logger"
|
||||
pod 'ReactCommon/turbomodule/core', :path => "#{prefix}/ReactCommon", :modular_headers => true
|
||||
pod 'React-NativeModulesApple', :path => "#{prefix}/ReactCommon/react/nativemodule/core/platform/ios", :modular_headers => true
|
||||
pod 'Yoga', :path => "#{prefix}/ReactCommon/yoga", :modular_headers => true
|
||||
setup_fabric!(:react_native_path => prefix)
|
||||
setup_bridgeless!(:react_native_path => prefix, :use_hermes => hermes_enabled)
|
||||
|
||||
if ReactNativeDependenciesUtils.build_react_native_deps_from_source()
|
||||
pod 'DoubleConversion', :podspec => "#{prefix}/third-party-podspecs/DoubleConversion.podspec"
|
||||
pod 'glog', :podspec => "#{prefix}/third-party-podspecs/glog.podspec"
|
||||
pod 'boost', :podspec => "#{prefix}/third-party-podspecs/boost.podspec"
|
||||
pod 'fast_float', :podspec => "#{prefix}/third-party-podspecs/fast_float.podspec"
|
||||
pod 'fmt', :podspec => "#{prefix}/third-party-podspecs/fmt.podspec", :modular_headers => true
|
||||
pod 'RCT-Folly', :podspec => "#{prefix}/third-party-podspecs/RCT-Folly.podspec", :modular_headers => true
|
||||
pod 'SocketRocket', "~> #{Helpers::Constants::socket_rocket_config[:version]}", :modular_headers => true
|
||||
if ReactNativeDependenciesUtils.build_react_native_deps_from_source()
|
||||
pod 'DoubleConversion', :podspec => "#{prefix}/third-party-podspecs/DoubleConversion.podspec"
|
||||
pod 'glog', :podspec => "#{prefix}/third-party-podspecs/glog.podspec"
|
||||
pod 'boost', :podspec => "#{prefix}/third-party-podspecs/boost.podspec"
|
||||
pod 'fast_float', :podspec => "#{prefix}/third-party-podspecs/fast_float.podspec"
|
||||
pod 'fmt', :podspec => "#{prefix}/third-party-podspecs/fmt.podspec", :modular_headers => true
|
||||
pod 'RCT-Folly', :podspec => "#{prefix}/third-party-podspecs/RCT-Folly.podspec", :modular_headers => true
|
||||
pod 'SocketRocket', "~> #{Helpers::Constants::socket_rocket_config[:version]}", :modular_headers => true
|
||||
else
|
||||
pod 'ReactNativeDependencies', :podspec => "#{prefix}/third-party-podspecs/ReactNativeDependencies.podspec", :modular_headers => true
|
||||
end
|
||||
else
|
||||
# Install prebuilt React Native Core and React Native Dependencies
|
||||
ReactNativeCoreUtils.rncore_log("Using React Native Core and React Native Dependencies prebuilt versions.")
|
||||
pod 'React-Core-prebuilt', :podspec => "#{prefix}/React-Core-prebuilt.podspec", :modular_headers => true
|
||||
pod 'ReactNativeDependencies', :podspec => "#{prefix}/third-party-podspecs/ReactNativeDependencies.podspec", :modular_headers => true
|
||||
pod 'hermes-engine', :podspec => "#{prefix}/sdks/hermes-engine/hermes-engine.podspec"
|
||||
end
|
||||
|
||||
pod 'ReactCodegen', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true
|
||||
pod 'ReactAppDependencyProvider', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true
|
||||
# Not needed, but run_codegen expects this to be set.
|
||||
folly_config = get_folly_config()
|
||||
run_codegen!(
|
||||
app_path,
|
||||
@@ -193,15 +210,6 @@ def use_react_native! (
|
||||
:folly_version => folly_config[:version]
|
||||
)
|
||||
|
||||
pod 'ReactCodegen', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true
|
||||
pod 'ReactAppDependencyProvider', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true
|
||||
|
||||
# Always need fabric to access the RCTSurfacePresenterBridgeAdapter which allow to enable the RuntimeScheduler
|
||||
# If the New Arch is turned off, we will use the Old Renderer, though.
|
||||
# RNTester always installed Fabric, this change is required to make the template work.
|
||||
setup_fabric!(:react_native_path => prefix)
|
||||
setup_bridgeless!(:react_native_path => prefix, :use_hermes => hermes_enabled)
|
||||
|
||||
pods_to_update = LocalPodspecPatch.pods_to_update(:react_native_path => prefix)
|
||||
if !pods_to_update.empty?
|
||||
if Pod::Lockfile.public_instance_methods.include?(:detect_changes_with_podfile)
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/**
|
||||
* 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 strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {ViewStyleProp} from '../../../../Libraries/StyleSheet/StyleSheet';
|
||||
import type {NativeSyntheticEvent} from '../../../../Libraries/Types/CoreEventTypes';
|
||||
import type ReadOnlyElement from '../../webapis/dom/nodes/ReadOnlyElement';
|
||||
import type {NativeModeChangeEvent} from './VirtualViewNativeComponent';
|
||||
|
||||
import StyleSheet from '../../../../Libraries/StyleSheet/StyleSheet';
|
||||
import VirtualViewNativeComponent from './VirtualViewNativeComponent';
|
||||
import nullthrows from 'nullthrows';
|
||||
import * as React from 'react';
|
||||
import {startTransition, useState} from 'react';
|
||||
|
||||
// @see VirtualViewNativeComponent
|
||||
export enum VirtualViewMode {
|
||||
Visible = 0,
|
||||
Prerender = 1,
|
||||
Hidden = 2,
|
||||
}
|
||||
|
||||
export type Rect = $ReadOnly<{
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
}>;
|
||||
|
||||
export type ModeChangeEvent = $ReadOnly<{
|
||||
...Omit<NativeModeChangeEvent, 'mode'>,
|
||||
mode: VirtualViewMode,
|
||||
target: ReadOnlyElement,
|
||||
}>;
|
||||
|
||||
type VirtualViewComponent = component(
|
||||
children?: React.Node,
|
||||
nativeID?: string,
|
||||
ref?: ?React.RefSetter<React.ElementRef<typeof VirtualViewNativeComponent>>,
|
||||
style?: ?ViewStyleProp,
|
||||
onModeChange?: (event: ModeChangeEvent) => void,
|
||||
);
|
||||
|
||||
type HiddenHeight = number;
|
||||
const NotHidden = null;
|
||||
|
||||
type State = HiddenHeight | typeof NotHidden;
|
||||
|
||||
function createVirtualView(initialState: State): VirtualViewComponent {
|
||||
const initialHidden = initialState !== NotHidden;
|
||||
|
||||
component VirtualView(
|
||||
children?: React.Node,
|
||||
nativeID?: string,
|
||||
ref?: ?React.RefSetter<React.ElementRef<typeof VirtualViewNativeComponent>>,
|
||||
style?: ?ViewStyleProp,
|
||||
onModeChange?: (event: ModeChangeEvent) => void,
|
||||
) {
|
||||
const [state, setState] = useState<State>(initialState);
|
||||
if (__DEV__) {
|
||||
_logs.states?.push(state);
|
||||
}
|
||||
const isHidden = state !== NotHidden;
|
||||
|
||||
const handleModeChange = (
|
||||
event: NativeSyntheticEvent<NativeModeChangeEvent>,
|
||||
) => {
|
||||
const mode = nullthrows(VirtualViewMode.cast(event.nativeEvent.mode));
|
||||
const emitModeChange =
|
||||
onModeChange == null
|
||||
? null
|
||||
: onModeChange.bind(null, {
|
||||
mode,
|
||||
// $FlowIgnore[incompatible-cast]
|
||||
target: event.currentTarget as ReadOnlyElement,
|
||||
targetRect: event.nativeEvent.targetRect,
|
||||
thresholdRect: event.nativeEvent.thresholdRect,
|
||||
});
|
||||
|
||||
switch (mode) {
|
||||
case VirtualViewMode.Visible: {
|
||||
setState(NotHidden);
|
||||
emitModeChange?.();
|
||||
break;
|
||||
}
|
||||
case VirtualViewMode.Prerender: {
|
||||
startTransition(() => {
|
||||
setState(NotHidden);
|
||||
emitModeChange?.();
|
||||
});
|
||||
break;
|
||||
}
|
||||
case VirtualViewMode.Hidden: {
|
||||
const {height} = event.nativeEvent.targetRect;
|
||||
startTransition(() => {
|
||||
setState(height as HiddenHeight);
|
||||
emitModeChange?.();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<VirtualViewNativeComponent
|
||||
initialHidden={initialHidden}
|
||||
nativeID={nativeID}
|
||||
ref={ref}
|
||||
style={
|
||||
isHidden
|
||||
? StyleSheet.compose(style, {
|
||||
height: Math.abs(nullthrows(state) as HiddenHeight),
|
||||
})
|
||||
: style
|
||||
}
|
||||
onModeChange={handleModeChange}>
|
||||
{isHidden ? null : children}
|
||||
</VirtualViewNativeComponent>
|
||||
);
|
||||
}
|
||||
return VirtualView;
|
||||
}
|
||||
|
||||
export default createVirtualView(NotHidden) as VirtualViewComponent;
|
||||
|
||||
export function createHiddenVirtualView(height: number): VirtualViewComponent {
|
||||
return createVirtualView(height as HiddenHeight);
|
||||
}
|
||||
|
||||
export const _logs: {states?: Array<State>} = {};
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* 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 strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
|
||||
import type {
|
||||
DirectEventHandler,
|
||||
Double,
|
||||
Int32,
|
||||
} from '../../../../Libraries/Types/CodegenTypes';
|
||||
import type {HostComponent} from '../../types/HostComponent';
|
||||
|
||||
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
|
||||
|
||||
export type NativeModeChangeEvent = $ReadOnly<{
|
||||
/**
|
||||
* Virtualization mode of the target view.
|
||||
*
|
||||
* - `0`: Target view is visible. (default)
|
||||
* - `1`: Target view is hidden, but can be prerendered.
|
||||
* - `2`: Target view is hidden.
|
||||
*
|
||||
* WORKAROUND: As of this writing, codegen doesn't support enums, so we need
|
||||
* to convert `number` into an enum in `VirtualView`.
|
||||
*/
|
||||
mode: Int32,
|
||||
|
||||
/**
|
||||
* Rect of the target view, relative to the nearest ancestor scroll container.
|
||||
*/
|
||||
targetRect: $ReadOnly<{
|
||||
x: Double,
|
||||
y: Double,
|
||||
width: Double,
|
||||
height: Double,
|
||||
}>,
|
||||
|
||||
/**
|
||||
* Rect of the threshold that determines the mode of the target view, relative
|
||||
* to the nearest ancestor scroll container.
|
||||
*
|
||||
* - `Visible`: Rect in which the target view is visible.
|
||||
* - `Prerender`: Rect in which the target view is prerendered.
|
||||
* - `Hidden`: Unused, without any guarantees.
|
||||
*
|
||||
* This can be used to determine whether and how much new content to render.
|
||||
*/
|
||||
thresholdRect: $ReadOnly<{
|
||||
x: Double,
|
||||
y: Double,
|
||||
width: Double,
|
||||
height: Double,
|
||||
}>,
|
||||
}>;
|
||||
|
||||
type VirtualViewNativeProps = $ReadOnly<{
|
||||
...ViewProps,
|
||||
|
||||
// Whether the initial mode should be `Hidden`.
|
||||
initialHidden?: boolean,
|
||||
|
||||
// Events
|
||||
onModeChange?: ?DirectEventHandler<NativeModeChangeEvent>,
|
||||
}>;
|
||||
|
||||
export default codegenNativeComponent<VirtualViewNativeProps>('VirtualView', {
|
||||
interfaceOnly: true,
|
||||
}) as HostComponent<VirtualViewNativeProps>;
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @fantom_react_fb_flags enableEagerAlternateStateNodeCleanup:true
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
|
||||
|
||||
import {createShadowNodeReferenceCountingRef} from '../../../__tests__/utilities/ShadowNodeReferenceCounter';
|
||||
import VirtualView, {_logs, VirtualViewMode} from '../VirtualView';
|
||||
import {dispatchModeChangeEvent} from './VirtualView-itest';
|
||||
import * as Fantom from '@react-native/fantom';
|
||||
import * as React from 'react';
|
||||
import {createRef} from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
beforeEach(() => {
|
||||
_logs.states = [];
|
||||
});
|
||||
|
||||
describe('VirtualView with enableEagerAlternateStateNodeCleanup flag', () => {
|
||||
test('does not retain shadow node after becoming hidden', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
const [getReferenceCount, childRef] =
|
||||
createShadowNodeReferenceCountingRef();
|
||||
const viewRef = createRef<React.RefOf<VirtualView>>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<VirtualView ref={viewRef}>
|
||||
<Text ref={childRef}>Child</Text>
|
||||
</VirtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(getReferenceCount()).toBeGreaterThan(0);
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden);
|
||||
|
||||
expect(getReferenceCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
-384
@@ -1,384 +0,0 @@
|
||||
/**
|
||||
* 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 strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
|
||||
|
||||
import type {Rect} from '../VirtualView';
|
||||
import type {NativeModeChangeEvent} from '../VirtualViewNativeComponent';
|
||||
|
||||
import ensureInstance from '../../../__tests__/utilities/ensureInstance';
|
||||
import isUnreachable from '../../../__tests__/utilities/isUnreachable';
|
||||
import {getNodeFromPublicInstance} from '../../../../../Libraries/ReactPrivate/ReactNativePrivateInterface';
|
||||
import ReactNativeElement from '../../../webapis/dom/nodes/ReactNativeElement';
|
||||
import VirtualView, {_logs, VirtualViewMode} from '../VirtualView';
|
||||
import * as Fantom from '@react-native/fantom';
|
||||
import nullthrows from 'nullthrows';
|
||||
import * as React from 'react';
|
||||
import {createRef, useEffect, useState} from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
beforeEach(() => {
|
||||
_logs.states = [];
|
||||
});
|
||||
|
||||
describe('mode changes', () => {
|
||||
test('changes mode from visible to hidden', () => {
|
||||
const root = Fantom.createRoot();
|
||||
const viewRef = createRef<React.RefOf<VirtualView>>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<VirtualView ref={viewRef}>
|
||||
<Text>Child</Text>
|
||||
</VirtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(_logs.states).toHaveLength(1);
|
||||
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
|
||||
<rn-virtualView>
|
||||
<rn-paragraph>Child</rn-paragraph>
|
||||
</rn-virtualView>,
|
||||
);
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden);
|
||||
|
||||
expect(_logs.states).toHaveLength(2);
|
||||
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
|
||||
<rn-virtualView />,
|
||||
);
|
||||
});
|
||||
|
||||
test('changes mode from hidden to visible', () => {
|
||||
const root = Fantom.createRoot();
|
||||
const viewRef = createRef<React.RefOf<VirtualView>>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<VirtualView ref={viewRef}>
|
||||
<Text>Child</Text>
|
||||
</VirtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden);
|
||||
|
||||
expect(_logs.states).toHaveLength(2);
|
||||
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
|
||||
<rn-virtualView />,
|
||||
);
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Visible);
|
||||
|
||||
expect(_logs.states).toHaveLength(3);
|
||||
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
|
||||
<rn-virtualView>
|
||||
<rn-paragraph>Child</rn-paragraph>
|
||||
</rn-virtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
test('changes mode from prerender to visible', () => {
|
||||
const root = Fantom.createRoot();
|
||||
const viewRef = createRef<React.RefOf<VirtualView>>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<VirtualView ref={viewRef}>
|
||||
<Text>Child</Text>
|
||||
</VirtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Prerender);
|
||||
|
||||
expect(_logs.states).toHaveLength(1);
|
||||
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
|
||||
<rn-virtualView>
|
||||
<rn-paragraph>Child</rn-paragraph>
|
||||
</rn-virtualView>,
|
||||
);
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Visible);
|
||||
|
||||
// Expects `VirtualView` does not undergo a state update.
|
||||
expect(_logs.states).toHaveLength(1);
|
||||
expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
|
||||
<rn-virtualView>
|
||||
<rn-paragraph>Child</rn-paragraph>
|
||||
</rn-virtualView>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('styles', () => {
|
||||
test('does not set height when visible', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<VirtualView />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
|
||||
<rn-virtualView />,
|
||||
);
|
||||
});
|
||||
|
||||
test('does not set height when prerendered', () => {
|
||||
const root = Fantom.createRoot();
|
||||
const viewRef = createRef<React.RefOf<VirtualView>>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<VirtualView ref={viewRef} />);
|
||||
});
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Prerender);
|
||||
|
||||
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
|
||||
<rn-virtualView />,
|
||||
);
|
||||
});
|
||||
|
||||
test('sets height when hidden', () => {
|
||||
const root = Fantom.createRoot();
|
||||
const viewRef = createRef<React.RefOf<VirtualView>>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<VirtualView ref={viewRef} />);
|
||||
});
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden);
|
||||
|
||||
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
|
||||
<rn-virtualView height="100.000000" />,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('memory management', () => {
|
||||
test('does not retain memory after destroying the root', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
const {callbackRef, weakRefs} = createWeakRefCallback<>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<VirtualView>
|
||||
<Text ref={callbackRef}>Child</Text>
|
||||
</VirtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(weakRefs.length).toBe(1);
|
||||
expect(isUnreachable(weakRefs[0])).toBe(false);
|
||||
|
||||
Fantom.runTask(() => root.destroy());
|
||||
|
||||
expect(isUnreachable(weakRefs[0])).toBe(true);
|
||||
});
|
||||
|
||||
test('does not retain memory after unmounting', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
const {callbackRef, weakRefs} = createWeakRefCallback<>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<VirtualView>
|
||||
<Text ref={callbackRef}>Child</Text>
|
||||
</VirtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(weakRefs.length).toBe(1);
|
||||
expect(isUnreachable(weakRefs[0])).toBe(false);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<React.Fragment />);
|
||||
});
|
||||
|
||||
expect(isUnreachable(weakRefs[0])).toBe(true);
|
||||
});
|
||||
|
||||
test('does not retain instances after becoming hidden', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
const {callbackRef, weakRefs} = createWeakRefCallback<>();
|
||||
const viewRef = createRef<React.RefOf<VirtualView>>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<VirtualView ref={viewRef}>
|
||||
<Text ref={callbackRef}>Child</Text>
|
||||
</VirtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(weakRefs.length).toBe(1);
|
||||
expect(isUnreachable(weakRefs[0])).toBe(false);
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden);
|
||||
|
||||
expect(isUnreachable(weakRefs[0])).toBe(true);
|
||||
});
|
||||
|
||||
test('does not retain internal node after becoming hidden', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
let weakRef: ?WeakRef<interface {}>;
|
||||
const callbackRef = (instance: React.ElementRef<typeof Text> | null) => {
|
||||
if (instance !== null) {
|
||||
weakRef = new WeakRef(getNodeAsObjectFromPublicInstance(instance));
|
||||
}
|
||||
};
|
||||
const viewRef = createRef<React.RefOf<VirtualView>>();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<VirtualView ref={viewRef}>
|
||||
<Text ref={callbackRef}>Child</Text>
|
||||
</VirtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(weakRef).not.toBe(undefined);
|
||||
expect(isUnreachable(nullthrows(weakRef))).toBe(false);
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden);
|
||||
|
||||
expect(isUnreachable(nullthrows(weakRef))).toBe(true);
|
||||
});
|
||||
|
||||
test('does not retain state after becoming hidden', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
let weakRef: ?WeakRef<interface {}>;
|
||||
const viewRef = createRef<React.RefOf<VirtualView>>();
|
||||
|
||||
function MemoryAllocator({children}: {children: React.Node}): React.Node {
|
||||
const [state] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
weakRef = new WeakRef(state);
|
||||
return () => {
|
||||
// Do nothing.
|
||||
};
|
||||
}, [state]);
|
||||
return children;
|
||||
}
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<VirtualView ref={viewRef}>
|
||||
<MemoryAllocator>
|
||||
<Text>Child</Text>
|
||||
</MemoryAllocator>
|
||||
</VirtualView>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(weakRef).not.toBe(undefined);
|
||||
expect(isUnreachable(nullthrows(weakRef))).toBe(false);
|
||||
|
||||
dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden);
|
||||
|
||||
expect(isUnreachable(nullthrows(weakRef))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to reduce duplication of the mock event payload.
|
||||
*/
|
||||
export function dispatchModeChangeEvent(
|
||||
instance: mixed,
|
||||
mode: VirtualViewMode,
|
||||
): void {
|
||||
const targetRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
} as Rect;
|
||||
const prerenderRect = {
|
||||
x: -50,
|
||||
y: -200,
|
||||
width: 200,
|
||||
height: 400,
|
||||
} as Rect;
|
||||
const visibleRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 200,
|
||||
} as Rect;
|
||||
|
||||
let thresholdRect;
|
||||
switch (mode) {
|
||||
case VirtualViewMode.Visible: {
|
||||
thresholdRect = visibleRect;
|
||||
break;
|
||||
}
|
||||
case VirtualViewMode.Prerender: {
|
||||
thresholdRect = prerenderRect;
|
||||
break;
|
||||
}
|
||||
case VirtualViewMode.Hidden: {
|
||||
thresholdRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
} as Rect;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Fantom.dispatchNativeEvent(
|
||||
ensureInstance(instance, ReactNativeElement),
|
||||
'onModeChange',
|
||||
{
|
||||
mode: mode as number,
|
||||
targetRect,
|
||||
// $FlowIssue[incompatible-cast] - https://fburl.com/workplace/t8a3yvuo
|
||||
thresholdRect,
|
||||
} as NativeModeChangeEvent,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a callback ref that records instances using WeakRefs.
|
||||
*/
|
||||
function createWeakRefCallback<T: interface {} = interface {}>(): $ReadOnly<{
|
||||
weakRefs: $ReadOnlyArray<WeakRef<T>>,
|
||||
callbackRef: React.RefSetter<T>,
|
||||
}> {
|
||||
const weakRefs: Array<WeakRef<T>> = [];
|
||||
return {
|
||||
callbackRef(instance: T | null) {
|
||||
if (instance !== null) {
|
||||
weakRefs.push(new WeakRef(instance));
|
||||
}
|
||||
},
|
||||
weakRefs,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the shadow node via `instance.__internalInstanceHandle.stateNode.node`.
|
||||
*/
|
||||
function getNodeAsObjectFromPublicInstance(instance: mixed): interface {} {
|
||||
const node = getNodeFromPublicInstance(
|
||||
ensureInstance(instance, ReactNativeElement),
|
||||
);
|
||||
if (node == null || typeof node !== 'object') {
|
||||
throw new Error('Expected node to be an object, got: ' + typeof node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@@ -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<<fae0668a375c5bbc4afe2c7c9bc9f0ef>>
|
||||
* @generated SignedSource<<eab8c8d6490730084776ef01c46e0f9d>>
|
||||
* @flow strict
|
||||
* @noformat
|
||||
*/
|
||||
@@ -71,6 +71,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{
|
||||
enableFontScaleChangesUpdatingLayout: Getter<boolean>,
|
||||
enableIOSTextBaselineOffsetPerLine: Getter<boolean>,
|
||||
enableIOSViewClipToPaddingBox: Getter<boolean>,
|
||||
enableIntersectionObserverEventLoopIntegration: Getter<boolean>,
|
||||
enableLayoutAnimationsOnAndroid: Getter<boolean>,
|
||||
enableLayoutAnimationsOnIOS: Getter<boolean>,
|
||||
enableMainQueueCoordinatorOnIOS: Getter<boolean>,
|
||||
@@ -267,6 +268,10 @@ export const enableIOSTextBaselineOffsetPerLine: Getter<boolean> = createNativeF
|
||||
* iOS Views will clip to their padding box vs border box
|
||||
*/
|
||||
export const enableIOSViewClipToPaddingBox: Getter<boolean> = createNativeFlagGetter('enableIOSViewClipToPaddingBox', false);
|
||||
/**
|
||||
* Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.
|
||||
*/
|
||||
export const enableIntersectionObserverEventLoopIntegration: Getter<boolean> = createNativeFlagGetter('enableIntersectionObserverEventLoopIntegration', true);
|
||||
/**
|
||||
* When enabled, LayoutAnimations API will animate state changes on Android.
|
||||
*/
|
||||
|
||||
+2
-1
@@ -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<<72695837632a7591378e84e14b3a9134>>
|
||||
* @generated SignedSource<<d4a7cb36fff440066957a7bba5a182fd>>
|
||||
* @flow strict
|
||||
* @noformat
|
||||
*/
|
||||
@@ -46,6 +46,7 @@ export interface Spec extends TurboModule {
|
||||
+enableFontScaleChangesUpdatingLayout?: () => boolean;
|
||||
+enableIOSTextBaselineOffsetPerLine?: () => boolean;
|
||||
+enableIOSViewClipToPaddingBox?: () => boolean;
|
||||
+enableIntersectionObserverEventLoopIntegration?: () => boolean;
|
||||
+enableLayoutAnimationsOnAndroid?: () => boolean;
|
||||
+enableLayoutAnimationsOnIOS?: () => boolean;
|
||||
+enableMainQueueCoordinatorOnIOS?: () => boolean;
|
||||
|
||||
+1
@@ -4,6 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @fantom_flags enableIntersectionObserverEventLoopIntegration:*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user