Sfoglia il codice sorgente

Add screenPopped event (#5748)

Called each time a screen is popped, either due to `Navigation.pop` or pop gesture.
closes #3941 

Navigation.events().registerScreenPoppedListener(({ componentId }) => {

});
Yogev Ben David 4 anni fa
parent
commit
71af55968d

+ 13
- 0
docs/docs/events.md Vedi File

@@ -145,6 +145,19 @@ const modalDismissedListener = Navigation.events().registerModalDismissedListene
145 145
 modalDismissedListener.remove();
146 146
 ```
147 147
 
148
+## registerScreenPoppedListener
149
+Invoked when screen is popped.
150
+
151
+```js
152
+// Subscribe
153
+const screenPoppedListener = Navigation.events().registerScreenPoppedListener(({ componentId }) => {
154
+
155
+});
156
+...
157
+// Unsubscribe
158
+screenPoppedListener.remove();
159
+```
160
+
148 161
 |       Parameter         | Description |
149 162
 |:--------------------:|:-----|
150 163
 |**componentId** | Id of the modal|

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java Vedi File

@@ -176,7 +176,7 @@ public class LayoutFactory {
176 176
     }
177 177
 
178 178
 	private ViewController createStack(LayoutNode node) {
179
-        return new StackControllerBuilder(activity)
179
+        return new StackControllerBuilder(activity, eventEmitter)
180 180
                 .setChildren(createChildren(node.children))
181 181
                 .setChildRegistry(childRegistry)
182 182
                 .setTopBarController(new TopBarController())

+ 7
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/EventEmitter.java Vedi File

@@ -19,6 +19,7 @@ public class EventEmitter {
19 19
     private static final String ComponentDidDisappear = "RNN.ComponentDidDisappear";
20 20
     private static final String NavigationButtonPressed = "RNN.NavigationButtonPressed";
21 21
     private static final String ModalDismissed = "RNN.ModalDismissed";
22
+    private static final String ScreenPopped = "RNN.ScreenPopped";
22 23
     @Nullable
23 24
     private ReactContext reactContext;
24 25
 
@@ -73,6 +74,12 @@ public class EventEmitter {
73 74
         emit(ModalDismissed, event);
74 75
     }
75 76
 
77
+    public void emitScreenPoppedEvent(String componentId) {
78
+        WritableMap event = Arguments.createMap();
79
+        event.putString("componentId", componentId);
80
+        emit(ScreenPopped, event);
81
+    }
82
+
76 83
     private void emit(String eventName) {
77 84
         emit(eventName, Arguments.createMap());
78 85
     }

+ 5
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java Vedi File

@@ -9,6 +9,7 @@ import com.reactnativenavigation.parse.Options;
9 9
 import com.reactnativenavigation.presentation.Presenter;
10 10
 import com.reactnativenavigation.presentation.StackPresenter;
11 11
 import com.reactnativenavigation.react.Constants;
12
+import com.reactnativenavigation.react.EventEmitter;
12 13
 import com.reactnativenavigation.utils.CommandListener;
13 14
 import com.reactnativenavigation.utils.CommandListenerAdapter;
14 15
 import com.reactnativenavigation.utils.CompatUtils;
@@ -43,12 +44,14 @@ public class StackController extends ParentController<StackLayout> {
43 44
 
44 45
     private IdStack<ViewController> stack = new IdStack<>();
45 46
     private final NavigationAnimator animator;
47
+    private final EventEmitter eventEmitter;
46 48
     private TopBarController topBarController;
47 49
     private BackButtonHelper backButtonHelper;
48 50
     private final StackPresenter presenter;
49 51
 
50
-    public StackController(Activity activity, List<ViewController> children, ChildControllersRegistry childRegistry, TopBarController topBarController, NavigationAnimator animator, String id, Options initialOptions, BackButtonHelper backButtonHelper, StackPresenter stackPresenter, Presenter presenter) {
52
+    public StackController(Activity activity, List<ViewController> children, ChildControllersRegistry childRegistry, EventEmitter eventEmitter, TopBarController topBarController, NavigationAnimator animator, String id, Options initialOptions, BackButtonHelper backButtonHelper, StackPresenter stackPresenter, Presenter presenter) {
51 53
         super(activity, childRegistry, id, presenter, initialOptions);
54
+        this.eventEmitter = eventEmitter;
52 55
         this.topBarController = topBarController;
53 56
         this.animator = animator;
54 57
         this.backButtonHelper = backButtonHelper;
@@ -277,6 +280,7 @@ public class StackController extends ParentController<StackLayout> {
277 280
     private void finishPopping(ViewController disappearing, CommandListener listener) {
278 281
         disappearing.destroy();
279 282
         listener.onSuccess(disappearing.getId());
283
+        eventEmitter.emitScreenPoppedEvent(disappearing.getId());
280 284
     }
281 285
 
282 286
     public void popTo(ViewController viewController, Options mergeOptions, CommandListener listener) {

+ 10
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerBuilder.java Vedi File

@@ -6,6 +6,7 @@ import com.reactnativenavigation.anim.NavigationAnimator;
6 6
 import com.reactnativenavigation.parse.Options;
7 7
 import com.reactnativenavigation.presentation.Presenter;
8 8
 import com.reactnativenavigation.presentation.StackPresenter;
9
+import com.reactnativenavigation.react.EventEmitter;
9 10
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
10 11
 import com.reactnativenavigation.viewcontrollers.ViewController;
11 12
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
@@ -26,13 +27,20 @@ public class StackControllerBuilder {
26 27
     private Presenter presenter;
27 28
     private StackPresenter stackPresenter;
28 29
     private List<ViewController> children = new ArrayList<>();
30
+    private EventEmitter eventEmitter;
29 31
 
30
-    public StackControllerBuilder(Activity activity) {
32
+    public StackControllerBuilder(Activity activity, EventEmitter eventEmitter) {
31 33
         this.activity = activity;
34
+        this.eventEmitter = eventEmitter;
32 35
         presenter = new Presenter(activity, new Options());
33 36
         animator = new NavigationAnimator(activity, new ElementTransitionManager());
34 37
     }
35 38
 
39
+    public StackControllerBuilder setEventEmitter(EventEmitter eventEmitter) {
40
+        this.eventEmitter = eventEmitter;
41
+        return this;
42
+    }
43
+
36 44
     public StackControllerBuilder setChildren(ViewController... children) {
37 45
         return setChildren(Arrays.asList(children));
38 46
     }
@@ -86,6 +94,7 @@ public class StackControllerBuilder {
86 94
         return new StackController(activity,
87 95
                 children,
88 96
                 childRegistry,
97
+                eventEmitter,
89 98
                 topBarController,
90 99
                 animator,
91 100
                 id,

+ 2
- 1
lib/android/app/src/test/java/com/reactnativenavigation/TestUtils.java Vedi File

@@ -12,6 +12,7 @@ import com.reactnativenavigation.parse.Options;
12 12
 import com.reactnativenavigation.parse.params.Bool;
13 13
 import com.reactnativenavigation.presentation.RenderChecker;
14 14
 import com.reactnativenavigation.presentation.StackPresenter;
15
+import com.reactnativenavigation.react.EventEmitter;
15 16
 import com.reactnativenavigation.utils.ImageLoader;
16 17
 import com.reactnativenavigation.utils.UiUtils;
17 18
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
@@ -33,7 +34,7 @@ public class TestUtils {
33 34
                 return topBar;
34 35
             }
35 36
         };
36
-        return new StackControllerBuilder(activity)
37
+        return new StackControllerBuilder(activity, Mockito.mock(EventEmitter.class))
37 38
                 .setId("stack")
38 39
                 .setChildRegistry(new ChildControllersRegistry())
39 40
                 .setTopBarController(topBarController)

+ 1
- 14
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java Vedi File

@@ -11,21 +11,14 @@ import com.reactnativenavigation.BaseTest;
11 11
 import com.reactnativenavigation.TestUtils;
12 12
 import com.reactnativenavigation.mocks.TestComponentLayout;
13 13
 import com.reactnativenavigation.mocks.TestReactView;
14
-import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
15
-import com.reactnativenavigation.mocks.TopBarBackgroundViewCreatorMock;
16
-import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
17 14
 import com.reactnativenavigation.parse.Options;
18 15
 import com.reactnativenavigation.parse.params.Bool;
19 16
 import com.reactnativenavigation.parse.params.Colour;
20 17
 import com.reactnativenavigation.parse.params.Text;
21 18
 import com.reactnativenavigation.presentation.ComponentPresenter;
22 19
 import com.reactnativenavigation.presentation.Presenter;
23
-import com.reactnativenavigation.presentation.RenderChecker;
24
-import com.reactnativenavigation.presentation.StackPresenter;
25 20
 import com.reactnativenavigation.utils.CommandListenerAdapter;
26
-import com.reactnativenavigation.utils.ImageLoader;
27 21
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
28
-import com.reactnativenavigation.viewcontrollers.stack.StackControllerBuilder;
29 22
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
30 23
 import com.reactnativenavigation.views.StackLayout;
31 24
 import com.reactnativenavigation.views.topbar.TopBar;
@@ -95,13 +88,7 @@ public class OptionsApplyingTest extends BaseTest {
95 88
     @Test
96 89
     public void initialOptionsAppliedOnAppear() {
97 90
         uut.options.topBar.title.text = new Text("the title");
98
-        StackController stackController =
99
-                new StackControllerBuilder(activity)
100
-                        .setTopBarController(new TopBarController())
101
-                        .setId("stackId")
102
-                        .setInitialOptions(new Options())
103
-                        .setStackPresenter(new StackPresenter(activity, new TitleBarReactViewCreatorMock(), new TopBarBackgroundViewCreatorMock(), new TopBarButtonCreatorMock(), new ImageLoader(), new RenderChecker(), new Options()))
104
-                        .build();
91
+        StackController stackController = TestUtils.newStackController(activity).build();
105 92
         stackController.ensureViewIsCreated();
106 93
         stackController.push(uut, new CommandListenerAdapter());
107 94
         assertThat(stackController.getTopBar().getTitle()).isEmpty();

+ 37
- 17
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java Vedi File

@@ -22,8 +22,8 @@ import com.reactnativenavigation.parse.params.Bool;
22 22
 import com.reactnativenavigation.parse.params.Text;
23 23
 import com.reactnativenavigation.presentation.RenderChecker;
24 24
 import com.reactnativenavigation.presentation.StackPresenter;
25
+import com.reactnativenavigation.react.EventEmitter;
25 26
 import com.reactnativenavigation.utils.CommandListenerAdapter;
26
-import com.reactnativenavigation.utils.ImageLoader;
27 27
 import com.reactnativenavigation.utils.StatusBarUtils;
28 28
 import com.reactnativenavigation.utils.TitleBarHelper;
29 29
 import com.reactnativenavigation.utils.UiUtils;
@@ -66,6 +66,7 @@ import static org.mockito.Mockito.mock;
66 66
 import static org.mockito.Mockito.spy;
67 67
 import static org.mockito.Mockito.times;
68 68
 import static org.mockito.Mockito.verify;
69
+import static org.mockito.Mockito.verifyNoMoreInteractions;
69 70
 import static org.mockito.Mockito.when;
70 71
 
71 72
 @LooperMode(LooperMode.Mode.PAUSED)
@@ -83,10 +84,12 @@ public class StackControllerTest extends BaseTest {
83 84
     private TopBarController topBarController;
84 85
     private StackPresenter presenter;
85 86
     private BackButtonHelper backButtonHelper;
87
+    private EventEmitter eventEmitter;
86 88
 
87 89
     @Override
88 90
     public void beforeEach() {
89 91
         super.beforeEach();
92
+        eventEmitter = Mockito.mock(EventEmitter.class);
90 93
         backButtonHelper = spy(new BackButtonHelper());
91 94
         activity = newActivity();
92 95
         StatusBarUtils.saveStatusBarHeight(63);
@@ -383,12 +386,10 @@ public class StackControllerTest extends BaseTest {
383 386
     @Test
384 387
     public void pop_layoutHandlesChildWillDisappear() {
385 388
         TopBarController topBarController = new TopBarController();
386
-        uut = new StackControllerBuilder(activity)
387
-                        .setTopBarController(topBarController)
388
-                        .setId("uut")
389
-                        .setInitialOptions(new Options())
390
-                        .setStackPresenter(new StackPresenter(activity, new TitleBarReactViewCreatorMock(), new TopBarBackgroundViewCreatorMock(), new TopBarButtonCreatorMock(), new ImageLoader(), new RenderChecker(), new Options()))
391
-                        .build();
389
+        uut = TestUtils.newStackController(activity)
390
+                .setTopBarController(topBarController)
391
+                .setId("uut")
392
+                .build();
392 393
         uut.ensureViewIsCreated();
393 394
         uut.push(child1, new CommandListenerAdapter());
394 395
         uut.push(child2, new CommandListenerAdapter() {
@@ -404,6 +405,30 @@ public class StackControllerTest extends BaseTest {
404 405
         });
405 406
     }
406 407
 
408
+    @Test
409
+    public void pop_popEventIsEmitted() {
410
+        disablePushAnimation(child1, child2);
411
+        disablePopAnimation(child2);
412
+        uut.push(child1, new CommandListenerAdapter());
413
+        uut.push(child2, new CommandListenerAdapter());
414
+
415
+        uut.pop(Options.EMPTY, new CommandListenerAdapter());
416
+        verify(eventEmitter).emitScreenPoppedEvent(child2.getId());
417
+    }
418
+
419
+    @Test
420
+    public void popToRoot_popEventIsEmitted() {
421
+        disablePushAnimation(child1, child2, child3);
422
+        disablePopAnimation(child2, child3);
423
+        uut.push(child1, new CommandListenerAdapter());
424
+        uut.push(child2, new CommandListenerAdapter());
425
+        uut.push(child3, new CommandListenerAdapter());
426
+
427
+        uut.pop(Options.EMPTY, new CommandListenerAdapter());
428
+        verify(eventEmitter).emitScreenPoppedEvent(child3.getId());
429
+        verifyNoMoreInteractions(eventEmitter);
430
+    }
431
+
407 432
     @Test
408 433
     public void stackOperations() {
409 434
         assertThat(uut.peek()).isNull();
@@ -957,12 +982,9 @@ public class StackControllerTest extends BaseTest {
957 982
 
958 983
     @Test
959 984
     public void mergeChildOptions_updatesViewWithNewOptions() {
960
-        StackController uut = spy(new StackControllerBuilder(activity)
961
-                        .setTopBarController(new TopBarController())
962
-                        .setId("stack")
963
-                        .setInitialOptions(new Options())
964
-                        .setStackPresenter(new StackPresenter(activity, new TitleBarReactViewCreatorMock(), new TopBarBackgroundViewCreatorMock(), new TitleBarReactViewCreatorMock(), ImageLoaderMock.mock(), new RenderChecker(), Options.EMPTY))
965
-                        .build());
985
+        StackController uut = spy(TestUtils.newStackController(activity)
986
+                .setId("stack")
987
+                .build());
966 988
         Options optionsToMerge = new Options();
967 989
         ViewController vc = mock(ViewController.class);
968 990
         uut.mergeChildOptions(optionsToMerge, vc);
@@ -971,11 +993,8 @@ public class StackControllerTest extends BaseTest {
971 993
 
972 994
     @Test
973 995
     public void mergeChildOptions_updatesParentControllerWithNewOptions() {
974
-        StackController uut = new StackControllerBuilder(activity)
975
-                        .setTopBarController(new TopBarController())
996
+        StackController uut = TestUtils.newStackController(activity)
976 997
                         .setId("stack")
977
-                        .setInitialOptions(new Options())
978
-                        .setStackPresenter(new StackPresenter(activity, new TitleBarReactViewCreatorMock(), new TopBarBackgroundViewCreatorMock(), new TitleBarReactViewCreatorMock(), ImageLoaderMock.mock(), new RenderChecker(), Options.EMPTY))
979 998
                         .build();
980 999
         ParentController parentController = Mockito.mock(ParentController.class);
981 1000
         uut.setParentController(parentController);
@@ -1136,6 +1155,7 @@ public class StackControllerTest extends BaseTest {
1136 1155
     private StackControllerBuilder createStackBuilder(String id, List<ViewController> children) {
1137 1156
         createTopBarController();
1138 1157
         return TestUtils.newStackController(activity)
1158
+                .setEventEmitter(eventEmitter)
1139 1159
                 .setChildren(children)
1140 1160
                 .setId(id)
1141 1161
                 .setTopBarController(topBarController)

+ 1
- 3
lib/ios/RNNCommandsHandler.m Vedi File

@@ -205,10 +205,8 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
205 205
 	UINavigationController *nvc = vc.navigationController;
206 206
 	
207 207
 	if ([nvc topViewController] == vc) {
208
-		if (vc.resolveOptionsWithDefault.animations.pop) {
208
+		if (vc.resolveOptions.animations.pop.hasCustomAnimation) {
209 209
 			nvc.delegate = vc;
210
-		} else {
211
-			nvc.delegate = nil;
212 210
 		}
213 211
 	} else {
214 212
 		NSMutableArray * vcs = nvc.viewControllers.mutableCopy;

+ 0
- 2
lib/ios/RNNComponentViewController.m Vedi File

@@ -11,8 +11,6 @@
11 11
 	
12 12
 	self.animator = [[RNNAnimator alloc] initWithTransitionOptions:self.resolveOptions.customTransition];
13 13
 	
14
-	self.navigationController.delegate = self;
15
-	
16 14
 	return self;
17 15
 }
18 16
 

+ 2
- 1
lib/ios/RNNEventEmitter.h Vedi File

@@ -1,4 +1,3 @@
1
-
2 1
 #import <Foundation/Foundation.h>
3 2
 
4 3
 #import <React/RCTEventEmitter.h>
@@ -26,5 +25,7 @@
26 25
 
27 26
 - (void)sendModalsDismissedEvent:(NSString *)componentId numberOfModalsDismissed:(NSNumber *)modalsDismissed;
28 27
 
28
+- (void)sendScreenPoppedEvent:(NSString *)componentId;
29
+
29 30
 
30 31
 @end

+ 87
- 79
lib/ios/RNNEventEmitter.m Vedi File

@@ -2,8 +2,8 @@
2 2
 #import "RNNUtils.h"
3 3
 
4 4
 @implementation RNNEventEmitter {
5
-	NSInteger _appLaunchedListenerCount;
6
-	BOOL _appLaunchedEventDeferred;
5
+    NSInteger _appLaunchedListenerCount;
6
+    BOOL _appLaunchedEventDeferred;
7 7
 }
8 8
 
9 9
 RCT_EXPORT_MODULE();
@@ -18,115 +18,123 @@ static NSString* const ModalDismissed	        = @"RNN.ModalDismissed";
18 18
 static NSString* const SearchBarUpdated 		= @"RNN.SearchBarUpdated";
19 19
 static NSString* const SearchBarCancelPressed 	= @"RNN.SearchBarCancelPressed";
20 20
 static NSString* const PreviewCompleted         = @"RNN.PreviewCompleted";
21
-
22
--(NSArray<NSString *> *)supportedEvents {
23
-	return @[AppLaunched,
24
-			 CommandCompleted,
25
-			 BottomTabSelected,
26
-			 ComponentDidAppear,
27
-			 ComponentDidDisappear,
28
-			 NavigationButtonPressed,
29
-			 ModalDismissed,
30
-			 SearchBarUpdated,
31
-			 SearchBarCancelPressed,
32
-			 PreviewCompleted];
21
+static NSString* const ScreenPopped             = @"RNN.ScreenPopped";
22
+
23
+- (NSArray<NSString *> *)supportedEvents {
24
+    return @[AppLaunched,
25
+             CommandCompleted,
26
+             BottomTabSelected,
27
+             ComponentDidAppear,
28
+             ComponentDidDisappear,
29
+             NavigationButtonPressed,
30
+             ModalDismissed,
31
+             SearchBarUpdated,
32
+             SearchBarCancelPressed,
33
+             PreviewCompleted,
34
+             ScreenPopped];
33 35
 }
34 36
 
35 37
 # pragma mark public
36 38
 
37
--(void)sendOnAppLaunched {
38
-	if (_appLaunchedListenerCount > 0) {
39
-		[self send:AppLaunched body:nil];
40
-	} else {
41
-		_appLaunchedEventDeferred = TRUE;
42
-	}
39
+- (void)sendOnAppLaunched {
40
+    if (_appLaunchedListenerCount > 0) {
41
+        [self send:AppLaunched body:nil];
42
+    } else {
43
+        _appLaunchedEventDeferred = TRUE;
44
+    }
43 45
 }
44 46
 
45
--(void)sendComponentDidAppear:(NSString *)componentId componentName:(NSString *)componentName {
46
-	[self send:ComponentDidAppear body:@{
47
-										 @"componentId":componentId,
48
-										 @"componentName": componentName
49
-										 }];
47
+- (void)sendComponentDidAppear:(NSString *)componentId componentName:(NSString *)componentName {
48
+    [self send:ComponentDidAppear body:@{
49
+        @"componentId":componentId,
50
+        @"componentName": componentName
51
+    }];
50 52
 }
51 53
 
52
--(void)sendComponentDidDisappear:(NSString *)componentId componentName:(NSString *)componentName{
53
-	[self send:ComponentDidDisappear body:@{
54
-											@"componentId":componentId,
55
-											@"componentName": componentName
56
-											}];
54
+- (void)sendComponentDidDisappear:(NSString *)componentId componentName:(NSString *)componentName{
55
+    [self send:ComponentDidDisappear body:@{
56
+        @"componentId":componentId,
57
+        @"componentName": componentName
58
+    }];
57 59
 }
58 60
 
59
--(void)sendOnNavigationButtonPressed:(NSString *)componentId buttonId:(NSString*)buttonId {
60
-	[self send:NavigationButtonPressed body:@{
61
-											  @"componentId": componentId,
62
-											  @"buttonId": buttonId
63
-											  }];
61
+- (void)sendOnNavigationButtonPressed:(NSString *)componentId buttonId:(NSString*)buttonId {
62
+    [self send:NavigationButtonPressed body:@{
63
+        @"componentId": componentId,
64
+        @"buttonId": buttonId
65
+    }];
64 66
 }
65 67
 
66
--(void)sendBottomTabSelected:(NSNumber *)selectedTabIndex unselected:(NSNumber*)unselectedTabIndex {
67
-	[self send:BottomTabSelected body:@{
68
-									  @"selectedTabIndex": selectedTabIndex,
69
-									  @"unselectedTabIndex": unselectedTabIndex
70
-									  }];
68
+- (void)sendBottomTabSelected:(NSNumber *)selectedTabIndex unselected:(NSNumber*)unselectedTabIndex {
69
+    [self send:BottomTabSelected body:@{
70
+        @"selectedTabIndex": selectedTabIndex,
71
+        @"unselectedTabIndex": unselectedTabIndex
72
+    }];
71 73
 }
72 74
 
73
--(void)sendOnNavigationCommandCompletion:(NSString *)commandName commandId:(NSString *)commandId params:(NSDictionary*)params {
74
-	[self send:CommandCompleted body:@{
75
-									   @"commandId":commandId,
76
-									   @"commandName":commandName,
77
-									   @"params": params,
78
-									   @"completionTime": [RNNUtils getCurrentTimestamp]
79
-									   }];
75
+- (void)sendOnNavigationCommandCompletion:(NSString *)commandName commandId:(NSString *)commandId params:(NSDictionary*)params {
76
+    [self send:CommandCompleted body:@{
77
+        @"commandId":commandId,
78
+        @"commandName":commandName,
79
+        @"params": params,
80
+        @"completionTime": [RNNUtils getCurrentTimestamp]
81
+    }];
80 82
 }
81 83
 
82
--(void)sendOnSearchBarUpdated:(NSString *)componentId
83
-						 text:(NSString*)text
84
-					isFocused:(BOOL)isFocused {
85
-	[self send:SearchBarUpdated body:@{
86
-									   @"componentId": componentId,
87
-									   @"text": text,
88
-									   @"isFocused": @(isFocused)
89
-									   }];
84
+- (void)sendOnSearchBarUpdated:(NSString *)componentId
85
+                          text:(NSString*)text
86
+                     isFocused:(BOOL)isFocused {
87
+    [self send:SearchBarUpdated body:@{
88
+        @"componentId": componentId,
89
+        @"text": text,
90
+        @"isFocused": @(isFocused)
91
+    }];
90 92
 }
91 93
 
92 94
 - (void)sendOnSearchBarCancelPressed:(NSString *)componentId {
93
-	[self send:SearchBarCancelPressed body:@{
94
-											@"componentId": componentId
95
-											}];
95
+    [self send:SearchBarCancelPressed body:@{
96
+        @"componentId": componentId
97
+    }];
96 98
 }
97 99
 
98 100
 - (void)sendOnPreviewCompleted:(NSString *)componentId previewComponentId:(NSString *)previewComponentId {
99
-	[self send:PreviewCompleted body:@{
100
-											 @"componentId": componentId,
101
-											 @"previewComponentId": previewComponentId
102
-                       }];
101
+    [self send:PreviewCompleted body:@{
102
+        @"componentId": componentId,
103
+        @"previewComponentId": previewComponentId
104
+    }];
103 105
 }
104 106
 
105 107
 - (void)sendModalsDismissedEvent:(NSString *)componentId numberOfModalsDismissed:(NSNumber *)modalsDismissed {
106
-	[self send:ModalDismissed body:@{
107
-											 @"componentId": componentId,
108
-											 @"modalsDismissed": modalsDismissed
109
-											 }];
108
+    [self send:ModalDismissed body:@{
109
+        @"componentId": componentId,
110
+        @"modalsDismissed": modalsDismissed
111
+    }];
112
+}
113
+
114
+- (void)sendScreenPoppedEvent:(NSString *)componentId {
115
+    [self send:ScreenPopped body:@{
116
+        @"componentId": componentId
117
+    }];
110 118
 }
111 119
 
112 120
 - (void)addListener:(NSString *)eventName {
113
-	[super addListener:eventName];
114
-	if ([eventName isEqualToString:AppLaunched]) {
115
-		_appLaunchedListenerCount++;
116
-		if (_appLaunchedEventDeferred) {
117
-			_appLaunchedEventDeferred = FALSE;
118
-			[self sendOnAppLaunched];
119
-		}
120
-	}
121
+    [super addListener:eventName];
122
+    if ([eventName isEqualToString:AppLaunched]) {
123
+        _appLaunchedListenerCount++;
124
+        if (_appLaunchedEventDeferred) {
125
+            _appLaunchedEventDeferred = FALSE;
126
+            [self sendOnAppLaunched];
127
+        }
128
+    }
121 129
 }
122 130
 
123 131
 # pragma mark private
124 132
 
125
--(void)send:(NSString *)eventName body:(id)body {
126
-	if (self.bridge == nil) {
127
-		return;
128
-	}
129
-	[self sendEventWithName:eventName body:body];
133
+- (void)send:(NSString *)eventName body:(id)body {
134
+    if (self.bridge == nil) {
135
+        return;
136
+    }
137
+    [self sendEventWithName:eventName body:body];
130 138
 }
131 139
 
132 140
 @end

+ 0
- 2
lib/ios/RNNNavigationStackManager.m Vedi File

@@ -19,8 +19,6 @@ typedef void (^RNNAnimationBlock)(void);
19 19
 
20 20
 	if (animationDelegate) {
21 21
 		nvc.delegate = animationDelegate;
22
-	} else {
23
-		nvc.delegate = nil;
24 22
 	}
25 23
 	
26 24
 	[self performAnimationBlock:^{

+ 33
- 10
lib/ios/RNNStackController.m Vedi File

@@ -1,9 +1,17 @@
1 1
 #import "RNNStackController.h"
2 2
 #import "RNNComponentViewController.h"
3 3
 
4
-@implementation RNNStackController
4
+@implementation RNNStackController {
5
+    UIViewController* _presentedViewController;
6
+}
7
+
8
+- (instancetype)init {
9
+    self = [super init];
10
+    self.delegate = self;
11
+    return self;
12
+}
5 13
 
6
--(void)setDefaultOptions:(RNNNavigationOptions *)defaultOptions {
14
+- (void)setDefaultOptions:(RNNNavigationOptions *)defaultOptions {
7 15
 	[super setDefaultOptions:defaultOptions];
8 16
 	[self.presenter setDefaultOptions:defaultOptions];
9 17
 }
@@ -30,17 +38,32 @@
30 38
 }
31 39
 
32 40
 - (UIViewController *)popViewControllerAnimated:(BOOL)animated {
33
-	if (self.viewControllers.count > 1) {
34
-		UIViewController *controller = self.viewControllers[self.viewControllers.count - 2];
35
-		if ([controller isKindOfClass:[RNNComponentViewController class]]) {
36
-			RNNComponentViewController *rnnController = (RNNComponentViewController *)controller;
37
-			[self.presenter applyOptionsBeforePopping:rnnController.resolveOptions];
38
-		}
39
-	}
40
-	
41
+    [self prepareForPop];
41 42
 	return [super popViewControllerAnimated:animated];
42 43
 }
43 44
 
45
+- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
46
+    if ([self.viewControllers indexOfObject:_presentedViewController] < 0) {
47
+        [self sendScreenPoppedEvent:_presentedViewController];
48
+    }
49
+    
50
+    _presentedViewController = viewController;
51
+}
52
+
53
+- (void)sendScreenPoppedEvent:(UIViewController *)poppedScreen {
54
+    [self.eventEmitter sendScreenPoppedEvent:poppedScreen.layoutInfo.componentId];
55
+}
56
+
57
+- (void)prepareForPop {
58
+    if (self.viewControllers.count > 1) {
59
+        UIViewController *controller = self.viewControllers[self.viewControllers.count - 2];
60
+        if ([controller isKindOfClass:[RNNComponentViewController class]]) {
61
+            RNNComponentViewController *rnnController = (RNNComponentViewController *)controller;
62
+            [self.presenter applyOptionsBeforePopping:rnnController.resolveOptions];
63
+        }
64
+    }
65
+}
66
+
44 67
 - (UIViewController *)childViewControllerForStatusBarStyle {
45 68
 	return self.topViewController;
46 69
 }

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

@@ -6,7 +6,8 @@ import {
6 6
   SearchBarUpdatedEvent,
7 7
   SearchBarCancelPressedEvent,
8 8
   PreviewCompletedEvent,
9
-  ModalDismissedEvent
9
+  ModalDismissedEvent,
10
+  ScreenPoppedEvent
10 11
 } from '../interfaces/ComponentEvents';
11 12
 import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';
12 13
 
@@ -67,4 +68,8 @@ export class NativeEventsReceiver {
67 68
   public registerBottomTabSelectedListener(callback: (data: BottomTabSelectedEvent) => void): EmitterSubscription {
68 69
     return this.emitter.addListener('RNN.BottomTabSelected', callback);
69 70
   }
71
+
72
+  public registerScreenPoppedListener(callback: (event: ScreenPoppedEvent) => void): EmitterSubscription {
73
+    return this.emitter.addListener('RNN.ScreenPopped', callback);
74
+  }
70 75
 }

+ 13
- 0
lib/src/events/ComponentEventsObserver.test.tsx Vedi File

@@ -18,6 +18,7 @@ describe('ComponentEventsObserver', () => {
18 18
   const searchBarCancelPressedFn = jest.fn();
19 19
   const previewCompletedFn = jest.fn();
20 20
   const modalDismissedFn = jest.fn();
21
+  const screenPoppedFn = jest.fn();
21 22
   let subscription: EventSubscription;
22 23
   let uut: ComponentEventsObserver;
23 24
 
@@ -68,6 +69,10 @@ describe('ComponentEventsObserver', () => {
68 69
       previewCompletedFn(event);
69 70
     }
70 71
 
72
+    screenPopped(event: any) {
73
+      screenPoppedFn(event);
74
+    }
75
+
71 76
     render() {
72 77
       return 'Hello';
73 78
     }
@@ -115,6 +120,10 @@ describe('ComponentEventsObserver', () => {
115 120
       previewCompletedFn(event);
116 121
     }
117 122
 
123
+    screenPopped(event: any) {
124
+      screenPoppedFn(event);
125
+    }
126
+
118 127
     render() {
119 128
       return 'Hello';
120 129
     }
@@ -191,6 +200,10 @@ describe('ComponentEventsObserver', () => {
191 200
     expect(previewCompletedFn).toHaveBeenCalledTimes(1);
192 201
     expect(previewCompletedFn).toHaveBeenCalledWith({ componentId: 'myCompId' });
193 202
 
203
+    uut.notifyScreenPopped({ componentId: 'myCompId' });
204
+    expect(screenPoppedFn).toHaveBeenCalledTimes(1);
205
+    expect(screenPoppedFn).toHaveBeenLastCalledWith({ componentId: 'myCompId' })
206
+
194 207
     tree.unmount();
195 208
     expect(willUnmountFn).toHaveBeenCalledTimes(1);
196 209
   });

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

@@ -12,7 +12,8 @@ import {
12 12
   SearchBarCancelPressedEvent,
13 13
   ComponentEvent,
14 14
   PreviewCompletedEvent,
15
-  ModalDismissedEvent
15
+  ModalDismissedEvent,
16
+  ScreenPoppedEvent
16 17
 } from '../interfaces/ComponentEvents';
17 18
 import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver';
18 19
 import { Store } from '../components/Store';
@@ -34,6 +35,7 @@ export class ComponentEventsObserver {
34 35
     this.notifySearchBarUpdated = this.notifySearchBarUpdated.bind(this);
35 36
     this.notifySearchBarCancelPressed = this.notifySearchBarCancelPressed.bind(this);
36 37
     this.notifyPreviewCompleted = this.notifyPreviewCompleted.bind(this);
38
+    this.notifyScreenPopped = this.notifyScreenPopped.bind(this);
37 39
   }
38 40
 
39 41
   public registerOnceForAllComponentEvents() {
@@ -46,6 +48,7 @@ export class ComponentEventsObserver {
46 48
     this.nativeEventsReceiver.registerSearchBarUpdatedListener(this.notifySearchBarUpdated);
47 49
     this.nativeEventsReceiver.registerSearchBarCancelPressedListener(this.notifySearchBarCancelPressed);
48 50
     this.nativeEventsReceiver.registerPreviewCompletedListener(this.notifyPreviewCompleted);
51
+    this.nativeEventsReceiver.registerScreenPoppedListener(this.notifyPreviewCompleted);
49 52
   }
50 53
 
51 54
   public bindComponent(component: React.Component<any>, componentId?: string): EventSubscription {
@@ -96,6 +99,10 @@ export class ComponentEventsObserver {
96 99
     this.triggerOnAllListenersByComponentId(event, 'previewCompleted');
97 100
   }
98 101
 
102
+  notifyScreenPopped(event: ScreenPoppedEvent) {
103
+    this.triggerOnAllListenersByComponentId(event, 'screenPopped');
104
+  }
105
+
99 106
   private triggerOnAllListenersByComponentId(event: ComponentEvent, method: string) {
100 107
     forEach(this.listeners[event.componentId], (component) => {
101 108
       if (component && component[method]) {

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

@@ -112,4 +112,11 @@ describe('EventsRegistry', () => {
112 112
     mockScreenEventsRegistry.bindComponent.mockReturnValueOnce(subscription);
113 113
     expect(uut.bindComponent({} as React.Component<any>)).toEqual(subscription);
114 114
   });
115
+
116
+  it('delegates screenPopped to nativeEventsReceiver', () => {
117
+    const cb = jest.fn();
118
+    uut.registerScreenPoppedListener(cb);
119
+    expect(mockNativeEventsReceiver.registerScreenPoppedListener).toHaveBeenCalledTimes(1);
120
+    expect(mockNativeEventsReceiver.registerScreenPoppedListener).toHaveBeenCalledWith(cb);
121
+  });
115 122
 });

+ 7
- 1
lib/src/events/EventsRegistry.ts Vedi File

@@ -11,7 +11,8 @@ import {
11 11
   SearchBarUpdatedEvent,
12 12
   SearchBarCancelPressedEvent,
13 13
   PreviewCompletedEvent,
14
-  ModalDismissedEvent
14
+  ModalDismissedEvent,
15
+  ScreenPoppedEvent
15 16
 } from '../interfaces/ComponentEvents';
16 17
 import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';
17 18
 
@@ -65,4 +66,9 @@ export class EventsRegistry {
65 66
   public bindComponent(component: React.Component<any>, componentId?: string): EventSubscription {
66 67
     return this.componentEventsObserver.bindComponent(component, componentId);
67 68
   }
69
+
70
+  public registerScreenPoppedListener(callback: (event: ScreenPoppedEvent) => void): EmitterSubscription {
71
+    return this.nativeEventsReceiver.registerScreenPoppedListener(callback);
72
+  }
73
+
68 74
 }

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

@@ -33,3 +33,7 @@ export interface PreviewCompletedEvent extends ComponentEvent {
33 33
   componentName?: string;
34 34
   previewComponentId?: string;
35 35
 }
36
+
37
+export interface ScreenPoppedEvent extends ComponentEvent {
38
+  componentId: string;
39
+}

+ 12
- 3
playground/ios/NavigationTests/RNNNavigationControllerTest.m Vedi File

@@ -17,17 +17,19 @@
17 17
 	UIViewController* _vc3;
18 18
 	RNNNavigationOptions* _options;
19 19
 	RNNTestRootViewCreator* _creator;
20
+	RNNEventEmitter* _eventEmitter;
20 21
 }
21 22
 
22 23
 - (void)setUp {
23 24
     [super setUp];
25
+	_eventEmitter = [OCMockObject niceMockForClass:[RNNEventEmitter class]];
24 26
 	_creator = [[RNNTestRootViewCreator alloc] init];
25 27
 	_vc1 = [[RNNComponentViewController alloc] initWithLayoutInfo:nil rootViewCreator:nil eventEmitter:nil presenter:[OCMockObject partialMockForObject:[[RNNComponentPresenter alloc] init]] options:[[RNNNavigationOptions alloc] initEmptyOptions] defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions]];
26 28
 	_vc2 = [[RNNComponentViewController alloc] initWithLayoutInfo:nil rootViewCreator:nil eventEmitter:nil presenter:[[RNNComponentPresenter alloc] init] options:[[RNNNavigationOptions alloc] initEmptyOptions] defaultOptions:[[RNNNavigationOptions alloc] initEmptyOptions]];
27 29
 	_vc2Mock = [OCMockObject partialMockForObject:_vc2];
28 30
 	_vc3 = [UIViewController new];
29 31
 	_options = [OCMockObject partialMockForObject:[[RNNNavigationOptions alloc] initEmptyOptions]];
30
-	self.uut = [[RNNStackController alloc] initWithLayoutInfo:nil creator:_creator options:_options defaultOptions:nil presenter:[OCMockObject partialMockForObject:[[RNNStackPresenter alloc] init]] eventEmitter:nil childViewControllers:@[_vc1, _vc2]];
32
+	self.uut = [[RNNStackController alloc] initWithLayoutInfo:nil creator:_creator options:_options defaultOptions:nil presenter:[OCMockObject partialMockForObject:[[RNNStackPresenter alloc] init]] eventEmitter:_eventEmitter childViewControllers:@[_vc1, _vc2]];
31 33
 }
32 34
 
33 35
 - (void)testInitWithLayoutInfo_shouldBindPresenter {
@@ -139,12 +141,19 @@
139 141
 	[uut setViewControllers:@[_vc1, _vc2]];
140 142
 	
141 143
 	[[(id)uut.presenter expect] applyOptionsBeforePopping:[OCMArg any]];
142
-	
143 144
 	[uut popViewControllerAnimated:NO];
144
-	
145 145
 	[(id)uut.presenter verify];
146 146
 }
147 147
 
148
+- (void)testPopViewController_ShouldEmitScreenPoppedEvent {
149
+	RNNStackController* uut = [RNNStackController new];
150
+	[uut setViewControllers:@[_vc1, _vc2]];
151
+	
152
+	[[(id)uut.eventEmitter expect] sendScreenPoppedEvent:_vc2.layoutInfo.componentId];
153
+	[uut popViewControllerAnimated:NO];
154
+	[(id)uut.eventEmitter verify];
155
+}
156
+
148 157
 - (void)testOverrideOptionsShouldOverrideOptionsState {
149 158
 	RNNNavigationOptions* overrideOptions = [[RNNNavigationOptions alloc] initEmptyOptions];
150 159
 	[(RNNNavigationOptions*)[(id)self.uut.options expect] overrideOptions:overrideOptions];