Browse Source

Emit modalDismissed event when modals are dismissed on Android

Guy Carmeli 6 years ago
parent
commit
9543b539d5

+ 9
- 4
lib/android/app/src/main/java/com/reactnativenavigation/react/EventEmitter.java View File

@@ -14,12 +14,11 @@ public class EventEmitter {
14 14
 	private static final String ComponentDidAppear      = "RNN.ComponentDidAppear";
15 15
 	private static final String ComponentDidDisappear   = "RNN.ComponentDidDisappear";
16 16
 	private static final String NavigationButtonPressed = "RNN.NavigationButtonPressed";
17
-	private static final String SearchBarUpdated        = "RNN.SearchBarUpdated";
18
-	private static final String SearchBarCancelPressed  = "RNN.SearchBarCancelPressed";
17
+    private static final String ModalDismissed          = "RNN.ModalDismissed";
19 18
 
20 19
 	private final RCTDeviceEventEmitter emitter;
21 20
 
22
-	EventEmitter(ReactContext reactContext) {
21
+	public EventEmitter(ReactContext reactContext) {
23 22
 		this.emitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
24 23
 	}
25 24
 
@@ -55,13 +54,19 @@ public class EventEmitter {
55 54
 		emit(BottomTabSelected, event);
56 55
 	}
57 56
 
58
-	public void emitCommandCompletedEvent(String commandId, long completionTime) {
57
+	public void emitCommandCompleted(String commandId, long completionTime) {
59 58
 		WritableMap event = Arguments.createMap();
60 59
 		event.putString("commandId", commandId);
61 60
 		event.putDouble("completionTime", completionTime);
62 61
 		emit(CommandCompleted, event);
63 62
 	}
64 63
 
64
+    public void emitModalDismissed(String id) {
65
+        WritableMap event = Arguments.createMap();
66
+        event.putString("componentId", id);
67
+        emit(ModalDismissed, event);
68
+    }
69
+
65 70
 	private void emit(String eventName) {
66 71
 		emit(eventName, Arguments.createMap());
67 72
 	}

+ 1
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java View File

@@ -61,6 +61,7 @@ public class NavigationModule extends ReactContextBaseJavaModule {
61 61
 	public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise) {
62 62
 		final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree).optJSONObject("root"));
63 63
 		handle(() -> {
64
+            navigator().setEventEmitter(eventEmitter);
64 65
             final ViewController viewController = newLayoutFactory().create(layoutTree);
65 66
             navigator().setRoot(viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));
66 67
         });

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/utils/NativeCommandListener.java View File

@@ -21,7 +21,7 @@ public class NativeCommandListener extends CommandListenerAdapter {
21 21
     @Override
22 22
     public void onSuccess(String childId) {
23 23
         if (promise != null) promise.resolve(childId);
24
-        eventEmitter.emitCommandCompletedEvent(commandId, now.now());
24
+        eventEmitter.emitCommandCompleted(commandId, now.now());
25 25
     }
26 26
 
27 27
     @Override

+ 5
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java View File

@@ -12,6 +12,7 @@ import com.reactnativenavigation.anim.NavigationAnimator;
12 12
 import com.reactnativenavigation.parse.Options;
13 13
 import com.reactnativenavigation.presentation.OptionsPresenter;
14 14
 import com.reactnativenavigation.presentation.OverlayManager;
15
+import com.reactnativenavigation.react.EventEmitter;
15 16
 import com.reactnativenavigation.utils.CommandListener;
16 17
 import com.reactnativenavigation.utils.CommandListenerAdapter;
17 18
 import com.reactnativenavigation.utils.CompatUtils;
@@ -214,4 +215,8 @@ public class Navigator extends ParentController {
214 215
                          fromId +
215 216
                          " was not found.");
216 217
     }
218
+
219
+    public void setEventEmitter(EventEmitter eventEmitter) {
220
+        modalStack.setEventEmitter(eventEmitter);
221
+    }
217 222
 }

