From e7bbd7dd926d961668790af9280ed3be65fff8a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 19 Mar 2025 10:47:22 +0100 Subject: [PATCH 1/5] feat: go back to shadow nodes measurements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added useMemo for routes array with hideOneTab dependency - Added useMemo for renderScene to prevent unnecessary recreations - Created memoized navigationState object that only updates when index or routes change - This optimization prevents unnecessary re-renders when the component updates Signed-off-by: Oskar Kwaƛniewski --- .../android/src/main/jni/CMakeLists.txt | 87 +++++++++++++++++++ .../android/src/main/jni/RNCTabView.h | 13 +++ .../RNCTabViewComponentDescriptor.h | 47 ++++++++++ .../RNCTabViewMeasurementsManager.cpp | 65 ++++++++++++++ .../RNCTabViewMeasurementsManager.h | 31 +++++++ .../RNCTabView/RNCTabViewShadowNode.cpp | 28 ++++++ .../RNCTabView/RNCTabViewShadowNode.h | 48 ++++++++++ .../components/RNCTabView/RNCTabViewState.h | 35 ++++++++ .../ios/Fabric/RCTTabViewComponentView.mm | 1 + .../react-native-bottom-tabs/package.json | 1 + .../react-native-bottom-tabs.podspec | 8 ++ .../react-native.config.js | 11 +++ .../react-native-bottom-tabs/src/TabView.tsx | 11 +-- .../src/TabViewNativeComponent.ts | 4 +- 14 files changed, 379 insertions(+), 11 deletions(-) create mode 100644 packages/react-native-bottom-tabs/android/src/main/jni/CMakeLists.txt create mode 100644 packages/react-native-bottom-tabs/android/src/main/jni/RNCTabView.h create mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h create mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.cpp create mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.h create mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp create mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h create mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h create mode 100644 packages/react-native-bottom-tabs/react-native.config.js diff --git a/packages/react-native-bottom-tabs/android/src/main/jni/CMakeLists.txt b/packages/react-native-bottom-tabs/android/src/main/jni/CMakeLists.txt new file mode 100644 index 00000000..71147d64 --- /dev/null +++ b/packages/react-native-bottom-tabs/android/src/main/jni/CMakeLists.txt @@ -0,0 +1,87 @@ +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE ON) + +set(LIB_LITERAL RNCTabView) +set(LIB_TARGET_NAME react_codegen_${LIB_LITERAL}) + +set(LIB_ANDROID_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..) +set(LIB_COMMON_DIR ${LIB_ANDROID_DIR}/../common/cpp) +set(LIB_ANDROID_GENERATED_JNI_DIR ${LIB_ANDROID_DIR}/build/generated/source/codegen/jni) +set(LIB_ANDROID_GENERATED_COMPONENTS_DIR ${LIB_ANDROID_GENERATED_JNI_DIR}/react/renderer/components/${LIB_LITERAL}) + +add_compile_options( + -fexceptions + -frtti + -std=c++20 + -Wall + -Wpedantic + -Wno-gnu-zero-variadic-macro-arguments +) + +file(GLOB LIB_CUSTOM_SRCS CONFIGURE_DEPENDS *.cpp ${LIB_COMMON_DIR}/react/renderer/components/${LIB_LITERAL}/*.cpp) +file(GLOB LIB_CODEGEN_SRCS CONFIGURE_DEPENDS ${LIB_ANDROID_GENERATED_JNI_DIR}/*.cpp ${LIB_ANDROID_GENERATED_COMPONENTS_DIR}/*.cpp) + +add_library( + ${LIB_TARGET_NAME} + SHARED + ${LIB_CUSTOM_SRCS} + ${LIB_CODEGEN_SRCS} +) + +target_include_directories( + ${LIB_TARGET_NAME} + PUBLIC + . + ${LIB_COMMON_DIR} + ${LIB_ANDROID_GENERATED_JNI_DIR} + ${LIB_ANDROID_GENERATED_COMPONENTS_DIR} +) + +# https://github.com/react-native-community/discussions-and-proposals/discussions/816 +# This if-then-else can be removed once this library does not support version below 0.76 +if (REACTNATIVE_MERGED_SO) + target_link_libraries( + ${LIB_TARGET_NAME} + fbjni + jsi + reactnative + ) +else() + target_link_libraries( + ${LIB_TARGET_NAME} + fbjni + folly_runtime + glog + jsi + react_codegen_rncore + react_debug + react_render_componentregistry + react_render_core + react_render_debug + react_render_graphics + react_render_imagemanager + react_render_mapbuffer + react_utils + react_nativemodule_core + rrc_image + turbomodulejsijni + rrc_view + yoga + ) +endif() + +target_compile_options( + ${LIB_TARGET_NAME} + PRIVATE + -DLOG_TAG=\"ReactNative\" + -fexceptions + -frtti + -std=c++20 + -Wall +) + +target_include_directories( + ${CMAKE_PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/packages/react-native-bottom-tabs/android/src/main/jni/RNCTabView.h b/packages/react-native-bottom-tabs/android/src/main/jni/RNCTabView.h new file mode 100644 index 00000000..f7a1fb25 --- /dev/null +++ b/packages/react-native-bottom-tabs/android/src/main/jni/RNCTabView.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include +#include + +namespace facebook::react { +JSI_EXPORT +std::shared_ptr RNCTabView_ModuleProvider( + const std::string &moduleName, + const JavaTurboModule::InitParams ¶ms); +} diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h new file mode 100644 index 00000000..9e80b06b --- /dev/null +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h @@ -0,0 +1,47 @@ +#ifdef __cplusplus + +#pragma once + +#include +#include + +namespace facebook::react { + +class RNCTabViewComponentDescriptor final : public ConcreteComponentDescriptor +{ +#ifdef ANDROID +public: + RNCTabViewComponentDescriptor(const ComponentDescriptorParameters ¶meters) + : ConcreteComponentDescriptor(parameters), measurementsManager_( + std::make_shared(contextContainer_)) {} + + void adopt(ShadowNode &shadowNode) const override + { + ConcreteComponentDescriptor::adopt(shadowNode); + + auto &rncTabViewShadowNode = + static_cast(shadowNode); + + // `RNCTabViewShadowNode` uses `RNCTabViewMeasurementsManager` to + // provide measurements to Yoga. + rncTabViewShadowNode.setSliderMeasurementsManager( + measurementsManager_); + + // All `RNCTabViewShadowNode`s must have leaf Yoga nodes with properly + // setup measure function. + rncTabViewShadowNode.enableMeasurement(); + } + +private: + const std::shared_ptr measurementsManager_; +#else +public: + RNCTabViewComponentDescriptor(const ComponentDescriptorParameters ¶meters) + : ConcreteComponentDescriptor(parameters) {} +#endif + +}; + +} + +#endif diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.cpp b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.cpp new file mode 100644 index 00000000..996da9f4 --- /dev/null +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.cpp @@ -0,0 +1,65 @@ +#ifdef ANDROID +#include "RNCTabViewMeasurementsManager.h" + +#include +#include +#include + + +using namespace facebook::jni; + +namespace facebook::react +{ +Size RNCTabViewMeasurementsManager::measure( + SurfaceId surfaceId, + LayoutConstraints layoutConstraints) const +{ + { + std::scoped_lock lock(mutex_); + if (hasBeenMeasured_) + { + return cachedMeasurement_; + } + } + + const jni::global_ref& fabricUIManager = + contextContainer_->at>("FabricUIManager"); + + static auto measure = facebook::jni::findClassStatic( + "com/facebook/react/fabric/FabricUIManager") + ->getMethod("measure"); + + auto minimumSize = layoutConstraints.minimumSize; + auto maximumSize = layoutConstraints.maximumSize; + + local_ref componentName = make_jstring("RNCTabView"); + + auto measurement = yogaMeassureToSize(measure( + fabricUIManager, + surfaceId, + componentName.get(), + nullptr, + nullptr, + nullptr, + minimumSize.width, + maximumSize.width, + minimumSize.height, + maximumSize.height)); + + std::scoped_lock lock(mutex_); + cachedMeasurement_ = measurement; + hasBeenMeasured_ = true; + return measurement; +} +} + +#endif diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.h new file mode 100644 index 00000000..1882ac43 --- /dev/null +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.h @@ -0,0 +1,31 @@ +#ifdef __cplusplus + +#ifdef ANDROID +#pragma once +#include +#include +#include + +namespace facebook::react +{ + + class RNCTabViewMeasurementsManager + { + public: + RNCTabViewMeasurementsManager( + const ContextContainer::Shared &contextContainer) + : contextContainer_(contextContainer) {} + + Size measure(SurfaceId surfaceId, LayoutConstraints layoutConstraints) const; + + private: + const ContextContainer::Shared contextContainer_; + mutable std::mutex mutex_; + mutable bool hasBeenMeasured_ = false; + mutable Size cachedMeasurement_{}; + }; +} + +#endif + +#endif diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp new file mode 100644 index 00000000..47867419 --- /dev/null +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp @@ -0,0 +1,28 @@ +#include "RNCTabViewShadowNode.h" +#include "RNCTabViewMeasurementsManager.h" + +namespace facebook::react { + +extern const char RNCTabViewComponentName[] = "RNCTabView"; + +#ifdef ANDROID +void RNCTabViewShadowNode::setSliderMeasurementsManager( + const std::shared_ptr & + measurementsManager) +{ + ensureUnsealed(); + measurementsManager_ = measurementsManager; +} + +#pragma mark - LayoutableShadowNode + +Size RNCTabViewShadowNode::measureContent( + const LayoutContext & /*layoutContext*/, + const LayoutConstraints &layoutConstraints) const +{ + return measurementsManager_->measure(getSurfaceId(), layoutConstraints); +} + +#endif + +} diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h new file mode 100644 index 00000000..f725d903 --- /dev/null +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h @@ -0,0 +1,48 @@ +#ifdef __cplusplus + +#pragma once + +#include +#include +#include +#include +#include + +#include "RNCTabViewMeasurementsManager.h" + +namespace facebook::react { + +JSI_EXPORT extern const char RNCTabViewComponentName[]; + +/* +* `ShadowNode` for component. +*/ +class JSI_EXPORT RNCTabViewShadowNode final +: public ConcreteViewShadowNode< +RNCTabViewComponentName, +RNCTabViewProps, +RNCTabViewEventEmitter, +RNCTabViewState> +{ +public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + +#ifdef ANDROID + void setSliderMeasurementsManager( + const std::shared_ptr &measurementsManager); + + #pragma mark - LayoutableShadowNode + + Size measureContent( + const LayoutContext &layoutContext, + const LayoutConstraints &layoutConstraints) const override; + +private: + std::shared_ptr measurementsManager_; +#endif + +}; + +} + +#endif diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h new file mode 100644 index 00000000..cabea07e --- /dev/null +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h @@ -0,0 +1,35 @@ +#ifdef __cplusplus + +#pragma once + +#ifdef ANDROID +#include +#include +#include +#endif + +namespace facebook::react { + +class RNCTabViewState +{ +public: + RNCTabViewState() = default; + +#ifdef ANDROID + RNCTabViewState(RNCTabViewState const &previousState, folly::dynamic data) {}; + + folly::dynamic getDynamic() const + { + return {}; + }; + + MapBuffer getMapBuffer() const + { + return MapBufferBuilder::EMPTY(); + }; +#endif +}; + +} + +#endif diff --git a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm index 7d55de42..4d622cef 100644 --- a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm +++ b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm @@ -2,6 +2,7 @@ #import "RCTTabViewComponentView.h" #import +#import #import #import #import diff --git a/packages/react-native-bottom-tabs/package.json b/packages/react-native-bottom-tabs/package.json index f5529e23..b2286a52 100644 --- a/packages/react-native-bottom-tabs/package.json +++ b/packages/react-native-bottom-tabs/package.json @@ -24,6 +24,7 @@ "files": [ "src", "lib", + "common", "android", "ios", "cpp", diff --git a/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec b/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec index d4b92897..03aa219f 100644 --- a/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec +++ b/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec @@ -1,6 +1,7 @@ require "json" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) +new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1' Pod::Spec.new do |s| s.name = "react-native-bottom-tabs" @@ -20,6 +21,13 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,cpp,swift}" s.static_framework = true + if new_arch_enabled + s.subspec "common" do |ss| + ss.source_files = "common/cpp/**/*.{cpp,h}" + ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/common/cpp\"" } + end + end + s.dependency "SwiftUIIntrospect", '~> 1.0' s.dependency 'SDWebImage', '>= 5.19.1' s.dependency 'SDWebImageSVGCoder', '>= 1.7.0' diff --git a/packages/react-native-bottom-tabs/react-native.config.js b/packages/react-native-bottom-tabs/react-native.config.js new file mode 100644 index 00000000..b3ebd7d7 --- /dev/null +++ b/packages/react-native-bottom-tabs/react-native.config.js @@ -0,0 +1,11 @@ +module.exports = { + dependency: { + platforms: { + android: { + libraryName: 'RNCTabView', + componentDescriptors: ['RNCTabViewComponentDescriptor'], + cmakeListsPath: 'src/main/jni/CMakeLists.txt', + }, + }, + }, +}; diff --git a/packages/react-native-bottom-tabs/src/TabView.tsx b/packages/react-native-bottom-tabs/src/TabView.tsx index 616f0ac9..5d81f395 100644 --- a/packages/react-native-bottom-tabs/src/TabView.tsx +++ b/packages/react-native-bottom-tabs/src/TabView.tsx @@ -195,9 +195,6 @@ const TabView = ({ // @ts-ignore const focusedKey = navigationState.routes[navigationState.index].key; const [tabBarHeight, setTabBarHeight] = React.useState(0); - const [measuredDimensions, setMeasuredDimensions] = React.useState< - { width: number; height: number } | undefined - >(); const trimmedRoutes = React.useMemo(() => { if ( @@ -304,9 +301,6 @@ const TabView = ({ onTabBarMeasured={({ nativeEvent: { height } }) => { setTabBarHeight(height); }} - onNativeLayout={({ nativeEvent: { width, height } }) => { - setMeasuredDimensions({ width, height }); - }} hapticFeedbackEnabled={hapticFeedbackEnabled} activeTintColor={activeTintColor} inactiveTintColor={inactiveTintColor} @@ -332,11 +326,8 @@ const TabView = ({ return ( ('RNCTabView'); +export default codegenNativeComponent('RNCTabView', { + interfaceOnly: true, +}); From f5dc359fac025a84f56cdd497f67f58f1c235302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 19 Mar 2025 14:32:24 +0100 Subject: [PATCH 2/5] feat: mutate child nodes with native state --- .../RNCTabViewComponentDescriptor.h | 33 ++-------- .../RNCTabViewMeasurementsManager.cpp | 65 ------------------- .../RNCTabViewMeasurementsManager.h | 31 --------- .../RNCTabView/RNCTabViewShadowNode.cpp | 30 ++++----- .../RNCTabView/RNCTabViewShadowNode.h | 20 +----- .../components/RNCTabView/RNCTabViewState.h | 17 +---- .../ios/Fabric/RCTTabViewComponentView.mm | 13 ++-- 7 files changed, 29 insertions(+), 180 deletions(-) delete mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.cpp delete mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.h diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h index 9e80b06b..a035e1f8 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h @@ -9,37 +9,18 @@ namespace facebook::react { class RNCTabViewComponentDescriptor final : public ConcreteComponentDescriptor { -#ifdef ANDROID public: RNCTabViewComponentDescriptor(const ComponentDescriptorParameters ¶meters) - : ConcreteComponentDescriptor(parameters), measurementsManager_( - std::make_shared(contextContainer_)) {} - - void adopt(ShadowNode &shadowNode) const override - { + : ConcreteComponentDescriptor(parameters) {} + + void adopt(ShadowNode &shadowNode) const override { + react_native_assert(dynamic_cast(&shadowNode)); + + const auto tabViewShadowNode = dynamic_cast(&shadowNode); + tabViewShadowNode->updateStateIfNeeded(); ConcreteComponentDescriptor::adopt(shadowNode); - - auto &rncTabViewShadowNode = - static_cast(shadowNode); - - // `RNCTabViewShadowNode` uses `RNCTabViewMeasurementsManager` to - // provide measurements to Yoga. - rncTabViewShadowNode.setSliderMeasurementsManager( - measurementsManager_); - - // All `RNCTabViewShadowNode`s must have leaf Yoga nodes with properly - // setup measure function. - rncTabViewShadowNode.enableMeasurement(); } -private: - const std::shared_ptr measurementsManager_; -#else -public: - RNCTabViewComponentDescriptor(const ComponentDescriptorParameters ¶meters) - : ConcreteComponentDescriptor(parameters) {} -#endif - }; } diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.cpp b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.cpp deleted file mode 100644 index 996da9f4..00000000 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifdef ANDROID -#include "RNCTabViewMeasurementsManager.h" - -#include -#include -#include - - -using namespace facebook::jni; - -namespace facebook::react -{ -Size RNCTabViewMeasurementsManager::measure( - SurfaceId surfaceId, - LayoutConstraints layoutConstraints) const -{ - { - std::scoped_lock lock(mutex_); - if (hasBeenMeasured_) - { - return cachedMeasurement_; - } - } - - const jni::global_ref& fabricUIManager = - contextContainer_->at>("FabricUIManager"); - - static auto measure = facebook::jni::findClassStatic( - "com/facebook/react/fabric/FabricUIManager") - ->getMethod("measure"); - - auto minimumSize = layoutConstraints.minimumSize; - auto maximumSize = layoutConstraints.maximumSize; - - local_ref componentName = make_jstring("RNCTabView"); - - auto measurement = yogaMeassureToSize(measure( - fabricUIManager, - surfaceId, - componentName.get(), - nullptr, - nullptr, - nullptr, - minimumSize.width, - maximumSize.width, - minimumSize.height, - maximumSize.height)); - - std::scoped_lock lock(mutex_); - cachedMeasurement_ = measurement; - hasBeenMeasured_ = true; - return measurement; -} -} - -#endif diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.h deleted file mode 100644 index 1882ac43..00000000 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewMeasurementsManager.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifdef __cplusplus - -#ifdef ANDROID -#pragma once -#include -#include -#include - -namespace facebook::react -{ - - class RNCTabViewMeasurementsManager - { - public: - RNCTabViewMeasurementsManager( - const ContextContainer::Shared &contextContainer) - : contextContainer_(contextContainer) {} - - Size measure(SurfaceId surfaceId, LayoutConstraints layoutConstraints) const; - - private: - const ContextContainer::Shared contextContainer_; - mutable std::mutex mutex_; - mutable bool hasBeenMeasured_ = false; - mutable Size cachedMeasurement_{}; - }; -} - -#endif - -#endif diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp index 47867419..7fede86c 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp @@ -1,28 +1,20 @@ #include "RNCTabViewShadowNode.h" -#include "RNCTabViewMeasurementsManager.h" namespace facebook::react { extern const char RNCTabViewComponentName[] = "RNCTabView"; -#ifdef ANDROID -void RNCTabViewShadowNode::setSliderMeasurementsManager( - const std::shared_ptr & - measurementsManager) -{ +void RNCTabViewShadowNode::updateStateIfNeeded() { ensureUnsealed(); - measurementsManager_ = measurementsManager; -} - -#pragma mark - LayoutableShadowNode - -Size RNCTabViewShadowNode::measureContent( - const LayoutContext & /*layoutContext*/, - const LayoutConstraints &layoutConstraints) const -{ - return measurementsManager_->measure(getSurfaceId(), layoutConstraints); -} - -#endif + const auto &stateData = getStateData(); + auto frameSize = stateData.frameSize; + + for (auto &child : getLayoutableChildNodes()) { + auto yogaChild = dynamic_cast(child); + if (!yogaChild->getSealed() && yogaChild->getLayoutMetrics().frame.size != frameSize) { + yogaChild->setSize(frameSize); + } + } +}; } diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h index f725d903..73480d3b 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h @@ -8,8 +8,6 @@ #include #include -#include "RNCTabViewMeasurementsManager.h" - namespace facebook::react { JSI_EXPORT extern const char RNCTabViewComponentName[]; @@ -26,23 +24,9 @@ RNCTabViewState> { public: using ConcreteViewShadowNode::ConcreteViewShadowNode; - -#ifdef ANDROID - void setSliderMeasurementsManager( - const std::shared_ptr &measurementsManager); - - #pragma mark - LayoutableShadowNode - - Size measureContent( - const LayoutContext &layoutContext, - const LayoutConstraints &layoutConstraints) const override; - -private: - std::shared_ptr measurementsManager_; -#endif - + + void updateStateIfNeeded(); }; - } #endif diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h index cabea07e..621e7842 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h @@ -1,7 +1,7 @@ #ifdef __cplusplus #pragma once - +#include #ifdef ANDROID #include #include @@ -14,20 +14,9 @@ class RNCTabViewState { public: RNCTabViewState() = default; + RNCTabViewState(Size frameSize): frameSize(frameSize) {}; -#ifdef ANDROID - RNCTabViewState(RNCTabViewState const &previousState, folly::dynamic data) {}; - - folly::dynamic getDynamic() const - { - return {}; - }; - - MapBuffer getMapBuffer() const - { - return MapBufferBuilder::EMPTY(); - }; -#endif + Size frameSize; }; } diff --git a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm index 4d622cef..86e82b5c 100644 --- a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm +++ b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm @@ -38,6 +38,7 @@ @interface RCTTabViewComponentView () *_reactSubviews; + RNCTabViewShadowNode::ConcreteState::Shared _state; } + (ComponentDescriptorProvider)componentDescriptorProvider @@ -85,6 +86,10 @@ - (void)unmountChildComponentView:(PlatformView *)chil [childComponentView removeFromSuperview]; } +- (void)updateState:(const facebook::react::State::Shared &)state oldState:(const facebook::react::State::Shared &)oldState { + _state = std::static_pointer_cast(state); +} + - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { const auto &oldViewProps = *std::static_pointer_cast(_props); @@ -246,13 +251,7 @@ - (void)onTabBarMeasuredWithHeight:(NSInteger)height reactTag:(NSNumber *)reactT } - (void)onLayoutWithSize:(CGSize)size reactTag:(NSNumber *)reactTag { - auto eventEmitter = std::static_pointer_cast(_eventEmitter); - if (eventEmitter) { - eventEmitter->onNativeLayout(RNCTabViewEventEmitter::OnNativeLayout { - .height = size.height, - .width = size.width - }); - } + _state->updateState(RNCTabViewState({size.width, size.height})); } @end From 2c4d5bd385dfb3300faf314a92191cba0ef2432f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Thu, 20 Mar 2025 10:56:24 +0100 Subject: [PATCH 3/5] [wip]: maybe in layout method --- .../RNCTabView/RNCTabViewShadowNode.cpp | 31 ++++++++++++++----- .../RNCTabView/RNCTabViewShadowNode.h | 6 ++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp index 7fede86c..58c2e89d 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp @@ -5,16 +5,33 @@ namespace facebook::react { extern const char RNCTabViewComponentName[] = "RNCTabView"; void RNCTabViewShadowNode::updateStateIfNeeded() { - ensureUnsealed(); +// ensureUnsealed(); +// const auto &stateData = getStateData(); +// auto frameSize = stateData.frameSize; +// +// for (auto &child : getLayoutableChildNodes()) { +// auto yogaChild = dynamic_cast(child); +// yogaChild->getState(); +// if (!yogaChild->getSealed() && yogaChild->getLayoutMetrics().frame.size != frameSize) { +// yogaChild->setSize(frameSize); +// } +// } + +}; + +void RNCTabViewShadowNode::layout(facebook::react::LayoutContext layoutContext) { + YogaLayoutableShadowNode::layout(layoutContext); + + // Now adjust children as needed const auto &stateData = getStateData(); auto frameSize = stateData.frameSize; - for (auto &child : getLayoutableChildNodes()) { - auto yogaChild = dynamic_cast(child); - if (!yogaChild->getSealed() && yogaChild->getLayoutMetrics().frame.size != frameSize) { - yogaChild->setSize(frameSize); - } + for (auto childNode : getLayoutableChildNodes()) { + childNode->ensureUnsealed(); + auto yogaChild = dynamic_cast(childNode); + yogaChild->setSize(frameSize); + } -}; +} } diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h index 73480d3b..076efa5c 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include namespace facebook::react { @@ -26,6 +28,10 @@ RNCTabViewState> using ConcreteViewShadowNode::ConcreteViewShadowNode; void updateStateIfNeeded(); + +#pragma mark - ShadowNode overrides + + void layout(LayoutContext layoutContext) override; }; } From 38df843d100b5be0580badc249affb386a781b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Thu, 20 Mar 2025 16:31:56 +0100 Subject: [PATCH 4/5] wip: add screen wrappers and update their shadow node state --- .../RNCTabViewComponentDescriptor.h | 28 --------- .../RNCTabViewScreenComponentDescriptor.h | 33 ++++++++++ .../RNCTabView/RNCTabViewScreenShadowNode.cpp | 7 +++ ...dowNode.h => RNCTabViewScreenShadowNode.h} | 22 +++---- ...TabViewState.h => RNCTabViewScreenState.h} | 8 +-- .../RNCTabView/RNCTabViewShadowNode.cpp | 37 ----------- .../ios/Fabric/RCTTabViewComponentView.mm | 26 +++++--- .../Fabric/RCTTabViewScreenComponentView.h | 19 ++++++ .../Fabric/RCTTabViewScreenComponentView.mm | 63 +++++++++++++++++++ .../react-native-bottom-tabs/package.json | 3 +- .../react-native-bottom-tabs/src/TabView.tsx | 5 +- .../src/TabViewNativeComponent.ts | 4 +- .../src/TabViewScreenNativeComponent.ts | 8 +++ 13 files changed, 167 insertions(+), 96 deletions(-) delete mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h create mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenComponentDescriptor.h create mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenShadowNode.cpp rename packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/{RNCTabViewShadowNode.h => RNCTabViewScreenShadowNode.h} (53%) rename packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/{RNCTabViewState.h => RNCTabViewScreenState.h} (69%) delete mode 100644 packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp create mode 100644 packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewScreenComponentView.h create mode 100644 packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewScreenComponentView.mm create mode 100644 packages/react-native-bottom-tabs/src/TabViewScreenNativeComponent.ts diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h deleted file mode 100644 index a035e1f8..00000000 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifdef __cplusplus - -#pragma once - -#include -#include - -namespace facebook::react { - -class RNCTabViewComponentDescriptor final : public ConcreteComponentDescriptor -{ -public: - RNCTabViewComponentDescriptor(const ComponentDescriptorParameters ¶meters) - : ConcreteComponentDescriptor(parameters) {} - - void adopt(ShadowNode &shadowNode) const override { - react_native_assert(dynamic_cast(&shadowNode)); - - const auto tabViewShadowNode = dynamic_cast(&shadowNode); - tabViewShadowNode->updateStateIfNeeded(); - ConcreteComponentDescriptor::adopt(shadowNode); - } - -}; - -} - -#endif diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenComponentDescriptor.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenComponentDescriptor.h new file mode 100644 index 00000000..c44c58fb --- /dev/null +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenComponentDescriptor.h @@ -0,0 +1,33 @@ +#ifdef __cplusplus + +#pragma once + +#include +#include + +namespace facebook::react { + +class RNCTabViewScreenComponentDescriptor final : public ConcreteComponentDescriptor +{ +public: + RNCTabViewScreenComponentDescriptor(const ComponentDescriptorParameters ¶meters) + : ConcreteComponentDescriptor(parameters) {} + + + /** + Retrieve shadow node's state and update it's layout size accordingly. + This is needed because we need to accomodate for bottom bar / sidebar size. + */ + void adopt(ShadowNode &shadowNode) const override { + auto& layoutableShadowNode = static_cast(shadowNode); + auto& stateData = layoutableShadowNode.getStateData(); + + layoutableShadowNode.setSize(stateData.frameSize); + + ConcreteComponentDescriptor::adopt(shadowNode); + } +}; + +} + +#endif diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenShadowNode.cpp b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenShadowNode.cpp new file mode 100644 index 00000000..2134c247 --- /dev/null +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenShadowNode.cpp @@ -0,0 +1,7 @@ +#include "RNCTabViewScreenShadowNode.h" + +namespace facebook::react { + +extern const char RNCTabViewScreenComponentName[] = "RNCTabViewScreen"; + +} diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenShadowNode.h similarity index 53% rename from packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h rename to packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenShadowNode.h index 076efa5c..f9dfe13f 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenShadowNode.h @@ -3,7 +3,7 @@ #pragma once #include -#include +#include #include #include #include @@ -12,26 +12,20 @@ namespace facebook::react { -JSI_EXPORT extern const char RNCTabViewComponentName[]; +JSI_EXPORT extern const char RNCTabViewScreenComponentName[]; /* -* `ShadowNode` for component. +* `ShadowNode` for component. */ -class JSI_EXPORT RNCTabViewShadowNode final +class JSI_EXPORT RNCTabViewScreenShadowNode final : public ConcreteViewShadowNode< -RNCTabViewComponentName, -RNCTabViewProps, -RNCTabViewEventEmitter, -RNCTabViewState> +RNCTabViewScreenComponentName, +RNCTabViewScreenProps, +RNCTabViewScreenEventEmitter, +RNCTabViewScreenState> { public: using ConcreteViewShadowNode::ConcreteViewShadowNode; - - void updateStateIfNeeded(); - -#pragma mark - ShadowNode overrides - - void layout(LayoutContext layoutContext) override; }; } diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenState.h similarity index 69% rename from packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h rename to packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenState.h index 621e7842..6c07034b 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenState.h @@ -10,12 +10,12 @@ namespace facebook::react { -class RNCTabViewState +class RNCTabViewScreenState { public: - RNCTabViewState() = default; - RNCTabViewState(Size frameSize): frameSize(frameSize) {}; - + RNCTabViewScreenState() = default; + RNCTabViewScreenState(Size frameSize): frameSize(frameSize) {}; + Size frameSize; }; diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp deleted file mode 100644 index 58c2e89d..00000000 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "RNCTabViewShadowNode.h" - -namespace facebook::react { - -extern const char RNCTabViewComponentName[] = "RNCTabView"; - -void RNCTabViewShadowNode::updateStateIfNeeded() { -// ensureUnsealed(); -// const auto &stateData = getStateData(); -// auto frameSize = stateData.frameSize; -// -// for (auto &child : getLayoutableChildNodes()) { -// auto yogaChild = dynamic_cast(child); -// yogaChild->getState(); -// if (!yogaChild->getSealed() && yogaChild->getLayoutMetrics().frame.size != frameSize) { -// yogaChild->setSize(frameSize); -// } -// } - -}; - -void RNCTabViewShadowNode::layout(facebook::react::LayoutContext layoutContext) { - YogaLayoutableShadowNode::layout(layoutContext); - - // Now adjust children as needed - const auto &stateData = getStateData(); - auto frameSize = stateData.frameSize; - - for (auto childNode : getLayoutableChildNodes()) { - childNode->ensureUnsealed(); - auto yogaChild = dynamic_cast(childNode); - yogaChild->setSize(frameSize); - - } -} - -} diff --git a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm index 86e82b5c..44d12fd8 100644 --- a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm +++ b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm @@ -1,8 +1,8 @@ #ifdef RCT_NEW_ARCH_ENABLED #import "RCTTabViewComponentView.h" +#import "RCTTabViewScreenComponentView.h" #import -#import #import #import #import @@ -38,7 +38,6 @@ @interface RCTTabViewComponentView () *_reactSubviews; - RNCTabViewShadowNode::ConcreteState::Shared _state; } + (ComponentDescriptorProvider)componentDescriptorProvider @@ -86,10 +85,6 @@ - (void)unmountChildComponentView:(PlatformView *)chil [childComponentView removeFromSuperview]; } -- (void)updateState:(const facebook::react::State::Shared &)state oldState:(const facebook::react::State::Shared &)oldState { - _state = std::static_pointer_cast(state); -} - - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { const auto &oldViewProps = *std::static_pointer_cast(_props); @@ -250,8 +245,25 @@ - (void)onTabBarMeasuredWithHeight:(NSInteger)height reactTag:(NSNumber *)reactT } } +- (size_t)getSelectedIndex +{ + const auto &props = *std::static_pointer_cast(_props); + + auto selectedItem = std::find_if(props.items.begin(), props.items.end(), + [&](const RNCTabViewItemsStruct &item) { + return item.key == props.selectedPage; + }); + + return std::distance(props.items.begin(), selectedItem); +} + - (void)onLayoutWithSize:(CGSize)size reactTag:(NSNumber *)reactTag { - _state->updateState(RNCTabViewState({size.width, size.height})); + size_t selectedIndex = [self getSelectedIndex]; + + RCTTabViewScreenComponentView *child = (RCTTabViewScreenComponentView *)_reactSubviews[selectedIndex]; + if ([child respondsToSelector:@selector(updateFrameSize:)]) { + [child updateFrameSize:size]; + } } @end diff --git a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewScreenComponentView.h b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewScreenComponentView.h new file mode 100644 index 00000000..a6374034 --- /dev/null +++ b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewScreenComponentView.h @@ -0,0 +1,19 @@ +#ifdef RCT_NEW_ARCH_ENABLED +#import +#if TARGET_OS_OSX +#import +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTTabViewScreenComponentView: RCTViewComponentView + +- (void)updateFrameSize:(CGSize)size; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* RCT_NEW_ARCH_ENABLED */ diff --git a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewScreenComponentView.mm b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewScreenComponentView.mm new file mode 100644 index 00000000..d860f5d5 --- /dev/null +++ b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewScreenComponentView.mm @@ -0,0 +1,63 @@ +#ifdef RCT_NEW_ARCH_ENABLED +#import "RCTTabViewScreenComponentView.h" + +#import +#import +#import +#import + +#import + +#if TARGET_OS_OSX +typedef NSView PlatformView; +#else +typedef UIView PlatformView; +#endif + + +using namespace facebook::react; + +@interface RCTTabViewScreenComponentView () { +} + +@end + +@implementation RCTTabViewScreenComponentView { + RNCTabViewScreenShadowNode::ConcreteState::Shared _state; +} + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + } + + return self; +} + +- (void)updateState:(const facebook::react::State::Shared &)state oldState:(const facebook::react::State::Shared &)oldState { + _state = std::static_pointer_cast(state); +} + +- (void)updateFrameSize:(CGSize)size +{ + _state->updateState(RNCTabViewScreenState({size.width, size.height})); +} + +@end + +Class RNCTabViewScreenCls(void) +{ + return RCTTabViewScreenComponentView.class; +} + +#endif // RCT_NEW_ARCH_ENABLED + + + diff --git a/packages/react-native-bottom-tabs/package.json b/packages/react-native-bottom-tabs/package.json index b2286a52..8e05a81d 100644 --- a/packages/react-native-bottom-tabs/package.json +++ b/packages/react-native-bottom-tabs/package.json @@ -127,7 +127,8 @@ }, "ios": { "componentProvider": { - "RNCTabView": "RCTTabViewComponentView" + "RNCTabView": "RCTTabViewComponentView", + "RNCTabViewScreen": "RCTTabViewScreenComponentView" } } } diff --git a/packages/react-native-bottom-tabs/src/TabView.tsx b/packages/react-native-bottom-tabs/src/TabView.tsx index 5d81f395..1bb70641 100644 --- a/packages/react-native-bottom-tabs/src/TabView.tsx +++ b/packages/react-native-bottom-tabs/src/TabView.tsx @@ -16,6 +16,7 @@ import NativeTabView from './TabViewNativeComponent'; import useLatestCallback from 'use-latest-callback'; import type { BaseRoute, NavigationState } from './types'; import DelayedFreeze from './DelayedFreeze'; +import TabViewScreenNativeComponent from './TabViewScreenNativeComponent'; const isAppleSymbol = (icon: any): icon is { sfSymbol: string } => icon?.sfSymbol; @@ -324,7 +325,7 @@ const TabView = ({ const freeze = !focused ? getFreezeOnBlur({ route }) : false; return ( - ({ jumpTo, })} - + ); })} diff --git a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts index ea75c65f..1a9da486 100644 --- a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts +++ b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts @@ -58,6 +58,4 @@ export interface TabViewProps extends ViewProps { fontSize?: Int32; } -export default codegenNativeComponent('RNCTabView', { - interfaceOnly: true, -}); +export default codegenNativeComponent('RNCTabView'); diff --git a/packages/react-native-bottom-tabs/src/TabViewScreenNativeComponent.ts b/packages/react-native-bottom-tabs/src/TabViewScreenNativeComponent.ts new file mode 100644 index 00000000..c6addc1e --- /dev/null +++ b/packages/react-native-bottom-tabs/src/TabViewScreenNativeComponent.ts @@ -0,0 +1,8 @@ +import type { ViewProps } from 'react-native'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; + +export interface TabViewScreenProps extends ViewProps {} + +export default codegenNativeComponent('RNCTabViewScreen', { + interfaceOnly: true, +}); From 3b34081f34cb2ecf51156359c8db58c343bec512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Thu, 20 Mar 2025 18:31:23 +0100 Subject: [PATCH 5/5] wip: rough working state for android --- apps/example/src/Screens/Article.tsx | 1 + .../main/java/com/rcttabview/RCTTabView.kt | 42 ++++---- .../java/com/rcttabview/RCTTabViewPackage.kt | 1 + .../android/src/main/jni/RNCTabView.h | 2 +- .../android/src/newarch/RCTTabViewManager.kt | 29 ++++-- .../src/newarch/RCTTabViewScreenManager.kt | 98 +++++++++++++++++++ .../RNCTabViewScreenComponentDescriptor.h | 6 +- .../RNCTabView/RNCTabViewScreenState.h | 10 +- .../react-native.config.js | 2 +- 9 files changed, 158 insertions(+), 33 deletions(-) create mode 100644 packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewScreenManager.kt diff --git a/apps/example/src/Screens/Article.tsx b/apps/example/src/Screens/Article.tsx index da3d5290..431d7820 100644 --- a/apps/example/src/Screens/Article.tsx +++ b/apps/example/src/Screens/Article.tsx @@ -53,6 +53,7 @@ export function Article({ console.log(Platform.OS, ' Rendering Article'); return ( console.log(layout)} ref={ref} style={{ backgroundColor: '#fff' }} contentContainerStyle={styles.content} diff --git a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt index 4719d1d8..f3baa61f 100644 --- a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt +++ b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt @@ -12,6 +12,7 @@ import android.util.Log import android.util.Size import android.util.TypedValue import android.view.Choreographer +import android.view.Gravity import android.view.HapticFeedbackConstants import android.view.MenuItem import android.view.View @@ -72,16 +73,19 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) { addView( layoutHolder, LayoutParams( - LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, 0, - ).apply { weight = 1f } + ) + .apply { weight = 1f } ) layoutHolder.isSaveEnabled = false addView(bottomNavigation, LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT - )) + ).apply { + gravity = Gravity.BOTTOM + }) uiModeConfiguration = resources.configuration.uiMode post { @@ -142,9 +146,11 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) { return } - val container = createContainer() - container.addView(child, params) - layoutHolder.addView(container, index) +// val container = createContainer() +// container.addView(child, params) + child.visibility = GONE + child.isEnabled = false + layoutHolder.addView(child, index) val itemKey = items[index].key if (selectedItem == itemKey) { @@ -153,18 +159,18 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) { } } - private fun createContainer(): FrameLayout { - val container = FrameLayout(context).apply { - layoutParams = FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT - ) - isSaveEnabled = false - visibility = GONE - isEnabled = false - } - return container - } +// private fun createContainer(): FrameLayout { +// val container = FrameLayout(context).apply { +// layoutParams = FrameLayout.LayoutParams( +// FrameLayout.LayoutParams.MATCH_PARENT, +// FrameLayout.LayoutParams.MATCH_PARENT +// ) +// isSaveEnabled = false +// visibility = GONE +// isEnabled = false +// } +// return container +// } private fun setSelectedIndex(itemId: Int) { bottomNavigation.selectedItemId = itemId diff --git a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewPackage.kt b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewPackage.kt index 54492e1b..729eecf7 100644 --- a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewPackage.kt +++ b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewPackage.kt @@ -10,6 +10,7 @@ class RCTTabViewPackage : ReactPackage { override fun createViewManagers(reactContext: ReactApplicationContext): List> { val viewManagers: MutableList> = ArrayList() viewManagers.add(RCTTabViewManager(reactContext)) + viewManagers.add(RCTTabViewScreenManager(reactContext)) return viewManagers } diff --git a/packages/react-native-bottom-tabs/android/src/main/jni/RNCTabView.h b/packages/react-native-bottom-tabs/android/src/main/jni/RNCTabView.h index f7a1fb25..9f7070bd 100644 --- a/packages/react-native-bottom-tabs/android/src/main/jni/RNCTabView.h +++ b/packages/react-native-bottom-tabs/android/src/main/jni/RNCTabView.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace facebook::react { JSI_EXPORT diff --git a/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewManager.kt b/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewManager.kt index 5d919760..8dca9840 100644 --- a/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewManager.kt +++ b/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewManager.kt @@ -1,7 +1,9 @@ package com.rcttabview +import android.util.Log import android.view.View -import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.core.view.children import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.module.annotations.ReactModule @@ -11,7 +13,6 @@ import com.facebook.react.uimanager.ViewGroupManager import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.viewmanagers.RNCTabViewManagerDelegate import com.facebook.react.viewmanagers.RNCTabViewManagerInterface -import com.rcttabview.events.OnNativeLayoutEvent import com.rcttabview.events.PageSelectedEvent import com.rcttabview.events.TabLongPressEvent @@ -36,9 +37,21 @@ class RCTTabViewManager(context: ReactApplicationContext) : eventDispatcher?.dispatchEvent(TabLongPressEvent(viewTag = view.id, key)) } - view.onNativeLayoutListener = { width, height -> - eventDispatcher?.dispatchEvent(OnNativeLayoutEvent(viewTag = view.id, width, height)) - } +// view.onNativeLayoutListener = { width, height -> +// val childCount = tabViewImpl.getChildCount(view) +// for (i in 0 until childCount) { // Use 'until' instead of '..' to exclude childCount +// val child = tabViewImpl.getChildAt(view, i) +// +// // Safe cast for FrameLayout +// if (child is RCTTabViewScreen) { +// child.updateFrame(width, height) +// +// Log.w("TAB_VIEW", "CHILD: ${child}") +// } +// } +// +//// eventDispatcher?.dispatchEvent(OnNativeLayoutEvent(viewTag = view.id, width, height)) +// } return view } @@ -72,9 +85,9 @@ class RCTTabViewManager(context: ReactApplicationContext) : tabViewImpl.removeViewAt(parent, index) } - override fun needsCustomLayoutForChildren(): Boolean { - return tabViewImpl.needsCustomLayoutForChildren() - } +// override fun needsCustomLayoutForChildren(): Boolean { +// return tabViewImpl.needsCustomLayoutForChildren() +// } override fun setItems(view: ReactBottomNavigationView?, value: ReadableArray?) { if (view != null && value != null) diff --git a/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewScreenManager.kt b/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewScreenManager.kt new file mode 100644 index 00000000..cd8e53e5 --- /dev/null +++ b/packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewScreenManager.kt @@ -0,0 +1,98 @@ +package com.rcttabview + +import android.content.Context +import android.os.Build +import android.util.Log +import android.view.View +import android.view.WindowInsets +import android.view.WindowManager +import android.widget.FrameLayout +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule +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.RNCTabViewScreenManagerDelegate +import com.facebook.react.viewmanagers.RNCTabViewScreenManagerInterface +import com.facebook.react.views.view.ReactViewGroup + +class RCTTabViewScreen(context: Context): ReactViewGroup(context) { + private var stateWrapper: StateWrapper? = null + + public fun setStateWrapper(stateWrapper: StateWrapper?) { + this.stateWrapper = stateWrapper + } + + init { + getSystemInsets(this) { left, top, right, bottom -> + Log.w("TAB_VIEW", "${left} ${top} ${right} ${bottom}") + + stateWrapper?.updateState(Arguments.createMap().apply { + putDouble("width", Utils.convertPixelsToDp(context, width)) + putDouble("height", Utils.convertPixelsToDp(context, height - bottom)) + }) + } + } + + fun getSystemInsets(view: View, callback: (left: Int, top: Int, right: Int, bottom: Int) -> Unit) { + ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + callback( + systemBars.left, // Left inset (for left-aligned Navigation Rail) + systemBars.top, // Top inset + systemBars.right, // Right inset (for right-aligned Navigation Rail) + systemBars.bottom // Bottom inset (for bottom navigation) + ) + insets + } + // Ensure we request insets + if (view.isAttachedToWindow) { + ViewCompat.requestApplyInsets(view) + } else { + view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + ViewCompat.requestApplyInsets(v) + v.removeOnAttachStateChangeListener(this) + } + override fun onViewDetachedFromWindow(v: View) = Unit + }) + } + } +} + + +@ReactModule(name = "RNCTabViewScreen") +class RCTTabViewScreenManager(context: ReactApplicationContext) : + ViewGroupManager(), + RNCTabViewScreenManagerInterface { + + private val delegate: RNCTabViewScreenManagerDelegate = + RNCTabViewScreenManagerDelegate(this) + + + override fun createViewInstance(context: ThemedReactContext): RCTTabViewScreen { + return RCTTabViewScreen(context); + } + + override fun updateState( + view: RCTTabViewScreen, + props: ReactStylesDiffMap?, + stateWrapper: StateWrapper? + ): Any? { + view.setStateWrapper(stateWrapper) + return super.updateState(view, props, stateWrapper) + } + + override fun getDelegate(): ViewManagerDelegate { + return delegate + } + + override fun getName(): String { + return "RNCTabViewScreen" + } +} diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenComponentDescriptor.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenComponentDescriptor.h index c44c58fb..1f94458e 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenComponentDescriptor.h +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenComponentDescriptor.h @@ -13,7 +13,7 @@ class RNCTabViewScreenComponentDescriptor final : public ConcreteComponentDescri RNCTabViewScreenComponentDescriptor(const ComponentDescriptorParameters ¶meters) : ConcreteComponentDescriptor(parameters) {} - + /** Retrieve shadow node's state and update it's layout size accordingly. This is needed because we need to accomodate for bottom bar / sidebar size. @@ -21,9 +21,9 @@ class RNCTabViewScreenComponentDescriptor final : public ConcreteComponentDescri void adopt(ShadowNode &shadowNode) const override { auto& layoutableShadowNode = static_cast(shadowNode); auto& stateData = layoutableShadowNode.getStateData(); - + layoutableShadowNode.setSize(stateData.frameSize); - + ConcreteComponentDescriptor::adopt(shadowNode); } }; diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenState.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenState.h index 6c07034b..5742ac94 100644 --- a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenState.h +++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewScreenState.h @@ -4,8 +4,6 @@ #include #ifdef ANDROID #include -#include -#include #endif namespace facebook::react { @@ -17,6 +15,14 @@ class RNCTabViewScreenState RNCTabViewScreenState(Size frameSize): frameSize(frameSize) {}; Size frameSize; + +#ifdef ANDROID + RNCTabViewScreenState(RNCTabViewScreenState const &previousState, folly::dynamic data) + : frameSize((Float)data["width"].getDouble(), (Float)data["height"].getDouble()) {}; + folly::dynamic getDynamic() const { + return {}; + }; +#endif }; } diff --git a/packages/react-native-bottom-tabs/react-native.config.js b/packages/react-native-bottom-tabs/react-native.config.js index b3ebd7d7..ef6fcb2a 100644 --- a/packages/react-native-bottom-tabs/react-native.config.js +++ b/packages/react-native-bottom-tabs/react-native.config.js @@ -3,7 +3,7 @@ module.exports = { platforms: { android: { libraryName: 'RNCTabView', - componentDescriptors: ['RNCTabViewComponentDescriptor'], + componentDescriptors: ['RNCTabViewScreenComponentDescriptor'], cmakeListsPath: 'src/main/jni/CMakeLists.txt', }, },