feat: move Fabric state wrapper to host view (#230)
* feat: move state wrapper from container view to host view * fix(ios): call updateStateIfNeeded on every state update * fix(ios): handle device rotation and only track width changes * feat(android): align state update flow with iOS * chore: improve clean script with colors
This commit is contained in:
parent
64cf0531ab
commit
248b64de25
135
AGENTS.md
135
AGENTS.md
@ -1,3 +1,5 @@
|
||||
# Agent Instructions
|
||||
|
||||
## Get Started
|
||||
|
||||
- See README to know more about this project.
|
||||
@ -6,5 +8,136 @@
|
||||
## Rules
|
||||
|
||||
1. I will do builds and UI test myself.
|
||||
2. YOU MUST NOT commit changes youself until I explicitly tell you to.
|
||||
2. YOU MUST NOT commit changes yourself until I explicitly tell you to.
|
||||
3. YOU MUST NOT create summary documents unless you are told to.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a React Native Fabric (New Architecture) bottom sheet library. It provides native bottom sheet functionality for both iOS and Android.
|
||||
|
||||
### Key Technologies
|
||||
|
||||
- **React Native New Architecture (Fabric)** - No bridge, direct C++ communication
|
||||
- **Codegen** - Auto-generates native interfaces from TypeScript specs
|
||||
- **C++ Shared Code** - State and shadow nodes shared between iOS and Android
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── fabric/ # Native component specs (codegen input)
|
||||
│ ├── TrueSheetViewNativeComponent.ts # Host view spec (has interfaceOnly: true)
|
||||
│ ├── TrueSheetContainerViewNativeComponent.ts
|
||||
│ ├── TrueSheetContentViewNativeComponent.ts
|
||||
│ └── TrueSheetFooterViewNativeComponent.ts
|
||||
├── TrueSheet.tsx # Main React component
|
||||
└── TrueSheet.types.ts # TypeScript types
|
||||
|
||||
ios/
|
||||
├── TrueSheetView.mm/.h # Host view (Fabric component)
|
||||
├── TrueSheetContainerView.mm/.h
|
||||
├── TrueSheetContentView.mm/.h
|
||||
├── TrueSheetFooterView.mm/.h
|
||||
├── TrueSheetViewController.mm/.h # UIViewController for sheet presentation
|
||||
└── TrueSheetModule.mm/.h # TurboModule for imperative methods
|
||||
|
||||
android/src/main/java/com/lodev09/truesheet/
|
||||
├── TrueSheetView.kt # Host view
|
||||
├── TrueSheetViewManager.kt # View manager
|
||||
├── TrueSheetContainerView.kt
|
||||
├── TrueSheetViewController.kt # Dialog/BottomSheet controller
|
||||
└── TrueSheetModule.kt # TurboModule
|
||||
|
||||
common/cpp/react/renderer/components/TrueSheetSpec/
|
||||
├── TrueSheetViewState.h/.cpp # Shared state (containerWidth)
|
||||
├── TrueSheetViewShadowNode.h/.cpp # Custom shadow node with adjustLayoutWithState()
|
||||
└── TrueSheetViewComponentDescriptor.h # Custom descriptor that calls adjustLayoutWithState()
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### View Hierarchy
|
||||
|
||||
```
|
||||
TrueSheetView (host view - hidden, manages state)
|
||||
└── TrueSheetContainerView (fills controller's view)
|
||||
├── TrueSheetContentView (sheet content)
|
||||
└── TrueSheetFooterView (sticky footer)
|
||||
```
|
||||
|
||||
### Fabric State Management
|
||||
|
||||
The host view (`TrueSheetView`) uses Fabric state to pass native dimensions to Yoga for layout:
|
||||
|
||||
1. **State files** (`TrueSheetViewState.h/.cpp`) - Hold `containerWidth` from native
|
||||
2. **Shadow node** (`TrueSheetViewShadowNode`) - `adjustLayoutWithState()` updates Yoga dimensions
|
||||
3. **Component descriptor** - Calls `adjustLayoutWithState()` on adopt
|
||||
4. **Native view** - Calls `updateState()` when dimensions change (e.g., rotation)
|
||||
|
||||
### iOS State Update Flow
|
||||
|
||||
```
|
||||
TrueSheetView.mm:
|
||||
updateState:oldState: → _state = new state, call updateStateIfNeeded
|
||||
updateStateIfNeeded → check if width changed, push to Yoga via _state->updateState()
|
||||
|
||||
TrueSheetViewController.mm:
|
||||
viewWillTransitionToSize: → notify delegate of size change (rotation)
|
||||
```
|
||||
|
||||
### Android State Update Flow
|
||||
|
||||
```
|
||||
TrueSheetView.kt:
|
||||
setStateWrapper() → store wrapper, call updateState if dimensions available
|
||||
updateState() → stateWrapper.updateState() with WritableNativeMap
|
||||
|
||||
TrueSheetViewManager.kt:
|
||||
updateState() → view.setStateWrapper(stateWrapper)
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### interfaceOnly: true
|
||||
|
||||
In `TrueSheetViewNativeComponent.ts`, `interfaceOnly: true` means:
|
||||
- Codegen generates interfaces but not the component descriptor
|
||||
- We provide custom C++ files (state, shadow node, descriptor) in `common/`
|
||||
|
||||
### Container View
|
||||
|
||||
The container view is a simple pass-through without custom state. It uses codegen-generated descriptor from `ComponentDescriptors.h`.
|
||||
|
||||
### iOS: Width-only tracking
|
||||
|
||||
iOS only tracks width changes for state updates since height is determined by content. See `_lastContainerWidth` in `TrueSheetView.mm`.
|
||||
|
||||
### Android: Width and Height
|
||||
|
||||
Android tracks both dimensions in state since the dialog size matters for layout.
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a new prop
|
||||
|
||||
1. Add to `TrueSheetViewNativeComponent.ts`
|
||||
2. Run codegen (build the app)
|
||||
3. Implement in `TrueSheetView.mm` (iOS) and `TrueSheetViewManager.kt` (Android)
|
||||
|
||||
### Adding a new event
|
||||
|
||||
1. Add `DirectEventHandler` to native component spec
|
||||
2. Create event class in `ios/events/` and `android/.../events/`
|
||||
3. Emit from native view
|
||||
|
||||
### Modifying state/shadow node
|
||||
|
||||
1. Update `TrueSheetViewState.h/.cpp`
|
||||
2. Update `TrueSheetViewShadowNode.cpp` if layout logic changes
|
||||
3. Update native views to push new state values
|
||||
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
yarn tidy # Format code (run before git commits)
|
||||
```
|
||||
|
||||
@ -3,9 +3,6 @@ package com.lodev09.truesheet
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import androidx.core.view.isNotEmpty
|
||||
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.views.view.ReactViewGroup
|
||||
|
||||
@ -29,53 +26,6 @@ class TrueSheetContainerView(private val reactContext: ThemedReactContext) :
|
||||
|
||||
var delegate: TrueSheetContainerViewDelegate? = null
|
||||
|
||||
private var stateWrapper: StateWrapper? = null
|
||||
|
||||
// Pending dimensions to update when stateWrapper becomes available
|
||||
private var pendingWidth: Int = 0
|
||||
private var pendingHeight: Int = 0
|
||||
|
||||
fun setStateWrapper(wrapper: StateWrapper?) {
|
||||
stateWrapper = wrapper
|
||||
|
||||
if (wrapper == null) return
|
||||
|
||||
// Get dimensions from parent controller and update state if we haven't yet
|
||||
val controller = parent as? TrueSheetViewController
|
||||
if (controller != null && pendingWidth == 0) {
|
||||
val w = controller.width
|
||||
val h = controller.height
|
||||
if (w > 0 && h > 0) {
|
||||
updateState(w, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state with container dimensions.
|
||||
* Called by the controller when the dialog size changes.
|
||||
*/
|
||||
fun updateState(width: Int, height: Int) {
|
||||
// Skip if dimensions haven't changed
|
||||
if (width == pendingWidth && height == pendingHeight && stateWrapper != null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Store dimensions
|
||||
pendingWidth = width
|
||||
pendingHeight = height
|
||||
|
||||
val sw = stateWrapper ?: return
|
||||
|
||||
val realWidth = width.toFloat().pxToDp()
|
||||
val realHeight = height.toFloat().pxToDp()
|
||||
|
||||
val newStateData = WritableNativeMap()
|
||||
newStateData.putDouble("containerWidth", realWidth.toDouble())
|
||||
newStateData.putDouble("containerHeight", realHeight.toDouble())
|
||||
sw.updateState(newStateData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference to content view (first child)
|
||||
*/
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package com.lodev09.truesheet
|
||||
|
||||
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
|
||||
|
||||
@ -17,11 +15,6 @@ class TrueSheetContainerViewManager : ViewGroupManager<TrueSheetContainerView>()
|
||||
|
||||
override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetContainerView = TrueSheetContainerView(reactContext)
|
||||
|
||||
override fun updateState(view: TrueSheetContainerView, props: ReactStylesDiffMap?, stateWrapper: StateWrapper?): Any? {
|
||||
view.setStateWrapper(stateWrapper)
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REACT_CLASS = "TrueSheetContainerView"
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@ import android.view.ViewStructure
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import androidx.annotation.UiThread
|
||||
import com.facebook.react.bridge.LifecycleEventListener
|
||||
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.UIManagerHelper
|
||||
import com.facebook.react.uimanager.events.EventDispatcher
|
||||
@ -48,6 +51,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
||||
var initialDetentIndex: Int = -1
|
||||
var initialDetentAnimated: Boolean = true
|
||||
|
||||
var stateWrapper: StateWrapper? = null
|
||||
|
||||
// Track last dimensions to avoid unnecessary state updates
|
||||
private var lastContainerWidth: Int = 0
|
||||
private var lastContainerHeight: Int = 0
|
||||
|
||||
/**
|
||||
* Tracks if initial presentation has been handled
|
||||
*/
|
||||
@ -85,10 +94,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
||||
TrueSheetModule.registerView(this, id)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
onDropInstance()
|
||||
@ -270,6 +275,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override fun viewControllerDidChangeSize(width: Int, height: Int) {
|
||||
updateState(width, height)
|
||||
}
|
||||
|
||||
// ==================== Property Setters (forward to controller) ====================
|
||||
|
||||
fun setMaxHeight(height: Int) {
|
||||
@ -325,6 +334,33 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
||||
viewController.edgeToEdgeFullScreen = edgeToEdgeFullScreen
|
||||
}
|
||||
|
||||
// ==================== State Management ====================
|
||||
|
||||
/**
|
||||
* Update state with container dimensions.
|
||||
* Called when the dialog size changes.
|
||||
*/
|
||||
fun updateState(width: Int, height: Int) {
|
||||
// Skip if dimensions haven't changed
|
||||
if (width == lastContainerWidth && height == lastContainerHeight) {
|
||||
return
|
||||
}
|
||||
|
||||
// Store new dimensions
|
||||
lastContainerWidth = width
|
||||
lastContainerHeight = height
|
||||
|
||||
val sw = stateWrapper ?: return
|
||||
|
||||
val realWidth = width.toFloat().pxToDp()
|
||||
val realHeight = height.toFloat().pxToDp()
|
||||
|
||||
val newStateData = WritableNativeMap()
|
||||
newStateData.putDouble("containerWidth", realWidth.toDouble())
|
||||
newStateData.putDouble("containerHeight", realHeight.toDouble())
|
||||
sw.updateState(newStateData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the sheet at the given detent index.
|
||||
*
|
||||
|
||||
@ -41,6 +41,7 @@ interface TrueSheetViewControllerDelegate {
|
||||
fun viewControllerDidDragChange(index: Int, position: Float)
|
||||
fun viewControllerDidDragEnd(index: Int, position: Float)
|
||||
fun viewControllerDidChangePosition(index: Int, position: Float, transitioning: Boolean)
|
||||
fun viewControllerDidChangeSize(width: Int, height: Int)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,9 +66,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
||||
private val jSTouchDispatcher = JSTouchDispatcher(this)
|
||||
private var jSPointerDispatcher: JSPointerDispatcher? = null
|
||||
|
||||
private var viewWidth = 0
|
||||
private var viewHeight = 0
|
||||
|
||||
/**
|
||||
* Delegate for handling view controller events
|
||||
*/
|
||||
@ -798,16 +796,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
viewWidth = w
|
||||
viewHeight = h
|
||||
|
||||
// Update container state with new dimensions
|
||||
containerView?.updateState(viewWidth, viewHeight)
|
||||
|
||||
// Only proceed if size actually changed
|
||||
if (w == oldw && h == oldh) return
|
||||
|
||||
// Update screen height based on new dimensions
|
||||
// Notify delegate about size change so host view can update state
|
||||
delegate?.viewControllerDidChangeSize(w, h)
|
||||
|
||||
val oldMaxScreenHeight = maxScreenHeight
|
||||
maxScreenHeight = ScreenUtils.getScreenHeight(reactContext, edgeToEdgeEnabled)
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@ import android.view.WindowManager
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.module.annotations.ReactModule
|
||||
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap
|
||||
import com.facebook.react.uimanager.StateWrapper
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.UIManagerHelper
|
||||
import com.facebook.react.uimanager.ViewGroupManager
|
||||
@ -43,6 +45,11 @@ class TrueSheetViewManager :
|
||||
view.eventDispatcher = dispatcher
|
||||
}
|
||||
|
||||
override fun updateState(view: TrueSheetView, props: ReactStylesDiffMap?, stateWrapper: StateWrapper?): Any? {
|
||||
view.stateWrapper = stateWrapper
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getDelegate(): ViewManagerDelegate<TrueSheetView> = delegate
|
||||
|
||||
/**
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <ReactCommon/JavaTurboModule.h>
|
||||
#include <ReactCommon/TurboModule.h>
|
||||
#include <jsi/jsi.h>
|
||||
#include <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewComponentDescriptor.h>
|
||||
#include <react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.h>
|
||||
#include <react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h>
|
||||
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/*
|
||||
* Descriptor for <TrueSheetContainerView> component.
|
||||
* Descriptor for <TrueSheetView> component.
|
||||
*/
|
||||
class TrueSheetContainerViewComponentDescriptor final
|
||||
: public ConcreteComponentDescriptor<TrueSheetContainerViewShadowNode> {
|
||||
class TrueSheetViewComponentDescriptor final
|
||||
: public ConcreteComponentDescriptor<TrueSheetViewShadowNode> {
|
||||
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
|
||||
|
||||
void adopt(ShadowNode &shadowNode) const override {
|
||||
auto &concreteShadowNode =
|
||||
static_cast<TrueSheetContainerViewShadowNode &>(shadowNode);
|
||||
static_cast<TrueSheetViewShadowNode &>(shadowNode);
|
||||
concreteShadowNode.adjustLayoutWithState();
|
||||
|
||||
ConcreteComponentDescriptor::adopt(shadowNode);
|
||||
@ -1,16 +1,16 @@
|
||||
#include "TrueSheetContainerViewShadowNode.h"
|
||||
#include "TrueSheetViewShadowNode.h"
|
||||
|
||||
#include <yoga/style/StyleSizeLength.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
extern const char TrueSheetContainerViewComponentName[] = "TrueSheetContainerView";
|
||||
extern const char TrueSheetViewComponentName[] = "TrueSheetView";
|
||||
|
||||
void TrueSheetContainerViewShadowNode::adjustLayoutWithState() {
|
||||
void TrueSheetViewShadowNode::adjustLayoutWithState() {
|
||||
ensureUnsealed();
|
||||
|
||||
auto state = std::static_pointer_cast<
|
||||
const TrueSheetContainerViewShadowNode::ConcreteState>(getState());
|
||||
const TrueSheetViewShadowNode::ConcreteState>(getState());
|
||||
auto stateData = state->getData();
|
||||
|
||||
// If container dimensions are set from native, override Yoga's dimensions
|
||||
@ -3,22 +3,22 @@
|
||||
#include <jsi/jsi.h>
|
||||
#include <react/renderer/components/TrueSheetSpec/EventEmitters.h>
|
||||
#include <react/renderer/components/TrueSheetSpec/Props.h>
|
||||
#include <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.h>
|
||||
#include <react/renderer/components/TrueSheetSpec/TrueSheetViewState.h>
|
||||
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
JSI_EXPORT extern const char TrueSheetContainerViewComponentName[];
|
||||
JSI_EXPORT extern const char TrueSheetViewComponentName[];
|
||||
|
||||
/*
|
||||
* `ShadowNode` for <TrueSheetContainerView> component.
|
||||
* `ShadowNode` for <TrueSheetView> component.
|
||||
*/
|
||||
class JSI_EXPORT TrueSheetContainerViewShadowNode final
|
||||
class JSI_EXPORT TrueSheetViewShadowNode final
|
||||
: public ConcreteViewShadowNode<
|
||||
TrueSheetContainerViewComponentName,
|
||||
TrueSheetContainerViewProps,
|
||||
TrueSheetContainerViewEventEmitter,
|
||||
TrueSheetContainerViewState> {
|
||||
TrueSheetViewComponentName,
|
||||
TrueSheetViewProps,
|
||||
TrueSheetViewEventEmitter,
|
||||
TrueSheetViewState> {
|
||||
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
||||
|
||||
public:
|
||||
@ -1,9 +1,9 @@
|
||||
#include "TrueSheetContainerViewState.h"
|
||||
#include "TrueSheetViewState.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
#ifdef ANDROID
|
||||
folly::dynamic TrueSheetContainerViewState::getDynamic() const {
|
||||
folly::dynamic TrueSheetViewState::getDynamic() const {
|
||||
return folly::dynamic::object("containerWidth", containerWidth)("containerHeight", containerHeight);
|
||||
}
|
||||
#endif
|
||||
@ -11,18 +11,18 @@
|
||||
namespace facebook::react {
|
||||
|
||||
/*
|
||||
* State for <TrueSheetContainerView> component.
|
||||
* State for <TrueSheetView> component.
|
||||
* Contains the container dimensions from native.
|
||||
*/
|
||||
class TrueSheetContainerViewState final {
|
||||
class TrueSheetViewState final {
|
||||
public:
|
||||
using Shared = std::shared_ptr<const TrueSheetContainerViewState>;
|
||||
using Shared = std::shared_ptr<const TrueSheetViewState>;
|
||||
|
||||
TrueSheetContainerViewState() = default;
|
||||
TrueSheetViewState() = default;
|
||||
|
||||
#ifdef ANDROID
|
||||
TrueSheetContainerViewState(
|
||||
TrueSheetContainerViewState const &previousState,
|
||||
TrueSheetViewState(
|
||||
TrueSheetViewState const &previousState,
|
||||
folly::dynamic data)
|
||||
: containerWidth(static_cast<float>(data["containerWidth"].getDouble())),
|
||||
containerHeight(static_cast<float>(data["containerHeight"].getDouble())) {}
|
||||
@ -2646,7 +2646,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNTrueSheet (3.0.0-beta.5):
|
||||
- RNTrueSheet (3.0.0-beta.6):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@ -3095,7 +3095,7 @@ SPEC CHECKSUMS:
|
||||
RNGestureHandler: e1cf8ef3f11045536eed6bd4f132b003ef5f9a5f
|
||||
RNReanimated: ac06da53579693ab451941ef89f5a55afeab0dd9
|
||||
RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479
|
||||
RNTrueSheet: 1666302304e0a1bc6075fac193dfb88bccae3099
|
||||
RNTrueSheet: 2ca9ea2c12271135b80eaa506794b9fa82ca0381
|
||||
RNWorklets: ab618bf7d1c7fd2cb793b9f0f39c3e29274b3ebf
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb
|
||||
|
||||
@ -9,18 +9,15 @@
|
||||
#ifdef RCT_NEW_ARCH_ENABLED
|
||||
|
||||
#import "TrueSheetContainerView.h"
|
||||
#import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/Props.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewComponentDescriptor.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.h>
|
||||
#import "TrueSheetContentView.h"
|
||||
#import "TrueSheetFooterView.h"
|
||||
|
||||
#import <React/RCTConversions.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import <react/renderer/core/State.h>
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
@ -30,8 +27,6 @@ using namespace facebook::react;
|
||||
@implementation TrueSheetContainerView {
|
||||
TrueSheetContentView *_contentView;
|
||||
TrueSheetFooterView *_footerView;
|
||||
TrueSheetContainerViewShadowNode::ConcreteState::Shared _state;
|
||||
CGFloat _lastContainerWidth;
|
||||
}
|
||||
|
||||
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
||||
@ -47,7 +42,6 @@ using namespace facebook::react;
|
||||
|
||||
_contentView = nil;
|
||||
_footerView = nil;
|
||||
_lastContainerWidth = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -61,37 +55,9 @@ using namespace facebook::react;
|
||||
if (!CGRectEqualToRect(self.frame, parentBounds)) {
|
||||
self.frame = parentBounds;
|
||||
}
|
||||
|
||||
// Update state with container width so Yoga can use it for children layout
|
||||
[self updateStateIfNeeded];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateStateIfNeeded {
|
||||
if (!self.superview) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat containerWidth = self.superview.bounds.size.width;
|
||||
if (containerWidth > 0 && fabs(containerWidth - _lastContainerWidth) > 0.5) {
|
||||
_lastContainerWidth = containerWidth;
|
||||
[self updateState];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateState {
|
||||
if (!_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
_state->updateState([=](TrueSheetContainerViewShadowNode::ConcreteState::Data const &oldData)
|
||||
-> TrueSheetContainerViewShadowNode::ConcreteState::SharedData {
|
||||
auto newData = oldData;
|
||||
newData.containerWidth = static_cast<float>(_lastContainerWidth);
|
||||
return std::make_shared<TrueSheetContainerViewShadowNode::ConcreteState::Data const>(newData);
|
||||
});
|
||||
}
|
||||
|
||||
- (CGFloat)contentHeight {
|
||||
return _contentView ? _contentView.frame.size.height : 0;
|
||||
}
|
||||
@ -153,19 +119,6 @@ using namespace facebook::react;
|
||||
[super updateProps:props oldProps:oldProps];
|
||||
}
|
||||
|
||||
- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState {
|
||||
_state = std::static_pointer_cast<TrueSheetContainerViewShadowNode::ConcreteState const>(state);
|
||||
|
||||
// Reset last width when state is updated to ensure we push the correct width
|
||||
// This handles re-presentation of the sheet where state is recreated
|
||||
_lastContainerWidth = 0;
|
||||
}
|
||||
|
||||
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask {
|
||||
[super finalizeUpdates:updateMask];
|
||||
[self updateStateIfNeeded];
|
||||
}
|
||||
|
||||
#pragma mark - TrueSheetContentViewDelegate
|
||||
|
||||
- (void)contentViewDidChangeSize:(CGSize)newSize {
|
||||
|
||||
@ -27,16 +27,19 @@
|
||||
#import "utils/LayoutUtil.h"
|
||||
#import "utils/WindowUtil.h"
|
||||
|
||||
#import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/Props.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h>
|
||||
#import <react/renderer/components/TrueSheetSpec/TrueSheetViewState.h>
|
||||
|
||||
#import <React/RCTConversions.h>
|
||||
#import <React/RCTFabricComponentsPlugins.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTSurfaceTouchHandler.h>
|
||||
#import <React/RCTUtils.h>
|
||||
#import <react/renderer/core/State.h>
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
@ -47,6 +50,8 @@ using namespace facebook::react;
|
||||
TrueSheetContainerView *_containerView;
|
||||
TrueSheetViewController *_controller;
|
||||
RCTSurfaceTouchHandler *_touchHandler;
|
||||
TrueSheetViewShadowNode::ConcreteState::Shared _state;
|
||||
CGFloat _lastContainerWidth;
|
||||
NSInteger _initialDetentIndex;
|
||||
BOOL _fitScrollView;
|
||||
BOOL _initialDetentAnimated;
|
||||
@ -66,6 +71,7 @@ using namespace facebook::react;
|
||||
|
||||
_containerView = nil;
|
||||
|
||||
_lastContainerWidth = 0;
|
||||
_initialDetentIndex = -1;
|
||||
_initialDetentAnimated = YES;
|
||||
_fitScrollView = NO;
|
||||
@ -238,6 +244,35 @@ using namespace facebook::react;
|
||||
_fitScrollView = newProps.fitScrollView;
|
||||
}
|
||||
|
||||
- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState {
|
||||
_state = std::static_pointer_cast<TrueSheetViewShadowNode::ConcreteState const>(state);
|
||||
[self updateStateIfNeeded];
|
||||
}
|
||||
|
||||
- (void)updateStateIfNeeded {
|
||||
if (!_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat containerWidth = _controller.view.bounds.size.width;
|
||||
|
||||
if (containerWidth <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL widthChanged = fabs(containerWidth - _lastContainerWidth) > 0.5;
|
||||
|
||||
if (widthChanged) {
|
||||
_lastContainerWidth = containerWidth;
|
||||
_state->updateState([=](TrueSheetViewShadowNode::ConcreteState::Data const &oldData)
|
||||
-> TrueSheetViewShadowNode::ConcreteState::SharedData {
|
||||
auto newData = oldData;
|
||||
newData.containerWidth = static_cast<float>(containerWidth);
|
||||
return std::make_shared<TrueSheetViewShadowNode::ConcreteState::Data const>(newData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask {
|
||||
[super finalizeUpdates:updateMask];
|
||||
|
||||
@ -292,10 +327,6 @@ using namespace facebook::react;
|
||||
// Ensure container is above background view
|
||||
[_controller.view bringSubviewToFront:_containerView];
|
||||
|
||||
// Force layout pass immediately so container gets correct width on mount
|
||||
// This pushes the width to Yoga before the sheet is presented
|
||||
[_controller.view layoutIfNeeded];
|
||||
|
||||
// Get initial content height from container
|
||||
CGFloat contentHeight = [_containerView contentHeight];
|
||||
if (contentHeight > 0) {
|
||||
@ -421,6 +452,10 @@ using namespace facebook::react;
|
||||
[OnPositionChangeEvent emit:_eventEmitter index:index position:position transitioning:transitioning];
|
||||
}
|
||||
|
||||
- (void)viewControllerDidChangeSize:(CGSize)size {
|
||||
[self updateStateIfNeeded];
|
||||
}
|
||||
|
||||
#pragma mark - Private Helpers
|
||||
|
||||
- (UIViewController *)findPresentingViewController {
|
||||
|
||||
@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)viewControllerDidChangeDetent:(NSInteger)index position:(CGFloat)position;
|
||||
- (void)viewControllerDidDrag:(UIGestureRecognizerState)state index:(NSInteger)index position:(CGFloat)position;
|
||||
- (void)viewControllerDidChangePosition:(NSInteger)index position:(CGFloat)position transitioning:(BOOL)transitioning;
|
||||
- (void)viewControllerDidChangeSize:(CGSize)size;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@ -164,6 +164,11 @@
|
||||
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
||||
// After rotation completes
|
||||
[self setupSheetDetents];
|
||||
|
||||
// Notify delegate of size change for state update
|
||||
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeSize:)]) {
|
||||
[self.delegate viewControllerDidChangeSize:size];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
"types": "./lib/typescript/src/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"source": "./src/index.tsx",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./lib/typescript/src/index.d.ts",
|
||||
"default": "./lib/module/index.js"
|
||||
},
|
||||
@ -38,7 +38,7 @@
|
||||
"typecheck": "tsc",
|
||||
"lint": "eslint --fix \"**/*.{ts,tsx}\"",
|
||||
"format": "prettier --write \"**/*.{ts,tsx}\"",
|
||||
"tidy": "yarn typecheck && yarn lint && yarn format && scripts/objc-lint.sh && scripts/ktlint.sh",
|
||||
"tidy": "yarn typecheck && yarn lint && yarn format && scripts/objclint.sh && scripts/ktlint.sh",
|
||||
"clean": "scripts/clean.sh",
|
||||
"prepare": "bob build",
|
||||
"release": "yarn tidy && release-it --only-version",
|
||||
|
||||
@ -3,7 +3,7 @@ module.exports = {
|
||||
platforms: {
|
||||
ios: {},
|
||||
android: {
|
||||
componentDescriptors: ['TrueSheetContainerViewComponentDescriptor'],
|
||||
componentDescriptors: ['TrueSheetViewComponentDescriptor'],
|
||||
cmakeListsPath: '../android/src/main/jni/CMakeLists.txt',
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,16 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "[Installing dependencies]"
|
||||
yarn
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo "[Cleaning android]"
|
||||
step() {
|
||||
echo -e "\n${BLUE}▶${NC} ${BOLD}$1${NC}"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
}
|
||||
|
||||
step "Installing dependencies"
|
||||
yarn && success "Dependencies installed"
|
||||
|
||||
step "Cleaning Android"
|
||||
cd example/android
|
||||
./gradlew clean
|
||||
./gradlew clean && success "Android cleaned"
|
||||
cd ../..
|
||||
|
||||
echo "[Removing temp directories]"
|
||||
del-cli android/build example/android/build example/android/app/build example/ios/build
|
||||
step "Cleaning up Watchman"
|
||||
watchman watch-del ./ ; watchman watch-project ./
|
||||
rm -rf $TMPDIR/metro-*
|
||||
success "Watchman cache cleared"
|
||||
|
||||
echo "[Installing pods]"
|
||||
npx pod-install example
|
||||
bob build
|
||||
step "Cleaning up Simulator cache"
|
||||
# fixes "Unable to boot Simulator" error
|
||||
rm -rf ~/Library/Developer/CoreSimulator/Caches
|
||||
success "Simulator cache cleared"
|
||||
|
||||
step "Removing temp directories"
|
||||
del-cli android/build example/android/build example/android/app/build example/ios/build
|
||||
success "Temp directories removed"
|
||||
|
||||
step "Installing pods"
|
||||
npx pod-install example && success "Pods installed"
|
||||
|
||||
step "Building with bob"
|
||||
bob build && success "Build complete"
|
||||
|
||||
echo -e "\n${GREEN}${BOLD}All done!${NC}"
|
||||
|
||||
@ -391,7 +391,6 @@ export class TrueSheet extends PureComponent<TrueSheetProps, TrueSheetState> {
|
||||
const styles = StyleSheet.create({
|
||||
sheetView: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
zIndex: -9999,
|
||||
},
|
||||
footer: {
|
||||
|
||||
@ -5,6 +5,4 @@ export interface NativeProps extends ViewProps {
|
||||
// No props needed - container accesses props from parent TrueSheetView
|
||||
}
|
||||
|
||||
export default codegenNativeComponent<NativeProps>('TrueSheetContainerView', {
|
||||
interfaceOnly: true,
|
||||
});
|
||||
export default codegenNativeComponent<NativeProps>('TrueSheetContainerView');
|
||||
|
||||
@ -56,4 +56,6 @@ export interface NativeProps extends ViewProps {
|
||||
onPositionChange?: DirectEventHandler<PositionChangeEventPayload>;
|
||||
}
|
||||
|
||||
export default codegenNativeComponent<NativeProps>('TrueSheetView');
|
||||
export default codegenNativeComponent<NativeProps>('TrueSheetView', {
|
||||
interfaceOnly: true,
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user