Add Android version of SafeAreaView (#46246)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/46246

Add Android implementation for 'SafeAreaView' (native implementation for [RCTSafeAreaViewNativeComponent](https://www.internalfb.com/code/fbsource/[cace8a68d2323612b199982f6784b32c9bd0ef21]/xplat/js/react-native-github/packages/react-native/src/private/specs/components/RCTSafeAreaViewNativeComponent.js)) which is intended to be used in RN core and RN Tester.

This is needed for forced edge-to-edge w/ targetSdk 35 in Android 15.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D61673059

fbshipit-source-id: ccc0866c30e1b6ef23756e2f2c216dfc6071a4c8
This commit is contained in:
Alan Lee
2024-09-02 15:44:55 -07:00
committed by Facebook GitHub Bot
parent 5bbf5a4878
commit ade863a48c
14 changed files with 272 additions and 1 deletions
@@ -5233,6 +5233,7 @@ public class com/facebook/react/uimanager/UIImplementation {
public fun setViewHierarchyUpdateDebugListener (Lcom/facebook/react/uimanager/debug/NotThreadSafeViewHierarchyUpdateDebugListener;)V
public fun setViewLocalData (ILjava/lang/Object;)V
public fun synchronouslyUpdateViewOnUIThread (ILcom/facebook/react/uimanager/ReactStylesDiffMap;)V
public fun updateInsetsPadding (IIIII)V
public fun updateNodeSize (III)V
public fun updateRootView (III)V
public fun updateRootView (Lcom/facebook/react/uimanager/ReactShadowNode;II)V
@@ -5322,6 +5323,7 @@ public class com/facebook/react/uimanager/UIManagerModule : com/facebook/react/b
public fun stopSurface (I)V
public fun sweepActiveTouchForTag (II)V
public fun synchronouslyUpdateViewOnUIThread (ILcom/facebook/react/bridge/ReadableMap;)V
public fun updateInsetsPadding (IIIII)V
public fun updateNodeSize (III)V
public fun updateRootLayoutSpecs (IIIII)V
public fun updateView (ILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V
@@ -6927,6 +6929,41 @@ public final class com/facebook/react/views/progressbar/ReactProgressBarViewMana
public final fun createProgressBar (Landroid/content/Context;I)Landroid/widget/ProgressBar;
}
public final class com/facebook/react/views/safeareaview/ReactSafeAreaView : android/view/ViewGroup {
public fun <init> (Lcom/facebook/react/uimanager/ThemedReactContext;)V
public final fun getReactContext ()Lcom/facebook/react/uimanager/ThemedReactContext;
}
public final class com/facebook/react/views/safeareaview/ReactSafeAreaViewManager : com/facebook/react/uimanager/ViewGroupManager, com/facebook/react/viewmanagers/SafeAreaViewManagerInterface {
public static final field Companion Lcom/facebook/react/views/safeareaview/ReactSafeAreaViewManager$Companion;
public static final field REACT_CLASS Ljava/lang/String;
public fun <init> ()V
public fun createShadowNodeInstance ()Lcom/facebook/react/uimanager/LayoutShadowNode;
public synthetic fun createShadowNodeInstance ()Lcom/facebook/react/uimanager/ReactShadowNode;
public synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View;
public fun getName ()Ljava/lang/String;
public fun getShadowNodeClass ()Ljava/lang/Class;
public synthetic fun updateState (Landroid/view/View;Lcom/facebook/react/uimanager/ReactStylesDiffMap;Lcom/facebook/react/uimanager/StateWrapper;)Ljava/lang/Object;
public fun updateState (Lcom/facebook/react/views/safeareaview/ReactSafeAreaView;Lcom/facebook/react/uimanager/ReactStylesDiffMap;Lcom/facebook/react/uimanager/StateWrapper;)Ljava/lang/Object;
}
public class com/facebook/react/views/safeareaview/ReactSafeAreaViewManager$$PropsSetter : com/facebook/react/uimanager/ViewManagerPropertyUpdater$ViewManagerSetter {
public fun <init> ()V
public fun getProperties (Ljava/util/Map;)V
public synthetic fun setProperty (Lcom/facebook/react/uimanager/ViewManager;Landroid/view/View;Ljava/lang/String;Ljava/lang/Object;)V
public fun setProperty (Lcom/facebook/react/views/safeareaview/ReactSafeAreaViewManager;Lcom/facebook/react/views/safeareaview/ReactSafeAreaView;Ljava/lang/String;Ljava/lang/Object;)V
}
public final class com/facebook/react/views/safeareaview/ReactSafeAreaViewManager$Companion {
}
public class com/facebook/react/views/safeareaview/ReactSafeAreaViewShadowNode$$PropsSetter : com/facebook/react/uimanager/ViewManagerPropertyUpdater$ShadowNodeSetter {
public fun <init> ()V
public fun getProperties (Ljava/util/Map;)V
public synthetic fun setProperty (Lcom/facebook/react/uimanager/ReactShadowNode;Ljava/lang/String;Ljava/lang/Object;)V
public fun setProperty (Lcom/facebook/react/views/safeareaview/ReactSafeAreaViewShadowNode;Ljava/lang/String;Ljava/lang/Object;)V
}
public abstract interface class com/facebook/react/views/scroll/FpsListener {
public abstract fun disable (Ljava/lang/String;)V
public abstract fun enable (Ljava/lang/String;)V
@@ -35,6 +35,7 @@ public object FabricComponents {
"WebView" to "RCTWebView",
"Keyframes" to "RCTKeyframes",
"ImpressionTrackingView" to "RCTImpressionTrackingView",
"SafeAreaView" to "RCTSafeAreaView",
)
/** @return the name of component in the Fabric environment */
@@ -48,6 +48,7 @@ import com.facebook.react.views.drawer.ReactDrawerLayoutManager;
import com.facebook.react.views.image.ReactImageManager;
import com.facebook.react.views.modal.ReactModalHostManager;
import com.facebook.react.views.progressbar.ReactProgressBarViewManager;
import com.facebook.react.views.safeareaview.ReactSafeAreaViewManager;
import com.facebook.react.views.scroll.ReactHorizontalScrollContainerViewManager;
import com.facebook.react.views.scroll.ReactHorizontalScrollViewManager;
import com.facebook.react.views.scroll.ReactScrollViewManager;
@@ -169,6 +170,7 @@ public class MainReactPackage extends BaseReactPackage implements ViewManagerOnD
viewManagers.add(new ReactProgressBarViewManager());
viewManagers.add(new ReactScrollViewManager());
viewManagers.add(new ReactSwitchManager());
viewManagers.add(new ReactSafeAreaViewManager());
viewManagers.add(new SwipeRefreshLayoutManager());
// Native equivalents
@@ -209,6 +211,7 @@ public class MainReactPackage extends BaseReactPackage implements ViewManagerOnD
ReactHorizontalScrollContainerViewManager::new);
appendMap(
viewManagers, ReactProgressBarViewManager.REACT_CLASS, ReactProgressBarViewManager::new);
appendMap(viewManagers, ReactSafeAreaViewManager.REACT_CLASS, ReactSafeAreaViewManager::new);
appendMap(viewManagers, ReactScrollViewManager.REACT_CLASS, ReactScrollViewManager::new);
appendMap(viewManagers, ReactSwitchManager.REACT_CLASS, ReactSwitchManager::new);
appendMap(
@@ -204,6 +204,20 @@ public class UIImplementation {
dispatchViewUpdatesIfNeeded();
}
public void updateInsetsPadding(int nodeViewTag, int top, int left, int bottom, int right) {
ReactShadowNode cssNode = mShadowNodeRegistry.getNode(nodeViewTag);
if (cssNode == null) {
FLog.w(ReactConstants.TAG, "Tried to update size of non-existent tag: " + nodeViewTag);
return;
}
cssNode.setPadding(Spacing.START, (float) left);
cssNode.setPadding(Spacing.TOP, (float) top);
cssNode.setPadding(Spacing.END, (float) right);
cssNode.setPadding(Spacing.BOTTOM, (float) bottom);
dispatchViewUpdatesIfNeeded();
}
public void setViewLocalData(int tag, Object data) {
ReactShadowNode shadowNode = mShadowNodeRegistry.getNode(tag);
@@ -400,6 +400,12 @@ public class UIManagerModule extends ReactContextBaseJavaModule
mUIImplementation.updateNodeSize(nodeViewTag, newWidth, newHeight);
}
public void updateInsetsPadding(int nodeViewTag, int top, int left, int bottom, int right) {
getReactApplicationContext().assertOnNativeModulesQueueThread();
mUIImplementation.updateInsetsPadding(nodeViewTag, top, left, bottom, right);
}
/**
* Sets local data for a shadow node corresponded with given tag. In some cases we need a way to
* specify some environmental data to shadow node to improve layout (or do something similar), so
@@ -0,0 +1,65 @@
/*
* 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.
*/
package com.facebook.react.views.safeareaview
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.CONSUMED
import com.facebook.react.bridge.GuardedRunnable
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.uimanager.PixelUtil.pxToDp
import com.facebook.react.uimanager.StateWrapper
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerModule
public class ReactSafeAreaView(public val reactContext: ThemedReactContext) :
ViewGroup(reactContext) {
internal var stateWrapper: StateWrapper? = null
override fun onAttachedToWindow() {
super.onAttachedToWindow()
ViewCompat.setOnApplyWindowInsetsListener(this) { _, windowInsets ->
val insets =
windowInsets.getInsets(
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())
updateState(insets)
CONSUMED
}
requestApplyInsets()
}
override fun onLayout(p0: Boolean, p1: Int, p2: Int, p3: Int, p4: Int): Unit = Unit
@UiThread
private fun updateState(insets: Insets) {
stateWrapper?.let { stateWrapper ->
// fabric
WritableNativeMap().apply {
putDouble("left", insets.left.toFloat().pxToDp().toDouble())
putDouble("top", insets.top.toFloat().pxToDp().toDouble())
putDouble("bottom", insets.bottom.toFloat().pxToDp().toDouble())
putDouble("right", insets.right.toFloat().pxToDp().toDouble())
stateWrapper.updateState(this)
}
}
// paper
?: reactContext.runOnNativeModulesQueueThread(
object : GuardedRunnable(reactContext) {
override fun runGuarded() {
this@ReactSafeAreaView.reactContext.reactApplicationContext
.getNativeModule(UIManagerModule::class.java)
?.updateInsetsPadding(id, insets.top, insets.left, insets.bottom, insets.right)
}
})
}
}
@@ -0,0 +1,51 @@
/*
* 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.
*/
package com.facebook.react.views.safeareaview
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.LayoutShadowNode
import com.facebook.react.uimanager.ReactStylesDiffMap
import com.facebook.react.uimanager.StateWrapper
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.viewmanagers.SafeAreaViewManagerDelegate
import com.facebook.react.viewmanagers.SafeAreaViewManagerInterface
/** View manager for [ReactSafeAreaView] components. */
@ReactModule(name = ReactSafeAreaViewManager.REACT_CLASS)
public class ReactSafeAreaViewManager() :
ViewGroupManager<ReactSafeAreaView>(), SafeAreaViewManagerInterface<ReactSafeAreaView> {
private val delegate: ViewManagerDelegate<ReactSafeAreaView> = SafeAreaViewManagerDelegate(this)
override fun getDelegate(): ViewManagerDelegate<ReactSafeAreaView> = delegate
override fun createViewInstance(context: ThemedReactContext): ReactSafeAreaView =
ReactSafeAreaView(context)
override fun getName(): String = REACT_CLASS
override fun createShadowNodeInstance(): LayoutShadowNode = ReactSafeAreaViewShadowNode()
public override fun getShadowNodeClass(): Class<out LayoutShadowNode> =
ReactSafeAreaViewShadowNode::class.java
public override fun updateState(
view: ReactSafeAreaView,
props: ReactStylesDiffMap,
stateWrapper: StateWrapper
): Any? {
view.stateWrapper = stateWrapper
return null
}
public companion object {
public const val REACT_CLASS: String = "RCTSafeAreaView"
}
}
@@ -0,0 +1,12 @@
/*
* 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.
*/
package com.facebook.react.views.safeareaview
import com.facebook.react.uimanager.LayoutShadowNode
internal class ReactSafeAreaViewShadowNode() : LayoutShadowNode() {}
@@ -99,6 +99,7 @@ add_react_common_subdir(react/renderer/components/text)
add_react_common_subdir(react/renderer/components/unimplementedview)
add_react_common_subdir(react/renderer/components/modal)
add_react_common_subdir(react/renderer/components/scrollview)
add_react_common_subdir(react/renderer/components/safeareaview)
add_react_common_subdir(react/renderer/leakchecker)
add_react_common_subdir(react/renderer/observers/events)
add_react_common_subdir(react/renderer/textlayoutmanager)
@@ -210,6 +211,7 @@ add_library(reactnative
$<TARGET_OBJECTS:rrc_native>
$<TARGET_OBJECTS:rrc_progressbar>
$<TARGET_OBJECTS:rrc_root>
$<TARGET_OBJECTS:rrc_safeareaview>
$<TARGET_OBJECTS:rrc_scrollview>
$<TARGET_OBJECTS:rrc_switch>
$<TARGET_OBJECTS:rrc_text>
@@ -294,6 +296,7 @@ target_include_directories(reactnative
$<TARGET_PROPERTY:rrc_native,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:rrc_progressbar,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:rrc_root,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:rrc_safeareaview,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:rrc_scrollview,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:rrc_switch,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:rrc_text,INTERFACE_INCLUDE_DIRECTORIES>
@@ -55,6 +55,7 @@ target_link_libraries(
rrc_modal
rrc_progressbar
rrc_root
rrc_safeareaview
rrc_scrollview
rrc_switch
rrc_text
@@ -16,6 +16,7 @@
#include <react/renderer/components/modal/ModalHostViewComponentDescriptor.h>
#include <react/renderer/components/progressbar/AndroidProgressBarComponentDescriptor.h>
#include <react/renderer/components/rncore/ComponentDescriptors.h>
#include <react/renderer/components/safeareaview/SafeAreaViewComponentDescriptor.h>
#include <react/renderer/components/scrollview/ScrollViewComponentDescriptor.h>
#include <react/renderer/components/text/ParagraphComponentDescriptor.h>
#include <react/renderer/components/text/RawTextComponentDescriptor.h>
@@ -47,6 +48,8 @@ sharedProviderRegistry() {
ModalHostViewComponentDescriptor>());
providerRegistry->add(concreteComponentDescriptorProvider<
AndroidSwitchComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<SafeAreaViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<TextComponentDescriptor>());
providerRegistry->add(
@@ -0,0 +1,46 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
file(GLOB rrc_safeareaview_SRCS CONFIGURE_DEPENDS *.cpp)
add_library(
rrc_safeareaview
STATIC
${rrc_safeareaview_SRCS}
)
target_include_directories(rrc_safeareaview PUBLIC .)
target_link_libraries(
rrc_safeareaview
glog
fbjni
folly_runtime
glog_init
react_codegen_rncore
react_debug
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_uimanager
reactnativejni
rrc_view
yoga
)
target_compile_options(
rrc_safeareaview
PRIVATE
-DLOG_TAG=\"Fabric\"
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
)
@@ -7,4 +7,13 @@
#include "SafeAreaViewState.h"
namespace facebook::react {} // namespace facebook::react
namespace facebook::react {
#ifdef ANDROID
folly::dynamic SafeAreaViewState::getDynamic() const {
return folly::dynamic::object("left", padding.left)("top", padding.top)(
"right", padding.right)("bottom", padding.bottom);
}
#endif
} // namespace facebook::react
@@ -9,6 +9,10 @@
#include <react/renderer/graphics/RectangleEdges.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#endif
namespace facebook::react {
/*
@@ -16,6 +20,22 @@ namespace facebook::react {
*/
class SafeAreaViewState final {
public:
#ifdef ANDROID
SafeAreaViewState() = default;
SafeAreaViewState(
const SafeAreaViewState& /*previousState*/,
folly::dynamic data)
: padding(EdgeInsets{
(Float)data["left"].getDouble(),
(Float)data["top"].getDouble(),
(Float)data["right"].getDouble(),
(Float)data["bottom"].getDouble(),
}){};
folly::dynamic getDynamic() const;
#endif
EdgeInsets padding{};
};