Browse Source

Wait for render android (#3517)

This commit introduces an option to control the way components are pushed into the stack.
When pushing components into a stack, they are pushed the moment they are measured.
If `waitForRender` is true, components will be pushed only after the initial render.

api:

```js
options: {
  animations: {
    push: {
      waitForRender: true
    },
    showModal: {
      waitForRender: true
    }
  }
}
```
Guy Carmeli 6 years ago
parent
commit
903601fa40
No account linked to committer's email address
30 changed files with 423 additions and 67 deletions
  1. 2
    1
      lib/android/app/src/main/java/com/reactnativenavigation/NavigationActivity.java
  2. 18
    15
      lib/android/app/src/main/java/com/reactnativenavigation/parse/AnimationOptions.java
  3. 5
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/NestedAnimationsOptions.java
  4. 4
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/StackOptionsPresenter.java
  5. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java
  6. 2
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/IReactView.java
  7. 7
    6
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java
  8. 23
    3
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java
  9. 28
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  10. 24
    8
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java
  11. 14
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalStack.java
  12. 21
    8
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java
  13. 2
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/Component.java
  14. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/ComponentLayout.java
  15. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/ExternalComponentLayout.java
  16. 19
    1
      lib/android/app/src/main/java/com/reactnativenavigation/views/StackLayout.java
  17. 12
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/toptabs/TopTabsViewPager.java
  18. 5
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleOverlay.java
  19. 5
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java
  20. 5
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestReactView.java
  21. 15
    4
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java
  22. 43
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java
  23. 26
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java
  24. 51
    17
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java
  25. 7
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java
  26. 11
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java
  27. 5
    0
      playground/src/app.js
  28. 13
    1
      playground/src/screens/ModalScreen.js
  29. 40
    0
      playground/src/screens/PushedScreen.js
  30. 1
    0
      playground/src/testIDs.js

+ 2
- 1
lib/android/app/src/main/java/com/reactnativenavigation/NavigationActivity.java View File

19
 import com.reactnativenavigation.utils.CommandListenerAdapter;
19
 import com.reactnativenavigation.utils.CommandListenerAdapter;
20
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
20
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
21
 import com.reactnativenavigation.viewcontrollers.Navigator;
21
 import com.reactnativenavigation.viewcontrollers.Navigator;
22
+import com.reactnativenavigation.viewcontrollers.modal.ModalStack;
22
 
23
 
23
 public class NavigationActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity, JsDevReloadHandler.ReloadListener {
24
 public class NavigationActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity, JsDevReloadHandler.ReloadListener {
24
     @Nullable
25
     @Nullable
29
     @Override
30
     @Override
30
     protected void onCreate(@Nullable Bundle savedInstanceState) {
31
     protected void onCreate(@Nullable Bundle savedInstanceState) {
31
         super.onCreate(savedInstanceState);
32
         super.onCreate(savedInstanceState);
32
-        navigator = new Navigator(this, new ChildControllersRegistry(), new OverlayManager());
33
+        navigator = new Navigator(this, new ChildControllersRegistry(), new ModalStack(this), new OverlayManager());
33
         getReactGateway().onActivityCreated(this);
34
         getReactGateway().onActivityCreated(this);
34
         navigator.getView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
35
         navigator.getView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
35
         setContentView(navigator.getView());
36
         setContentView(navigator.getView());

+ 18
- 15
lib/android/app/src/main/java/com/reactnativenavigation/parse/AnimationOptions.java View File

26
         AnimationOptions options = new AnimationOptions();
26
         AnimationOptions options = new AnimationOptions();
27
         if (json == null) return options;
27
         if (json == null) return options;
28
 
28
 
29
-        options.hasValue = true;
30
         for (Iterator<String> it = json.keys(); it.hasNext(); ) {
29
         for (Iterator<String> it = json.keys(); it.hasNext(); ) {
31
             String key = it.next();
30
             String key = it.next();
32
             switch (key) {
31
             switch (key) {
36
                 case "enable":
35
                 case "enable":
37
                     options.enable = BoolParser.parse(json, key);
36
                     options.enable = BoolParser.parse(json, key);
38
                     break;
37
                     break;
38
+                case "waitForRender":
39
+                    options.waitForRender = BoolParser.parse(json, key);
40
+                    break;
39
                 default:
41
                 default:
40
                     options.valueOptions.add(ValueAnimationOptions.parse(json.optJSONObject(key), getAnimProp(key)));
42
                     options.valueOptions.add(ValueAnimationOptions.parse(json.optJSONObject(key), getAnimProp(key)));
41
             }
43
             }
44
         return options;
46
         return options;
45
     }
47
     }
46
 
48
 
47
-    private boolean hasValue = false;
48
-
49
     public Text id = new NullText();
49
     public Text id = new NullText();
50
     public Bool enable = new NullBool();
50
     public Bool enable = new NullBool();
51
+    public Bool waitForRender = new NullBool();
51
     private HashSet<ValueAnimationOptions> valueOptions = new HashSet<>();
52
     private HashSet<ValueAnimationOptions> valueOptions = new HashSet<>();
52
 
53
 
53
     void mergeWith(AnimationOptions other) {
54
     void mergeWith(AnimationOptions other) {
54
-        if (other.hasValue()) {
55
-            hasValue = true;
56
-            id = other.id;
57
-            valueOptions = other.valueOptions;
58
-        }
55
+        if (other.id.hasValue()) id = other.id;
56
+        if (other.enable.hasValue()) enable = other.enable;
57
+        if (other.waitForRender.hasValue()) waitForRender = other.waitForRender;
58
+        if (!other.valueOptions.isEmpty()) valueOptions = other.valueOptions;
59
     }
59
     }
60
 
60
 
61
     void mergeWithDefault(AnimationOptions defaultOptions) {
61
     void mergeWithDefault(AnimationOptions defaultOptions) {
62
-        if (defaultOptions.hasValue()) {
63
-            hasValue = true;
64
-            id = defaultOptions.id;
65
-            valueOptions = defaultOptions.valueOptions;
66
-        }
62
+        if (!id.hasValue()) id = defaultOptions.id;
63
+        if (!enable.hasValue()) enable = defaultOptions.enable;
64
+        if (!waitForRender.hasValue()) waitForRender = defaultOptions.waitForRender;
65
+        if (valueOptions.isEmpty()) valueOptions = defaultOptions.valueOptions;
67
     }
66
     }
68
 
67
 
69
     public boolean hasValue() {
68
     public boolean hasValue() {
70
-        return hasValue;
69
+        return id.hasValue() || enable.hasValue() || waitForRender.hasValue();
71
     }
70
     }
72
 
71
 
73
     public AnimatorSet getAnimation(View view) {
72
     public AnimatorSet getAnimation(View view) {
75
     }
74
     }
76
 
75
 
77
     public AnimatorSet getAnimation(View view, AnimatorSet defaultAnimation) {
76
     public AnimatorSet getAnimation(View view, AnimatorSet defaultAnimation) {
78
-        if (!hasValue()) return defaultAnimation;
77
+        if (!hasAnimation()) return defaultAnimation;
79
         AnimatorSet animationSet = new AnimatorSet();
78
         AnimatorSet animationSet = new AnimatorSet();
80
         List<Animator> animators = new ArrayList<>();
79
         List<Animator> animators = new ArrayList<>();
81
         for (ValueAnimationOptions options : valueOptions) {
80
         for (ValueAnimationOptions options : valueOptions) {
106
         }
105
         }
107
         throw new IllegalArgumentException("This animation is not supported: " + key);
106
         throw new IllegalArgumentException("This animation is not supported: " + key);
108
     }
107
     }
108
+
109
+    public boolean hasAnimation() {
110
+        return !valueOptions.isEmpty();
111
+    }
109
 }
112
 }

+ 5
- 1
lib/android/app/src/main/java/com/reactnativenavigation/parse/NestedAnimationsOptions.java View File

16
         options.bottomTabs = AnimationOptions.parse(json.optJSONObject("bottomTabs"));
16
         options.bottomTabs = AnimationOptions.parse(json.optJSONObject("bottomTabs"));
17
         options.topBar = AnimationOptions.parse(json.optJSONObject("topBar"));
17
         options.topBar = AnimationOptions.parse(json.optJSONObject("topBar"));
18
         options.enable = BoolParser.parse(json, "enable");
18
         options.enable = BoolParser.parse(json, "enable");
19
+        options.waitForRender = BoolParser.parse(json, "waitForRender");
19
 
20
 
20
         return options;
21
         return options;
21
     }
22
     }
22
 
23
 
23
     public Bool enable = new NullBool();
24
     public Bool enable = new NullBool();
25
+    public Bool waitForRender = new NullBool();
24
     public AnimationOptions content = new AnimationOptions();
26
     public AnimationOptions content = new AnimationOptions();
25
     public AnimationOptions bottomTabs = new AnimationOptions();
27
     public AnimationOptions bottomTabs = new AnimationOptions();
26
     public AnimationOptions topBar = new AnimationOptions();
28
     public AnimationOptions topBar = new AnimationOptions();
30
         content.mergeWith(other.content);
32
         content.mergeWith(other.content);
31
         bottomTabs.mergeWith(other.bottomTabs);
33
         bottomTabs.mergeWith(other.bottomTabs);
32
         if (other.enable.hasValue()) enable = other.enable;
34
         if (other.enable.hasValue()) enable = other.enable;
35
+        if (other.waitForRender.hasValue()) waitForRender = other.waitForRender;
33
     }
