Browse Source

Implement selected and unselected toTab color (#2429)

* Implement selected and unselected toTab color

* Test options are applied when layout is visible
Guy Carmeli 6 years ago
parent
commit
5624c4f309
No account linked to committer's email address
27 changed files with 295 additions and 116 deletions
  1. 12
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/ColorParser.java
  2. 3
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java
  3. 2
    4
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java
  4. 23
    4
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopTabsOptions.java
  5. 6
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java
  6. 27
    83
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java
  7. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsAdapter.java
  8. 9
    5
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java
  9. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java
  10. 14
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabs.java
  11. 4
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayout.java
  12. 22
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayoutCreator.java
  13. 26
    1
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsStyleHelper.java
  14. 30
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/style/Color.java
  15. 18
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/style/NullColor.java
  16. 18
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java
  17. 4
    3
      lib/src/Navigation.js
  18. 1
    0
      lib/src/commands/Commands.js
  19. 4
    3
      lib/src/commands/LayoutTreeParser.js
  20. 2
    1
      lib/src/commands/LayoutTreeParser.test.js
  21. 8
    4
      lib/src/params/containers/Container.js
  22. 12
    3
      lib/src/params/containers/Container.test.js
  23. 3
    0
      lib/src/params/options/NavigationOptions.js
  24. 8
    1
      lib/src/params/options/NavigationOptions.test.js
  25. 12
    0
      lib/src/params/options/TopTabs.js
  26. 14
    0
      lib/src/params/options/TopTabs.test.js
  27. 7
    1
      playground/src/containers/WelcomeScreen.js

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

@@ -0,0 +1,12 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+import com.reactnativenavigation.views.style.Color;
4
+import com.reactnativenavigation.views.style.NullColor;
5
+
6
+import org.json.JSONObject;
7
+
8
+public class ColorParser {
9
+    public static Color parse(JSONObject json, String color) {
10
+        return json.has(color) ? new Color(json.optInt(color)) : new NullColor();
11
+    }
12
+}

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

@@ -14,6 +14,7 @@ import com.reactnativenavigation.viewcontrollers.overlay.DialogViewController;
14 14
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabController;
15 15
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsController;
16 16
 import com.reactnativenavigation.views.ContainerViewCreator;
17
+import com.reactnativenavigation.views.TopTabsLayoutCreator;
17 18
 
18 19
 import java.util.ArrayList;
19 20
 import java.util.List;
@@ -136,7 +137,8 @@ public class LayoutFactory {
136 137
             tabController.setTabIndex(i);
137 138
             tabs.add(tabController);
138 139
         }
139
-        return new TopTabsController(activity, node.id, tabs);
140
+        NavigationOptions navigationOptions = NavigationOptions.parse(typefaceManager, node.getNavigationOptions(), defaultOptions);
141
+        return new TopTabsController(activity, node.id, tabs, new TopTabsLayoutCreator(activity, tabs), navigationOptions);
140 142
     }
141 143
 
