diff --git a/patches/react-native/details.md b/patches/react-native/details.md index f14c955dcd35..8c69f93c673f 100644 --- a/patches/react-native/details.md +++ b/patches/react-native/details.md @@ -252,9 +252,3 @@ - Upstream PR/issue: 🛑 - E/App issue: https://github.com/Expensify/App/issues/85877 - PR introducing patch: 🛑 - -### [react-native+0.83.1+034+fix-pressability-new-arch.patch](react-native+0.83.1+034+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+034+fix-pressability-new-arch.patch b/patches/react-native/react-native+0.83.1+034+fix-pressability-new-arch.patch deleted file mode 100644 index e8674a7d717e..000000000000 --- a/patches/react-native/react-native+0.83.1+034+fix-pressability-new-arch.patch +++ /dev/null @@ -1,592 +0,0 @@ -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 #%@", reactTag); -+ callback({}); -+ 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) { -+ // TODO: add error handling callback -+ 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,30 @@ 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) { -+ // 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: MeasureInWindowOnSuccessCallback, -+ ) => 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 new file mode 100644 index 000000000000..1fd17cf02641 --- /dev/null +++ b/src/components/ProductTrainingContext/createPressHandler/index.android.ts @@ -0,0 +1,14 @@ +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 new file mode 100644 index 000000000000..4de60a9716fc --- /dev/null +++ b/src/components/ProductTrainingContext/createPressHandler/index.ts @@ -0,0 +1,9 @@ +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 new file mode 100644 index 000000000000..90c464216c2a --- /dev/null +++ b/src/components/ProductTrainingContext/createPressHandler/types.ts @@ -0,0 +1,6 @@ +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 81135dc0b345..7958fcdc2f53 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -21,6 +21,7 @@ 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'; @@ -294,7 +295,8 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou shouldUseAutoHitSlop accessibilityLabel={translate('common.noThanks')} role={CONST.ROLE.BUTTON} - onPress={() => hideTooltip(true)} + // eslint-disable-next-line react/jsx-props-no-spreading + {...createPressHandler(() => hideTooltip(true))} >