diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index 89523ad5d9bf61..f5d3556453b2c0 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -12,6 +12,9 @@ const AppContainer = require('../ReactNative/AppContainer'); const I18nManager = require('../ReactNative/I18nManager'); +import NativeEventEmitter from '../EventEmitter/NativeEventEmitter'; +import NativeModalManager from './NativeModalManager'; +const Platform = require('../Utilities/Platform'); const React = require('react'); const ScrollView = require('../Components/ScrollView/ScrollView'); const StyleSheet = require('../StyleSheet/StyleSheet'); @@ -26,6 +29,11 @@ import type {DirectEventHandler} from '../Types/CodegenTypes'; import {type EventSubscription} from '../vendor/emitter/EventEmitter'; import RCTModalHostView from './RCTModalHostViewNativeComponent'; +const ModalEventEmitter = + Platform.OS === 'ios' && NativeModalManager != null + ? new NativeEventEmitter(NativeModalManager) + : null; + /** * The Modal component is a simple way to present content above an enclosing view. * @@ -161,9 +169,22 @@ class Modal extends React.Component { this._identifier = uniqueModalIdentifier++; } + componentDidMount() { + if (ModalEventEmitter) { + this._eventSubscription = ModalEventEmitter.addListener( + 'modalDismissed', + event => { + if (event.modalID === this._identifier && this.props.onDismiss) { + this.props.onDismiss(); + } + }, + ); + } + } + componentWillUnmount() { - if (this.props.onDismiss != null) { - this.props.onDismiss(); + if (this._eventSubscription) { + this._eventSubscription.remove(); } } diff --git a/Libraries/Modal/RCTModalHostViewNativeComponent.js b/Libraries/Modal/RCTModalHostViewNativeComponent.js index 62bd8a8daf144a..7d8151eae25df0 100644 --- a/Libraries/Modal/RCTModalHostViewNativeComponent.js +++ b/Libraries/Modal/RCTModalHostViewNativeComponent.js @@ -15,6 +15,7 @@ import type {HostComponent} from '../Renderer/shims/ReactNativeTypes'; import type { WithDefault, DirectEventHandler, + BubblingEventHandler, Int32, } from '../Types/CodegenTypes'; @@ -86,6 +87,14 @@ type NativeProps = $ReadOnly<{| */ onShow?: ?DirectEventHandler, + /** + * The `onDismiss` prop allows passing a function that will be called once + * the modal has been dismissed. + * + * See https://reactnative.dev/docs/modal.html#ondismiss + */ + onDismiss?: ?BubblingEventHandler, + /** * Deprecated. Use the `animationType` prop instead. */ diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m index 14c220ba5b90e9..91d83aabbdc604 100644 --- a/React/Views/RCTModalHostViewManager.m +++ b/React/Views/RCTModalHostViewManager.m @@ -10,6 +10,7 @@ #import "RCTBridge.h" #import "RCTModalHostView.h" #import "RCTModalHostViewController.h" +#import "RCTModalManager.h" #import "RCTShadowView.h" #import "RCTUtils.h" @@ -46,8 +47,6 @@ - (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex @interface RCTModalHostViewManager () -@property (nonatomic, copy) dispatch_block_t dismissWaitingBlock; - @end @implementation RCTModalHostViewManager { @@ -79,16 +78,9 @@ - (void)presentModalHostView:(RCTModalHostView *)modalHostView if (_presentationBlock) { _presentationBlock([modalHostView reactViewController], viewController, animated, completionBlock); } else { - __weak typeof(self) weakself = self; [[modalHostView reactViewController] presentViewController:viewController animated:animated - completion:^{ - !completionBlock ?: completionBlock(); - __strong typeof(weakself) strongself = weakself; - !strongself.dismissWaitingBlock - ?: strongself.dismissWaitingBlock(); - strongself.dismissWaitingBlock = nil; - }]; + completion:completionBlock]; } } @@ -96,16 +88,15 @@ - (void)dismissModalHostView:(RCTModalHostView *)modalHostView withViewController:(RCTModalHostViewController *)viewController animated:(BOOL)animated { + dispatch_block_t completionBlock = ^{ + if (modalHostView.identifier) { + [[self.bridge moduleForClass:[RCTModalManager class]] modalDismissed:modalHostView.identifier]; + } + }; if (_dismissalBlock) { - _dismissalBlock([modalHostView reactViewController], viewController, animated, nil); + _dismissalBlock([modalHostView reactViewController], viewController, animated, completionBlock); } else { - self.dismissWaitingBlock = ^{ - [viewController.presentingViewController dismissViewControllerAnimated:animated completion:nil]; - }; - if (viewController.presentingViewController) { - self.dismissWaitingBlock(); - self.dismissWaitingBlock = nil; - } + [viewController.presentingViewController dismissViewControllerAnimated:animated completion:completionBlock]; } } diff --git a/React/Views/RCTModalManager.h b/React/Views/RCTModalManager.h new file mode 100644 index 00000000000000..4fbe6dfbd01e25 --- /dev/null +++ b/React/Views/RCTModalManager.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import +#import + +@interface RCTModalManager : RCTEventEmitter + +- (void)modalDismissed:(NSNumber *)modalID; + +@end diff --git a/React/Views/RCTModalManager.m b/React/Views/RCTModalManager.m new file mode 100644 index 00000000000000..992b73c62db660 --- /dev/null +++ b/React/Views/RCTModalManager.m @@ -0,0 +1,42 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTModalManager.h" + +@interface RCTModalManager () + +@property BOOL shouldEmit; + +@end + +@implementation RCTModalManager + +RCT_EXPORT_MODULE(); + +- (NSArray *)supportedEvents +{ + return @[ @"modalDismissed" ]; +} + +- (void)startObserving +{ + _shouldEmit = YES; +} + +- (void)stopObserving +{ + _shouldEmit = NO; +} + +- (void)modalDismissed:(NSNumber *)modalID +{ + if (_shouldEmit) { + [self sendEventWithName:@"modalDismissed" body:@{@"modalID" : modalID}]; + } +} + +@end diff --git a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/jni/react/renderer/components/rncore/EventEmitters.cpp b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/jni/react/renderer/components/rncore/EventEmitters.cpp index 68ba41cdd1c633..ee226666fb4fd6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/jni/react/renderer/components/rncore/EventEmitters.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/jni/react/renderer/components/rncore/EventEmitters.cpp @@ -129,6 +129,13 @@ void ModalHostViewEventEmitter::onShow(OnShow event) const { return payload; }); } +void ModalHostViewEventEmitter::onDismiss(OnDismiss event) const { + dispatchEvent("dismiss", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + + return payload; + }); +} void ModalHostViewEventEmitter::onOrientationChange(OnOrientationChange event) const { dispatchEvent("orientationChange", [event=std::move(event)](jsi::Runtime &runtime) { auto payload = jsi::Object(runtime); diff --git a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/jni/react/renderer/components/rncore/EventEmitters.h b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/jni/react/renderer/components/rncore/EventEmitters.h index 412c956aad8770..16cc009e715739 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/jni/react/renderer/components/rncore/EventEmitters.h +++ b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/jni/react/renderer/components/rncore/EventEmitters.h @@ -196,6 +196,10 @@ class ModalHostViewEventEmitter : public ViewEventEmitter { }; + struct OnDismiss { + + }; + enum class OnOrientationChangeOrientation { Portrait, Landscape @@ -216,6 +220,8 @@ class ModalHostViewEventEmitter : public ViewEventEmitter { void onShow(OnShow value) const; + void onDismiss(OnDismiss value) const; + void onOrientationChange(OnOrientationChange value) const; };