+ 7
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java View File

@@ -8,6 +8,7 @@ import android.view.ViewGroup;
8 8
 import com.reactnativenavigation.anim.ModalAnimator;
9 9
 import com.reactnativenavigation.parse.ModalPresentationStyle;
10 10
 import com.reactnativenavigation.parse.Options;
11
+import com.reactnativenavigation.react.EventEmitter;
11 12
 import com.reactnativenavigation.utils.CommandListener;
12 13
 import com.reactnativenavigation.viewcontrollers.ViewController;
13 14
 
@@ -16,6 +17,7 @@ public class ModalPresenter {
16 17
     private ViewGroup content;
17 18
     private ModalAnimator animator;
18 19
     private Options defaultOptions = new Options();
20
+    private EventEmitter eventEmitter;
19 21
 
20 22
     ModalPresenter(ModalAnimator animator) {
21 23
         this.animator = animator;
@@ -84,6 +86,11 @@ public class ModalPresenter {
84 86
 
85 87
     private void onDismissEnd(ViewController toDismiss, CommandListener listener) {
86 88
         toDismiss.destroy();
89
+        eventEmitter.emitModalDismissed(toDismiss.getId());
87 90
         listener.onSuccess(toDismiss.getId());
88 91
     }
92
+
93
+    public void setEventEmitter(EventEmitter eventEmitter) {
94
+        this.eventEmitter = eventEmitter;
95
+    }
89 96
 }

+ 5
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalStack.java View File

@@ -6,6 +6,7 @@ import android.view.ViewGroup;
6 6
 
7 7
 import com.reactnativenavigation.anim.ModalAnimator;
8 8
 import com.reactnativenavigation.parse.Options;
9
+import com.reactnativenavigation.react.EventEmitter;
9 10
 import com.reactnativenavigation.utils.CommandListener;
10 11
 import com.reactnativenavigation.viewcontrollers.ViewController;
11 12
 
@@ -125,4 +126,8 @@ public class ModalStack {
125 126
         }
126 127
         return null;
127 128
     }
129
+
130
+    public void setEventEmitter(EventEmitter eventEmitter) {
131
+        presenter.setEventEmitter(eventEmitter);
132
+    }
128 133
 }

+ 1
- 1
lib/android/app/src/test/java/com/reactnativenavigation/utils/NativeCommandListenerTest.java View File

