diff --git a/patches/react-native/details.md b/patches/react-native/details.md index 6503468ae6208..413aa3abfa412 100644 --- a/patches/react-native/details.md +++ b/patches/react-native/details.md @@ -258,3 +258,9 @@ - Reason: Fixes a Fabric regression where VoiceOver on iOS only announces "expanded" but never "collapsed" for elements with `accessibilityState.expanded`. In `RCTViewComponentView.mm`, the code uses `value_or(false)` which skips the announcement entirely when `expanded` is `false`. This patch changes the logic to use `has_value()` and correctly announce both "expanded" and "collapsed" states, matching the old architecture (Paper) behavior. - Upstream PR/issue: https://github.com/facebook/react-native/issues/56296 - E/App issue: [#76929](https://github.com/Expensify/App/issues/76929) + +### [react-native+0.83.1+035+fix-pressability-new-arch.patch](react-native+0.83.1+035+fix-pressability-new-arch.patch) + +- Reason: Fixes an Android-specific issue (reproducible on certain Samsung models) where `onPress` events do not trigger for `Pressable` components when used inside a `Tooltip`. The root cause is that in the new architecture, `Pressability.measure()` reads stale layout information from the shadow tree instead of the actual native view hierarchy. This patch introduces a new `measureAsyncOnUI` method that measures the view asynchronously using the native layout hierarchy on the UI thread, bypassing stale shadow tree data. +- Upstream PR/issue: [facebook/react-native#51835](https://github.com/facebook/react-native/pull/51835) +- E/App issue: [#59953](https://github.com/Expensify/App/issues/59953) diff --git a/patches/react-native/react-native+0.83.1+035+fix-pressability-new-arch.patch b/patches/react-native/react-native+0.83.1+035+fix-pressability-new-arch.patch new file mode 100644 index 0000000000000..2a0267f1e2b36 --- /dev/null +++ b/patches/react-native/react-native+0.83.1+035+fix-pressability-new-arch.patch @@ -0,0 +1,596 @@ +diff --git a/node_modules/react-native/Libraries/Pressability/Pressability.js b/node_modules/react-native/Libraries/Pressability/Pressability.js +index ff70d67..2c5d3b1 100644 +--- a/node_modules/react-native/Libraries/Pressability/Pressability.js ++++ b/node_modules/react-native/Libraries/Pressability/Pressability.js +@@ -805,7 +805,11 @@ export default class Pressability { + if (typeof this._responderID === 'number') { + UIManager.measure(this._responderID, this._measureCallback); + } else { +- this._responderID.measure(this._measureCallback); ++ if (typeof this._responderID.measureAsyncOnUI === 'function') { ++ this._responderID.measureAsyncOnUI(this._measureCallback); ++ } else { ++ this._responderID.measure(this._measureCallback); ++ } + } + } + +diff --git a/node_modules/react-native/React/Fabric/Mounting/RCTMountingManager.h b/node_modules/react-native/React/Fabric/Mounting/RCTMountingManager.h +index ceac3ee..15dbff9 100644 +--- a/node_modules/react-native/React/Fabric/Mounting/RCTMountingManager.h ++++ b/node_modules/react-native/React/Fabric/Mounting/RCTMountingManager.h +@@ -69,6 +69,10 @@ NS_ASSUME_NONNULL_BEGIN + - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag + changedProps:(folly::dynamic)props + componentDescriptor:(const facebook::react::ComponentDescriptor &)componentDescriptor; ++ ++- (void)measureAsyncOnUI:(ReactTag)reactTag ++ rootView:(UIView *)rootView ++ callback:(const std::function &)callback; + @end + + NS_ASSUME_NONNULL_END +diff --git a/node_modules/react-native/React/Fabric/Mounting/RCTMountingManager.mm b/node_modules/react-native/React/Fabric/Mounting/RCTMountingManager.mm +index 1dbf9c5..f0e0a00 100644 +--- a/node_modules/react-native/React/Fabric/Mounting/RCTMountingManager.mm ++++ b/node_modules/react-native/React/Fabric/Mounting/RCTMountingManager.mm +@@ -347,4 +347,30 @@ - (void)synchronouslyDispatchAccessbilityEventOnUIThread:(ReactTag)reactTag even + } + } + ++- (void)measureAsyncOnUI:(ReactTag)reactTag rootView:(UIView*)rootView callback:(const std::function &)callback { ++ RCTAssertMainQueue(); ++ ++ UIView *view = [self->_componentViewRegistry findComponentViewWithTag:reactTag]; ++ if (!view) { ++ // this view was probably collapsed out ++ RCTLogWarn(@"measure cannot find view with tag #%ld", (long)reactTag); ++ callback(folly::dynamic::array(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); ++ return; ++ } ++ ++ // By convention, all coordinates, whether they be touch coordinates, or ++ // measurement coordinates are with respect to the root view. ++ CGRect frame = view.frame; ++ CGRect globalBounds = [view convertRect:view.bounds toView:rootView]; ++ ++ callback( ++ folly::dynamic::array(frame.origin.x, ++ frame.origin.y, ++ globalBounds.size.width, ++ globalBounds.size.height, ++ globalBounds.origin.x, ++ globalBounds.origin.y) ++ ); ++} ++ + @end +diff --git a/node_modules/react-native/React/Fabric/RCTScheduler.h b/node_modules/react-native/React/Fabric/RCTScheduler.h +index ed58539..8c549b3 100644 +--- a/node_modules/react-native/React/Fabric/RCTScheduler.h ++++ b/node_modules/react-native/React/Fabric/RCTScheduler.h +@@ -43,6 +43,9 @@ NS_ASSUME_NONNULL_BEGIN + forShadowView:(const facebook::react::ShadowView &)shadowView; + + - (void)schedulerDidSynchronouslyUpdateViewOnUIThread:(facebook::react::Tag)reactTag props:(folly::dynamic)props; ++ ++- (void)schedulerMeasureAsyncOnUI:(const facebook::react::ShadowView &)shadowView ++ callback:(const std::function &)callback; + @end + + /** +diff --git a/node_modules/react-native/React/Fabric/RCTScheduler.mm b/node_modules/react-native/React/Fabric/RCTScheduler.mm +index 6cc4e5b..d5139ee 100644 +--- a/node_modules/react-native/React/Fabric/RCTScheduler.mm ++++ b/node_modules/react-native/React/Fabric/RCTScheduler.mm +@@ -78,6 +78,11 @@ void schedulerDidUpdateShadowTree(const std::unordered_map + // This delegate method is not currently used on iOS. + } + ++ void schedulerMeasureAsyncOnUI(const ShadowView& shadowView, const std::function &callback) override { ++ RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; ++ [scheduler.delegate schedulerMeasureAsyncOnUI:shadowView callback:callback]; ++ } ++ + private: + void *scheduler_; + }; +diff --git a/node_modules/react-native/React/Fabric/RCTSurfacePresenter.mm b/node_modules/react-native/React/Fabric/RCTSurfacePresenter.mm +index 1e67509..cd57d32 100644 +--- a/node_modules/react-native/React/Fabric/RCTSurfacePresenter.mm ++++ b/node_modules/react-native/React/Fabric/RCTSurfacePresenter.mm +@@ -332,6 +332,19 @@ - (void)schedulerDidSetIsJSResponder:(BOOL)isJSResponder + [_mountingManager setIsJSResponder:isJSResponder blockNativeResponder:blockNativeResponder forShadowView:shadowView]; + } + ++- (void)schedulerMeasureAsyncOnUI:(const facebook::react::ShadowView &)shadowView callback:(const std::function &)callback { ++ ReactTag tag = shadowView.tag; ++ SurfaceId surfaceId = shadowView.surfaceId; ++ RCTFabricSurface *surface = [self surfaceForRootTag:surfaceId]; ++ ++ std::function callbackCopy = callback; ++ RCTExecuteOnMainQueue(^{ ++ UIView *rootView = surface.view; ++ [self->_mountingManager measureAsyncOnUI:tag rootView:rootView callback:callbackCopy]; ++ }); ++} ++ ++ + - (void)addObserver:(id)observer + { + std::unique_lock lock(_observerListMutex); +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +index 6199a2c..18e848f 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +@@ -25,6 +25,7 @@ import android.os.SystemClock; + import android.view.View; + import android.view.accessibility.AccessibilityEvent; + import androidx.annotation.AnyThread; ++import androidx.annotation.NonNull; + import androidx.annotation.Nullable; + import androidx.annotation.UiThread; + import androidx.core.util.Preconditions; +@@ -34,6 +35,7 @@ import com.facebook.infer.annotation.Assertions; + import com.facebook.infer.annotation.Nullsafe; + import com.facebook.infer.annotation.ThreadConfined; + import com.facebook.proguard.annotations.DoNotStripAny; ++import com.facebook.react.bridge.Callback; + import com.facebook.react.bridge.ColorPropConverter; + import com.facebook.react.bridge.GuardedRunnable; + import com.facebook.react.bridge.LifecycleEventListener; +@@ -1275,6 +1277,44 @@ public class FabricUIManager + }); + } + ++ public void measureAsyncOnUI(int surfaceId, int reactTag, final Callback callback) { ++ mMountItemDispatcher.addMountItem( ++ new MountItem() { ++ @Override ++ public void execute(@NonNull MountingManager mountingManager) { ++ int[] mMeasureBuffer = new int[4]; ++ boolean result = mountingManager.measureAsyncOnUI( ++ surfaceId, ++ reactTag, ++ mMeasureBuffer ++ ); ++ ++ if (!result) { ++ callback.invoke(0, 0, 0, 0, 0, 0); ++ return; ++ } ++ ++ double x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); ++ double y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); ++ double width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); ++ double height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); ++ ++ callback.invoke(0, 0, width, height, x, y); ++ } ++ ++ @Override ++ public int getSurfaceId() { ++ return surfaceId; ++ } ++ ++ @NonNull ++ @Override ++ public String toString() { ++ return "MEASURE_VIEW"; ++ } ++ }); ++ } ++ + @Override + public void profileNextBatch() { + // TODO T31905686: Remove this method and add support for multi-threading performance counters +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt +new file mode 100644 +index 0000000..9680cf5 +--- /dev/null ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt +@@ -0,0 +1,63 @@ ++/* ++ * 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.graphics.RectF ++import android.view.View ++import android.view.ViewParent ++ ++public object MeasureAsyncUtil { ++ private val mBoundingBox = RectF() ++ ++ /** ++ * Output buffer will be {x, y, width, height}. ++ */ ++ public fun measure(rootView: View, viewToMeasure: View, outputBuffer: IntArray) { ++ computeBoundingBox(rootView, outputBuffer) ++ val rootX = outputBuffer[0] ++ val rootY = outputBuffer[1] ++ computeBoundingBox(viewToMeasure, outputBuffer) ++ outputBuffer[0] -= rootX ++ outputBuffer[1] -= rootY ++ } ++ ++ private fun computeBoundingBox(view: View, outputBuffer: IntArray) { ++ mBoundingBox.set(0f, 0f, view.width.toFloat(), view.height.toFloat()) ++ mapRectFromViewToWindowCoords(view, mBoundingBox) ++ ++ outputBuffer[0] = Math.round(mBoundingBox.left) ++ outputBuffer[1] = Math.round(mBoundingBox.top) ++ outputBuffer[2] = Math.round(mBoundingBox.right - mBoundingBox.left) ++ outputBuffer[3] = Math.round(mBoundingBox.bottom - mBoundingBox.top) ++ } ++ ++ private fun mapRectFromViewToWindowCoords(view: View, rect: RectF) { ++ var matrix = view.getMatrix() ++ if (!matrix.isIdentity) { ++ matrix.mapRect(rect) ++ } ++ ++ rect.offset(view.left.toFloat(), view.top.toFloat()) ++ ++ var parent: ViewParent? = view.parent ++ while (parent is View) { ++ val parentView = parent as View ++ ++ rect.offset(-parentView.scrollX.toFloat(), -parentView.scrollY.toFloat()) ++ ++ matrix = parentView.getMatrix() ++ if (!matrix.isIdentity) { ++ matrix.mapRect(rect) ++ } ++ ++ rect.offset(parentView.left.toFloat(), parentView.top.toFloat()) ++ ++ parent = parentView.parent ++ } ++ } ++} +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt +index 4b52ade..3fca2c6 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt +@@ -24,6 +24,7 @@ import com.facebook.react.common.mapbuffer.MapBuffer + import com.facebook.react.fabric.events.EventEmitterWrapper + import com.facebook.react.fabric.mounting.mountitems.MountItem + import com.facebook.react.touch.JSResponderHandler ++import com.facebook.react.uimanager.IllegalViewOperationException + import com.facebook.react.uimanager.RootViewManager + import com.facebook.react.uimanager.ThemedReactContext + import com.facebook.react.uimanager.ViewManagerRegistry +@@ -333,6 +334,32 @@ internal class MountingManager( + attachmentsPositions, + ) + ++ @UiThread ++ @ThreadConfined(ThreadConfined.UI) ++ fun measureAsyncOnUI( ++ surfaceId: Int, ++ reactTag: Int, ++ outputBuffer: IntArray, ++ ): Boolean { ++ val smm = getSurfaceManagerEnforced(surfaceId, "measure") ++ val view = try { ++ smm.getView(reactTag); ++ } catch (ex: IllegalViewOperationException) { ++ FLog.e(TAG, "Failed to find view for tag: %d. Error: %s", reactTag, ex.message); ++ return false ++ } ++ ++ val rootView = try { ++ smm.getView(surfaceId) ++ } catch (ex: IllegalViewOperationException) { ++ FLog.e(TAG, "Failed to find root view for surfaceId: %d. Error: %s", surfaceId, ex.message); ++ return false ++ } ++ ++ MeasureAsyncUtil.measure(rootView, view, outputBuffer) ++ return true; ++ } ++ + fun enqueuePendingEvent( + surfaceId: Int, + reactTag: Int, +diff --git a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp +index 16ab55a..bc3e7ba 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp ++++ b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1111,4 +1112,16 @@ void FabricMountingManager::synchronouslyUpdateViewOnUIThread( + synchronouslyUpdateViewOnUIThreadJNI(javaUIManager_, viewTag, propsMap); + } + ++void FabricMountingManager::measureAsyncOnUI( ++ const ShadowView& shadowView, ++ const std::function& callback) { ++ static auto measureJNI = ++ JFabricUIManager::javaClassStatic()->getMethod)>( ++ "measureAsyncOnUI"); ++ ++ auto javaCallback = JCxxCallbackImpl::newObjectCxxArgs(callback); ++ ++ measureJNI(javaUIManager_, shadowView.surfaceId, shadowView.tag, javaCallback); ++} ++ + } // namespace facebook::react +diff --git a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h +index 6197831..8357c25 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h ++++ b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h +@@ -55,5 +55,9 @@ class FabricMountingManager final { + void synchronouslyUpdateViewOnUIThread(Tag viewTag, const folly::dynamic &props); + ++ void measureAsyncOnUI( ++ const ShadowView& shadowView, ++ const std::function& callback); ++ + private: + bool isOnMainThread(); + +diff --git a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp +index 3cc20c3..62b07a6 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp ++++ b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp +@@ -724,6 +724,17 @@ void FabricUIManagerBinding::schedulerDidUpdateShadowTree( + // no-op + } + ++void FabricUIManagerBinding::schedulerMeasureAsyncOnUI( ++ const facebook::react::ShadowView& shadowView, ++ const std::function& callback) { ++ auto mountingManager = getMountingManager("schedulerMeasureAsyncOnUI"); ++ if (!mountingManager) { ++ return; ++ } ++ ++ mountingManager->measureAsyncOnUI(shadowView, callback); ++} ++ + void FabricUIManagerBinding::onAnimationStarted() { + auto mountingManager = getMountingManager("onAnimationStarted"); + if (!mountingManager) { +diff --git a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h +index 39e7240..22a717b 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h ++++ b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h +@@ -113,5 +113,9 @@ class FabricUIManagerBinding : public jni::HybridClass, + void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; + ++ void schedulerMeasureAsyncOnUI( ++ const ShadowView& shadowView, ++ const std::function& callback) override; ++ + void setPixelDensity(float pointScaleFactor); + + void driveCxxAnimations(); +diff --git a/node_modules/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp b/node_modules/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp +index 75cbcff..43b0582 100644 +--- a/node_modules/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp ++++ b/node_modules/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp +@@ -466,6 +466,34 @@ void NativeDOM::measureLayout( + onSuccess(rect.x, rect.y, rect.width, rect.height); + } + ++void NativeDOM::measureAsyncOnUI( ++ jsi::Runtime& rt, ++ std::shared_ptr shadowNode, ++ const MeasureAsyncOnUICallback& callback) { ++ UIManager& uiManager = getUIManagerFromRuntime(rt); ++ UIManagerDelegate* uiManagerDelegate = uiManager.getDelegate(); ++ if (uiManagerDelegate == nullptr) { ++ return; ++ } ++ ++ std::function jsCallback = [callback](const folly::dynamic& args) { ++ if (!args.isArray() || args.size() < 6) { ++ callback.call(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); ++ return; ++ } ++ // TODO: can we make the rest accept an AsyncFunction directly? ++ callback.call( ++ args.at(0).getDouble(), ++ args.at(1).getDouble(), ++ args.at(2).getDouble(), ++ args.at(3).getDouble(), ++ args.at(4).getDouble(), ++ args.at(5).getDouble()); ++ }; ++ ++ uiManagerDelegate->uiManagerMeasureAsyncOnUI(shadowNode, jsCallback); ++} ++ + #pragma mark - Legacy direct manipulation APIs (for `ReactNativeElement`). + + void NativeDOM::setNativeProps( +diff --git a/node_modules/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h b/node_modules/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h +index d902259..44813ac 100644 +--- a/node_modules/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h ++++ b/node_modules/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h +@@ -108,6 +108,14 @@ class NativeDOM : public NativeDOMCxxSpec { + jsi::Function onFail, + const MeasureLayoutOnSuccessCallback &onSuccess); + ++ using MeasureAsyncOnUICallback = ++ AsyncCallback; ++ ++ void measureAsyncOnUI( ++ jsi::Runtime& rt, ++ std::shared_ptr shadowNode, ++ const MeasureAsyncOnUICallback& callback); ++ + #pragma mark - Legacy direct manipulation APIs (for `ReactNativeElement`). + + void setNativeProps(jsi::Runtime &rt, std::shared_ptr shadowNode, jsi::Value updatePayload); +diff --git a/node_modules/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/node_modules/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +index 49cd7d5..0b60cf0 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp ++++ b/node_modules/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +@@ -344,6 +344,15 @@ void Scheduler::uiManagerDidStartSurface(const ShadowTree& shadowTree) { + } + } + ++void Scheduler::uiManagerMeasureAsyncOnUI( ++ const std::shared_ptr& shadowNode, ++ const std::function& callback) { ++ if (delegate_ != nullptr) { ++ auto shadowView = ShadowView(*shadowNode); ++ delegate_->schedulerMeasureAsyncOnUI(shadowView, callback); ++ } ++} ++ + void Scheduler::reportMount(SurfaceId surfaceId) const { + uiManager_->reportMount(surfaceId); + } +diff --git a/node_modules/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/node_modules/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +index e25f755..754e7fa 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +@@ -99,5 +99,8 @@ class Scheduler final : public UIManagerDelegate { + void uiManagerShouldRemoveEventListener(const std::shared_ptr &listener) final; + void uiManagerDidStartSurface(const ShadowTree &shadowTree) override; ++ void uiManagerMeasureAsyncOnUI( ++ const std::shared_ptr& shadowNode, ++ const std::function& callback) override; + + #pragma mark - ContextContainer + std::shared_ptr getContextContainer() const; +diff --git a/node_modules/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h b/node_modules/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h +index 0a88e24..fdae7f7 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h +@@ -61,5 +61,9 @@ class SchedulerDelegate { + virtual void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) = 0; + ++ virtual void schedulerMeasureAsyncOnUI( ++ const ShadowView& shadowView, ++ const std::function& callback) = 0; ++ + virtual ~SchedulerDelegate() noexcept = default; + }; + +diff --git a/node_modules/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h b/node_modules/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +index e649a27..2d9424a 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +@@ -80,5 +80,13 @@ class UIManagerDelegate { + virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr &listener) = 0; + ++ /* ++ * Measures the layout of the shadow node async on the UI thread using the native layout hierarchy. ++ * As this has to schedule a call on the UI thread its async. ++ */ ++ virtual void uiManagerMeasureAsyncOnUI( ++ const std::shared_ptr& shadowNode, ++ const std::function& callback) = 0; ++ + /* + * Start surface. + */ +diff --git a/node_modules/react-native/src/private/types/HostInstance.js b/node_modules/react-native/src/private/types/HostInstance.js +index 6776545..94c4f14 100644 +--- a/node_modules/react-native/src/private/types/HostInstance.js ++++ b/node_modules/react-native/src/private/types/HostInstance.js +@@ -69,6 +69,11 @@ export interface LegacyHostInstanceMethods { + * prop](docs/view.html#onlayout) instead. + */ + measure(callback: MeasureOnSuccessCallback): void; ++ /** ++ * Same as `measure()`, but instead of using the shadow nodes layout information, ++ * it uses the native layout hierarchy to measure the view on the UI thread. ++ */ ++ measureAsyncOnUI(callback: MeasureOnSuccessCallback): void; + /** + * Determines the location of the given view in the window and returns the + * values via an async callback. If the React root view is embedded in +diff --git a/node_modules/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js b/node_modules/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js +index abf3c73..4e51bec 100644 +--- a/node_modules/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js ++++ b/node_modules/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js +@@ -154,6 +154,18 @@ class ReactNativeElement extends ReadOnlyElement implements NativeMethods { + } + } + ++ measureAsyncOnUI(callback: MeasureOnSuccessCallback) { ++ const node = getNativeElementReference(this); ++ if (node != null) { ++ if (typeof NativeDOM.measureAsyncOnUI === 'function') { ++ NativeDOM.measureAsyncOnUI(node, callback); ++ } else { ++ // Fallback to regular measure if native implementation is not available ++ NativeDOM.measure(node, callback); ++ } ++ } ++ } ++ + measureInWindow(callback: MeasureInWindowOnSuccessCallback) { + const node = getNativeElementReference(this); + if (node != null) { +diff --git a/node_modules/react-native/React/FBReactNativeSpec/FBReactNativeSpecJSI.h b/node_modules/react-native/React/FBReactNativeSpec/FBReactNativeSpecJSI.h +index 1234567..abcdefg 100644 +--- a/node_modules/react-native/React/FBReactNativeSpec/FBReactNativeSpecJSI.h ++++ b/node_modules/react-native/React/FBReactNativeSpec/FBReactNativeSpecJSI.h +@@ -5809,6 +5809,7 @@ + methodMap_["measure"] = MethodMetadata {.argCount = 2, .invoker = __measure}; + methodMap_["measureInWindow"] = MethodMetadata {.argCount = 2, .invoker = __measureInWindow}; + methodMap_["measureLayout"] = MethodMetadata {.argCount = 4, .invoker = __measureLayout}; ++ methodMap_["measureAsyncOnUI"] = MethodMetadata {.argCount = 2, .invoker = __measureAsyncOnUI}; + methodMap_["setNativeProps"] = MethodMetadata {.argCount = 2, .invoker = __setNativeProps}; + } + +@@ -5984,5 +5985,14 @@ + count <= 2 ? throw jsi::JSError(rt, "Expected argument in position 2 to be passed") : args[2].asObject(rt).asFunction(rt), + count <= 3 ? throw jsi::JSError(rt, "Expected argument in position 3 to be passed") : args[3].asObject(rt).asFunction(rt));return jsi::Value::undefined(); + } ++ ++ static jsi::Value __measureAsyncOnUI(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { ++ static_assert( ++ bridging::getParameterCount(&T::measureAsyncOnUI) == 3, ++ "Expected measureAsyncOnUI(...) to have 3 parameters"); ++ bridging::callFromJs(rt, &T::measureAsyncOnUI, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule), ++ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : jsi::Value(rt, args[0]), ++ count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt));return jsi::Value::undefined(); ++ } + + static jsi::Value __setNativeProps(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { +diff --git a/node_modules/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js b/node_modules/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js +index 60e23b2..6c63789 100644 +--- a/node_modules/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js ++++ b/node_modules/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js +@@ -156,6 +156,11 @@ export interface Spec extends TurboModule { + onSuccess: MeasureLayoutOnSuccessCallback, + ) => void; + ++ +measureAsyncOnUI: ( ++ nativeElementReference: mixed, ++ callback: MeasureOnSuccessCallback, ++ ) => void; ++ + /** + * Legacy direct manipulation APIs (for `ReactNativeElement`). + */ +@@ -423,6 +428,11 @@ export interface RefinedSpec { + onSuccess: MeasureLayoutOnSuccessCallback, + ) => void; + ++ +measureAsyncOnUI: ( ++ nativeElementReference: NativeElementReference, ++ callback: MeasureOnSuccessCallback, ++ ) => void; ++ + /** + * Legacy direct manipulation APIs + */ diff --git a/src/components/ProductTrainingContext/createPressHandler/index.android.ts b/src/components/ProductTrainingContext/createPressHandler/index.android.ts deleted file mode 100644 index 1fd17cf026415..0000000000000 --- a/src/components/ProductTrainingContext/createPressHandler/index.android.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type PressHandlerProps from './types'; - -/** - * This is a workaround for a known issue on certain Samsung Android devices - * So, we use `onPressIn` for Android to ensure the button is pressable. - * This will be removed once the issue https://github.com/Expensify/App/issues/59953 is resolved. - */ -function createPressHandler(onPress?: () => void): PressHandlerProps { - return { - onPressIn: onPress, - }; -} - -export default createPressHandler; diff --git a/src/components/ProductTrainingContext/createPressHandler/index.ts b/src/components/ProductTrainingContext/createPressHandler/index.ts deleted file mode 100644 index 4de60a9716fcd..0000000000000 --- a/src/components/ProductTrainingContext/createPressHandler/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type PressHandlerProps from './types'; - -function createPressHandler(onPress?: () => void): PressHandlerProps { - return { - onPress, - }; -} - -export default createPressHandler; diff --git a/src/components/ProductTrainingContext/createPressHandler/types.ts b/src/components/ProductTrainingContext/createPressHandler/types.ts deleted file mode 100644 index 90c464216c2a4..0000000000000 --- a/src/components/ProductTrainingContext/createPressHandler/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type {ButtonProps} from '@components/Button'; -import type PressableProps from '@components/Pressable/GenericPressable/types'; - -type PressHandlerProps = Pick; - -export default PressHandlerProps; diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 7958fcdc2f537..81135dc0b345b 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -21,7 +21,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; -import createPressHandler from './createPressHandler'; import type {ProductTrainingTooltipName} from './TOOLTIPS'; import TOOLTIPS from './TOOLTIPS'; @@ -295,8 +294,7 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou shouldUseAutoHitSlop accessibilityLabel={translate('common.noThanks')} role={CONST.ROLE.BUTTON} - // eslint-disable-next-line react/jsx-props-no-spreading - {...createPressHandler(() => hideTooltip(true))} + onPress={() => hideTooltip(true)} >