142 144
     private ViewController createTopTab(LayoutNode node) {

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

@@ -44,12 +44,10 @@ public class TopBarOptions implements DEFAULT_VALUES {
44 44
 			textFontSize = other.textFontSize;
45 45
 		if (other.textFontFamily != null)
46 46
 			textFontFamily = other.textFontFamily;
47
-		if (other.hidden != NavigationOptions.BooleanOptions.NoValue) {
47
+		if (other.hidden != NavigationOptions.BooleanOptions.NoValue)
48 48
 			hidden = other.hidden;
49
-		}
50
-		if (other.animateHide != NavigationOptions.BooleanOptions.NoValue) {
49
+		if (other.animateHide != NavigationOptions.BooleanOptions.NoValue)
51 50
 			animateHide = other.animateHide;
52
-		}
53 51
 	}
54 52
 
55 53
     void mergeWithDefault(TopBarOptions defaultOptions) {

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

@@ -1,22 +1,41 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3
+import android.support.annotation.NonNull;
3 4
 import android.support.annotation.Nullable;
4 5
 
6
+import com.reactnativenavigation.views.style.Color;
7
+import com.reactnativenavigation.views.style.NullColor;
8
+
5 9
 import org.json.JSONObject;
6 10
 
7 11
 public class TopTabsOptions implements DEFAULT_VALUES {
8 12
 
13
+    @NonNull public Color selectedTabColor = new NullColor();
14
+    @NonNull public Color unselectedTabColor = new NullColor();
15
+
9 16
     public static TopTabsOptions parse(@Nullable JSONObject json) {
10 17
         TopTabsOptions result = new TopTabsOptions();
11 18
         if (json == null) return result;
19
+        result.selectedTabColor = ColorParser.parse(json, "selectedTabColor");
20
+        result.unselectedTabColor = ColorParser.parse(json, "unselectedTabColor");
12 21
         return result;
13 22
     }
14 23
 
15
-    void mergeWith(TopTabsOptions topTabsOptions) {
16
-
24
+    void mergeWith(TopTabsOptions other) {
25
+        if (other.selectedTabColor.hasColor()) {
26
+            selectedTabColor = other.selectedTabColor;
27
+        }
28
+        if (other.unselectedTabColor.hasColor()) {
29
+            unselectedTabColor = other.unselectedTabColor;
30
+        }
17 31
     }
18 32
 
19
-    void mergeWithDefault(TopTabsOptions topTabsOptions) {
20
-
33
+    void mergeWithDefault(TopTabsOptions defaultOptions) {
34
+        if (!selectedTabColor.hasColor()) {
35
+            selectedTabColor = defaultOptions.selectedTabColor;
36
+        }
37
+        if (!unselectedTabColor.hasColor()) {
38
+            unselectedTabColor = defaultOptions.unselectedTabColor;
39
+        }
21 40
     }
22 41
 }

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

@@ -7,6 +7,7 @@ import com.reactnativenavigation.parse.Button;
7 7
 import com.reactnativenavigation.parse.NavigationOptions;
8 8
 import com.reactnativenavigation.parse.TopBarOptions;
9 9
 import com.reactnativenavigation.parse.TopTabOptions;
10
+import com.reactnativenavigation.parse.TopTabsOptions;
10 11
 import com.reactnativenavigation.views.TopBar;
11 12
 
12 13
 import java.util.ArrayList;
@@ -26,6 +27,7 @@ public class OptionsPresenter {
26 27
     public void applyOptions(NavigationOptions options) {
27 28
         applyTopBarOptions(options.topBarOptions);
28 29
         applyButtons(options.leftButtons, options.rightButtons);
30
+        applyTopTabsOptions(options.topTabsOptions);
29 31
         applyTopTabOptions(options.topTabOptions);
30 32
     }
31 33
 
@@ -70,6 +72,10 @@ public class OptionsPresenter {
70 72
         topBar.setButtons(leftButtons, rightButtons);
71 73
     }
72 74
 
75
+    private void applyTopTabsOptions(TopTabsOptions options) {
76
+        topBar.applyTopTabsColors(options.selectedTabColor, options.unselectedTabColor);
77
+    }
78
+
73 79
     private void applyTopTabOptions(TopTabOptions topTabOptions) {
74 80
         if (topTabOptions.fontFamily != null) {
75 81
             topBar.setTopTabFontFamily(topTabOptions.tabIndex, topTabOptions.fontFamily);

+ 27
- 83
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java View File

@@ -25,7 +25,8 @@ public class NavigationModule extends ReactContextBaseJavaModule {
25 25
 	private static final String NAME = "RNNBridgeModule";
26 26
 	private final ReactInstanceManager reactInstanceManager;
27 27
 
28
-	public NavigationModule(final ReactApplicationContext reactContext, final ReactInstanceManager reactInstanceManager) {
28
+	@SuppressWarnings("WeakerAccess")
29
+    public NavigationModule(final ReactApplicationContext reactContext, final ReactInstanceManager reactInstanceManager) {
29 30
 		super(reactContext);
30 31
 		this.reactInstanceManager = reactInstanceManager;
31 32
 	}
@@ -38,130 +39,78 @@ public class NavigationModule extends ReactContextBaseJavaModule {
38 39
 	@ReactMethod
39 40
 	public void setRoot(final ReadableMap rawLayoutTree, final Promise promise) {
40 41
 		final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
41
-		handle(new Runnable() {
42
-			@Override
43
-			public void run() {
44
-				final ViewController viewController = newLayoutFactory().create(layoutTree);
45
-				navigator().setRoot(viewController, promise);
46
-			}
47
-		});
42
+		handle(() -> {
43
+            final ViewController viewController = newLayoutFactory().create(layoutTree);
44
+            navigator().setRoot(viewController, promise);
45
+        });
48 46
 	}
49 47
 
50 48
 	@ReactMethod
51 49
 	public void setDefaultOptions(final ReadableMap options) {
52 50
         final NavigationOptions defaultOptions = NavigationOptions.parse(new TypefaceLoader(activity()), JSONParser.parse(options));
53
-        handle(new Runnable() {
54
-            @Override
55
-            public void run() {
56
-                navigator().setDefaultOptions(defaultOptions);
57
-            }
58
-        });
51
+        handle(() -> navigator().setDefaultOptions(defaultOptions));
59 52
     }
60 53
 
61 54
 	@ReactMethod
62 55
 	public void setOptions(final String onContainerId, final ReadableMap options) {
63 56
 		final NavigationOptions navOptions = NavigationOptions.parse(new TypefaceLoader(activity()), JSONParser.parse(options));
64
-		handle(new Runnable() {
65
-			@Override
66
-			public void run() {
67
-				navigator().setOptions(onContainerId, navOptions);
68
-			}
69
-		});
57
+		handle(() -> navigator().setOptions(onContainerId, navOptions));
70 58
 	}
71 59
 
72 60
 	@ReactMethod
73 61
 	public void push(final String onContainerId, final ReadableMap rawLayoutTree, final Promise promise) {
74 62
 		final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
75
-		handle(new Runnable() {
76
-			@Override
77
-			public void run() {
78
-				final ViewController viewController = newLayoutFactory().create(layoutTree);
79
-				navigator().push(onContainerId, viewController, promise);
80
-			}
81
-		});
63
+		handle(() -> {
64
+            final ViewController viewController = newLayoutFactory().create(layoutTree);
65
+            navigator().push(onContainerId, viewController, promise);
66
+        });
82 67
 	}
83 68
 
84 69
 	@ReactMethod
85 70
 	public void pop(final String onContainerId, final ReadableMap options, final Promise promise) {
86
-		handle(new Runnable() {
87
-			@Override
88
-			public void run() {
89
-				navigator().popSpecific(onContainerId, promise);
90
-			}
91
-		});
71
+		handle(() -> navigator().popSpecific(onContainerId, promise));
92 72
 	}
93 73
 
94 74
 	@ReactMethod
95 75
 	public void popTo(final String containerId, final Promise promise) {
96
-		handle(new Runnable() {
97
-			@Override
98
-			public void run() {
99
-				navigator().popTo(containerId, promise);
100
-			}
101
-		});
76
+		handle(() -> navigator().popTo(containerId, promise));
102 77
 	}
103 78
 
104 79
 	@ReactMethod
105 80
 	public void popToRoot(final String containerId, final Promise promise) {
106
-		handle(new Runnable() {
107
-			@Override
108
-			public void run() {
109
-				navigator().popToRoot(containerId, promise);
110
-			}
111
-		});
81
+		handle(() -> navigator().popToRoot(containerId, promise));
112 82
 	}
113 83
 
114 84
 	@ReactMethod
115 85
 	public void showModal(final ReadableMap rawLayoutTree, final Promise promise) {
116 86
 		final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
117
-		handle(new Runnable() {
118
-			@Override
119
-			public void run() {
120
-				final ViewController viewController = newLayoutFactory().create(layoutTree);
121
-				navigator().showModal(viewController, promise);
122
-			}
123
-		});
87
+		handle(() -> {
88
+            final ViewController viewController = newLayoutFactory().create(layoutTree);
89
+            navigator().showModal(viewController, promise);
90
+        });
124 91
 	}
125 92
 
126 93
 	@ReactMethod
127 94
 	public void dismissModal(final String containerId, final Promise promise) {
128
-		handle(new Runnable() {
129
-			@Override
130
-			public void run() {
131
-				navigator().dismissModal(containerId, promise);
132
-			}
133
-		});
95
+		handle(() -> navigator().dismissModal(containerId, promise));
134 96
 	}
135 97
 
136 98
 	@ReactMethod
137 99
 	public void dismissAllModals(final Promise promise) {
138
-		handle(new Runnable() {
139
-			@Override
140
-			public void run() {
141
-				navigator().dismissAllModals(promise);
142
-			}
143
-		});
100
+		handle(() -> navigator().dismissAllModals(promise));
144 101
 	}
145 102
 
146 103
 	@ReactMethod
147 104
 	public void showOverlay(final String type, final ReadableMap data, final Promise promise) {
148 105
 		if (OverlayFactory.Overlay.create(type) == OverlayFactory.Overlay.CustomDialog) {
149 106
 			final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(data));
150
-			handle(new Runnable() {
151
-				@Override
152
-				public void run() {
153
-					ViewController viewController = newLayoutFactory().create(layoutTree);
154
-					navigator().showOverlay(type, OverlayOptions.create(viewController), promise);
155
-				}
156
-			});
107
+			handle(() -> {
108
+                ViewController viewController = newLayoutFactory().create(layoutTree);
109
+                navigator().showOverlay(type, OverlayOptions.create(viewController), promise);
110
+            });
157 111
 		} else {
158 112
 			final OverlayOptions overlayOptions = OverlayOptions.parse(JSONParser.parse(data));
159
-			handle(new Runnable() {
160
-				@Override
161
-				public void run() {
162
-					navigator().showOverlay(type, overlayOptions, promise);
163
-				}
164
-			});
113
+			handle(() -> navigator().showOverlay(type, overlayOptions, promise));
165 114
 		}
166 115
 
167 116
 
@@ -169,12 +118,7 @@ public class NavigationModule extends ReactContextBaseJavaModule {
169 118
 
170 119
 	@ReactMethod
171 120
 	public void dismissOverlay() {
172
-		handle(new Runnable() {
173
-			@Override
174
-			public void run() {
175
-				navigator().dismissOverlay();
176
-			}
177
-		});
121
+		handle(() -> navigator().dismissOverlay());
178 122
 	}
179 123
 
180 124
 	private NavigationActivity activity() {

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

@@ -11,7 +11,7 @@ public class TopTabsAdapter extends PagerAdapter implements ViewPager.OnPageChan
11 11
     private List<TopTabController> tabs;
12 12
     private int currentPage = 0;
13 13
 
14
-    TopTabsAdapter(List<TopTabController> tabs) {
14
+    public TopTabsAdapter(List<TopTabController> tabs) {
15 15
         this.tabs = tabs;
16 16
     }
17 17
 

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

@@ -10,6 +10,7 @@ import com.reactnativenavigation.utils.Task;
10 10
 import com.reactnativenavigation.viewcontrollers.ParentController;
11 11
 import com.reactnativenavigation.viewcontrollers.ViewController;
12 12
 import com.reactnativenavigation.views.TopTabsLayout;
13
+import com.reactnativenavigation.views.TopTabsLayoutCreator;
13 14
 
14 15
 import java.util.Collection;
15 16
 import java.util.List;
@@ -18,12 +19,14 @@ public class TopTabsController extends ParentController implements NavigationOpt
18 19
 
19 20
     private List<TopTabController> tabs;
20 21
     private TopTabsLayout topTabsLayout;
21
-    private TopTabsAdapter adapter;
22
+    private TopTabsLayoutCreator viewCreator;
23
+    private NavigationOptions navigationOptions;
22 24
 
23
-    public TopTabsController(Activity activity, String id, List<TopTabController> tabs) {
25
+    public TopTabsController(Activity activity, String id, List<TopTabController> tabs, TopTabsLayoutCreator viewCreator, NavigationOptions navigationOptions) {
24 26
         super(activity, id);
27
+        this.viewCreator = viewCreator;
28
+        this.navigationOptions = navigationOptions;
25 29
         this.tabs = tabs;
26
-        this.adapter = new TopTabsAdapter(tabs);
27 30
         for (ViewController tab : tabs) {
28 31
             tab.setParentController(this);
29 32
         }
@@ -32,7 +35,7 @@ public class TopTabsController extends ParentController implements NavigationOpt
32 35
     @NonNull
33 36
     @Override
34 37
     protected ViewGroup createView() {
35
-        topTabsLayout = new TopTabsLayout(getActivity(), tabs, adapter);
38
+        topTabsLayout = viewCreator.create();
36 39
         return topTabsLayout;
37 40
     }
38 41
 
@@ -44,6 +47,7 @@ public class TopTabsController extends ParentController implements NavigationOpt
44 47
 
45 48
     @Override
46 49
     public void onViewAppeared() {
50
+        applyOptions(navigationOptions);
47 51
         performOnCurrentTab(TopTabController::onViewAppeared);
48 52
     }
49 53
 
@@ -67,6 +71,6 @@ public class TopTabsController extends ParentController implements NavigationOpt
67 71
     }
68 72
 
69 73
     private void performOnCurrentTab(Task<TopTabController> task) {
70
-        task.run(tabs.get(adapter.getCurrentItem()));
74
+        task.run(tabs.get(topTabsLayout.getCurrentItem()));
71 75
     }
72 76
 }

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

@@ -15,6 +15,7 @@ import android.widget.TextView;
15 15
 
16 16
 import com.reactnativenavigation.parse.Button;
17 17
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsViewPager;
18
+import com.reactnativenavigation.views.style.Color;
18 19
 
19 20
 import java.util.ArrayList;
20 21
 
@@ -62,6 +63,10 @@ public class TopBar extends AppBarLayout {
62 63
         topTabs.setFontFamily(tabIndex, fontFamily);
63 64
     }
64 65
 
66
+    public void applyTopTabsColors(Color selectedTabColor, Color unselectedTabColor) {
67
+        topTabs.applyTopTabsColors(selectedTabColor, unselectedTabColor);
68
+    }
69
+
65 70
 	public void setButtons(ArrayList<Button> leftButtons, ArrayList<Button> rightButtons) {
66 71
 		setLeftButtons(leftButtons);
67 72
 		setRightButtons(rightButtons);

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

@@ -4,6 +4,8 @@ import android.content.Context;
4 4
 import android.graphics.Typeface;
5 5
 import android.support.design.widget.TabLayout;
6 6
 
7
+import com.reactnativenavigation.views.style.Color;
8
+
7 9
 public class TopTabs extends TabLayout {
8 10
     private final TopTabsStyleHelper styleHelper;
9 11
 
@@ -15,4 +17,16 @@ public class TopTabs extends TabLayout {
15 17
     public void setFontFamily(int tabIndex, Typeface fontFamily) {
16 18
         styleHelper.setFontFamily(tabIndex, fontFamily);
17 19
     }
20
+
21
+    public int[] getSelectedTabColors() {
22
+        return SELECTED_STATE_SET;
23
+    }
24
+
25
+    public int[] getDefaultTabColors() {
26
+        return EMPTY_STATE_SET;
27
+    }
28
+
29
+    public void applyTopTabsColors(Color selectedTabColor, Color unselectedTabColor) {
30
+        styleHelper.applyTopTabsColors(selectedTabColor, unselectedTabColor);
31
+    }
18 32
 }

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

@@ -60,4 +60,8 @@ public class TopTabsLayout extends LinearLayout implements Container {
60 60
     public void switchToTab(int index) {
61 61
         viewPager.setCurrentItem(index);
62 62
     }
63
+
64
+    public int getCurrentItem() {
65
+        return viewPager.getCurrentItem();
66
+    }
63 67
 }

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

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

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

@@ -1,10 +1,13 @@
1 1
 package com.reactnativenavigation.views;
2 2
 
3
+import android.content.res.ColorStateList;
3 4
 import android.graphics.Typeface;
4 5
 import android.view.ViewGroup;
5 6
 import android.widget.TextView;
6 7
 
8
+import com.reactnativenavigation.utils.Task;
7 9
 import com.reactnativenavigation.utils.ViewUtils;
10
+import com.reactnativenavigation.views.style.Color;
8 11
 
9 12
 class TopTabsStyleHelper {
10 13
     private TopTabs topTabs;
@@ -13,10 +16,32 @@ class TopTabsStyleHelper {
13 16
         this.topTabs = topTabs;
14 17
     }
15 18
 
19
+    void applyTopTabsColors(Color selected, Color unselected) {
20
+        if (!selected.hasColor() && !unselected.hasColor()) return;
21
+
22
+        ColorStateList originalColors = topTabs.getTabTextColors();
23
+        int selectedTabColor = originalColors != null ? originalColors.getColorForState(topTabs.getSelectedTabColors(), -1) : -1;
24
+        int tabTextColor = originalColors != null ? originalColors.getColorForState(topTabs.getDefaultTabColors(), -1) : -1;
25
+
26
+        if (selected.hasColor()) {
27
+            tabTextColor = selected.get();
28
+        }
29
+
30
+        if (unselected.hasColor()) {
31
+            selectedTabColor = unselected.get();
32
+        }
33
+
34
+        topTabs.setTabTextColors(tabTextColor, selectedTabColor);
35
+    }
36
+
16 37
     void setFontFamily(int tabIndex, Typeface fontFamily) {
38
+        applyOnTabTitle(tabIndex, (title) -> title.setTypeface(fontFamily));
39
+    }
40
+
41
+    private void applyOnTabTitle(int tabIndex, Task<TextView> action) {
17 42
         TextView title = ViewUtils.findChildByClass(getTabView(tabIndex), TextView.class);
18 43
         if (title != null) {
19
-            title.setTypeface(fontFamily);
44
+            action.run(title);
20 45
         }
21 46
     }
22 47
 

+ 30
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/style/Color.java View File

@@ -0,0 +1,30 @@
1
+package com.reactnativenavigation.views.style;
2
+
3
+import android.support.annotation.ColorInt;
4
+
5
+public class Color {
6
+
7
+    private Integer color;
8
+
9
+    public Color(@ColorInt int color) {
10
+        this.color = color;
11
+    }
12
+
13
+    public boolean hasColor() {
14
+        return color != null;
15
+    }
16
+
17
+    @ColorInt
18
+    public int get() {
19
+        if (hasColor()) {
20
+            return color;
21
+        }
22
+        throw new RuntimeException("Tried to get null color!");
23
+    }
24
+
25
+    @SuppressWarnings("MagicNumber")
26
+    @Override
27
+    public String toString() {
28
+        return String.format("#%06X", (0xFFFFFF & get()));
29
+    }
30
+}

+ 18
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/style/NullColor.java View File

@@ -0,0 +1,18 @@
1
+package com.reactnativenavigation.views.style;
2
+
3
+public class NullColor extends Color {
4
+
5
+    public NullColor() {
6
+        super(0);
7
+    }
8
+
9
+    public boolean hasColor() {
10
+        return false;
11
+    }
12
+
13
+    @SuppressWarnings("MagicNumber")
14
+    @Override
15
+    public String toString() {
16
+        return "Null Color";
17
+    }
18
+}

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

@@ -8,10 +8,13 @@ import com.reactnativenavigation.mocks.TopTabLayoutMock;
8 8
 import com.reactnativenavigation.parse.NavigationOptions;
9 9
 import com.reactnativenavigation.viewcontrollers.ContainerViewController.IReactView;
10 10
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabController;
11
+import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsAdapter;
11 12
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsController;
12 13
 import com.reactnativenavigation.views.TopTabsLayout;
14
+import com.reactnativenavigation.views.TopTabsLayoutCreator;
13 15
 
14 16
 import org.junit.Test;
17
+import org.mockito.Mockito;
15 18
 
16 19
 import java.util.ArrayList;
17 20
 import java.util.List;
@@ -28,6 +31,8 @@ public class TopTabsViewControllerTest extends BaseTest {
28 31
     private List<TopTabLayoutMock> tabs = new ArrayList<>(SIZE);
29 32
     private List<TopTabController> tabControllers = new ArrayList<>(SIZE);
30 33
     private List<NavigationOptions> tabOptions = new ArrayList<>(SIZE);
34
+    private NavigationOptions navigationOptions;
35
+    private TopTabsLayout topTabsLayout;
31 36
 
32 37
     @Override
33 38
     public void beforeEach() {
@@ -36,7 +41,12 @@ public class TopTabsViewControllerTest extends BaseTest {
36 41
         tabs.clear();
37 42
         Activity activity = newActivity();
38 43
         createTabs(activity);
39
-        uut = new TopTabsController(activity, "containerId", tabControllers);
44
+        navigationOptions = new NavigationOptions();
45
+        topTabsLayout = spy(new TopTabsLayout(activity, tabControllers, new TopTabsAdapter(tabControllers)));
46
+
47
+        TopTabsLayoutCreator layoutCreator = Mockito.mock(TopTabsLayoutCreator.class);
48
+        Mockito.when(layoutCreator.create()).thenReturn(topTabsLayout);
49
+        uut = new TopTabsController(activity, "containerId", tabControllers, layoutCreator, navigationOptions);
40 50
     }
41 51
 
42 52
     private void createTabs(Activity activity) {
@@ -116,6 +126,13 @@ public class TopTabsViewControllerTest extends BaseTest {
116 126
         verify(tabControllers.get(0), times(2)).applyOptions(tabOptions.get(0));
117 127
     }
118 128
 
129
+    @Test
130
+    public void appliesOptionsOnLayoutWhenVisible() throws Exception {
131
+        uut.ensureViewIsCreated();
132
+        uut.onViewAppeared();
133
+        verify(topTabsLayout, times(1)).applyOptions(navigationOptions);
134
+    }
135
+
119 136
     private IReactView tab(TopTabsLayout topTabs, final int index) {
120 137
         return (IReactView) ((ViewGroup) topTabs.getViewPager().getChildAt(index)).getChildAt(0);
121 138
     }

+ 4
- 3
lib/src/Navigation.js View File

@@ -10,6 +10,7 @@ const PrivateEventsListener = require('./events/PrivateEventsListener');
10 10
 const PublicEventsRegistry = require('./events/PublicEventsRegistry');
11 11
 const Element = require('./adapters/Element');
12 12
 const Root = require('./params/containers/Root');
13
+const Container = require('./params/containers/Container');
13 14
 const NavigationOptions = require('./params/options/NavigationOptions');
14 15
 
15 16
 /** @constructor */
@@ -89,10 +90,10 @@ class Navigation {
89 90
   /**
90 91
    * Push a new screen into this screen's navigation stack.
91 92
    * @param {string} containerId The container's id.
92
-   * @param {*} params
93
+   * @param {Container} container
93 94
    */
94
-  push(containerId, params) {
95
-    return this.commands.push(containerId, params);
95
+  push(containerId, container) {
96
+    return this.commands.push(containerId, new Container(container));
96 97
   }
97 98
 
98 99
   /**

+ 1
- 0
lib/src/commands/Commands.js View File

@@ -44,6 +44,7 @@ class Commands {
44 44
 
45 45
   push(onContainerId, containerData) {
46 46
     const input = _.cloneDeep(containerData);
47
+    OptionsProcessor.processOptions(input);
47 48
     const layout = this.layoutTreeParser.parseFromSimpleJSON(input);
48 49
     this.layoutTreeCrawler.crawl(layout);
49 50
     return this.nativeCommandsSender.push(onContainerId, layout);

+ 4
- 3
lib/src/commands/LayoutTreeParser.js View File

@@ -13,7 +13,7 @@ class LayoutTreeParser {
13 13
       return this._createTabs(simpleJsonApi.bottomTabs);
14 14
     }
15 15
     if (simpleJsonApi.topTabs) {
16
-      return this._createTopTabs(simpleJsonApi.topTabs);
16
+      return this._createTopTabs(simpleJsonApi);
17 17
     }
18 18
     if (simpleJsonApi.name) {
19 19
       return this._createContainer(simpleJsonApi);
@@ -36,10 +36,11 @@ class LayoutTreeParser {
36 36
     };
37 37
   }
38 38
 
39
-  _createTopTabs(topTabs) {
39
+  _createTopTabs(node) {
40 40
     return {
41 41
       type: LayoutTypes.TopTabs,
42
-      children: _.map(topTabs, (t) => this._createTopTab(t))
42
+      data: _.pick(node, 'navigationOptions'),
43
+      children: _.map(node.topTabs, (t) => this._createTopTab(t))
43 44
     };
44 45
   }
45 46
 

+ 2
- 1
lib/src/commands/LayoutTreeParser.test.js View File

@@ -312,7 +312,8 @@ describe('LayoutTreeParser', () => {
312 312
               },
313 313
               children: []
314 314
             }
315
-          ]
315
+          ],
316
+          data: {}
316 317
         });
317 318
     });
318 319
   });

+ 8
- 4
lib/src/params/containers/Container.js View File

@@ -1,16 +1,20 @@
1
+const NavigationOptions = require('./../options/NavigationOptions');
2
+
1 3
 class Container {
2 4
   /**
3 5
    * @property {string} name The container's registered name
6
+   * @property {Container[]} [topTabs]
4 7
    * @property {object} [passProps] props
5 8
    * @property {NavigationOptions} navigationOptions
6 9
    */
7 10
   constructor(params) {
8
-    if (!params || !params.name) {
9
-      throw new Error('Container name is undefined');
10
-    }
11 11
     this.name = params.name;
12
+    if (params.topTabs) {
13
+      params.topTabs.map((t) => new Container(t));
14
+      this.topTabs = params.topTabs;
15
+    }
12 16
     this.passProps = params.passProps;
13
-    this.navigationOptions = params.navigationOptions;
17
+    this.navigationOptions = params.navigationOptions && new NavigationOptions(params.navigationOptions);
14 18
   }
15 19
 }
16 20
 

+ 12
- 3
lib/src/params/containers/Container.test.js View File

@@ -5,7 +5,14 @@ const PASS_PROPS = {
5 5
 };
6 6
 const CONTAINER = {
7 7
   name: 'myScreen',
8
-  passProps: PASS_PROPS
8
+  passProps: PASS_PROPS,
9
+  navigationOptions: {}
10
+};
11
+const TOP_TABS_CONTAINER = {
12
+  topTabs: [
13
+    CONTAINER,
14
+    CONTAINER
15
+  ]
9 16
 };
10 17
 
11 18
 describe('ContainerRegistry', () => {
@@ -13,9 +20,11 @@ describe('ContainerRegistry', () => {
13 20
     const uut = new Container(CONTAINER);
14 21
     expect(uut.name).toBe('myScreen');
15 22
     expect(uut.passProps).toEqual(PASS_PROPS);
23
+    expect(uut.navigationOptions).toEqual({});
16 24
   });
17 25
 
18
-  it('throws if container name is missing', () => {
19
-    expect(() => new Container()).toThrowError('Container name is undefined');
26
+  it('parses TopTabs container', () => {
27
+    const uut = new Container(TOP_TABS_CONTAINER);
28
+    expect(uut.topTabs).toEqual([CONTAINER, CONTAINER]);
20 29
   });
21 30
 });

+ 3
- 0
lib/src/params/options/NavigationOptions.js View File

@@ -2,6 +2,7 @@ const TopBar = require('./TopBar');
2 2
 const BottomTabs = require('./BottomTabs');
3 3
 const Button = require('./Button');
4 4
 const BottomTab = require('./BottomTab');
5
+const TopTabs = require('./TopTabs');
5 6
 
6 7
 class NavigationOptions {
7 8
   /**
@@ -9,6 +10,7 @@ class NavigationOptions {
9 10
    * @property {options:BottomTabs} [bottomTabs]
10 11
    * @property {options:BottomTab} [bottomTab]
11 12
    * @property {string} [orientation]
13
+   * @property {options:TopTabs} [topTabs]
12 14
    * @property {options:Button[]} [rightButtons]
13 15
    * @property {options:Button[]} [leftButtons]
14 16
    */
@@ -23,6 +25,7 @@ class NavigationOptions {
23 25
     this.backgroundImage = options.backgroundImage;
24 26
     this.rootBackgroundImage = options.rootBackgroundImage;
25 27
     this.screenBackgroundColor = options.screenBackgroundColor;
28
+    this.topTabs = options.topTabs && new TopTabs(options.topTabs);
26 29
   }
27 30
 }
28 31
 

+ 8
- 1
lib/src/params/options/NavigationOptions.test.js View File

@@ -2,6 +2,7 @@ const NavigationOptions = require('./NavigationOptions');
2 2
 const BottomTabs = require('./BottomTabs');
3 3
 const TopBar = require('./TopBar');
4 4
 const BottomTab = require('./BottomTab');
5
+const TopTabs = require('./TopTabs');
5 6
 
6 7
 const TAB_BAR = {};
7 8
 const TOP_BAR = {};
@@ -22,13 +23,18 @@ const LEFT_BUTTONS = [
22 23
     buttonColor: 'red'
23 24
   }
24 25
 ];
26
+const TOP_TABS = {
27
+  selectedTabColor: 'blue',
28
+  unselectedTabColor: 'red'
29
+};
25 30
 const NAVIGATION_OPTIONS = {
26 31
   topBar: TOP_BAR,
27 32
   bottomTabs: TAB_BAR,
28 33
   bottomTab: TAB_ITEM,
29 34
   orientation: 'portrait',
30 35
   rightButtons: RIGHT_BUTTONS,
31
-  leftButtons: LEFT_BUTTONS
36
+  leftButtons: LEFT_BUTTONS,
37
+  topTabs: TOP_TABS
32 38
 };
33 39
 
34 40
 describe('NavigationOptions', () => {
@@ -40,5 +46,6 @@ describe('NavigationOptions', () => {
40 46
     expect(uut.orientation).toEqual('portrait');
41 47
     expect(uut.rightButtons).toEqual(RIGHT_BUTTONS);
42 48
     expect(uut.leftButtons).toEqual(LEFT_BUTTONS);
49
+    expect(uut.topTabs).toBeInstanceOf(TopTabs);
43 50
   });
44 51
 });

+ 12
- 0
lib/src/params/options/TopTabs.js View File

@@ -0,0 +1,12 @@
1
+class TopTabs {
2
+  /**
3
+   * @property {string} [selectedTabColor] Selected tab color
4
+   * @property {string} unselectedTabColor Unselected tab color
5
+   */
6
+  constructor(topTabs) {
7
+    this.selectedTabColor = topTabs.selectedTabColor;
8
+    this.unselectedTabColor = topTabs.unselectedTabColor;
9
+  }
10
+}
11
+
12
+module.exports = TopTabs;

+ 14
- 0
lib/src/params/options/TopTabs.test.js View File

@@ -0,0 +1,14 @@
1
+const TopTabs = require('./TopTabs');
2
+
3
+const TOP_TABS = {
4
+  selectedTabColor: 'red',
5
+  unselectedTabColor: 'blue'
6
+};
7
+
8
+describe('TopTabs', () => {
9
+  it('Parses TopTabs', () => {
10
+    const uut = new TopTabs(TOP_TABS);
11
+    expect(uut.selectedTabColor).toEqual('red');
12
+    expect(uut.unselectedTabColor).toEqual('blue');
13
+  });
14
+});

+ 7
- 1
playground/src/containers/WelcomeScreen.js View File

@@ -199,7 +199,13 @@ class WelcomeScreen extends Component {
199 199
             }
200 200
           }
201 201
         }
202
-      ]
202
+      ],
203
+      navigationOptions: {
204
+        topTabs: {
205
+          selectedTabColor: '#12766b',
206
+          unselectedTabColor: 'red'
207
+        }
208
+      }
203 209
     });
204 210
   }
205 211