Browse Source

Stack layout android (#2531)

* Change topBar backgroundColor to Color object

* StackLayout implementation

Unfortunately, this commit also contains a lot of refactoring and a few
bug fixes relating to styles.

* TopBar is not part of Component, instead it's a part of StackLayout
  and shared between all components pushed to the stack Similar to iOS.
* Handle ScrollEvent in ReactView instead of passing the listener down
* Implement Fraction and Text null objects for style params
* Converted a few style params to null objects. This fixes some styles
  being applied even when they were not specified explicitly.
* Stop sending null promise to commands
* Seperate push and pop into animatePush/push and animatePop/pop
  to avoid boolean param

* Adapt TopTabs to new API
Guy Carmeli 6 years ago
parent
commit
bb77b8ca4a
No account linked to committer's email address
51 changed files with 785 additions and 684 deletions
  1. 5
    5
      lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarAnimator.java
  2. 1
    2
      lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarCollapseBehavior.java
  3. 7
    7
      lib/android/app/src/main/java/com/reactnativenavigation/parse/BottomTabsOptions.java
  4. 15
    16
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Button.java
  5. 0
    5
      lib/android/app/src/main/java/com/reactnativenavigation/parse/DEFAULT_VALUES.java
  6. 7
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Fraction.java
  7. 9
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/FractionParser.java
  8. 6
    4
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java
  9. 12
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/NullFraction.java
  10. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/NullNumber.java
  11. 12
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/NullText.java
  12. 6
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java
  13. 4
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Param.java
  14. 7
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Text.java
  15. 9
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TextParser.java
  16. 17
    18
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java
  17. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopTabOptions.java
  18. 10
    11
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java
  19. 37
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/NoOpPromise.java
  20. 20
    22
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/BottomTabsController.java
  21. 6
    8
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java
  22. 0
    104
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ContainerViewController.java
  23. 8
    38
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java
  24. 16
    5
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java
  25. 79
    64
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java
  26. 12
    9
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  27. 2
    3
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabController.java
  28. 8
    5
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsAdapter.java
  29. 5
    5
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java
  30. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsViewPager.java
  31. 1
    3
      lib/android/app/src/main/java/com/reactnativenavigation/views/Component.java
  32. 6
    22
      lib/android/app/src/main/java/com/reactnativenavigation/views/ComponentLayout.java
  33. 46
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/StackLayout.java
  34. 6
    6
      lib/android/app/src/main/java/com/reactnativenavigation/views/TitleBarButton.java
  35. 22
    16
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java
  36. 16
    28
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayout.java
  37. 3
    3
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayoutCreator.java
  38. 10
    17
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestComponentLayout.java
  39. 8
    8
      lib/android/app/src/test/java/com/reactnativenavigation/parse/NavigationOptionsTest.java
  40. 1
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/BottomTabsControllerTest.java
  41. 16
    13
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ComponentViewControllerTest.java
  42. 83
    67
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java
  43. 71
    56
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java
  44. 3
    3
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java
  45. 141
    93
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java
  46. 11
    6
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java
  47. 1
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java
  48. 1
    1
      lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarTest.java
  49. 2
    1
      playground/src/screens/OptionsScreen.js
  50. 6
    1
      playground/src/screens/PushedScreen.js
  51. 6
    1
      playground/src/screens/WelcomeScreen.js

+ 5
- 5
lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarAnimator.java View File

@@ -63,11 +63,11 @@ public class TopBarAnimator {
63 63
     }
64 64
 
65 65
     void hide(float startTranslation, TimeInterpolator interpolator, int duration) {
66
-        ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, -1 * topBar.getMeasuredHeight());
67
-        topbarAnim.setInterpolator(interpolator);
68
-        topbarAnim.setDuration(duration);
66
+        ObjectAnimator animator = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, -1 * topBar.getMeasuredHeight());
67
+        animator.setInterpolator(interpolator);
68
+        animator.setDuration(duration);
69 69
 