36
     }
34
 
37
 
35
     void mergeWithDefault(NestedAnimationsOptions defaultOptions) {
38
     void mergeWithDefault(NestedAnimationsOptions defaultOptions) {
37
         bottomTabs.mergeWithDefault(defaultOptions.bottomTabs);
40
         bottomTabs.mergeWithDefault(defaultOptions.bottomTabs);
38
         topBar.mergeWithDefault(defaultOptions.topBar);
41
         topBar.mergeWithDefault(defaultOptions.topBar);
39
         if (!enable.hasValue()) enable = defaultOptions.enable;
42
         if (!enable.hasValue()) enable = defaultOptions.enable;
43
+        if (!waitForRender.hasValue()) waitForRender = defaultOptions.waitForRender;
40
     }
44
     }
41
 
45
 
42
     public boolean hasValue() {
46
     public boolean hasValue() {
43
-        return topBar.hasValue() || content.hasValue() || bottomTabs.hasValue();
47
+        return topBar.hasValue() || content.hasValue() || bottomTabs.hasValue() || waitForRender.hasValue();
44
     }
48
     }
45
 }
49
 }

+ 4
- 0
lib/android/app/src/main/java/com/reactnativenavigation/presentation/StackOptionsPresenter.java View File

38
         this.defaultOptions = defaultOptions;
38
         this.defaultOptions = defaultOptions;
39
     }
39
     }
40
 
40
 
41
+    public Options getDefaultOptions() {
42
+        return defaultOptions;
43
+    }
44
+
41
     public void bindView(TopBar topBar) {
45
     public void bindView(TopBar topBar) {
42
         this.topBar = topBar;
46
         this.topBar = topBar;
43
     }
47
     }

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

92
         jsTouchDispatcher.handleTouchEvent(event, getEventDispatcher());
92
         jsTouchDispatcher.handleTouchEvent(event, getEventDispatcher());
93
     }
93
     }
94
 
94
 
95
+    @Override
96
+    public boolean isRendered() {
97
+        return getChildCount() >= 1;
98
+    }
99
+
95
     public EventDispatcher getEventDispatcher() {
100
     public EventDispatcher getEventDispatcher() {
96
         ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
101
         ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
97
         return reactContext == null ? null : reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
102
         return reactContext == null ? null : reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();

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

20
     ScrollEventListener getScrollEventListener();
20
     ScrollEventListener getScrollEventListener();
21
 
21
 
22
     void dispatchTouchEventToJs(MotionEvent event);
22
     void dispatchTouchEventToJs(MotionEvent event);
23
+
24
+    boolean isRendered();
23
 }
25
 }

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

8
 import android.view.ViewGroup;
8
 import android.view.ViewGroup;
9
 import android.widget.FrameLayout;
9
 import android.widget.FrameLayout;
10
 
10
 
11
-import com.reactnativenavigation.anim.ModalAnimator;
12
 import com.reactnativenavigation.anim.NavigationAnimator;
11
 import com.reactnativenavigation.anim.NavigationAnimator;
13
 import com.reactnativenavigation.parse.Options;
12
 import com.reactnativenavigation.parse.Options;
14
 import com.reactnativenavigation.presentation.OptionsPresenter;
13
 import com.reactnativenavigation.presentation.OptionsPresenter;
16
 import com.reactnativenavigation.utils.CommandListener;
15
 import com.reactnativenavigation.utils.CommandListener;
17
 import com.reactnativenavigation.utils.CommandListenerAdapter;
16
 import com.reactnativenavigation.utils.CommandListenerAdapter;
18
 import com.reactnativenavigation.utils.CompatUtils;
17
 import com.reactnativenavigation.utils.CompatUtils;
19
-import com.reactnativenavigation.viewcontrollers.modal.ModalPresenter;
20
 import com.reactnativenavigation.viewcontrollers.modal.ModalStack;
18
 import com.reactnativenavigation.viewcontrollers.modal.ModalStack;
21
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
19
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
22
 
20
 
35
     @Override
33
     @Override
36
     public void setDefaultOptions(Options defaultOptions) {
34
     public void setDefaultOptions(Options defaultOptions) {
37
         this.defaultOptions = defaultOptions;
35
         this.defaultOptions = defaultOptions;
38
-        if (root != null) root.setDefaultOptions(defaultOptions);
36
+        if (root != null) {
37
+            root.setDefaultOptions(defaultOptions);
38
+            modalStack.setDefaultOptions(defaultOptions);
39
+        }
39
     }
40
     }
40
 
41
 
41
     public Options getDefaultOptions() {
42
     public Options getDefaultOptions() {
42
         return defaultOptions;
43
         return defaultOptions;
43
     }
44
     }
44
 
45
 
45
-    public Navigator(final Activity activity, ChildControllersRegistry childRegistry, OverlayManager overlayManager) {
46
+    public Navigator(final Activity activity, ChildControllersRegistry childRegistry, ModalStack modalStack, OverlayManager overlayManager) {
46
         super(activity, childRegistry,"navigator" + CompatUtils.generateViewId(), new OptionsPresenter(activity, new Options()), new Options());
47
         super(activity, childRegistry,"navigator" + CompatUtils.generateViewId(), new OptionsPresenter(activity, new Options()), new Options());
47
-        modalStack = new ModalStack(new ModalPresenter(new ModalAnimator(activity)));
48
+        this.modalStack = modalStack;
48
         this.overlayManager = overlayManager;
49
         this.overlayManager = overlayManager;
49
     }
50
     }
50
 
51
 
105
         destroyRoot();
106
         destroyRoot();
106
         root = viewController;
107
         root = viewController;
107
         contentLayout.addView(viewController.getView());
108
         contentLayout.addView(viewController.getView());
