Skip to content

Commit

Permalink
Merge pull request #760 from grahammendick/sheet-nonmodal
Browse files Browse the repository at this point in the history
  • Loading branch information
grahammendick authored Dec 30, 2023
2 parents c431503 + 61ad9d3 commit 4b8e0a3
Show file tree
Hide file tree
Showing 18 changed files with 203 additions and 114 deletions.
127 changes: 59 additions & 68 deletions NavigationReactNative/src/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,70 @@
import React from 'react';
import { requireNativeComponent, Platform, UIManager, View, StyleSheet } from 'react-native';
import React, {useRef, useState} from 'react';
import { requireNativeComponent, Platform, UIManager, StyleSheet } from 'react-native';
import useNavigated from './useNavigated';

class BottomSheet extends React.Component<any, any> {
private dragging = false;
constructor(props) {
super(props);
this.state = {selectedDetent: props.detent || props.defaultDetent, mostRecentEventCount: 0, dismissed: (props.detent || props.defaultDetent) === 'hidden'};
this.onDetentChanged = this.onDetentChanged.bind(this);
}
static defaultProps = {
draggable: true,
defaultDetent: 'collapsed'
}
static getDerivedStateFromProps({detent}, {selectedDetent, dismissed}) {
if (detent != null && detent !== selectedDetent)
return {selectedDetent: detent, dismissed: detent === 'hidden' && dismissed};
return null;
}
onDetentChanged({nativeEvent}) {
var {eventCount: mostRecentEventCount, detent: nativeDetent} = nativeEvent;
var detents = (UIManager as any).getViewManagerConfig('NVBottomSheet').Constants?.Detent;
var detent = Platform.OS === 'android'? Object.keys(detents).find(name => detents[name] === nativeDetent) : nativeDetent;
this.dragging = !detent
if (detent) {
this.changeDetent(detent);
this.setState({mostRecentEventCount});
}
}
changeDetent(selectedDetent) {
var {detent, onChangeDetent} = this.props;
if (this.state.selectedDetent !== selectedDetent) {
const BottomSheet = ({detent, defaultDetent = 'collapsed', expandedHeight, expandedOffset, peekHeight, halfExpandedRatio, hideable, skipCollapsed, draggable = true, modal = Platform.OS === 'ios', onChangeDetent, children}) => {
const [sheetState, setSheetState] = useState({selectedDetent: detent || defaultDetent, mostRecentEventCount: 0, dismissed: (detent || defaultDetent) === 'hidden'})
const dragging = useRef(false);
const changeDetent = (selectedDetent) => {
if (sheetState.selectedDetent !== selectedDetent) {
if (detent == null)
this.setState({selectedDetent});
setSheetState(prevSheetState => ({...prevSheetState, selectedDetent}));
if (!!onChangeDetent)
onChangeDetent(selectedDetent);
}
}
render() {
if (Platform.OS === 'ios' && +Platform.Version < 15) return null;
const { expandedHeight, expandedOffset, peekHeight, halfExpandedRatio, hideable, skipCollapsed, draggable, modal, children } = this.props
const detents = (UIManager as any).getViewManagerConfig('NVBottomSheet').Constants?.Detent;
const BottomSheetView = Platform.OS === 'ios' || !modal ? NVBottomSheet : NVBottomSheetDialog;
if (this.state.dismissed) return null;
return (
<BottomSheetView
detent={Platform.OS === 'android' ? '' + detents[this.state.selectedDetent] : this.state.selectedDetent}
peekHeight={peekHeight}
expandedHeight={expandedHeight}
expandedOffset={expandedOffset}
fitToContents={expandedOffset == null && (!halfExpandedRatio || !!expandedHeight)}
halfExpandedRatio={halfExpandedRatio}
hideable={hideable}
skipCollapsed={skipCollapsed}
draggable={draggable}
sheetHeight={expandedHeight != null ? expandedHeight : 0}
mostRecentEventCount={this.state.mostRecentEventCount}
onMoveShouldSetResponderCapture={() => this.dragging}
onDetentChanged={this.onDetentChanged}
onDismissed={() => this.setState({dismissed: true})}
style={[
styles.bottomSheet,
expandedHeight != null ? { height: expandedHeight } : null,
expandedOffset != null ? { top: expandedOffset } : null,
expandedHeight == null && expandedOffset == null ? { top: 0 } : null,
Platform.OS === 'ios' || modal ? { height: undefined, top: undefined } : null,
]}
>
{children}
</BottomSheetView>
)
useNavigated(() => {
if (Platform.OS === 'ios' && sheetState.selectedDetent !== 'hidden' && sheetState.dismissed)
setSheetState(prevSheetState => ({...prevSheetState, dismissed: false}));
})
if (Platform.OS === 'ios' && +Platform.Version < 15) return null;
if (detent != null && detent !== sheetState.selectedDetent)
setSheetState(prevSheetState => ({...prevSheetState, selectedDetent: detent, dismissed: detent === 'hidden' && sheetState.dismissed}));
const detents = (UIManager as any).getViewManagerConfig('NVBottomSheet').Constants?.Detent;
const onDetentChanged = ({nativeEvent}) => {
const {eventCount: mostRecentEventCount, detent: nativeDetent} = nativeEvent;
const selectedDetent = Platform.OS === 'android'? Object.keys(detents).find(name => detents[name] === nativeDetent) : nativeDetent;
dragging.current = !selectedDetent;
if (selectedDetent) {
changeDetent(selectedDetent);
setSheetState(prevSheetState => ({...prevSheetState, mostRecentEventCount}));
}
}
}
const BottomSheetView = Platform.OS === 'ios' || !modal ? NVBottomSheet : NVBottomSheetDialog;
if ((Platform.OS === 'ios' || modal) && sheetState.dismissed && sheetState.selectedDetent === 'hidden') return null;
return (
<BottomSheetView
detent={Platform.OS === 'android' ? '' + detents[sheetState.selectedDetent] : sheetState.selectedDetent}
modal={modal}
dismissed={sheetState.dismissed}
peekHeight={peekHeight}
expandedHeight={expandedHeight}
expandedOffset={expandedOffset}
fitToContents={expandedOffset == null && (!halfExpandedRatio || !!expandedHeight)}
halfExpandedRatio={halfExpandedRatio}
hideable={hideable}
skipCollapsed={skipCollapsed}
draggable={draggable}
sheetHeight={expandedHeight != null ? expandedHeight : 0}
mostRecentEventCount={sheetState.mostRecentEventCount}
onMoveShouldSetResponderCapture={() => dragging.current}
onDetentChanged={onDetentChanged}
onDismissed={() => setSheetState(prevSheetState => ({...prevSheetState, dismissed: true}))}
style={[
styles.bottomSheet,
expandedHeight != null ? { height: expandedHeight } : null,
expandedOffset != null ? { top: expandedOffset } : null,
expandedHeight == null && expandedOffset == null ? { top: 0 } : null,
Platform.OS === 'ios' || modal ? { height: undefined, top: undefined } : null,
]}
>
{children}
</BottomSheetView>
)
}

var NVBottomSheet = global.nativeFabricUIManager ? require('./BottomSheetNativeComponent').default : requireNativeComponent('NVBottomSheet');
var NVBottomSheetDialog = global.nativeFabricUIManager ? require('./BottomSheetDialogNativeComponent').default : requireNativeComponent('NVBottomSheetDialog');
const NVBottomSheet = global.nativeFabricUIManager ? require('./BottomSheetNativeComponent').default : requireNativeComponent('NVBottomSheet');
const NVBottomSheetDialog = global.nativeFabricUIManager ? require('./BottomSheetDialogNativeComponent').default : requireNativeComponent('NVBottomSheetDialog');

const styles = StyleSheet.create({
bottomSheet: {
Expand Down
2 changes: 2 additions & 0 deletions NavigationReactNative/src/BottomSheetNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNati
type NativeProps = $ReadOnly<{|
...ViewProps,
detent: string,
modal: boolean,
dismissed: boolean,
mostRecentEventCount: Int32,
peekHeight: Int32,
expandedHeight: Int32,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public BottomSheetDialogView(Context context) {
}

void onAfterUpdateTransaction() {
nativeEventCount = Math.max(nativeEventCount, mostRecentEventCount);
int eventLag = nativeEventCount - mostRecentEventCount;
if (eventLag == 0) {
detent = pendingDetent;
Expand Down Expand Up @@ -102,7 +103,7 @@ public static class BottomSheetFragment extends BottomSheetDialogFragment {
private BottomSheetDialogView dialogView;
BottomSheetBehavior.BottomSheetCallback bottomSheetCallback;

@SuppressLint("Range")
@SuppressLint({"Range", "WrongConstant"})
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Expand All @@ -115,6 +116,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
behavior.setHideable(dialogView.bottomSheetBehavior.isHideable());
behavior.setSkipCollapsed(dialogView.bottomSheetBehavior.getSkipCollapsed());
behavior.setDraggable(dialogView.bottomSheetBehavior.isDraggable());
behavior.setState(dialogView.bottomSheetBehavior.getState());
bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View view, int i) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public void onSlide(@NonNull View view, float v) {
}

void onAfterUpdateTransaction() {
nativeEventCount = Math.max(nativeEventCount, mostRecentEventCount);
int eventLag = nativeEventCount - mostRecentEventCount;
if (eventLag == 0) {
detent = pendingDetent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ public void setDetent(BottomSheetView view, String detent) {
view.pendingDetent = Integer.parseInt(detent);
}

@Override
public void setModal(BottomSheetView view, boolean value) {
}

@Override
public void setDismissed(BottomSheetView view, boolean value) {
}

@ReactProp(name = "mostRecentEventCount")
public void setMostRecentEventCount(BottomSheetView view, int mostRecentEventCount) {
view.mostRecentEventCount = mostRecentEventCount;
Expand Down
36 changes: 30 additions & 6 deletions NavigationReactNative/src/ios/NVBottomSheetComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ @implementation NVBottomSheetComponentView
NVBottomSheetController *_oldBottomSheetController;
CGSize _oldSize;
BOOL _presented;
BOOL _dismissed;
NSInteger _nativeEventCount;
NSString *_detent;
BOOL _hideable;
Expand Down Expand Up @@ -55,14 +56,27 @@ - (void)ensureBottomSheetController
[_oldBottomSheetController.view removeFromSuperview];
[_oldBottomSheetController removeFromParentViewController];
_bottomSheetController = [[NVBottomSheetController alloc] init];
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(resizeView)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
id __weak weakSelf = self;
_bottomSheetController.boundsDidChangeBlock = ^(CGRect newBounds) {
[weakSelf notifyForBoundsChange:newBounds];
};
_bottomSheetController.didDismiss = ^{
[weakSelf sheetViewDidDismiss];
};
}
}

-(void)sheetViewDidDismiss
{
_presented = NO;
_dismissed = YES;
[_displayLink invalidate];
if (_eventEmitter != nullptr) {
std::static_pointer_cast<NVBottomSheetEventEmitter const>(_eventEmitter)
->onDismissed(NVBottomSheetEventEmitter::OnDismissed{});
}
}

- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
[self ensureBottomSheetController];
Expand Down Expand Up @@ -92,17 +106,21 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
}];
}
}
_dismissed = newViewProps.dismissed;
_hideable = newViewProps.hideable;
NSInteger eventLag = _nativeEventCount - newViewProps.mostRecentEventCount;
_detent = [[NSString alloc] initWithUTF8String: newViewProps.detent.c_str()];
UISheetPresentationControllerDetentIdentifier newDetent = [_detent isEqual: @"collapsed"] ? [self collapsedIdentifier] : ([_detent isEqual: @"expanded"] ? [self expandedIdentifier] : [self halfExpandedIdentifier]);
if (![_detent isEqual: @"hidden"]) {
if (self.window && !_presented) {
if (self.window && !_presented && !_dismissed) {
_presented = YES;
_bottomSheetController.sheetPresentationController.delegate = self;
_bottomSheetController.presentationController.delegate = self;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(resizeView)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[self reactViewController] presentViewController:_bottomSheetController animated:true completion:nil];
}
sheet.largestUndimmedDetentIdentifier = !newViewProps.modal ? [self expandedIdentifier] : nil;
dispatch_async(dispatch_get_main_queue(), ^{
[sheet animateChanges:^{
[sheet setDetents: [[self halfExpandedIdentifier] isEqual:UISheetPresentationControllerDetentIdentifierLarge] ? @[self->_collapsedDetent, self->_expandedDetent] : @[self->_collapsedDetent, self->_halfExpandedDetent, self->_expandedDetent]];
Expand All @@ -120,6 +138,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
} else {
[_bottomSheetController dismissViewControllerAnimated:YES completion:nil];
_presented = NO;
[_displayLink invalidate];
}
[super updateProps:props oldProps:oldProps];
}
Expand Down Expand Up @@ -164,6 +183,11 @@ - (void)prepareForRecycle
[_displayLink invalidate];
}

- (void)dealloc
{
[_bottomSheetController dismissViewControllerAnimated:NO completion:nil];
}

- (void)notifyForBoundsChange:(CGRect)newBounds
{
if (_state != nullptr) {
Expand All @@ -188,10 +212,12 @@ - (void)resizeView
- (void)didMoveToWindow
{
[super didMoveToWindow];
if (![_detent isEqual: @"hidden"] && !_presented) {
if (![_detent isEqual: @"hidden"] && !_presented && !_dismissed && self.window) {
_presented = YES;
_bottomSheetController.sheetPresentationController.delegate = self;
_bottomSheetController.presentationController.delegate = self;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(resizeView)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[self reactViewController] presentViewController:_bottomSheetController animated:true completion:nil];
}
}
Expand Down Expand Up @@ -223,8 +249,6 @@ - (void)presentationControllerDidDismiss:(UIPresentationController *)presentatio
.detent = std::string([@"hidden" UTF8String]),
.eventCount = static_cast<int>(_nativeEventCount),
});
std::static_pointer_cast<NVBottomSheetEventEmitter const>(_eventEmitter)
->onDismissed(NVBottomSheetEventEmitter::OnDismissed{});
}
}

Expand Down
1 change: 1 addition & 0 deletions NavigationReactNative/src/ios/NVBottomSheetController.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
@interface NVBottomSheetController : UIViewController

@property (nonatomic, copy) void (^boundsDidChangeBlock)(CGRect newBounds);
@property (nonatomic, copy) void (^didDismiss)(void);

@end
9 changes: 9 additions & 0 deletions NavigationReactNative/src/ios/NVBottomSheetController.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,13 @@ - (void)viewDidLayoutSubviews
}
}

- (void)viewDidDisappear:(BOOL)animated
{
if (self.didDismiss) {
dispatch_async(dispatch_get_main_queue(), ^{
self.didDismiss();
});
}
}

@end
9 changes: 3 additions & 6 deletions NavigationReactNative/src/ios/NVBottomSheetManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#import <React/RCTViewManager.h>
#import <React/RCTInvalidating.h>

@interface NVBottomSheetManager : RCTViewManager <RCTInvalidating>
@interface NVBottomSheetManager : RCTViewManager

@end

Expand All @@ -20,12 +20,9 @@ - (UIView *)view {
}
}

-(void)invalidate
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"dismissBottomSheet" object:nil];
}

RCT_EXPORT_VIEW_PROPERTY(detent, NSString)
RCT_EXPORT_VIEW_PROPERTY(modal, BOOL)
RCT_EXPORT_VIEW_PROPERTY(dismissed, BOOL)
RCT_EXPORT_VIEW_PROPERTY(peekHeight, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(expandedHeight, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(expandedOffset, NSInteger)
Expand Down
4 changes: 3 additions & 1 deletion NavigationReactNative/src/ios/NVBottomSheetView.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
#import <React/RCTInvalidating.h>

API_AVAILABLE(ios(15.0))
@interface NVBottomSheetView : UIView <UISheetPresentationControllerDelegate, UIAdaptivePresentationControllerDelegate, RCTInvalidating>
@interface NVBottomSheetView : UIView <UISheetPresentationControllerDelegate, UIAdaptivePresentationControllerDelegate>

@property (nonatomic, copy) NSString *detent;
@property (nonatomic, assign) BOOL modal;
@property (nonatomic, assign) BOOL dismissed;
@property (nonatomic, assign) NSInteger peekHeight;
@property (nonatomic, assign) NSInteger expandedHeight;
@property (nonatomic, assign) NSInteger expandedOffset;
Expand Down
Loading

0 comments on commit 4b8e0a3

Please sign in to comment.