diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index c5547ca8d3a5..ca491603af6f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -13,8 +13,6 @@ 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 @@ -23,6 +21,7 @@ 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 @@ -34,29 +33,17 @@ public class StatusBarModule(reactContext: ReactApplicationContext?) : @Suppress("DEPRECATION") override fun getTypedExportedConstants(): Map { + val currentActivity = reactApplicationContext.currentActivity val statusBarColor = currentActivity?.window?.statusBarColor?.let { color -> String.format("#%06X", 0xFFFFFF and color) } ?: "black" return mapOf( - HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx()), + HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx(currentActivity).toFloat()), DEFAULT_BACKGROUND_COLOR_KEY to statusBarColor, ) } - @Suppress("DEPRECATION") - private fun getStatusBarHeightPx(): Float { - val windowInsets = - currentActivity?.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() 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 d23014cb04bf..75573be1aadd 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 @@ -7,9 +7,12 @@ 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 @@ -99,4 +102,14 @@ 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 + } } 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 0894abf74e1f..46e13042b103 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 @@ -37,6 +37,8 @@ import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.common.ReactConstants import com.facebook.react.common.annotations.VisibleForTesting 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 @@ -49,6 +51,7 @@ import com.facebook.react.views.common.ContextUtils 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 import java.util.Objects /** @@ -119,6 +122,7 @@ public class ReactModalHostView(context: ThemedReactContext) : private var createNewDialog = false init { + initStatusBarHeight(context) dialogRootViewGroup = DialogRootViewGroup(context) } @@ -409,6 +413,26 @@ 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()) } /** @@ -424,6 +448,7 @@ 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 diff --git a/packages/react-native/ReactCommon/React-FabricComponents.podspec b/packages/react-native/ReactCommon/React-FabricComponents.podspec index d8ff12b5d411..b7bd1a2eb114 100644 --- a/packages/react-native/ReactCommon/React-FabricComponents.podspec +++ b/packages/react-native/ReactCommon/React-FabricComponents.podspec @@ -112,7 +112,7 @@ Pod::Spec.new do |s| ss.subspec "modal" do |sss| sss.dependency folly_dep_name, folly_version sss.compiler_flags = folly_compiler_flags - 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 diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/modal/CMakeLists.txt index dd1a291061ba..a84728b6b281 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/CMakeLists.txt @@ -14,7 +14,10 @@ add_compile_options( -Wpedantic -DLOG_TAG=\"Fabric\") -file(GLOB rrc_modal_SRC CONFIGURE_DEPENDS *.cpp) +file(GLOB rrc_modal_SRC CONFIGURE_DEPENDS + *.cpp + platform/android/*.cpp) + add_library(rrc_modal STATIC ${rrc_modal_SRC}) target_include_directories(rrc_modal PUBLIC ${REACT_COMMON_DIR}) 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 9ecdc54de4cf..db62b76165c2 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.h @@ -30,8 +30,9 @@ class ModalHostViewComponentDescriptor final *shadowNode.getState()) .getData(); - layoutableShadowNode.setSize( - Size{stateData.screenSize.width, stateData.screenSize.height}); + layoutableShadowNode.setSize(Size{ + .width = stateData.screenSize.width, + .height = stateData.screenSize.height}); layoutableShadowNode.setPositionType(YGPositionTypeAbsolute); ConcreteComponentDescriptor::adopt(shadowNode); diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewState.h b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewState.h index 2b713926dd5e..75e305d1fb17 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewState.h +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewState.h @@ -9,15 +9,12 @@ #include #include +#include "ModalHostViewUtils.h" #ifdef ANDROID #include #endif -#if defined(__APPLE__) && TARGET_OS_IOS -#include "ModalHostViewUtils.h" -#endif - namespace facebook::react { /* @@ -27,12 +24,7 @@ class ModalHostViewState final { public: using Shared = std::shared_ptr; -#if defined(__APPLE__) && TARGET_OS_IOS - ModalHostViewState() : screenSize(RCTModalHostViewScreenSize()) { -#else - ModalHostViewState(){ -#endif - }; + ModalHostViewState() : screenSize(ModalHostViewScreenSize()) {} ModalHostViewState(Size screenSize_) : screenSize(screenSize_){}; #ifdef ANDROID @@ -40,8 +32,8 @@ class ModalHostViewState final { const ModalHostViewState& previousState, folly::dynamic data) : screenSize(Size{ - (Float)data["screenWidth"].getDouble(), - (Float)data["screenHeight"].getDouble()}){}; + .width = (Float)data["screenWidth"].getDouble(), + .height = (Float)data["screenHeight"].getDouble()}){}; #endif const Size screenSize{}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.h b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.h index fc038ad33de5..86b375d850c3 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.h +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.h @@ -7,10 +7,10 @@ #pragma once -#include +#include namespace facebook::react { -Size RCTModalHostViewScreenSize(void); +Size ModalHostViewScreenSize(void); } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.mm b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.mm index d2c3560dca8f..7444e3a76e72 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.mm @@ -11,7 +11,7 @@ namespace facebook::react { -Size RCTModalHostViewScreenSize(void) +Size ModalHostViewScreenSize(void) { CGSize screenSize = RCTScreenSize(); return {screenSize.width, screenSize.height}; 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 new file mode 100644 index 000000000000..f4300046116a --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/platform/android/JReactModalHostView.h @@ -0,0 +1,38 @@ +/* + * 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 new file mode 100644 index 000000000000..50e2ce89584d --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/platform/android/ModalHostViewUtils.cpp @@ -0,0 +1,18 @@ +/* + * 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 +#include +#include "JReactModalHostView.h" + +namespace facebook::react { + +Size ModalHostViewScreenSize() { + return JReactModalHostView::getDisplayMetrics(); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/platform/cxx/ModalHostViewUtils.cpp b/packages/react-native/ReactCommon/react/renderer/components/modal/platform/cxx/ModalHostViewUtils.cpp new file mode 100644 index 000000000000..a661b73ccfa5 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/platform/cxx/ModalHostViewUtils.cpp @@ -0,0 +1,17 @@ +/* + * 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 +#include + +namespace facebook::react { + +Size ModalHostViewScreenSize() { + return Size{0, 0}; +} + +} // namespace facebook::react