108
-        if (viewController.options.animations.startApp.hasValue()) {
109
+        if (viewController.options.animations.startApp.hasAnimation()) {
109
             new NavigationAnimator(viewController.getActivity(), viewController.options.animations)
110
             new NavigationAnimator(viewController.getActivity(), viewController.options.animations)
110
                     .animateStartApp(viewController.getView(), new AnimatorListenerAdapter() {
111
                     .animateStartApp(viewController.getView(), new AnimatorListenerAdapter() {
111
                         @Override
112
                         @Override

+ 23
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java View File

9
 import android.view.ViewGroup;
9
 import android.view.ViewGroup;
10
 
10
 
11
 import com.reactnativenavigation.parse.Options;
11
 import com.reactnativenavigation.parse.Options;
12
+import com.reactnativenavigation.parse.params.Bool;
12
 import com.reactnativenavigation.presentation.OptionsPresenter;
13
 import com.reactnativenavigation.presentation.OptionsPresenter;
13
 import com.reactnativenavigation.utils.CollectionUtils;
14
 import com.reactnativenavigation.utils.CollectionUtils;
14
 import com.reactnativenavigation.views.Component;
15
 import com.reactnativenavigation.views.Component;
21
 		super(activity, childRegistry, id, presenter, initialOptions);
22
 		super(activity, childRegistry, id, presenter, initialOptions);
22
 	}
23
 	}
23
 
24
 
24
-	@Override
25
+    @Override
26
+    public void setWaitForRender(Bool waitForRender) {
27
+        super.setWaitForRender(waitForRender);
28
+        applyOnController(getCurrentChild(), controller -> ((ViewController) controller).setWaitForRender(waitForRender));
29
+    }
30
+
31
+    @Override
25
     public void setDefaultOptions(Options defaultOptions) {
32
     public void setDefaultOptions(Options defaultOptions) {
26
         Collection<? extends ViewController> children = getChildControllers();
33
         Collection<? extends ViewController> children = getChildControllers();
27
         if (!CollectionUtils.isNullOrEmpty(children)) {
34
         if (!CollectionUtils.isNullOrEmpty(children)) {
35
     @CheckResult
42
     @CheckResult
36
     public Options resolveCurrentOptions() {
43
     public Options resolveCurrentOptions() {
37
 	    if (CollectionUtils.isNullOrEmpty(getChildControllers())) return options;
44
 	    if (CollectionUtils.isNullOrEmpty(getChildControllers())) return options;
38
-        Options o = getCurrentChild().resolveCurrentOptions();
39
-        return o.copy().mergeWith(options);
45
+        return getCurrentChild()
46
+                .resolveCurrentOptions()
47
+                .copy()
48
+                .mergeWith(options);
49
+    }
50
+
51
+    @Override
52
+    @CheckResult
53
+    public Options resolveCurrentOptions(Options defaultOptions) {
54
+        return resolveCurrentOptions().withDefaultOptions(defaultOptions);
40
     }
55
     }
41
 
56
 
42
     protected abstract ViewController getCurrentChild();
57
     protected abstract ViewController getCurrentChild();
113
     public void clearTopTabs() {
128
     public void clearTopTabs() {
114
 
129
 
115
     }
130
     }
131
+
132
+    @Override
133
+    public boolean isRendered() {
134
+        return getCurrentChild() != null && getCurrentChild().isRendered();
135
+    }
116
 }
136
 }

+ 28
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java View File

12
 import android.view.ViewTreeObserver;
12
 import android.view.ViewTreeObserver;
13
 
13
 
14
 import com.reactnativenavigation.parse.Options;
14
 import com.reactnativenavigation.parse.Options;
15
+import com.reactnativenavigation.parse.params.Bool;
16
+import com.reactnativenavigation.parse.params.NullBool;
15
 import com.reactnativenavigation.presentation.FabOptionsPresenter;
17
 import com.reactnativenavigation.presentation.FabOptionsPresenter;
16
 import com.reactnativenavigation.utils.CommandListener;
18
 import com.reactnativenavigation.utils.CommandListener;
17
 import com.reactnativenavigation.utils.StringUtils;
19
 import com.reactnativenavigation.utils.StringUtils;
23
 public abstract class ViewController<T extends ViewGroup> implements ViewTreeObserver.OnGlobalLayoutListener {
25
 public abstract class ViewController<T extends ViewGroup> implements ViewTreeObserver.OnGlobalLayoutListener {
24
 
26
 
25
     private Runnable onAppearedListener;
27
     private Runnable onAppearedListener;
28
+    private Bool waitForRender = new NullBool();
26
 
29
 
27
     public interface ViewVisibilityListener {
30
     public interface ViewVisibilityListener {
28
         /**
31
         /**
56
         options = initialOptions.copy();
59
         options = initialOptions.copy();
57
     }
60
     }
58
 
61
 
62
+    public void setWaitForRender(Bool waitForRender) {
63
+        this.waitForRender = waitForRender;
64
+    }
65
+
59
     public void setOnAppearedListener(Runnable onAppearedListener) {
66
     public void setOnAppearedListener(Runnable onAppearedListener) {
60
         this.onAppearedListener = onAppearedListener;
67
         this.onAppearedListener = onAppearedListener;
61
     }
68
     }
80
         return options;
87
         return options;
81
     }
88
     }
82
 
89
 
90
+    @CheckResult
91
+    public Options resolveCurrentOptions(Options defaultOptions) {
92
+        return options.copy().withDefaultOptions(defaultOptions);
93
+    }
94
+
83
     @CallSuper
95
     @CallSuper
84
     public void mergeOptions(Options options) {
96
     public void mergeOptions(Options options) {
85
         this.options = this.options.mergeWith(options);
97
         this.options = this.options.mergeWith(options);
235
     public abstract void sendOnNavigationButtonPressed(String buttonId);
247
     public abstract void sendOnNavigationButtonPressed(String buttonId);
236
 
248
 
237
     protected boolean isViewShown() {
249
     protected boolean isViewShown() {
238
-        return !isDestroyed && getView().isShown();
250
+        return !isDestroyed &&
251
+               getView().isShown() &&
252
+               view != null &&
253
+               isRendered();
254
+    }
255
+
256
+    public boolean isRendered() {
257
+        return view != null && (
258
+                waitForRender.isFalseOrUndefined() ||
259
+                !(view instanceof Component) ||
260
+                ((Component) view).isRendered()
261
+        );
262
+    }
263
+
264
+    void applyOnController(ViewController controller, Task<ViewController> task) {
265
+        if (controller != null) task.run(controller);
239
     }
266
     }
240
 }
267
 }

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

7
 
7
 
8
 import com.reactnativenavigation.anim.ModalAnimator;
8
 import com.reactnativenavigation.anim.ModalAnimator;
9
 import com.reactnativenavigation.parse.ModalPresentationStyle;
9
 import com.reactnativenavigation.parse.ModalPresentationStyle;
10
+import com.reactnativenavigation.parse.Options;
10
 import com.reactnativenavigation.utils.CommandListener;
11
 import com.reactnativenavigation.utils.CommandListener;
11
 import com.reactnativenavigation.viewcontrollers.ViewController;
12
 import com.reactnativenavigation.viewcontrollers.ViewController;
12
 
13
 
14
 
15
 
15
     private ViewGroup content;
16
     private ViewGroup content;
16
     private ModalAnimator animator;
17
     private ModalAnimator animator;
18
+    private Options defaultOptions = new Options();
17
 
19
 
18
-    public ModalPresenter(ModalAnimator animator) {
20
+    ModalPresenter(ModalAnimator animator) {
19
         this.animator = animator;
21
         this.animator = animator;
20
     }
22
     }
21
 
23
 
23
         this.content = contentLayout;
25
         this.content = contentLayout;
24
     }
26
     }
25
 
27
 
28
+    public void setDefaultOptions(Options defaultOptions) {
29
+        this.defaultOptions = defaultOptions;
30
+    }
31
+
26
     public void showModal(ViewController toAdd, ViewController toRemove, CommandListener listener) {
32
     public void showModal(ViewController toAdd, ViewController toRemove, CommandListener listener) {
33
+        Options options = toAdd.resolveCurrentOptions(defaultOptions);
34
+        toAdd.setWaitForRender(options.animations.showModal.waitForRender);
27
         content.addView(toAdd.getView());
35
         content.addView(toAdd.getView());
28
-        if (toAdd.options.animations.showModal.enable.isTrueOrUndefined()) {
29
-            animator.show(toAdd.getView(), toAdd.options.animations.showModal, new AnimatorListenerAdapter() {
30
-                @Override
31
-                public void onAnimationEnd(Animator animation) {
32
-                    onShowModalEnd(toAdd, toRemove, listener);
33
-                }
34
-            });
36
+        if (options.animations.showModal.enable.isTrueOrUndefined()) {
37
+            if (options.animations.showModal.waitForRender.isTrue()) {
38
+                toAdd.setOnAppearedListener(() -> animateShow(toAdd, toRemove, listener, options));
39
+            } else {
40
+                animateShow(toAdd, toRemove, listener, options);
41
+            }
35
         } else {
42
         } else {
36
             onShowModalEnd(toAdd, toRemove, listener);
43
             onShowModalEnd(toAdd, toRemove, listener);
37
         }
44
         }
38
     }
45
     }
39
 
46
 
47
+    private void animateShow(ViewController toAdd, ViewController toRemove, CommandListener listener, Options options) {
48
+        animator.show(toAdd.getView(), options.animations.showModal, new AnimatorListenerAdapter() {
49
+            @Override
50
+            public void onAnimationEnd(Animator animation) {
51
+                onShowModalEnd(toAdd, toRemove, listener);
52
+            }
53
+        });
54
+    }
55
+
40
     private void onShowModalEnd(ViewController toAdd, ViewController toRemove, CommandListener listener) {
56
     private void onShowModalEnd(ViewController toAdd, ViewController toRemove, CommandListener listener) {
41
         if (toAdd.options.modal.presentationStyle != ModalPresentationStyle.OverCurrentContext) {
57
         if (toAdd.options.modal.presentationStyle != ModalPresentationStyle.OverCurrentContext) {
42
             toRemove.detachView();
58
             toRemove.detachView();

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

1
 package com.reactnativenavigation.viewcontrollers.modal;
1
 package com.reactnativenavigation.viewcontrollers.modal;
2
 
2
 
3
+import android.app.Activity;
4
+import android.support.annotation.RestrictTo;
3
 import android.view.ViewGroup;
5
 import android.view.ViewGroup;
4
 
6
 
7
+import com.reactnativenavigation.anim.ModalAnimator;
8
+import com.reactnativenavigation.parse.Options;
5
 import com.reactnativenavigation.utils.CommandListener;
9
 import com.reactnativenavigation.utils.CommandListener;
6
 import com.reactnativenavigation.viewcontrollers.ViewController;
10
 import com.reactnativenavigation.viewcontrollers.ViewController;
7
 
11
 
15
     private List<ViewController> modals = new ArrayList<>();
19
     private List<ViewController> modals = new ArrayList<>();
16
     private final ModalPresenter presenter;
20
     private final ModalPresenter presenter;
17
 
21
 
18
-    public ModalStack(ModalPresenter presenter) {
22
+    public ModalStack(Activity activity) {
23
+        this.presenter = new ModalPresenter(new ModalAnimator(activity));
24
+    }
25
+
26
+    @RestrictTo(RestrictTo.Scope.TESTS)
27
+    ModalStack(ModalPresenter presenter) {
19
         this.presenter = presenter;
28
         this.presenter = presenter;
20
     }
29
     }
21
 
30
 
23
         presenter.setContentLayout(contentLayout);
32
         presenter.setContentLayout(contentLayout);
24
     }
33
     }
25
 
34
 
35
+    public void setDefaultOptions(Options defaultOptions) {
36
+        presenter.setDefaultOptions(defaultOptions);
37
+    }
38
+
26
     public void showModal(ViewController viewController, ViewController root, CommandListener listener) {
39
     public void showModal(ViewController viewController, ViewController root, CommandListener listener) {
27
         ViewController toRemove = isEmpty() ? root : peek();
40
         ViewController toRemove = isEmpty() ? root : peek();
28
         modals.add(viewController);
41
         modals.add(viewController);

+ 21
- 8
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java View File

5
 import android.support.annotation.RestrictTo;
5
 import android.support.annotation.RestrictTo;
6
 import android.support.annotation.VisibleForTesting;
6
 import android.support.annotation.VisibleForTesting;
7
 import android.support.v4.view.ViewPager;
7
 import android.support.v4.view.ViewPager;
8
+import android.view.View;
8
 import android.view.ViewGroup;
9
 import android.view.ViewGroup;
9
 import android.widget.RelativeLayout;
10
 import android.widget.RelativeLayout;
10
 
11
 
130
         child.setParentController(this);
131
         child.setParentController(this);
131
         stack.push(child.getId(), child);
132
         stack.push(child.getId(), child);
132
         backButtonHelper.addToPushedChild(this, child);
133
         backButtonHelper.addToPushedChild(this, child);
133
-        ViewGroup childView = child.getView();
134
-        childView.setLayoutParams(new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
135
-        presenter.applyLayoutParamsOptions(resolveCurrentOptions(), childView);
136
-        getView().addView(childView);
134
+        Options resolvedOptions = resolveCurrentOptions(presenter.getDefaultOptions());
135
+        addChildToStack(child, child.getView(), resolvedOptions);
137
 
136
 
138
         if (toRemove != null) {
137
         if (toRemove != null) {
139
             if (child.options.animations.push.enable.isTrueOrUndefined()) {
138
             if (child.options.animations.push.enable.isTrueOrUndefined()) {
140
-                animator.push(child.getView(), () -> {
141
-                    getView().removeView(toRemove.getView());
142
-                    listener.onSuccess(child.getId());
143
-                });
139
+                if (resolvedOptions.animations.push.waitForRender.isTrue()) {
140
+                    child.setOnAppearedListener(() -> animator.push(child.getView(), () -> {
141
+                        getView().removeView(toRemove.getView());
142
+                        listener.onSuccess(child.getId());
143
+                    }));
144
+                } else {
145
+                    animator.push(child.getView(), () -> {
146
+                        getView().removeView(toRemove.getView());
147
+                        listener.onSuccess(child.getId());
148
+                    });
149
+                }
144
             } else {
150
             } else {
145
                 getView().removeView(toRemove.getView());
151
                 getView().removeView(toRemove.getView());
146
                 listener.onSuccess(child.getId());
152
                 listener.onSuccess(child.getId());
150
         }
156
         }
151
     }
157
     }
152
 
158
 
159
+    private void addChildToStack(ViewController child, View view, Options resolvedOptions) {
160
+        view.setLayoutParams(new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
161
+        child.setWaitForRender(resolvedOptions.animations.push.waitForRender);
162
+        presenter.applyLayoutParamsOptions(resolvedOptions, view);
163
+        getView().addView(view);
164
+    }
165
+
153
     public void setRoot(ViewController child, CommandListener listener) {
166
     public void setRoot(ViewController child, CommandListener listener) {
154
         backButtonHelper.clear(child);
167
         backButtonHelper.clear(child);
155
         push(child, new CommandListenerAdapter() {
168
         push(child, new CommandListenerAdapter() {

+ 2
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/Component.java View File

6
     void drawBehindTopBar();
6
     void drawBehindTopBar();
7
 
7
 
8
     void drawBelowTopBar(TopBar topBar);
8
     void drawBelowTopBar(TopBar topBar);
9
+
10
+    boolean isRendered();
9
 }
11
 }

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

99
         }
99
         }
100
     }
100
     }
101
 
101
 
102
+    @Override
103
+    public boolean isRendered() {
104
+        return reactView.isRendered();
105
+    }
106
+
102
     @Override
107
     @Override
103
     public void onPress(String buttonId) {
108
     public void onPress(String buttonId) {
104
         reactView.sendOnNavigationButtonPressed(buttonId);
109
         reactView.sendOnNavigationButtonPressed(buttonId);

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

33
             setLayoutParams(layoutParams);
33
             setLayoutParams(layoutParams);
34
         }
34
         }
35
     }
35
     }
36
+
37
+    @Override
38
+    public boolean isRendered() {
39
+        return getChildCount() >= 1;
40
+    }
36
 }
41
 }

+ 19
- 1
lib/android/app/src/main/java/com/reactnativenavigation/views/StackLayout.java View File

9
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarBackgroundViewController;
9
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarBackgroundViewController;
10
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
10
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
11
 import com.reactnativenavigation.views.titlebar.TitleBarReactViewCreator;
11
 import com.reactnativenavigation.views.titlebar.TitleBarReactViewCreator;
12
+import com.reactnativenavigation.views.topbar.TopBar;
12
 
13
 
13
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
14
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
14
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
15
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
15
 
16
 
16
 @SuppressLint("ViewConstructor")
17
 @SuppressLint("ViewConstructor")
17
-public class StackLayout extends RelativeLayout {
18
+public class StackLayout extends RelativeLayout implements Component {
18
     private String stackId;
19
     private String stackId;
19
 
20
 
20
     public StackLayout(Context context, ReactViewCreator topBarButtonCreator, TitleBarReactViewCreator titleBarReactViewCreator, TopBarBackgroundViewController topBarBackgroundViewController, TopBarController topBarController, TopBarButtonController.OnClickListener topBarButtonClickListener, String stackId) {
21
     public StackLayout(Context context, ReactViewCreator topBarButtonCreator, TitleBarReactViewCreator titleBarReactViewCreator, TopBarBackgroundViewController topBarBackgroundViewController, TopBarController topBarController, TopBarButtonController.OnClickListener topBarButtonClickListener, String stackId) {
31
     public String getStackId() {
32
     public String getStackId() {
32
         return stackId;
33
         return stackId;
33
     }
34
     }
35
+
36
+    @Override
37
+    public void drawBehindTopBar() {
38
+
39
+    }
40
+
41
+    @Override
42
+    public void drawBelowTopBar(TopBar topBar) {
43
+
44
+    }
45
+
46
+    @Override
47
+    public boolean isRendered() {
48
+        return getChildCount() >= 2 &&
49
+               getChildAt(1) instanceof Component &&
50
+               ((Component) getChildAt(1)).isRendered();
51
+    }
34
 }
52
 }

+ 12
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/toptabs/TopTabsViewPager.java View File

55
         setLayoutParams(layoutParams);
55
         setLayoutParams(layoutParams);
56
     }
56
     }
57
 
57
 
58
+    @Override
59
+    public boolean isRendered() {
60
+        return tabs.size() != 0 && areAllTabsRendered();
61
+    }
62
+
63
+    private boolean areAllTabsRendered() {
64
+        for (ViewController tab : tabs) {
65
+            if (!tab.isRendered()) return false;
66
+        }
67
+        return true;
68
+    }
69
+
58
     public void switchToTab(int index) {
70
     public void switchToTab(int index) {
59
         setCurrentItem(index);
71
         setCurrentItem(index);
60
     }
72
     }

+ 5
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleOverlay.java View File

56
     public void dispatchTouchEventToJs(MotionEvent event) {
56
     public void dispatchTouchEventToJs(MotionEvent event) {
57
 
57
 
58
     }
58
     }
59
+
60
+    @Override
61
+    public boolean isRendered() {
62
+        return getChildCount() >= 1;
63
+    }
59
 }
64
 }

+ 5
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java View File

77
             }
77
             }
78
         }
78
         }
79
 
79
 
80
+        @Override
81
+        public boolean isRendered() {
82
+            return getChildCount() >= 1;
83
+        }
84
+
80
         @Override
85
         @Override
81
         public boolean isReady() {
86
         public boolean isReady() {
82
             return false;
87
             return false;

+ 5
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestReactView.java View File

54
     public void dispatchTouchEventToJs(MotionEvent event) {
54
     public void dispatchTouchEventToJs(MotionEvent event) {
55
 
55
 
56
     }
56
     }
57
+
58
+    @Override
59
+    public boolean isRendered() {
60
+        return getChildCount() >= 1;
61
+    }
57
 }
62
 }

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

23
 import com.reactnativenavigation.utils.ImageLoader;
23
 import com.reactnativenavigation.utils.ImageLoader;
24
 import com.reactnativenavigation.utils.OptionHelper;
24
 import com.reactnativenavigation.utils.OptionHelper;
25
 import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController;
25
 import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController;
26
+import com.reactnativenavigation.viewcontrollers.modal.ModalStack;
26
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
27
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
27
 
28
 
28
 import org.junit.Test;
29
 import org.junit.Test;
55
     private OverlayManager overlayManager;
56
     private OverlayManager overlayManager;
56
     private EventEmitter eventEmitter;
57
     private EventEmitter eventEmitter;
57
     private ViewController.ViewVisibilityListener parentVisibilityListener;
58
     private ViewController.ViewVisibilityListener parentVisibilityListener;
59
+    private ModalStack modalStack;
58
 
60
 
59
     @Override
61
     @Override
60
     public void beforeEach() {
62
     public void beforeEach() {
64
         imageLoaderMock = ImageLoaderMock.mock();
66
         imageLoaderMock = ImageLoaderMock.mock();
65
         activityController = newActivityController(TestActivity.class);
67
         activityController = newActivityController(TestActivity.class);
66
         activity = activityController.create().get();
68
         activity = activityController.create().get();
67
-        uut = new Navigator(activity, childRegistry, overlayManager);
69
+        modalStack = spy(new ModalStack(activity));
70
+        uut = new Navigator(activity, childRegistry, modalStack, overlayManager);
68
         activity.setNavigator(uut);
71
         activity.setNavigator(uut);
69
 
72
 
70
         parentController = newStack();
73
         parentController = newStack();
100
         Options defaultOptions = new Options();
103
         Options defaultOptions = new Options();
101
         uut.setDefaultOptions(defaultOptions);
104
         uut.setDefaultOptions(defaultOptions);
102
         verify(spy, times(1)).setDefaultOptions(defaultOptions);
105
         verify(spy, times(1)).setDefaultOptions(defaultOptions);
106
+        verify(modalStack).setDefaultOptions(defaultOptions);
103
     }
107
     }
104
 
108
 
105
     @Test
109
     @Test
119
     @Test
123
     @Test
120
     public void hasUniqueId() {
124
     public void hasUniqueId() {
121
         assertThat(uut.getId()).startsWith("navigator");
125
         assertThat(uut.getId()).startsWith("navigator");
122
-        assertThat(new Navigator(activity, childRegistry, overlayManager).getId()).isNotEqualTo(uut.getId());
126
+        assertThat(new Navigator(activity, childRegistry, modalStack, overlayManager).getId()).isNotEqualTo(uut.getId());
123
     }
127
     }
124
 
128
 
125
     @Test
129
     @Test
429
 
433
 
430
     @Test
434
     @Test
431
     public void dismissModal_onViewAppearedInvokedOnRoot() {
435
     public void dismissModal_onViewAppearedInvokedOnRoot() {
432
-        disableShowModalAnimation(child1, child2);
436
+        disableShowModalAnimation(child1, child2, child3);
433
         disableDismissModalAnimation(child1, child2);
437
         disableDismissModalAnimation(child1, child2);
434
 
438
 
435
         uut.setRoot(parentController, new CommandListenerAdapter());
439
         uut.setRoot(parentController, new CommandListenerAdapter());
440
+        parentController.push(child3, new CommandListenerAdapter());
436
         uut.showModal(child1, new CommandListenerAdapter());
441
         uut.showModal(child1, new CommandListenerAdapter());
437
         uut.showModal(child2, new CommandListenerAdapter());
442
         uut.showModal(child2, new CommandListenerAdapter());
438
 
443
 
448
 
453
 
449
     @Test
454
     @Test
450
     public void dismissAllModals_onViewAppearedInvokedOnRoot() {
455
     public void dismissAllModals_onViewAppearedInvokedOnRoot() {
456
+        disablePushAnimation(child2);
451
         disableShowModalAnimation(child1);
457
         disableShowModalAnimation(child1);
452
 
458
 
453
         uut.dismissAllModals(new CommandListenerAdapter());
459
         uut.dismissAllModals(new CommandListenerAdapter());
454
         verify(parentVisibilityListener, times(0)).onViewAppeared(parentController.getView());
460
         verify(parentVisibilityListener, times(0)).onViewAppeared(parentController.getView());
455
 
461
 
456
         uut.setRoot(parentController, new CommandListenerAdapter());
462
         uut.setRoot(parentController, new CommandListenerAdapter());
463
+        parentController.push(child2, new CommandListenerAdapter());
457
 
464
 
458
         verify(parentVisibilityListener, times(1)).onViewAppeared(parentController.getView());
465
         verify(parentVisibilityListener, times(1)).onViewAppeared(parentController.getView());
459
         uut.showModal(child1, new CommandListenerAdapter());
466
         uut.showModal(child1, new CommandListenerAdapter());
464
 
471
 
465
     @Test
472
     @Test
466
     public void handleBack_onViewAppearedInvokedOnRoot() {
473
     public void handleBack_onViewAppearedInvokedOnRoot() {
467
-        disableShowModalAnimation(child1, child2);
474
+        disableShowModalAnimation(child1, child2, child3);
468
 
475
 
476
+        parentController.push(child3, new CommandListenerAdapter());
469
         StackController spy = spy(parentController);
477
         StackController spy = spy(parentController);
470
         uut.setRoot(spy, new CommandListenerAdapter());
478
         uut.setRoot(spy, new CommandListenerAdapter());
471
         uut.showModal(child1, new CommandListenerAdapter());
479
         uut.showModal(child1, new CommandListenerAdapter());
485
 
493
 
486
     @Test
494
     @Test
487
     public void destroy_destroyedRoot() {
495
     public void destroy_destroyedRoot() {
496
+        disablePushAnimation(child1);
497
+
488
         StackController spy = spy(parentController);
498
         StackController spy = spy(parentController);
489
         spy.options.animations.startApp.enable = new Bool(false);
499
         spy.options.animations.startApp.enable = new Bool(false);
490
         uut.setRoot(spy, new CommandListenerAdapter());
500
         uut.setRoot(spy, new CommandListenerAdapter());
501
+        spy.push(child1, new CommandListenerAdapter());
491
         activityController.destroy();
502
         activityController.destroy();
492
         verify(spy, times(1)).destroy();
503
         verify(spy, times(1)).destroy();
493
     }
504
     }

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

27
 import static org.mockito.Mockito.spy;
27
 import static org.mockito.Mockito.spy;
28
 import static org.mockito.Mockito.times;
28
 import static org.mockito.Mockito.times;
29
 import static org.mockito.Mockito.verify;
29
 import static org.mockito.Mockito.verify;
30
+import static org.mockito.Mockito.when;
30
 
31
 
31
 public class ParentControllerTest extends BaseTest {
32
 public class ParentControllerTest extends BaseTest {
32
 
33
 
182
         uut.applyChildOptions(options, child1.getView());
183
         uut.applyChildOptions(options, child1.getView());
183
         verify(presenter, times(0)).applyRootOptions(uut.getView(), options);
184
         verify(presenter, times(0)).applyRootOptions(uut.getView(), options);
184
     }
185
     }
186
+
187
+    @Test
188
+    public void resolveCurrentOptions_returnOptionsIfNoChildren() {
189
+        assertThat(uut.getChildControllers().size()).isZero();
190
+        assertThat(uut.resolveCurrentOptions()).isEqualTo(uut.options);
191
+    }
192
+
193
+    @Test
194
+    public void resolveCurrentOptions_mergesWithCurrentChild() {
195
+        ViewController child1 = Mockito.mock(ViewController.class);
196
+        when(child1.getView()).thenReturn(new FrameLayout(activity));
197
+        Options copiedChildOptions = spy(new Options());
198
+        Options childOptions = spy(new Options() {
199
+            @Override
200
+            public Options copy() {
201
+                return copiedChildOptions;
202
+            }
203
+        });
204
+        when(child1.resolveCurrentOptions()).thenReturn(childOptions);
205
+
206
+        children.add(child1);
207
+
208
+        uut.ensureViewIsCreated();
209
+        assertThat(uut.getCurrentChild()).isEqualTo(child1);
210
+        uut.resolveCurrentOptions();
211
+        verify(child1).resolveCurrentOptions();
212
+        verify(copiedChildOptions).mergeWith(uut.options);
213
+    }
214
+
215
+    @Test
216
+    public void resolveCurrentOptions_withDefaultOptions() {
217
+        SimpleViewController child1 = new SimpleViewController(activity, childRegistry, "child1", new Options());
218
+        children.add(child1);
219
+        uut.ensureViewIsCreated();
220
+
221
+        Options defaultOptions = new Options();
222
+        Options currentOptions = spy(new Options());
223
+        ParentController spy = spy(uut);
224
+        Mockito.when(spy.resolveCurrentOptions()).thenReturn(currentOptions);
225
+        spy.resolveCurrentOptions(defaultOptions);
226
+        verify(currentOptions).withDefaultOptions(defaultOptions);
227
+    }
185
 }
228
 }

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

2
 
2
 
3
 import android.app.Activity;
3
 import android.app.Activity;
4
 import android.view.View;
4
 import android.view.View;
5
+import android.view.ViewGroup;
5
 import android.view.ViewParent;
6
 import android.view.ViewParent;
6
 import android.widget.FrameLayout;
7
 import android.widget.FrameLayout;
7
 import android.widget.LinearLayout;
8
 import android.widget.LinearLayout;
10
 import com.reactnativenavigation.TestUtils;
11
 import com.reactnativenavigation.TestUtils;
11
 import com.reactnativenavigation.mocks.SimpleViewController;
12
 import com.reactnativenavigation.mocks.SimpleViewController;
12
 import com.reactnativenavigation.parse.Options;
13
 import com.reactnativenavigation.parse.Options;
14
+import com.reactnativenavigation.parse.params.Bool;
15
+import com.reactnativenavigation.parse.params.NullBool;
13
 import com.reactnativenavigation.utils.CommandListenerAdapter;
16
 import com.reactnativenavigation.utils.CommandListenerAdapter;
14
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
17
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
18
+import com.reactnativenavigation.views.Component;
15
 
19
 
16
 import org.assertj.android.api.Assertions;
20
 import org.assertj.android.api.Assertions;
17
 import org.junit.Test;
21
 import org.junit.Test;
25
 import static org.mockito.Mockito.spy;
29
 import static org.mockito.Mockito.spy;
26
 import static org.mockito.Mockito.times;
30
 import static org.mockito.Mockito.times;
27
 import static org.mockito.Mockito.verify;
31
 import static org.mockito.Mockito.verify;
32
+import static org.mockito.Mockito.withSettings;
28
 
33
 
29
 public class ViewControllerTest extends BaseTest {
34
 public class ViewControllerTest extends BaseTest {
30
 
35
 
222
         spy.ensureViewIsCreated();
227
         spy.ensureViewIsCreated();
223
         verify(spy, times(1)).getView();
228
         verify(spy, times(1)).getView();
224
     }
229
     }
230
+
231
+    @Test
232
+    public void isRendered_falseIfViewIsNotCreated() {
233
+        uut.setWaitForRender(new Bool(true));
234
+        assertThat(uut.isRendered()).isFalse();
235
+    }
236
+
237
+    @Test
238
+    public void isRendered_delegatesToView() {
239
+        uut.setWaitForRender(new Bool(true));
240
+        uut.view = Mockito.mock(ViewGroup.class, withSettings().extraInterfaces(Component.class));
241
+        uut.isRendered();
242
+        verify((Component) uut.view).isRendered();
243
+    }
244
+
245
+    @Test
246
+    public void isRendered_returnsTrueForEveryViewByDefault() {
247
+        uut.setWaitForRender(new NullBool());
248
+        uut.view = Mockito.mock(ViewGroup.class);
249
+        assertThat(uut.isRendered()).isTrue();
250
+    }
225
 }
251
 }
226
 
252
 

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

6
 import com.reactnativenavigation.BaseTest;
6
 import com.reactnativenavigation.BaseTest;
7
 import com.reactnativenavigation.anim.ModalAnimator;
7
 import com.reactnativenavigation.anim.ModalAnimator;
8
 import com.reactnativenavigation.mocks.SimpleViewController;
8
 import com.reactnativenavigation.mocks.SimpleViewController;
9
+import com.reactnativenavigation.parse.AnimationOptions;
9
 import com.reactnativenavigation.parse.ModalPresentationStyle;
10
 import com.reactnativenavigation.parse.ModalPresentationStyle;
10
 import com.reactnativenavigation.parse.Options;
11
 import com.reactnativenavigation.parse.Options;
12
+import com.reactnativenavigation.parse.params.Bool;
11
 import com.reactnativenavigation.utils.CommandListener;
13
 import com.reactnativenavigation.utils.CommandListener;
12
 import com.reactnativenavigation.utils.CommandListenerAdapter;
14
 import com.reactnativenavigation.utils.CommandListenerAdapter;
13
 import com.reactnativenavigation.viewcontrollers.ChildController;
15
 import com.reactnativenavigation.viewcontrollers.ChildController;
14
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
16
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
15
 import com.reactnativenavigation.viewcontrollers.ViewController;
17
 import com.reactnativenavigation.viewcontrollers.ViewController;
16
 
18
 
19
+import org.json.JSONException;
20
+import org.json.JSONObject;
17
 import org.junit.Test;
21
 import org.junit.Test;
18
 
22
 
19
 import static org.assertj.core.api.Java6Assertions.assertThat;
23
 import static org.assertj.core.api.Java6Assertions.assertThat;
22
 import static org.mockito.Mockito.spy;
26
 import static org.mockito.Mockito.spy;
23
 import static org.mockito.Mockito.times;
27
 import static org.mockito.Mockito.times;
24
 import static org.mockito.Mockito.verify;
28
 import static org.mockito.Mockito.verify;
29
+import static org.mockito.Mockito.verifyZeroInteractions;
25
 
30
 
26
 public class ModalPresenterTest extends BaseTest {
31
 public class ModalPresenterTest extends BaseTest {
27
     private static final String MODAL_ID_1 = "modalId1";
32
     private static final String MODAL_ID_1 = "modalId1";
32
     private ModalPresenter uut;
37
     private ModalPresenter uut;
33
     private FrameLayout contentLayout;
38
     private FrameLayout contentLayout;
34
     private ModalAnimator animator;
39
     private ModalAnimator animator;
35
-    private ViewController rootController;
40
+    private ViewController root;
36
 
41
 
37
     @Override
42
     @Override
38
     public void beforeEach() {
43
     public void beforeEach() {
39
         Activity activity = newActivity();
44
         Activity activity = newActivity();
40
         ChildControllersRegistry childRegistry = new ChildControllersRegistry();
45
         ChildControllersRegistry childRegistry = new ChildControllersRegistry();
41
 
46
 
42
-        rootController = spy(new SimpleViewController(activity, childRegistry, "root", new Options()));
47
+        root = spy(new SimpleViewController(activity, childRegistry, "root", new Options()));
43
         contentLayout = new FrameLayout(activity);
48
         contentLayout = new FrameLayout(activity);
44
-        contentLayout.addView(rootController.getView());
49
+        contentLayout.addView(root.getView());
45
         activity.setContentView(contentLayout);
50
         activity.setContentView(contentLayout);
46
 
51
 
47
         animator = spy(new ModalAnimator(activity));
52
         animator = spy(new ModalAnimator(activity));
51
         modal2 = spy(new SimpleViewController(activity, childRegistry, MODAL_ID_2, new Options()));
56
         modal2 = spy(new SimpleViewController(activity, childRegistry, MODAL_ID_2, new Options()));
52
     }
57
     }
53
 
58
 
59
+    @Test
60
+    public void showModal() {
61
+        Options defaultOptions = new Options();
62
+        uut.setDefaultOptions(defaultOptions);
63
+        disableShowModalAnimation(modal1);
64
+        uut.showModal(modal1, root, new CommandListenerAdapter());
65
+        verify(modal1).setWaitForRender(any());
66
+        verify(modal1).resolveCurrentOptions(defaultOptions);
67
+    }
68
+
54
     @Test
69
     @Test
55
     public void showModal_noAnimation() {
70
     public void showModal_noAnimation() {
56
         disableShowModalAnimation(modal1);
71
         disableShowModalAnimation(modal1);
61
                 verify(modal1, times(1)).onViewAppeared();
76
                 verify(modal1, times(1)).onViewAppeared();
62
             }
77
             }
63
         });
78
         });
64
-        uut.showModal(modal1, rootController, listener);
79
+        uut.showModal(modal1, root, listener);
65
         verify(animator, times(0)).show(
80
         verify(animator, times(0)).show(
66
                 eq(modal1.getView()),
81
                 eq(modal1.getView()),
67
                 eq(modal1.options.animations.showModal),
82
                 eq(modal1.options.animations.showModal),
70
         verify(listener, times(1)).onSuccess(MODAL_ID_1);
85
         verify(listener, times(1)).onSuccess(MODAL_ID_1);
71
     }
86
     }
72
 
87
 
88
+    @Test
89
+    public void showModal_resolvesDefaultOptions() throws JSONException {
90
+        Options defaultOptions = new Options();
91
+        JSONObject disabledShowModalAnimation = new JSONObject().put("enable", false);
92
+        defaultOptions.animations.showModal = AnimationOptions.parse(disabledShowModalAnimation);
93
+
94
+        uut.setDefaultOptions(defaultOptions);
95
+        uut.showModal(modal1, root, new CommandListenerAdapter());
96
+        verifyZeroInteractions(animator);
97
+    }
98
+
73
     @Test
99
     @Test
74
     public void showModal_previousModalIsRemovedFromHierarchy() {
100
     public void showModal_previousModalIsRemovedFromHierarchy() {
75
         uut.showModal(modal1, null, new CommandListenerAdapter() {
101
         uut.showModal(modal1, null, new CommandListenerAdapter() {
102
         });
128
         });
103
     }
129
     }
104
 
130
 
131
+    @Test
132
+    public void showModal_waitForRender() {
133
+        modal1.options.animations.showModal.waitForRender = new Bool(true);
134
+        uut.showModal(modal1, root, new CommandListenerAdapter());
135
+        verify(modal1).setOnAppearedListener(any());
136
+        verifyZeroInteractions(animator);
137
+    }
138
+
105
     @Test
139
     @Test
106
     public void dismissModal_animatesByDefault() {
140
     public void dismissModal_animatesByDefault() {
107
         disableShowModalAnimation(modal1);
141
         disableShowModalAnimation(modal1);
108
 
142
 
109
-        uut.showModal(modal1, rootController, new CommandListenerAdapter());
110
-        uut.dismissTopModal(modal1, rootController, new CommandListenerAdapter() {
143
+        uut.showModal(modal1, root, new CommandListenerAdapter());
144
+        uut.dismissTopModal(modal1, root, new CommandListenerAdapter() {
111
             @Override
145
             @Override
112
             public void onSuccess(String childId) {
146
             public void onSuccess(String childId) {
113
                 verify(modal1, times(1)).onViewDisappear();
147
                 verify(modal1, times(1)).onViewDisappear();
132
         disableShowModalAnimation(modal1);
166
         disableShowModalAnimation(modal1);
133
         disableDismissModalAnimation(modal1);
167
         disableDismissModalAnimation(modal1);
134
 
168
 
135
-        uut.showModal(modal1, rootController, new CommandListenerAdapter());
136
-        uut.dismissTopModal(modal1, rootController, new CommandListenerAdapter());
169
+        uut.showModal(modal1, root, new CommandListenerAdapter());
170
+        uut.dismissTopModal(modal1, root, new CommandListenerAdapter());
137
         verify(modal1, times(1)).onViewDisappear();
171
         verify(modal1, times(1)).onViewDisappear();
138
         verify(modal1, times(1)).destroy();
172
         verify(modal1, times(1)).destroy();
139
         verify(animator, times(0)).dismiss(any(), any());
173
         verify(animator, times(0)).dismiss(any(), any());
143
     public void dismissModal_previousModalIsAddedBackToHierarchy() {
177
     public void dismissModal_previousModalIsAddedBackToHierarchy() {
144
         disableShowModalAnimation(modal1, modal2);
178
         disableShowModalAnimation(modal1, modal2);
145
 
179
 
146
-        uut.showModal(modal1, rootController, new CommandListenerAdapter());
180
+        uut.showModal(modal1, root, new CommandListenerAdapter());
147
         uut.showModal(modal2, modal1, new CommandListenerAdapter());
181
         uut.showModal(modal2, modal1, new CommandListenerAdapter());
148
         assertThat(modal1.getView().getParent()).isNull();
182
         assertThat(modal1.getView().getParent()).isNull();
149
         uut.dismissTopModal(modal2, modal1, new CommandListenerAdapter());
183
         uut.dismissTopModal(modal2, modal1, new CommandListenerAdapter());
155
         disableShowModalAnimation(modal1, modal2);
189
         disableShowModalAnimation(modal1, modal2);
156
         disableDismissModalAnimation(modal1, modal2);
190
         disableDismissModalAnimation(modal1, modal2);
157
 
191
 
158
-        uut.showModal(modal1, rootController, new CommandListenerAdapter());
192
+        uut.showModal(modal1, root, new CommandListenerAdapter());
159
         uut.showModal(modal2, modal1, new CommandListenerAdapter());
193
         uut.showModal(modal2, modal1, new CommandListenerAdapter());
160
         assertThat(modal1.getView().getParent()).isNull();
194
         assertThat(modal1.getView().getParent()).isNull();
161
-        assertThat(rootController.getView().getParent()).isNull();
195
+        assertThat(root.getView().getParent()).isNull();
162
 
196
 
163
         uut.dismissModal(modal1, new CommandListenerAdapter());
197
         uut.dismissModal(modal1, new CommandListenerAdapter());
164
-        assertThat(rootController.getView().getParent()).isNull();
198
+        assertThat(root.getView().getParent()).isNull();
165
 
199
 
166
-        uut.dismissTopModal(modal2, rootController, new CommandListenerAdapter());
167
-        assertThat(rootController.getView().getParent()).isNotNull();
200
+        uut.dismissTopModal(modal2, root, new CommandListenerAdapter());
201
+        assertThat(root.getView().getParent()).isNotNull();
168
     }
202
     }
169
 
203
 
170
     @Test
204
     @Test
172
         modal1.options.modal.presentationStyle = ModalPresentationStyle.OverCurrentContext;
206
         modal1.options.modal.presentationStyle = ModalPresentationStyle.OverCurrentContext;
173
         disableShowModalAnimation(modal1, modal2);
207
         disableShowModalAnimation(modal1, modal2);
174
 
208
 
175
-        uut.showModal(modal1, rootController, new CommandListenerAdapter());
176
-        assertThat(rootController.getView().getParent()).isNotNull();
177
-        verify(rootController, times(0)).onViewDisappear();
209
+        uut.showModal(modal1, root, new CommandListenerAdapter());
210
+        assertThat(root.getView().getParent()).isNotNull();
211
+        verify(root, times(0)).onViewDisappear();
178
     }
212
     }
179
 }
213
 }

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

256
         verify(backHandlingModal, times(0)).onViewDisappear();
256
         verify(backHandlingModal, times(0)).onViewDisappear();
257
     }
257
     }
258
 
258
 
259
+    @Test
260
+    public void setDefaultOptions() {
261
+        Options defaultOptions = new Options();
262
+        uut.setDefaultOptions(defaultOptions);
263
+        verify(presenter).setDefaultOptions(defaultOptions);
264
+    }
265
+
259
     private ViewController findModal(String id) {
266
     private ViewController findModal(String id) {
260
         return uut.findControllerById(id);
267
         return uut.findControllerById(id);
261
     }
268
     }

+ 11
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java View File

158
         verify(child1, times(0)).mergeOptions(any());
158
         verify(child1, times(0)).mergeOptions(any());
159
     }
159
     }
160
 
160
 
161
+    @Test
162
+    public void push_waitForRender() {
163
+        disablePushAnimation(child1);
164
+        uut.push(child1, new CommandListenerAdapter());
165
+
166
+        child2.options.animations.push.waitForRender = new Bool(true);
167
+        uut.push(child2, new CommandListenerAdapter());
168
+        verify(child2).setOnAppearedListener(any());
169
+        verify(animator, times(0)).push(eq(child1.getView()), any());
170
+    }
171
+
161
     @Test
172
     @Test
162
     public void animateSetRoot() {
173
     public void animateSetRoot() {
163
         disablePushAnimation(child1, child2, child3);
174
         disablePushAnimation(child1, child2, child3);

+ 5
- 0
playground/src/app.js View File

32
         fontFamily: 'HelveticaNeue-Italic',
32
         fontFamily: 'HelveticaNeue-Italic',
33
         fontSize: 13
33
         fontSize: 13
34
       },
34
       },
35
+      _animations: {
36
+        push: {
37
+          waitForRender: false,
38
+        }
39
+      },
35
       _animations: {
40
       _animations: {
36
         startApp: {
41
         startApp: {
37
           y: {
42
           y: {

+ 13
- 1
playground/src/screens/ModalScreen.js View File

17
         backgroundColor: 'transparent'
17
         backgroundColor: 'transparent'
18
       },
18
       },
19
       layout: {
19
       layout: {
20
-        orientation: ['portrait']
20
+        orientation: ['portrait'],
21
+        backgroundColor: '#f5fcff'
22
+      },
23
+      _animations: {
24
+        showModal: {
25
+          waitForRender: true
26
+        }
21
       }
27
       }
22
     };
28
     };
23
   }
29
   }
24
 
30
 
25
   constructor(props) {
31
   constructor(props) {
26
     super(props);
32
     super(props);
33
+    // this.simulateLongRunningTask();
27
     this.onClickShowModal = this.onClickShowModal.bind(this);
34
     this.onClickShowModal = this.onClickShowModal.bind(this);
28
     this.onClickDismissModal = this.onClickDismissModal.bind(this);
35
     this.onClickDismissModal = this.onClickDismissModal.bind(this);
29
     this.onClickDismissPreviousModal = this.onClickDismissPreviousModal.bind(this);
36
     this.onClickDismissPreviousModal = this.onClickDismissPreviousModal.bind(this);
36
     this.onClickModalLifecycle = this.onClickModalLifecycle.bind(this);
43
     this.onClickModalLifecycle = this.onClickModalLifecycle.bind(this);
37
   }
44
   }
38
 
45
 
46
+  simulateLongRunningTask = () => {
47
+    // tslint:disable-next-line
48
+    for (let i = 0; i < Math.pow(2, 25); i++);
49
+  }
50
+
39
   render() {
51
   render() {
40
     return (
52
     return (
41
       <View style={styles.root}>
53
       <View style={styles.root}>

+ 40
- 0
playground/src/screens/PushedScreen.js View File

18
       },
18
       },
19
       topBar: {
19
       topBar: {
20
         testID: testIDs.TOP_BAR_ELEMENT
20
         testID: testIDs.TOP_BAR_ELEMENT
21
+      },
22
+      layout: {
23
+        backgroundColor: '#f5fcff'
21
       }
24
       }
22
     };
25
     };
23
   }
26
   }
24
 
27
 
25
   constructor(props) {
28
   constructor(props) {
26
     super(props);
29
     super(props);
30
+    if (this.props.simulateLongRunningTask) {
31
+      this.simulateLongRunningTask();
32
+    }
27
     this.onClickPush = this.onClickPush.bind(this);
33
     this.onClickPush = this.onClickPush.bind(this);
28
     this.onClickPop = this.onClickPop.bind(this);
34
     this.onClickPop = this.onClickPop.bind(this);
29
     this.onClickPopPrevious = this.onClickPopPrevious.bind(this);
35
     this.onClickPopPrevious = this.onClickPopPrevious.bind(this);
33
     this.state = { disabled: false };
39
     this.state = { disabled: false };
34
   }
40
   }
35
 
41
 
42
+  simulateLongRunningTask() {
43
+    // tslint:disable-next-line
44
+    for (let i = 0; i < Math.pow(2, 25); i++);
45
+  }
46
+
36
   listeners = [];
47
   listeners = [];
37
 
48
 
38
   componentDidMount() {
49
   componentDidMount() {
76
         <Button title='Pop Previous' testID={testIDs.POP_PREVIOUS_BUTTON} onPress={this.onClickPopPrevious} />
87
         <Button title='Pop Previous' testID={testIDs.POP_PREVIOUS_BUTTON} onPress={this.onClickPopPrevious} />
77
         <Button title='Pop To Root' testID={testIDs.POP_TO_ROOT} onPress={this.onClickPopToRoot} />
88
         <Button title='Pop To Root' testID={testIDs.POP_TO_ROOT} onPress={this.onClickPopToRoot} />
78
         <Button title='Set Stack Root' testID={testIDs.SET_STACK_ROOT_BUTTON} onPress={this.onClickSetStackRoot} />
89
         <Button title='Set Stack Root' testID={testIDs.SET_STACK_ROOT_BUTTON} onPress={this.onClickSetStackRoot} />
90
+        <Button title='Push and Wait for Render' testID={testIDs.PUSH_BUTTON_WAIT_FOR_RENDER} onPress={this.onClickPushWaitForRender} />
79
         {stackPosition > 2 && <Button title='Pop To Stack Position 1' testID={testIDs.POP_STACK_POSITION_ONE_BUTTON} onPress={this.onClickPopToFirstPosition} />}
91
         {stackPosition > 2 && <Button title='Pop To Stack Position 1' testID={testIDs.POP_STACK_POSITION_ONE_BUTTON} onPress={this.onClickPopToFirstPosition} />}
80
         <Text style={styles.footer}>{`this.props.componentId = ${this.props.componentId}`}</Text>
92
         <Text style={styles.footer}>{`this.props.componentId = ${this.props.componentId}`}</Text>
81
       </View>
93
       </View>
174
     });
186
     });
175
   }
187
   }
176
 
188
 
189
+  onClickPushWaitForRender = async () => {
190
+    await Navigation.push(this.props.componentId, {
191
+      component: {
192
+        name: 'navigation.playground.PushedScreen',
193
+        passProps: {
194
+          stackPosition: this.getStackPosition() + 1,
195
+          previousScreenIds: _.concat([], this.props.previousScreenIds || [], this.props.componentId),
196
+          simulateLongRunningTask: true
197
+        },
198
+        options: {
199
+          layout: {
200
+            backgroundColor: 'transparent'
201
+          },
202
+          topBar: {
203
+            title: {
204
+              text: `Pushed ${this.getStackPosition() + 1}`
205
+            }
206
+          },
207
+          animations: {
208
+            push: {
209
+              waitForRender: true
210
+            }
211
+          }
212
+        }
213
+      }
214
+    });
215
+  }
216
+
177
   getStackPosition() {
217
   getStackPosition() {
178
     return this.props.stackPosition || 1;
218
     return this.props.stackPosition || 1;
179
   }
219
   }

+ 1
- 0
playground/src/testIDs.js View File

44
   TOGGLE_TOP_BAR_HIDE_ON_SCROLL: `TOGGLE_TOP_BAR_HIDE_ON_SCROLL`,
44
   TOGGLE_TOP_BAR_HIDE_ON_SCROLL: `TOGGLE_TOP_BAR_HIDE_ON_SCROLL`,
45
   SET_TAB_BADGE_BUTTON: `SET_TAB_BADGE_BUTTON`,
45
   SET_TAB_BADGE_BUTTON: `SET_TAB_BADGE_BUTTON`,
46
   PUSH_TO_TEST_DID_DISAPPEAR_BUTTON: `PUSH_TO_TEST_DID_DISAPPEAR_BUTTON`,
46
   PUSH_TO_TEST_DID_DISAPPEAR_BUTTON: `PUSH_TO_TEST_DID_DISAPPEAR_BUTTON`,
47
+  PUSH_BUTTON_WAIT_FOR_RENDER: `PUSH_AND_WAIT_FOR_RENDER`,
47
   SHOW_LEFT_SIDE_MENU_BUTTON: `SHOW_LEFT_SIDE_MENU_BUTTON`,
48
   SHOW_LEFT_SIDE_MENU_BUTTON: `SHOW_LEFT_SIDE_MENU_BUTTON`,
48
   SHOW_RIGHT_SIDE_MENU_BUTTON: `SHOW_RIGHT_SIDE_MENU_BUTTON`,
49
   SHOW_RIGHT_SIDE_MENU_BUTTON: `SHOW_RIGHT_SIDE_MENU_BUTTON`,
49
   HIDE_LEFT_SIDE_MENU_BUTTON: `HIDE_LEFT_SIDE_MENU_BUTTON`,
50
   HIDE_LEFT_SIDE_MENU_BUTTON: `HIDE_LEFT_SIDE_MENU_BUTTON`,