@@ -39,7 +39,7 @@ public class NativeCommandListenerTest extends BaseTest {
39 39
     @Test
40 40
     public void onSuccess_emitsNavigationEvent() {
41 41
         uut.onSuccess(CHILD_ID);
42
-        verify(eventEmitter, times(1)).emitCommandCompletedEvent(COMMAND_ID, NOW);
42
+        verify(eventEmitter, times(1)).emitCommandCompleted(COMMAND_ID, NOW);
43 43
     }
44 44
 
45 45
     @Test

+ 1
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java View File

@@ -67,6 +67,7 @@ public class NavigatorTest extends BaseTest {
67 67
         activityController = newActivityController(TestActivity.class);
68 68
         activity = activityController.create().get();
69 69
         modalStack = spy(new ModalStack(activity));
70
+        modalStack.setEventEmitter(Mockito.mock(EventEmitter.class));
70 71
         uut = new Navigator(activity, childRegistry, modalStack, overlayManager);
71 72
         activity.setNavigator(uut);
72 73
 

+ 3
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java View File

@@ -10,6 +10,7 @@ import com.reactnativenavigation.parse.AnimationOptions;
10 10
 import com.reactnativenavigation.parse.ModalPresentationStyle;
11 11
 import com.reactnativenavigation.parse.Options;
12 12
 import com.reactnativenavigation.parse.params.Bool;
13
+import com.reactnativenavigation.react.EventEmitter;
13 14
 import com.reactnativenavigation.utils.CommandListener;
14 15
 import com.reactnativenavigation.utils.CommandListenerAdapter;
15 16
 import com.reactnativenavigation.viewcontrollers.ChildController;
@@ -19,6 +20,7 @@ import com.reactnativenavigation.viewcontrollers.ViewController;
19 20
 import org.json.JSONException;
20 21
 import org.json.JSONObject;
21 22
 import org.junit.Test;
23
+import org.mockito.Mockito;
22 24
 
23 25
 import static org.assertj.core.api.Java6Assertions.assertThat;
24 26
 import static org.mockito.ArgumentMatchers.any;
@@ -54,6 +56,7 @@ public class ModalPresenterTest extends BaseTest {
54 56
         uut.setContentLayout(contentLayout);
55 57
         modal1 = spy(new SimpleViewController(activity, childRegistry, MODAL_ID_1, new Options()));
56 58
         modal2 = spy(new SimpleViewController(activity, childRegistry, MODAL_ID_2, new Options()));
59
+        uut.setEventEmitter(Mockito.mock(EventEmitter.class));
57 60
     }
58 61
 
59 62
     @Test

+ 3
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java View File

@@ -8,12 +8,14 @@ import com.reactnativenavigation.BaseTest;
8 8
 import com.reactnativenavigation.anim.ModalAnimator;
9 9
 import com.reactnativenavigation.mocks.SimpleViewController;
10 10
 import com.reactnativenavigation.parse.Options;
11
+import com.reactnativenavigation.react.EventEmitter;
11 12
 import com.reactnativenavigation.utils.CommandListener;
12 13
 import com.reactnativenavigation.utils.CommandListenerAdapter;
13 14
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
14 15
 import com.reactnativenavigation.viewcontrollers.ViewController;
15 16
 
16 17
 import org.junit.Test;
18
+import org.mockito.Mockito;
17 19
 
18 20
 import java.util.EmptyStackException;
19 21
 
@@ -56,6 +58,7 @@ public class ModalStackTest extends BaseTest {
56 58
         presenter = spy(new ModalPresenter(animator));
57 59
         uut = new ModalStack(presenter);
58 60
         uut.setContentLayout(activityContentView);
61
+        uut.setEventEmitter(Mockito.mock(EventEmitter.class));
59 62
         modal1 = spy(new SimpleViewController(activity, childRegistry, MODAL_ID_1, new Options()));
60 63
         modal2 = spy(new SimpleViewController(activity, childRegistry, MODAL_ID_2, new Options()));
61 64
         modal3 = spy(new SimpleViewController(activity, childRegistry, MODAL_ID_3, new Options()));

+ 6
- 1
lib/src/adapters/NativeEventsReceiver.ts View File

@@ -5,7 +5,8 @@ import {
5 5
   ComponentDidDisappearEvent,
6 6
   NavigationButtonPressedEvent,
7 7
   SearchBarUpdatedEvent,
8
-  SearchBarCancelPressedEvent
8
+  SearchBarCancelPressedEvent,
9
+  ModalDismissedEvent
9 10
 } from '../interfaces/ComponentEvents';
10 11
 import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';
11 12
 
@@ -41,6 +42,10 @@ export class NativeEventsReceiver {
41 42
     return this.emitter.addListener('RNN.NavigationButtonPressed', callback);
42 43
   }
43 44
 
45
+  public registerModalDismissedListener(callback: (event: ModalDismissedEvent) => void): EventSubscription {
46
+    return this.emitter.addListener('RNN.ModalDismissed', callback);
47
+  }
48
+
44 49
   public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EventSubscription {
45 50
     return this.emitter.addListener('RNN.SearchBarUpdated', callback);
46 51
   }

+ 9
- 0
lib/src/events/ComponentEventsObserver.test.tsx View File

@@ -13,6 +13,7 @@ describe('ComponentEventsObserver', () => {
13 13
   const navigationButtonPressedFn = jest.fn();
14 14
   const searchBarUpdatedFn = jest.fn();
15 15
   const searchBarCancelPressedFn = jest.fn();
16
+  const modalDismissedFn = jest.fn();
16 17
   let subscription;
17 18
 
18 19
   class SimpleScreen extends React.Component<any, any> {
@@ -47,6 +48,10 @@ describe('ComponentEventsObserver', () => {
47 48
       navigationButtonPressedFn(event);
48 49
     }
49 50
 
51
+    modalDismissed(event) {
52
+      modalDismissedFn(event);
53
+    }
54
+
50 55
     searchBarUpdated(event) {
51 56
       searchBarUpdatedFn(event);
52 57
     }
@@ -85,6 +90,10 @@ describe('ComponentEventsObserver', () => {
85 90
     expect(navigationButtonPressedFn).toHaveBeenCalledTimes(1);
86 91
     expect(navigationButtonPressedFn).toHaveBeenCalledWith({ buttonId: 'myButtonId', componentId: 'myCompId' });
87 92
 
93
+    uut.notifyModalDismissed({ componentId: 'myCompId' });
94
+    expect(modalDismissedFn).toHaveBeenCalledTimes(1);
95
+    expect(modalDismissedFn).toHaveBeenLastCalledWith({ componentId: 'myCompId' })
96
+
88 97
     uut.notifySearchBarUpdated({ componentId: 'myCompId', text: 'theText', isFocused: true });
89 98
     expect(searchBarUpdatedFn).toHaveBeenCalledTimes(1);
90 99
     expect(searchBarUpdatedFn).toHaveBeenCalledWith({ componentId: 'myCompId', text: 'theText', isFocused: true });

+ 8
- 1
lib/src/events/ComponentEventsObserver.ts View File

@@ -6,7 +6,8 @@ import {
6 6
   NavigationButtonPressedEvent,
7 7
   SearchBarUpdatedEvent,
8 8
   SearchBarCancelPressedEvent,
9
-  ComponentEvent
9
+  ComponentEvent,
10
+  ModalDismissedEvent
10 11
 } from '../interfaces/ComponentEvents';
11 12
 import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver';
12 13
 
@@ -18,6 +19,7 @@ export class ComponentEventsObserver {
18 19
     this.notifyComponentDidAppear = this.notifyComponentDidAppear.bind(this);
19 20
     this.notifyComponentDidDisappear = this.notifyComponentDidDisappear.bind(this);
20 21
     this.notifyNavigationButtonPressed = this.notifyNavigationButtonPressed.bind(this);
22
+    this.notifyModalDismissed = this.notifyModalDismissed.bind(this);
21 23
     this.notifySearchBarUpdated = this.notifySearchBarUpdated.bind(this);
22 24
     this.notifySearchBarCancelPressed = this.notifySearchBarCancelPressed.bind(this);
23 25
   }
@@ -28,6 +30,7 @@ export class ComponentEventsObserver {
28 30
     this.nativeEventsReceiver.registerComponentDidAppearListener(this.notifyComponentDidAppear);
29 31
     this.nativeEventsReceiver.registerComponentDidDisappearListener(this.notifyComponentDidDisappear);
30 32
     this.nativeEventsReceiver.registerNavigationButtonPressedListener(this.notifyNavigationButtonPressed);
33
+    this.nativeEventsReceiver.registerModalDismissedListener(this.notifyModalDismissed);
31 34
     this.nativeEventsReceiver.registerSearchBarUpdatedListener(this.notifySearchBarUpdated);
32 35
     this.nativeEventsReceiver.registerSearchBarCancelPressedListener(this.notifySearchBarCancelPressed);
33 36
   }
@@ -62,6 +65,10 @@ export class ComponentEventsObserver {
62 65
     this.triggerOnAllListenersByComponentId(event, 'navigationButtonPressed');
63 66
   }
64 67
 
68
+  notifyModalDismissed(event: ModalDismissedEvent) {
69
+    this.triggerOnAllListenersByComponentId(event, 'modalDismissed');
70
+  }
71
+
65 72
   notifySearchBarUpdated(event: SearchBarUpdatedEvent) {
66 73
     this.triggerOnAllListenersByComponentId(event, 'searchBarUpdated');
67 74
   }

+ 7
- 0
lib/src/events/EventsRegistry.test.tsx View File

@@ -60,6 +60,13 @@ describe('EventsRegistry', () => {
60 60
     expect(mockNativeEventsReceiver.registerNavigationButtonPressedListener).toHaveBeenCalledWith(cb);
61 61
   });
62 62
 
63
+  it('delegates modalDismissed to nativeEventsReceiver', () => {
64
+    const cb = jest.fn();
65
+    uut.registerModalDismissedListener(cb);
66
+    expect(mockNativeEventsReceiver.registerModalDismissedListener).toHaveBeenCalledTimes(1);
67
+    expect(mockNativeEventsReceiver.registerModalDismissedListener).toHaveBeenCalledWith(cb);
68
+  });
69
+
63 70
   it('delegates searchBarUpdated to nativeEventsReceiver', () => {
64 71
     const cb = jest.fn();
65 72
     uut.registerSearchBarUpdatedListener(cb);

+ 6
- 1
lib/src/events/EventsRegistry.ts View File

@@ -7,7 +7,8 @@ import {
7 7
   ComponentDidDisappearEvent,
8 8
   NavigationButtonPressedEvent,
9 9
   SearchBarUpdatedEvent,
10
-  SearchBarCancelPressedEvent
10
+  SearchBarCancelPressedEvent,
11
+  ModalDismissedEvent
11 12
 } from '../interfaces/ComponentEvents';
12 13
 import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';
13 14
 
@@ -38,6 +39,10 @@ export class EventsRegistry {
38 39
     return this.nativeEventsReceiver.registerNavigationButtonPressedListener(callback);
39 40
   }
40 41
 
42
+  public registerModalDismissedListener(callback: (event: ModalDismissedEvent) => void): EventSubscription {
43
+    return this.nativeEventsReceiver.registerModalDismissedListener(callback);
44
+  }
45
+
41 46
   public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EventSubscription {
42 47
     return this.nativeEventsReceiver.registerSearchBarUpdatedListener(callback);
43 48
   }

+ 4
- 0
lib/src/interfaces/ComponentEvents.ts View File

@@ -14,6 +14,10 @@ export interface NavigationButtonPressedEvent extends ComponentEvent {
14 14
   buttonId: string;
15 15
 }
16 16
 
17
+export interface ModalDismissedEvent extends ComponentEvent {
18
+  componentId: string;
19
+}
20
+
17 21
 export interface SearchBarUpdatedEvent extends ComponentEvent {
18 22
   text: string;
19 23
   isFocused: boolean;

+ 8
- 1
playground/src/screens/StaticLifecycleOverlay.js View File

@@ -34,6 +34,11 @@ class StaticLifecycleOverlay extends Component {
34 34
         events: [...this.state.events, { event: 'navigationButtonPressed', buttonId, componentId }]
35 35
       });
36 36
     }));
37
+    this.listeners.push(Navigation.events().registerModalDismissedListener(({ componentId }) => {
38
+      this.setState({
39
+        events: [...this.state.events, { event: 'modalDismissed', componentId }]
40
+      });
41
+    }));
37 42
   }
38 43
 
39 44
   componentWillUnmount() {
@@ -47,8 +52,10 @@ class StaticLifecycleOverlay extends Component {
47 52
       return <Text style={styles.h2}>{`${event.commandId}`}</Text>;
48 53
     } else if (event.componentName) {
49 54
       return <Text style={styles.h2}>{`${event.event} | ${event.componentName}`}</Text>;
50
-    } else {
55
+    } else if (event.buttonId) {
51 56
       return <Text style={styles.h2}>{`${event.event} | ${event.buttonId}`}</Text>;
57
+    } else {
58
+      return <Text style={styles.h2}>{`${event.event} | ${event.componentId}`}</Text>;
52 59
     }
53 60
   }
54 61