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 Aug 30, 2024
1 parent f00e8ba commit 24acce7
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 1 deletion.
34 changes: 34 additions & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -6927,6 +6927,40 @@ 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> (Landroid/content/Context;)V
}

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
@@ -0,0 +1,52 @@
/*
* 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.content.Context
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.WritableNativeMap
import com.facebook.react.uimanager.PixelUtil.pxToDp
import com.facebook.react.uimanager.StateWrapper

public class ReactSafeAreaView(context: Context) : ViewGroup(context) {
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 ->
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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 var shadowNode: ReactSafeAreaViewShadowNode? = null

private val delegate: ViewManagerDelegate<ReactSafeAreaView> = SafeAreaViewManagerDelegate(this)

override fun getDelegate(): ViewManagerDelegate<ReactSafeAreaView> = delegate

override fun createViewInstance(context: ThemedReactContext): ReactSafeAreaView =
ReactSafeAreaView(context).also { shadowNode?.setInsetPadding() }

override fun getName(): String = REACT_CLASS

override fun createShadowNodeInstance(): LayoutShadowNode =
ReactSafeAreaViewShadowNode().also { shadowNode = it }

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,33 @@
/*
* 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 androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.facebook.react.uimanager.LayoutShadowNode
import com.facebook.react.uimanager.Spacing

internal class ReactSafeAreaViewShadowNode() : LayoutShadowNode() {

fun setInsetPadding() {
themedContext.currentActivity?.window?.decorView?.let { decorView ->
ViewCompat.setOnApplyWindowInsetsListener(decorView) { _, windowInsets ->
val insetsType =
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
val insets = windowInsets.getInsets(insetsType)

setDefaultPadding(Spacing.START, insets.left.toFloat())
setDefaultPadding(Spacing.TOP, insets.top.toFloat())
setDefaultPadding(Spacing.END, insets.right.toFloat())
setDefaultPadding(Spacing.BOTTOM, insets.bottom.toFloat())

WindowInsetsCompat.CONSUMED
}
}
}
}
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 24acce7

Please sign in to comment.