mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Fix Modal first frame being rendered on top-left corner (#51048)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51048 Fixes https://github.com/facebook/react-native/issues/50442 Closes https://github.com/facebook/react-native/pull/50704 Users reported that Modals on Android are first renderer anchored in 0,0. That results in them being on the top left corner of the screen for some seconds. This is happening because the native state of the Modal on Android as width/height set at 0,0 - which we then update in a subsequent callback. I'm fixing this by making sure we render the Modal the first time with the right screen size - the status bar size Changelog: [Android] [Fixed] - Fix Modal first frame being rendered on top-left corner Reviewed By: javache Differential Revision: D73948178 fbshipit-source-id: 055c12aa62d70acc1e4c5a2a5c4ea0c5608e22c7
This commit is contained in:
+3
-16
@@ -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<String, Any> {
|
||||
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()
|
||||
|
||||
+13
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+25
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
+4
-12
@@ -9,15 +9,12 @@
|
||||
|
||||
#include <react/renderer/core/graphicsConversions.h>
|
||||
#include <react/renderer/graphics/Float.h>
|
||||
#include "ModalHostViewUtils.h"
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <folly/dynamic.h>
|
||||
#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<const ModalHostViewState>;
|
||||
|
||||
#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{};
|
||||
|
||||
+2
-2
@@ -7,10 +7,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/renderer/core/graphicsConversions.h>
|
||||
#include <react/renderer/graphics/Size.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
Size RCTModalHostViewScreenSize(void);
|
||||
Size ModalHostViewScreenSize(void);
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
Size RCTModalHostViewScreenSize(void)
|
||||
Size ModalHostViewScreenSize(void)
|
||||
{
|
||||
CGSize screenSize = RCTScreenSize();
|
||||
return {screenSize.width, screenSize.height};
|
||||
|
||||
+38
@@ -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 <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
@@ -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 <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
@@ -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 <react/renderer/components/modal/ModalHostViewUtils.h>
|
||||
#include <react/renderer/graphics/Size.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
Size ModalHostViewScreenSize() {
|
||||
return Size{0, 0};
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
Reference in New Issue
Block a user