mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Correctly create the first modal state (#52835)
Summary: There is currently a bug with Modals with New Architecture where the first frame is rendered incorrectly, specifically not accounting for all the vertical insets (only the status bar). This fixes it. Specifically: 1. I've removed the caching of the statusbar height from `ReactModalHostView` as that was not working correctly. Sometimes the value returned `0` meaning that it was not yet computed when Fabric was asking for it. In the updated implementation we now query `FabricUIManager` given the `surfaceId` of the modal. 2. I've modified the logic to account for all the vertical insets, not just the status bar. ## Changelog: [ANDROID] [FIXED] - Correctly account for insets on first render of Modals on New Arch Pull Request resolved: https://github.com/facebook/react-native/pull/52835 Test Plan: Tested on Marketplace Location Picker and the picker is still working correctly: https://pxl.cl/7NjtJ Reviewed By: mdvacca Differential Revision: D78975126 Pulled By: cortinico fbshipit-source-id: d7afb4fa5d2f43a7e33da3860432fa6dfe0dc8d7
This commit is contained in:
committed by
Facebook GitHub Bot
parent
5fc23d7c62
commit
2e76fc8e8e
+19
@@ -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);
|
||||
|
||||
+30
@@ -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())
|
||||
}
|
||||
|
||||
-26
@@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+48
@@ -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<jobject>& fabricUIManager =
|
||||
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
|
||||
|
||||
static auto getEncodedScreenSizeWithoutVerticalInsets =
|
||||
jni::findClassStatic(UIManagerJavaDescriptor)
|
||||
->getMethod<jlong(jint)>("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<float*>(&wBits);
|
||||
auto* measuredHeight = reinterpret_cast<float*>(&hBits);
|
||||
|
||||
return std::make_shared<ModalHostViewShadowNode::ConcreteState>(
|
||||
std::make_shared<const ModalHostViewState>(ModalHostViewState(
|
||||
Size{.width = *measuredWidth, .height = *measuredHeight})),
|
||||
family);
|
||||
}
|
||||
#endif // ANDROID
|
||||
|
||||
} // namespace facebook::react
|
||||
+10
@@ -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
|
||||
|
||||
-38
@@ -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 <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
|
||||
+1
-2
@@ -7,12 +7,11 @@
|
||||
|
||||
#include <react/renderer/components/modal/ModalHostViewUtils.h>
|
||||
#include <react/renderer/graphics/Size.h>
|
||||
#include "JReactModalHostView.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
Size ModalHostViewScreenSize() {
|
||||
return JReactModalHostView::getDisplayMetrics();
|
||||
return Size{0, 0};
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
Reference in New Issue
Block a user