From 87af42a56be7deaa32678afb72846c92e293f524 Mon Sep 17 00:00:00 2001 From: manicantic Date: Sun, 12 Jan 2020 11:09:26 +0100 Subject: [PATCH] Added modalAttemptedToDismiss event with tests and docs (#5832) * 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 Co-authored-by: Yogev Ben David --- docs/docs/events.md | 16 ++++++++++++++++ lib/ios/RNNCommandsHandler.m | 4 ++++ lib/ios/RNNEventEmitter.h | 2 ++ lib/ios/RNNEventEmitter.m | 10 +++++++++- lib/ios/RNNModalManager.h | 1 + lib/ios/RNNModalManager.m | 4 ++++ lib/src/adapters/NativeEventsReceiver.ts | 7 ++++++- .../events/ComponentEventsObserver.test.tsx | 19 ++++++++++++++++--- lib/src/events/ComponentEventsObserver.ts | 9 ++++++++- lib/src/events/EventsRegistry.test.tsx | 7 +++++++ lib/src/events/EventsRegistry.ts | 7 ++++++- lib/src/interfaces/ComponentEvents.ts | 4 ++++ .../NavigationTests/RNNCommandsHandlerTest.m | 1 + playground/src/screens/ModalScreen.js | 12 ++++++++++++ playground/src/screens/NavigationScreen.js | 16 +++++++++++++--- playground/src/testIDs.js | 3 ++- 16 files changed, 111 insertions(+), 11 deletions(-) diff --git a/docs/docs/events.md b/docs/docs/events.md index 9cd1f8676e3..3ada0e780fc 100644 --- a/docs/docs/events.md +++ b/docs/docs/events.md @@ -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. diff --git a/lib/ios/RNNCommandsHandler.m b/lib/ios/RNNCommandsHandler.m index 0dc96fc4251..2e6b3527520 100644 --- a/lib/ios/RNNCommandsHandler.m +++ b/lib/ios/RNNCommandsHandler.m @@ -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)]; diff --git a/lib/ios/RNNEventEmitter.h b/lib/ios/RNNEventEmitter.h index 339702d33c4..66d22efe67a 100644 --- a/lib/ios/RNNEventEmitter.h +++ b/lib/ios/RNNEventEmitter.h @@ -24,6 +24,8 @@ - (void)sendModalsDismissedEvent:(NSString *)componentId numberOfModalsDismissed:(NSNumber *)modalsDismissed; +- (void)sendModalAttemptedToDismissEvent:(NSString *)componentId; + - (void)sendScreenPoppedEvent:(NSString *)componentId; diff --git a/lib/ios/RNNEventEmitter.m b/lib/ios/RNNEventEmitter.m index 5a6c6649bac..35f2443bede 100644 --- a/lib/ios/RNNEventEmitter.m +++ b/lib/ios/RNNEventEmitter.m @@ -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"; @@ -31,7 +32,8 @@ @implementation RNNEventEmitter { SearchBarUpdated, SearchBarCancelPressed, PreviewCompleted, - ScreenPopped]; + ScreenPopped, + ModalAttemptedToDismiss]; } # pragma mark public @@ -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 diff --git a/lib/ios/RNNModalManager.h b/lib/ios/RNNModalManager.h index 58011665ccf..ce10ecebbad 100644 --- a/lib/ios/RNNModalManager.h +++ b/lib/ios/RNNModalManager.h @@ -8,6 +8,7 @@ typedef void (^RNNTransitionRejectionBlock)(NSString *code, NSString *message, N @protocol RNNModalManagerDelegate - (void)dismissedModal:(UIViewController *)viewController; +- (void)attemptedToDismissModal:(UIViewController *)viewController; - (void)dismissedMultipleModals:(NSArray *)viewControllers; @end diff --git a/lib/ios/RNNModalManager.m b/lib/ios/RNNModalManager.m index 9120f184ffb..a2c0e8a588d 100644 --- a/lib/ios/RNNModalManager.m +++ b/lib/ios/RNNModalManager.m @@ -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) { diff --git a/lib/src/adapters/NativeEventsReceiver.ts b/lib/src/adapters/NativeEventsReceiver.ts index ba5b1a54158..eda39da28a3 100644 --- a/lib/src/adapters/NativeEventsReceiver.ts +++ b/lib/src/adapters/NativeEventsReceiver.ts @@ -7,7 +7,8 @@ import { SearchBarCancelPressedEvent, PreviewCompletedEvent, ModalDismissedEvent, - ScreenPoppedEvent + ScreenPoppedEvent, + ModalAttemptedToDismissEvent } from '../interfaces/ComponentEvents'; import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events'; @@ -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); } diff --git a/lib/src/events/ComponentEventsObserver.test.tsx b/lib/src/events/ComponentEventsObserver.test.tsx index 9e09b2c6aa5..d6eff477a5e 100644 --- a/lib/src/events/ComponentEventsObserver.test.tsx +++ b/lib/src/events/ComponentEventsObserver.test.tsx @@ -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; @@ -57,6 +58,10 @@ describe('ComponentEventsObserver', () => { modalDismissedFn(event); } + modalAttemptedToDismiss(event: any) { + modalAttemptedToDismissFn(event); + } + searchBarUpdated(event: any) { searchBarUpdatedFn(event); } @@ -108,6 +113,10 @@ describe('ComponentEventsObserver', () => { modalDismissedFn(event); } + modalAttemptedToDismiss(event: any) { + modalAttemptedToDismissFn(event); + } + searchBarUpdated(event: any) { searchBarUpdatedFn(event); } @@ -153,14 +162,14 @@ describe('ComponentEventsObserver', () => { }); it(`bindComponent should use optional componentId if component has a componentId in props`, () => { - const tree = renderer.create(); + const tree = renderer.create(); 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); @@ -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 }); diff --git a/lib/src/events/ComponentEventsObserver.ts b/lib/src/events/ComponentEventsObserver.ts index 6c4c4db2ff9..98cd2a76eaf 100644 --- a/lib/src/events/ComponentEventsObserver.ts +++ b/lib/src/events/ComponentEventsObserver.ts @@ -13,7 +13,8 @@ import { ComponentEvent, PreviewCompletedEvent, ModalDismissedEvent, - ScreenPoppedEvent + ScreenPoppedEvent, + ModalAttemptedToDismissEvent } from '../interfaces/ComponentEvents'; import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver'; import { Store } from '../components/Store'; @@ -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); @@ -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); @@ -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'); } diff --git a/lib/src/events/EventsRegistry.test.tsx b/lib/src/events/EventsRegistry.test.tsx index 4707342182e..a734acc0a9b 100644 --- a/lib/src/events/EventsRegistry.test.tsx +++ b/lib/src/events/EventsRegistry.test.tsx @@ -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); diff --git a/lib/src/events/EventsRegistry.ts b/lib/src/events/EventsRegistry.ts index 2099c26195e..86a5ad96d11 100644 --- a/lib/src/events/EventsRegistry.ts +++ b/lib/src/events/EventsRegistry.ts @@ -12,7 +12,8 @@ import { SearchBarCancelPressedEvent, PreviewCompletedEvent, ModalDismissedEvent, - ScreenPoppedEvent + ScreenPoppedEvent, + ModalAttemptedToDismissEvent } from '../interfaces/ComponentEvents'; import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events'; @@ -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); } diff --git a/lib/src/interfaces/ComponentEvents.ts b/lib/src/interfaces/ComponentEvents.ts index 923f3045309..1ef9f67fe50 100644 --- a/lib/src/interfaces/ComponentEvents.ts +++ b/lib/src/interfaces/ComponentEvents.ts @@ -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; diff --git a/playground/ios/NavigationTests/RNNCommandsHandlerTest.m b/playground/ios/NavigationTests/RNNCommandsHandlerTest.m index 9609922b99d..af38876e1fb 100644 --- a/playground/ios/NavigationTests/RNNCommandsHandlerTest.m +++ b/playground/ios/NavigationTests/RNNCommandsHandlerTest.m @@ -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]; diff --git a/playground/src/screens/ModalScreen.js b/playground/src/screens/ModalScreen.js index 7c5f42bfa5e..5d78a83b2c9 100644 --- a/playground/src/screens/ModalScreen.js +++ b/playground/src/screens/ModalScreen.js @@ -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 ( diff --git a/playground/src/screens/NavigationScreen.js b/playground/src/screens/NavigationScreen.js index bbc6406f26c..a43dba12ef1 100644 --- a/playground/src/screens/NavigationScreen.js +++ b/playground/src/screens/NavigationScreen.js @@ -2,6 +2,7 @@ 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, @@ -9,11 +10,12 @@ const { 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: { @@ -34,6 +36,7 @@ class NavigationScreen extends React.Component {