Browse Source

Android implementation of Navigation.setDefaultOptions (#2299)

Usage
```js
Navigation.setDefaultOptions({
      topBar: {
        backgroundColor: 'red'
      }
    });
```
Guy Carmeli 6 years ago
parent
commit
cdbb834c82

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

@@ -1,6 +1,8 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3 3
 
4
+import com.reactnativenavigation.parse.NavigationOptions.BooleanOptions;
5
+
4 6
 import org.json.JSONObject;
5 7
 
6 8
 public class BottomTabsOptions implements DEFAULT_VALUES {
@@ -12,15 +14,15 @@ public class BottomTabsOptions implements DEFAULT_VALUES {
12 14
 		options.currentTabId = json.optString("currentTabId", NO_VALUE);
13 15
 		options.currentTabIndex = json.optInt("currentTabIndex", NO_INT_VALUE);
14 16
 		options.tabBadge = json.optInt("tabBadge", NO_INT_VALUE);
15
-		options.hidden = NavigationOptions.BooleanOptions.parse(json.optString("hidden"));
16
-		options.animateHide = NavigationOptions.BooleanOptions.parse(json.optString("animateHide"));
17
+		options.hidden = BooleanOptions.parse(json.optString("hidden"));
18
+		options.animateHide = BooleanOptions.parse(json.optString("animateHide"));
17 19
 
18 20
 		return options;
19 21
 	}
20 22
 
21 23
 	public int tabBadge = NO_INT_VALUE;
22
-	public NavigationOptions.BooleanOptions hidden = NavigationOptions.BooleanOptions.False;
23
-	public NavigationOptions.BooleanOptions animateHide = NavigationOptions.BooleanOptions.False;
24
+	public BooleanOptions hidden = BooleanOptions.False;
25
+	public BooleanOptions animateHide = BooleanOptions.False;
24 26
 	public int currentTabIndex = NO_INT_VALUE;
25 27
 	public String currentTabId = NO_VALUE;
26 28
 
@@ -29,16 +31,34 @@ public class BottomTabsOptions implements DEFAULT_VALUES {
29 31
 			currentTabId = other.currentTabId;
30 32
 		}
31 33
 		if (NO_INT_VALUE != other.currentTabIndex) {
32
-			currentTabId = other.currentTabId;
34
+            currentTabIndex = other.currentTabIndex;
33 35
 		}
34 36
 		if (NO_INT_VALUE != other.tabBadge) {
35 37
 			tabBadge = other.tabBadge;
36 38
 		}
37
-		if (other.hidden != NavigationOptions.BooleanOptions.NoValue) {
39
+		if (other.hidden != BooleanOptions.NoValue) {
38 40
 			hidden = other.hidden;
39 41
 		}
40
-		if (other.animateHide != NavigationOptions.BooleanOptions.NoValue) {
42
+		if (other.animateHide != BooleanOptions.NoValue) {
41 43
 			animateHide = other.animateHide;
42 44
 		}
43 45
 	}
46
+
47
+    void mergeWithDefault(final BottomTabsOptions defaultOptions) {
48
+        if (NO_VALUE.equals(currentTabId)) {
49
+            currentTabId = defaultOptions.currentTabId;
50
+        }
51
+        if (NO_INT_VALUE == currentTabIndex) {
52
+            currentTabIndex = defaultOptions.currentTabIndex;
53
+        }
54
+        if (NO_INT_VALUE == tabBadge) {
55
+            tabBadge = defaultOptions.tabBadge;
56
+        }
57
+        if (hidden == BooleanOptions.NoValue) {
58
+            hidden = defaultOptions.hidden;
59
+        }
60
+        if (animateHide == BooleanOptions.NoValue) {
61
+            animateHide = defaultOptions.animateHide;
62
+        }
63
+    }
44 64
 }

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

@@ -19,11 +19,13 @@ public class LayoutFactory {
19 19
 
20 20
 	private final Activity activity;
21 21
 	private final ReactInstanceManager reactInstanceManager;
22
+    private NavigationOptions defaultOptions;
22 23
 
23
-	public LayoutFactory(Activity activity, final ReactInstanceManager reactInstanceManager) {
24
+    public LayoutFactory(Activity activity, final ReactInstanceManager reactInstanceManager, NavigationOptions defaultOptions) {
24 25
 		this.activity = activity;
25 26
 		this.reactInstanceManager = reactInstanceManager;
26
-	}
27
+        this.defaultOptions = defaultOptions;
28
+    }
27 29
 
28 30
 	public ViewController create(final LayoutNode node) {
29 31
 		switch (node.type) {
@@ -84,7 +86,7 @@ public class LayoutFactory {
84 86
 	private ViewController createContainer(LayoutNode node) {
85 87
 		String id = node.id;
86 88
 		String name = node.data.optString("name");
87
-		NavigationOptions navigationOptions = NavigationOptions.parse(node.data.optJSONObject("navigationOptions"));
89
+		NavigationOptions navigationOptions = NavigationOptions.parse(node.data.optJSONObject("navigationOptions"), defaultOptions);
88 90
 		return new ContainerViewController(activity, id, name,
89 91
 				new TopbarContainerViewCreator(new ReactContainerViewCreator(reactInstanceManager)), navigationOptions);
90 92
 	}

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

@@ -1,14 +1,12 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3
-import android.graphics.Color;
4
-import android.support.annotation.ColorInt;
5 3
 import android.support.annotation.NonNull;
6 4
 
7 5
 import org.json.JSONObject;
8 6
 
9 7
 public class NavigationOptions implements DEFAULT_VALUES {
10 8
 
11
-	public enum BooleanOptions {
9
+    public enum BooleanOptions {
12 10
 		True,
13 11
 		False,
14 12
 		NoValue;
@@ -21,22 +19,33 @@ public class NavigationOptions implements DEFAULT_VALUES {
21 19
 		}
22 20
 	}
23 21
 
22
+    @NonNull
23
+    public static NavigationOptions parse(JSONObject json) {
24
+        return parse(json, new NavigationOptions());
25
+    }
26
+
24 27
 	@NonNull
25
-	public static NavigationOptions parse(JSONObject json) {
28
+	public static NavigationOptions parse(JSONObject json, @NonNull NavigationOptions defaultOptions) {
26 29
 		NavigationOptions result = new NavigationOptions();
27 30
 		if (json == null) return result;
28 31
 
29 32
 		result.topBarOptions = TopBarOptions.parse(json.optJSONObject("topBar"));
30 33
 		result.bottomTabsOptions = BottomTabsOptions.parse(json.optJSONObject("tabBar"));
31 34
 
32
-		return result;
35
+		return result.withDefaultOptions(defaultOptions);
33 36
 	}
34 37
 
35 38
 	public TopBarOptions topBarOptions = new TopBarOptions();
36 39
 	public BottomTabsOptions bottomTabsOptions = new BottomTabsOptions();
37 40
 
38 41
 	public void mergeWith(final NavigationOptions other) {
39
-		topBarOptions.mergeWith(other.topBarOptions);
42
+        topBarOptions.mergeWith(other.topBarOptions);
40 43
 		bottomTabsOptions.mergeWith(other.bottomTabsOptions);
41 44
 	}
45
+
46
+    NavigationOptions withDefaultOptions(final NavigationOptions other) {
47
+        topBarOptions.mergeWithDefault(other.topBarOptions);
48
+        bottomTabsOptions.mergeWithDefault(other.bottomTabsOptions);
49
+        return this;
50
+    }
42 51
 }

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

@@ -49,4 +49,21 @@ public class TopBarOptions implements DEFAULT_VALUES {
49 49
 			animateHide = other.animateHide;
50 50
 		}
51 51
 	}
52
+
53
+    void mergeWithDefault(TopBarOptions defaultOptions) {
54
+        if (NO_VALUE.equals(title))
55
+            title = defaultOptions.title;
56
+        if (backgroundColor == NO_COLOR_VALUE)
57
+            backgroundColor = defaultOptions.backgroundColor;
58
+        if (textColor == NO_COLOR_VALUE)
59
+            textColor = defaultOptions.textColor;
60
+        if (textFontSize == NO_FLOAT_VALUE)
61
+            textFontSize = defaultOptions.textFontSize;
62
+        if (NO_VALUE.equals(textFontFamily))
63
+            textFontFamily = defaultOptions.textFontFamily;
64
+        if (hidden == NavigationOptions.BooleanOptions.NoValue)
65
+            hidden = defaultOptions.hidden;
66
+        if (animateHide == NavigationOptions.BooleanOptions.NoValue)
67
+            animateHide = defaultOptions.animateHide;
68
+    }
52 69
 }

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

@@ -9,20 +9,17 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
9 9
 import com.facebook.react.bridge.ReactMethod;
10 10
 import com.facebook.react.bridge.ReadableMap;
11 11
 import com.reactnativenavigation.NavigationActivity;
12
+import com.reactnativenavigation.parse.JSONParser;
12 13
 import com.reactnativenavigation.parse.LayoutFactory;
13 14
 import com.reactnativenavigation.parse.LayoutNode;
14
-import com.reactnativenavigation.parse.NavigationOptions;
15
-import com.reactnativenavigation.parse.JSONParser;
16 15
 import com.reactnativenavigation.parse.LayoutNodeParser;
16
+import com.reactnativenavigation.parse.NavigationOptions;
17 17
 import com.reactnativenavigation.parse.OverlayOptions;
18 18
 import com.reactnativenavigation.utils.UiThread;
19
-import com.reactnativenavigation.viewcontrollers.ContainerViewController;
20 19
 import com.reactnativenavigation.viewcontrollers.Navigator;
21 20
 import com.reactnativenavigation.viewcontrollers.ViewController;
22 21
 import com.reactnativenavigation.viewcontrollers.overlay.OverlayFactory;
23 22
 
24
-import org.json.JSONObject;
25
-
26 23
 public class NavigationModule extends ReactContextBaseJavaModule {
27 24
 	private static final String NAME = "RNNBridgeModule";
28 25
 	private final ReactInstanceManager reactInstanceManager;
@@ -49,6 +46,17 @@ public class NavigationModule extends ReactContextBaseJavaModule {
49 46
 		});
50 47
 	}
51 48
 
49
+	@ReactMethod
50
+	public void setDefaultOptions(final ReadableMap options) {
51
+        final NavigationOptions defaultOptions = NavigationOptions.parse(JSONParser.parse(options));
52
+        handle(new Runnable() {
53
+            @Override
54
+            public void run() {
55
+                navigator().setDefaultOptions(defaultOptions);
56
+            }
57
+        });
58
+    }
59
+
52 60
 	@ReactMethod
53 61
 	public void setOptions(final String onContainerId, final ReadableMap options) {
54 62
 		final NavigationOptions navOptions = NavigationOptions.parse(JSONParser.parse(options));
@@ -178,7 +186,7 @@ public class NavigationModule extends ReactContextBaseJavaModule {
178 186
 
179 187
 	@NonNull
180 188
 	private LayoutFactory newLayoutFactory() {
181
-		return new LayoutFactory(activity(), reactInstanceManager);
189
+		return new LayoutFactory(activity(), reactInstanceManager, navigator().getDefaultOptions());
182 190
 	}
183 191
 
184 192
 	private void handle(Runnable task) {

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

@@ -12,8 +12,6 @@ import com.reactnativenavigation.presentation.NavigationOptionsListener;
12 12
 import com.reactnativenavigation.presentation.OverlayPresenter;
13 13
 import com.reactnativenavigation.utils.CompatUtils;
14 14
 
15
-import org.json.JSONObject;
16
-
17 15
 import java.util.Collection;
18 16
 import java.util.Collections;
19 17
 
@@ -22,8 +20,9 @@ public class Navigator extends ParentController {
22 20
 	private final ModalStack modalStack = new ModalStack();
23 21
 	private ViewController root;
24 22
 	private OverlayPresenter overlayPresenter;
23
+    private NavigationOptions defaultOptions = new NavigationOptions();
25 24
 
26
-	public Navigator(final Activity activity) {
25
+    public Navigator(final Activity activity) {
27 26
 		super(activity, "navigator" + CompatUtils.generateViewId());
28 27
 	}
29 28
 
@@ -70,6 +69,14 @@ public class Navigator extends ParentController {
70 69
 		}
71 70
 	}
72 71
 
72
+    public void setDefaultOptions(NavigationOptions defaultOptions) {
73
+        this.defaultOptions = defaultOptions;
74
+    }
75
+
76
+    public NavigationOptions getDefaultOptions() {
77
+        return defaultOptions;
78
+    }
79
+
73 80
 	public void setOptions(final String containerId, NavigationOptions options) {
74 81
 		ViewController target = findControllerById(containerId);
75 82
 		if (target instanceof NavigationOptionsListener) {

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

@@ -1,7 +1,10 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3
+import android.support.annotation.NonNull;
4
+
3 5
 import com.reactnativenavigation.BaseTest;
4 6
 
7
+import org.json.JSONException;
5 8
 import org.json.JSONObject;
6 9
 import org.junit.Test;
7 10
 
@@ -10,47 +13,114 @@ import static org.assertj.core.api.Java6Assertions.assertThat;
10 13
 
11 14
 public class NavigationOptionsTest extends BaseTest {
12 15
 
13
-	@Test
16
+    private static final String TITLE = "the title";
17
+    private static final int TOP_BAR_BACKGROUND_COLOR = 0xff123456;
18
+    private static final int TOP_BAR_TEXT_COLOR = 0xff123456;
19
+    private static final int TOP_BAR_FONT_SIZE = 18;
20
+    private static final String TOP_BAR_FONT_FAMILY = "HelveticaNeue-CondensedBold";
21
+    private static final NavigationOptions.BooleanOptions TOP_BAR_HIDDEN = True;
22
+    private static final NavigationOptions.BooleanOptions BOTTOM_TABS_ANIMATE_HIDE = True;
23
+    private static final NavigationOptions.BooleanOptions BOTTOM_TABS_HIDDEN = True;
24
+    private static final int BOTTOM_TABS_BADGE = 3;
25
+    private static final String BOTTOM_TABS_CURRENT_TAB_ID = "ContainerId";
26
+    private static final int BOTTOM_TABS_CURRENT_TAB_INDEX = 1;
27
+
28
+    @Test
14 29
 	public void parsesNullAsDefaultEmptyOptions() throws Exception {
15 30
 		assertThat(NavigationOptions.parse(null)).isNotNull();
16 31
 	}
17 32
 
18 33
 	@Test
19 34
 	public void parsesJson() throws Exception {
20
-		JSONObject json = new JSONObject();
21
-		JSONObject topBarJson = new JSONObject();
35
+		JSONObject json = new JSONObject()
36
+                .put("topBar", createTopBar())
37
+                .put("tabBar", createTabBar());
38
+		NavigationOptions result = NavigationOptions.parse(json);
39
+        assertResult(result);
40
+	}
22 41
 
23
-		topBarJson.put("title", "the title");
24
-		topBarJson.put("backgroundColor", 0xff123456);
25
-		topBarJson.put("textColor", 0xff123456);
26
-		topBarJson.put("textFontSize", 18);
27
-		topBarJson.put("textFontFamily", "HelveticaNeue-CondensedBold");
28
-		topBarJson.put("hidden", true);
42
+    private void assertResult(NavigationOptions result) {
43
+        assertThat(result.topBarOptions.title).isEqualTo(TITLE);
44
+        assertThat(result.topBarOptions.backgroundColor).isEqualTo(TOP_BAR_BACKGROUND_COLOR);
45
+        assertThat(result.topBarOptions.textColor).isEqualTo(TOP_BAR_TEXT_COLOR);
46
+        assertThat(result.topBarOptions.textFontSize).isEqualTo(TOP_BAR_FONT_SIZE);
47
+        assertThat(result.topBarOptions.textFontFamily).isEqualTo(TOP_BAR_FONT_FAMILY);
48
+        assertThat(result.topBarOptions.hidden).isEqualTo(TOP_BAR_HIDDEN);
49
+        assertThat(result.bottomTabsOptions.animateHide).isEqualTo(BOTTOM_TABS_ANIMATE_HIDE);
50
+        assertThat(result.bottomTabsOptions.hidden).isEqualTo(BOTTOM_TABS_HIDDEN);
51
+        assertThat(result.bottomTabsOptions.tabBadge).isEqualTo(BOTTOM_TABS_BADGE);
52
+        assertThat(result.bottomTabsOptions.currentTabId).isEqualTo(BOTTOM_TABS_CURRENT_TAB_ID);
53
+        assertThat(result.bottomTabsOptions.currentTabIndex).isEqualTo(BOTTOM_TABS_CURRENT_TAB_INDEX);
54
+    }
29 55
 
30
-		json.put("topBar", topBarJson);
56
+    @NonNull
57
+    private JSONObject createTabBar() throws JSONException {
58
+        return new JSONObject()
59
+            .put("currentTabId", BOTTOM_TABS_CURRENT_TAB_ID)
60
+            .put("currentTabIndex", BOTTOM_TABS_CURRENT_TAB_INDEX)
61
+            .put("hidden", BOTTOM_TABS_HIDDEN)
62
+            .put("animateHide", BOTTOM_TABS_ANIMATE_HIDE)
63
+            .put("tabBadge", BOTTOM_TABS_BADGE);
64
+    }
31 65
 
32
-		JSONObject tabBarJson = new JSONObject();
33
-		tabBarJson.put("currentTabId", "ContainerId");
34
-		tabBarJson.put("currentTabIndex", 1);
35
-		tabBarJson.put("hidden", true);
36
-		tabBarJson.put("animateHide", true);
37
-		tabBarJson.put("tabBadge", 3);
66
+    @NonNull
67
+    private JSONObject createTopBar() throws JSONException {
68
+        return new JSONObject()
69
+            .put("title", TITLE)
70
+            .put("backgroundColor", TOP_BAR_BACKGROUND_COLOR)
71
+            .put("textColor", TOP_BAR_TEXT_COLOR)
72
+            .put("textFontSize", TOP_BAR_FONT_SIZE)
73
+            .put("textFontFamily", TOP_BAR_FONT_FAMILY)
74
+            .put("hidden", TOP_BAR_HIDDEN);
75
+    }
38 76
 
39
-		json.put("tabBar", tabBarJson);
77
+    @NonNull
78
+    private JSONObject createOtherTopBar() throws JSONException {
79
+        return new JSONObject()
80
+                .put("title", TITLE)
81
+                .put("backgroundColor", TOP_BAR_BACKGROUND_COLOR)
82
+                .put("textColor", TOP_BAR_TEXT_COLOR)
83
+                .put("textFontSize", TOP_BAR_FONT_SIZE)
84
+                .put("textFontFamily", TOP_BAR_FONT_FAMILY)
85
+                .put("hidden", TOP_BAR_HIDDEN);
86
+    }
40 87
 
41
-		NavigationOptions result = NavigationOptions.parse(json);
42
-		assertThat(result.topBarOptions.title).isEqualTo("the title");
43
-		assertThat(result.topBarOptions.backgroundColor).isEqualTo(0xff123456);
44
-		assertThat(result.topBarOptions.textColor).isEqualTo(0xff123456);
45
-		assertThat(result.topBarOptions.textFontSize).isEqualTo(18);
46
-		assertThat(result.topBarOptions.textFontFamily).isEqualTo("HelveticaNeue-CondensedBold");
47
-		assertThat(result.topBarOptions.hidden).isEqualTo(True);
48
-		assertThat(result.bottomTabsOptions.animateHide).isEqualTo(True);
49
-		assertThat(result.bottomTabsOptions.hidden).isEqualTo(True);
50
-		assertThat(result.bottomTabsOptions.tabBadge).isEqualTo(3);
51
-		assertThat(result.bottomTabsOptions.currentTabId).isEqualTo("ContainerId");
52
-		assertThat(result.bottomTabsOptions.currentTabIndex).isEqualTo(1);
53
-	}
88
+    @NonNull
89
+    private JSONObject createOtherTabBar() throws JSONException {
90
+        return new JSONObject()
91
+                .put("currentTabId", BOTTOM_TABS_CURRENT_TAB_ID)
92
+                .put("currentTabIndex", BOTTOM_TABS_CURRENT_TAB_INDEX)
93
+                .put("hidden", BOTTOM_TABS_HIDDEN)
94
+                .put("animateHide", BOTTOM_TABS_ANIMATE_HIDE)
95
+                .put("tabBadge", BOTTOM_TABS_BADGE);
96
+    }
97
+
98
+    @Test
99
+    public void mergeDefaultOptions() throws Exception {
100
+        JSONObject json = new JSONObject();
101
+        json.put("topBar", createTopBar());
102
+        json.put("tabBar", createTabBar());
103
+        NavigationOptions defaultOptions = NavigationOptions.parse(json);
104
+        NavigationOptions options = new NavigationOptions();
105
+
106
+        options.mergeWith(defaultOptions);
107
+        assertResult(options);
108
+    }
109
+
110
+    @Test
111
+    public void mergedDefaultOptionsDontOverrideGivenOptions() throws Exception {
112
+        JSONObject defaultJson = new JSONObject()
113
+            .put("topBar", createOtherTopBar())
114
+            .put("tabBar", createOtherTabBar());
115
+        NavigationOptions defaultOptions = NavigationOptions.parse(defaultJson);
116
+
117
+        JSONObject json = new JSONObject()
118
+                .put("topBar", createTopBar())
119
+                .put("tabBar", createTabBar());
120
+        NavigationOptions options = NavigationOptions.parse(json);
121
+        options.withDefaultOptions(defaultOptions);
122
+        assertResult(options);
123
+    }
54 124
 
55 125
 	@Test
56 126
 	public void defaultEmptyOptions() throws Exception {

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

@@ -32,6 +32,10 @@ class Navigation {
32 32
     return this.commands.setRoot(params);
33 33
   }
34 34
 
35
+  setDefaultOptions(options) {
36
+    this.commands.setDefaultOptions(options);
37
+  }
38
+
35 39
   setOptions(containerId, options) {
36 40
     this.commands.setOptions(containerId, options);
37 41
   }

+ 4
- 0
lib/src/adapters/NativeCommandsSender.js View File

@@ -9,6 +9,10 @@ class NativeCommandsSender {
9 9
     return this.nativeCommandsModule.setRoot(layoutTree);
10 10
   }
11 11
 
12
+  setDefaultOptions(options) {
13
+    this.nativeCommandsModule.setDefaultOptions(options);
14
+  }
15
+
12 16
   setOptions(containerId, options) {
13 17
     this.nativeCommandsModule.setOptions(containerId, options);
14 18
   }

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

@@ -15,6 +15,12 @@ class Commands {
15 15
     return this.nativeCommandsSender.setRoot(layout);
16 16
   }
17 17
 
18
+  setDefaultOptions(options) {
19
+    const input = _.cloneDeep(options);
20
+    OptionsProcessor.processOptions(input);
21
+    this.nativeCommandsSender.setDefaultOptions(input);
22
+  }
23
+
18 24
   setOptions(containerId, options) {
19 25
     const input = _.cloneDeep(options);
20 26
     OptionsProcessor.processOptions(input);

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

@@ -77,6 +77,14 @@ describe('Commands', () => {
77 77
     });
78 78
   });
79 79
 
80
+  describe('setDefaultOptions', () => {
81
+    it('deep clones input to avoid mutation errors', () => {
82
+      const obj = { title: 'test' };
83
+      uut.setDefaultOptions(obj);
84
+      expect(mockCommandsSender.setDefaultOptions.mock.calls[0][0]).not.toBe(obj);
85
+    });
86
+  });
87
+
80 88
   describe('showModal', () => {
81 89
     it('sends command to native after parsing into a correct layout tree', () => {
82 90
       uut.showModal({