Skip to content

Commit

Permalink
chore: add preventNativeDismiss prop for Fabric (7) (#1444)
Browse files Browse the repository at this point in the history
* chore: move preventNativeDismiss prop to common section in RNSScreen
interface

* fix: merge artifact

* chore: add preventNativeDismiss prop to codegen

* chore: add preventNativeDismiss prop update

* fix: add noop for Android

* chore: unify business logic between archs (8) (#1446)

* chore: unify RNSScreen#vieDidLayoutSubviews

* chore: make RNSScreenStack#hitTest:withEvent: method shared

#1416 (comment)

* chore: make RNSScreenStackView#isScrollViewPanGestureRecongnizer & some
helper methods shared

#1416 (comment)

* chore: move backButtonInCustomView to shared sectionin
RNSScreenStackHeaderConfig.h

* chore: move backButtonInCustomView prop to implemented section in JS

* chore: update `backButtonInCustomView` for Fabric

* chore: unify
RNSScreenStackHeaderConfigView#willShowViewController:animated:withConfig

* refact: linter suggestion

* fix: RNSScreen viewDidLayoutSubviews

* chore: copy interfaces to android/src/paper directory

* fix: commit address in comment

It changed after I rebased
  • Loading branch information
kkafar authored May 13, 2022
1 parent 4e424e0 commit 69ab053
Show file tree
Hide file tree
Showing 12 changed files with 97 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ class ScreenViewManager : ViewGroupManager<Screen>(), RNSScreenManagerInterface<

override fun setHomeIndicatorHidden(view: Screen?, value: Boolean) = Unit

override fun setPreventNativeDismiss(view: Screen?, value: Boolean) = Unit

override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
val map: MutableMap<String, Any> = MapBuilder.of(
ScreenDismissedEvent.EVENT_NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.BaseViewManagerInterface;

Expand All @@ -22,9 +23,18 @@ public RNSScreenManagerDelegate(U viewManager) {
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "customAnimationOnSwipe":
mViewManager.setCustomAnimationOnSwipe(view, value == null ? false : (boolean) value);
break;
case "fullScreenSwipeEnabled":
mViewManager.setFullScreenSwipeEnabled(view, value == null ? false : (boolean) value);
break;
case "homeIndicatorHidden":
mViewManager.setHomeIndicatorHidden(view, value == null ? false : (boolean) value);
break;
case "preventNativeDismiss":
mViewManager.setPreventNativeDismiss(view, value == null ? false : (boolean) value);
break;
case "gestureEnabled":
mViewManager.setGestureEnabled(view, value == null ? true : (boolean) value);
break;
Expand All @@ -46,6 +56,9 @@ public void setProperty(T view, String propName, @Nullable Object value) {
case "statusBarTranslucent":
mViewManager.setStatusBarTranslucent(view, value == null ? false : (boolean) value);
break;
case "gestureResponseDistance":
mViewManager.setGestureResponseDistance(view, (ReadableMap) value);
break;
case "stackPresentation":
mViewManager.setStackPresentation(view, (String) value);
break;
Expand All @@ -58,6 +71,12 @@ public void setProperty(T view, String propName, @Nullable Object value) {
case "replaceAnimation":
mViewManager.setReplaceAnimation(view, (String) value);
break;
case "hideKeyboardOnSwipe":
mViewManager.setHideKeyboardOnSwipe(view, value == null ? false : (boolean) value);
break;
case "activityState":
mViewManager.setActivityState(view, value == null ? -1 : ((Double) value).intValue());
break;
case "navigationBarColor":
mViewManager.setNavigationBarColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
Expand All @@ -67,9 +86,6 @@ public void setProperty(T view, String propName, @Nullable Object value) {
case "nativeBackButtonDismissalEnabled":
mViewManager.setNativeBackButtonDismissalEnabled(view, value == null ? false : (boolean) value);
break;
case "activityState":
mViewManager.setActivityState(view, value == null ? -1 : ((Double) value).intValue());
break;
default:
super.setProperty(view, propName, value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,28 @@

import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;

public interface RNSScreenManagerInterface<T extends View> {
void setCustomAnimationOnSwipe(T view, boolean value);
void setFullScreenSwipeEnabled(T view, boolean value);
void setHomeIndicatorHidden(T view, boolean value);
void setPreventNativeDismiss(T view, boolean value);
void setGestureEnabled(T view, boolean value);
void setStatusBarColor(T view, @Nullable Integer value);
void setStatusBarHidden(T view, boolean value);
void setScreenOrientation(T view, @Nullable String value);
void setStatusBarAnimation(T view, @Nullable String value);
void setStatusBarStyle(T view, @Nullable String value);
void setStatusBarTranslucent(T view, boolean value);
void setGestureResponseDistance(T view, @Nullable ReadableMap value);
void setStackPresentation(T view, @Nullable String value);
void setStackAnimation(T view, @Nullable String value);
void setTransitionDuration(T view, int value);
void setReplaceAnimation(T view, @Nullable String value);
void setHideKeyboardOnSwipe(T view, boolean value);
void setActivityState(T view, int value);
void setNavigationBarColor(T view, @Nullable Integer value);
void setNavigationBarHidden(T view, boolean value);
void setNativeBackButtonDismissalEnabled(T view, boolean value);
void setActivityState(T view, int value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ public void setProperty(T view, String propName, @Nullable Object value) {
case "hideBackButton":
mViewManager.setHideBackButton(view, value == null ? false : (boolean) value);
break;
case "topInsetEnabled":
mViewManager.setTopInsetEnabled(view, value == null ? false : (boolean) value);
break;
case "backButtonInCustomView":
mViewManager.setBackButtonInCustomView(view, value == null ? false : (boolean) value);
break;
case "topInsetEnabled":
mViewManager.setTopInsetEnabled(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ public interface RNSScreenStackHeaderConfigManagerInterface<T extends View> {
void setTitleColor(T view, @Nullable Integer value);
void setDisableBackButtonMenu(T view, boolean value);
void setHideBackButton(T view, boolean value);
void setTopInsetEnabled(T view, boolean value);
void setBackButtonInCustomView(T view, boolean value);
void setTopInsetEnabled(T view, boolean value);
}
3 changes: 1 addition & 2 deletions ios/RNSScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) BOOL dismissed;
@property (nonatomic) BOOL hideKeyboardOnSwipe;
@property (nonatomic) BOOL customAnimationOnSwipe;
@property (nonatomic) BOOL preventNativeDismiss;
@property (nonatomic, retain) RNSScreen *controller;
@property (nonatomic, copy) NSDictionary *gestureResponseDistance;
@property (nonatomic) int activityState;
Expand All @@ -82,8 +83,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, copy) RCTDirectEventBlock onWillDisappear;
@property (nonatomic, copy) RCTDirectEventBlock onNativeDismissCancelled;
@property (nonatomic, copy) RCTDirectEventBlock onTransitionProgress;

@property (nonatomic) BOOL preventNativeDismiss;
#endif

- (void)notifyFinishTransitioning;
Expand Down
28 changes: 15 additions & 13 deletions ios/RNSScreen.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
#import "RNSConvert.h"
#else
#import <React/RCTTouchHandler.h>
#import "RNSScreenStack.h"
#endif

#import <React/RCTShadowView.h>
#import <React/RCTUIManager.h>
#import "RNSScreenStack.h"
#import "RNSScreenStackHeaderConfig.h"

@interface RNSScreenView ()
Expand Down Expand Up @@ -467,6 +467,8 @@ - (void)updateProps:(facebook::react::Props::Shared const &)props
setGestureResponseDistance:[RNSConvert
gestureResponseDistanceDictFromCppStruct:newScreenProps.gestureResponseDistance]];

[self setPreventNativeDismiss:newScreenProps.preventNativeDismiss];

[self setActivityStateOrNil:[NSNumber numberWithInt:newScreenProps.activityState]];

#if !TARGET_OS_TV
Expand Down Expand Up @@ -609,10 +611,10 @@ - (void)invalidate

@implementation RNSScreen {
__weak id _previousFirstResponder;
CGRect _lastViewFrame;
#ifdef RN_FABRIC_ENABLED
RNSScreenView *_initialView;
#else
CGRect _lastViewFrame;
UIView *_fakeView;
CADisplayLink *_animationTimer;
CGFloat _currentAlpha;
Expand Down Expand Up @@ -770,12 +772,7 @@ - (void)viewDidDisappear:(BOOL)animated
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
#ifdef RN_FABRIC_ENABLED
BOOL isDisplayedWithinUINavController = [self.parentViewController isKindOfClass:[UINavigationController class]];
if (isDisplayedWithinUINavController) {
[_initialView updateBounds];
}
#else

// The below code makes the screen view adapt dimensions provided by the system. We take these
// into account only when the view is mounted under RNScreensNavigationController in which case system
// provides additional padding to account for possible header, and in the case when screen is
Expand All @@ -784,12 +781,17 @@ - (void)viewDidLayoutSubviews
BOOL isDisplayedWithinUINavController =
[self.parentViewController isKindOfClass:[RNScreensNavigationController class]];
BOOL isPresentedAsNativeModal = self.parentViewController == nil && self.presentingViewController != nil;
if ((isDisplayedWithinUINavController || isPresentedAsNativeModal) &&
!CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
_lastViewFrame = self.view.frame;
[((RNSScreenView *)self.viewIfLoaded) updateBounds];
}

if (isDisplayedWithinUINavController || isPresentedAsNativeModal) {
#ifdef RN_FABRIC_ENABLED
[_initialView updateBounds];
#else
if (!CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
_lastViewFrame = self.view.frame;
[((RNSScreenView *)self.viewIfLoaded) updateBounds];
}
#endif
}
}

- (void)notifyFinishTransitioning
Expand Down
84 changes: 42 additions & 42 deletions ios/RNSScreenStack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ - (BOOL)isInGestureResponseDistance:(UIGestureRecognizer *)gestureRecognizer top
x = _controller.view.frame.size.width - x;
}

// see: https://github.com/software-mansion/react-native-screens/pull/1442/commits/c0413bddadc1023022d3d390373321632ad3539d
// see: https://github.com/software-mansion/react-native-screens/pull/1442/commits/74d4bae321875d8305ad021b3d448ebf713e7d56
// this prop is always default initialized so we do not expect any nils
float start = [gestureResponseDistanceValues[@"start"] floatValue];
float end = [gestureResponseDistanceValues[@"end"] floatValue];
Expand All @@ -767,6 +767,47 @@ - (BOOL)isInGestureResponseDistance:(UIGestureRecognizer *)gestureRecognizer top
(bottom != -1 && y > bottom));
}

// By default, the header buttons that are not inside the native hit area
// cannot be clicked, so we check it by ourselves
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(_controller.navigationBar.frame, point)) {
// headerConfig should be the first subview of the topmost screen
UIView *headerConfig = [[_reactSubviews.lastObject reactSubviews] firstObject];
if ([headerConfig isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
UIView *headerHitTestResult = [headerConfig hitTest:point withEvent:event];
if (headerHitTestResult != nil) {
return headerHitTestResult;
}
}
}
return [super hitTest:point withEvent:event];
}

- (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
// NOTE: This hack is required to restore native behavior of edge swipe (interactive pop gesture)
// without this, on a screen with a scroll view, it's only possible to pop view by panning horizontally
// if even slightly diagonal (or if in motion), scroll view will scroll, and edge swipe will be cancelled
if (![[gestureRecognizer view] isKindOfClass:[UIScrollView class]]) {
return NO;
}
UIScrollView *scrollView = (UIScrollView *)gestureRecognizer.view;
return scrollView.panGestureRecognizer == gestureRecognizer;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return [self isScrollViewPanGestureRecognizer:otherGestureRecognizer];
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return [self isScrollViewPanGestureRecognizer:otherGestureRecognizer];
}

#ifdef RN_FABRIC_ENABLED
#pragma mark - Fabric specific

Expand Down Expand Up @@ -892,23 +933,6 @@ - (void)didUpdateReactSubviews
});
}

// By default, the header buttons that are not inside the native hit area
// cannot be clicked, so we check it by ourselves
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(_controller.navigationBar.frame, point)) {
// headerConfig should be the first subview of the topmost screen
UIView *headerConfig = [[_reactSubviews.lastObject reactSubviews] firstObject];
if ([headerConfig isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
UIView *headerHitTestResult = [headerConfig hitTest:point withEvent:event];
if (headerHitTestResult != nil) {
return headerHitTestResult;
}
}
}
return [super hitTest:point withEvent:event];
}

- (void)invalidate
{
_invalidated = YES;
Expand All @@ -920,30 +944,6 @@ - (void)invalidate
[_controller removeFromParentViewController];
}

- (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
// NOTE: This hack is required to restore native behavior of edge swipe (interactive pop gesture)
// without this, on a screen with a scroll view, it's only possible to pop view by panning horizontally
// if even slightly diagonal (or if in motion), scroll view will scroll, and edge swipe will be cancelled
if (![[gestureRecognizer view] isKindOfClass:[UIScrollView class]]) {
return NO;
}
UIScrollView *scrollView = gestureRecognizer.view;
return scrollView.panGestureRecognizer == gestureRecognizer;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return [self isScrollViewPanGestureRecognizer:otherGestureRecognizer];
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return [self isScrollViewPanGestureRecognizer:otherGestureRecognizer];
}

- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:
(id<UIViewControllerAnimatedTransitioning>)animationController
Expand Down
2 changes: 1 addition & 1 deletion ios/RNSScreenStackHeaderConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
#else
@property (nonatomic) UIBlurEffectStyle blurEffect;
@property (nonatomic) BOOL hide;
@property (nonatomic) BOOL backButtonInCustomView;
#endif

@property (nonatomic, retain) NSString *title;
Expand All @@ -48,6 +47,7 @@
@property (nonatomic) BOOL disableBackButtonMenu;
@property (nonatomic) BOOL hideShadow;
@property (nonatomic) BOOL translucent;
@property (nonatomic) BOOL backButtonInCustomView;
@property (nonatomic) UISemanticContentAttribute direction;

+ (void)willShowViewController:(UIViewController *)vc
Expand Down
7 changes: 4 additions & 3 deletions ios/RNSScreenStackHeaderConfig.mm
Original file line number Diff line number Diff line change
Expand Up @@ -543,12 +543,9 @@ + (void)updateViewController:(UIViewController *)vc
for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
switch (subview.type) {
case RNSScreenStackHeaderSubviewTypeLeft: {
#ifdef RN_FABRIC_ENABLED
#else
#if !TARGET_OS_TV
navitem.leftItemsSupplementBackButton = config.backButtonInCustomView;
#endif
#endif // RN_FABRIC_ENABLED
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithCustomView:subview];
navitem.leftBarButtonItem = buttonItem;
break;
Expand Down Expand Up @@ -706,6 +703,10 @@ - (void)updateProps:(facebook::react::Props::Shared const &)props
_translucent = newScreenProps.translucent;
needsNavigationControllerLayout = YES;
}

if (newScreenProps.backButtonInCustomView != _backButtonInCustomView) {
[self setBackButtonInCustomView:newScreenProps.backButtonInCustomView];
}

_title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title);
if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) {
Expand Down
1 change: 1 addition & 0 deletions src/fabric/ScreenNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export type NativeProps = $ReadOnly<{|
customAnimationOnSwipe?: boolean,
fullScreenSwipeEnabled?: boolean,
homeIndicatorHidden?: boolean,
preventNativeDismiss?: boolean,
gestureEnabled?: WithDefault<boolean, true>,
statusBarColor?: ColorValue,
statusBarHidden?: boolean,
Expand Down
2 changes: 1 addition & 1 deletion src/fabric/ScreenStackHeaderConfigNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export type NativeProps = $ReadOnly<{|
titleColor?: ColorValue,
disableBackButtonMenu?: boolean,
hideBackButton?: boolean,
backButtonInCustomView?: boolean,
// TODO: implement this props on iOS
topInsetEnabled?: boolean,
backButtonInCustomView?: boolean,
|}>;

type ComponentType = HostComponent<NativeProps>;
Expand Down

0 comments on commit 69ab053

Please sign in to comment.