Bläddra i källkod

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 år sedan
förälder
incheckning
f59f432901
30 ändrade filer med 431 tillägg och 110 borttagningar
  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 Visa fil

@@ -3,7 +3,9 @@ package com.reactnativenavigation.bridge;
3 3
 import com.facebook.react.bridge.Arguments;
4 4
 import com.facebook.react.bridge.WritableMap;
5 5
 import com.reactnativenavigation.NavigationApplication;
6
+import com.reactnativenavigation.params.BaseScreenParams;
6 7
 import com.reactnativenavigation.react.ReactGateway;
8
+import com.reactnativenavigation.screens.NavigationType;
7 9
 
8 10
 public class EventEmitter {
9 11
     private ReactGateway reactGateway;
@@ -12,12 +14,41 @@ public class EventEmitter {
12 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 38
         WritableMap map = Arguments.createMap();
17 39
         map.putString("type", "ScreenChangedEvent");
18 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 52
     public void sendNavigatorEvent(String eventId, String navigatorEventId) {
22 53
         if (!NavigationApplication.instance.isReactContextInitialized()) {
23 54
             return;

+ 3
- 2
android/app/src/main/java/com/reactnativenavigation/controllers/Modal.java Visa fil

@@ -21,6 +21,7 @@ import com.reactnativenavigation.params.ScreenParams;
21 21
 import com.reactnativenavigation.params.SlidingOverlayParams;
22 22
 import com.reactnativenavigation.params.TitleBarButtonParams;
23 23
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
24
+import com.reactnativenavigation.screens.NavigationType;
24 25
 
25 26
 import java.util.List;
26 27
 
@@ -177,8 +178,8 @@ public class Modal extends Dialog implements DialogInterface.OnDismissListener,
177 178
     @Override
178 179
     public void dismiss() {
179 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 184
         super.dismiss();
184 185
     }

+ 7
- 6
android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java Visa fil

@@ -35,6 +35,7 @@ import com.reactnativenavigation.params.SnackbarParams;
35 35
 import com.reactnativenavigation.params.TitleBarButtonParams;
36 36
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
37 37
 import com.reactnativenavigation.react.ReactGateway;
38
+import com.reactnativenavigation.screens.NavigationType;
38 39
 import com.reactnativenavigation.screens.Screen;
39 40
 import com.reactnativenavigation.utils.OrientationHelper;
40 41
 import com.reactnativenavigation.views.SideMenu.Side;
@@ -237,23 +238,23 @@ public class NavigationActivity extends AppCompatActivity implements DefaultHard
237 238
 
238 239
     void showModal(ScreenParams screenParams) {
239 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 243
         modalController.showModal(screenParams);
243 244
     }
244 245
 
245 246
     void dismissTopModal() {
246 247
         modalController.dismissTopModal();
247 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 253
     void dismissAllModals() {
253 254
         modalController.dismissAllModals();
254 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 260
     public void showLightBox(LightBoxParams params) {

+ 15
- 11
android/app/src/main/java/com/reactnativenavigation/layouts/BottomTabsLayout.java Visa fil

@@ -27,6 +27,7 @@ import com.reactnativenavigation.params.SnackbarParams;
27 27
 import com.reactnativenavigation.params.StyleParams;
28 28
 import com.reactnativenavigation.params.TitleBarButtonParams;
29 29
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
30
+import com.reactnativenavigation.screens.NavigationType;
30 31
 import com.reactnativenavigation.screens.Screen;
31 32
 import com.reactnativenavigation.screens.ScreenStack;
32 33
 import com.reactnativenavigation.utils.ViewUtils;
@@ -132,7 +133,7 @@ public class BottomTabsLayout extends BaseLayout implements AHBottomNavigation.O
132 133
     }
133 134
 
134 135
     private void showInitialScreenStack() {
135
-        showStackAndUpdateStyle(screenStacks[0]);
136
+        showStackAndUpdateStyle(screenStacks[0], NavigationType.InitialScreen);
136 137
         EventBus.instance.post(new ScreenChangedEvent(screenStacks[0].peek().getScreenParams()));
137 138
     }
138 139
 
@@ -148,7 +149,7 @@ public class BottomTabsLayout extends BaseLayout implements AHBottomNavigation.O
148 149
         }
149 150
 
150 151
         if (getCurrentScreenStack().canPop()) {
151
-            getCurrentScreenStack().pop(true);
152
+            getCurrentScreenStack().pop(true, System.currentTimeMillis());
152 153
             setBottomTabsStyleFromCurrentScreen();
153 154
             EventBus.instance.post(new ScreenChangedEvent(getCurrentScreenStack().peek().getScreenParams()));
154 155
             return true;
@@ -289,6 +290,9 @@ public class BottomTabsLayout extends BaseLayout implements AHBottomNavigation.O
289 290
 
290 291
     @Override
291 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 296
         EventBus.instance.post(new ScreenChangedEvent(getCurrentScreenStack().peek().getScreenParams()));
293 297
     }
294 298
 
@@ -348,7 +352,7 @@ public class BottomTabsLayout extends BaseLayout implements AHBottomNavigation.O
348 352
 
349 353
     @Override
350 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 356
             @Override
353 357
             public void onScreenPopAnimationEnd() {
354 358
                 setBottomTabsStyleFromCurrentScreen();
@@ -360,7 +364,7 @@ public class BottomTabsLayout extends BaseLayout implements AHBottomNavigation.O
360 364
 
361 365
     @Override
362 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 368
             @Override
365 369
             public void onScreenPopAnimationEnd() {
366 370
                 setBottomTabsStyleFromCurrentScreen();
@@ -414,7 +418,7 @@ public class BottomTabsLayout extends BaseLayout implements AHBottomNavigation.O
414 418
 
415 419
         final int unselectedTabIndex = currentStackIndex;
416 420
         hideCurrentStack();
417
-        showNewStack(position);
421
+        showNewStack(position, NavigationType.BottomTabSelected);
418 422
         EventBus.instance.post(new ScreenChangedEvent(getCurrentScreenStack().peek().getScreenParams()));
419 423
         sendTabSelectedEventToJs(position, unselectedTabIndex);
420 424
         return true;
@@ -442,19 +446,19 @@ public class BottomTabsLayout extends BaseLayout implements AHBottomNavigation.O
442 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 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 456
         setStyleFromScreen(newStack.getCurrentScreenStyleParams());
453 457
     }
454 458
 
455 459
     private void hideCurrentStack() {
456 460
         ScreenStack currentScreenStack = getCurrentScreenStack();
457
-        currentScreenStack.hide();
461
+        currentScreenStack.hide(NavigationType.BottomTabSelected);
458 462
     }
459 463
 
460 464
     private ScreenStack getCurrentScreenStack() {
@@ -510,7 +514,7 @@ public class BottomTabsLayout extends BaseLayout implements AHBottomNavigation.O
510 514
     @Override
511 515
     public boolean onTitleBarBackButtonClick() {
512 516
         if (getCurrentScreenStack().canPop()) {
513
-            getCurrentScreenStack().pop(true, new ScreenStack.OnScreenPop() {
517
+            getCurrentScreenStack().pop(true, System.currentTimeMillis(), new ScreenStack.OnScreenPop() {
514 518
                 @Override
515 519
                 public void onScreenPopAnimationEnd() {
516 520
                     setBottomTabsStyleFromCurrentScreen();

+ 1
- 1
android/app/src/main/java/com/reactnativenavigation/layouts/ModalScreenLayout.java Visa fil

@@ -16,6 +16,6 @@ public class ModalScreenLayout extends SingleScreenLayout {
16 16
 
17 17
     @Override
18 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 Visa fil

@@ -19,6 +19,7 @@ import com.reactnativenavigation.params.SlidingOverlayParams;
19 19
 import com.reactnativenavigation.params.SnackbarParams;
20 20
 import com.reactnativenavigation.params.TitleBarButtonParams;
21 21
 import com.reactnativenavigation.params.TitleBarLeftButtonParams;
22
+import com.reactnativenavigation.screens.NavigationType;
22 23
 import com.reactnativenavigation.screens.Screen;
23 24
 import com.reactnativenavigation.screens.ScreenStack;
24 25
 import com.reactnativenavigation.views.LeftButtonOnClickListener;
@@ -87,7 +88,7 @@ public class SingleScreenLayout extends BaseLayout {
87 88
 
88 89
     protected void pushInitialScreen(LayoutParams lp) {
89 90
         stack.pushInitialScreen(screenParams, lp);
90
-        stack.show();
91
+        stack.show(NavigationType.Push);
91 92
     }
92 93
 
93 94
     private void sendScreenChangedEventAfterInitialPush() {
@@ -113,7 +114,7 @@ public class SingleScreenLayout extends BaseLayout {
113 114
         }
114 115
 
115 116
         if (stack.canPop()) {
116
-            stack.pop(true);
117
+            stack.pop(true, System.currentTimeMillis());
117 118
             EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
118 119
             return true;
119 120
         } else {
@@ -145,7 +146,7 @@ public class SingleScreenLayout extends BaseLayout {
145 146
 
146 147
     @Override
147 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 150
             @Override
150 151
             public void onScreenPopAnimationEnd() {
151 152
                 EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
@@ -155,7 +156,7 @@ public class SingleScreenLayout extends BaseLayout {
155 156
 
156 157
     @Override
157 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 160
             @Override
160 161
             public void onScreenPopAnimationEnd() {
161 162
                 EventBus.instance.post(new ScreenChangedEvent(stack.peek().getScreenParams()));
@@ -285,6 +286,9 @@ public class SingleScreenLayout extends BaseLayout {
285 286
 
286 287
     @Override
287 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 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 Visa fil

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

+ 1
- 3
android/app/src/main/java/com/reactnativenavigation/params/SideMenuParams.java Visa fil

@@ -2,9 +2,7 @@ package com.reactnativenavigation.params;
2 2
 
3 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 6
     public boolean disableOpenGesture;
9 7
     public SideMenu.Side side;
10 8
 }

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

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

+ 12
- 0
android/app/src/main/java/com/reactnativenavigation/screens/NavigationType.java Visa fil

@@ -0,0 +1,12 @@
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 Visa fil

@@ -251,23 +251,32 @@ public abstract class Screen extends RelativeLayout implements Subscriber {
251 251
 
252 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 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 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 282
     public void showWithSharedElementsTransitions(Map<String, SharedElementTransition> fromElements, final Runnable onAnimationEnd) {
@@ -282,24 +291,25 @@ public abstract class Screen extends RelativeLayout implements Subscriber {
282 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 295
         removeHiddenSharedElements();
287 296
         if (hasVisibleSharedElements()) {
288 297
             hideWithSharedElementTransitions(sharedElements, onAnimationEnd);
289 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 304
         removeHiddenSharedElements();
296 305
         if (hasVisibleSharedElements()) {
297 306
             hideWithSharedElementTransitions(sharedElements, onAnimationEnd);
298 307
         } else {
299
-            hide(true, onAnimationEnd);
308
+            hide(true, onAnimationEnd, type);
300 309
         }
301 310
     }
302 311
 
312
+    @SuppressWarnings("SimplifiableIfStatement")
303 313
     private boolean hasVisibleSharedElements() {
304 314
         if (screenParams.sharedElementsTransitions.isEmpty()) {
305 315
             return false;
@@ -311,10 +321,15 @@ public abstract class Screen extends RelativeLayout implements Subscriber {
311 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 335
     public void showContextualMenu(ContextualMenuParams params, Callback onButtonClicked) {

+ 30
- 37
android/app/src/main/java/com/reactnativenavigation/screens/ScreenStack.java Visa fil

@@ -78,14 +78,14 @@ public class ScreenStack {
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 82
         isStackVisible = true;
83 83
         pushInitialScreen(initialScreenParams, params);
84 84
         final Screen screen = stack.peek();
85 85
         screen.setOnDisplayListener(new Screen.OnDisplayListener() {
86 86
             @Override
87 87
             public void onDisplay() {
88
-                screen.show(initialScreenParams.animateScreenTransitions);
88
+                screen.show(initialScreenParams.animateScreenTransitions, NavigationType.ShowModal);
89 89
                 screen.setStyle();
90 90
             }
91 91
         });
@@ -94,15 +94,6 @@ public class ScreenStack {
94 94
     public void pushInitialScreen(ScreenParams initialScreenParams, LayoutParams params) {
95 95
         Screen initialScreen = ScreenFactory.create(activity, initialScreenParams, leftButtonOnClickListener);
96 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 97
         addScreen(initialScreen, params);
107 98
     }
108 99
 
@@ -131,7 +122,7 @@ public class ScreenStack {
131 122
                                           @Nullable final Screen.OnDisplayListener onDisplay) {
132 123
         nextScreen.setVisibility(View.INVISIBLE);
133 124
         addScreen(nextScreen, layoutParams);
134
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willDisappear", previousScreen.getNavigatorEventId());
125
+        NavigationApplication.instance.getEventEmitter().sendWillDisappearEvent(previousScreen.screenParams, NavigationType.Push);
135 126
         nextScreen.setOnDisplayListener(new Screen.OnDisplayListener() {
136 127
             @Override
137 128
             public void onDisplay() {
@@ -139,10 +130,10 @@ public class ScreenStack {
139 130
                     @Override
140 131
                     public void run() {
141 132
                         if (onDisplay != null) onDisplay.onDisplay();
142
-                        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didDisappear", previousScreen.getNavigatorEventId());
133
+                        NavigationApplication.instance.getEventEmitter().sendDidDisappearEvent(previousScreen.screenParams, NavigationType.Push);
143 134
                         parent.removeView(previousScreen);
144 135
                     }
145
-                });
136
+                }, NavigationType.Push);
146 137
             }
147 138
         });
148 139
     }
@@ -178,11 +169,11 @@ public class ScreenStack {
178 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 177
         if (!canPop()) {
187 178
             return;
188 179
         }
@@ -191,18 +182,19 @@ public class ScreenStack {
191 182
                 @Override
192 183
                 public void run() {
193 184
                     keyboardVisibilityDetector.setKeyboardCloseListener(null);
194
-                    popInternal(animated, onScreenPop);
185
+                    popInternal(animated, jsPopTimestamp, onScreenPop);
195 186
                 }
196 187
             });
197 188
             keyboardVisibilityDetector.closeKeyboard();
198 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 195
         final Screen toRemove = stack.pop();
205 196
         final Screen previous = stack.peek();
197
+        previous.screenParams.timestamp = jsPopTimestamp;
206 198
         swapScreens(animated, toRemove, previous, onScreenPop);
207 199
     }
208 200
 
@@ -215,18 +207,20 @@ public class ScreenStack {
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 212
         Runnable onAnimationEnd = new Runnable() {
220 213
             @Override
221 214
             public void run() {
222 215
                 toRemove.destroy();
223 216
                 parent.removeView(toRemove);
217
+                NavigationApplication.instance.getEventEmitter().sendDidAppearEvent(previous.screenParams, NavigationType.Pop);
224 218
             }
225 219
         };
226 220
         if (animated) {
227
-            toRemove.animateHide(previous.sharedElements.getToElements(), onAnimationEnd);
221
+            toRemove.animateHide(previous.sharedElements.getToElements(), onAnimationEnd, NavigationType.Pop);
228 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,32 +230,30 @@ public class ScreenStack {
236 230
 
237 231
     private void readdPrevious(Screen previous) {
238 232
         previous.setVisibility(View.VISIBLE);
239
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", previous.getNavigatorEventId());
240
-        NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", previous.getNavigatorEventId());
241 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 237
         if (keyboardVisibilityDetector.isKeyboardVisible()) {
246 238
             keyboardVisibilityDetector.setKeyboardCloseListener(new Runnable() {
247 239
                 @Override
248 240
                 public void run() {
249 241
                     keyboardVisibilityDetector.setKeyboardCloseListener(null);
250
-                    popToRootInternal(animated, onScreenPop);
242
+                    popToRootInternal(animated, jsPopTimestamp, onScreenPop);
251 243
                 }
252 244
             });
253 245
             keyboardVisibilityDetector.closeKeyboard();
254 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 252
         while (canPop()) {
261 253
             if (stack.size() == 2) {
262
-                popInternal(animated, onScreenPop);
254
+                popInternal(animated, jsPopTimestamp, onScreenPop);
263 255
             } else {
264
-                popInternal(animated, null);
256
+                popInternal(animated, jsPopTimestamp, null);
265 257
             }
266 258
         }
267 259
     }
@@ -421,17 +413,18 @@ public class ScreenStack {
421 413
         }
422 414
     }
423 415
 
424
-    public void show() {
416
+    public void show(NavigationType type) {
425 417
         isStackVisible = true;
426 418
         stack.peek().setStyle();
427 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 428
         isStackVisible = false;
436 429
         stack.peek().setVisibility(View.INVISIBLE);
437 430
     }

+ 15
- 4
android/app/src/main/java/com/reactnativenavigation/views/SideMenu.java Visa fil

@@ -9,11 +9,16 @@ import android.view.ViewGroup;
9 9
 import android.widget.RelativeLayout;
10 10
 
11 11
 import com.reactnativenavigation.NavigationApplication;
12
+import com.reactnativenavigation.params.BaseScreenParams;
12 13
 import com.reactnativenavigation.params.SideMenuParams;
14
+import com.reactnativenavigation.screens.NavigationType;
13 15
 import com.reactnativenavigation.screens.Screen;
14 16
 import com.reactnativenavigation.utils.ViewUtils;
15 17
 
16 18
 public class SideMenu extends DrawerLayout {
19
+    private SideMenuParams leftMenuParams;
20
+    private SideMenuParams rightMenuParams;
21
+
17 22
     public enum Side {
18 23
         Left(Gravity.LEFT), Right(Gravity.RIGHT);
19 24
 
@@ -91,6 +96,8 @@ public class SideMenu extends DrawerLayout {
91 96
 
92 97
     public SideMenu(Context context, SideMenuParams leftMenuParams, SideMenuParams rightMenuParams) {
93 98
         super(context);
99
+        this.leftMenuParams = leftMenuParams;
100
+        this.rightMenuParams = rightMenuParams;
94 101
         createContentContainer();
95 102
         leftSideMenuView = createSideMenu(leftMenuParams);
96 103
         rightSideMenuView = createSideMenu(rightMenuParams);
@@ -133,14 +140,18 @@ public class SideMenu extends DrawerLayout {
133 140
         sideMenuListener = new SimpleDrawerListener() {
134 141
             @Override
135 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 147
             @Override
141 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 157
         addDrawerListener(sideMenuListener);

+ 3
- 1
example/src/app.js Visa fil

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

+ 2
- 2
example/src/screens/Types.js Visa fil

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

+ 11
- 2
example/src/screens/index.js Visa fil

@@ -1,4 +1,4 @@
1
-import {Navigation} from 'react-native-navigation';
1
+import {Navigation, ScreenVisibilityListener} from 'react-native-navigation';
2 2
 
3 3
 import Types from './Types';
4 4
 import Actions from './Actions';
@@ -23,7 +23,7 @@ import CardsInfo from './transitions/sharedElementTransitions/Cards/Info';
23 23
 import Masonry from './transitions/sharedElementTransitions/Masonry/Masonry';
24 24
 import MasonryItem from './transitions/sharedElementTransitions/Masonry/Item';
25 25
 
26
-export default function () {
26
+export function registerScreens() {
27 27
   Navigation.registerComponent('example.Types', () => Types);
28 28
   Navigation.registerComponent('example.Actions', () => Actions);
29 29
   Navigation.registerComponent('example.Transitions', () => Transitions);
@@ -46,3 +46,12 @@ export default function () {
46 46
   Navigation.registerComponent('example.Transitions.SharedElementTransitions.Masonry', () => Masonry);
47 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 Visa fil

@@ -1,12 +1,49 @@
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 28
   render() {
7 29
     return (
8 30
       <View style={styles.container}>
9 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 47
       </View>
11 48
     );
12 49
   }
@@ -19,6 +56,9 @@ const styles = StyleSheet.create({
19 56
     justifyContent: 'center',
20 57
     backgroundColor: '#ffffff',
21 58
   },
59
+  button: {
60
+    marginTop: 16
61
+  }
22 62
 });
23 63
 
24 64
 export default Modal;

+ 44
- 3
example/src/screens/types/Push.js Visa fil

@@ -1,12 +1,50 @@
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 29
   render() {
7 30
     return (
8 31
       <View style={styles.container}>
9 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 48
       </View>
11 49
     );
12 50
   }
@@ -19,6 +57,9 @@ const styles = StyleSheet.create({
19 57
     justifyContent: 'center',
20 58
     backgroundColor: '#ffffff',
21 59
   },
60
+  button: {
61
+    marginTop: 16
62
+  }
22 63
 });
23 64
 
24 65
 export default Push;

+ 1
- 0
ios/Helpers/RCTHelpers.h Visa fil

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

+ 4
- 0
ios/Helpers/RCTHelpers.m Visa fil

@@ -200,5 +200,9 @@
200 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 208
 @end

+ 11
- 2
ios/RCCManagerModule.m Visa fil

@@ -9,6 +9,7 @@
9 9
 #import "RCCTabBarController.h"
10 10
 #import "RCCTheSideBarManagerViewController.h"
11 11
 #import "RCCNotification.h"
12
+#import "RCTHelpers.h"
12 13
 
13 14
 #define kSlideDownAnimationDuration 0.35
14 15
 
@@ -222,11 +223,15 @@ RCT_EXPORT_METHOD(
222 223
 
223 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 230
     // first clear the registry to remove any refernece to the previous controllers
226 231
     [[RCCManager sharedInstance] clearModuleRegistry];
227 232
     
228 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 235
     if (controller == nil) return;
231 236
     
232 237
     id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
@@ -318,7 +323,11 @@ RCT_EXPORT_METHOD(
318 323
 RCT_EXPORT_METHOD(
319 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 331
     if (controller == nil)
323 332
     {
324 333
         [RCCManagerModule handleRCTPromiseRejectBlock:reject

+ 5
- 2
ios/RCCNavigationController.m Visa fil

@@ -74,7 +74,9 @@ NSString const *CALLBACK_ASSOCIATED_ID = @"RCCNavigationController.CALLBACK_ASSO
74 74
     NSString *component = actionParams[@"component"];
75 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 80
     NSDictionary *navigatorStyle = actionParams[@"style"];
79 81
     
80 82
     // merge the navigatorStyle of our parent
@@ -206,7 +208,8 @@ NSString const *CALLBACK_ASSOCIATED_ID = @"RCCNavigationController.CALLBACK_ASSO
206 208
     NSString *component = actionParams[@"component"];
207 209
     if (!component) return;
208 210
     
209
-    NSDictionary *passProps = actionParams[@"passProps"];
211
+    NSMutableDictionary *passProps = [actionParams[@"passProps"] mutableCopy];
212
+    passProps[@"commantType"] = @"resetTo";
210 213
     NSDictionary *navigatorStyle = actionParams[@"style"];
211 214
     
212 215
     RCCViewController *viewController = [[RCCViewController alloc] initWithComponent:component passProps:passProps navigatorStyle:navigatorStyle globalProps:nil bridge:bridge];

+ 11
- 0
ios/RCCTabBarController.m Visa fil

@@ -35,6 +35,17 @@
35 35
     [RCCTabBarController sendScreenTabChangedEvent:viewController body:body];
36 36
     
37 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 49
   } else {
39 50
     [RCCTabBarController sendScreenTabPressedEvent:viewController body:nil];
40 51
   }

+ 10
- 0
ios/RCCViewController.h Visa fil

@@ -1,12 +1,22 @@
1 1
 #import <UIKit/UIKit.h>
2 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 12
 extern NSString* const RCCViewControllerCancelReactTouchesNotification;
5 13
 
6 14
 @interface RCCViewController : UIViewController
7 15
 
8 16
 @property (nonatomic) NSMutableDictionary *navigatorStyle;
9 17
 @property (nonatomic) BOOL navBarHidden;
18
+@property (nonatomic, strong) NSString *commandType;
19
+@property (nonatomic, strong) NSString *timestamp;
10 20
 
11 21
 + (UIViewController*)controllerWithLayout:(NSDictionary *)layout globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge;
12 22
 

+ 69
- 1
ios/RCCViewController.m Visa fil

@@ -107,6 +107,23 @@ const NSInteger TRANSPARENT_NAVBAR_TAG = 78264803;
107 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 127
 - (instancetype)initWithProps:(NSDictionary *)props children:(NSArray *)children globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge
111 128
 {
112 129
   NSString *component = props[@"component"];
@@ -142,7 +159,9 @@ const NSInteger TRANSPARENT_NAVBAR_TAG = 78264803;
142 159
   self = [super init];
143 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 166
   return self;
148 167
 }
@@ -161,6 +180,10 @@ const NSInteger TRANSPARENT_NAVBAR_TAG = 78264803;
161 180
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRNReload) name:RCTJavaScriptWillStartLoadingNotification object:nil];
162 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 187
   // In order to support 3rd party native ViewControllers, we support passing a class name as a prop mamed `ExternalNativeScreenClass`
165 188
   // In this case, we create an instance and add it as a child ViewController which preserves the VC lifecycle.
166 189
   // In case some props are necessary in the native ViewController, the ExternalNativeScreenProps can be used to pass them
@@ -203,15 +226,58 @@ const NSInteger TRANSPARENT_NAVBAR_TAG = 78264803;
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 268
 - (void)viewDidAppear:(BOOL)animated
207 269
 {
208 270
   [super viewDidAppear:animated];
271
+  
272
+  [self sendGlobalScreenEvent:@"didAppear" endTimestampString:[self getTimestampString] shouldReset:YES];
209 273
   [self sendScreenChangedEvent:@"didAppear"];
274
+  
210 275
 }
211 276
 
212 277
 - (void)viewWillAppear:(BOOL)animated
213 278
 {
214 279
   [super viewWillAppear:animated];
280
+  [self sendGlobalScreenEvent:@"willAppear" endTimestampString:[self getTimestampString] shouldReset:NO];
215 281
   [self sendScreenChangedEvent:@"willAppear"];
216 282
   [self setStyleOnAppear];
217 283
 }
@@ -219,12 +285,14 @@ const NSInteger TRANSPARENT_NAVBAR_TAG = 78264803;
219 285
 - (void)viewDidDisappear:(BOOL)animated
220 286
 {
221 287
   [super viewDidDisappear:animated];
288
+  [self sendGlobalScreenEvent:@"didDisappear" endTimestampString:[self getTimestampString] shouldReset:YES];
222 289
   [self sendScreenChangedEvent:@"didDisappear"];
223 290
 }
224 291
 
225 292
 - (void)viewWillDisappear:(BOOL)animated
226 293
 {
227 294
   [super viewWillDisappear:animated];
295
+  [self sendGlobalScreenEvent:@"willDisappear" endTimestampString:[self getTimestampString] shouldReset:NO];
228 296
   [self sendScreenChangedEvent:@"willDisappear"];
229 297
   [self setStyleOnDisappear];
230 298
 }

+ 37
- 0
src/ScreenVisibilityListener.js Visa fil

@@ -0,0 +1,37 @@
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 Visa fil

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

+ 3
- 1
src/deprecated/indexDeprecated.ios.js Visa fil

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

+ 6
- 1
src/deprecated/platformSpecificDeprecated.android.js Visa fil

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

+ 7
- 3
src/deprecated/platformSpecificDeprecated.ios.js Visa fil

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