diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 282cc585b33fe9..7513accbd5f311 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 (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 ()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 ()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 ()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 diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricComponents.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricComponents.kt index 0ef05b59791500..ee1c55701719e8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricComponents.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricComponents.kt @@ -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 */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index eede2e8704451f..2546a181fc8770 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -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; @@ -169,6 +170,7 @@ public List 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 @@ -209,6 +211,7 @@ public Map 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( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index c8959a4c1fe1d2..401e8b26a3079c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -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); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index ea72dc0ca175c5..9ecad6f5d88f03 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -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 diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/safeareaview/ReactSafeAreaView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/safeareaview/ReactSafeAreaView.kt new file mode 100644 index 00000000000000..34c94d75a67456 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/safeareaview/ReactSafeAreaView.kt @@ -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) + } + }) + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/safeareaview/ReactSafeAreaViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/safeareaview/ReactSafeAreaViewManager.kt new file mode 100644 index 00000000000000..f414b281ae7f75 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/safeareaview/ReactSafeAreaViewManager.kt @@ -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(), SafeAreaViewManagerInterface { + + private val delegate: ViewManagerDelegate = SafeAreaViewManagerDelegate(this) + + override fun getDelegate(): ViewManagerDelegate = 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 = + 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" + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/safeareaview/ReactSafeAreaViewShadowNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/safeareaview/ReactSafeAreaViewShadowNode.kt new file mode 100644 index 00000000000000..811ae46e9b2e66 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/safeareaview/ReactSafeAreaViewShadowNode.kt @@ -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() {} diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index 03155b84204566..a6ec2cd614919f 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -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 $ $ $ + $ $ $ $ @@ -294,6 +296,7 @@ target_include_directories(reactnative $ $ $ + $ $ $ $ diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt index 9946a248835597..2e1b9845d37a97 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt @@ -55,6 +55,7 @@ target_link_libraries( rrc_modal rrc_progressbar rrc_root + rrc_safeareaview rrc_scrollview rrc_switch rrc_text diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp index b4a1117673d3a1..669fe3d6bcfce0 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,8 @@ sharedProviderRegistry() { ModalHostViewComponentDescriptor>()); providerRegistry->add(concreteComponentDescriptorProvider< AndroidSwitchComponentDescriptor>()); + providerRegistry->add( + concreteComponentDescriptorProvider()); providerRegistry->add( concreteComponentDescriptorProvider()); providerRegistry->add( diff --git a/packages/react-native/ReactCommon/react/renderer/components/safeareaview/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/safeareaview/CMakeLists.txt new file mode 100644 index 00000000000000..168910563358ce --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/safeareaview/CMakeLists.txt @@ -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 +) diff --git a/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewState.cpp b/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewState.cpp index da495d27eba46d..44b7721404b790 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewState.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewState.cpp @@ -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 diff --git a/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewState.h b/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewState.h index aa4c45b5d4fd2f..3554408753230e 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewState.h +++ b/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewState.h @@ -9,6 +9,10 @@ #include +#ifdef ANDROID +#include +#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{}; };