Skip to content

Commit

Permalink
Added modalAttemptedToDismiss event with tests and docs (#5832)
Browse files Browse the repository at this point in the history
* Added modalAttemptedToDismiss event with tests and docs

* Typo on pageSheet modal button label

* Update ModalScreen.js

* Fixed button testID

* Revert label to fix test cases

Co-authored-by: Guy Carmeli <guyca@users.noreply.github.com>
Co-authored-by: Yogev Ben David <yogevbd@wix.com>
  • Loading branch information
3 people committed Jan 12, 2020
1 parent cf591d9 commit 87af42a
Show file tree
Hide file tree
Showing 16 changed files with 111 additions and 11 deletions.
16 changes: 16 additions & 0 deletions docs/docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,22 @@ const modalDismissedListener = Navigation.events().registerModalDismissedListene
modalDismissedListener.remove();
```

## registerModalAttemptedToDismissListener(iOS 13+ only)
Invoked only on iOS pageSheet modal when swipeToDismiss flag is set to true and modal swiped down to dismiss.

```js
// Subscribe
const modalAttemptedToDismissListener = Navigation.events().registerModalAttemptedToDismissListener(({ componentId }) => {

});
...
// Unsubscribe
modalDismissedListener.remove();
```
| Parameter | Description |
|:--------------------:|:-----|
|**componentId** | Id of the modal tried to dismiss|

## registerScreenPoppedListener
Invoked when screen is popped.

Expand Down
4 changes: 4 additions & 0 deletions lib/ios/RNNCommandsHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,10 @@ - (void)dismissedModal:(UIViewController *)viewController {
[_eventEmitter sendModalsDismissedEvent:viewController.layoutInfo.componentId numberOfModalsDismissed:@(1)];
}

- (void)attemptedToDismissModal:(UIViewController *)viewController {
[_eventEmitter sendModalAttemptedToDismissEvent:viewController.layoutInfo.componentId];
}

- (void)dismissedMultipleModals:(NSArray *)viewControllers {
if (viewControllers && viewControllers.count) {
[_eventEmitter sendModalsDismissedEvent:((UIViewController *)viewControllers.lastObject).layoutInfo.componentId numberOfModalsDismissed:@(viewControllers.count)];
Expand Down
2 changes: 2 additions & 0 deletions lib/ios/RNNEventEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

- (void)sendModalsDismissedEvent:(NSString *)componentId numberOfModalsDismissed:(NSNumber *)modalsDismissed;

- (void)sendModalAttemptedToDismissEvent:(NSString *)componentId;

- (void)sendScreenPoppedEvent:(NSString *)componentId;


Expand Down
10 changes: 9 additions & 1 deletion lib/ios/RNNEventEmitter.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ @implementation RNNEventEmitter {
static NSString* const ComponentDidDisappear = @"RNN.ComponentDidDisappear";
static NSString* const NavigationButtonPressed = @"RNN.NavigationButtonPressed";
static NSString* const ModalDismissed = @"RNN.ModalDismissed";
static NSString* const ModalAttemptedToDismiss = @"RNN.ModalAttemptedToDismiss";
static NSString* const SearchBarUpdated = @"RNN.SearchBarUpdated";
static NSString* const SearchBarCancelPressed = @"RNN.SearchBarCancelPressed";
static NSString* const PreviewCompleted = @"RNN.PreviewCompleted";
Expand All @@ -31,7 +32,8 @@ @implementation RNNEventEmitter {
SearchBarUpdated,
SearchBarCancelPressed,
PreviewCompleted,
ScreenPopped];
ScreenPopped,
ModalAttemptedToDismiss];
}

# pragma mark public
Expand Down Expand Up @@ -113,6 +115,12 @@ - (void)sendModalsDismissedEvent:(NSString *)componentId numberOfModalsDismissed
}];
}

- (void)sendModalAttemptedToDismissEvent:(NSString *)componentId {
[self send:ModalAttemptedToDismiss body:@{
@"componentId": componentId,
}];
}

- (void)sendScreenPoppedEvent:(NSString *)componentId {
[self send:ScreenPopped body:@{
@"componentId": componentId
Expand Down
1 change: 1 addition & 0 deletions lib/ios/RNNModalManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ typedef void (^RNNTransitionRejectionBlock)(NSString *code, NSString *message, N
@protocol RNNModalManagerDelegate <NSObject>

- (void)dismissedModal:(UIViewController *)viewController;
- (void)attemptedToDismissModal:(UIViewController *)viewController;
- (void)dismissedMultipleModals:(NSArray *)viewControllers;

@end
Expand Down
4 changes: 4 additions & 0 deletions lib/ios/RNNModalManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ - (void)presentationControllerDidDismiss:(UIPresentationController *)presentatio
[_delegate dismissedModal:presentationController.presentedViewController.presentedComponentViewController];
}

- (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)presentationController {
[_delegate attemptedToDismissModal:presentationController.presentedViewController.presentedComponentViewController];
}

-(UIViewController*)topPresentedVC {
UIViewController *root = UIApplication.sharedApplication.delegate.window.rootViewController;
while(root.presentedViewController) {
Expand Down
7 changes: 6 additions & 1 deletion lib/src/adapters/NativeEventsReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
SearchBarCancelPressedEvent,
PreviewCompletedEvent,
ModalDismissedEvent,
ScreenPoppedEvent
ScreenPoppedEvent,
ModalAttemptedToDismissEvent
} from '../interfaces/ComponentEvents';
import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';

Expand Down Expand Up @@ -49,6 +50,10 @@ export class NativeEventsReceiver {
return this.emitter.addListener('RNN.ModalDismissed', callback);
}

public registerModalAttemptedToDismissListener(callback: (event: ModalAttemptedToDismissEvent) => void): EmitterSubscription {
return this.emitter.addListener('RNN.ModalAttemptedToDismiss', callback);
}

public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EmitterSubscription {
return this.emitter.addListener('RNN.SearchBarUpdated', callback);
}
Expand Down
19 changes: 16 additions & 3 deletions lib/src/events/ComponentEventsObserver.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('ComponentEventsObserver', () => {
const searchBarCancelPressedFn = jest.fn();
const previewCompletedFn = jest.fn();
const modalDismissedFn = jest.fn();
const modalAttemptedToDismissFn = jest.fn();
const screenPoppedFn = jest.fn();
let subscription: EventSubscription;
let uut: ComponentEventsObserver;
Expand Down Expand Up @@ -57,6 +58,10 @@ describe('ComponentEventsObserver', () => {
modalDismissedFn(event);
}

modalAttemptedToDismiss(event: any) {
modalAttemptedToDismissFn(event);
}

searchBarUpdated(event: any) {
searchBarUpdatedFn(event);
}
Expand Down Expand Up @@ -108,6 +113,10 @@ describe('ComponentEventsObserver', () => {
modalDismissedFn(event);
}

modalAttemptedToDismiss(event: any) {
modalAttemptedToDismissFn(event);
}

searchBarUpdated(event: any) {
searchBarUpdatedFn(event);
}
Expand Down Expand Up @@ -153,14 +162,14 @@ describe('ComponentEventsObserver', () => {
});

it(`bindComponent should use optional componentId if component has a componentId in props`, () => {
const tree = renderer.create(<UnboundScreen componentId={'doNotUseThisId'} />);
const tree = renderer.create(<UnboundScreen componentId={'doNotUseThisId'} />);
uut.bindComponent(tree.getInstance() as any, 'myCompId')

expect(tree.toJSON()).toBeDefined();

uut.notifyComponentDidAppear({ componentId: 'dontUseThisId', componentName: 'doesnt matter', componentType: 'Component' });
expect(didAppearFn).not.toHaveBeenCalled();


uut.notifyComponentDidAppear({ componentId: 'myCompId', componentName: 'doesnt matter', componentType: 'Component' });
expect(didAppearFn).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -188,6 +197,10 @@ describe('ComponentEventsObserver', () => {
expect(modalDismissedFn).toHaveBeenCalledTimes(1);
expect(modalDismissedFn).toHaveBeenLastCalledWith({ componentId: 'myCompId', modalsDismissed: 1 })

uut.notifyModalAttemptedToDismiss({ componentId: 'myCompId' });
expect(modalAttemptedToDismissFn).toHaveBeenCalledTimes(1);
expect(modalAttemptedToDismissFn).toHaveBeenLastCalledWith({ componentId: 'myCompId' })

uut.notifySearchBarUpdated({ componentId: 'myCompId', text: 'theText', isFocused: true });
expect(searchBarUpdatedFn).toHaveBeenCalledTimes(1);
expect(searchBarUpdatedFn).toHaveBeenCalledWith({ componentId: 'myCompId', text: 'theText', isFocused: true });
Expand Down
9 changes: 8 additions & 1 deletion lib/src/events/ComponentEventsObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
ComponentEvent,
PreviewCompletedEvent,
ModalDismissedEvent,
ScreenPoppedEvent
ScreenPoppedEvent,
ModalAttemptedToDismissEvent
} from '../interfaces/ComponentEvents';
import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver';
import { Store } from '../components/Store';
Expand All @@ -32,6 +33,7 @@ export class ComponentEventsObserver {
this.notifyComponentDidDisappear = this.notifyComponentDidDisappear.bind(this);
this.notifyNavigationButtonPressed = this.notifyNavigationButtonPressed.bind(this);
this.notifyModalDismissed = this.notifyModalDismissed.bind(this);
this.notifyModalAttemptedToDismiss = this.notifyModalAttemptedToDismiss.bind(this);
this.notifySearchBarUpdated = this.notifySearchBarUpdated.bind(this);
this.notifySearchBarCancelPressed = this.notifySearchBarCancelPressed.bind(this);
this.notifyPreviewCompleted = this.notifyPreviewCompleted.bind(this);
Expand All @@ -45,6 +47,7 @@ export class ComponentEventsObserver {
this.nativeEventsReceiver.registerComponentDidDisappearListener(this.notifyComponentDidDisappear);
this.nativeEventsReceiver.registerNavigationButtonPressedListener(this.notifyNavigationButtonPressed);
this.nativeEventsReceiver.registerModalDismissedListener(this.notifyModalDismissed);
this.nativeEventsReceiver.registerModalAttemptedToDismissListener(this.notifyModalAttemptedToDismiss);
this.nativeEventsReceiver.registerSearchBarUpdatedListener(this.notifySearchBarUpdated);
this.nativeEventsReceiver.registerSearchBarCancelPressedListener(this.notifySearchBarCancelPressed);
this.nativeEventsReceiver.registerPreviewCompletedListener(this.notifyPreviewCompleted);
Expand Down Expand Up @@ -87,6 +90,10 @@ export class ComponentEventsObserver {
this.triggerOnAllListenersByComponentId(event, 'modalDismissed');
}

notifyModalAttemptedToDismiss(event: ModalAttemptedToDismissEvent) {
this.triggerOnAllListenersByComponentId(event, 'modalAttemptedToDismiss');
}

notifySearchBarUpdated(event: SearchBarUpdatedEvent) {
this.triggerOnAllListenersByComponentId(event, 'searchBarUpdated');
}
Expand Down
7 changes: 7 additions & 0 deletions lib/src/events/EventsRegistry.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ describe('EventsRegistry', () => {
expect(mockNativeEventsReceiver.registerModalDismissedListener).toHaveBeenCalledWith(cb);
});

it('delegates modalAttemptedToDimiss to nativeEventsReceiver', () => {
const cb = jest.fn();
uut.registerModalAttemptedToDismissListener(cb);
expect(mockNativeEventsReceiver.registerModalAttemptedToDismissListener).toHaveBeenCalledTimes(1);
expect(mockNativeEventsReceiver.registerModalAttemptedToDismissListener).toHaveBeenCalledWith(cb);
});

it('delegates searchBarUpdated to nativeEventsReceiver', () => {
const cb = jest.fn();
uut.registerSearchBarUpdatedListener(cb);
Expand Down
7 changes: 6 additions & 1 deletion lib/src/events/EventsRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
SearchBarCancelPressedEvent,
PreviewCompletedEvent,
ModalDismissedEvent,
ScreenPoppedEvent
ScreenPoppedEvent,
ModalAttemptedToDismissEvent
} from '../interfaces/ComponentEvents';
import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';

Expand Down Expand Up @@ -47,6 +48,10 @@ export class EventsRegistry {
return this.nativeEventsReceiver.registerModalDismissedListener(callback);
}

public registerModalAttemptedToDismissListener(callback: (event: ModalAttemptedToDismissEvent) => void): EmitterSubscription {
return this.nativeEventsReceiver.registerModalAttemptedToDismissListener(callback);
}

public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EmitterSubscription {
return this.nativeEventsReceiver.registerSearchBarUpdatedListener(callback);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/src/interfaces/ComponentEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export interface ModalDismissedEvent extends ComponentEvent {
modalsDismissed: number;
}

export interface ModalAttemptedToDismissEvent extends ComponentEvent {
componentId: string;
}

export interface SearchBarUpdatedEvent extends ComponentEvent {
text: string;
isFocused: boolean;
Expand Down
1 change: 1 addition & 0 deletions playground/ios/NavigationTests/RNNCommandsHandlerTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ -(NSArray*) getPublicMethodNamesForObject:(NSObject*)obj{
[skipMethods addObject:@"readyToReceiveCommands"];
[skipMethods addObject:@".cxx_destruct"];
[skipMethods addObject:@"dismissedModal:"];
[skipMethods addObject:@"attemptedToDismissModal:"];
[skipMethods addObject:@"dismissedMultipleModals:"];

NSMutableArray* result = [NSMutableArray new];
Expand Down
12 changes: 12 additions & 0 deletions playground/src/screens/ModalScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ class ModalScreen extends React.Component {
};
}

state = {
dimissCounter: 0,
}

componentDidMount() {
Navigation.events().bindComponent(this);
}

modalAttemptedToDismiss() {
return this.setState(state => ({ dimissCounter: state.dimissCounter + 1 }))
}

render() {
return (
<Root componentId={this.props.componentId} footer={`Modal Stack Position: ${this.getModalPosition()}`}>
Expand Down
16 changes: 13 additions & 3 deletions playground/src/screens/NavigationScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ const React = require('react');
const Root = require('../components/Root');
const Button = require('../components/Button')
const Navigation = require('./../services/Navigation');
const { Platform } = require('react-native');
const {
NAVIGATION_TAB,
MODAL_BTN,
OVERLAY_BTN,
EXTERNAL_COMP_BTN,
SHOW_STATIC_EVENTS_SCREEN,
SHOW_ORIENTATION_SCREEN,
SET_ROOT_BTN
SET_ROOT_BTN,
PAGE_SHEET_MODAL_BTN
} = require('../testIDs');
const Screens = require('./Screens');

class NavigationScreen extends React.Component {
class NavigationScreen extends React.Component {
static options() {
return {
topBar: {
Expand All @@ -34,6 +36,7 @@ class NavigationScreen extends React.Component {
<Root componentId={this.props.componentId}>
<Button label='Set Root' testID={SET_ROOT_BTN} onPress={this.setRoot} />
<Button label='Modal' testID={MODAL_BTN} onPress={this.showModal} />
{Platform.OS === 'ios' && <Button label='PageSheet modal' testID={PAGE_SHEET_MODAL_BTN} onPress={this.showPageSheetModal} />}
<Button label='Overlay' testID={OVERLAY_BTN} onPress={this.showOverlay} />
<Button label='External Component' testID={EXTERNAL_COMP_BTN} onPress={this.externalComponent} />
<Button label='Static Events' testID={SHOW_STATIC_EVENTS_SCREEN} onPress={this.pushStaticEventsScreen} />
Expand All @@ -50,13 +53,20 @@ class NavigationScreen extends React.Component {

setRoot = () => Navigation.showModal(Screens.SetRoot);
showModal = () => Navigation.showModal(Screens.Modal);

showPageSheetModal = () => Navigation.showModal(Screens.Modal, {
modalPresentationStyle: 'pageSheet',
modal: {
swipeToDismiss: false,
}
});
showOverlay = () => Navigation.showModal(Screens.Overlay);
externalComponent = () => Navigation.showModal(Screens.ExternalComponent);
pushStaticEventsScreen = () => Navigation.showModal(Screens.EventsScreen)
orientation = () => Navigation.showModal(Screens.Orientation);
pushContextScreen = () => Navigation.push(this, Screens.ContextScreen);
sharedElement = () => Navigation.showModal(Screens.CocktailsListScreen)
preview = ({reactTag}) => {
preview = ({ reactTag }) => {
Navigation.push(this.props.componentId, {
component: {
name: Screens.Pushed,
Expand Down
3 changes: 2 additions & 1 deletion playground/src/testIDs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
OVERLAY_BTN: 'OVERLAY_BTN',
SIDE_MENU_BTN: 'SIDE_MENU_BTN',
MODAL_BTN: 'SHOW_MODAL_BUTTON',
PAGE_SHEET_MODAL_BTN: 'SHOW_PAGE_SHEET_MODAL_BUTTON',
DISMISS_MODAL_BTN: 'DISMISS_MODAL_BUTTON',
MODAL_SCREEN_HEADER: 'MODAL_SCREEN_HEADER',
ALERT_BUTTON: 'ALERT_BUTTON',
Expand Down Expand Up @@ -129,7 +130,7 @@ module.exports = {
SET_INTERCEPT_TOUCH: `SET_INTERCEPT_TOUCH`,
PUSH_BOTTOM_TABS_BUTTON: `PUSH_BOTTOM_TABS_BUTTON`,
SET_STACK_ROOT_BUTTON: `SET_STACK_ROOT_BUTTON`,
SET_ROOT:'SET_ROOT',
SET_ROOT: 'SET_ROOT',
RESET_BUTTONS: 'RESET_BUTTONS',
SHOW_LIFECYCLE_BTN: 'SHOW_LIFECYCLE_BTN',
CHANGE_BUTTON_PROPS: 'CHANGE_BUTTON_PROPS',
Expand Down

0 comments on commit 87af42a

Please sign in to comment.