70
-        topbarAnim.addListener(new AnimatorListenerAdapter() {
70
+        animator.addListener(new AnimatorListenerAdapter() {
71 71
             @Override
72 72
             public void onAnimationEnd(Animator animation) {
73 73
                 if (contentView != null) {
@@ -79,6 +79,6 @@ public class TopBarAnimator {
79 79
                 topBar.setVisibility(View.GONE);
80 80
             }
81 81
         });
82
-        topbarAnim.start();
82
+        animator.start();
83 83
     }
84 84
 }

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

@@ -11,10 +11,9 @@ public class TopBarCollapseBehavior implements ScrollEventListener.OnScrollListe
11 11
     private ScrollEventListener scrollEventListener;
12 12
     private TopBarAnimator animator;
13 13
 
14
-    public TopBarCollapseBehavior(TopBar topBar, ScrollEventListener scrollEventListener) {
14
+    public TopBarCollapseBehavior(TopBar topBar) {
15 15
         this.topBar = topBar;
16 16
         this.animator = new TopBarAnimator(topBar);
17
-        this.scrollEventListener = scrollEventListener;
18 17
     }
19 18
 
20 19
     public void enableCollapse() {

+ 7
- 7
lib/android/app/src/main/java/com/reactnativenavigation/parse/BottomTabsOptions.java View File

@@ -11,7 +11,7 @@ public class BottomTabsOptions implements DEFAULT_VALUES {
11 11
 		BottomTabsOptions options = new BottomTabsOptions();
12 12
 		if (json == null) return options;
13 13
 
14
-		options.currentTabId = json.optString("currentTabId", NO_VALUE);
14
+		options.currentTabId = TextParser.parse(json, "currentTabId");
15 15
 		options.currentTabIndex = json.optInt("currentTabIndex", NO_INT_VALUE);
16 16
 		options.tabBadge = json.optInt("tabBadge", NO_INT_VALUE);
17 17
 		options.hidden = BooleanOptions.parse(json.optString("hidden"));
@@ -20,14 +20,14 @@ public class BottomTabsOptions implements DEFAULT_VALUES {
20 20
 		return options;
21 21
 	}
22 22
 
23
-	public int tabBadge = NO_INT_VALUE;
24
-	public BooleanOptions hidden = BooleanOptions.False;
25
-	public BooleanOptions animateHide = BooleanOptions.False;
23
+	int tabBadge = NO_INT_VALUE;
24
+	BooleanOptions hidden = BooleanOptions.False;
25
+	BooleanOptions animateHide = BooleanOptions.False;
26 26
 	public int currentTabIndex = NO_INT_VALUE;
27
-	public String currentTabId = NO_VALUE;
27
+	public Text currentTabId = new NullText();
28 28
 
29 29
 	void mergeWith(final BottomTabsOptions other) {
30
-		if (!NO_VALUE.equals(other.currentTabId)) {
30
+		if (other.currentTabId.hasValue()) {
31 31
 			currentTabId = other.currentTabId;
32 32
 		}
33 33
 		if (NO_INT_VALUE != other.currentTabIndex) {
@@ -45,7 +45,7 @@ public class BottomTabsOptions implements DEFAULT_VALUES {
45 45
 	}
46 46
 
47 47
     void mergeWithDefault(final BottomTabsOptions defaultOptions) {
48
-        if (NO_VALUE.equals(currentTabId)) {
48
+        if (!currentTabId.hasValue()) {
49 49
             currentTabId = defaultOptions.currentTabId;
50 50
         }
51 51
         if (NO_INT_VALUE == currentTabIndex) {

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

@@ -8,40 +8,38 @@ import org.json.JSONObject;
8 8
 
9 9
 import java.util.ArrayList;
10 10
 
11
-import static com.reactnativenavigation.parse.DEFAULT_VALUES.NO_VALUE;
12 11
 import static com.reactnativenavigation.parse.Options.NO_INT_VALUE;
13 12
 
14 13
 public class Button {
15 14
 	public String id;
16
-	public String title;
15
+	public Text title;
17 16
 	public Options.BooleanOptions disabled;
18 17
 	public Options.BooleanOptions disableIconTint;
19 18
 	public int showAsAction;
20 19
 	@ColorInt public int buttonColor;
21 20
 	public int buttonFontSize;
22
-	public String buttonFontWeight;
23
-	public String icon;
21
+	public Text buttonFontWeight;
22
+	public Text icon;
24 23
 
25 24
 	private static Button parseJson(JSONObject json)  {
26 25
 		Button button = new Button();
27 26
 		button.id = json.optString("id");
28
-		button.title = json.optString("title", NO_VALUE);
29
-		button.disabled = Options.BooleanOptions.parse(json.optString("disabled", NO_VALUE));
30
-		button.disableIconTint = Options.BooleanOptions.parse(json.optString("disableIconTint", NO_VALUE));
31
-		button.showAsAction = parseShowAsAction(json.optString("showAsAction", NO_VALUE));
27
+		button.title = TextParser.parse(json, "title");
28
+		button.disabled = Options.BooleanOptions.parse(json.optString("disabled", ""));
29
+		button.disableIconTint = Options.BooleanOptions.parse(json.optString("disableIconTint", ""));
30
+		button.showAsAction = parseShowAsAction(json);
32 31
 		button.buttonColor = json.optInt("buttonColor", NO_INT_VALUE);
33 32
 		button.buttonFontSize = json.optInt("buttonFontSize", NO_INT_VALUE);
34
-		button.buttonFontWeight = json.optString("buttonFontWeight", NO_VALUE);
33
+		button.buttonFontWeight = TextParser.parse(json, "buttonFontWeight");
35 34
 
36
-		JSONObject iconObject = json.optJSONObject("icon");
37
-		if (iconObject != null) {
38
-			button.icon = iconObject.optString("uri", NO_VALUE);
35
+		if (json.has("icon")) {
36
+			button.icon = TextParser.parse(json.optJSONObject("icon"), "uri");
39 37
 		}
40 38
 
41 39
 		return button;
42 40
 	}
43 41
 
44
-	public static ArrayList<Button> parseJsonArray(JSONArray jsonArray) {
42
+	static ArrayList<Button> parseJsonArray(JSONArray jsonArray) {
45 43
 		ArrayList<Button> buttons = new ArrayList<>();
46 44
 
47 45
 		if (jsonArray == null) {
@@ -57,12 +55,13 @@ public class Button {
57 55
 		return buttons;
58 56
 	}
59 57
 
60
-	private static int parseShowAsAction(String showAsAction) {
61
-		if (NO_VALUE.equals(showAsAction)) {
58
+	private static int parseShowAsAction(JSONObject json) {
59
+	    final Text showAsAction = TextParser.parse(json, "showAsAction");
60
+		if (!showAsAction.hasValue()) {
62 61
 			return MenuItem.SHOW_AS_ACTION_IF_ROOM;
63 62
 		}
64 63
 
65
-		switch (showAsAction) {
64
+		switch (showAsAction.get()) {
66 65
 			case "always":
67 66
 				return MenuItem.SHOW_AS_ACTION_ALWAYS;
68 67
 			case "never":

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

@@ -1,11 +1,6 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3 3
 
4
-import android.graphics.Color;
5
-
6 4
 public interface DEFAULT_VALUES {
7
-	String NO_VALUE = "";
8 5
 	int NO_INT_VALUE = Integer.MIN_VALUE;
9
-	float NO_FLOAT_VALUE = Float.MIN_VALUE;
10
-	int NO_COLOR_VALUE = Color.TRANSPARENT;
11 6
 }

+ 7
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/Fraction.java View File

@@ -0,0 +1,7 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+public class Fraction extends Param<Float> {
4
+    public Fraction(float value) {
5
+        super(value);
6
+    }
7
+}

+ 9
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/FractionParser.java View File

@@ -0,0 +1,9 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+import org.json.JSONObject;
4
+
5
+public class FractionParser {
6
+    public static Fraction parse(JSONObject json, String fraction) {
7
+        return json.has(fraction) ? new Fraction(json.optInt(fraction)) : new NullFraction();
8
+    }
9
+}

+ 6
- 4
lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java View File

@@ -3,6 +3,7 @@ package com.reactnativenavigation.parse;
3 3
 import android.app.Activity;
4 4
 
5 5
 import com.facebook.react.ReactInstanceManager;
6
+import com.reactnativenavigation.utils.NoOpPromise;
6 7
 import com.reactnativenavigation.utils.TypefaceLoader;
7 8
 import com.reactnativenavigation.viewcontrollers.BottomTabsController;
8 9
 import com.reactnativenavigation.viewcontrollers.ComponentViewController;
@@ -102,7 +103,7 @@ public class LayoutFactory {
102 103
 	private ViewController createStack(LayoutNode node) {
103 104
 		StackController stackController = new StackController(activity, node.id);
104 105
 		for (LayoutNode child : node.children) {
105
-			stackController.push(create(child), null);
106
+			stackController.animatePush(create(child), new NoOpPromise());
106 107
 		}
107 108
 		return stackController;
108 109
 	}
@@ -118,10 +119,11 @@ public class LayoutFactory {
118 119
 	}
119 120
 
120 121
     private ViewController createTopTabs(LayoutNode node) {
121
-        final List<TopTabController> tabs = new ArrayList<>();
122
+        final List<ViewController> tabs = new ArrayList<>();
122 123
         for (int i = 0; i < node.children.size(); i++) {
123
-            TopTabController tabController = (TopTabController) create(node.children.get(i));
124
-            tabController.setTabIndex(i);
124
+            ViewController tabController = create(node.children.get(i));
125
+            Options options = Options.parse(typefaceManager, node.children.get(i).getNavigationOptions(), defaultOptions);
126
+            options.setTopTabIndex(i);
125 127
             tabs.add(tabController);
126 128
         }
127 129
         Options options = Options.parse(typefaceManager, node.getNavigationOptions(), defaultOptions);

+ 12
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/NullFraction.java View File

@@ -0,0 +1,12 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+public class NullFraction extends Fraction {
4
+    NullFraction() {
5
+        super(0);
6
+    }
7
+
8
+    @Override
9
+    public boolean hasValue() {
10
+        return false;
11
+    }
12
+}

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

@@ -1,7 +1,7 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3 3
 public class NullNumber extends Number {
4
-    public NullNumber() {
4
+    NullNumber() {
5 5
         super(0);
6 6
     }
7 7
 

+ 12
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/NullText.java View File

@@ -0,0 +1,12 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+public class NullText extends Text {
4
+    public NullText() {
5
+        super("");
6
+    }
7
+
8
+    @Override
9
+    public boolean hasValue() {
10
+        return false;
11
+    }
12
+}

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

@@ -1,6 +1,7 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3 3
 import android.support.annotation.NonNull;
4
+import android.text.TextUtils;
4 5
 
5 6
 import com.reactnativenavigation.utils.TypefaceLoader;
6 7
 
@@ -14,7 +15,7 @@ public class Options implements DEFAULT_VALUES {
14 15
 		NoValue;
15 16
 
16 17
 		static BooleanOptions parse(String value) {
17
-			if (value != null && !value.equals("")) {
18
+			if (!TextUtils.isEmpty(value)) {
18 19
 				return Boolean.valueOf(value) ? True : False;
19 20
 			}
20 21
 			return NoValue;
@@ -44,6 +45,10 @@ public class Options implements DEFAULT_VALUES {
44 45
     @NonNull public TopTabOptions topTabOptions = new TopTabOptions();
45 46
     @NonNull public BottomTabsOptions bottomTabsOptions = new BottomTabsOptions();
46 47
 
48
+    void setTopTabIndex(int i) {
49
+        topTabOptions.tabIndex = i;
50
+    }
51
+
47 52
 	public void mergeWith(final Options other) {
48 53
         topBarOptions.mergeWith(other.topBarOptions);
49 54
         topTabsOptions.mergeWith(other.topTabsOptions);

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

@@ -14,6 +14,10 @@ public abstract class Param<T> {
14 14
         throw new RuntimeException("Tried to get null value!");
15 15
     }
16 16
 
17
+    public T get(T defaultValue) {
18
+        return hasValue() ? value : defaultValue;
19
+    }
20
+
17 21
     public boolean hasValue() {
18 22
         return value != null;
19 23
     }

+ 7
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/Text.java View File

@@ -0,0 +1,7 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+public class Text extends Param<String> {
4
+    public Text(String value) {
5
+        super(value);
6
+    }
7
+}

+ 9
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/TextParser.java View File

@@ -0,0 +1,9 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+import org.json.JSONObject;
4
+
5
+public class TextParser {
6
+    public static Text parse(JSONObject json, String text) {
7
+        return json.has(text) ? new Text(json.optString(text)) : new NullText();
8
+    }
9
+}

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

@@ -2,7 +2,6 @@ package com.reactnativenavigation.parse;
2 2
 
3 3
 
4 4
 import android.graphics.Typeface;
5
-import android.support.annotation.ColorInt;
6 5
 import android.support.annotation.Nullable;
7 6
 
8 7
 import com.reactnativenavigation.utils.TypefaceLoader;
@@ -17,11 +16,11 @@ public class TopBarOptions implements DEFAULT_VALUES {
17 16
         TopBarOptions options = new TopBarOptions();
18 17
         if (json == null) return options;
19 18
 
20
-        options.title = json.optString("title", NO_VALUE);
21
-        options.backgroundColor = json.optInt("backgroundColor", NO_COLOR_VALUE);
22
-        options.textColor = json.optInt("textColor", NO_COLOR_VALUE);
23
-        options.textFontSize = (float) json.optDouble("textFontSize", NO_FLOAT_VALUE);
24
-        options.textFontFamily = typefaceManager.getTypeFace(json.optString("textFontFamily", NO_VALUE));
19
+        options.title = TextParser.parse(json, "title");
20
+        options.backgroundColor = ColorParser.parse(json, "backgroundColor");
21
+        options.textColor = ColorParser.parse(json, "textColor");
22
+        options.textFontSize = FractionParser.parse(json, "textFontSize");
23
+        options.textFontFamily = typefaceManager.getTypeFace(json.optString("textFontFamily", ""));
25 24
         options.hidden = Options.BooleanOptions.parse(json.optString("hidden"));
26 25
         options.animateHide = Options.BooleanOptions.parse(json.optString("animateHide"));
27 26
         options.hideOnScroll = Options.BooleanOptions.parse(json.optString("hideOnScroll"));
@@ -32,10 +31,10 @@ public class TopBarOptions implements DEFAULT_VALUES {
32 31
         return options;
33 32
     }
34 33
 
35
-    public String title = NO_VALUE;
36
-    @ColorInt public int backgroundColor = NO_COLOR_VALUE;
37
-    @ColorInt public int textColor = NO_COLOR_VALUE;
38
-    public float textFontSize = NO_FLOAT_VALUE;
34
+    public Text title = new NullText();
35
+    public Color backgroundColor = new NullColor();
36
+    public Color textColor = new NullColor();
37
+    public Fraction textFontSize = new NullFraction();
39 38
     @Nullable public Typeface textFontFamily;
40 39
     public Options.BooleanOptions hidden = Options.BooleanOptions.NoValue;
41 40
     public Options.BooleanOptions animateHide = Options.BooleanOptions.NoValue;
@@ -45,12 +44,12 @@ public class TopBarOptions implements DEFAULT_VALUES {
45 44
     public ArrayList<Button> rightButtons;
46 45
 
47 46
     void mergeWith(final TopBarOptions other) {
48
-        if (!NO_VALUE.equals(other.title)) title = other.title;
49
-        if (other.backgroundColor != NO_COLOR_VALUE)
47
+        if (other.title != null) title = other.title;
48
+        if (other.backgroundColor.hasValue())
50 49
             backgroundColor = other.backgroundColor;
51
-        if (other.textColor != NO_COLOR_VALUE)
50
+        if (other.textColor.hasValue())
52 51
             textColor = other.textColor;
53
-        if (other.textFontSize != NO_FLOAT_VALUE)
52
+        if (other.textFontSize.hasValue())
54 53
             textFontSize = other.textFontSize;
55 54
         if (other.textFontFamily != null)
56 55
             textFontFamily = other.textFontFamily;
@@ -73,13 +72,13 @@ public class TopBarOptions implements DEFAULT_VALUES {
73 72
     }
74 73
 
75 74
     void mergeWithDefault(TopBarOptions defaultOptions) {
76
-        if (NO_VALUE.equals(title))
75
+        if (title == null)
77 76
             title = defaultOptions.title;
78
-        if (backgroundColor == NO_COLOR_VALUE)
77
+        if (!backgroundColor.hasValue())
79 78
             backgroundColor = defaultOptions.backgroundColor;
80
-        if (textColor == NO_COLOR_VALUE)
79
+        if (!textColor.hasValue())
81 80
             textColor = defaultOptions.textColor;
82
-        if (textFontSize == NO_FLOAT_VALUE)
81
+        if (!textFontSize.hasValue())
83 82
             textFontSize = defaultOptions.textFontSize;
84 83
         if (textFontFamily == null)
85 84
             textFontFamily = defaultOptions.textFontFamily;

+ 2
- 2
lib/android/app/src/main/java/com/reactnativenavigation/parse/TopTabOptions.java View File

@@ -8,7 +8,7 @@ import com.reactnativenavigation.utils.TypefaceLoader;
8 8
 import org.json.JSONObject;
9 9
 
10 10
 public class TopTabOptions implements DEFAULT_VALUES {
11
-    public String title = NO_VALUE;
11
+    public Text title = new NullText();
12 12
     @Nullable public Typeface fontFamily;
13 13
     public int tabIndex;
14 14
 
@@ -16,7 +16,7 @@ public class TopTabOptions implements DEFAULT_VALUES {
16 16
         TopTabOptions result = new TopTabOptions();
17 17
         if (json == null) return result;
18 18
 
19
-        result.title = json.optString("title", NO_VALUE);
19
+        result.title = TextParser.parse(json, "title");
20 20
         result.fontFamily = typefaceManager.getTypeFace(json.optString("titleFontFamily"));
21 21
         return result;
22 22
     }

+ 10
- 11
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java View File

@@ -5,7 +5,7 @@ import com.reactnativenavigation.parse.Options;
5 5
 import com.reactnativenavigation.parse.TopBarOptions;
6 6
 import com.reactnativenavigation.parse.TopTabOptions;
7 7
 import com.reactnativenavigation.parse.TopTabsOptions;
8
-import com.reactnativenavigation.views.Component;
8
+import com.reactnativenavigation.views.ReactComponent;
9 9
 import com.reactnativenavigation.views.TopBar;
10 10
 
11 11
 import java.util.ArrayList;
@@ -14,13 +14,12 @@ import static com.reactnativenavigation.parse.Options.BooleanOptions.False;
14 14
 import static com.reactnativenavigation.parse.Options.BooleanOptions.True;
15 15
 
16 16
 public class OptionsPresenter {
17
-
18
-    private Component reactComponent;
19 17
     private TopBar topBar;
18
+    private ReactComponent component;
20 19
 
21
-    public OptionsPresenter(Component reactComponent) {
22
-        this.reactComponent = reactComponent;
23
-        this.topBar = reactComponent.getTopBar();
20
+    public OptionsPresenter(TopBar topBar, ReactComponent component) {
21
+        this.topBar = topBar;
22
+        this.component = component;
24 23
     }
25 24
 
26 25
     public void applyOptions(Options options) {
@@ -31,22 +30,22 @@ public class OptionsPresenter {
31 30
     }
32 31
 
33 32
     private void applyTopBarOptions(TopBarOptions options) {
34
-        topBar.setTitle(options.title);
33
+        if (options.title.hasValue()) topBar.setTitle(options.title.get());
35 34
         topBar.setBackgroundColor(options.backgroundColor);
36 35
         topBar.setTitleTextColor(options.textColor);
37 36
         topBar.setTitleFontSize(options.textFontSize);
38 37
 
39 38
         topBar.setTitleTypeface(options.textFontFamily);
40
-        if (options.hidden == Options.BooleanOptions.True) {
39
+        if (options.hidden == True) {
41 40
             topBar.hide(options.animateHide);
42 41
         }
43
-        if (options.hidden == Options.BooleanOptions.False) {
42
+        if (options.hidden == False) {
44 43
             topBar.show(options.animateHide);
45 44
         }
46 45
         if (options.drawBehind == True) {
47
-            reactComponent.drawBehindTopBar();
46
+            component.drawBehindTopBar();
48 47
         } else if (options.drawBehind == False) {
49
-            reactComponent.drawBelowTopBar();
48
+            component.drawBelowTopBar(topBar);
50 49
         }
51 50
 
52 51
         if (options.hideOnScroll == True) {

+ 37
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/NoOpPromise.java View File

@@ -0,0 +1,37 @@
1
+package com.reactnativenavigation.utils;
2
+
3
+import com.facebook.react.bridge.Promise;
4
+
5
+import javax.annotation.Nullable;
6
+
7
+public class NoOpPromise implements Promise {
8
+    @Override
9
+    public void resolve(@Nullable Object value) {
10
+
11
+    }
12
+
13
+    @Override
14
+    public void reject(String code, String message) {
15
+
16
+    }
17
+
18
+    @Override
19
+    public void reject(String code, Throwable e) {
20
+
21
+    }
22
+
23
+    @Override
24
+    public void reject(String code, String message, Throwable e) {
25
+
26
+    }
27
+
28
+    @Override
29
+    public void reject(String message) {
30
+
31
+    }
32
+
33
+    @Override
34
+    public void reject(Throwable reason) {
35
+
36
+    }
37
+}

+ 20
- 22
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/BottomTabsController.java View File

@@ -11,6 +11,7 @@ import android.view.ViewGroup;
11 11
 import android.widget.RelativeLayout;
12 12
 
13 13
 import com.reactnativenavigation.parse.Options;
14
+import com.reactnativenavigation.parse.Text;
14 15
 import com.reactnativenavigation.presentation.NavigationOptionsListener;
15 16
 import com.reactnativenavigation.utils.CompatUtils;
16 17
 
@@ -23,7 +24,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
23 24
 import static android.widget.RelativeLayout.ABOVE;
24 25
 import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
25 26
 import static com.reactnativenavigation.parse.DEFAULT_VALUES.NO_INT_VALUE;
26
-import static com.reactnativenavigation.parse.DEFAULT_VALUES.NO_VALUE;
27 27
 
28 28
 public class BottomTabsController extends ParentController
29 29
 		implements BottomNavigationView.OnNavigationItemSelectedListener, NavigationOptionsListener {
@@ -60,7 +60,7 @@ public class BottomTabsController extends ParentController
60 60
 		return true;
61 61
 	}
62 62
 
63
-	public void selectTabAtIndex(final int newIndex) {
63
+	void selectTabAtIndex(final int newIndex) {
64 64
 		tabs.get(selectedIndex).getView().setVisibility(View.GONE);
65 65
 		selectedIndex = newIndex;
66 66
 		tabs.get(selectedIndex).getView().setVisibility(View.VISIBLE);
@@ -87,7 +87,7 @@ public class BottomTabsController extends ParentController
87 87
 		getView().addView(tab.getView(), params);
88 88
 	}
89 89
 
90
-	public int getSelectedIndex() {
90
+	int getSelectedIndex() {
91 91
 		return selectedIndex;
92 92
 	}
93 93
 
@@ -99,25 +99,23 @@ public class BottomTabsController extends ParentController
99 99
 
100 100
 	@Override
101 101
 	public void mergeOptions(Options options) {
102
-		if (options.bottomTabsOptions != null) {
103
-			if (options.bottomTabsOptions.currentTabIndex != NO_INT_VALUE) {
104
-				selectTabAtIndex(options.bottomTabsOptions.currentTabIndex);
105
-			}
106
-			if (!NO_VALUE.equals(options.bottomTabsOptions.currentTabId)) {
107
-				String id = options.bottomTabsOptions.currentTabId;
108
-				for (ViewController controller : tabs) {
109
-					if (controller.getId().equals(id)) {
110
-						selectTabAtIndex(tabs.indexOf(controller));
111
-					}
112
-					if (controller instanceof StackController) {
113
-						if (hasControlWithId((StackController) controller, id)) {
114
-							selectTabAtIndex(tabs.indexOf(controller));
115
-						}
116
-					}
117
-				}
118
-			}
119
-		}
120
-	}
102
+        if (options.bottomTabsOptions.currentTabIndex != NO_INT_VALUE) {
103
+            selectTabAtIndex(options.bottomTabsOptions.currentTabIndex);
104
+        }
105
+        if (options.bottomTabsOptions.currentTabId.hasValue()) {
106
+            Text id = options.bottomTabsOptions.currentTabId;
107
+            for (ViewController controller : tabs) {
108
+                if (controller.getId().equals(id.get())) {
109
+                    selectTabAtIndex(tabs.indexOf(controller));
110
+                }
111
+                if (controller instanceof StackController) {
112
+                    if (hasControlWithId((StackController) controller, id.get())) {
113
+                        selectTabAtIndex(tabs.indexOf(controller));
114
+                    }
115
+                }
116
+            }
117
+        }
118
+    }
121 119
 
122 120
 	private boolean hasControlWithId(StackController controller, String id) {
123 121
 		for (ViewController child : controller.getChildControllers()) {

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

@@ -36,7 +36,6 @@ public class ComponentViewController extends ViewController implements Navigatio
36 36
     private final String componentName;
37 37
 
38 38
     private final ReactViewCreator viewCreator;
39
-    private Options options;
40 39
     private ReactComponent component;
41 40
 
42 41
     public ComponentViewController(final Activity activity,
@@ -47,12 +46,7 @@ public class ComponentViewController extends ViewController implements Navigatio
47 46
         super(activity, id);
48 47
         this.componentName = componentName;
49 48
         this.viewCreator = viewCreator;
50
-        this.options = initialNavigationOptions;
51
-    }
52
-
53
-    @RestrictTo(RestrictTo.Scope.TESTS)
54
-    TopBar getTopBar() {
55
-        return component.getTopBar();
49
+        options = initialNavigationOptions;
56 50
     }
57 51
 
58 52
     @Override
@@ -66,7 +60,10 @@ public class ComponentViewController extends ViewController implements Navigatio
66 60
     public void onViewAppeared() {
67 61
         super.onViewAppeared();
68 62
         ensureViewIsCreated();
69
-        component.applyOptions(options);
63
+        applyOnParentStack(parentController -> {
64
+            parentController.clearOptions();
65
+            parentController.applyOptions(options, component);
66
+        });
70 67
         component.sendComponentStart();
71 68
     }
72 69
 
@@ -92,6 +89,7 @@ public class ComponentViewController extends ViewController implements Navigatio
92 89
     public void mergeOptions(Options options) {
93 90
         this.options.mergeWith(options);
94 91
         component.applyOptions(this.options);
92
+        applyOnParentStack(parentController -> parentController.applyOptions(this.options, component));
95 93
     }
96 94
 
97 95
     Options getOptions() {

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

@@ -1,104 +0,0 @@
1
-package com.reactnativenavigation.viewcontrollers;
2
-
3
-import android.app.Activity;
4
-import android.support.annotation.NonNull;
5
-import android.support.annotation.RestrictTo;
6
-import android.view.View;
7
-
8
-import com.reactnativenavigation.parse.Options;
9
-import com.reactnativenavigation.presentation.NavigationOptionsListener;
10
-import com.reactnativenavigation.views.ReactComponent;
11
-import com.reactnativenavigation.views.TopBar;
12
-
13
-public class ContainerViewController extends ViewController implements NavigationOptionsListener {
14
-
15
-    public interface ReactViewCreator {
16
-
17
-        IReactView create(Activity activity, String containerId, String containerName);
18
-    }
19
-
20
-    public interface IReactView {
21
-
22
-        boolean isReady();
23
-
24
-        View asView();
25
-
26
-        void destroy();
27
-
28
-        void sendComponentStart();
29
-
30
-        void sendComponentStop();
31
-
32
-        void sendOnNavigationButtonPressed(String buttonId);
33
-    }
34
-
35
-    private final String componentName;
36
-
37
-    private final ReactViewCreator viewCreator;
38
-    private Options options;
39
-    private ReactComponent component;
40
-
41
-    public ContainerViewController(final Activity activity,
42
-                                   final String id,
43
-                                   final String componentName,
44
-                                   final ReactViewCreator viewCreator,
45
-                                   final Options initialOptions) {
46
-        super(activity, id);
47
-        this.componentName = componentName;
48
-        this.viewCreator = viewCreator;
49
-        this.options = initialOptions;
50
-    }
51
-
52
-    @RestrictTo(RestrictTo.Scope.TESTS)
53
-    TopBar getTopBar() {
54
-        return component.getTopBar();
55
-    }
56
-
57
-    @RestrictTo(RestrictTo.Scope.TESTS)
58
-    ReactComponent getComponent() {
59
-        return component;
60
-    }
61
-
62
-    @Override
63
-    public void destroy() {
64
-        super.destroy();
65
-        if (component != null) component.destroy();
66
-        component = null;
67
-    }
68
-
69
-    @Override
70
-    public void onViewAppeared() {
71
-        super.onViewAppeared();
72
-        ensureViewIsCreated();
73
-        component.applyOptions(options);
74
-        component.sendComponentStart();
75
-    }
76
-
77
-    @Override
78
-    public void onViewDisappear() {
79
-        super.onViewDisappear();
80
-        component.sendComponentStop();
81
-    }
82
-
83
-    @Override
84
-    protected boolean isViewShown() {
85
-        return super.isViewShown() && component.isReady();
86
-    }
87
-
88
-    @NonNull
89
-    @Override
90
-    protected View createView() {
91
-        component = (ReactComponent) viewCreator.create(getActivity(), getId(), componentName);
92
-        return component.asView();
93
-    }
94
-
95
-    @Override
96
-    public void mergeOptions(Options options) {
97
-        this.options.mergeWith(options);
98
-        component.applyOptions(this.options);
99
-    }
100
-
101
-    Options getOptions() {
102
-        return options;
103
-    }
104
-}

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

@@ -12,13 +12,15 @@ import com.reactnativenavigation.parse.OverlayOptions;
12 12
 import com.reactnativenavigation.presentation.NavigationOptionsListener;
13 13
 import com.reactnativenavigation.presentation.OverlayPresenter;
14 14
 import com.reactnativenavigation.utils.CompatUtils;
15
+import com.reactnativenavigation.utils.NoOpPromise;
15 16
 
16 17
 import java.util.Collection;
17 18
 import java.util.Collections;
18 19
 
19 20
 public class Navigator extends ParentController {
20 21
 
21
-	private final ModalStack modalStack = new ModalStack();
22
+    private static final NoOpPromise NO_OP = new NoOpPromise();
23
+    private final ModalStack modalStack = new ModalStack();
22 24
 	private ViewController root;
23 25
 	private OverlayPresenter overlayPresenter;
24 26
     private Options defaultOptions = new Options();
@@ -27,7 +29,7 @@ public class Navigator extends ParentController {
27 29
 		super(activity, "navigator" + CompatUtils.generateViewId());
28 30
 	}
29 31
 
30
-	@NonNull
32
+    @NonNull
31 33
 	@Override
32 34
 	protected ViewGroup createView() {
33 35
 		return new FrameLayout(getActivity());
@@ -46,18 +48,10 @@ public class Navigator extends ParentController {
46 48
 
47 49
 	@Override
48 50
 	public void destroy() {
49
-		modalStack.dismissAll(null);
51
+		modalStack.dismissAll(NO_OP);
50 52
 		super.destroy();
51 53
 	}
52 54
 
53
-	/*
54
-	 * Navigation methods
55
-	 */
56
-
57
-	void setRoot(final ViewController viewController) {
58
-		setRoot(viewController, null);
59
-	}
60
-
61 55
 	public void setRoot(final ViewController viewController, Promise promise) {
62 56
 		if (root != null) {
63 57
 			root.destroy();
@@ -65,9 +59,7 @@ public class Navigator extends ParentController {
65 59
 
66 60
 		root = viewController;
67 61
 		getView().addView(viewController.getView());
68
-		if (promise != null) {
69
-			promise.resolve(viewController.getId());
70
-		}
62
+        promise.resolve(viewController.getId());
71 63
 	}
72 64
 
73 65
     public void setDefaultOptions(Options defaultOptions) {
@@ -88,21 +80,13 @@ public class Navigator extends ParentController {
88 80
 		}
89 81
 	}
90 82
 
91
-	public void push(final String fromId, final ViewController viewController) {
92
-		push(fromId, viewController, null);
93
-	}
94
-
95 83
 	public void push(final String fromId, final ViewController viewController, Promise promise) {
96 84
 		ViewController from = findControllerById(fromId);
97 85
 		if (from != null) {
98
-		    from.performOnParentStack(stack -> stack.push(viewController, promise));
86
+		    from.performOnParentStack(stack -> stack.animatePush(viewController, promise));
99 87
 		}
100 88
 	}
101 89
 
102
-	void pop(final String fromId) {
103
-		pop(fromId, null);
104
-	}
105
-
106 90
 	void pop(final String fromId, Promise promise) {
107 91
 		ViewController from = findControllerById(fromId);
108 92
 		if (from != null) {
@@ -110,10 +94,6 @@ public class Navigator extends ParentController {
110 94
 		}
111 95
 	}
112 96
 
113
-	void popSpecific(final String id) {
114
-		popSpecific(id, null);
115
-	}
116
-
117 97
 	public void popSpecific(final String id, Promise promise) {
118 98
 		ViewController from = findControllerById(id);
119 99
 		if (from != null) {
@@ -123,10 +103,6 @@ public class Navigator extends ParentController {
123 103
 		}
124 104
 	}
125 105
 
126
-	void popToRoot(final String id) {
127
-		popToRoot(id, null);
128
-	}
129
-
130 106
 	public void popToRoot(final String id, Promise promise) {
131 107
 		ViewController from = findControllerById(id);
132 108
 		if (from != null) {
@@ -134,10 +110,6 @@ public class Navigator extends ParentController {
134 110
 		}
135 111
 	}
136 112
 
137
-	void popTo(final String componentId) {
138
-		popTo(componentId, null);
139
-	}
140
-
141 113
 	public void popTo(final String componentId, Promise promise) {
142 114
 		ViewController target = findControllerById(componentId);
143 115
 		if (target != null) {
@@ -170,9 +142,7 @@ public class Navigator extends ParentController {
170 142
 	}
171 143
 
172 144
 	static void rejectPromise(Promise promise) {
173
-		if (promise != null) {
174
-			promise.reject(new Throwable("Nothing to pop"));
175
-		}
145
+        promise.reject(new Throwable("Nothing to pop"));
176 146
 	}
177 147
 
178 148
     @Nullable

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

@@ -5,9 +5,12 @@ import android.support.annotation.NonNull;
5 5
 import android.support.annotation.Nullable;
6 6
 import android.view.ViewGroup;
7 7
 
8
+import com.reactnativenavigation.parse.Options;
9
+import com.reactnativenavigation.views.ReactComponent;
10
+
8 11
 import java.util.Collection;
9 12
 
10
-public abstract class ParentController extends ViewController {
13
+public abstract class ParentController<T extends ViewGroup> extends ViewController {
11 14
 
12 15
 	public ParentController(final Activity activity, final String id) {
13 16
 		super(activity, id);
@@ -15,15 +18,15 @@ public abstract class ParentController extends ViewController {
15 18
 
16 19
 	@NonNull
17 20
 	@Override
18
-	public ViewGroup getView() {
19
-		return (ViewGroup) super.getView();
21
+	public T getView() {
22
+		return (T) super.getView();
20 23
 	}
21 24
 
22 25
 	@NonNull
23 26
 	@Override
24
-	protected abstract ViewGroup createView();
27
+	protected abstract T createView();
25 28
 
26
-	@NonNull
29
+    @NonNull
27 30
 	public abstract Collection<? extends ViewController> getChildControllers();
28 31
 
29 32
 	@Nullable
@@ -40,6 +43,10 @@ public abstract class ParentController extends ViewController {
40 43
 		return null;
41 44
 	}
42 45
 
46
+    public void applyOptions(Options options, ReactComponent childComponent) {
47
+
48
+    }
49
+
43 50
 	@Override
44 51
 	public void destroy() {
45 52
 		super.destroy();
@@ -47,4 +54,8 @@ public abstract class ParentController extends ViewController {
47 54
 			child.destroy();
48 55
 		}
49 56
 	}
57
+
58
+    void clearOptions() {
59
+
60
+    }
50 61
 }

+ 79
- 64
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java View File

@@ -2,106 +2,117 @@ package com.reactnativenavigation.viewcontrollers;
2 2
 
3 3
 import android.app.Activity;
4 4
 import android.support.annotation.NonNull;
5
+import android.support.annotation.RestrictTo;
5 6
 import android.view.View;
6
-import android.view.ViewGroup;
7
-import android.widget.FrameLayout;
8 7
 
9 8
 import com.facebook.react.bridge.Promise;
10 9
 import com.reactnativenavigation.anim.NavigationAnimator;
10
+import com.reactnativenavigation.parse.Options;
11
+import com.reactnativenavigation.utils.NoOpPromise;
12
+import com.reactnativenavigation.views.ReactComponent;
13
+import com.reactnativenavigation.views.StackLayout;
14
+import com.reactnativenavigation.views.TopBar;
11 15
 
12 16
 import java.util.Collection;
13 17
 import java.util.Iterator;
14 18
 
15
-public class StackController extends ParentController {
19
+public class StackController extends ParentController <StackLayout> {
16 20
 
17
-	private final IdStack<ViewController> stack = new IdStack<>();
18
-	private final NavigationAnimator animator;
21
+    private static final NoOpPromise NO_OP = new NoOpPromise();
22
+    private final IdStack<ViewController> stack = new IdStack<>();
23
+    private final NavigationAnimator animator;
24
+    private StackLayout stackLayout;
19 25
 
20
-	public StackController(final Activity activity, String id) {
21
-		this(activity, id, new NavigationAnimator(activity));
22
-	}
23
-
24
-	public StackController(final Activity activity, String id, NavigationAnimator animator) {
26
+    public StackController(final Activity activity, String id) {
25 27
 		super(activity, id);
26
-		this.animator = animator;
27
-	}
28
+        animator = new NavigationAnimator(activity);
29
+    }
30
+
31
+    @RestrictTo(RestrictTo.Scope.TESTS)
32
+    TopBar getTopBar() {
33
+        return stackLayout.getTopBar();
34
+    }
28 35
 
29
-	public void push(final ViewController child, final Promise promise) {
30
-		final ViewController previousTop = peek();
36
+    @Override
37
+    public void applyOptions(Options options, ReactComponent component) {
38
+        stackLayout.applyOptions(options, component);
39
+    }
40
+
41
+    @Override
42
+    void clearOptions() {
43
+        stackLayout.clearOptions();
44
+    }
45
+
46
+    public void animatePush(final ViewController child, final Promise promise) {
47
+		final ViewController toRemove = stack.peek();
31 48
 
32 49
 		child.setParentController(this);
33 50
 		stack.push(child.getId(), child);
34 51
 		View enteringView = child.getView();
35 52
 		getView().addView(enteringView);
36 53
 
37
-		if (previousTop != null) {
38
-			animator.animatePush(enteringView, () -> {
39
-                getView().removeView(previousTop.getView());
40
-                if (promise != null) {
41
-                    promise.resolve(child.getId());
42
-                }
54
+		if (toRemove != null) {
55
+            animator.animatePush(enteringView, () -> {
56
+                getView().removeView(toRemove.getView());
57
+                promise.resolve(child.getId());
43 58
             });
44
-		} else if (promise != null) {
59
+		} else {
45 60
 			promise.resolve(child.getId());
46 61
 		}
47 62
 	}
48 63
 
49
-	boolean canPop() {
50
-		return stack.size() > 1;
51
-	}
64
+    public void pop(final Promise promise) {
65
+        if (!canPop()) {
66
+            Navigator.rejectPromise(promise);
67
+            return;
68
+        }
52 69
 
53
-	void pop(Promise promise) {
54
-		pop(true, promise);
55
-	}
70
+        final ViewController poppedTop = stack.pop();
71
+        ViewController newTop = stack.peek();
72
+
73
+        View enteringView = newTop.getView();
74
+        final View exitingView = poppedTop.getView();
75
+        getView().addView(enteringView, getView().getChildCount() - 1);
56 76
 
57
-	private void pop(boolean animate, final Promise promise) {
77
+        finishPopping(exitingView, poppedTop, promise);
78
+    }
79
+
80
+	public void animatePop(final Promise promise) {
58 81
 		if (!canPop()) {
59 82
 			Navigator.rejectPromise(promise);
60 83
 			return;
61 84
 		}
62 85
 
63 86
 		final ViewController poppedTop = stack.pop();
64
-		ViewController newTop = peek();
87
+		ViewController newTop = stack.peek();
65 88
 
66 89
 		View enteringView = newTop.getView();
67 90
 		final View exitingView = poppedTop.getView();
68 91
 		getView().addView(enteringView, getView().getChildCount() - 1);
69 92
 
70
-		if (animate) {
71
-			animator.animatePop(exitingView, () -> finishPopping(exitingView, poppedTop, promise));
72
-		} else {
73
-			finishPopping(exitingView, poppedTop, promise);
74
-		}
93
+        animator.animatePop(exitingView, () -> finishPopping(exitingView, poppedTop, promise));
75 94
 	}
76 95
 
96
+    boolean canPop() {
97
+        return stack.size() > 1;
98
+    }
99
+
77 100
 	private void finishPopping(View exitingView, ViewController poppedTop, Promise promise) {
78 101
 		getView().removeView(exitingView);
79 102
 		poppedTop.destroy();
80
-		if (promise != null) {
81
-			promise.resolve(poppedTop.getId());
82
-		}
83
-	}
84
-
85
-	void popSpecific(final ViewController childController) {
86
-		popSpecific(childController, null);
103
+        promise.resolve(poppedTop.getId());
87 104
 	}
88 105
 
89 106
 	void popSpecific(final ViewController childController, Promise promise) {
90 107
 		if (stack.isTop(childController.getId())) {
91
-			pop(promise);
108
+			animatePop(promise);
92 109
 		} else {
93 110
 			stack.remove(childController.getId());
94 111
 			childController.destroy();
95
-			if (promise != null) {
96
-				promise.resolve(childController.getId());
97
-			}
112
+            promise.resolve(childController.getId());
98 113
 		}
99 114
 	}
100 115
 
101
-	void popTo(ViewController viewController) {
102
-		popTo(viewController, null);
103
-	}
104
-
105 116
 	void popTo(final ViewController viewController, Promise promise) {
106 117
 		if (!stack.containsId(viewController.getId())) {
107 118
 			Navigator.rejectPromise(promise);
@@ -113,19 +124,23 @@ public class StackController extends ParentController {
113 124
 		while (!viewController.getId().equals(currentControlId)) {
114 125
 			String nextControlId = iterator.next();
115 126
 			boolean animate = nextControlId.equals(viewController.getId());
116
-			pop(animate, animate ? promise : null);
127
+			if (animate) {
128
+			    animatePop(promise);
129
+            } else {
130
+			    pop(NO_OP);
131
+            }
117 132
 			currentControlId = nextControlId;
118 133
 		}
119 134
 	}
120 135
 
121
-	void popToRoot() {
122
-		popToRoot(null);
123
-	}
124
-
125 136
 	void popToRoot(Promise promise) {
126 137
 		while (canPop()) {
127
-			boolean animate = stack.size() == 2; //first element is root
128
-			pop(animate, animate ? promise : null);
138
+			boolean animate = stack.size() == 2; // First element is root
139
+            if (animate) {
140
+                animatePop(promise);
141
+            } else {
142
+                pop(NO_OP);
143
+            }
129 144
 		}
130 145
 	}
131 146
 
@@ -144,18 +159,18 @@ public class StackController extends ParentController {
144 159
 	@Override
145 160
 	public boolean handleBack() {
146 161
 		if (canPop()) {
147
-			pop(null);
162
+			animatePop(NO_OP);
148 163
 			return true;
149
-		} else {
150
-			return false;
151 164
 		}
165
+        return false;
152 166
 	}
153 167
 
154
-	@NonNull
155
-	@Override
156
-	protected ViewGroup createView() {
157
-		return new FrameLayout(getActivity());
158
-	}
168
+    @NonNull
169
+    @Override
170
+    protected StackLayout createView() {
171
+        stackLayout = new StackLayout(getActivity());
172
+        return stackLayout;
173
+    }
159 174
 
160 175
 	@NonNull
161 176
 	@Override

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

@@ -16,13 +16,13 @@ import com.reactnativenavigation.utils.Task;
16 16
 
17 17
 public abstract class ViewController implements ViewTreeObserver.OnGlobalLayoutListener {
18 18
 
19
+    public Options options;
20
+
19 21
 	private final Activity activity;
20 22
 	private final String id;
21
-
22 23
 	private View view;
23 24
 	private ParentController parentController;
24 25
 	private boolean isShown = false;
25
-
26 26
     private boolean isDestroyed;
27 27
 
28 28
 	public ViewController(Activity activity, String id) {
@@ -32,7 +32,8 @@ public abstract class ViewController implements ViewTreeObserver.OnGlobalLayoutL
32 32
 
33 33
 	protected abstract View createView();
34 34
 
35
-	@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
35
+	@SuppressWarnings("WeakerAccess")
36
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
36 37
 	public void ensureViewIsCreated() {
37 38
 		getView();
38 39
 	}
@@ -41,12 +42,18 @@ public abstract class ViewController implements ViewTreeObserver.OnGlobalLayoutL
41 42
 		return false;
42 43
 	}
43 44
 
45
+    public void applyOptions(Options options) {
46
+
47
+    }
48
+
44 49
 	public Activity getActivity() {
45 50
 		return activity;
46 51
 	}
47 52
 
48
-    protected ViewController getParentController() {
49
-	    return parentController;
53
+	protected void applyOnParentStack(Task<ParentController> task) {
54
+        if (parentController != null) {
55
+            task.run(parentController);
56
+        }
50 57
     }
51 58
 
52 59
 	@Nullable
@@ -106,10 +113,6 @@ public abstract class ViewController implements ViewTreeObserver.OnGlobalLayoutL
106 113
 
107 114
 	public void onViewDisappear() {
108 115
         isShown = false;
109
-    }
110
-
111
-    public void applyOptions(Options options) {
112
-
113 116
     }
114 117
 
115 118
 	public void destroy() {

+ 2
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabController.java View File

@@ -13,7 +13,6 @@ public class TopTabController extends ViewController implements NavigationOption
13 13
 
14 14
     private final String componentName;
15 15
     private ComponentViewController.ReactViewCreator viewCreator;
16
-    private final Options options;
17 16
     private TopTab topTab;
18 17
     private boolean isSelectedTab;
19 18
 
@@ -34,7 +33,7 @@ public class TopTabController extends ViewController implements NavigationOption
34 33
 
35 34
     @Override
36 35
     public void applyOptions(Options options) {
37
-        getParentController().applyOptions(options);
36
+        applyOnParentStack(parentController -> parentController.applyOptions(options));
38 37
     }
39 38
 
40 39
     @Override
@@ -72,7 +71,7 @@ public class TopTabController extends ViewController implements NavigationOption
72 71
     }
73 72
 
74 73
     String getTabTitle() {
75
-        return options.topTabOptions.title;
74
+        return options.topTabOptions.title.get("");
76 75
     }
77 76
 
78 77
     public void setTabIndex(int i) {

+ 8
- 5
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsAdapter.java View File

@@ -5,19 +5,22 @@ import android.support.v4.view.ViewPager;
5 5
 import android.view.View;
6 6
 import android.view.ViewGroup;
7 7
 
8
+import com.reactnativenavigation.parse.Options;
9
+import com.reactnativenavigation.viewcontrollers.ViewController;
10
+
8 11
 import java.util.List;
9 12
 
10 13
 public class TopTabsAdapter extends PagerAdapter implements ViewPager.OnPageChangeListener {
11
-    private List<TopTabController> tabs;
14
+    private List<ViewController> tabs;
12 15
     private int currentPage = 0;
13 16
 
14
-    public TopTabsAdapter(List<TopTabController> tabs) {
17
+    public TopTabsAdapter(List<ViewController> tabs) {
15 18
         this.tabs = tabs;
16 19
     }
17 20
 
18 21
     @Override
19 22
     public CharSequence getPageTitle(int position) {
20
-        return tabs.get(position).getTabTitle();
23
+        return getTabOptions(position).topTabOptions.title.get("");
21 24
     }
22 25
 
23 26
     @Override
@@ -52,7 +55,7 @@ public class TopTabsAdapter extends PagerAdapter implements ViewPager.OnPageChan
52 55
 
53 56
     }
54 57
 
55
-    int getCurrentItem() {
56
-        return currentPage;
58
+    private Options getTabOptions(int position) {
59
+        return tabs.get(position).options;
57 60
     }
58 61
 }

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

@@ -17,12 +17,12 @@ import java.util.List;
17 17
 
18 18
 public class TopTabsController extends ParentController implements NavigationOptionsListener {
19 19
 
20
-    private List<TopTabController> tabs;
20
+    private List<ViewController> tabs;
21 21
     private TopTabsLayout topTabsLayout;
22 22
     private TopTabsLayoutCreator viewCreator;
23 23
     private Options options;
24 24
 
25
-    public TopTabsController(Activity activity, String id, List<TopTabController> tabs, TopTabsLayoutCreator viewCreator, Options options) {
25
+    public TopTabsController(Activity activity, String id, List<ViewController> tabs, TopTabsLayoutCreator viewCreator, Options options) {
26 26
         super(activity, id);
27 27
         this.viewCreator = viewCreator;
28 28
         this.options = options;
@@ -48,12 +48,12 @@ public class TopTabsController extends ParentController implements NavigationOpt
48 48
     @Override
49 49
     public void onViewAppeared() {
50 50
         applyOptions(options);
51
-        performOnCurrentTab(TopTabController::onViewAppeared);
51
+        performOnCurrentTab(ViewController::onViewAppeared);
52 52
     }
53 53
 
54 54
     @Override
55 55
     public void onViewDisappear() {
56
-        performOnCurrentTab(TopTabController::onViewDisappear);
56
+        performOnCurrentTab(ViewController::onViewDisappear);
57 57
     }
58 58
 
59 59
     @Override
@@ -70,7 +70,7 @@ public class TopTabsController extends ParentController implements NavigationOpt
70 70
         topTabsLayout.switchToTab(index);
71 71
     }
72 72
 
73
-    private void performOnCurrentTab(Task<TopTabController> task) {
73
+    private void performOnCurrentTab(Task<ViewController> task) {
74 74
         task.run(tabs.get(topTabsLayout.getCurrentItem()));
75 75
     }
76 76
 }

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

@@ -15,9 +15,9 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
15 15
 @SuppressLint("ViewConstructor")
16 16
 public class TopTabsViewPager extends ViewPager {
17 17
     private static final int OFFSCREEN_PAGE_LIMIT = 99;
18
-    private List<TopTabController> tabs;
18
+    private List<ViewController> tabs;
19 19
 
20
-    public TopTabsViewPager(Context context, List<TopTabController> tabs, TopTabsAdapter adapter) {
20
+    public TopTabsViewPager(Context context, List<ViewController> tabs, TopTabsAdapter adapter) {
21 21
         super(context);
22 22
         this.tabs = tabs;
23 23
         init(adapter);

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

@@ -5,9 +5,7 @@ import com.reactnativenavigation.parse.Options;
5 5
 public interface Component {
6 6
     void applyOptions(Options options);
7 7
 
8
-    TopBar getTopBar();
9
-
10 8
     void drawBehindTopBar();
11 9
 
12
-    void drawBelowTopBar();
10
+    void drawBelowTopBar(TopBar topBar);
13 11
 }

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

@@ -3,36 +3,25 @@ package com.reactnativenavigation.views;
3 3
 import android.annotation.SuppressLint;
4 4
 import android.content.Context;
5 5
 import android.view.View;
6
+import android.widget.FrameLayout;
6 7
 import android.widget.RelativeLayout;
7 8
 
8 9
 import com.reactnativenavigation.interfaces.ScrollEventListener;
9 10
 import com.reactnativenavigation.parse.Options;
10
-import com.reactnativenavigation.presentation.OptionsPresenter;
11 11
 import com.reactnativenavigation.viewcontrollers.ComponentViewController.IReactView;
12 12
 
13 13
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
14
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
14
+import static android.widget.RelativeLayout.BELOW;
15 15
 
16 16
 @SuppressLint("ViewConstructor")
17
-public class ComponentLayout extends RelativeLayout implements ReactComponent, TitleBarButton.OnClickListener {
17
+public class ComponentLayout extends FrameLayout implements ReactComponent, TitleBarButton.OnClickListener {
18 18
 
19
-    private TopBar topBar;
20 19
     private IReactView reactView;
21
-    private final OptionsPresenter optionsPresenter;
22 20
 
23 21
 	public ComponentLayout(Context context, IReactView reactView) {
24 22
 		super(context);
25 23
 		this.reactView = reactView;
26
-		this.topBar = new TopBar(context, reactView.asView(), reactView.getScrollEventListener(), this);
27
-        optionsPresenter = new OptionsPresenter(this);
28
-        initViews();
29
-    }
30
-
31
-    private void initViews() {
32
-        LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
33
-        layoutParams.addRule(BELOW, topBar.getId());
34
-        addView(reactView.asView(), layoutParams);
35
-        addView(topBar, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
24
+        addView(reactView.asView(), MATCH_PARENT, MATCH_PARENT);
36 25
     }
37 26
 
38 27
     @Override
@@ -62,7 +51,7 @@ public class ComponentLayout extends RelativeLayout implements ReactComponent, T
62 51
 
63 52
     @Override
64 53
     public void applyOptions(Options options) {
65
-        optionsPresenter.applyOptions(options);
54
+
66 55
     }
67 56
 
68 57
     @Override
@@ -75,11 +64,6 @@ public class ComponentLayout extends RelativeLayout implements ReactComponent, T
75 64
         return reactView.getScrollEventListener();
76 65
     }
77 66
 
78
-    @Override
79
-    public TopBar getTopBar() {
80
-        return topBar;
81
-    }
82
-
83 67
     @Override
84 68
     public void drawBehindTopBar() {
85 69
         RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) reactView.asView().getLayoutParams();
@@ -88,7 +72,7 @@ public class ComponentLayout extends RelativeLayout implements ReactComponent, T
88 72
     }
89 73
 
90 74
     @Override
91
-    public void drawBelowTopBar() {
75
+    public void drawBelowTopBar(TopBar topBar) {
92 76
         RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) reactView.asView().getLayoutParams();
93 77
         layoutParams.addRule(BELOW, topBar.getId());
94 78
         reactView.asView().setLayoutParams(layoutParams);

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

@@ -0,0 +1,46 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.content.Context;
4
+import android.support.annotation.RestrictTo;
5
+import android.widget.RelativeLayout;
6
+
7
+import com.reactnativenavigation.parse.Options;
8
+import com.reactnativenavigation.presentation.OptionsPresenter;
9
+import com.reactnativenavigation.utils.CompatUtils;
10
+
11
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
12
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
13
+
14
+public class StackLayout extends RelativeLayout implements TitleBarButton.OnClickListener {
15
+
16
+    private final TopBar topBar;
17
+
18
+    public StackLayout(Context context) {
19
+        super(context);
20
+        topBar = new TopBar(context, this);
21
+        topBar.setId(CompatUtils.generateViewId());
22
+        createLayout();
23
+    }
24
+
25
+    void createLayout() {
26
+        addView(topBar, MATCH_PARENT, WRAP_CONTENT);
27
+    }
28
+
29
+    @Override
30
+    public void onPress(String buttonId) {
31
+
32
+    }
33
+
34
+    public void applyOptions(Options options, ReactComponent component) {
35
+        new OptionsPresenter(topBar, component).applyOptions(options);
36
+    }
37
+
38
+    @RestrictTo(RestrictTo.Scope.TESTS)
39
+    public TopBar getTopBar() {
40
+        return topBar;
41
+    }
42
+
43
+    public void clearOptions() {
44
+        topBar.clear();
45
+    }
46
+}

+ 6
- 6
lib/android/app/src/main/java/com/reactnativenavigation/views/TitleBarButton.java View File

@@ -38,7 +38,7 @@ public class TitleBarButton implements MenuItem.OnMenuItemClickListener {
38 38
 	}
39 39
 
40 40
 	void addToMenu(Context context, final Menu menu) {
41
-		MenuItem menuItem = menu.add(button.title);
41
+		MenuItem menuItem = menu.add(button.title.get(""));
42 42
 		menuItem.setShowAsAction(button.showAsAction);
43 43
 		menuItem.setEnabled(button.disabled != Options.BooleanOptions.True);
44 44
 		menuItem.setOnMenuItemClickListener(this);
@@ -57,7 +57,7 @@ public class TitleBarButton implements MenuItem.OnMenuItemClickListener {
57 57
 			return;
58 58
 		}
59 59
 
60
-		ImageUtils.tryLoadIcon(context, button.icon, new ImageUtils.ImageLoadingListener() {
60
+		ImageUtils.tryLoadIcon(context, button.icon.get(), new ImageUtils.ImageLoadingListener() {
61 61
 			@Override
62 62
 			public void onComplete(@NonNull Drawable drawable) {
63 63
 				icon = drawable;
@@ -77,7 +77,7 @@ public class TitleBarButton implements MenuItem.OnMenuItemClickListener {
77 77
 	}
78 78
 
79 79
 	private void applyIcon(Context context, final MenuItem menuItem) {
80
-		ImageUtils.tryLoadIcon(context, button.icon, new ImageUtils.ImageLoadingListener() {
80
+		ImageUtils.tryLoadIcon(context, button.icon.get(), new ImageUtils.ImageLoadingListener() {
81 81
 			@Override
82 82
 			public void onComplete(@NonNull Drawable drawable) {
83 83
 				icon = drawable;
@@ -116,8 +116,8 @@ public class TitleBarButton implements MenuItem.OnMenuItemClickListener {
116 116
 	}
117 117
 
118 118
 	private void setFontSize(MenuItem menuItem) {
119
-		SpannableString spanString = new SpannableString(button.title);
120
-		spanString.setSpan(new AbsoluteSizeSpan(button.buttonFontSize, true), 0, button.title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
119
+		SpannableString spanString = new SpannableString(button.title.get());
120
+		spanString.setSpan(new AbsoluteSizeSpan(button.buttonFontSize, true), 0, button.title.get().length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
121 121
 		menuItem.setTitleCondensed(spanString);
122 122
 	}
123 123
 
@@ -134,7 +134,7 @@ public class TitleBarButton implements MenuItem.OnMenuItemClickListener {
134 134
 	@NonNull
135 135
 	private ArrayList<View> findActualTextViewInMenuByLabel() {
136 136
 		ArrayList<View> outViews = new ArrayList<>();
137
-		this.toolbar.findViewsWithText(outViews, button.title, View.FIND_VIEWS_WITH_TEXT);
137
+		this.toolbar.findViewsWithText(outViews, button.title.get(), View.FIND_VIEWS_WITH_TEXT);
138 138
 		return outViews;
139 139
 	}
140 140
 

+ 22
- 16
lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java View File

@@ -3,7 +3,6 @@ package com.reactnativenavigation.views;
3 3
 import android.annotation.SuppressLint;
4 4
 import android.content.Context;
5 5
 import android.graphics.Typeface;
6
-import android.support.annotation.ColorInt;
7 6
 import android.support.annotation.Nullable;
8 7
 import android.support.design.widget.AppBarLayout;
9 8
 import android.support.v7.widget.Toolbar;
@@ -18,6 +17,7 @@ import com.reactnativenavigation.anim.TopBarCollapseBehavior;
18 17
 import com.reactnativenavigation.interfaces.ScrollEventListener;
19 18
 import com.reactnativenavigation.parse.Button;
20 19
 import com.reactnativenavigation.parse.Color;
20
+import com.reactnativenavigation.parse.Fraction;
21 21
 import com.reactnativenavigation.parse.Number;
22 22
 import com.reactnativenavigation.parse.Options;
23 23
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsViewPager;
@@ -32,13 +32,13 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
32 32
     private final TopBarAnimator animator;
33 33
     private TopTabs topTabs;
34 34
 
35
-    public TopBar(final Context context, View contentView, ScrollEventListener scrollEventListener, TitleBarButton.OnClickListener onClickListener) {
35
+    public TopBar(final Context context, TitleBarButton.OnClickListener onClickListener) {
36 36
         super(context);
37 37
         this.onClickListener = onClickListener;
38
-        collapsingBehavior = new TopBarCollapseBehavior(this, scrollEventListener);
38
+        collapsingBehavior = new TopBarCollapseBehavior(this);
39 39
         titleBar = new Toolbar(context);
40 40
         topTabs = new TopTabs(getContext());
41
-        this.animator = new TopBarAnimator(this, contentView);
41
+        this.animator = new TopBarAnimator(this);
42 42
         addView(titleBar);
43 43
     }
44 44
 
@@ -50,14 +50,14 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
50 50
         return titleBar.getTitle() != null ? titleBar.getTitle().toString() : "";
51 51
     }
52 52
 
53
-    public void setTitleTextColor(@ColorInt int color) {
54
-        titleBar.setTitleTextColor(color);
53
+    public void setTitleTextColor(Color color) {
54
+        if (color.hasValue()) titleBar.setTitleTextColor(color.get());
55 55
     }
56 56
 
57
-    public void setTitleFontSize(float size) {
57
+    public void setTitleFontSize(Fraction size) {
58 58
         TextView titleTextView = getTitleTextView();
59
-        if (titleTextView != null) {
60
-            titleTextView.setTextSize(size);
59
+        if (titleTextView != null && size.hasValue()) {
60
+            titleTextView.setTextSize(size.get());
61 61
         }
62 62
     }
63 63
 
@@ -89,27 +89,27 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
89 89
         return findTextView(titleBar);
90 90
     }
91 91
 
92
-    @Override
93
-    public void setBackgroundColor(@ColorInt int color) {
94
-        titleBar.setBackgroundColor(color);
92
+    public void setBackgroundColor(Color color) {
93
+        if (color.hasValue()) titleBar.setBackgroundColor(color.get());
95 94
     }
96 95
 
97 96
     @Nullable
98 97
     private TextView findTextView(ViewGroup root) {
99 98
         for (int i = 0; i < root.getChildCount(); i++) {
100 99
             View view = root.getChildAt(i);
100
+            if (view instanceof ViewGroup) {
101
+                view = findTextView((ViewGroup) view);
102
+            }
101 103
             if (view instanceof TextView) {
102 104
                 return (TextView) view;
103 105
             }
104
-            if (view instanceof ViewGroup) {
105
-                return findTextView((ViewGroup) view);
106
-            }
107 106
         }
108 107
         return null;
109 108
     }
110 109
 
111 110
     private void setLeftButtons(ArrayList<Button> leftButtons) {
112
-        if (leftButtons == null || leftButtons.isEmpty()) {
111
+        if (leftButtons == null) return;
112
+        if (leftButtons.isEmpty()) {
113 113
             titleBar.setNavigationIcon(null);
114 114
             return;
115 115
         }
@@ -185,4 +185,10 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
185 185
             setVisibility(View.GONE);
186 186
         }
187 187
     }
188
+
189
+    public void clear() {
190
+        titleBar.setTitle(null);
191
+        titleBar.setNavigationIcon(null);
192
+        titleBar.getMenu().clear();
193
+    }
188 194
 }

+ 16
- 28
lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayout.java View File

@@ -1,34 +1,32 @@
1 1
 package com.reactnativenavigation.views;
2 2
 
3
-import android.annotation.*;
4
-import android.content.*;
5
-import android.support.annotation.*;
6
-import android.support.v4.view.*;
7
-import android.view.*;
8
-import android.widget.*;
3
+import android.annotation.SuppressLint;
4
+import android.content.Context;
5
+import android.support.annotation.RestrictTo;
6
+import android.support.v4.view.ViewPager;
7
+import android.view.View;
8
+import android.view.ViewGroup;
9
+import android.widget.RelativeLayout;
9 10
 
10
-import com.reactnativenavigation.parse.*;
11
-import com.reactnativenavigation.presentation.*;
12
-import com.reactnativenavigation.utils.*;
13
-import com.reactnativenavigation.viewcontrollers.toptabs.*;
11
+import com.reactnativenavigation.parse.Options;
12
+import com.reactnativenavigation.viewcontrollers.ViewController;
13
+import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsAdapter;
14
+import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsViewPager;
14 15
 
15
-import java.util.*;
16
+import java.util.List;
16 17
 
17 18
 @SuppressLint("ViewConstructor")
18 19
 public class TopTabsLayout extends RelativeLayout implements Component, TitleBarButton.OnClickListener {
19 20
 
20 21
     private TopBar topBar;
21
-    private List<TopTabController> tabs;
22 22
     private TopTabsViewPager viewPager;
23
-    private final OptionsPresenter optionsPresenter;
24 23
 
25
-    public TopTabsLayout(Context context, List<TopTabController> tabs, TopTabsAdapter adapter) {
24
+    public TopTabsLayout(Context context, List<ViewController> tabs, TopTabsAdapter adapter) {
26 25
         super(context);
27
-        this.tabs = tabs;
28 26
         viewPager = new TopTabsViewPager(context, tabs, adapter);
29
-        topBar = new TopBar(context, viewPager, null, this);
27
+        topBar = new TopBar(context, this);
30 28
         topBar.setId(View.generateViewId());
31
-        optionsPresenter = new OptionsPresenter(this);
29
+
32 30
         initViews();
33 31
     }
34 32
 
@@ -42,12 +40,7 @@ public class TopTabsLayout extends RelativeLayout implements Component, TitleBar
42 40
 
43 41
     @Override
44 42
     public void applyOptions(Options options) {
45
-        optionsPresenter.applyOptions(options);
46
-    }
47 43
 
48
-    @Override
49
-    public TopBar getTopBar() {
50
-        return topBar;
51 44
     }
52 45
 
53 46
     @Override
@@ -56,7 +49,7 @@ public class TopTabsLayout extends RelativeLayout implements Component, TitleBar
56 49
     }
57 50
 
58 51
     @Override
59
-    public void drawBelowTopBar() {
52
+    public void drawBelowTopBar(TopBar topBar) {
60 53
 
61 54
     }
62 55
 
@@ -65,11 +58,6 @@ public class TopTabsLayout extends RelativeLayout implements Component, TitleBar
65 58
         return viewPager;
66 59
     }
67 60
 
68
-
69
-    public void performOnCurrentTab(Task<TopTabController> task) {
70
-        task.run(tabs.get(viewPager.getCurrentItem()));
71
-    }
72
-
73 61
     public void switchToTab(int index) {
74 62
         viewPager.setCurrentItem(index);
75 63
     }

+ 3
- 3
lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayoutCreator.java View File

@@ -2,16 +2,16 @@ package com.reactnativenavigation.views;
2 2
 
3 3
 import android.content.Context;
4 4
 
5
-import com.reactnativenavigation.viewcontrollers.toptabs.TopTabController;
5
+import com.reactnativenavigation.viewcontrollers.ViewController;
6 6
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsAdapter;
7 7
 
8 8
 import java.util.List;
9 9
 
10 10
 public class TopTabsLayoutCreator {
11 11
     private Context context;
12
-    private List<TopTabController> tabs;
12
+    private List<ViewController> tabs;
13 13
 
14
-    public TopTabsLayoutCreator(Context context, List<TopTabController> tabs) {
14
+    public TopTabsLayoutCreator(Context context, List<ViewController> tabs) {
15 15
         this.context = context;
16 16
         this.tabs = tabs;
17 17
     }

+ 10
- 17
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestComponentLayout.java View File

@@ -1,31 +1,24 @@
1 1
 package com.reactnativenavigation.mocks;
2 2
 
3
-import android.content.*;
4
-import android.view.*;
5
-import android.widget.*;
3
+import android.content.Context;
4
+import android.view.View;
5
+import android.view.ViewGroup;
6
+import android.widget.RelativeLayout;
6 7
 
7 8
 import com.reactnativenavigation.interfaces.ScrollEventListener;
8
-import com.reactnativenavigation.parse.*;
9
-import com.reactnativenavigation.presentation.*;
10
-import com.reactnativenavigation.views.*;
9
+import com.reactnativenavigation.parse.Options;
10
+import com.reactnativenavigation.views.ReactComponent;
11
+import com.reactnativenavigation.views.TitleBarButton;
12
+import com.reactnativenavigation.views.TopBar;
11 13
 
12 14
 public class TestComponentLayout extends RelativeLayout implements ReactComponent, TitleBarButton.OnClickListener {
13 15
 
14
-    private final TopBar topBar;
15 16
     private final View contentView;
16
-    private final OptionsPresenter optionsPresenter;
17 17
 
18 18
     public TestComponentLayout(final Context context) {
19 19
         super(context);
20 20
         contentView = new View(context);
21
-        topBar = new TopBar(context, contentView, null, this);
22
-        addView(topBar);
23 21
         addView(contentView);
24
-        optionsPresenter = new OptionsPresenter(this);
25
-    }
26
-
27
-    public TopBar getTopBar() {
28
-        return topBar;
29 22
     }
30 23
 
31 24
     @Override
@@ -36,7 +29,7 @@ public class TestComponentLayout extends RelativeLayout implements ReactComponen
36 29
     }
37 30
 
38 31
     @Override
39
-    public void drawBelowTopBar() {
32
+    public void drawBelowTopBar(TopBar topBar) {
40 33
         RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
41 34
         layoutParams.addRule(BELOW, topBar.getId());
42 35
         contentView.setLayoutParams(layoutParams);
@@ -66,7 +59,7 @@ public class TestComponentLayout extends RelativeLayout implements ReactComponen
66 59
 
67 60
     @Override
68 61
     public void applyOptions(Options options) {
69
-        optionsPresenter.applyOptions(options);
62
+
70 63
     }
71 64
 
72 65
     @Override

+ 8
- 8
lib/android/app/src/test/java/com/reactnativenavigation/parse/NavigationOptionsTest.java View File

@@ -53,10 +53,10 @@ public class NavigationOptionsTest extends BaseTest {
53 53
     }
54 54
 
55 55
     private void assertResult(Options result) {
56
-        assertThat(result.topBarOptions.title).isEqualTo(TITLE);
57
-        assertThat(result.topBarOptions.backgroundColor).isEqualTo(TOP_BAR_BACKGROUND_COLOR);
58
-        assertThat(result.topBarOptions.textColor).isEqualTo(TOP_BAR_TEXT_COLOR);
59
-        assertThat(result.topBarOptions.textFontSize).isEqualTo(TOP_BAR_FONT_SIZE);
56
+        assertThat(result.topBarOptions.title.get()).isEqualTo(TITLE);
57
+        assertThat(result.topBarOptions.backgroundColor.get()).isEqualTo(TOP_BAR_BACKGROUND_COLOR);
58
+        assertThat(result.topBarOptions.textColor.get()).isEqualTo(TOP_BAR_TEXT_COLOR);
59
+        assertThat(result.topBarOptions.textFontSize.get()).isEqualTo(TOP_BAR_FONT_SIZE);
60 60
         assertThat(result.topBarOptions.textFontFamily).isEqualTo(TOP_BAR_TYPEFACE);
61 61
         assertThat(result.topBarOptions.hidden).isEqualTo(TOP_BAR_HIDDEN);
62 62
         assertThat(result.topBarOptions.drawBehind).isEqualTo(TOP_BAR_DRAW_BEHIND);
@@ -64,7 +64,7 @@ public class NavigationOptionsTest extends BaseTest {
64 64
         assertThat(result.bottomTabsOptions.animateHide).isEqualTo(BOTTOM_TABS_ANIMATE_HIDE);
65 65
         assertThat(result.bottomTabsOptions.hidden).isEqualTo(BOTTOM_TABS_HIDDEN);
66 66
         assertThat(result.bottomTabsOptions.tabBadge).isEqualTo(BOTTOM_TABS_BADGE);
67
-        assertThat(result.bottomTabsOptions.currentTabId).isEqualTo(BOTTOM_TABS_CURRENT_TAB_ID);
67
+        assertThat(result.bottomTabsOptions.currentTabId.get()).isEqualTo(BOTTOM_TABS_CURRENT_TAB_ID);
68 68
         assertThat(result.bottomTabsOptions.currentTabIndex).isEqualTo(BOTTOM_TABS_CURRENT_TAB_INDEX);
69 69
     }
70 70
 
@@ -81,7 +81,7 @@ public class NavigationOptionsTest extends BaseTest {
81 81
     @NonNull
82 82
     private JSONObject createTopBar() throws JSONException {
83 83
         return new JSONObject()
84
-                .put("title", TITLE)
84
+                .put("title", "the title")
85 85
                 .put("backgroundColor", TOP_BAR_BACKGROUND_COLOR)
86 86
                 .put("textColor", TOP_BAR_TEXT_COLOR)
87 87
                 .put("textFontSize", TOP_BAR_FONT_SIZE)
@@ -94,7 +94,7 @@ public class NavigationOptionsTest extends BaseTest {
94 94
     @NonNull
95 95
     private JSONObject createOtherTopBar() throws JSONException {
96 96
         return new JSONObject()
97
-                .put("title", TITLE)
97
+                .put("title", "the title")
98 98
                 .put("backgroundColor", TOP_BAR_BACKGROUND_COLOR)
99 99
                 .put("textColor", TOP_BAR_TEXT_COLOR)
100 100
                 .put("textFontSize", TOP_BAR_FONT_SIZE)
@@ -142,6 +142,6 @@ public class NavigationOptionsTest extends BaseTest {
142 142
     @Test
143 143
     public void defaultEmptyOptions() throws Exception {
144 144
         Options uut = new Options();
145
-        assertThat(uut.topBarOptions.title).isEmpty();
145
+        assertThat(uut.topBarOptions.title.get("")).isEmpty();
146 146
     }
147 147
 }

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

@@ -76,7 +76,7 @@ public class BottomTabsControllerTest extends BaseTest {
76 76
         assertThat(uut.findControllerById("123")).isNull();
77 77
         assertThat(uut.findControllerById(uut.getId())).isEqualTo(uut);
78 78
         StackController inner = new StackController(activity, "inner");
79
-        inner.push(child1, new MockPromise());
79
+        inner.animatePush(child1, new MockPromise());
80 80
         assertThat(uut.findControllerById(child1.getId())).isNull();
81 81
         uut.setTabs(Collections.singletonList(inner));
82 82
         assertThat(uut.findControllerById(child1.getId())).isEqualTo(child1);

+ 16
- 13
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ComponentViewControllerTest.java View File

@@ -1,18 +1,23 @@
1 1
 package com.reactnativenavigation.viewcontrollers;
2 2
 
3
-import android.app.*;
3
+import android.app.Activity;
4 4
 
5
-import com.reactnativenavigation.*;
6
-import com.reactnativenavigation.mocks.*;
7
-import com.reactnativenavigation.parse.*;
5
+import com.reactnativenavigation.BaseTest;
6
+import com.reactnativenavigation.mocks.TestComponentLayout;
7
+import com.reactnativenavigation.parse.Options;
8
+import com.reactnativenavigation.views.StackLayout;
8 9
 
9
-import org.junit.*;
10
+import org.junit.Test;
10 11
 
11
-import static org.assertj.core.api.Java6Assertions.*;
12
-import static org.mockito.Mockito.*;
12
+import static org.assertj.core.api.Java6Assertions.assertThat;
13
+import static org.mockito.Mockito.spy;
14
+import static org.mockito.Mockito.times;
15
+import static org.mockito.Mockito.verify;
16
+import static org.mockito.Mockito.when;
13 17
 
14 18
 public class ComponentViewControllerTest extends BaseTest {
15 19
     private ComponentViewController uut;
20
+    private ParentController<StackLayout> parentController;
16 21
     private ComponentViewController.IReactView view;
17 22
 
18 23
     @Override
@@ -20,12 +25,10 @@ public class ComponentViewControllerTest extends BaseTest {
20 25
         super.beforeEach();
21 26
         Activity activity = newActivity();
22 27
         view = spy(new TestComponentLayout(activity));
23
-        uut = new ComponentViewController(activity, "componentId1", "componentName", new ComponentViewController.ReactViewCreator() {
24
-            @Override
25
-            public ComponentViewController.IReactView create(final Activity activity1, final String componentId, final String componentName) {
26
-                return view;
27
-            }
28
-        }, new Options());
28
+        parentController = new StackController(activity, "stack");
29
+        uut = new ComponentViewController(activity, "componentId1", "componentName", (activity1, componentId, componentName) -> view, new Options());
30
+        uut.setParentController(parentController);
31
+        parentController.ensureViewIsCreated();
29 32
     }
30 33
 
31 34
     @Test

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

@@ -20,6 +20,7 @@ import static org.mockito.Mockito.*;
20 20
 public class NavigatorTest extends BaseTest {
21 21
     private Activity activity;
22 22
     private Navigator uut;
23
+    private ParentController parentController;
23 24
     private SimpleViewController child1;
24 25
     private ViewController child2;
25 26
     private ViewController child3;
@@ -32,6 +33,8 @@ public class NavigatorTest extends BaseTest {
32 33
         super.beforeEach();
33 34
         activity = newActivity();
34 35
         uut = new Navigator(activity);
36
+        parentController = new StackController(activity, "stack");
37
+        parentController.ensureViewIsCreated();
35 38
         child1 = new SimpleViewController(activity, "child1");
36 39
         child2 = new SimpleViewController(activity, "child2");
37 40
         child3 = new SimpleViewController(activity, "child3");
@@ -42,14 +45,14 @@ public class NavigatorTest extends BaseTest {
42 45
     @Test
43 46
     public void setRoot_AddsChildControllerView() throws Exception {
44 47
         assertThat(uut.getView().getChildCount()).isZero();
45
-        uut.setRoot(child1);
48
+        uut.setRoot(child1, new MockPromise());
46 49
         assertIsChildById(uut.getView(), child1.getView());
47 50
     }
48 51
 
49 52
     @Test
50 53
     public void setRoot_ReplacesExistingChildControllerViews() throws Exception {
51
-        uut.setRoot(child1);
52
-        uut.setRoot(child2);
54
+        uut.setRoot(child1, new MockPromise());
55
+        uut.setRoot(child2, new MockPromise());
53 56
         assertIsChildById(uut.getView(), child2.getView());
54 57
     }
55 58
 
@@ -62,13 +65,13 @@ public class NavigatorTest extends BaseTest {
62 65
     @Test
63 66
     public void push() throws Exception {
64 67
         StackController stackController = newStack();
65
-        stackController.push(child1, new MockPromise());
66
-        uut.setRoot(stackController);
68
+        stackController.animatePush(child1, new MockPromise());
69
+        uut.setRoot(stackController, new MockPromise());
67 70
 
68 71
         assertIsChildById(uut.getView(), stackController.getView());
69 72
         assertIsChildById(stackController.getView(), child1.getView());
70 73
 
71
-        uut.push(child1.getId(), child2);
74
+        uut.push(child1.getId(), child2, new MockPromise());
72 75
 
73 76
         assertIsChildById(uut.getView(), stackController.getView());
74 77
         assertIsChildById(stackController.getView(), child2.getView());
@@ -76,8 +79,8 @@ public class NavigatorTest extends BaseTest {
76 79
 
77 80
     @Test
78 81
     public void push_InvalidPushWithoutAStack_DoesNothing() throws Exception {
79
-        uut.setRoot(child1);
80
-        uut.push(child1.getId(), child2);
82
+        uut.setRoot(child1, new MockPromise());
83
+        uut.push(child1.getId(), child2, new MockPromise());
81 84
         assertIsChildById(uut.getView(), child1.getView());
82 85
     }
83 86
 
@@ -86,13 +89,13 @@ public class NavigatorTest extends BaseTest {
86 89
         BottomTabsController bottomTabsController = newTabs();
87 90
         StackController stack1 = newStack();
88 91
         StackController stack2 = newStack();
89
-        stack1.push(child1, new MockPromise());
90
-        stack2.push(child2, new MockPromise());
92
+        stack1.animatePush(child1, new MockPromise());
93
+        stack2.animatePush(child2, new MockPromise());
91 94
         bottomTabsController.setTabs(Arrays.asList(stack1, stack2));
92
-        uut.setRoot(bottomTabsController);
95
+        uut.setRoot(bottomTabsController, new MockPromise());
93 96
 
94 97
         SimpleViewController newChild = new SimpleViewController(activity, "new child");
95
-        uut.push(child2.getId(), newChild);
98
+        uut.push(child2.getId(), newChild, new MockPromise());
96 99
 
97 100
         assertThat(stack1.getChildControllers()).doesNotContain(newChild);
98 101
         assertThat(stack2.getChildControllers()).contains(newChild);
@@ -100,9 +103,9 @@ public class NavigatorTest extends BaseTest {
100 103
 
101 104
     @Test
102 105
     public void pop_InvalidDoesNothing() throws Exception {
103
-        uut.pop("123");
104
-        uut.setRoot(child1);
105
-        uut.pop(child1.getId());
106
+        uut.pop("123", new MockPromise());
107
+        uut.setRoot(child1, new MockPromise());
108
+        uut.pop(child1.getId(), new MockPromise());
106 109
         assertThat(uut.getChildControllers()).hasSize(1);
107 110
     }
108 111
 
@@ -111,16 +114,22 @@ public class NavigatorTest extends BaseTest {
111 114
         BottomTabsController bottomTabsController = newTabs();
112 115
         StackController stack1 = newStack();
113 116
         StackController stack2 = newStack();
114
-        stack1.push(child1, new MockPromise());
115
-        stack2.push(child2, new MockPromise());
116
-        stack2.push(child3, new MockPromise());
117
-        stack2.push(child4, new MockPromise());
118 117
         bottomTabsController.setTabs(Arrays.asList(stack1, stack2));
119
-        uut.setRoot(bottomTabsController);
120
-
121
-        uut.pop("child4");
122
-
123
-        assertThat(stack2.getChildControllers()).containsOnly(child2, child3);
118
+        uut.setRoot(bottomTabsController, new MockPromise());
119
+        stack1.animatePush(child1, new MockPromise());
120
+        stack2.animatePush(child2, new MockPromise());
121
+        stack2.animatePush(child3, new MockPromise() {
122
+            @Override
123
+            public void resolve(@Nullable Object value) {
124
+                stack2.animatePush(child4, new MockPromise() {
125
+                    @Override
126
+                    public void resolve(@Nullable Object value) {
127
+                        uut.pop("child4", new MockPromise());
128
+                        assertThat(stack2.getChildControllers()).containsOnly(child2, child3);
129
+                    }
130
+                });
131
+            }
132
+        });
124 133
     }
125 134
 
126 135
     @Test
@@ -128,14 +137,14 @@ public class NavigatorTest extends BaseTest {
128 137
         BottomTabsController bottomTabsController = newTabs();
129 138
         StackController stack1 = newStack();
130 139
         StackController stack2 = newStack();
131
-        stack1.push(child1, new MockPromise());
132
-        stack2.push(child2, new MockPromise());
133
-        stack2.push(child3, new MockPromise());
134
-        stack2.push(child4, new MockPromise());
140
+        stack1.animatePush(child1, new MockPromise());
141
+        stack2.animatePush(child2, new MockPromise());
142
+        stack2.animatePush(child3, new MockPromise());
143
+        stack2.animatePush(child4, new MockPromise());
135 144
         bottomTabsController.setTabs(Arrays.asList(stack1, stack2));
136
-        uut.setRoot(bottomTabsController);
145
+        uut.setRoot(bottomTabsController, new MockPromise());
137 146
 
138
-        uut.popSpecific(child2.getId());
147
+        uut.popSpecific(child2.getId(), new MockPromise());
139 148
 
140 149
         assertThat(stack2.getChildControllers()).containsOnly(child4, child3);
141 150
     }
@@ -145,17 +154,20 @@ public class NavigatorTest extends BaseTest {
145 154
         BottomTabsController bottomTabsController = newTabs();
146 155
         StackController stack1 = newStack();
147 156
         StackController stack2 = newStack();
148
-        stack1.push(child1, new MockPromise());
149
-        stack2.push(child2, new MockPromise());
150
-        stack2.push(child3, new MockPromise());
151
-        stack2.push(child4, new MockPromise());
152
-        stack2.push(child5, new MockPromise());
153 157
         bottomTabsController.setTabs(Arrays.asList(stack1, stack2));
154
-        uut.setRoot(bottomTabsController);
158
+        uut.setRoot(bottomTabsController, new MockPromise());
155 159
 
156
-        uut.popTo(child2.getId());
157
-
158
-        assertThat(stack2.getChildControllers()).containsOnly(child2);
160
+        stack1.animatePush(child1, new MockPromise());
161
+        stack2.animatePush(child2, new MockPromise());
162
+        stack2.animatePush(child3, new MockPromise());
163
+        stack2.animatePush(child4, new MockPromise());
164
+        stack2.animatePush(child5, new MockPromise() {
165
+            @Override
166
+            public void resolve(@Nullable Object value) {
167
+                uut.popTo(child2.getId(), new MockPromise());
168
+                assertThat(stack2.getChildControllers()).containsOnly(child2);
169
+            }
170
+        });
159 171
     }
160 172
 
161 173
     @Test
@@ -163,25 +175,28 @@ public class NavigatorTest extends BaseTest {
163 175
         BottomTabsController bottomTabsController = newTabs();
164 176
         StackController stack1 = newStack();
165 177
         StackController stack2 = newStack();
166
-        stack1.push(child1, new MockPromise());
167
-        stack2.push(child2, new MockPromise());
168
-        stack2.push(child3, new MockPromise());
169
-        stack2.push(child4, new MockPromise());
170
-        stack2.push(child5, new MockPromise());
171
-
172 178
         bottomTabsController.setTabs(Arrays.asList(stack1, stack2));
173
-        uut.setRoot(bottomTabsController);
179
+        uut.setRoot(bottomTabsController, new MockPromise());
174 180
 
175
-        uut.popToRoot(child3.getId());
181
+        stack1.animatePush(child1, new MockPromise());
182
+        stack2.animatePush(child2, new MockPromise());
183
+        stack2.animatePush(child3, new MockPromise());
184
+        stack2.animatePush(child4, new MockPromise());
185
+        stack2.animatePush(child5, new MockPromise() {
186
+            @Override
187
+            public void resolve(@Nullable Object value) {
188
+                uut.popToRoot(child3.getId(), new MockPromise());
176 189
 
177
-        assertThat(stack2.getChildControllers()).containsOnly(child2);
190
+                assertThat(stack2.getChildControllers()).containsOnly(child2);
191
+            }
192
+        });
178 193
     }
179 194
 
180 195
     @Test
181 196
     public void handleBack_DelegatesToRoot() throws Exception {
182 197
         assertThat(uut.handleBack()).isFalse();
183 198
         ViewController spy = spy(child1);
184
-        uut.setRoot(spy);
199
+        uut.setRoot(spy, new MockPromise());
185 200
         when(spy.handleBack()).thenReturn(true);
186 201
         assertThat(uut.handleBack()).isTrue();
187 202
         verify(spy, times(1)).handleBack();
@@ -190,14 +205,15 @@ public class NavigatorTest extends BaseTest {
190 205
     @Test
191 206
     public void setOptions_CallsApplyNavigationOptions() {
192 207
         ComponentViewController componentVc = new SimpleComponentViewController(activity, "theId");
193
-        uut.setRoot(componentVc);
194
-        assertThat(componentVc.getOptions().topBarOptions.title).isEmpty();
208
+        componentVc.setParentController(parentController);
209
+        assertThat(componentVc.getOptions().topBarOptions.title.get("")).isEmpty();
210
+        uut.setRoot(componentVc, new MockPromise());
195 211
 
196 212
         Options options = new Options();
197
-        options.topBarOptions.title = "new title";
213
+        options.topBarOptions.title = new Text("new title");
198 214
 
199 215
         uut.setOptions("theId", options);
200
-        assertThat(componentVc.getOptions().topBarOptions.title).isEqualTo("new title");
216
+        assertThat(componentVc.getOptions().topBarOptions.title.get()).isEqualTo("new title");
201 217
     }
202 218
 
203 219
     @Test
@@ -212,14 +228,14 @@ public class NavigatorTest extends BaseTest {
212 228
 
213 229
     @NonNull
214 230
     private StackController newStack() {
215
-        return new StackController(activity, "stack" + CompatUtils.generateViewId(), new TestNavigationAnimator());
231
+        return new StackController(activity, "stack" + CompatUtils.generateViewId());
216 232
     }
217 233
 
218 234
     @Test
219 235
     public void push_Promise() throws Exception {
220 236
         final StackController stackController = newStack();
221
-        stackController.push(child1, new MockPromise());
222
-        uut.setRoot(stackController);
237
+        stackController.animatePush(child1, new MockPromise());
238
+        uut.setRoot(stackController, new MockPromise());
223 239
 
224 240
         assertIsChildById(uut.getView(), stackController.getView());
225 241
         assertIsChildById(stackController.getView(), child1.getView());
@@ -235,7 +251,7 @@ public class NavigatorTest extends BaseTest {
235 251
 
236 252
     @Test
237 253
     public void push_InvalidPushWithoutAStack_DoesNothing_Promise() throws Exception {
238
-        uut.setRoot(child1);
254
+        uut.setRoot(child1, new MockPromise());
239 255
         uut.push(child1.getId(), child2, new MockPromise() {
240 256
             @Override
241 257
             public void reject(String code, Throwable e) {
@@ -247,8 +263,8 @@ public class NavigatorTest extends BaseTest {
247 263
 
248 264
     @Test
249 265
     public void pop_InvalidDoesNothing_Promise() throws Exception {
250
-        uut.pop("123");
251
-        uut.setRoot(child1);
266
+        uut.pop("123", new MockPromise());
267
+        uut.setRoot(child1, new MockPromise());
252 268
         uut.pop(child1.getId(), new MockPromise() {
253 269
             @Override
254 270
             public void reject(Throwable reason) {
@@ -262,16 +278,16 @@ public class NavigatorTest extends BaseTest {
262 278
         BottomTabsController bottomTabsController = newTabs();
263 279
         StackController stack1 = newStack();
264 280
         final StackController stack2 = newStack();
265
-        stack1.push(child1, new MockPromise());
266
-        stack2.push(child2, new MockPromise());
267
-        stack2.push(child3, new MockPromise());
268
-        stack2.push(child4, new MockPromise());
269 281
         bottomTabsController.setTabs(Arrays.asList(stack1, stack2));
270
-        uut.setRoot(bottomTabsController);
282
+        uut.setRoot(bottomTabsController, new MockPromise());
271 283
 
272
-        uut.pop("child4", new MockPromise() {
284
+        stack1.animatePush(child1, new MockPromise());
285
+        stack2.animatePush(child2, new MockPromise());
286
+        stack2.animatePush(child3, new MockPromise());
287
+        stack2.animatePush(child4, new MockPromise() {
273 288
             @Override
274 289
             public void resolve(@Nullable Object value) {
290
+                uut.pop("child4", new MockPromise());
275 291
                 assertThat(stack2.getChildControllers()).containsOnly(child2, child3);
276 292
             }
277 293
         });
@@ -280,9 +296,9 @@ public class NavigatorTest extends BaseTest {
280 296
     @Test
281 297
     public void pushIntoModal() throws Exception {
282 298
         StackController stackController = newStack();
283
-        stackController.push(child1, new MockPromise());
299
+        stackController.animatePush(child1, new MockPromise());
284 300
         uut.showModal(stackController, new MockPromise());
285
-        uut.push(stackController.getId(), child2);
301
+        uut.push(stackController.getId(), child2, new MockPromise());
286 302
         assertIsChildById(stackController.getView(), child2.getView());
287 303
     }
288 304
 }

+ 71
- 56
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java View File

@@ -1,23 +1,30 @@
1 1
 package com.reactnativenavigation.viewcontrollers;
2 2
 
3
-import android.app.*;
3
+import android.app.Activity;
4 4
 import android.graphics.Color;
5
-import android.graphics.drawable.*;
6
-import android.view.*;
7
-import android.widget.*;
5
+import android.graphics.drawable.ColorDrawable;
6
+import android.view.View;
7
+import android.view.ViewGroup;
8
+import android.widget.RelativeLayout;
8 9
 
9
-import com.reactnativenavigation.*;
10
-import com.reactnativenavigation.mocks.*;
11
-import com.reactnativenavigation.parse.*;
10
+import com.reactnativenavigation.BaseTest;
11
+import com.reactnativenavigation.mocks.MockPromise;
12
+import com.reactnativenavigation.mocks.TestComponentLayout;
13
+import com.reactnativenavigation.parse.Fraction;
14
+import com.reactnativenavigation.parse.Options;
15
+import com.reactnativenavigation.parse.Text;
12 16
 
13
-import org.junit.*;
17
+import org.junit.Test;
14 18
 
15
-import static android.widget.RelativeLayout.*;
16
-import static org.assertj.core.api.Java6Assertions.*;
17
-import static org.mockito.Mockito.*;
19
+import javax.annotation.Nullable;
20
+
21
+import static android.widget.RelativeLayout.BELOW;
22
+import static org.assertj.core.api.Java6Assertions.assertThat;
23
+import static org.mockito.Mockito.spy;
18 24
 
19 25
 public class OptionsApplyingTest extends BaseTest {
20 26
     private Activity activity;
27
+    private StackController stackController;
21 28
     private ComponentViewController uut;
22 29
     private ComponentViewController.IReactView view;
23 30
     private Options initialNavigationOptions;
@@ -34,11 +41,14 @@ public class OptionsApplyingTest extends BaseTest {
34 41
                 (activity1, componentId, componentName) -> view,
35 42
                 initialNavigationOptions
36 43
         );
37
-        uut.ensureViewIsCreated();
44
+        stackController = new StackController(activity, "stack");
45
+        stackController.ensureViewIsCreated();
46
+        uut.setParentController(stackController);
38 47
     }
39 48
 
40 49
     @Test
41 50
     public void applyNavigationOptionsHandlesNoParentStack() throws Exception {
51
+        uut.setParentController(null);
42 52
         assertThat(uut.getParentStackController()).isNull();
43 53
         uut.onViewAppeared();
44 54
         assertThat(uut.getParentStackController()).isNull();
@@ -47,110 +57,115 @@ public class OptionsApplyingTest extends BaseTest {
47 57
     @Test
48 58
     public void initialOptionsAppliedOnAppear() throws Exception {
49 59
         assertThat(uut.getOptions()).isSameAs(initialNavigationOptions);
50
-        initialNavigationOptions.topBarOptions.title = "the title";
60
+        initialNavigationOptions.topBarOptions.title = new Text("the title");
51 61
         StackController stackController = new StackController(activity, "stackId");
52
-        stackController.push(uut, new MockPromise());
53
-        assertThat(uut.getTopBar().getTitle()).isEmpty();
62
+        stackController.animatePush(uut, new MockPromise() {});
63
+        assertThat(stackController.getTopBar().getTitle()).isEmpty();
54 64
 
55 65
         uut.onViewAppeared();
56
-        assertThat(uut.getTopBar().getTitle()).isEqualTo("the title");
66
+        assertThat(stackController.getTopBar().getTitle()).isEqualTo("the title");
57 67
     }
58 68
 
59 69
     @Test
60 70
     public void mergeNavigationOptionsUpdatesCurrentOptions() throws Exception {
61
-        assertThat(uut.getOptions().topBarOptions.title).isEmpty();
71
+        uut.ensureViewIsCreated();
72
+        assertThat(uut.getOptions().topBarOptions.title.get("")).isEmpty();
62 73
         Options options = new Options();
63
-        options.topBarOptions.title = "new title";
74
+        options.topBarOptions.title = new Text("new title");
64 75
         uut.mergeOptions(options);
65
-        assertThat(uut.getOptions().topBarOptions.title).isEqualTo("new title");
76
+        assertThat(uut.getOptions().topBarOptions.title.get()).isEqualTo("new title");
66 77
         assertThat(uut.getOptions()).isSameAs(initialNavigationOptions);
67 78
     }
68 79
 
69 80
     @Test
70 81
     public void reappliesOptionsOnMerge() throws Exception {
71 82
         uut.onViewAppeared();
72
-        assertThat(uut.getTopBar().getTitle()).isEmpty();
83
+        assertThat(stackController.getTopBar().getTitle()).isEmpty();
73 84
 
74 85
         Options opts = new Options();
75
-        opts.topBarOptions.title = "the new title";
86
+        opts.topBarOptions.title = new Text("the new title");
76 87
         uut.mergeOptions(opts);
77 88
 
78
-        assertThat(uut.getTopBar().getTitle()).isEqualTo("the new title");
89
+        assertThat(stackController.getTopBar().getTitle()).isEqualTo("the new title");
79 90
     }
80 91
 
81 92
     @Test
82 93
     public void appliesTopBackBackgroundColor() throws Exception {
83 94
         uut.onViewAppeared();
84
-        //TODO: FIX TEST
85
-        assertThat(((ColorDrawable) uut.getTopBar().getTitleBar().getBackground()).getColor()).isNotEqualTo(Color.RED);
86 95
 
87 96
         Options opts = new Options();
88
-        opts.topBarOptions.backgroundColor = Color.RED;
97
+        opts.topBarOptions.backgroundColor = new com.reactnativenavigation.parse.Color(Color.RED);
89 98
         uut.mergeOptions(opts);
90 99
 
91
-        assertThat(((ColorDrawable) uut.getTopBar().getTitleBar().getBackground()).getColor()).isEqualTo(Color.RED);
100
+        assertThat(((ColorDrawable) stackController.getTopBar().getTitleBar().getBackground()).getColor()).isEqualTo(Color.RED);
92 101
     }
93 102
 
94 103
     @Test
95 104
     public void appliesTopBarTextColor() throws Exception {
96 105
         assertThat(uut.getOptions()).isSameAs(initialNavigationOptions);
97
-        initialNavigationOptions.topBarOptions.title = "the title";
98
-        uut.onViewAppeared();
99
-        assertThat(uut.getTopBar().getTitleTextView().getCurrentTextColor()).isNotEqualTo(Color.RED);
100
-
101
-        Options opts = new Options();
102
-        opts.topBarOptions.title = "the title";
103
-        opts.topBarOptions.textColor = Color.RED;
104
-        uut.mergeOptions(opts);
105
-
106
-        assertThat(uut.getTopBar().getTitleTextView()).isNotEqualTo(null);
107
-        assertThat(uut.getTopBar().getTitleTextView().getCurrentTextColor()).isEqualTo(Color.RED);
106
+        initialNavigationOptions.topBarOptions.title = new Text("the title");
107
+        stackController.animatePush(uut, new MockPromise() {
108
+            @Override
109
+            public void resolve(@Nullable Object value) {
110
+                Options opts = new Options();
111
+                opts.topBarOptions.title = new Text("the title");
112
+                opts.topBarOptions.textColor = new com.reactnativenavigation.parse.Color(Color.RED);
113
+                uut.mergeOptions(opts);
114
+
115
+                assertThat(stackController.getTopBar().getTitleTextView()).isNotEqualTo(null);
116
+                assertThat(stackController.getTopBar().getTitleTextView().getCurrentTextColor()).isEqualTo(Color.RED);
117
+            }
118
+        });
108 119
     }
109 120
 
110 121
     @Test
111 122
     public void appliesTopBarTextSize() throws Exception {
112 123
         assertThat(uut.getOptions()).isSameAs(initialNavigationOptions);
113
-        initialNavigationOptions.topBarOptions.title = "the title";
124
+        initialNavigationOptions.topBarOptions.title = new Text("the title");
114 125
         uut.onViewAppeared();
115
-        assertThat(uut.getTopBar().getTitleTextView().getTextSize()).isNotEqualTo(18);
116 126
 
117 127
         Options opts = new Options();
118
-        opts.topBarOptions.title = "the title";
119
-        opts.topBarOptions.textFontSize = 18;
128
+        opts.topBarOptions.title = new Text("the title");
129
+        opts.topBarOptions.textFontSize = new Fraction(18);
120 130
         uut.mergeOptions(opts);
121 131
 
122
-        assertThat(uut.getTopBar().getTitleTextView()).isNotEqualTo(null);
123
-        assertThat(uut.getTopBar().getTitleTextView().getTextSize()).isEqualTo(18);
132
+        assertThat(stackController.getTopBar().getTitleTextView()).isNotEqualTo(null);
133
+        assertThat(stackController.getTopBar().getTitleTextView().getTextSize()).isEqualTo(18);
124 134
     }
125 135
 
126 136
     @Test
127 137
     public void appliesTopBarHidden() throws Exception {
128 138
         assertThat(uut.getOptions()).isSameAs(initialNavigationOptions);
129
-        initialNavigationOptions.topBarOptions.title = "the title";
139
+        initialNavigationOptions.topBarOptions.title = new Text("the title");
130 140
         uut.onViewAppeared();
131
-        assertThat(uut.getTopBar().getVisibility()).isNotEqualTo(View.GONE);
141
+        assertThat(stackController.getTopBar().getVisibility()).isNotEqualTo(View.GONE);
132 142
 
133 143
         Options opts = new Options();
134 144
         opts.topBarOptions.hidden = Options.BooleanOptions.True;
135 145
         uut.mergeOptions(opts);
136 146
 
137
-        assertThat(uut.getTopBar().getVisibility()).isEqualTo(View.GONE);
147
+        assertThat(stackController.getTopBar().getVisibility()).isEqualTo(View.GONE);
138 148
     }
139 149
 
140 150
     @Test
141 151
     public void appliesDrawUnder() throws Exception {
142 152
         assertThat(uut.getOptions()).isSameAs(initialNavigationOptions);
143
-        initialNavigationOptions.topBarOptions.title = "the title";
153
+        initialNavigationOptions.topBarOptions.title = new Text("the title");
144 154
         initialNavigationOptions.topBarOptions.drawBehind = Options.BooleanOptions.False;
145 155
         uut.onViewAppeared();
146
-        RelativeLayout.LayoutParams uutLayoutParams = (RelativeLayout.LayoutParams) ((ViewGroup) uut.getComponent().asView()).getChildAt(1).getLayoutParams();
147
-        assertThat(uutLayoutParams.getRule(BELOW)).isNotEqualTo(0);
148
-
149
-        Options opts = new Options();
150
-        opts.topBarOptions.drawBehind = Options.BooleanOptions.True;
151
-        uut.mergeOptions(opts);
152
-
153
-        uutLayoutParams = (RelativeLayout.LayoutParams) ((ViewGroup) uut.getComponent().asView()).getChildAt(1).getLayoutParams();
154
-        assertThat(uutLayoutParams.getRule(BELOW)).isEqualTo(0);
156
+        stackController.animatePush(uut, new MockPromise() {
157
+            @Override
158
+            public void resolve(@Nullable Object value) {
159
+                RelativeLayout.LayoutParams uutLayoutParams = (RelativeLayout.LayoutParams) ((ViewGroup) uut.getComponent().asView()).getChildAt(0).getLayoutParams();
160
+                assertThat(uutLayoutParams.getRule(BELOW)).isNotEqualTo(0);
161
+
162
+                Options opts = new Options();
163
+                opts.topBarOptions.drawBehind = Options.BooleanOptions.True;
164
+                uut.mergeOptions(opts);
165
+
166
+                uutLayoutParams = (RelativeLayout.LayoutParams) (uut.getComponent().asView()).getLayoutParams();
167
+                assertThat(uutLayoutParams.getRule(BELOW)).isNotEqualTo(stackController.getTopBar().getId());
168
+            }
169
+        });
155 170
     }
156 171
 }

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

@@ -65,11 +65,11 @@ public class ParentControllerTest extends BaseTest {
65 65
 
66 66
     @Test
67 67
     public void findControllerById_Recursive() throws Exception {
68
-        StackController stackController = new StackController(activity, "stack", new TestNavigationAnimator());
68
+        StackController stackController = new StackController(activity, "stack");
69 69
         SimpleViewController child1 = new SimpleViewController(activity, "child1");
70 70
         SimpleViewController child2 = new SimpleViewController(activity, "child2");
71
-        stackController.push(child1, new MockPromise());
72
-        stackController.push(child2, new MockPromise());
71
+        stackController.animatePush(child1, new MockPromise());
72
+        stackController.animatePush(child2, new MockPromise());
73 73
         children.add(stackController);
74 74
 
75 75
         assertThat(uut.findControllerById("child2")).isEqualTo(child2);

+ 141
- 93
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java View File

@@ -9,6 +9,8 @@ import com.reactnativenavigation.mocks.*;
9 9
 import org.assertj.core.api.iterable.*;
10 10
 import org.junit.*;
11 11
 
12
+import javax.annotation.Nullable;
13
+
12 14
 import static org.assertj.core.api.Java6Assertions.*;
13 15
 import static org.mockito.Mockito.*;
14 16
 
@@ -24,7 +26,7 @@ public class StackControllerTest extends BaseTest {
24 26
     public void beforeEach() {
25 27
         super.beforeEach();
26 28
         activity = newActivity();
27
-        uut = new StackController(activity, "uut", new TestNavigationAnimator());
29
+        uut = new StackController(activity, "uut");
28 30
         child1 = new SimpleViewController(activity, "child1");
29 31
         child2 = new SimpleViewController(activity, "child2");
30 32
         child3 = new SimpleViewController(activity, "child3");
@@ -38,9 +40,9 @@ public class StackControllerTest extends BaseTest {
38 40
     @Test
39 41
     public void holdsAStackOfViewControllers() throws Exception {
40 42
         assertThat(uut.isEmpty()).isTrue();
41
-        uut.push(child1, new MockPromise());
42
-        uut.push(child2, new MockPromise());
43
-        uut.push(child3, new MockPromise());
43
+        uut.animatePush(child1, new MockPromise());
44
+        uut.animatePush(child2, new MockPromise());
45
+        uut.animatePush(child3, new MockPromise());
44 46
         assertThat(uut.peek()).isEqualTo(child3);
45 47
         assertContainsOnlyId(child1.getId(), child2.getId(), child3.getId());
46 48
     }
@@ -48,17 +50,21 @@ public class StackControllerTest extends BaseTest {
48 50
     @Test
49 51
     public void push() throws Exception {
50 52
         assertThat(uut.isEmpty()).isTrue();
51
-        uut.push(child1, new MockPromise());
53
+        uut.animatePush(child1, new MockPromise());
52 54
         assertContainsOnlyId(child1.getId());
53 55
     }
54 56
 
55 57
     @Test
56 58
     public void pop() throws Exception {
57
-        uut.push(child1, new MockPromise());
58
-        uut.push(child2, new MockPromise());
59
-        assertContainsOnlyId(child2.getId(), child1.getId());
60
-        uut.pop(new MockPromise());
61
-        assertContainsOnlyId(child1.getId());
59
+        uut.animatePush(child1, new MockPromise());
60
+        uut.animatePush(child2, new MockPromise() {
61
+            @Override
62
+            public void resolve(@Nullable Object value) {
63
+                assertContainsOnlyId(child2.getId(), child1.getId());
64
+                uut.pop(new MockPromise());
65
+                assertContainsOnlyId(child1.getId());
66
+            }
67
+        });
62 68
     }
63 69
 
64 70
     @Test
@@ -66,7 +72,7 @@ public class StackControllerTest extends BaseTest {
66 72
         assertThat(uut.peek()).isNull();
67 73
         assertThat(uut.size()).isZero();
68 74
         assertThat(uut.isEmpty()).isTrue();
69
-        uut.push(child1, new MockPromise());
75
+        uut.animatePush(child1, new MockPromise());
70 76
         assertThat(uut.peek()).isEqualTo(child1);
71 77
         assertThat(uut.size()).isEqualTo(1);
72 78
         assertThat(uut.isEmpty()).isFalse();
@@ -75,11 +81,11 @@ public class StackControllerTest extends BaseTest {
75 81
     @Test
76 82
     public void pushAssignsRefToSelfOnPushedController() throws Exception {
77 83
         assertThat(child1.getParentStackController()).isNull();
78
-        uut.push(child1, new MockPromise());
84
+        uut.animatePush(child1, new MockPromise());
79 85
         assertThat(child1.getParentStackController()).isEqualTo(uut);
80 86
 
81
-        StackController anotherNavController = new StackController(activity, "another", new TestNavigationAnimator());
82
-        anotherNavController.push(child2, new MockPromise());
87
+        StackController anotherNavController = new StackController(activity, "another");
88
+        anotherNavController.animatePush(child2, new MockPromise());
83 89
         assertThat(child2.getParentStackController()).isEqualTo(anotherNavController);
84 90
     }
85 91
 
@@ -88,15 +94,20 @@ public class StackControllerTest extends BaseTest {
88 94
         assertThat(uut.isEmpty()).isTrue();
89 95
         assertThat(uut.handleBack()).isFalse();
90 96
 
91
-        uut.push(child1, new MockPromise());
97
+        uut.animatePush(child1, new MockPromise());
92 98
         assertThat(uut.size()).isEqualTo(1);
93 99
         assertThat(uut.handleBack()).isFalse();
94 100
 
95
-        uut.push(child2, new MockPromise());
96
-        assertThat(uut.size()).isEqualTo(2);
97
-        assertThat(uut.handleBack()).isTrue();
98
-        assertThat(uut.size()).isEqualTo(1);
99
-        assertThat(uut.handleBack()).isFalse();
101
+        uut.animatePush(child2, new MockPromise() {
102
+            @Override
103
+            public void resolve(@Nullable Object value) {
104
+                assertThat(uut.size()).isEqualTo(2);
105
+                assertThat(uut.handleBack()).isTrue();
106
+
107
+                assertThat(uut.size()).isEqualTo(1);
108
+                assertThat(uut.handleBack()).isFalse();
109
+            }
110
+        });
100 111
     }
101 112
 
102 113
     @Test
@@ -105,7 +116,7 @@ public class StackControllerTest extends BaseTest {
105 116
         uut.pop(new MockPromise());
106 117
         assertThat(uut.isEmpty()).isTrue();
107 118
 
108
-        uut.push(child1, new MockPromise());
119
+        uut.animatePush(child1, new MockPromise());
109 120
         uut.pop(new MockPromise());
110 121
         assertContainsOnlyId(child1.getId());
111 122
     }
@@ -114,10 +125,10 @@ public class StackControllerTest extends BaseTest {
114 125
     public void canPopWhenSizeIsMoreThanOne() throws Exception {
115 126
         assertThat(uut.isEmpty()).isTrue();
116 127
         assertThat(uut.canPop()).isFalse();
117
-        uut.push(child1, new MockPromise());
128
+        uut.animatePush(child1, new MockPromise());
118 129
         assertContainsOnlyId(child1.getId());
119 130
         assertThat(uut.canPop()).isFalse();
120
-        uut.push(child2, new MockPromise());
131
+        uut.animatePush(child2, new MockPromise());
121 132
         assertContainsOnlyId(child1.getId(), child2.getId());
122 133
         assertThat(uut.canPop()).isTrue();
123 134
     }
@@ -125,95 +136,121 @@ public class StackControllerTest extends BaseTest {
125 136
     @Test
126 137
     public void pushAddsToViewTree() throws Exception {
127 138
         assertThat(uut.getView().findViewById(child1.getView().getId())).isNull();
128
-        uut.push(child1, new MockPromise());
139
+        uut.animatePush(child1, new MockPromise());
129 140
         assertThat(uut.getView().findViewById(child1.getView().getId())).isNotNull();
130 141
     }
131 142
 
132 143
     @Test
133 144
     public void pushRemovesPreviousFromTree() throws Exception {
134 145
         assertThat(uut.getView().findViewById(child1.getView().getId())).isNull();
135
-        uut.push(child1, new MockPromise());
146
+        uut.animatePush(child1, new MockPromise());
136 147
         assertThat(uut.getView().findViewById(child1.getView().getId())).isNotNull();
137
-        uut.push(child2, new MockPromise());
138
-        assertThat(uut.getView().findViewById(child1.getView().getId())).isNull();
139
-        assertThat(uut.getView().findViewById(child2.getView().getId())).isNotNull();
148
+        uut.animatePush(child2, new MockPromise() {
149
+            @Override
150
+            public void resolve(@Nullable Object value) {
151
+                assertThat(uut.getView().findViewById(child1.getView().getId())).isNull();
152
+                assertThat(uut.getView().findViewById(child2.getView().getId())).isNotNull();
153
+            }
154
+        });
140 155
     }
141 156
 
142 157
     @Test
143 158
     public void popReplacesViewWithPrevious() throws Exception {
144
-        uut.push(child1, new MockPromise());
145
-        uut.push(child2, new MockPromise());
146 159
         final View child2View = child2.getView();
147 160
         final View child1View = child1.getView();
148
-        assertIsChildById(uut.getView(), child2View);
149
-        assertNotChildOf(uut.getView(), child1View);
150
-        uut.pop(new MockPromise());
151
-        assertNotChildOf(uut.getView(), child2View);
152
-        assertIsChildById(uut.getView(), child1View);
161
+
162
+        uut.animatePush(child1, new MockPromise());
163
+        uut.animatePush(child2, new MockPromise() {
164
+            @Override
165
+            public void resolve(@Nullable Object value) {
166
+                assertIsChildById(uut.getView(), child2View);
167
+                assertNotChildOf(uut.getView(), child1View);
168
+                uut.pop(new MockPromise());
169
+                assertNotChildOf(uut.getView(), child2View);
170
+                assertIsChildById(uut.getView(), child1View);
171
+            }
172
+        });
153 173
     }
154 174
 
155 175
     @Test
156 176
     public void popSpecificWhenTopIsRegularPop() throws Exception {
157
-        uut.push(child1, new MockPromise());
158
-        uut.push(child2, new MockPromise());
159
-        uut.popSpecific(child2);
160
-        assertContainsOnlyId(child1.getId());
161
-        assertIsChildById(uut.getView(), child1.getView());
177
+        uut.animatePush(child1, new MockPromise());
178
+        uut.animatePush(child2, new MockPromise() {
179
+            @Override
180
+            public void resolve(@Nullable Object value) {
181
+                uut.popSpecific(child2, new MockPromise() {
182
+                    @Override
183
+                    public void resolve(@Nullable Object value) {
184
+                        assertContainsOnlyId(child1.getId());
185
+                        assertIsChildById(uut.getView(), child1.getView());
186
+                    }
187
+                });
188
+            }
189
+        });
162 190
     }
163 191
 
164 192
     @Test
165 193
     public void popSpecificDeepInStack() throws Exception {
166
-        uut.push(child1, new MockPromise());
167
-        uut.push(child2, new MockPromise());
194
+        uut.animatePush(child1, new MockPromise());
195
+        uut.animatePush(child2, new MockPromise());
168 196
         assertIsChildById(uut.getView(), child2.getView());
169
-        uut.popSpecific(child1);
197
+        uut.popSpecific(child1, new MockPromise());
170 198
         assertContainsOnlyId(child2.getId());
171 199
         assertIsChildById(uut.getView(), child2.getView());
172 200
     }
173 201
 
174 202
     @Test
175 203
     public void popTo_PopsTopUntilControllerIsNewTop() throws Exception {
176
-        uut.push(child1, new MockPromise());
177
-        uut.push(child2, new MockPromise());
178
-        uut.push(child3, new MockPromise());
179
-
180
-        assertThat(uut.size()).isEqualTo(3);
181
-        assertThat(uut.peek()).isEqualTo(child3);
182
-
183
-        uut.popTo(child1);
184
-
185
-        assertThat(uut.size()).isEqualTo(1);
186
-        assertThat(uut.peek()).isEqualTo(child1);
204
+        uut.animatePush(child1, new MockPromise());
205
+        uut.animatePush(child2, new MockPromise());
206
+        uut.animatePush(child3, new MockPromise() {
207
+            @Override
208
+            public void resolve(@Nullable Object value) {
209
+                assertThat(uut.size()).isEqualTo(3);
210
+                assertThat(uut.peek()).isEqualTo(child3);
211
+
212
+                uut.popTo(child1, new MockPromise());
213
+
214
+                assertThat(uut.size()).isEqualTo(1);
215
+                assertThat(uut.peek()).isEqualTo(child1);
216
+            }
217
+        });
187 218
     }
188 219
 
189 220
     @Test
190 221
     public void popTo_NotAChildOfThisStack_DoesNothing() throws Exception {
191
-        uut.push(child1, new MockPromise());
192
-        uut.push(child3, new MockPromise());
222
+        uut.animatePush(child1, new MockPromise());
223
+        uut.animatePush(child3, new MockPromise());
193 224
         assertThat(uut.size()).isEqualTo(2);
194
-        uut.popTo(child2);
225
+        uut.popTo(child2, new MockPromise());
195 226
         assertThat(uut.size()).isEqualTo(2);
196 227
     }
197 228
 
198 229
     @Test
199 230
     public void popToRoot_PopsEverythingAboveFirstController() throws Exception {
200
-        uut.push(child1, new MockPromise());
201
-        uut.push(child2, new MockPromise());
202
-        uut.push(child3, new MockPromise());
203
-
204
-        assertThat(uut.size()).isEqualTo(3);
205
-        assertThat(uut.peek()).isEqualTo(child3);
206
-
207
-        uut.popToRoot();
208
-
209
-        assertThat(uut.size()).isEqualTo(1);
210
-        assertThat(uut.peek()).isEqualTo(child1);
231
+        uut.animatePush(child1, new MockPromise());
232
+        uut.animatePush(child2, new MockPromise());
233
+        uut.animatePush(child3, new MockPromise() {
234
+            @Override
235
+            public void resolve(@Nullable Object value) {
236
+                assertThat(uut.size()).isEqualTo(3);
237
+                assertThat(uut.peek()).isEqualTo(child3);
238
+
239
+                uut.popToRoot(new MockPromise() {
240
+                    @Override
241
+                    public void resolve(@Nullable Object value) {
242
+                        assertThat(uut.size()).isEqualTo(1);
243
+                        assertThat(uut.peek()).isEqualTo(child1);
244
+                    }
245
+                });
246
+            }
247
+        });
211 248
     }
212 249
 
213 250
     @Test
214 251
     public void popToRoot_EmptyStackDoesNothing() throws Exception {
215 252
         assertThat(uut.isEmpty()).isTrue();
216
-        uut.popToRoot();
253
+        uut.popToRoot(new MockPromise());
217 254
         assertThat(uut.isEmpty()).isTrue();
218 255
     }
219 256
 
@@ -221,15 +258,15 @@ public class StackControllerTest extends BaseTest {
221 258
     public void findControllerById_ReturnsSelfOrChildrenById() throws Exception {
222 259
         assertThat(uut.findControllerById("123")).isNull();
223 260
         assertThat(uut.findControllerById(uut.getId())).isEqualTo(uut);
224
-        uut.push(child1, new MockPromise());
261
+        uut.animatePush(child1, new MockPromise());
225 262
         assertThat(uut.findControllerById(child1.getId())).isEqualTo(child1);
226 263
     }
227 264
 
228 265
     @Test
229 266
     public void findControllerById_Deeply() throws Exception {
230
-        StackController stack = new StackController(activity, "stack2", new TestNavigationAnimator());
231
-        stack.push(child2, new MockPromise());
232
-        uut.push(stack, new MockPromise());
267
+        StackController stack = new StackController(activity, "stack2");
268
+        stack.animatePush(child2, new MockPromise());
269
+        uut.animatePush(stack, new MockPromise());
233 270
         assertThat(uut.findControllerById(child2.getId())).isEqualTo(child2);
234 271
     }
235 272
 
@@ -238,13 +275,16 @@ public class StackControllerTest extends BaseTest {
238 275
         child1 = spy(child1);
239 276
         child2 = spy(child2);
240 277
         child3 = spy(child3);
241
-        uut.push(child1, new MockPromise());
242
-        uut.push(child2, new MockPromise());
243
-        uut.push(child3, new MockPromise());
244
-
245
-        verify(child3, times(0)).destroy();
246
-        uut.pop(new MockPromise());
247
-        verify(child3, times(1)).destroy();
278
+        uut.animatePush(child1, new MockPromise());
279
+        uut.animatePush(child2, new MockPromise());
280
+        uut.animatePush(child3, new MockPromise() {
281
+            @Override
282
+            public void resolve(@Nullable Object value) {
283
+                verify(child3, times(0)).destroy();
284
+                uut.pop(new MockPromise());
285
+                verify(child3, times(1)).destroy();
286
+            }
287
+        });
248 288
     }
249 289
 
250 290
     @Test
@@ -252,12 +292,12 @@ public class StackControllerTest extends BaseTest {
252 292
         child1 = spy(child1);
253 293
         child2 = spy(child2);
254 294
         child3 = spy(child3);
255
-        uut.push(child1, new MockPromise());
256
-        uut.push(child2, new MockPromise());
257
-        uut.push(child3, new MockPromise());
295
+        uut.animatePush(child1, new MockPromise());
296
+        uut.animatePush(child2, new MockPromise());
297
+        uut.animatePush(child3, new MockPromise());
258 298
 
259 299
         verify(child2, times(0)).destroy();
260
-        uut.popSpecific(child2);
300
+        uut.popSpecific(child2, new MockPromise());
261 301
         verify(child2, times(1)).destroy();
262 302
     }
263 303
 
@@ -266,15 +306,23 @@ public class StackControllerTest extends BaseTest {
266 306
         child1 = spy(child1);
267 307
         child2 = spy(child2);
268 308
         child3 = spy(child3);
269
-        uut.push(child1, new MockPromise());
270
-        uut.push(child2, new MockPromise());
271
-        uut.push(child3, new MockPromise());
272
-
273
-        verify(child2, times(0)).destroy();
274
-        verify(child3, times(0)).destroy();
275
-        uut.popTo(child1);
276
-        verify(child2, times(1)).destroy();
277
-        verify(child3, times(1)).destroy();
309
+        uut.animatePush(child1, new MockPromise());
310
+        uut.animatePush(child2, new MockPromise());
311
+        uut.animatePush(child3, new MockPromise() {
312
+            @Override
313
+            public void resolve(@Nullable Object value) {
314
+                verify(child2, times(0)).destroy();
315
+                verify(child3, times(0)).destroy();
316
+
317
+                uut.popTo(child1, new MockPromise() {
318
+                    @Override
319
+                    public void resolve(@Nullable Object value) {
320
+                        verify(child2, times(1)).destroy();
321
+                        verify(child3, times(1)).destroy();
322
+                    }
323
+                });
324
+            }
325
+        });
278 326
     }
279 327
 
280 328
     private void assertContainsOnlyId(String... ids) {

+ 11
- 6
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java View File

@@ -23,7 +23,7 @@ public class TopTabsViewControllerTest extends BaseTest {
23 23
 
24 24
     private TopTabsController uut;
25 25
     private List<TopTabLayoutMock> tabs = new ArrayList<>(SIZE);
26
-    private List<TopTabController> tabControllers = new ArrayList<>(SIZE);
26
+    private List<ViewController> tabControllers = new ArrayList<>(SIZE);
27 27
     private List<Options> tabOptions = new ArrayList<>(SIZE);
28 28
     private Options options;
29 29
     private TopTabsLayout topTabsLayout;
@@ -34,7 +34,7 @@ public class TopTabsViewControllerTest extends BaseTest {
34 34
         tabControllers.clear();
35 35
         tabs.clear();
36 36
         Activity activity = newActivity();
37
-        createTabs(activity);
37
+        tabControllers = createTabs(activity);
38 38
         options = new Options();
39 39
         topTabsLayout = spy(new TopTabsLayout(activity, tabControllers, new TopTabsAdapter(tabControllers)));
40 40
 
@@ -43,18 +43,23 @@ public class TopTabsViewControllerTest extends BaseTest {
43 43
         uut = new TopTabsController(activity, "componentId", tabControllers, layoutCreator, options);
44 44
     }
45 45
 
46
-    private void createTabs(Activity activity) {
46
+    private List<ViewController> createTabs(Activity activity) {
47
+        List<ViewController> result = new ArrayList<>(SIZE);
48
+        tabOptions = new ArrayList<>();
47 49
         for (int i = 0; i < SIZE; i++) {
48 50
             TopTabLayoutMock reactView = spy(new TopTabLayoutMock(activity));
49 51
             tabs.add(reactView);
50
-            tabOptions.add(new Options());
51
-            tabControllers.add(spy(new TopTabController(activity,
52
+            Options options = new Options();
53
+            options.topTabOptions.title = new Text("Tab " + i);
54
+            tabOptions.add(options);
55
+            result.add(spy(new TopTabController(activity,
52 56
                     "idTab" + i,
53 57
                     "tab" + i,
54 58
                     (activity1, componentId, componentName) -> reactView,
55
-                    tabOptions.get(i))
59
+                    options)
56 60
             ));
57 61
         }
62
+        return result;
58 63
     }
59 64
 
60 65
     @Test

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

@@ -59,7 +59,7 @@ public class ViewControllerTest extends BaseTest {
59 59
     public void holdsAReferenceToStackControllerOrNull() throws Exception {
60 60
         assertThat(uut.getParentStackController()).isNull();
61 61
         StackController nav = new StackController(activity, "stack");
62
-        nav.push(uut, new MockPromise());
62
+        nav.animatePush(uut, new MockPromise());
63 63
         assertThat(uut.getParentStackController()).isEqualTo(nav);
64 64
     }
65 65
 

+ 1
- 1
lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarTest.java View File

@@ -9,7 +9,7 @@ import static org.assertj.core.api.Java6Assertions.*;
9 9
 public class TopBarTest extends BaseTest {
10 10
     @Test
11 11
     public void title() throws Exception {
12
-        TopBar topBar = new TopBar(newActivity(), null, null, buttonId -> {});
12
+        TopBar topBar = new TopBar(newActivity(), buttonId -> {});
13 13
         assertThat(topBar.getTitle()).isEmpty();
14 14
 
15 15
         topBar.setTitle("new title");

+ 2
- 1
playground/src/screens/OptionsScreen.js View File

@@ -209,7 +209,8 @@ const styles = {
209 209
   root: {
210 210
     flexGrow: 1,
211 211
     justifyContent: 'center',
212
-    alignItems: 'center'
212
+    alignItems: 'center',
213
+    backgroundColor: 'white'
213 214
   },
214 215
   h1: {
215 216
     fontSize: 24,

+ 6
- 1
playground/src/screens/PushedScreen.js View File

@@ -9,7 +9,7 @@ const Navigation = require('react-native-navigation');
9 9
 const testIDs = require('../testIDs');
10 10
 
11 11
 class PushedScreen extends Component {
12
-  static get navigationOptions() {
12
+  static get options() {
13 13
     return {
14 14
       topBar: {
15 15
         testID: testIDs.TOP_BAR_ELEMENT
@@ -49,6 +49,11 @@ class PushedScreen extends Component {
49 49
         passProps: {
50 50
           stackPosition: this.getStackPosition() + 1,
51 51
           previousScreenIds: _.concat([], this.props.previousScreenIds || [], this.props.componentId)
52
+        },
53
+        options: {
54
+          topBar: {
55
+            title: `Pushed ${this.getStackPosition() + 1}`
56
+          }
52 57
         }
53 58
       }
54 59
     });

+ 6
- 1
playground/src/screens/WelcomeScreen.js View File

@@ -168,7 +168,12 @@ class WelcomeScreen extends Component {
168 168
   async onClickPush() {
169 169
     await Navigation.push(this.props.componentId, {
170 170
       component: {
171
-        name: 'navigation.playground.PushedScreen'
171
+        name: 'navigation.playground.PushedScreen',
172
+        options: {
173
+          topBar: {
174
+            title: 'pushed'
175
+          }
176
+        }
172 177
       }
173 178
     });
174 179
   }