Skip to content

Commit

Permalink
Add Android version of SafeAreaView (#46246)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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]

Differential Revision: D61673059

Reviewed By: mdvacca
  • Loading branch information
alanleedev authored and facebook-github-bot committed Sep 1, 2024
1 parent 305b435 commit e5021a8
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 1 deletion.
37 changes: 37 additions & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
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;
Expand Down Expand Up @@ -169,6 +170,7 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
viewManagers.add(new ReactProgressBarViewManager());
viewManagers.add(new ReactScrollViewManager());
viewManagers.add(new ReactSwitchManager());
viewManagers.add(new ReactSafeAreaViewManager());
viewManagers.add(new SwipeRefreshLayoutManager());

// Native equivalents
Expand Down Expand Up @@ -209,6 +211,7 @@ public Map<String, ModuleSpec> getViewManagersMap() {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,20 @@ public void updateNodeSize(int nodeViewTag, int newWidth, int newHeight) {
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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,12 @@ public void updateNodeSize(int nodeViewTag, int newWidth, int newHeight) {
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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_safearea,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:rrc_scrollview,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:rrc_switch,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:rrc_text,INTERFACE_INCLUDE_DIRECTORIES>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ target_link_libraries(
rrc_modal
rrc_progressbar
rrc_root
rrc_safeareaview
rrc_scrollview
rrc_switch
rrc_text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -47,6 +48,8 @@ sharedProviderRegistry() {
ModalHostViewComponentDescriptor>());
providerRegistry->add(concreteComponentDescriptorProvider<
AndroidSwitchComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<SafeAreaViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<TextComponentDescriptor>());
providerRegistry->add(
Expand Down
Original file line number Diff line number Diff line change
@@ -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 ${REACT_COMMON_DIR})

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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,33 @@

#include <react/renderer/graphics/RectangleEdges.h>

#ifdef ANDROID
#include <folly/dynamic.h>
#endif

namespace facebook::react {

/*
* State for <SafeAreaView> component.
*/
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{};
};

Expand Down

0 comments on commit e5021a8

Please sign in to comment.