diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 429f689d107..cb16e0b0ca2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -70,6 +70,7 @@ import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatur import com.facebook.react.internal.interop.InteropEventEmitter; import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.modules.i18nmanager.I18nUtil; +import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.GuardedFrameCallback; import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.PixelUtil; @@ -97,6 +98,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @@ -725,6 +727,23 @@ public class FabricUIManager return true; } + /** + * This method is used to get the encoded screen size without vertical insets for a given surface. + * It's used by the Modal component to determine the size of the screen without vertical insets. + * The method is private as it's accessed via JNI from C++. + * + * @param surfaceId The surface ID of the surface for which the Modal is going to render. + * @return The encoded screen size as a long (both width and height) are represented without + * vertical insets. + */ + private long getEncodedScreenSizeWithoutVerticalInsets(int surfaceId) { + SurfaceMountingManager surfaceMountingManager = mMountingManager.getSurfaceManager(surfaceId); + Objects.requireNonNull(surfaceMountingManager); + ThemedReactContext context = Objects.requireNonNull(surfaceMountingManager.getContext()); + return DisplayMetricsHolder.getEncodedScreenSizeWithoutVerticalInsets( + context.getCurrentActivity()); + } + @Override public void addUIManagerEventListener(UIManagerListener listener) { mListeners.add(listener); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt index 0320c443011..05762d98026 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt @@ -16,6 +16,7 @@ import androidx.core.view.WindowInsetsCompat import androidx.window.layout.WindowMetricsCalculator import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.WritableNativeMap +import com.facebook.react.uimanager.PixelUtil.pxToDp import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn /** @@ -140,4 +141,33 @@ public object DisplayMetricsHolder { WindowInsetsCompat.Type.displayCutout()) .top } + + /** + * Returns the encoded screen size without vertical insets. + * + * This is needed to render components that needs to be correctly positioned on the screen on + * their first frame. Modal is one of such components. + * + * @param activity the [Activity] to get the insets from. + * @return the encoded screen size as a [Long] value, where the first 32 bits represent the width + * and the last 32 bits represent the height in dp (density-independent pixels). + */ + // This annotation can be removed once FabricUIManager is migrated to Kotlin + @JvmName("getEncodedScreenSizeWithoutVerticalInsets") + @JvmStatic + internal fun getEncodedScreenSizeWithoutVerticalInsets(activity: Activity?): Long { + val windowInsets = activity?.window?.decorView?.let(ViewCompat::getRootWindowInsets) ?: return 0 + val insets = + windowInsets.getInsets( + WindowInsetsCompat.Type.statusBars() or + WindowInsetsCompat.Type.navigationBars() or + WindowInsetsCompat.Type.displayCutout()) + val verticalInsets = insets.top + insets.bottom + return encodeFloatsToLong( + (checkNotNull(screenDisplayMetrics).widthPixels).toFloat().pxToDp(), + (checkNotNull(screenDisplayMetrics).heightPixels - verticalInsets).toFloat().pxToDp()) + } + + private fun encodeFloatsToLong(width: Float, height: Float): Long = + (width.toRawBits().toLong()) shl 32 or (height.toRawBits().toLong()) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt index 4e82b62376c..ddc116a6595 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt @@ -41,8 +41,6 @@ import com.facebook.react.common.annotations.UnstableReactNativeAPI 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 @@ -52,13 +50,11 @@ import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerModule import com.facebook.react.uimanager.events.EventDispatcher import com.facebook.react.views.common.ContextUtils -import com.facebook.react.views.modal.ReactModalHostView.DialogRootViewGroup import com.facebook.react.views.view.ReactViewGroup import com.facebook.react.views.view.disableEdgeToEdge import com.facebook.react.views.view.enableEdgeToEdge import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn import com.facebook.react.views.view.setStatusBarTranslucency -import com.facebook.yoga.annotations.DoNotStrip /** * ReactModalHostView is a view that sits in the view hierarchy representing a Modal view. @@ -73,7 +69,6 @@ import com.facebook.yoga.annotations.DoNotStrip * addition and removal of views to the DialogRootViewGroup. */ @SuppressLint("ViewConstructor") -@DoNotStrip public class ReactModalHostView(context: ThemedReactContext) : ViewGroup(context), LifecycleEventListener { @@ -132,7 +127,6 @@ public class ReactModalHostView(context: ThemedReactContext) : private var createNewDialog = false init { - initStatusBarHeight(context) dialogRootViewGroup = DialogRootViewGroup(context) } @@ -485,26 +479,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()) } /** diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.cpp b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.cpp new file mode 100644 index 00000000000..4e79bc4edf5 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.cpp @@ -0,0 +1,48 @@ +/* + * 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 "ModalHostViewComponentDescriptor.h" + +namespace facebook::react { + +#ifdef ANDROID +State::Shared ModalHostViewComponentDescriptor::createInitialState( + const Props::Shared& props, + const ShadowNodeFamily::Shared& family) const { + // For Android, we need to get the size of the screen without the vertical + // insets to correctly position the modal on the first rendering. + // For this reason we provide the `createInitialState` implementation + // that will query FabricUIManager for the size of the screen without + // vertical insets. + + int surfaceId = family->getSurfaceId(); + + const jni::global_ref& fabricUIManager = + contextContainer_->at>("FabricUIManager"); + + static auto getEncodedScreenSizeWithoutVerticalInsets = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod("getEncodedScreenSizeWithoutVerticalInsets"); + + auto result = + getEncodedScreenSizeWithoutVerticalInsets(fabricUIManager, surfaceId); + + // Inspired from yogaMeasureToSize from conversions.h + int32_t wBits = 0xFFFFFFFF & (result >> 32); + int32_t hBits = 0xFFFFFFFF & result; + + auto* measuredWidth = reinterpret_cast(&wBits); + auto* measuredHeight = reinterpret_cast(&hBits); + + return std::make_shared( + std::make_shared(ModalHostViewState( + Size{.width = *measuredWidth, .height = *measuredHeight})), + family); +} +#endif // ANDROID + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.h index db62b76165c..51f60b9c503 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.h @@ -37,6 +37,16 @@ class ModalHostViewComponentDescriptor final ConcreteComponentDescriptor::adopt(shadowNode); } + +#ifdef ANDROID + State::Shared createInitialState( + const Props::Shared& props, + const ShadowNodeFamily::Shared& family) const override; +#endif // ANDROID + + private: + constexpr static auto UIManagerJavaDescriptor = + "com/facebook/react/fabric/FabricUIManager"; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/platform/android/JReactModalHostView.h b/packages/react-native/ReactCommon/react/renderer/components/modal/platform/android/JReactModalHostView.h deleted file mode 100644 index f4300046116..00000000000 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/platform/android/JReactModalHostView.h +++ /dev/null @@ -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 -#include - -namespace facebook::react { - -class JReactModalHostView - : public facebook::jni::JavaClass { - public: - static auto constexpr kJavaDescriptor = - "Lcom/facebook/react/views/modal/ReactModalHostView;"; - - static Size getDisplayMetrics() { - static auto method = - JReactModalHostView::javaClassStatic()->getStaticMethod( - "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(&wBits); - auto* measuredHeight = reinterpret_cast(&hBits); - - return Size{.width = *measuredWidth, .height = *measuredHeight}; - } -}; - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/platform/android/ModalHostViewUtils.cpp b/packages/react-native/ReactCommon/react/renderer/components/modal/platform/android/ModalHostViewUtils.cpp index 50e2ce89584..a661b73ccfa 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/platform/android/ModalHostViewUtils.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/platform/android/ModalHostViewUtils.cpp @@ -7,12 +7,11 @@ #include #include -#include "JReactModalHostView.h" namespace facebook::react { Size ModalHostViewScreenSize() { - return JReactModalHostView::getDisplayMetrics(); + return Size{0, 0}; } } // namespace facebook::react