Browse Source

Global screen visibility listener (#1575)

* Add screen visibility listener which can be registered globally

API
new ScreenVisibilityListener({
    willAppear: ({screen, timestamp}) => console.log(`Displaying screen ${screen}`),
    didAppear: ({screen, timestamp}) => console.log(`Screen ${screen} displayed in ${Date.now() - timestamp} millis`)
  }).register();

todo
* Add timestamp to modal, resetTo
* implement unregister
* implement iOS

* Add global screen visibility listener to example app

* Think I better assign a value to emitter

* Send timestamp in resetTo

* Moved listener to library, it can be deleted from the app

* Add push and resetTo buttons to pushed screens

* Send appear/disappear events after animation ends

* Pass pop timestamp from js

* Send timestamp in popToRoot

* Add popToRoot button in push screen

* Add few navigation buttons to modal screen

* Pass timestamp to tabs

relevant only to first/initial tab

* Update timestamp when navigating between BottomTabs

* Send screen visible events after modal dismiss

* log willDisappear and didDisappear

* Report both start and end timestamps

* Implement unregister

* Send NavigationType parameter in visibilityEvent

This indicates the type of the navigation command that triggered the visibility change

* Rename pushInitialScreen to more explicit name

* Fix duplicate initialScreen visibility events

* Update screen visibility log statement

* Show popToRoot in modal screen only if screens were pushed

* implement global screen event for iOS

* fix lint issues
Ran Greenberg 7 years ago
parent
commit
f59f432901
30 changed files with 431 additions and 110 deletions
  1. 32
    1
      android/app/src/main/java/com/reactnativenavigation/bridge/EventEmitter.java
  2. 3
    2
      android/app/src/main/java/com/reactnativenavigation/controllers/Modal.java
  3. 7
    6
      android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java
  4. 15
    11
      android/app/src/main/java/com/reactnativenavigation/layouts/BottomTabsLayout.java
  5. 1
    1
      android/app/src/main/java/com/reactnativenavigation/layouts/ModalScreenLayout.java
  6. 8
    4
      android/app/src/main/java/com/reactnativenavigation/layouts/SingleScreenLayout.java
  7. 1
    0
      android/app/src/main/java/com/reactnativenavigation/params/BaseScreenParams.java
  8. 1
    3
      android/app/src/main/java/com/reactnativenavigation/params/SideMenuParams.java
  9. 2
    0
      android/app/src/main/java/com/reactnativenavigation/params/parsers/ScreenParamsParser.java
  10. 12
    0
      android/app/src/main/java/com/reactnativenavigation/screens/NavigationType.java
  11. 34
    19
      android/app/src/main/java/com/reactnativenavigation/screens/Screen.java
  12. 30
    37
      android/app/src/main/java/com/reactnativenavigation/screens/ScreenStack.java
  13. 15
    4
      android/app/src/main/java/com/reactnativenavigation/views/SideMenu.java
  14. 3
    1
      example/src/app.js
  15. 2
    2
      example/src/screens/Types.js
  16. 11
    2
      example/src/screens/index.js
  17. 43
    3
      example/src/screens/types/Modal.js
  18. 44
    3
      example/src/screens/types/Push.js
  19. 1
    0
      ios/Helpers/RCTHelpers.h
  20. 4
    0
      ios/Helpers/RCTHelpers.m
  21. 11
    2
      ios/RCCManagerModule.m
  22. 5
    2
      ios/RCCNavigationController.m
  23. 11
    0
      ios/RCCTabBarController.m
  24. 10
    0
      ios/RCCViewController.h
  25. 69
    1
      ios/RCCViewController.m
  26. 37
    0
      src/ScreenVisibilityListener.js
  27. 3
    1
      src/deprecated/indexDeprecated.android.js
  28. 3
    1
      src/deprecated/indexDeprecated.ios.js
  29. 6
    1
      src/deprecated/platformSpecificDeprecated.android.js
  30. 7
    3
      src/deprecated/platformSpecificDeprecated.ios.js

+ 32
- 1
android/app/src/main/java/com/reactnativenavigation/bridge/EventEmitter.java View File

3
 import com.facebook.react.bridge.Arguments;
3
 import com.facebook.react.bridge.Arguments;
4
 import com.facebook.react.bridge.WritableMap;
4
 import com.facebook.react.bridge.WritableMap;
5
 import com.reactnativenavigation.NavigationApplication;
5
 import com.reactnativenavigation.NavigationApplication;
6
+import com.reactnativenavigation.params.BaseScreenParams;
6
 import com.reactnativenavigation.react.ReactGateway;
7
 import com.reactnativenavigation.react.ReactGateway;
8
+import com.reactnativenavigation.screens.NavigationType;
7
 
9
 
8
 public class EventEmitter {
10
 public class EventEmitter {
9
     private ReactGateway reactGateway;
11
     private ReactGateway reactGateway;
12
         this.reactGateway = reactGateway;
14
         this.reactGateway = reactGateway;
13
     }
15
     }
14
 
16
 
15
-    public void sendScreenChangedEvent(String eventId, String navigatorEventId) {
17
+    public void sendWillAppearEvent(BaseScreenParams params, NavigationType type) {
18
+        sendScreenChangedEventToJsScreen("willAppear", params.getNavigatorEventId());
19
+        sendGlobalScreenChangedEvent("willAppear", params.timestamp, params.screenId, type);
20
+    }
21
+
22
+    public void sendDidAppearEvent(BaseScreenParams params, NavigationType type) {
23
+        sendScreenChangedEventToJsScreen("didAppear", params.getNavigatorEventId());
24
+        sendGlobalScreenChangedEvent("didAppear", params.timestamp, params.screenId, type);
25
+    }
26
+
27
+    public void sendWillDisappearEvent(BaseScreenParams params, NavigationType type) {
28
+        sendScreenChangedEventToJsScreen("willDisappear", params.getNavigatorEventId());
29
+        sendGlobalScreenChangedEvent("willDisappear", params.timestamp, params.screenId, type);
30
+    }
31
+
32
+    public void sendDidDisappearEvent(BaseScreenParams params, NavigationType type) {
33
+        sendScreenChangedEventToJsScreen("didDisappear", params.getNavigatorEventId());
34
+        sendGlobalScreenChangedEvent("didDisappear", params.timestamp, params.screenId, type);
35
+    }
36
+
37
+    private void sendScreenChangedEventToJsScreen(String eventId, String navigatorEventId) {
16
         WritableMap map = Arguments.createMap();
38
         WritableMap map = Arguments.createMap();
17
         map.putString("type", "ScreenChangedEvent");
39
         map.putString("type", "ScreenChangedEvent");
18
         sendNavigatorEvent(eventId, navigatorEventId, map);
40
         sendNavigatorEvent(eventId, navigatorEventId, map);
19
     }
41
     }
20
 
42
 
43
+    private void sendGlobalScreenChangedEvent(String eventId, double timestamp, String screenId, NavigationType type) {
44
+        WritableMap map = Arguments.createMap();
45
+        map.putDouble("startTime", timestamp);
46
+        map.putDouble("endTime", System.currentTimeMillis());
47
+        map.putString("screen", screenId);
48
+        map.putString("commandType", String.valueOf(type));
49
+        sendNavigatorEvent(eventId, map);
50
+    }
51
+
21
     public void sendNavigatorEvent(String eventId, String navigatorEventId) {
52
     public void sendNavigatorEvent(String eventId, String navigatorEventId) {
22
         if (!NavigationApplication.instance.isReactContextInitialized()) {
53
         if (!NavigationApplication.instance.isReactContextInitialized()) {
23
             return;
54
             return;

+ 3
- 2
android/app/src/main/java/com/reactnativenavigation/controllers/Modal.java View File

21
 import com.reactnativenavigation.params.SlidingOverlayParams;
21
 import com.reactnativenavigation.params.SlidingOverlayParams;
22
 import com.reactnativenavigation.params.TitleBarButtonParams;
22
 import com.reactnativenavigation.params.TitleBarButtonParams;
23
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
23
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
24
+import com.reactnativenavigation.screens.NavigationType;
24
 
25
 
25
 import java.util.List;
26
 import java.util.List;
26
 
27
 
177
     @Override
178
     @Override
178
     public void dismiss() {
179
     public void dismiss() {
179
         if (!isDestroyed) {
180
         if (!isDestroyed) {
180
-            NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willDisappear", layout.getCurrentScreen().getNavigatorEventId());
181
-            NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didDisappear", layout.getCurrentScreen().getNavigatorEventId());
181
+            NavigationApplication.instance.getEventEmitter().sendWillDisappearEvent(layout.getCurrentScreen().getScreenParams(), NavigationType.DismissModal);
182
+            NavigationApplication.instance.getEventEmitter().sendDidDisappearEvent(layout.getCurrentScreen().getScreenParams(), NavigationType.DismissModal);
182
         }
183
         }
183
         super.dismiss();
184
         super.dismiss();
184
     }
185
     }

+ 7
- 6
android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java View File

35
 import com.reactnativenavigation.params.TitleBarButtonParams;
35
 import com.reactnativenavigation.params.TitleBarButtonParams;
36
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
36
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
37
 import com.reactnativenavigation.react.ReactGateway;
37
 import com.reactnativenavigation.react.ReactGateway;
38
+import com.reactnativenavigation.screens.NavigationType;
38
 import com.reactnativenavigation.screens.Screen;
39
 import com.reactnativenavigation.screens.Screen;
39
 import com.reactnativenavigation.utils.OrientationHelper;
40
 import com.reactnativenavigation.utils.OrientationHelper;
40
 import com.reactnativenavigation.views.SideMenu.Side;
41
 import com.reactnativenavigation.views.SideMenu.Side;
237
 
238
 
238
     void showModal(ScreenParams screenParams) {
239
     void showModal(ScreenParams screenParams) {
239
         Screen previousScreen = layout.getCurrentScreen();
240
         Screen previousScreen = layout.getCurrentScreen();
240
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willDisappear", previousScreen.getNavigatorEventId());
241
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didDisappear", previousScreen.getNavigatorEventId());
241
+        NavigationApplication.instance.getEventEmitter().sendWillDisappearEvent(previousScreen.getScreenParams(), NavigationType.ShowModal);
242
+        NavigationApplication.instance.getEventEmitter().sendDidDisappearEvent(previousScreen.getScreenParams(), NavigationType.ShowModal);
242
         modalController.showModal(screenParams);
243
         modalController.showModal(screenParams);
243
     }
244
     }
244
 
245
 
245
     void dismissTopModal() {
246
     void dismissTopModal() {
246
         modalController.dismissTopModal();
247
         modalController.dismissTopModal();
247
         Screen previousScreen = layout.getCurrentScreen();
248
         Screen previousScreen = layout.getCurrentScreen();
248
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", previousScreen.getNavigatorEventId());
249
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", previousScreen.getNavigatorEventId());
249
+        NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(previousScreen.getScreenParams(), NavigationType.DismissModal);
250
+        NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(previousScreen.getScreenParams(), NavigationType.DismissModal);
250
     }
251
     }
251
 
252
 
252
     void dismissAllModals() {
253
     void dismissAllModals() {
253
         modalController.dismissAllModals();
254
         modalController.dismissAllModals();
254
         Screen previousScreen = layout.getCurrentScreen();
255
         Screen previousScreen = layout.getCurrentScreen();
255
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", previousScreen.getNavigatorEventId());
256
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", previousScreen.getNavigatorEventId());
256
+        NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(previousScreen.getScreenParams(), NavigationType.DismissModal);
257
+        NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(previousScreen.getScreenParams(), NavigationType.DismissModal);
257
     }
258
     }
258
 
259
 
259
     public void showLightBox(LightBoxParams params) {
260
     public void showLightBox(LightBoxParams params) {

+ 15
- 11
android/app/src/main/java/com/reactnativenavigation/layouts/BottomTabsLayout.java View File

27
 import com.reactnativenavigation.params.StyleParams;
27
 import com.reactnativenavigation.params.StyleParams;
28
 import com.reactnativenavigation.params.TitleBarButtonParams;
28
 import com.reactnativenavigation.params.TitleBarButtonParams;
29
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
29
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
30
+import com.reactnativenavigation.screens.NavigationType;
30
 import com.reactnativenavigation.screens.Screen;
31
 import com.reactnativenavigation.screens.Screen;
31
 import com.reactnativenavigation.screens.ScreenStack;
32
 import com.reactnativenavigation.screens.ScreenStack;
32
 import com.reactnativenavigation.utils.ViewUtils;
33
 import com.reactnativenavigation.utils.ViewUtils;
132
     }
133
     }
133
 
134
 
134
     private void showInitialScreenStack() {
135
     private void showInitialScreenStack() {
135
-        showStackAndUpdateStyle(screenStacks[0]);
136
+        showStackAndUpdateStyle(screenStacks[0], NavigationType.InitialScreen);
136
         EventBus.instance.post(new ScreenChangedEvent(screenStacks[0].peek().getScreenParams()));
137
         EventBus.instance.post(new ScreenChangedEvent(screenStacks[0].peek().getScreenParams()));
137
     }
138
     }
138
 
139
 
148
         }
149
         }
149
 
150
 
150
         if (getCurrentScreenStack().canPop()) {
151
         if (getCurrentScreenStack().canPop()) {
151
-            getCurrentScreenStack().pop(true);
152
+            getCurrentScreenStack().pop(true, System.currentTimeMillis());
152
             setBottomTabsStyleFromCurrentScreen();
153
             setBottomTabsStyleFromCurrentScreen();
153
             EventBus.instance.post(new ScreenChangedEvent(getCurrentScreenStack().peek().getScreenParams()));
154
             EventBus.instance.post(new ScreenChangedEvent(getCurrentScreenStack().peek().getScreenParams()));
154
             return true;
155
             return true;
289
 
290
 
290
     @Override
291
     @Override
291
     public void onModalDismissed() {
292
     public void onModalDismissed() {
293
+        getCurrentScreenStack().peek().getScreenParams().timestamp = System.currentTimeMillis();
294
+        NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(getCurrentScreenStack().peek().getScreenParams(), NavigationType.DismissModal);
295
+        NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(getCurrentScreenStack().peek().getScreenParams(), NavigationType.DismissModal);
292
         EventBus.instance.post(new ScreenChangedEvent(getCurrentScreenStack().peek().getScreenParams()));
296
         EventBus.instance.post(new ScreenChangedEvent(getCurrentScreenStack().peek().getScreenParams()));
293
     }
297
     }
294
 
298
 
348
 
352
 
349
     @Override
353
     @Override
350
     public void pop(final ScreenParams params) {
354
     public void pop(final ScreenParams params) {
351
-        getCurrentScreenStack().pop(params.animateScreenTransitions, new ScreenStack.OnScreenPop() {
355
+        getCurrentScreenStack().pop(params.animateScreenTransitions, params.timestamp, new ScreenStack.OnScreenPop() {
352
             @Override
356
             @Override
353
             public void onScreenPopAnimationEnd() {
357
             public void onScreenPopAnimationEnd() {
354
                 setBottomTabsStyleFromCurrentScreen();
358
                 setBottomTabsStyleFromCurrentScreen();
360
 
364
 
361
     @Override
365
     @Override
362
     public void popToRoot(final ScreenParams params) {
366
     public void popToRoot(final ScreenParams params) {
363
-        getCurrentScreenStack().popToRoot(params.animateScreenTransitions, new ScreenStack.OnScreenPop() {
367
+        getCurrentScreenStack().popToRoot(params.animateScreenTransitions, params.timestamp, new ScreenStack.OnScreenPop() {
364
             @Override
368
             @Override
365
             public void onScreenPopAnimationEnd() {
369
             public void onScreenPopAnimationEnd() {
366
                 setBottomTabsStyleFromCurrentScreen();
370
                 setBottomTabsStyleFromCurrentScreen();
414
 
418
 
415
         final int unselectedTabIndex = currentStackIndex;
419
         final int unselectedTabIndex = currentStackIndex;
416
         hideCurrentStack();
420
         hideCurrentStack();
417
-        showNewStack(position);
421
+        showNewStack(position, NavigationType.BottomTabSelected);
418
         EventBus.instance.post(new ScreenChangedEvent(getCurrentScreenStack().peek().getScreenParams()));
422
         EventBus.instance.post(new ScreenChangedEvent(getCurrentScreenStack().peek().getScreenParams()));
419
         sendTabSelectedEventToJs(position, unselectedTabIndex);
423
         sendTabSelectedEventToJs(position, unselectedTabIndex);
420
         return true;
424
         return true;
442
         NavigationApplication.instance.getEventEmitter().sendNavigatorEvent("bottomTabReselected", navigatorEventId, data);
446
         NavigationApplication.instance.getEventEmitter().sendNavigatorEvent("bottomTabReselected", navigatorEventId, data);
443
     }
447
     }
444
 
448
 
445
-    private void showNewStack(int position) {
446
-        showStackAndUpdateStyle(screenStacks[position]);
449
+    private void showNewStack(int position, NavigationType type) {
450
+        showStackAndUpdateStyle(screenStacks[position], type);
447
         currentStackIndex = position;
451
         currentStackIndex = position;
448
     }
452
     }
449
 
453
 
450
-    private void showStackAndUpdateStyle(ScreenStack newStack) {
451
-        newStack.show();
454
+    private void showStackAndUpdateStyle(ScreenStack newStack, NavigationType type) {
455
+        newStack.show(type);
452
         setStyleFromScreen(newStack.getCurrentScreenStyleParams());
456
         setStyleFromScreen(newStack.getCurrentScreenStyleParams());
453
     }
457
     }
454
 
458
 
455
     private void hideCurrentStack() {
459
     private void hideCurrentStack() {
456
         ScreenStack currentScreenStack = getCurrentScreenStack();
460
         ScreenStack currentScreenStack = getCurrentScreenStack();
457
-        currentScreenStack.hide();
461
+        currentScreenStack.hide(NavigationType.BottomTabSelected);
458
     }
462
     }
459
 
463
 
460
     private ScreenStack getCurrentScreenStack() {
464
     private ScreenStack getCurrentScreenStack() {
510
     @Override
514
     @Override
511
     public boolean onTitleBarBackButtonClick() {
515
     public boolean onTitleBarBackButtonClick() {
512
         if (getCurrentScreenStack().canPop()) {
516
         if (getCurrentScreenStack().canPop()) {
513
-            getCurrentScreenStack().pop(true, new ScreenStack.OnScreenPop() {
517
+            getCurrentScreenStack().pop(true, System.currentTimeMillis(), new ScreenStack.OnScreenPop() {
514
                 @Override
518
                 @Override
515
                 public void onScreenPopAnimationEnd() {
519
                 public void onScreenPopAnimationEnd() {
516
                     setBottomTabsStyleFromCurrentScreen();
520
                     setBottomTabsStyleFromCurrentScreen();

+ 1
- 1
android/app/src/main/java/com/reactnativenavigation/layouts/ModalScreenLayout.java View File

16
 
16
 
17
     @Override
17
     @Override
18
     protected void pushInitialScreen(LayoutParams lp) {
18
     protected void pushInitialScreen(LayoutParams lp) {
19
-        stack.pushInitialScreenWithAnimation(screenParams, lp);
19
+        stack.pushInitialModalScreenWithAnimation(screenParams, lp);
20
     }
20
     }
21
 }
21
 }

+ 8
- 4
android/app/src/main/java/com/reactnativenavigation/layouts/SingleScreenLayout.java View File

19
 import com.reactnativenavigation.params.SnackbarParams;
19
 import com.reactnativenavigation.params.SnackbarParams;
20
 import com.reactnativenavigation.params.TitleBarButtonParams;
20
 import com.reactnativenavigation.params.TitleBarButtonParams;
21
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
21
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
22
+import com.reactnativenavigation.screens.NavigationType;
22
 import com.reactnativenavigation.screens.Screen;
23
 import com.reactnativenavigation.screens.Screen;
23
 import com.reactnativenavigation.screens.ScreenStack;
24
 import com.reactnativenavigation.screens.ScreenStack;
24
 import com.reactnativenavigation.views.LeftButtonOnClickListener;
25
 import com.reactnativenavigation.views.LeftButtonOnClickListener;
87
 
88
 
88
     protected void pushInitialScreen(LayoutParams lp) {
89
     protected void pushInitialScreen(LayoutParams lp) {
89
         stack.pushInitialScreen(screenParams, lp);
90
         stack.pushInitialScreen(screenParams, lp);
90
-        stack.show();
91
+        stack.show(NavigationType.Push);
91
     }
92
     }
92
 
93
 
93
     private void sendScreenChangedEventAfterInitialPush() {
94
     private void sendScreenChangedEventAfterInitialPush() {
113
         }
114
         }
114
 
115
 
115
         if (stack.canPop()) {
116
         if (stack.canPop()) {
116
-            stack.pop(true);
117
+            stack.pop(true, System.currentTimeMillis());
117
             EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
118
             EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
118
             return true;
119
             return true;
119
         } else {
120
         } else {
145
 
146
 
146
     @Override
147
     @Override
147
     public void pop(ScreenParams params) {
148
     public void pop(ScreenParams params) {
148
-        stack.pop(params.animateScreenTransitions, new ScreenStack.OnScreenPop() {
149
+        stack.pop(params.animateScreenTransitions, params.timestamp, new ScreenStack.OnScreenPop() {
149
             @Override
150
             @Override
150
             public void onScreenPopAnimationEnd() {
151
             public void onScreenPopAnimationEnd() {
151
                 EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
152
                 EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
155
 
156
 
156
     @Override
157
     @Override
157
     public void popToRoot(ScreenParams params) {
158
     public void popToRoot(ScreenParams params) {
158
-        stack.popToRoot(params.animateScreenTransitions, new ScreenStack.OnScreenPop() {
159
+        stack.popToRoot(params.animateScreenTransitions, params.timestamp, new ScreenStack.OnScreenPop() {
159
             @Override
160
             @Override
160
             public void onScreenPopAnimationEnd() {
161
             public void onScreenPopAnimationEnd() {
161
                 EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
162
                 EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
285
 
286
 
286
     @Override
287
     @Override
287
     public void onModalDismissed() {
288
     public void onModalDismissed() {
289
+        stack.peek().getScreenParams().timestamp = System.currentTimeMillis();
290
+        NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(stack.peek().getScreenParams(), NavigationType.DismissModal);
291
+        NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(stack.peek().getScreenParams(), NavigationType.DismissModal);
288
         EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
292
         EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
289
     }
293
     }
290
 
294
 

+ 1
- 0
android/app/src/main/java/com/reactnativenavigation/params/BaseScreenParams.java View File

6
 import java.util.List;
6
 import java.util.List;
7
 
7
 
8
 public class BaseScreenParams {
8
 public class BaseScreenParams {
9
+    public double timestamp;
9
     public String screenId;
10
     public String screenId;
10
     public String title;
11
     public String title;
11
     public String subtitle;
12
     public String subtitle;

+ 1
- 3
android/app/src/main/java/com/reactnativenavigation/params/SideMenuParams.java View File

2
 
2
 
3
 import com.reactnativenavigation.views.SideMenu;
3
 import com.reactnativenavigation.views.SideMenu;
4
 
4
 
5
-public class SideMenuParams {
6
-    public String screenId;
7
-    public NavigationParams navigationParams;
5
+public class SideMenuParams extends BaseScreenParams {
8
     public boolean disableOpenGesture;
6
     public boolean disableOpenGesture;
9
     public SideMenu.Side side;
7
     public SideMenu.Side side;
10
 }
8
 }

+ 2
- 0
android/app/src/main/java/com/reactnativenavigation/params/parsers/ScreenParamsParser.java View File

15
     private static final String KEY_TITLE = "title";
15
     private static final String KEY_TITLE = "title";
16
     private static final String KEY_SUBTITLE = "subtitle";
16
     private static final String KEY_SUBTITLE = "subtitle";
17
     private static final String KEY_SCREEN_ID = "screenId";
17
     private static final String KEY_SCREEN_ID = "screenId";
18
+    private static final String KEY_TIMESTAMP = "timestamp";
18
     private static final String KEY_NAVIGATION_PARAMS = "navigationParams";
19
     private static final String KEY_NAVIGATION_PARAMS = "navigationParams";
19
     private static final String STYLE_PARAMS = "styleParams";
20
     private static final String STYLE_PARAMS = "styleParams";
20
     private static final String TOP_TABS = "topTabs";
21
     private static final String TOP_TABS = "topTabs";
27
     public static ScreenParams parse(Bundle params) {
28
     public static ScreenParams parse(Bundle params) {
28
         ScreenParams result = new ScreenParams();
29
         ScreenParams result = new ScreenParams();
29
         result.screenId = params.getString(KEY_SCREEN_ID);
30
         result.screenId = params.getString(KEY_SCREEN_ID);
31
+        result.timestamp = params.getDouble(KEY_TIMESTAMP);
30
         assertKeyExists(params, KEY_NAVIGATION_PARAMS);
32
         assertKeyExists(params, KEY_NAVIGATION_PARAMS);
31
         result.navigationParams = new NavigationParams(params.getBundle(KEY_NAVIGATION_PARAMS));
33
         result.navigationParams = new NavigationParams(params.getBundle(KEY_NAVIGATION_PARAMS));
32
 
34
 

+ 12
- 0
android/app/src/main/java/com/reactnativenavigation/screens/NavigationType.java View File

1
+package com.reactnativenavigation.screens;
2
+
3
+public enum NavigationType {
4
+    Push,
5
+    Pop,
6
+    BottomTabSelected,
7
+    InitialScreen,
8
+    ShowModal,
9
+    DismissModal,
10
+    OpenSideMenu,
11
+    CloseSideMenu
12
+}

+ 34
- 19
android/app/src/main/java/com/reactnativenavigation/screens/Screen.java View File

251
 
251
 
252
     public abstract void setOnDisplayListener(OnDisplayListener onContentViewDisplayedListener);
252
     public abstract void setOnDisplayListener(OnDisplayListener onContentViewDisplayedListener);
253
 
253
 
254
-    public void show() {
255
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", screenParams.getNavigatorEventId());
256
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", screenParams.getNavigatorEventId());
254
+    public void show(NavigationType type) {
255
+        NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(screenParams, type);
256
+        NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(screenParams, type);
257
         screenAnimator.show(screenParams.animateScreenTransitions);
257
         screenAnimator.show(screenParams.animateScreenTransitions);
258
     }
258
     }
259
 
259
 
260
-    public void show(boolean animated) {
261
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", screenParams.getNavigatorEventId());
262
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", screenParams.getNavigatorEventId());
263
-        screenAnimator.show(animated);
260
+    public void show(boolean animated, final NavigationType type) {
261
+        NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(screenParams, type);
262
+        screenAnimator.show(animated, new Runnable() {
263
+            @Override
264
+            public void run() {
265
+                NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(screenParams, type);
266
+            }
267
+        });
264
     }
268
     }
265
 
269
 
266
-    public void show(boolean animated, Runnable onAnimationEnd) {
267
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", screenParams.getNavigatorEventId());
268
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", screenParams.getNavigatorEventId());
270
+    public void show(boolean animated, final Runnable onAnimationEnd, final NavigationType type) {
271
+        NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(screenParams, type);
269
         setStyle();
272
         setStyle();
270
-        screenAnimator.show(animated, onAnimationEnd);
273
+        screenAnimator.show(animated, new Runnable() {
274
+            @Override
275
+            public void run() {
276
+                NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(screenParams, type);
277
+                if (onAnimationEnd != null) onAnimationEnd.run();
278
+            }
279
+        });
271
     }
280
     }
272
 
281
 
273
     public void showWithSharedElementsTransitions(Map<String, SharedElementTransition> fromElements, final Runnable onAnimationEnd) {
282
     public void showWithSharedElementsTransitions(Map<String, SharedElementTransition> fromElements, final Runnable onAnimationEnd) {
282
         screenAnimator.hideWithSharedElementsTransition(onAnimationEnd);
291
         screenAnimator.hideWithSharedElementsTransition(onAnimationEnd);
283
     }
292
     }
284
 
293
 
285
-    public void hide(Map<String, SharedElementTransition> sharedElements, Runnable onAnimationEnd) {
294
+    public void hide(Map<String, SharedElementTransition> sharedElements, Runnable onAnimationEnd, NavigationType type) {
286
         removeHiddenSharedElements();
295
         removeHiddenSharedElements();
287
         if (hasVisibleSharedElements()) {
296
         if (hasVisibleSharedElements()) {
288
             hideWithSharedElementTransitions(sharedElements, onAnimationEnd);
297
             hideWithSharedElementTransitions(sharedElements, onAnimationEnd);
289
         } else {
298
         } else {
290
-            hide(false, onAnimationEnd);
299
+            hide(false, onAnimationEnd, type);
291
         }
300
         }
292
     }
301
     }
293
 
302
 
294
-    public void animateHide(Map<String, SharedElementTransition> sharedElements, Runnable onAnimationEnd) {
303
+    public void animateHide(Map<String, SharedElementTransition> sharedElements, Runnable onAnimationEnd, NavigationType type) {
295
         removeHiddenSharedElements();
304
         removeHiddenSharedElements();
296
         if (hasVisibleSharedElements()) {
305
         if (hasVisibleSharedElements()) {
297
             hideWithSharedElementTransitions(sharedElements, onAnimationEnd);
306
             hideWithSharedElementTransitions(sharedElements, onAnimationEnd);
298
         } else {
307
         } else {
299
-            hide(true, onAnimationEnd);
308
+            hide(true, onAnimationEnd, type);
300
         }
309
         }
301
     }
310
     }
302
 
311
 
312
+    @SuppressWarnings("SimplifiableIfStatement")
303
     private boolean hasVisibleSharedElements() {
313
     private boolean hasVisibleSharedElements() {
304
         if (screenParams.sharedElementsTransitions.isEmpty()) {
314
         if (screenParams.sharedElementsTransitions.isEmpty()) {
305
             return false;
315
             return false;
311
         sharedElements.removeHiddenElements();
321
         sharedElements.removeHiddenElements();
312
     }
322
     }
313
 
323
 
314
-    private void hide(boolean animated, Runnable onAnimatedEnd) {
315
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willDisappear", screenParams.getNavigatorEventId());
316
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didDisappear", screenParams.getNavigatorEventId());
317
-        screenAnimator.hide(animated, onAnimatedEnd);
324
+    private void hide(boolean animated, final Runnable onAnimatedEnd, final NavigationType type) {
325
+        NavigationApplication.instance.getEventEmitter().sendWillDisappearEvent(screenParams, type);
326
+        screenAnimator.hide(animated, new Runnable() {
327
+            @Override
328
+            public void run() {
329
+                NavigationApplication.instance.getEventEmitter().sendDidDisappearEvent(screenParams, type);
330
+                if (onAnimatedEnd != null) onAnimatedEnd.run();
331
+            }
332
+        });
318
     }
333
     }
319
 
334
 
320
     public void showContextualMenu(ContextualMenuParams params, Callback onButtonClicked) {
335
     public void showContextualMenu(ContextualMenuParams params, Callback onButtonClicked) {

+ 30
- 37
android/app/src/main/java/com/reactnativenavigation/screens/ScreenStack.java View File

78
         }
78
         }
79
     }
79
     }
80
 
80
 
81
-    public void pushInitialScreenWithAnimation(final ScreenParams initialScreenParams, LayoutParams params) {
81
+    public void pushInitialModalScreenWithAnimation(final ScreenParams initialScreenParams, LayoutParams params) {
82
         isStackVisible = true;
82
         isStackVisible = true;
83
         pushInitialScreen(initialScreenParams, params);
83
         pushInitialScreen(initialScreenParams, params);
84
         final Screen screen = stack.peek();
84
         final Screen screen = stack.peek();
85
         screen.setOnDisplayListener(new Screen.OnDisplayListener() {
85
         screen.setOnDisplayListener(new Screen.OnDisplayListener() {
86
             @Override
86
             @Override
87
             public void onDisplay() {
87
             public void onDisplay() {
88
-                screen.show(initialScreenParams.animateScreenTransitions);
88
+                screen.show(initialScreenParams.animateScreenTransitions, NavigationType.ShowModal);
89
                 screen.setStyle();
89
                 screen.setStyle();
90
             }
90
             }
91
         });
91
         });
94
     public void pushInitialScreen(ScreenParams initialScreenParams, LayoutParams params) {
94
     public void pushInitialScreen(ScreenParams initialScreenParams, LayoutParams params) {
95
         Screen initialScreen = ScreenFactory.create(activity, initialScreenParams, leftButtonOnClickListener);
95
         Screen initialScreen = ScreenFactory.create(activity, initialScreenParams, leftButtonOnClickListener);
96
         initialScreen.setVisibility(View.INVISIBLE);
96
         initialScreen.setVisibility(View.INVISIBLE);
97
-        initialScreen.setOnDisplayListener(new Screen.OnDisplayListener() {
98
-            @Override
99
-            public void onDisplay() {
100
-                if (isStackVisible) {
101
-                    NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", stack.peek().getNavigatorEventId());
102
-                    NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", stack.peek().getNavigatorEventId());
103
-                }
104
-            }
105
-        });
106
         addScreen(initialScreen, params);
97
         addScreen(initialScreen, params);
107
     }
98
     }
108
 
99
 
131
                                           @Nullable final Screen.OnDisplayListener onDisplay) {
122
                                           @Nullable final Screen.OnDisplayListener onDisplay) {
132
         nextScreen.setVisibility(View.INVISIBLE);
123
         nextScreen.setVisibility(View.INVISIBLE);
133
         addScreen(nextScreen, layoutParams);
124
         addScreen(nextScreen, layoutParams);
134
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willDisappear", previousScreen.getNavigatorEventId());
125
+        NavigationApplication.instance.getEventEmitter().sendWillDisappearEvent(previousScreen.screenParams, NavigationType.Push);
135
         nextScreen.setOnDisplayListener(new Screen.OnDisplayListener() {
126
         nextScreen.setOnDisplayListener(new Screen.OnDisplayListener() {
136
             @Override
127
             @Override
137
             public void onDisplay() {
128
             public void onDisplay() {
139
                     @Override
130
                     @Override
140
                     public void run() {
131
                     public void run() {
141
                         if (onDisplay != null) onDisplay.onDisplay();
132
                         if (onDisplay != null) onDisplay.onDisplay();
142
-                        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didDisappear", previousScreen.getNavigatorEventId());
133
+                        NavigationApplication.instance.getEventEmitter().sendDidDisappearEvent(previousScreen.screenParams, NavigationType.Push);
143
                         parent.removeView(previousScreen);
134
                         parent.removeView(previousScreen);
144
                     }
135
                     }
145
-                });
136
+                }, NavigationType.Push);
146
             }
137
             }
147
         });
138
         });
148
     }
139
     }
178
         parent.addView(screen, parent.getChildCount() - 1, layoutParams);
169
         parent.addView(screen, parent.getChildCount() - 1, layoutParams);
179
     }
170
     }
180
 
171
 
181
-    public void pop(boolean animated) {
182
-        pop(animated, null);
172
+    public void pop(boolean animated, double jsPopTimestamp) {
173
+        pop(animated, jsPopTimestamp, null);
183
     }
174
     }
184
 
175
 
185
-    public void pop(final boolean animated, @Nullable final OnScreenPop onScreenPop) {
176
+    public void pop(final boolean animated, final double jsPopTimestamp, @Nullable final OnScreenPop onScreenPop) {
186
         if (!canPop()) {
177
         if (!canPop()) {
187
             return;
178
             return;
188
         }
179
         }
191
                 @Override
182
                 @Override
192
                 public void run() {
183
                 public void run() {
193
                     keyboardVisibilityDetector.setKeyboardCloseListener(null);
184
                     keyboardVisibilityDetector.setKeyboardCloseListener(null);
194
-                    popInternal(animated, onScreenPop);
185
+                    popInternal(animated, jsPopTimestamp, onScreenPop);
195
                 }
186
                 }
196
             });
187
             });
197
             keyboardVisibilityDetector.closeKeyboard();
188
             keyboardVisibilityDetector.closeKeyboard();
198
         } else {
189
         } else {
199
-            popInternal(animated, onScreenPop);
190
+            popInternal(animated, jsPopTimestamp, onScreenPop);
200
         }
191
         }
201
     }
192
     }
202
 
193
 
203
-    private void popInternal(final boolean animated, @Nullable final OnScreenPop onScreenPop) {
194
+    private void popInternal(final boolean animated, double jsPopTimestamp, @Nullable final OnScreenPop onScreenPop) {
204
         final Screen toRemove = stack.pop();
195
         final Screen toRemove = stack.pop();
205
         final Screen previous = stack.peek();
196
         final Screen previous = stack.peek();
197
+        previous.screenParams.timestamp = jsPopTimestamp;
206
         swapScreens(animated, toRemove, previous, onScreenPop);
198
         swapScreens(animated, toRemove, previous, onScreenPop);
207
     }
199
     }
208
 
200
 
215
         }
207
         }
216
     }
208
     }
217
 
209
 
218
-    private void hideScreen(boolean animated, final Screen toRemove, Screen previous) {
210
+    private void hideScreen(boolean animated, final Screen toRemove, final Screen previous) {
211
+        NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(previous.screenParams, NavigationType.Pop);
219
         Runnable onAnimationEnd = new Runnable() {
212
         Runnable onAnimationEnd = new Runnable() {
220
             @Override
213
             @Override
221
             public void run() {
214
             public void run() {
222
                 toRemove.destroy();
215
                 toRemove.destroy();
223
                 parent.removeView(toRemove);
216
                 parent.removeView(toRemove);
217
+                NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(previous.screenParams, NavigationType.Pop);
224
             }
218
             }
225
         };
219
         };
226
         if (animated) {
220
         if (animated) {
227
-            toRemove.animateHide(previous.sharedElements.getToElements(), onAnimationEnd);
221
+            toRemove.animateHide(previous.sharedElements.getToElements(), onAnimationEnd, NavigationType.Pop);
228
         } else {
222
         } else {
229
-            toRemove.hide(previous.sharedElements.getToElements(), onAnimationEnd);
223
+            toRemove.hide(previous.sharedElements.getToElements(), onAnimationEnd, NavigationType.Pop);
230
         }
224
         }
231
     }
225
     }
232
 
226
 
236
 
230
 
237
     private void readdPrevious(Screen previous) {
231
     private void readdPrevious(Screen previous) {
238
         previous.setVisibility(View.VISIBLE);
232
         previous.setVisibility(View.VISIBLE);
239
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", previous.getNavigatorEventId());
240
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", previous.getNavigatorEventId());
241
         parent.addView(previous, 0);
233
         parent.addView(previous, 0);
242
     }
234
     }
243
 
235
 
244
-    public void popToRoot(final boolean animated, @Nullable final OnScreenPop onScreenPop) {
236
+    public void popToRoot(final boolean animated, final double jsPopTimestamp, @Nullable final OnScreenPop onScreenPop) {
245
         if (keyboardVisibilityDetector.isKeyboardVisible()) {
237
         if (keyboardVisibilityDetector.isKeyboardVisible()) {
246
             keyboardVisibilityDetector.setKeyboardCloseListener(new Runnable() {
238
             keyboardVisibilityDetector.setKeyboardCloseListener(new Runnable() {
247
                 @Override
239
                 @Override
248
                 public void run() {
240
                 public void run() {
249
                     keyboardVisibilityDetector.setKeyboardCloseListener(null);
241
                     keyboardVisibilityDetector.setKeyboardCloseListener(null);
250
-                    popToRootInternal(animated, onScreenPop);
242
+                    popToRootInternal(animated, jsPopTimestamp, onScreenPop);
251
                 }
243
                 }
252
             });
244
             });
253
             keyboardVisibilityDetector.closeKeyboard();
245
             keyboardVisibilityDetector.closeKeyboard();
254
         } else {
246
         } else {
255
-            popToRootInternal(animated, onScreenPop);
247
+            popToRootInternal(animated, jsPopTimestamp, onScreenPop);
256
         }
248
         }
257
     }
249
     }
258
 
250
 
259
-    private void popToRootInternal(final boolean animated, @Nullable final OnScreenPop onScreenPop) {
251
+    private void popToRootInternal(final boolean animated, double jsPopTimestamp, @Nullable final OnScreenPop onScreenPop) {
260
         while (canPop()) {
252
         while (canPop()) {
261
             if (stack.size() == 2) {
253
             if (stack.size() == 2) {
262
-                popInternal(animated, onScreenPop);
254
+                popInternal(animated, jsPopTimestamp, onScreenPop);
263
             } else {
255
             } else {
264
-                popInternal(animated, null);
256
+                popInternal(animated, jsPopTimestamp, null);
265
             }
257
             }
266
         }
258
         }
267
     }
259
     }
421
         }
413
         }
422
     }
414
     }
423
 
415
 
424
-    public void show() {
416
+    public void show(NavigationType type) {
425
         isStackVisible = true;
417
         isStackVisible = true;
426
         stack.peek().setStyle();
418
         stack.peek().setStyle();
427
         stack.peek().setVisibility(View.VISIBLE);
419
         stack.peek().setVisibility(View.VISIBLE);
428
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", stack.peek().getNavigatorEventId());
429
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", stack.peek().getNavigatorEventId());
420
+        stack.peek().screenParams.timestamp = System.currentTimeMillis();
421
+        NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(stack.peek().screenParams, type);
422
+        NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(stack.peek().screenParams, type);
430
     }
423
     }
431
 
424
 
432
-    public void hide() {
433
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willDisappear", stack.peek().getNavigatorEventId());
434
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didDisappear", stack.peek().getNavigatorEventId());
425
+    public void hide(NavigationType type) {
426
+        NavigationApplication.instance.getEventEmitter().sendWillDisappearEvent(stack.peek().screenParams, type);
427
+        NavigationApplication.instance.getEventEmitter().sendDidDisappearEvent(stack.peek().screenParams, type);
435
         isStackVisible = false;
428
         isStackVisible = false;
436
         stack.peek().setVisibility(View.INVISIBLE);
429
         stack.peek().setVisibility(View.INVISIBLE);
437
     }
430
     }

+ 15
- 4
android/app/src/main/java/com/reactnativenavigation/views/SideMenu.java View File

9
 import android.widget.RelativeLayout;
9
 import android.widget.RelativeLayout;
10
 
10
 
11
 import com.reactnativenavigation.NavigationApplication;
11
 import com.reactnativenavigation.NavigationApplication;
12
+import com.reactnativenavigation.params.BaseScreenParams;
12
 import com.reactnativenavigation.params.SideMenuParams;
13
 import com.reactnativenavigation.params.SideMenuParams;
14
+import com.reactnativenavigation.screens.NavigationType;
13
 import com.reactnativenavigation.screens.Screen;
15
 import com.reactnativenavigation.screens.Screen;
14
 import com.reactnativenavigation.utils.ViewUtils;
16
 import com.reactnativenavigation.utils.ViewUtils;
15
 
17
 
16
 public class SideMenu extends DrawerLayout {
18
 public class SideMenu extends DrawerLayout {
19
+    private SideMenuParams leftMenuParams;
20
+    private SideMenuParams rightMenuParams;
21
+
17
     public enum Side {
22
     public enum Side {
18
         Left(Gravity.LEFT), Right(Gravity.RIGHT);
23
         Left(Gravity.LEFT), Right(Gravity.RIGHT);
19
 
24
 
91
 
96
 
92
     public SideMenu(Context context, SideMenuParams leftMenuParams, SideMenuParams rightMenuParams) {
97
     public SideMenu(Context context, SideMenuParams leftMenuParams, SideMenuParams rightMenuParams) {
93
         super(context);
98
         super(context);
99
+        this.leftMenuParams = leftMenuParams;
100
+        this.rightMenuParams = rightMenuParams;
94
         createContentContainer();
101
         createContentContainer();
95
         leftSideMenuView = createSideMenu(leftMenuParams);
102
         leftSideMenuView = createSideMenu(leftMenuParams);
96
         rightSideMenuView = createSideMenu(rightMenuParams);
103
         rightSideMenuView = createSideMenu(rightMenuParams);
133
         sideMenuListener = new SimpleDrawerListener() {
140
         sideMenuListener = new SimpleDrawerListener() {
134
             @Override
141
             @Override
135
             public void onDrawerOpened(View drawerView) {
142
             public void onDrawerOpened(View drawerView) {
136
-                NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", ((ContentView)drawerView).getNavigatorEventId());
137
-                NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", ((ContentView)drawerView).getNavigatorEventId());
143
+                NavigationApplication.instance.getEventEmitter().sendWillAppearEvent(getVisibleDrawerScreenParams(), NavigationType.OpenSideMenu);
144
+                NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(getVisibleDrawerScreenParams(), NavigationType.OpenSideMenu);
138
             }
145
             }
139
 
146
 
140
             @Override
147
             @Override
141
             public void onDrawerClosed(View drawerView) {
148
             public void onDrawerClosed(View drawerView) {
142
-                NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willDisappear", ((ContentView)drawerView).getNavigatorEventId());
143
-                NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didDisappear", ((ContentView)drawerView).getNavigatorEventId());
149
+                NavigationApplication.instance.getEventEmitter().sendWillDisappearEvent(getVisibleDrawerScreenParams(), NavigationType.CloseSideMenu);
150
+                NavigationApplication.instance.getEventEmitter().sendDidDisappearEvent(getVisibleDrawerScreenParams(), NavigationType.CloseSideMenu);
151
+            }
152
+
153
+            private BaseScreenParams getVisibleDrawerScreenParams() {
154
+                return isDrawerOpen(Side.Left.gravity) ? leftMenuParams : rightMenuParams;
144
             }
155
             }
145
         };
156
         };
146
         addDrawerListener(sideMenuListener);
157
         addDrawerListener(sideMenuListener);

+ 3
- 1
example/src/app.js View File

1
 import {Platform} from 'react-native';
1
 import {Platform} from 'react-native';
2
 import {Navigation} from 'react-native-navigation';
2
 import {Navigation} from 'react-native-navigation';
3
-import registerScreens from './screens';
3
+import {registerScreens, registerScreenVisibilityListener} from './screens';
4
+
4
 
5
 
5
 // screen related book keeping
6
 // screen related book keeping
6
 registerScreens();
7
 registerScreens();
8
+registerScreenVisibilityListener();
7
 
9
 
8
 const tabs = [{
10
 const tabs = [{
9
   label: 'Navigation',
11
   label: 'Navigation',

+ 2
- 2
example/src/screens/Types.js View File

1
-import React from 'react';
1
+import React, {Component} from 'react';
2
 import {StyleSheet, ScrollView} from 'react-native';
2
 import {StyleSheet, ScrollView} from 'react-native';
3
 import Row from '../components/Row';
3
 import Row from '../components/Row';
4
 
4
 
5
-class Types extends React.Component {
5
+class Types extends Component {
6
 
6
 
7
   toggleDrawer = () => {
7
   toggleDrawer = () => {
8
     this.props.navigator.toggleDrawer({
8
     this.props.navigator.toggleDrawer({

+ 11
- 2
example/src/screens/index.js View File

1
-import {Navigation} from 'react-native-navigation';
1
+import {Navigation, ScreenVisibilityListener} from 'react-native-navigation';
2
 
2
 
3
 import Types from './Types';
3
 import Types from './Types';
4
 import Actions from './Actions';
4
 import Actions from './Actions';
23
 import Masonry from './transitions/sharedElementTransitions/Masonry/Masonry';
23
 import Masonry from './transitions/sharedElementTransitions/Masonry/Masonry';
24
 import MasonryItem from './transitions/sharedElementTransitions/Masonry/Item';
24
 import MasonryItem from './transitions/sharedElementTransitions/Masonry/Item';
25
 
25
 
26
-export default function () {
26
+export function registerScreens() {
27
   Navigation.registerComponent('example.Types', () => Types);
27
   Navigation.registerComponent('example.Types', () => Types);
28
   Navigation.registerComponent('example.Actions', () => Actions);
28
   Navigation.registerComponent('example.Actions', () => Actions);
29
   Navigation.registerComponent('example.Transitions', () => Transitions);
29
   Navigation.registerComponent('example.Transitions', () => Transitions);
46
   Navigation.registerComponent('example.Transitions.SharedElementTransitions.Masonry', () => Masonry);
46
   Navigation.registerComponent('example.Transitions.SharedElementTransitions.Masonry', () => Masonry);
47
   Navigation.registerComponent('example.Transitions.SharedElementTransitions.Masonry.Item', () => MasonryItem);
47
   Navigation.registerComponent('example.Transitions.SharedElementTransitions.Masonry.Item', () => MasonryItem);
48
 }
48
 }
49
+
50
+export function registerScreenVisibilityListener() {
51
+  new ScreenVisibilityListener({
52
+    willAppear: ({screen}) => console.log(`Displaying screen ${screen}`),
53
+    didAppear: ({screen, startTime, endTime, commandType}) => console.log('screenVisibility', `Screen ${screen} displayed in ${endTime - startTime} millis [${commandType}]`),
54
+    willDisappear: ({screen}) => console.log(`Screen will disappear ${screen}`),
55
+    didDisappear: ({screen}) => console.log(`Screen disappeared ${screen}`)
56
+  }).register();
57
+}

+ 43
- 3
example/src/screens/types/Modal.js View File

1
-import React from 'react';
2
-import {StyleSheet, View, Text} from 'react-native';
1
+import React, {Component} from 'react';
2
+import {StyleSheet, View, Text, Button} from 'react-native';
3
 
3
 
4
-class Modal extends React.Component {
4
+class Modal extends Component {
5
+
6
+  onPushScreen = () => {
7
+    this.props.navigator.push({
8
+      screen: 'example.Types.Modal',
9
+      title: `Screen ${this.props.count || 1}`,
10
+      passProps: {
11
+        count: this.props.count ? this.props.count + 1 : 2
12
+      }
13
+    });
14
+  };
15
+
16
+  onResetTo = () => {
17
+    this.props.navigator.resetTo({
18
+      screen: 'example.Types.Modal',
19
+      icon: require('../../../img/list.png'),
20
+      title: 'Modal'
21
+    });
22
+  };
23
+
24
+  onPopToRoot = () => {
25
+    this.props.navigator.popToRoot();
26
+  };
5
 
27
 
6
   render() {
28
   render() {
7
     return (
29
     return (
8
       <View style={styles.container}>
30
       <View style={styles.container}>
9
         <Text>Modal Screen</Text>
31
         <Text>Modal Screen</Text>
32
+        <View style={styles.button}>
33
+          <Button
34
+            onPress={this.onPushScreen}
35
+            title="Push Screen"/>
36
+        </View>
37
+        <View style={styles.button}>
38
+          <Button
39
+            onPress={this.onResetTo}
40
+            title="Reset Stack"/>
41
+        </View>
42
+        {this.props.count > 1 && <View style={styles.button}>
43
+          <Button
44
+            onPress={this.onPopToRoot}
45
+            title="Pop To Root"/>
46
+        </View>}
10
       </View>
47
       </View>
11
     );
48
     );
12
   }
49
   }
19
     justifyContent: 'center',
56
     justifyContent: 'center',
20
     backgroundColor: '#ffffff',
57
     backgroundColor: '#ffffff',
21
   },
58
   },
59
+  button: {
60
+    marginTop: 16
61
+  }
22
 });
62
 });
23
 
63
 
24
 export default Modal;
64
 export default Modal;

+ 44
- 3
example/src/screens/types/Push.js View File

1
-import React from 'react';
2
-import {StyleSheet, View, Text} from 'react-native';
1
+import React, {Component} from 'react';
2
+import {StyleSheet, View, Text, Button} from 'react-native';
3
 
3
 
4
-class Push extends React.Component {
4
+class Push extends Component {
5
+
6
+  onPushAnother = () => {
7
+    this.props.navigator.push({
8
+      screen: 'example.Types.Push',
9
+      title: `Screen ${this.props.count || 1}`,
10
+      passProps: {
11
+        count: this.props.count ? this.props.count + 1 : 2
12
+      }
13
+    });
14
+  };
15
+
16
+  onResetTo = () => {
17
+    this.props.navigator.resetTo({
18
+      label: 'Navigation',
19
+      screen: 'example.Types',
20
+      icon: require('../../../img/list.png'),
21
+      title: 'Navigation Types'
22
+    });
23
+  };
24
+
25
+  onPopToRoot = () => {
26
+    this.props.navigator.popToRoot();
27
+  };
5
 
28
 
6
   render() {
29
   render() {
7
     return (
30
     return (
8
       <View style={styles.container}>
31
       <View style={styles.container}>
9
         <Text>Pushed Screen</Text>
32
         <Text>Pushed Screen</Text>
33
+        <View style={styles.button}>
34
+          <Button
35
+            onPress={this.onPushAnother}
36
+            title="Push Another Screen"/>
37
+        </View>
38
+        <View style={styles.button}>
39
+          <Button
40
+            onPress={this.onResetTo}
41
+            title="Reset Stack"/>
42
+        </View>
43
+        <View style={styles.button}>
44
+          <Button
45
+            onPress={this.onPopToRoot}
46
+            title="Pop To Root"/>
47
+        </View>
10
       </View>
48
       </View>
11
     );
49
     );
12
   }
50
   }
19
     justifyContent: 'center',
57
     justifyContent: 'center',
20
     backgroundColor: '#ffffff',
58
     backgroundColor: '#ffffff',
21
   },
59
   },
60
+  button: {
61
+    marginTop: 16
62
+  }
22
 });
63
 });
23
 
64
 
24
 export default Push;
65
 export default Push;

+ 1
- 0
ios/Helpers/RCTHelpers.h View File

13
 +(BOOL)removeYellowBox:(RCTRootView*)reactRootView;
13
 +(BOOL)removeYellowBox:(RCTRootView*)reactRootView;
14
 + (NSMutableDictionary *)textAttributesFromDictionary:(NSDictionary *)dictionary withPrefix:(NSString *)prefix;
14
 + (NSMutableDictionary *)textAttributesFromDictionary:(NSDictionary *)dictionary withPrefix:(NSString *)prefix;
15
 + (NSMutableDictionary *)textAttributesFromDictionary:(NSDictionary *)dictionary withPrefix:(NSString *)prefix baseFont:(UIFont *)font;
15
 + (NSMutableDictionary *)textAttributesFromDictionary:(NSDictionary *)dictionary withPrefix:(NSString *)prefix baseFont:(UIFont *)font;
16
++ (NSString *)getTimestampString;
16
 @end
17
 @end

+ 4
- 0
ios/Helpers/RCTHelpers.m View File

200
     return [self textAttributesFromDictionary:dictionary withPrefix:prefix baseFont:[UIFont systemFontOfSize:[UIFont systemFontSize]]];
200
     return [self textAttributesFromDictionary:dictionary withPrefix:prefix baseFont:[UIFont systemFontOfSize:[UIFont systemFontSize]]];
201
 }
201
 }
202
 
202
 
203
++ (NSString *)getTimestampString {
204
+    long long milliseconds = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
205
+    return [NSString stringWithFormat:@"%lld", milliseconds];
206
+}
203
 
207
 
204
 @end
208
 @end

+ 11
- 2
ios/RCCManagerModule.m View File

9
 #import "RCCTabBarController.h"
9
 #import "RCCTabBarController.h"
10
 #import "RCCTheSideBarManagerViewController.h"
10
 #import "RCCTheSideBarManagerViewController.h"
11
 #import "RCCNotification.h"
11
 #import "RCCNotification.h"
12
+#import "RCTHelpers.h"
12
 
13
 
13
 #define kSlideDownAnimationDuration 0.35
14
 #define kSlideDownAnimationDuration 0.35
14
 
15
 
222
 
223
 
223
 -(void)performSetRootController:(NSDictionary*)layout animationType:(NSString*)animationType globalProps:(NSDictionary*)globalProps
224
 -(void)performSetRootController:(NSDictionary*)layout animationType:(NSString*)animationType globalProps:(NSDictionary*)globalProps
224
 {
225
 {
226
+    
227
+    NSMutableDictionary *modifiedGloablProps = [globalProps mutableCopy];
228
+    modifiedGloablProps[GLOBAL_SCREEN_ACTION_COMMAND_TYPE] = COMMAND_TYPE_INITIAL_SCREEN;
229
+    
225
     // first clear the registry to remove any refernece to the previous controllers
230
     // first clear the registry to remove any refernece to the previous controllers
226
     [[RCCManager sharedInstance] clearModuleRegistry];
231
     [[RCCManager sharedInstance] clearModuleRegistry];
227
     
232
     
228
     // create the new controller
233
     // create the new controller
229
-    UIViewController *controller = [RCCViewController controllerWithLayout:layout globalProps:globalProps bridge:[[RCCManager sharedInstance] getBridge]];
234
+    UIViewController *controller = [RCCViewController controllerWithLayout:layout globalProps:modifiedGloablProps bridge:[[RCCManager sharedInstance] getBridge]];
230
     if (controller == nil) return;
235
     if (controller == nil) return;
231
     
236
     
232
     id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
237
     id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
318
 RCT_EXPORT_METHOD(
323
 RCT_EXPORT_METHOD(
319
                   showController:(NSDictionary*)layout animationType:(NSString*)animationType globalProps:(NSDictionary*)globalProps resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
324
                   showController:(NSDictionary*)layout animationType:(NSString*)animationType globalProps:(NSDictionary*)globalProps resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
320
 {
325
 {
321
-    UIViewController *controller = [RCCViewController controllerWithLayout:layout globalProps:globalProps bridge:[[RCCManager sharedInstance] getBridge]];
326
+
327
+    NSMutableDictionary *modifiedGlobalProps = [globalProps mutableCopy];
328
+    layout[@"props"][@"passProps"][GLOBAL_SCREEN_ACTION_COMMAND_TYPE] = COMMAND_TYPE_SHOW_MODAL;
329
+    
330
+    UIViewController *controller = [RCCViewController controllerWithLayout:layout globalProps:modifiedGlobalProps bridge:[[RCCManager sharedInstance] getBridge]];
322
     if (controller == nil)
331
     if (controller == nil)
323
     {
332
     {
324
         [RCCManagerModule handleRCTPromiseRejectBlock:reject
333
         [RCCManagerModule handleRCTPromiseRejectBlock:reject

+ 5
- 2
ios/RCCNavigationController.m View File

74
     NSString *component = actionParams[@"component"];
74
     NSString *component = actionParams[@"component"];
75
     if (!component) return;
75
     if (!component) return;
76
     
76
     
77
-    NSDictionary *passProps = actionParams[@"passProps"];
77
+    NSMutableDictionary *passProps = [actionParams[@"passProps"] mutableCopy];
78
+    passProps[GLOBAL_SCREEN_ACTION_COMMAND_TYPE] = COMMAND_TYPE_PUSH;
79
+    passProps[GLOBAL_SCREEN_ACTION_TIMESTAMP] = actionParams[GLOBAL_SCREEN_ACTION_TIMESTAMP];
78
     NSDictionary *navigatorStyle = actionParams[@"style"];
80
     NSDictionary *navigatorStyle = actionParams[@"style"];
79
     
81
     
80
     // merge the navigatorStyle of our parent
82
     // merge the navigatorStyle of our parent
206
     NSString *component = actionParams[@"component"];
208
     NSString *component = actionParams[@"component"];
207
     if (!component) return;
209
     if (!component) return;
208
     
210
     
209
-    NSDictionary *passProps = actionParams[@"passProps"];
211
+    NSMutableDictionary *passProps = [actionParams[@"passProps"] mutableCopy];
212
+    passProps[@"commantType"] = @"resetTo";
210
     NSDictionary *navigatorStyle = actionParams[@"style"];
213
     NSDictionary *navigatorStyle = actionParams[@"style"];
211
     
214
     
212
     RCCViewController *viewController = [[RCCViewController alloc] initWithComponent:component passProps:passProps navigatorStyle:navigatorStyle globalProps:nil bridge:bridge];
215
     RCCViewController *viewController = [[RCCViewController alloc] initWithComponent:component passProps:passProps navigatorStyle:navigatorStyle globalProps:nil bridge:bridge];

+ 11
- 0
ios/RCCTabBarController.m View File

35
     [RCCTabBarController sendScreenTabChangedEvent:viewController body:body];
35
     [RCCTabBarController sendScreenTabChangedEvent:viewController body:body];
36
     
36
     
37
     [[[RCCManager sharedInstance] getBridge].eventDispatcher sendAppEventWithName:@"bottomTabSelected" body:body];
37
     [[[RCCManager sharedInstance] getBridge].eventDispatcher sendAppEventWithName:@"bottomTabSelected" body:body];
38
+    if ([viewController isKindOfClass:[UINavigationController class]]) {
39
+      UINavigationController *navigationController = (UINavigationController*)viewController;
40
+      UIViewController *topViewController = navigationController.topViewController;
41
+      
42
+      if ([topViewController isKindOfClass:[RCCViewController class]]) {
43
+        RCCViewController *topRCCViewController = (RCCViewController*)topViewController;
44
+        topRCCViewController.commandType = COMMAND_TYPE_BOTTOME_TAB_SELECTED;
45
+        topRCCViewController.timestamp = [RCTHelpers getTimestampString];
46
+      }
47
+    }
48
+    
38
   } else {
49
   } else {
39
     [RCCTabBarController sendScreenTabPressedEvent:viewController body:nil];
50
     [RCCTabBarController sendScreenTabPressedEvent:viewController body:nil];
40
   }
51
   }

+ 10
- 0
ios/RCCViewController.h View File

1
 #import <UIKit/UIKit.h>
1
 #import <UIKit/UIKit.h>
2
 #import <React/RCTBridge.h>
2
 #import <React/RCTBridge.h>
3
 
3
 
4
+#define GLOBAL_SCREEN_ACTION_COMMAND_TYPE       @"commandType"
5
+#define GLOBAL_SCREEN_ACTION_TIMESTAMP          @"timestamp"
6
+#define COMMAND_TYPE_PUSH                       @"Push"
7
+#define COMMAND_TYPE_SHOW_MODAL                 @"ShowModal"
8
+#define COMMAND_TYPE_BOTTOME_TAB_SELECTED       @"BottomTabSelected"
9
+#define COMMAND_TYPE_INITIAL_SCREEN             @"InitialScreen"
10
+
11
+
4
 extern NSString* const RCCViewControllerCancelReactTouchesNotification;
12
 extern NSString* const RCCViewControllerCancelReactTouchesNotification;
5
 
13
 
6
 @interface RCCViewController : UIViewController
14
 @interface RCCViewController : UIViewController
7
 
15
 
8
 @property (nonatomic) NSMutableDictionary *navigatorStyle;
16
 @property (nonatomic) NSMutableDictionary *navigatorStyle;
9
 @property (nonatomic) BOOL navBarHidden;
17
 @property (nonatomic) BOOL navBarHidden;
18
+@property (nonatomic, strong) NSString *commandType;
19
+@property (nonatomic, strong) NSString *timestamp;
10
 
20
 
11
 + (UIViewController*)controllerWithLayout:(NSDictionary *)layout globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge;
21
 + (UIViewController*)controllerWithLayout:(NSDictionary *)layout globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge;
12
 
22
 

+ 69
- 1
ios/RCCViewController.m View File

107
   return controller;
107
   return controller;
108
 }
108
 }
109
 
109
 
110
+-(NSDictionary*)addCommandTypeAndTimestampIfExists:(NSDictionary*)globalProps passProps:(NSDictionary*)passProps {
111
+  NSMutableDictionary *modifiedPassProps = [NSMutableDictionary dictionaryWithDictionary:passProps];
112
+  
113
+  NSString *commandType = globalProps[GLOBAL_SCREEN_ACTION_COMMAND_TYPE];
114
+  if (commandType) {
115
+    modifiedPassProps[GLOBAL_SCREEN_ACTION_COMMAND_TYPE] = commandType;
116
+  }
117
+  
118
+  NSString *timestamp = globalProps[GLOBAL_SCREEN_ACTION_TIMESTAMP];
119
+  if (timestamp) {
120
+    modifiedPassProps[GLOBAL_SCREEN_ACTION_TIMESTAMP] = timestamp;
121
+  }
122
+  return modifiedPassProps;
123
+}
124
+
125
+
126
+
110
 - (instancetype)initWithProps:(NSDictionary *)props children:(NSArray *)children globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge
127
 - (instancetype)initWithProps:(NSDictionary *)props children:(NSArray *)children globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge
111
 {
128
 {
112
   NSString *component = props[@"component"];
129
   NSString *component = props[@"component"];
142
   self = [super init];
159
   self = [super init];
143
   if (!self) return nil;
160
   if (!self) return nil;
144
   
161
   
145
-  [self commonInit:reactView navigatorStyle:navigatorStyle props:passProps];
162
+  NSDictionary *modifiedPassProps = [self addCommandTypeAndTimestampIfExists:globalProps passProps:passProps];
163
+
164
+  [self commonInit:reactView navigatorStyle:navigatorStyle props:modifiedPassProps];
146
   
165
   
147
   return self;
166
   return self;
148
 }
167
 }
161
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRNReload) name:RCTJavaScriptWillStartLoadingNotification object:nil];
180
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRNReload) name:RCTJavaScriptWillStartLoadingNotification object:nil];
162
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onCancelReactTouches) name:RCCViewControllerCancelReactTouchesNotification object:nil];
181
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onCancelReactTouches) name:RCCViewControllerCancelReactTouchesNotification object:nil];
163
   
182
   
183
+  self.commandType = props[GLOBAL_SCREEN_ACTION_COMMAND_TYPE];
184
+  self.timestamp = props[GLOBAL_SCREEN_ACTION_TIMESTAMP];
185
+  
186
+  
164
   // In order to support 3rd party native ViewControllers, we support passing a class name as a prop mamed `ExternalNativeScreenClass`
187
   // In order to support 3rd party native ViewControllers, we support passing a class name as a prop mamed `ExternalNativeScreenClass`
165
   // In this case, we create an instance and add it as a child ViewController which preserves the VC lifecycle.
188
   // In this case, we create an instance and add it as a child ViewController which preserves the VC lifecycle.
166
   // In case some props are necessary in the native ViewController, the ExternalNativeScreenProps can be used to pass them
189
   // In case some props are necessary in the native ViewController, the ExternalNativeScreenProps can be used to pass them
203
   }
226
   }
204
 }
227
 }
205
 
228
 
229
+- (void)sendGlobalScreenEvent:(NSString *)eventName endTimestampString:(NSString *)endTimestampStr shouldReset:(BOOL)shouldReset {
230
+  
231
+  if (!self.commandType) return;
232
+  
233
+  if ([self.view isKindOfClass:[RCTRootView class]]){
234
+    NSString *screenName = [((RCTRootView*)self.view) moduleName];
235
+    
236
+    [[[RCCManager sharedInstance] getBridge].eventDispatcher sendAppEventWithName:eventName body:@
237
+     {
238
+       @"commandType": self.commandType ? self.commandType : @"",
239
+       @"screen": screenName ? screenName : @"",
240
+       @"startTime": self.timestamp ? self.timestamp : @"",
241
+       @"endTime": endTimestampStr ? endTimestampStr : @""
242
+     }];
243
+    
244
+    if (shouldReset) {
245
+      self.commandType = nil;
246
+      self.timestamp = nil;
247
+    }
248
+  }
249
+}
250
+
251
+
252
+-(BOOL)isDisappearTriggeredFromPop:(NSString *)eventName {
253
+
254
+  NSArray *navigationViewControllers = self.navigationController.viewControllers;
255
+  
256
+  if (navigationViewControllers.lastObject == self || [navigationViewControllers indexOfObject:self] == NSNotFound) {
257
+    return YES;
258
+  }
259
+  return NO;
260
+}
261
+
262
+- (NSString *)getTimestampString {
263
+  long long milliseconds = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
264
+  return [NSString stringWithFormat:@"%lld", milliseconds];
265
+}
266
+
267
+
206
 - (void)viewDidAppear:(BOOL)animated
268
 - (void)viewDidAppear:(BOOL)animated
207
 {
269
 {
208
   [super viewDidAppear:animated];
270
   [super viewDidAppear:animated];
271
+  
272
+  [self sendGlobalScreenEvent:@"didAppear" endTimestampString:[self getTimestampString] shouldReset:YES];
209
   [self sendScreenChangedEvent:@"didAppear"];
273
   [self sendScreenChangedEvent:@"didAppear"];
274
+  
210
 }
275
 }
211
 
276
 
212
 - (void)viewWillAppear:(BOOL)animated
277
 - (void)viewWillAppear:(BOOL)animated
213
 {
278
 {
214
   [super viewWillAppear:animated];
279
   [super viewWillAppear:animated];
280
+  [self sendGlobalScreenEvent:@"willAppear" endTimestampString:[self getTimestampString] shouldReset:NO];
215
   [self sendScreenChangedEvent:@"willAppear"];
281
   [self sendScreenChangedEvent:@"willAppear"];
216
   [self setStyleOnAppear];
282
   [self setStyleOnAppear];
217
 }
283
 }
219
 - (void)viewDidDisappear:(BOOL)animated
285
 - (void)viewDidDisappear:(BOOL)animated
220
 {
286
 {
221
   [super viewDidDisappear:animated];
287
   [super viewDidDisappear:animated];
288
+  [self sendGlobalScreenEvent:@"didDisappear" endTimestampString:[self getTimestampString] shouldReset:YES];
222
   [self sendScreenChangedEvent:@"didDisappear"];
289
   [self sendScreenChangedEvent:@"didDisappear"];
223
 }
290
 }
224
 
291
 
225
 - (void)viewWillDisappear:(BOOL)animated
292
 - (void)viewWillDisappear:(BOOL)animated
226
 {
293
 {
227
   [super viewWillDisappear:animated];
294
   [super viewWillDisappear:animated];
295
+  [self sendGlobalScreenEvent:@"willDisappear" endTimestampString:[self getTimestampString] shouldReset:NO];
228
   [self sendScreenChangedEvent:@"willDisappear"];
296
   [self sendScreenChangedEvent:@"willDisappear"];
229
   [self setStyleOnDisappear];
297
   [self setStyleOnDisappear];
230
 }
298
 }

+ 37
- 0
src/ScreenVisibilityListener.js View File

1
+import {
2
+  NativeAppEventEmitter,
3
+  DeviceEventEmitter,
4
+  Platform
5
+} from 'react-native';
6
+export default class ScreenVisibilityListener {
7
+  constructor(listeners) {
8
+    this.emitter = Platform.OS === 'android' ? DeviceEventEmitter : NativeAppEventEmitter;
9
+    this.listeners = listeners;
10
+  }
11
+
12
+  register() {
13
+    const {willAppear, didAppear, willDisappear, didDisappear} = this.listeners;
14
+    this.willAppearSubscription = willAppear && this.emitter.addListener('willAppear', willAppear);
15
+    this.didAppearSubscription = didAppear && this.emitter.addListener('didAppear', didAppear);
16
+    this.willDisappearSubscription = willDisappear && this.emitter.addListener('willDisappear', willDisappear);
17
+    this.didDisappearSubscription = didDisappear && this.emitter.addListener('didDisappear', didDisappear);
18
+  }
19
+
20
+  unregister() {
21
+    if (this.willAppearSubscription) {
22
+      this.willAppearSubscription.remove();
23
+    }
24
+
25
+    if (this.didAppearSubscription) {
26
+      this.didAppearSubscription.remove();
27
+    }
28
+
29
+    if (this.willDisappearSubscription) {
30
+      this.willDisappearSubscription.remove();
31
+    }
32
+
33
+    if (this.didDisappearSubscription) {
34
+      this.didDisappearSubscription.remove();
35
+    }
36
+  }
37
+}

+ 3
- 1
src/deprecated/indexDeprecated.android.js View File

1
 import Navigation from './../Navigation';
1
 import Navigation from './../Navigation';
2
 import SharedElementTransition from './../views/sharedElementTransition';
2
 import SharedElementTransition from './../views/sharedElementTransition';
3
 import NativeEventsReceiver from './../NativeEventsReceiver';
3
 import NativeEventsReceiver from './../NativeEventsReceiver';
4
+import ScreenVisibilityListener from './../ScreenVisibilityListener';
4
 
5
 
5
 module.exports = {
6
 module.exports = {
6
   Navigation,
7
   Navigation,
7
   SharedElementTransition,
8
   SharedElementTransition,
8
-  NativeEventsReceiver
9
+  NativeEventsReceiver,
10
+  ScreenVisibilityListener
9
 };
11
 };

+ 3
- 1
src/deprecated/indexDeprecated.ios.js View File

2
 import {NavigationToolBarIOS} from './controllers';
2
 import {NavigationToolBarIOS} from './controllers';
3
 import SharedElementTransition from '../views/sharedElementTransition';
3
 import SharedElementTransition from '../views/sharedElementTransition';
4
 import NativeEventsReceiver from './../NativeEventsReceiver';
4
 import NativeEventsReceiver from './../NativeEventsReceiver';
5
+import ScreenVisibilityListener from './../ScreenVisibilityListener';
5
 
6
 
6
 module.exports = {
7
 module.exports = {
7
   Navigation,
8
   Navigation,
8
   NavigationToolBarIOS,
9
   NavigationToolBarIOS,
9
   SharedElementTransition,
10
   SharedElementTransition,
10
-  NativeEventsReceiver
11
+  NativeEventsReceiver,
12
+  ScreenVisibilityListener
11
 };
13
 };

+ 6
- 1
src/deprecated/platformSpecificDeprecated.android.js View File

73
   addNavigationStyleParams(params);
73
   addNavigationStyleParams(params);
74
 
74
 
75
   adaptTopTabs(params, params.navigatorID);
75
   adaptTopTabs(params, params.navigatorID);
76
-  // findSharedElementsNodeHandles(params);
77
 
76
 
78
   params.screenId = params.screen;
77
   params.screenId = params.screen;
79
   let adapted = adaptNavigationStyleToScreenStyle(params);
78
   let adapted = adaptNavigationStyleToScreenStyle(params);
80
   adapted = adaptNavigationParams(adapted);
79
   adapted = adaptNavigationParams(adapted);
81
   adapted.overrideBackPress = params.overrideBackPress;
80
   adapted.overrideBackPress = params.overrideBackPress;
81
+  adapted.timestamp = Date.now();
82
 
82
 
83
   newPlatformSpecific.push(adapted);
83
   newPlatformSpecific.push(adapted);
84
 }
84
 }
89
   params.screenId = params.screen;
89
   params.screenId = params.screen;
90
   let adapted = adaptNavigationStyleToScreenStyle(params);
90
   let adapted = adaptNavigationStyleToScreenStyle(params);
91
   adapted = adaptNavigationParams(adapted);
91
   adapted = adaptNavigationParams(adapted);
92
+  adapted.timestamp = Date.now();
92
 
93
 
93
   newPlatformSpecific.pop(adapted);
94
   newPlatformSpecific.pop(adapted);
94
 }
95
 }
99
   params.screenId = params.screen;
100
   params.screenId = params.screen;
100
   let adapted = adaptNavigationStyleToScreenStyle(params);
101
   let adapted = adaptNavigationStyleToScreenStyle(params);
101
   adapted = adaptNavigationParams(adapted);
102
   adapted = adaptNavigationParams(adapted);
103
+  adapted.timestamp = Date.now();
102
 
104
 
103
   newPlatformSpecific.popToRoot(adapted);
105
   newPlatformSpecific.popToRoot(adapted);
104
 }
106
 }
113
   params.screenId = params.screen;
115
   params.screenId = params.screen;
114
   let adapted = adaptNavigationStyleToScreenStyle(params);
116
   let adapted = adaptNavigationStyleToScreenStyle(params);
115
   adapted = adaptNavigationParams(adapted);
117
   adapted = adaptNavigationParams(adapted);
118
+  adapted.timestamp = Date.now();
116
 
119
 
117
   newPlatformSpecific.newStack(adapted);
120
   newPlatformSpecific.newStack(adapted);
118
 }
121
 }
266
     let newtab = adaptNavigationStyleToScreenStyle(tab);
269
     let newtab = adaptNavigationStyleToScreenStyle(tab);
267
     newtab = adaptNavigationParams(tab);
270
     newtab = adaptNavigationParams(tab);
268
     newtab.overrideBackPress = tab.overrideBackPress;
271
     newtab.overrideBackPress = tab.overrideBackPress;
272
+    newtab.timestamp = Date.now();
269
     newTabs.push(newtab);
273
     newTabs.push(newtab);
270
   });
274
   });
271
   params.tabs = newTabs;
275
   params.tabs = newTabs;
429
   let adapted = adaptNavigationStyleToScreenStyle(params);
433
   let adapted = adaptNavigationStyleToScreenStyle(params);
430
   adapted = adaptNavigationParams(adapted);
434
   adapted = adaptNavigationParams(adapted);
431
   adapted.overrideBackPress = params.overrideBackPress;
435
   adapted.overrideBackPress = params.overrideBackPress;
436
+  adapted.timestamp = Date.now();
432
 
437
 
433
   newPlatformSpecific.showModal(adapted);
438
   newPlatformSpecific.showModal(adapted);
434
 }
439
 }

+ 7
- 3
src/deprecated/platformSpecificDeprecated.ios.js View File

82
                     passProps={{
82
                     passProps={{
83
                     navigatorID: tab.navigationParams.navigatorID,
83
                     navigatorID: tab.navigationParams.navigatorID,
84
                     screenInstanceID: tab.navigationParams.screenInstanceID,
84
                     screenInstanceID: tab.navigationParams.screenInstanceID,
85
-                    navigatorEventID: tab.navigationParams.navigatorEventID
85
+                    navigatorEventID: tab.navigationParams.navigatorEventID,
86
                   }}
86
                   }}
87
                     style={tab.navigationParams.navigatorStyle}
87
                     style={tab.navigationParams.navigatorStyle}
88
                     leftButtons={tab.navigationParams.navigatorButtons.leftButtons}
88
                     leftButtons={tab.navigationParams.navigatorButtons.leftButtons}
97
     }
97
     }
98
   });
98
   });
99
   savePassProps(params);
99
   savePassProps(params);
100
+  _.set(params, 'passProps.timestamp', Date.now());
100
 
101
 
101
   ControllerRegistry.registerController(controllerID, () => Controller);
102
   ControllerRegistry.registerController(controllerID, () => Controller);
102
   ControllerRegistry.setRootController(controllerID, params.animationType, params.passProps || {});
103
   ControllerRegistry.setRootController(controllerID, params.animationType, params.passProps || {});
247
     backButtonTitle: params.backButtonTitle,
248
     backButtonTitle: params.backButtonTitle,
248
     backButtonHidden: params.backButtonHidden,
249
     backButtonHidden: params.backButtonHidden,
249
     leftButtons: navigatorButtons.leftButtons,
250
     leftButtons: navigatorButtons.leftButtons,
250
-    rightButtons: navigatorButtons.rightButtons
251
+    rightButtons: navigatorButtons.rightButtons,
252
+    timestamp: Date.now()
251
   });
253
   });
252
 }
254
 }
253
 
255
 
254
 function navigatorPop(navigator, params) {
256
 function navigatorPop(navigator, params) {
255
   Controllers.NavigationControllerIOS(navigator.navigatorID).pop({
257
   Controllers.NavigationControllerIOS(navigator.navigatorID).pop({
256
     animated: params.animated,
258
     animated: params.animated,
257
-    animationType: params.animationType
259
+    animationType: params.animationType,
260
+    timestamp: Date.now()
258
   });
261
   });
259
 }
262
 }
260
 
263
 
457
   passProps.navigatorID = navigatorID;
460
   passProps.navigatorID = navigatorID;
458
   passProps.screenInstanceID = screenInstanceID;
461
   passProps.screenInstanceID = screenInstanceID;
459
   passProps.navigatorEventID = navigatorEventID;
462
   passProps.navigatorEventID = navigatorEventID;
463
+  passProps.timestamp = Date.now();
460
 
464
 
461
   params.navigationParams = {
465
   params.navigationParams = {
462
     screenInstanceID,
466
     screenInstanceID,