Skip to content

Commit 2247f5a

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 Differential Revision: D73948178
1 parent ba5acdb commit 2247f5a

File tree

6 files changed

+89
-12
lines changed

6 files changed

+89
-12
lines changed

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import android.view.accessibility.AccessibilityEvent
2626
import android.view.accessibility.AccessibilityNodeInfo
2727
import android.widget.FrameLayout
2828
import androidx.annotation.UiThread
29+
import androidx.core.view.ViewCompat
2930
import androidx.core.view.WindowInsetsCompat
3031
import androidx.core.view.WindowInsetsControllerCompat
3132
import com.facebook.common.logging.FLog
@@ -40,6 +41,7 @@ import com.facebook.react.common.ReactConstants
4041
import com.facebook.react.common.annotations.VisibleForTesting
4142
import com.facebook.react.common.build.ReactBuildConfig
4243
import com.facebook.react.config.ReactFeatureFlags
44+
import com.facebook.react.uimanager.DisplayMetricsHolder
4345
import com.facebook.react.uimanager.JSPointerDispatcher
4446
import com.facebook.react.uimanager.JSTouchDispatcher
4547
import com.facebook.react.uimanager.PixelUtil.pxToDp
@@ -52,6 +54,7 @@ import com.facebook.react.views.common.ContextUtils
5254
import com.facebook.react.views.view.ReactViewGroup
5355
import com.facebook.react.views.view.setStatusBarTranslucency
5456
import com.facebook.react.views.view.setSystemBarsTranslucency
57+
import com.facebook.yoga.annotations.DoNotStrip
5558
import java.util.Objects
5659

5760
/**
@@ -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

@@ -431,6 +435,18 @@ public class ReactModalHostView(context: ThemedReactContext) :
431435
}
432436
}
433437

438+
private fun initStatusBarHeight(reactContext: ReactContext) {
439+
val windowInsets =
440+
reactContext.getCurrentActivity()?.window?.decorView?.let(ViewCompat::getRootWindowInsets)
441+
statusBarHeight =
442+
windowInsets
443+
?.getInsets(
444+
WindowInsetsCompat.Type.statusBars() or
445+
WindowInsetsCompat.Type.navigationBars() or
446+
WindowInsetsCompat.Type.displayCutout())
447+
?.top ?: 0
448+
}
449+
434450
/**
435451
* Sets the testID on the DialogRootViewGroup. Since the accessibility events are not triggered on
436452
* the on the ReactModalHostView, the testID is forwarded to the DialogRootViewGroup to set the
@@ -449,6 +465,19 @@ public class ReactModalHostView(context: ThemedReactContext) :
449465

450466
private companion object {
451467
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+
@JvmStatic
474+
@DoNotStrip
475+
private fun getScreenDisplayMetricsWithoutInsets(): FloatArray {
476+
val displayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics()
477+
return floatArrayOf(
478+
displayMetrics.widthPixels.toFloat().pxToDp(),
479+
(displayMetrics.heightPixels - statusBarHeight).toFloat().pxToDp())
480+
}
452481
}
453482

454483
/**
@@ -464,6 +493,7 @@ public class ReactModalHostView(context: ThemedReactContext) :
464493
*/
465494
public class DialogRootViewGroup internal constructor(context: Context) :
466495
ReactViewGroup(context), RootView {
496+
467497
internal var stateWrapper: StateWrapper? = null
468498
internal var eventDispatcher: EventDispatcher? = null
469499

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/ModalHostViewState.h

Lines changed: 2 additions & 10 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 ANDROID
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,12 +24,7 @@ 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(RCTModalHostViewScreenSize()) {}
3628
ModalHostViewState(Size screenSize_) : screenSize(screenSize_){};
3729

3830
#ifdef ANDROID

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <fbjni/fbjni.h>
11+
#include <react/renderer/graphics/Size.h>
12+
13+
#include <android/log.h>
14+
15+
namespace facebook::react {
16+
17+
class JReactModalHostView
18+
: public facebook::jni::JavaClass<JReactModalHostView> {
19+
public:
20+
static auto constexpr kJavaDescriptor =
21+
"Lcom/facebook/react/views/modal/ReactModalHostView;";
22+
23+
static Size getDisplayMetrics() {
24+
static auto method = JReactModalHostView::javaClassStatic()
25+
->getStaticMethod<jni::JArrayFloat()>(
26+
"getScreenDisplayMetricsWithoutInsets");
27+
auto result = method(javaClassStatic());
28+
size_t size = result->size();
29+
std::vector<jfloat> elements(size + 1L);
30+
result->getRegion(0, size, elements.data());
31+
return Size{elements[0], elements[1]};
32+
}
33+
};
34+
35+
} // namespace facebook::react
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <react/renderer/components/modal/ModalHostViewUtils.h>
9+
#include "JReactModalHostView.h"
10+
11+
namespace facebook::react {
12+
13+
Size RCTModalHostViewScreenSize(void) {
14+
return JReactModalHostView::getDisplayMetrics();
15+
}
16+
17+
} // namespace facebook::react

0 commit comments

Comments
 (0)