Skip to content

Commit ef6acf9

Browse files
cortinicofacebook-github-bot
authored andcommitted
Fix Modal first frame being rendered on top-left corner (#51048)
Summary: Fixes #50442 Closes #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
1 parent 2a52ee8 commit ef6acf9

File tree

13 files changed

+132
-39
lines changed

13 files changed

+132
-39
lines changed

packages/react-native/Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ let reactFabricComponents = RNTarget(
416416
name: .reactFabricComponents,
417417
path: "ReactCommon/react/renderer",
418418
excludedPaths: [
419+
"components/modal/platform/android",
420+
"components/modal/platform/cxx",
419421
"components/view/platform/android",
420422
"components/view/platform/windows",
421423
"components/view/platform/macos",

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import android.os.Build
1313
import android.view.View
1414
import android.view.WindowInsetsController
1515
import android.view.WindowManager
16-
import androidx.core.view.ViewCompat
17-
import androidx.core.view.WindowInsetsCompat
1816
import com.facebook.common.logging.FLog
1917
import com.facebook.fbreact.specs.NativeStatusBarManagerAndroidSpec
2018
import com.facebook.react.bridge.GuardedRunnable
@@ -23,6 +21,7 @@ import com.facebook.react.bridge.ReactApplicationContext
2321
import com.facebook.react.bridge.UiThreadUtil
2422
import com.facebook.react.common.ReactConstants
2523
import com.facebook.react.module.annotations.ReactModule
24+
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
2625
import com.facebook.react.uimanager.PixelUtil
2726
import com.facebook.react.views.view.setStatusBarTranslucency
2827
import com.facebook.react.views.view.setStatusBarVisibility
@@ -34,32 +33,17 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
3433

3534
@Suppress("DEPRECATION")
3635
override fun getTypedExportedConstants(): Map<String, Any> {
36+
val currentActivity = reactApplicationContext.currentActivity
3737
val statusBarColor =
38-
reactApplicationContext.getCurrentActivity()?.window?.statusBarColor?.let { color ->
38+
currentActivity?.window?.statusBarColor?.let { color ->
3939
String.format("#%06X", 0xFFFFFF and color)
4040
} ?: "black"
4141
return mapOf(
42-
HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx()),
42+
HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx(currentActivity).toFloat()),
4343
DEFAULT_BACKGROUND_COLOR_KEY to statusBarColor,
4444
)
4545
}
4646

47-
private fun getStatusBarHeightPx(): Float {
48-
val windowInsets =
49-
reactApplicationContext
50-
.getCurrentActivity()
51-
?.window
52-
?.decorView
53-
?.let(ViewCompat::getRootWindowInsets) ?: return 0f
54-
return windowInsets
55-
.getInsets(
56-
WindowInsetsCompat.Type.statusBars() or
57-
WindowInsetsCompat.Type.navigationBars() or
58-
WindowInsetsCompat.Type.displayCutout())
59-
.top
60-
.toFloat()
61-
}
62-
6347
@Suppress("DEPRECATION")
6448
override fun setColor(colorDouble: Double, animated: Boolean) {
6549
val color = colorDouble.toInt()

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77

88
package com.facebook.react.uimanager
99

10+
import android.app.Activity
1011
import android.content.Context
1112
import android.util.DisplayMetrics
1213
import android.view.WindowManager
14+
import androidx.core.view.ViewCompat
15+
import androidx.core.view.WindowInsetsCompat
1316
import com.facebook.react.bridge.WritableMap
1417
import com.facebook.react.bridge.WritableNativeMap
1518

@@ -98,4 +101,14 @@ public object DisplayMetricsHolder {
98101
putDouble("fontScale", fontScale)
99102
putDouble("densityDpi", displayMetrics.densityDpi.toDouble())
100103
}
104+
105+
internal fun getStatusBarHeightPx(activity: Activity?): Int {
106+
val windowInsets = activity?.window?.decorView?.let(ViewCompat::getRootWindowInsets) ?: return 0
107+
return windowInsets
108+
.getInsets(
109+
WindowInsetsCompat.Type.statusBars() or
110+
WindowInsetsCompat.Type.navigationBars() or
111+
WindowInsetsCompat.Type.displayCutout())
112+
.top
113+
}
101114
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import com.facebook.react.bridge.WritableNativeMap
4040
import com.facebook.react.common.annotations.VisibleForTesting
4141
import com.facebook.react.common.build.ReactBuildConfig
4242
import com.facebook.react.config.ReactFeatureFlags
43+
import com.facebook.react.uimanager.DisplayMetricsHolder
44+
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
4345
import com.facebook.react.uimanager.JSPointerDispatcher
4446
import com.facebook.react.uimanager.JSTouchDispatcher
4547
import com.facebook.react.uimanager.PixelUtil.pxToDp
@@ -53,6 +55,7 @@ import com.facebook.react.views.modal.ReactModalHostView.DialogRootViewGroup
5355
import com.facebook.react.views.view.ReactViewGroup
5456
import com.facebook.react.views.view.setStatusBarTranslucency
5557
import com.facebook.react.views.view.setSystemBarsTranslucency
58+
import com.facebook.yoga.annotations.DoNotStrip
5659

5760
/**
5861
* ReactModalHostView is a view that sits in the view hierarchy representing a Modal view.
@@ -122,6 +125,7 @@ public class ReactModalHostView(context: ThemedReactContext) :
122125
private var createNewDialog = false
123126

124127
init {
128+
initStatusBarHeight(context)
125129
dialogRootViewGroup = DialogRootViewGroup(context)
126130
}
127131

@@ -461,6 +465,26 @@ public class ReactModalHostView(context: ThemedReactContext) :
461465

462466
private companion object {
463467
private const val TAG = "ReactModalHost"
468+
469+
// We store the status bar height to be able to properly position
470+
// the modal on the first render.
471+
private var statusBarHeight = 0
472+
473+
private fun initStatusBarHeight(reactContext: ReactContext) {
474+
statusBarHeight = getStatusBarHeightPx(reactContext.currentActivity)
475+
}
476+
477+
@JvmStatic
478+
@DoNotStrip
479+
private fun getScreenDisplayMetricsWithoutInsets(): Long {
480+
val displayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics()
481+
return encodeFloatsToLong(
482+
displayMetrics.widthPixels.toFloat().pxToDp(),
483+
(displayMetrics.heightPixels - statusBarHeight).toFloat().pxToDp())
484+
}
485+
486+
private fun encodeFloatsToLong(width: Float, height: Float): Long =
487+
(width.toRawBits().toLong()) shl 32 or (height.toRawBits().toLong())
464488
}
465489

466490
/**
@@ -476,6 +500,7 @@ public class ReactModalHostView(context: ThemedReactContext) :
476500
*/
477501
public class DialogRootViewGroup internal constructor(context: Context) :
478502
ReactViewGroup(context), RootView {
503+
479504
internal var stateWrapper: StateWrapper? = null
480505
internal var eventDispatcher: EventDispatcher? = null
481506

packages/react-native/ReactCommon/React-FabricComponents.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Pod::Spec.new do |s|
8989
end
9090

9191
ss.subspec "modal" do |sss|
92-
sss.source_files = "react/renderer/components/modal/**/*.{m,mm,cpp,h}"
92+
sss.source_files = "react/renderer/components/modal/*.{m,mm,cpp,h}"
9393
sss.exclude_files = "react/renderer/components/modal/tests"
9494
sss.header_dir = "react/renderer/components/modal"
9595
end

packages/react-native/ReactCommon/react/renderer/components/modal/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ set(CMAKE_VERBOSE_MAKEFILE on)
88

99
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
1010

11-
file(GLOB rrc_modal_SRC CONFIGURE_DEPENDS *.cpp)
11+
file(GLOB rrc_modal_SRC CONFIGURE_DEPENDS
12+
*.cpp
13+
platform/android/*.cpp)
14+
1215
add_library(rrc_modal STATIC ${rrc_modal_SRC})
1316

1417
target_include_directories(rrc_modal PUBLIC ${REACT_COMMON_DIR})

packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ class ModalHostViewComponentDescriptor final
3030
*shadowNode.getState())
3131
.getData();
3232

33-
layoutableShadowNode.setSize(
34-
Size{stateData.screenSize.width, stateData.screenSize.height});
33+
layoutableShadowNode.setSize(Size{
34+
.width = stateData.screenSize.width,
35+
.height = stateData.screenSize.height});
3536
layoutableShadowNode.setPositionType(YGPositionTypeAbsolute);
3637

3738
ConcreteComponentDescriptor::adopt(shadowNode);

packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewState.h

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,12 @@
99

1010
#include <react/renderer/core/graphicsConversions.h>
1111
#include <react/renderer/graphics/Float.h>
12+
#include "ModalHostViewUtils.h"
1213

1314
#ifdef RN_SERIALIZABLE_STATE
1415
#include <folly/dynamic.h>
1516
#endif
1617

17-
#if defined(__APPLE__) && TARGET_OS_IOS
18-
#include "ModalHostViewUtils.h"
19-
#endif
20-
2118
namespace facebook::react {
2219

2320
/*
@@ -27,21 +24,16 @@ class ModalHostViewState final {
2724
public:
2825
using Shared = std::shared_ptr<const ModalHostViewState>;
2926

30-
#if defined(__APPLE__) && TARGET_OS_IOS
31-
ModalHostViewState() : screenSize(RCTModalHostViewScreenSize()) {
32-
#else
33-
ModalHostViewState(){
34-
#endif
35-
};
27+
ModalHostViewState() : screenSize(ModalHostViewScreenSize()) {}
3628
ModalHostViewState(Size screenSize_) : screenSize(screenSize_){};
3729

3830
#ifdef RN_SERIALIZABLE_STATE
3931
ModalHostViewState(
4032
const ModalHostViewState& previousState,
4133
folly::dynamic data)
4234
: screenSize(Size{
43-
(Float)data["screenWidth"].getDouble(),
44-
(Float)data["screenHeight"].getDouble()}){};
35+
.width = (Float)data["screenWidth"].getDouble(),
36+
.height = (Float)data["screenHeight"].getDouble()}){};
4537
#endif
4638

4739
const Size screenSize{};

packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
#pragma once
99

10-
#include <react/renderer/core/graphicsConversions.h>
10+
#include <react/renderer/graphics/Size.h>
1111

1212
namespace facebook::react {
1313

14-
Size RCTModalHostViewScreenSize(void);
14+
Size ModalHostViewScreenSize(void);
1515

1616
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewUtils.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace facebook::react {
1313

14-
Size RCTModalHostViewScreenSize(void)
14+
Size ModalHostViewScreenSize(void)
1515
{
1616
CGSize screenSize = RCTScreenSize();
1717
return {screenSize.width, screenSize.height};

0 commit comments

Comments
 (0)