Browse Source

V2 custom component refactor (#2997)

* pass props for topBar background component

* refactored custom components

* pass props for topBar background component

* refactored custom components

* refactored RNNOptions object

* Use margin to control component position relative to TopBar

RelativeLayout rules caused excessive CPU usage due to issues with RN's
keyboard detection mechanism (based on global layout listener)

* Skip custom transition e2e on Android

* Improved the clarity of the top-level API doc (#2984)

I clarified some of the language here so that it is easier for the beginner to understand. (It was a little bit awkward before)

* Increase gradle daemon process memory

* Disable Orientation test on Android

* Fix SideMenu e2e

* Fix popTo

* Use margin instead of RelativeLayout rules to align component to BottomTabs

Rules caused excessive CPU usage due to RN issue with global layout listener
and keyboard visibility detection.

* Update README.md

for triggering build

* Update README.md

* Consolidate pop and animatePop

* Update top-level-api.md ... more copyediting. (#3002)

Further clarification of some of the top level API stuff.

* popTo animates top screen

* Refactor component options on Android

* Stop processing passProps from options

* removed comments
Yogev B 6 years ago
parent
commit
796397e8e4
39 changed files with 283 additions and 118 deletions
  1. 40
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Component.java
  2. 4
    23
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TitleOptions.java
  3. 5
    10
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarBackgroundOptions.java
  4. 13
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java
  5. 3
    2
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Button.java
  6. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java
  7. 5
    4
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarReactViewController.java
  8. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TopBarButtonController.java
  9. 7
    6
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarBackgroundViewController.java
  10. 7
    5
      lib/android/app/src/main/java/com/reactnativenavigation/views/titlebar/TitleBar.java
  11. 5
    5
      lib/android/app/src/main/java/com/reactnativenavigation/views/topbar/TopBar.java
  12. 1
    4
      lib/android/app/src/test/java/com/reactnativenavigation/parse/OptionsTest.java
  13. 1
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java
  14. 3
    3
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsMergingTest.java
  15. 20
    9
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TitleBarTest.java
  16. 2
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarButtonControllerTest.java
  17. 13
    3
      lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarBackgroundComponentTest.java
  18. 2
    1
      lib/ios/RNNBackgroundOptions.h
  19. 9
    0
      lib/ios/RNNComponentOptions.h
  20. 5
    0
      lib/ios/RNNComponentOptions.m
  21. 1
    1
      lib/ios/RNNNavigationButtons.m
  22. 1
    1
      lib/ios/RNNNavigationOptions.m
  23. 28
    1
      lib/ios/RNNOptions.m
  24. 4
    0
      lib/ios/RNNReactRootViewCreator.m
  25. 7
    8
      lib/ios/RNNRootViewController.m
  26. 3
    1
      lib/ios/RNNRootViewCreator.h
  27. 2
    1
      lib/ios/RNNTitleOptions.h
  28. 2
    1
      lib/ios/RNNTopBarOptions.h
  29. 1
    3
      lib/ios/RNNTopBarOptions.m
  30. 1
    1
      lib/ios/RNNTransitionsOptions.m
  31. 8
    0
      lib/ios/ReactNativeNavigation.xcodeproj/project.pbxproj
  32. 1
    1
      lib/src/commands/Commands.test.ts
  33. 2
    6
      lib/src/commands/Commands.ts
  34. 7
    2
      lib/src/commands/LayoutTreeCrawler.ts
  35. 1
    1
      lib/src/commands/LayoutType.test.ts
  36. 38
    1
      lib/src/commands/OptionsProcessor.test.ts
  37. 13
    2
      lib/src/commands/OptionsProcessor.ts
  38. 13
    4
      playground/src/screens/OptionsScreen.js
  39. 2
    3
      playground/src/screens/TopBarBackground.js

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

@@ -0,0 +1,40 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+import com.reactnativenavigation.parse.params.NullText;
4
+import com.reactnativenavigation.parse.params.Text;
5
+import com.reactnativenavigation.parse.parsers.TextParser;
6
+
7
+import org.json.JSONObject;
8
+
9
+public class Component {
10
+    public static Component parse(JSONObject json) {
11
+        Component result = new Component();
12
+        if (json == null) return result;
13
+
14
+        result.name = TextParser.parse(json, "name");
15
+        result.componentId = TextParser.parse(json, "componentId");
16
+        result.alignment = Alignment.fromString(TextParser.parse(json, "alignment").get(""));
17
+
18
+        return result;
19
+    }
20
+
21
+    public Text name = new NullText();
22
+    public Text componentId = new NullText();
23
+    public Alignment alignment = Alignment.Default;
24
+
25
+    void mergeWith(Component other) {
26
+        if (other.componentId.hasValue()) componentId = other.componentId;
27
+        if (other.name.hasValue()) name = other.name;
28
+        if (other.alignment != Alignment.Default) alignment = other.alignment;
29
+    }
30
+
31
+    public void mergeWithDefault(Component defaultOptions) {
32
+        if (!componentId.hasValue()) componentId = defaultOptions.componentId;
33
+        if (!name.hasValue()) name = defaultOptions.name;
34
+        if (alignment == Alignment.Default) alignment = defaultOptions.alignment;
35
+    }
36
+
37
+    public boolean hasValue() {
38
+        return name.hasValue();
39
+    }
40
+}

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

@@ -2,9 +2,7 @@ package com.reactnativenavigation.parse;
2 2
 
3 3
 import android.graphics.Typeface;
4 4
 import android.support.annotation.Nullable;
5
-import android.util.Log;
6 5
 
7
-import com.reactnativenavigation.BuildConfig;
8 6
 import com.reactnativenavigation.parse.params.Color;
9 7
 import com.reactnativenavigation.parse.params.Fraction;
10 8
 import com.reactnativenavigation.parse.params.NullColor;
@@ -22,19 +20,14 @@ public class TitleOptions {
22 20
 
23 21
     public static TitleOptions parse(TypefaceLoader typefaceManager, JSONObject json) {
24 22
         final TitleOptions options = new TitleOptions();
25
-        if (json == null) {
26
-            return options;
27
-        }
23
+        if (json == null) return options;
28 24
 
25
+        options.component = Component.parse(json.optJSONObject("component"));
29 26
         options.text = TextParser.parse(json, "text");
30 27
         options.color = ColorParser.parse(json, "color");
31 28
         options.fontSize = FractionParser.parse(json, "fontSize");
32 29
         options.fontFamily = typefaceManager.getTypeFace(json.optString("fontFamily", ""));
33 30
         options.alignment = Alignment.fromString(TextParser.parse(json, "alignment").get(""));
34
-        options.component = TextParser.parse(json, "component");
35
-        options.componentAlignment = Alignment.fromString(TextParser.parse(json, "componentAlignment").get(""));
36
-
37
-        validate(options);
38 31
 
39 32
         return options;
40 33
     }
@@ -44,8 +37,7 @@ public class TitleOptions {
44 37
     public Fraction fontSize = new NullFraction();
45 38
     public Alignment alignment = Alignment.Default;
46 39
     @Nullable public Typeface fontFamily;
47
-    public Text component = new NullText();
48
-    public Alignment componentAlignment = Alignment.Default;
40
+    public Component component = new Component();
49 41
 
50 42
     void mergeWith(final TitleOptions other) {
51 43
         if (other.text.hasValue()) text = other.text;
@@ -54,8 +46,6 @@ public class TitleOptions {
54 46
         if (other.fontFamily != null) fontFamily = other.fontFamily;
55 47
         if (other.alignment != Alignment.Default) alignment = other.alignment;
56 48
         if (other.component.hasValue()) component = other.component;
57
-        if (other.componentAlignment != Alignment.Default) componentAlignment = other.componentAlignment;
58
-        validate(this);
59 49
     }
60 50
 
61 51
     void mergeWithDefault(TitleOptions defaultOptions) {
@@ -64,15 +54,6 @@ public class TitleOptions {
64 54
         if (!fontSize.hasValue()) fontSize = defaultOptions.fontSize;
65 55
         if (fontFamily == null) fontFamily = defaultOptions.fontFamily;
66 56
         if (alignment == Alignment.Default) alignment = defaultOptions.alignment;
67
-        if (!component.hasValue()) component = defaultOptions.component;
68
-        if (componentAlignment == Alignment.Default) componentAlignment = defaultOptions.componentAlignment;
69
-        validate(this);
70
-    }
71
-
72
-    private static void validate(TitleOptions options) {
73
-        if (options.component.hasValue() && options.text.hasValue()) {
74
-            if (BuildConfig.DEBUG) Log.w("RNN", "A screen can't use both text and component - clearing text.");
75
-            options.text = new NullText();
76
-        }
57
+        component.mergeWithDefault(defaultOptions.component);
77 58
     }
78 59
 }

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

@@ -2,22 +2,17 @@ package com.reactnativenavigation.parse;
2 2
 
3 3
 import com.reactnativenavigation.parse.params.Color;
4 4
 import com.reactnativenavigation.parse.params.NullColor;
5
-import com.reactnativenavigation.parse.params.NullText;
6
-import com.reactnativenavigation.parse.params.Text;
7 5
 import com.reactnativenavigation.parse.parsers.ColorParser;
8
-import com.reactnativenavigation.parse.parsers.TextParser;
9 6
 
10 7
 import org.json.JSONObject;
11 8
 
12 9
 public class TopBarBackgroundOptions {
13 10
     public static TopBarBackgroundOptions parse(JSONObject json) {
14 11
         TopBarBackgroundOptions options = new TopBarBackgroundOptions();
15
-        if (json == null) {
16
-            return options;
17
-        }
12
+        if (json == null) return options;
18 13
 
19 14
         options.color = ColorParser.parse(json, "color");
20
-        options.component = TextParser.parse(json, "component");
15
+        options.component = Component.parse(json.optJSONObject("component"));
21 16
 
22 17
         if (options.component.hasValue()) {
23 18
             options.color = new Color(android.graphics.Color.TRANSPARENT);
@@ -27,15 +22,15 @@ public class TopBarBackgroundOptions {
27 22
     }
28 23
 
29 24
     public Color color = new NullColor();
30
-    public Text component = new NullText();
25
+    public Component component = new Component();
31 26
 
32 27
     void mergeWith(final TopBarBackgroundOptions other) {
33 28
         if (other.color.hasValue()) color = other.color;
34
-        if (other.component.hasValue()) component = other.component;
29
+        component.mergeWith(other.component);
35 30
     }
36 31
 
37 32
     void mergeWithDefault(TopBarBackgroundOptions defaultOptions) {
38 33
         if (!color.hasValue()) color = defaultOptions.color;
39
-        if (!component.hasValue()) component = defaultOptions.component;
34
+        component.mergeWithDefault(defaultOptions.component);
40 35
     }
41 36
 }

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

@@ -2,7 +2,9 @@ package com.reactnativenavigation.parse;
2 2
 
3 3
 
4 4
 import android.support.annotation.Nullable;
5
+import android.util.Log;
5 6
 
7
+import com.reactnativenavigation.BuildConfig;
6 8
 import com.reactnativenavigation.parse.params.Bool;
7 9
 import com.reactnativenavigation.parse.params.Button;
8 10
 import com.reactnativenavigation.parse.params.NullBool;
@@ -33,6 +35,7 @@ public class TopBarOptions {
33 35
         options.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons"));
34 36
         options.testId = TextParser.parse(json, "testID");
35 37
 
38
+        options.validate();
36 39
         return options;
37 40
     }
38 41
 
@@ -58,6 +61,7 @@ public class TopBarOptions {
58 61
         if (other.drawBehind.hasValue()) drawBehind = other.drawBehind;
59 62
         if (other.leftButtons != null) leftButtons = other.leftButtons;
60 63
         if (other.rightButtons != null) rightButtons = other.rightButtons;
64
+        validate();
61 65
     }
62 66
 
63 67
     void mergeWithDefault(TopBarOptions defaultOptions) {
@@ -71,5 +75,14 @@ public class TopBarOptions {
71 75
         if (leftButtons == null) leftButtons = defaultOptions.leftButtons;
72 76
         if (rightButtons == null) rightButtons = defaultOptions.rightButtons;
73 77
         if (!testId.hasValue()) testId = defaultOptions.testId;
78
+        validate();
79
+    }
80
+
81
+    private void validate() {
82
+        if (title.component.hasValue() && (title.text.hasValue() || subtitle.text.hasValue())) {
83
+            if (BuildConfig.DEBUG) Log.w("RNN", "A screen can't use both text and component - clearing text.");
84
+            title.text = new NullText();
85
+            subtitle.text = new NullText();
86
+        }
74 87
     }
75 88
 }

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

@@ -2,6 +2,7 @@ package com.reactnativenavigation.parse.params;
2 2
 
3 3
 import android.view.MenuItem;
4 4
 
5
+import com.reactnativenavigation.parse.Component;
5 6
 import com.reactnativenavigation.parse.parsers.BoolParser;
6 7
 import com.reactnativenavigation.parse.parsers.ColorParser;
7 8
 import com.reactnativenavigation.parse.parsers.NumberParser;
@@ -23,7 +24,7 @@ public class Button {
23 24
     private Text buttonFontWeight = new NullText();
24 25
     public Text icon = new NullText();
25 26
     public Text testId = new NullText();
26
-    public Text component = new NullText();
27
+    public Component component = new Component();
27 28
 
28 29
     private static Button parseJson(JSONObject json) {
29 30
         Button button = new Button();
@@ -36,7 +37,7 @@ public class Button {
36 37
         button.buttonFontSize = NumberParser.parse(json, "buttonFontSize");
37 38
         button.buttonFontWeight = TextParser.parse(json, "buttonFontWeight");
38 39
         button.testId = TextParser.parse(json, "testID");
39
-        button.component = TextParser.parse(json, "component");
40
+        button.component = Component.parse(json.optJSONObject("component"));
40 41
 
41 42
         if (json.has("icon")) {
42 43
             button.icon = TextParser.parse(json.optJSONObject("icon"), "uri");

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

@@ -47,7 +47,7 @@ public class OptionsPresenter {
47 47
 
48 48
     private void applyTopBarOptions(TopBarOptions options, AnimationsOptions animationOptions, Component component) {
49 49
         topBar.setTitle(options.title.text.get(""));
50
-        if (options.title.component.hasValue()) topBar.setTitleComponent(options.title.component.get(), options.title.componentAlignment);
50
+        if (options.title.component.hasValue()) topBar.setTitleComponent(options.title.component);
51 51
         topBar.setTitleFontSize(options.title.fontSize.get(defaultTitleFontSize));
52 52
         topBar.setTitleTextColor(options.title.color.get(DEFAULT_TITLE_COLOR));
53 53
         topBar.setTitleTypeface(options.title.fontFamily);
@@ -138,7 +138,7 @@ public class OptionsPresenter {
138 138
 
139 139
     private void mergeTopBarOptions(TopBarOptions options, AnimationsOptions animationsOptions, Component component) {
140 140
         if (options.title.text.hasValue()) topBar.setTitle(options.title.text.get());
141
-        if (options.title.component.hasValue()) topBar.setTitleComponent(options.title.component.get(), options.title.componentAlignment);
141
+        if (options.title.component.hasValue()) topBar.setTitleComponent(options.title.component);
142 142
         if (options.title.color.hasValue()) topBar.setTitleTextColor(options.title.color.get());
143 143
         if (options.title.fontSize.hasValue()) topBar.setTitleFontSize(options.title.fontSize.get());
144 144
         if (options.title.fontFamily != null) topBar.setTitleTypeface(options.title.fontFamily);

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

@@ -2,6 +2,7 @@ package com.reactnativenavigation.viewcontrollers;
2 2
 
3 3
 import android.app.Activity;
4 4
 
5
+import com.reactnativenavigation.parse.Component;
5 6
 import com.reactnativenavigation.parse.Options;
6 7
 import com.reactnativenavigation.utils.CompatUtils;
7 8
 import com.reactnativenavigation.views.titlebar.TitleBarReactView;
@@ -10,7 +11,7 @@ import com.reactnativenavigation.views.titlebar.TitleBarReactViewCreator;
10 11
 public class TitleBarReactViewController extends ViewController<TitleBarReactView> {
11 12
 
12 13
     private final TitleBarReactViewCreator reactViewCreator;
13
-    private String componentName;
14
+    private Component component;
14 15
 
15 16
     public TitleBarReactViewController(Activity activity, TitleBarReactViewCreator reactViewCreator) {
16 17
         super(activity, CompatUtils.generateViewId() + "", new Options());
@@ -32,7 +33,7 @@ public class TitleBarReactViewController extends ViewController<TitleBarReactVie
32 33
 
33 34
     @Override
34 35
     protected TitleBarReactView createView() {
35
-        return reactViewCreator.create(getActivity(), getId(), componentName);
36
+        return reactViewCreator.create(getActivity(), getId(), component.name.get());
36 37
     }
37 38
 
38 39
     @Override
@@ -40,7 +41,7 @@ public class TitleBarReactViewController extends ViewController<TitleBarReactVie
40 41
 
41 42
     }
42 43
 
43
-    public void setComponent(String componentName) {
44
-        this.componentName = componentName;
44
+    public void setComponent(Component component) {
45
+        this.component = component;
45 46
     }
46 47
 }

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

@@ -63,7 +63,7 @@ public class TopBarButtonController extends ViewController<TitleBarReactButtonVi
63 63
     @NonNull
64 64
     @Override
65 65
     protected TitleBarReactButtonView createView() {
66
-        view = (TitleBarReactButtonView) viewCreator.create(getActivity(), getId(), button.component.get());
66
+        view = (TitleBarReactButtonView) viewCreator.create(getActivity(), button.component.componentId.get(), button.component.name.get());
67 67
         return (TitleBarReactButtonView) view.asView();
68 68
     }
69 69
 

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

@@ -2,6 +2,7 @@ package com.reactnativenavigation.viewcontrollers.topbar;
2 2
 
3 3
 import android.app.Activity;
4 4
 
5
+import com.reactnativenavigation.parse.Component;
5 6
 import com.reactnativenavigation.parse.Options;
6 7
 import com.reactnativenavigation.utils.CompatUtils;
7 8
 import com.reactnativenavigation.viewcontrollers.ViewController;
@@ -11,21 +12,21 @@ import com.reactnativenavigation.views.topbar.TopBarBackgroundViewCreator;
11 12
 public class TopBarBackgroundViewController extends ViewController<TopBarBackgroundView> {
12 13
 
13 14
     private TopBarBackgroundViewCreator viewCreator;
14
-    private String component;
15
+    private Component component;
15 16
 
16 17
     public TopBarBackgroundViewController(Activity activity, TopBarBackgroundViewCreator viewCreator) {
17 18
         super(activity, CompatUtils.generateViewId() + "", new Options());
18 19
         this.viewCreator = viewCreator;
19 20
     }
20 21
 
21
-    public TopBarBackgroundViewController(TopBarBackgroundViewController topBarBackgroundViewController) {
22
-        super(topBarBackgroundViewController.getActivity(), CompatUtils.generateViewId() + "", new Options());
23
-        this.viewCreator = topBarBackgroundViewController.viewCreator;
22
+    public TopBarBackgroundViewController(TopBarBackgroundViewController controller) {
23
+        super(controller.getActivity(), controller.getId(), controller.options);
24
+        this.viewCreator = controller.viewCreator;
24 25
     }
25 26
 
26 27
     @Override
27 28
     protected TopBarBackgroundView createView() {
28
-        return viewCreator.create(getActivity(), getId(), component);
29
+        return viewCreator.create(getActivity(), component.componentId.get(), component.name.get());
29 30
     }
30 31
 
31 32
     @Override
@@ -45,7 +46,7 @@ public class TopBarBackgroundViewController extends ViewController<TopBarBackgro
45 46
 
46 47
     }
47 48
 
48
-    public void setComponent(String component) {
49
+    public void setComponent(Component component) {
49 50
         this.component = component;
50 51
     }
51 52
 }

+ 7
- 5
lib/android/app/src/main/java/com/reactnativenavigation/views/titlebar/TitleBar.java View File

@@ -10,6 +10,7 @@ import android.view.Gravity;
10 10
 import android.widget.TextView;
11 11
 
12 12
 import com.reactnativenavigation.parse.Alignment;
13
+import com.reactnativenavigation.parse.Component;
13 14
 import com.reactnativenavigation.parse.params.Button;
14 15
 import com.reactnativenavigation.parse.params.Color;
15 16
 import com.reactnativenavigation.utils.UiUtils;
@@ -58,10 +59,11 @@ public class TitleBar extends Toolbar {
58 59
         if (color.hasValue()) setTitleTextColor(color.get());
59 60
     }
60 61
 
61
-    public void setComponent(String componentName, Alignment alignment) {
62
+    public void setComponent(Component component) {
62 63
         clearTitle();
63
-        reactViewController.setComponent(componentName);
64
-        addView(reactViewController.getView(), getComponentLayoutParams(alignment));
64
+        clearSubtitle();
65
+        reactViewController.setComponent(component);
66
+        addView(reactViewController.getView(), getComponentLayoutParams(component));
65 67
     }
66 68
 
67 69
     public void setBackgroundColor(Color color) {
@@ -191,9 +193,9 @@ public class TitleBar extends Toolbar {
191 193
         return new TopBarButtonController((Activity) getContext(), button, buttonCreator, onClickListener);
192 194
     }
193 195
 
194
-    public Toolbar.LayoutParams getComponentLayoutParams(Alignment alignment) {
196
+    public Toolbar.LayoutParams getComponentLayoutParams(Component component) {
195 197
         LayoutParams lp = new LayoutParams(MATCH_PARENT, getHeight());
196
-        if (alignment == Alignment.Center) {
198
+        if (component.alignment == Alignment.Center) {
197 199
             lp.gravity = Gravity.CENTER;
198 200
         }
199 201
         return lp;

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

@@ -19,10 +19,10 @@ import com.reactnativenavigation.anim.TopBarCollapseBehavior;
19 19
 import com.reactnativenavigation.interfaces.ScrollEventListener;
20 20
 import com.reactnativenavigation.parse.Alignment;
21 21
 import com.reactnativenavigation.parse.AnimationOptions;
22
+import com.reactnativenavigation.parse.Component;
22 23
 import com.reactnativenavigation.parse.params.Button;
23 24
 import com.reactnativenavigation.parse.params.Color;
24 25
 import com.reactnativenavigation.parse.params.Number;
25
-import com.reactnativenavigation.parse.params.Text;
26 26
 import com.reactnativenavigation.utils.CompatUtils;
27 27
 import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
28 28
 import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
@@ -119,13 +119,13 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
119 119
         titleBar.setTitleAlignment(alignment);
120 120
     }
121 121
 
122
-    public void setTitleComponent(String componentName, Alignment alignment) {
123
-        titleBar.setComponent(componentName, alignment);
122
+    public void setTitleComponent(Component component) {
123
+        titleBar.setComponent(component);
124 124
     }
125 125
 
126
-    public void setBackgroundComponent(Text component) {
126
+    public void setBackgroundComponent(Component component) {
127 127
         if (component.hasValue()) {
128
-            topBarBackgroundViewController.setComponent(component.get());
128
+            topBarBackgroundViewController.setComponent(component);
129 129
             RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, getHeight());
130 130
             root.addView(topBarBackgroundViewController.getView(), 0, lp);
131 131
         }

+ 1
- 4
lib/android/app/src/test/java/com/reactnativenavigation/parse/OptionsTest.java View File

@@ -41,7 +41,6 @@ public class OptionsTest extends BaseTest {
41 41
     private static final Typeface SUBTITLE_TYPEFACE = Typeface.create("HelveticaNeue-Condensed", Typeface.NORMAL);
42 42
     private static final String SUBTITLE_ALIGNMENT = "center";
43 43
     private static final Typeface TOP_BAR_TYPEFACE = Typeface.create("HelveticaNeue-CondensedBold", Typeface.BOLD);
44
-    private static final String TITLE_ALIGNMENT = "center";
45 44
     private static final Bool TOP_BAR_VISIBLE = new Bool(true);
46 45
     private static final Bool TOP_BAR_DRAW_BEHIND = new Bool(true);
47 46
     private static final Bool TOP_BAR_HIDE_ON_SCROLL = new Bool(true);
@@ -100,7 +99,6 @@ public class OptionsTest extends BaseTest {
100 99
         assertThat(result.fabOptions.hideOnScroll.get()).isEqualTo(FAB_HIDE_ON_SCROLL);
101 100
         assertThat(result.fabOptions.alignVertically.get()).isEqualTo(FAB_ALIGN_VERTICALLY);
102 101
         assertThat(result.fabOptions.alignHorizontally.get()).isEqualTo(FAB_ALIGN_HORIZONTALLY);
103
-        assertThat(result.topBarOptions.title.componentAlignment).isEqualTo(Alignment.Center);
104 102
     }
105 103
 
106 104
     @NonNull
@@ -133,8 +131,7 @@ public class OptionsTest extends BaseTest {
133 131
                 .put("text", "the title")
134 132
                 .put("color", TOP_BAR_TEXT_COLOR)
135 133
                 .put("fontSize", TOP_BAR_FONT_SIZE)
136
-                .put("fontFamily", TOP_BAR_FONT_FAMILY)
137
-                .put("componentAlignment", TITLE_ALIGNMENT);
134
+                .put("fontFamily", TOP_BAR_FONT_FAMILY);
138 135
     }
139 136
 
140 137
     private JSONObject createSubtitle() throws JSONException {

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

@@ -190,7 +190,7 @@ public class OptionsApplyingTest extends BaseTest {
190 190
     @Test
191 191
     public void appliesTopBarComponent() throws Exception {
192 192
         JSONObject json = new JSONObject();
193
-        json.put("component", "someComponent");
193
+        json.put("component", new JSONObject().put("name","someComponent").put("componentId", "id"));
194 194
         uut.options.topBarOptions.background = TopBarBackgroundOptions.parse(json);
195 195
         uut.ensureViewIsCreated();
196 196
         stackController.push(uut);

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

@@ -84,7 +84,8 @@ public class OptionsMergingTest extends BaseTest {
84 84
 
85 85
         TitleOptions titleOptions = new TitleOptions();
86 86
         titleOptions.text = new Text("abc");
87
-        titleOptions.component = new Text("someComponent");
87
+        titleOptions.component.name = new Text("someComponent");
88
+        titleOptions.component.componentId = new Text("compId");
88 89
         titleOptions.color = new Color(0);
89 90
         titleOptions.fontSize = new Fraction(1.0f);
90 91
         titleOptions.fontFamily = Typeface.DEFAULT_BOLD;
@@ -140,11 +141,10 @@ public class OptionsMergingTest extends BaseTest {
140 141
         verify(topBar, times(1)).setTopTabFontFamily(1, Typeface.DEFAULT_BOLD);
141 142
     }
142 143
 
143
-
144 144
     private void assertTopBarOptions(int t) {
145 145
         verify(topBar, times(t)).setTitle(any());
146 146
         verify(topBar, times(t)).setSubtitle(any());
147
-        verify(topBar, times(t)).setTitleComponent(any(), any());
147
+        verify(topBar, times(t)).setTitleComponent(any());
148 148
         verify(topBar, times(t)).setBackgroundColor(any());
149 149
         verify(topBar, times(t)).setTitleTextColor(anyInt());
150 150
         verify(topBar, times(t)).setTitleFontSize(anyFloat());

+ 20
- 9
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TitleBarTest.java View File

@@ -9,6 +9,7 @@ import com.reactnativenavigation.BaseTest;
9 9
 import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
10 10
 import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
11 11
 import com.reactnativenavigation.parse.Alignment;
12
+import com.reactnativenavigation.parse.Component;
12 13
 import com.reactnativenavigation.parse.params.Button;
13 14
 import com.reactnativenavigation.parse.params.Text;
14 15
 import com.reactnativenavigation.react.ReactView;
@@ -60,7 +61,7 @@ public class TitleBarTest extends BaseTest {
60 61
     private void createButtons() {
61 62
         leftButton = new Button();
62 63
         leftButton.id = "back";
63
-        leftButton.title = new Text("jfjf");
64
+        leftButton.title = new Text("abc");
64 65
 
65 66
         textButton = new Button();
66 67
         textButton.id = "textButton";
@@ -68,7 +69,8 @@ public class TitleBarTest extends BaseTest {
68 69
 
69 70
         customButton = new Button();
70 71
         customButton.id = "customBtn";
71
-        customButton.component = new Text("com.rnn.customBtn");
72
+        customButton.component.name = new Text("com.rnn.customBtn");
73
+        customButton.component.componentId = new Text("component4");
72 74
     }
73 75
 
74 76
     @Test
@@ -83,7 +85,7 @@ public class TitleBarTest extends BaseTest {
83 85
         uut.setLeftButtons(leftButton(leftButton));
84 86
         uut.setRightButtons(rightButtons(customButton));
85 87
         ReactView btnView = (ReactView) uut.getMenu().getItem(0).getActionView();
86
-        assertThat(btnView.getComponentName()).isEqualTo(customButton.component.get());
88
+        assertThat(btnView.getComponentName()).isEqualTo(customButton.component.name.get());
87 89
     }
88 90
 
89 91
     @Test
@@ -144,14 +146,22 @@ public class TitleBarTest extends BaseTest {
144 146
 
145 147
     @Test
146 148
     public void setComponent_addsComponentToTitleBar() {
147
-        uut.setComponent("com.rnn.CustomView", Alignment.Center);
149
+        uut.setComponent(component("com.rnn.CustomView", Alignment.Center));
148 150
         verify(uut, times(1)).addView(any(TitleBarReactView.class), any(Toolbar.LayoutParams.class));
149 151
     }
150 152
 
153
+    private Component component(String name, Alignment alignment) {
154
+        Component component = new Component();
155
+        component.name = new Text(name);
156
+        component.alignment = alignment;
157
+        return component;
158
+    }
159
+
151 160
     @Test
152 161
     public void setComponent_alignFill() {
153
-        uut.setComponent("com.rnn.CustomView", Alignment.Fill);
154
-        verify(uut, times(1)).getComponentLayoutParams(Alignment.Fill);
162
+        Component component = component("com.rnn.CustomView", Alignment.Fill);
163
+        uut.setComponent(component);
164
+        verify(uut, times(1)).getComponentLayoutParams(component);
155 165
         ArgumentCaptor<Toolbar.LayoutParams> lpCaptor = ArgumentCaptor.forClass(Toolbar.LayoutParams.class);
156 166
         verify(uut, times(1)).addView(any(TitleBarReactView.class), lpCaptor.capture());
157 167
         assertThat(lpCaptor.getValue().width == ViewGroup.LayoutParams.MATCH_PARENT);
@@ -159,8 +169,9 @@ public class TitleBarTest extends BaseTest {
159 169
 
160 170
     @Test
161 171
     public void setComponent_alignCenter() {
162
-        uut.setComponent("com.rnn.CustomView", Alignment.Center);
163
-        verify(uut, times(1)).getComponentLayoutParams(Alignment.Center);
172
+        Component component = component("com.rnn.CustomView", Alignment.Center);
173
+        uut.setComponent(component);
174
+        verify(uut, times(1)).getComponentLayoutParams(component);
164 175
         ArgumentCaptor<Toolbar.LayoutParams> lpCaptor = ArgumentCaptor.forClass(Toolbar.LayoutParams.class);
165 176
         verify(uut, times(1)).addView(any(TitleBarReactView.class), lpCaptor.capture());
166 177
         assertThat(lpCaptor.getValue().width == ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -169,7 +180,7 @@ public class TitleBarTest extends BaseTest {
169 180
 
170 181
     @Test
171 182
     public void clear() {
172
-        uut.setComponent("someComponent", Alignment.Center);
183
+        uut.setComponent(component("someComponent", Alignment.Center));
173 184
         uut.clear();
174 185
         assertThat(uut.getTitle()).isNullOrEmpty();
175 186
         assertThat(uut.getMenu().size()).isZero();

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

@@ -55,7 +55,8 @@ public class TopBarButtonControllerTest extends BaseTest {
55 55
     private Button createButton() {
56 56
         Button button = new Button();
57 57
         button.id = "btnId";
58
-        button.component = new Text("com.example.customBtn");
58
+        button.component.name = new Text("com.example.customBtn");
59
+        button.component.componentId = new Text("component666");
59 60
         return button;
60 61
     }
61 62
 }

+ 13
- 3
lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarBackgroundComponentTest.java View File

@@ -9,6 +9,7 @@ import com.reactnativenavigation.R;
9 9
 import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
10 10
 import com.reactnativenavigation.mocks.TopBarBackgroundViewCreatorMock;
11 11
 import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
12
+import com.reactnativenavigation.parse.Component;
12 13
 import com.reactnativenavigation.parse.params.Text;
13 14
 import com.reactnativenavigation.utils.ViewUtils;
14 15
 import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
@@ -47,7 +48,10 @@ public class TopBarBackgroundComponentTest extends BaseTest {
47 48
     @Test
48 49
     public void setBackgroundComponent() {
49 50
         uut.getLayoutParams().height = 100;
50
-        uut.setBackgroundComponent(new Text("someComponent"));
51
+        Component component = new Component();
52
+        component.name = new Text("someComponent");
53
+        component.componentId = new Text("id");
54
+        uut.setBackgroundComponent(component);
51 55
         TopBarBackgroundView background = (TopBarBackgroundView) ViewUtils.findChildrenByClassRecursive(uut, TopBarBackgroundView.class).get(0);
52 56
         assertThat(background).isNotNull();
53 57
         assertThat(background.getLayoutParams().width).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT);
@@ -56,13 +60,19 @@ public class TopBarBackgroundComponentTest extends BaseTest {
56 60
 
57 61
     @Test
58 62
     public void setBackgroundComponent_doesNotSetIfNoComponentIsDefined() {
59
-        uut.setBackgroundComponent(new Text("someComponent"));
63
+        Component component = new Component();
64
+        component.name = new Text("someComponent");
65
+        component.componentId = new Text("id");
66
+        uut.setBackgroundComponent(component);
60 67
         assertThat(uut.findViewById(R.id.topBarBackgroundComponent)).isNull();
61 68
     }
62 69
 
63 70
     @Test
64 71
     public void clear_componentIsDestroyed() {
65
-        uut.setBackgroundComponent(new Text("someComponent"));
72
+        Component component = new Component();
73
+        component.name = new Text("someComponent");
74
+        component.componentId = new Text("id");
75
+        uut.setBackgroundComponent(component);
66 76
         uut.clear();
67 77
         verify(topBarBackgroundViewController, times(1)).destroy();
68 78
     }

+ 2
- 1
lib/ios/RNNBackgroundOptions.h View File

@@ -1,8 +1,9 @@
1 1
 #import "RNNOptions.h"
2
+#import "RNNComponentOptions.h"
2 3
 
3 4
 @interface RNNBackgroundOptions : RNNOptions
4 5
 
5 6
 @property (nonatomic, strong) NSNumber* color;
6
-@property (nonatomic, strong) NSString* component;
7
+@property (nonatomic, strong) RNNComponentOptions* component;
7 8
 
8 9
 @end

+ 9
- 0
lib/ios/RNNComponentOptions.h View File

@@ -0,0 +1,9 @@
1
+#import "RNNOptions.h"
2
+
3
+@interface RNNComponentOptions : RNNOptions
4
+
5
+@property (nonatomic, strong) NSString* name;
6
+@property (nonatomic, strong) NSString* componentId;
7
+@property (nonatomic, strong) NSString* alignment;
8
+
9
+@end

+ 5
- 0
lib/ios/RNNComponentOptions.m View File

@@ -0,0 +1,5 @@
1
+#import "RNNComponentOptions.h"
2
+
3
+@implementation RNNComponentOptions
4
+
5
+@end

+ 1
- 1
lib/ios/RNNNavigationButtons.m View File

@@ -54,7 +54,7 @@
54 54
 -(RNNUIBarButtonItem*)buildButton: (NSDictionary*)dictionary {
55 55
 	NSString* buttonId = dictionary[@"id"];
56 56
 	NSString* title = dictionary[@"title"];
57
-	NSString* component = dictionary[@"component"];
57
+	NSString* component = dictionary[@"component"][@"name"];
58 58
 	
59 59
 	if (!buttonId) {
60 60
 		@throw [NSException exceptionWithName:@"NSInvalidArgumentException" reason:[@"button id is not specified " stringByAppendingString:title] userInfo:nil];

+ 1
- 1
lib/ios/RNNNavigationOptions.m View File

@@ -36,7 +36,7 @@ const NSInteger TOP_BAR_TRANSPARENT_TAG = 78264803;
36 36
 	self.animated = [options objectForKey:@"animated"];
37 37
 	self.customTransition = [[RNNAnimationOptions alloc] initWithDict:[options objectForKey:@"customTransition"]];
38 38
 	self.animations = [[RNNTransitionsOptions alloc] initWithDict:[options objectForKey:@"animations"]];
39
-	
39
+
40 40
 	return self;
41 41
 }
42 42
 

+ 28
- 1
lib/ios/RNNOptions.m View File

@@ -1,10 +1,13 @@
1 1
 
2 2
 #import "RNNOptions.h"
3
+#import <objc/runtime.h>
3 4
 
4 5
 @implementation RNNOptions
5 6
 
6
--(instancetype)initWithDict:(NSDictionary *)dict {
7
+-(instancetype)	initWithDict:(NSDictionary *)dict {
7 8
 	self = [super init];
9
+	[self initializeOptionsPropertiesWithDict:dict];
10
+	
8 11
 	[self mergeWith:dict];
9 12
 	return self;
10 13
 }
@@ -44,4 +47,28 @@
44 47
 	return [self respondsToSelector:NSSelectorFromString(propName)];
45 48
 }
46 49
 
50
+- (void)initializeOptionsPropertiesWithDict:(NSDictionary*)dict {
51
+	unsigned int count;
52
+	objc_property_t* props = class_copyPropertyList([self class], &count);
53
+	for (int i = 0; i < count; i++) {
54
+		objc_property_t property = props[i];
55
+		NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
56
+		const char * type = property_getAttributes(property);
57
+		NSString * typeString = [NSString stringWithUTF8String:type];
58
+		NSArray * attributes = [typeString componentsSeparatedByString:@","];
59
+		NSString * typeAttribute = [attributes objectAtIndex:0];
60
+		
61
+		if ([typeAttribute hasPrefix:@"T@"] && [typeAttribute length] > 3) {
62
+			NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)];
63
+			Class typeClass = NSClassFromString(typeClassName);
64
+			if ([typeClass isSubclassOfClass:[RNNOptions class]]) {
65
+				RNNOptions* value = [[typeClass alloc] initWithDict:dict[propertyName]];
66
+				[self setValue:value forKey:propertyName];
67
+			}
68
+		}
69
+		
70
+	}
71
+	free(props);
72
+}
73
+
47 74
 @end

+ 4
- 0
lib/ios/RNNReactRootViewCreator.m View File

@@ -34,4 +34,8 @@
34 34
 	return view;
35 35
 }
36 36
 
37
+-(UIView*)createRootViewFromComponentOptions:(RNNComponentOptions*)componentOptions {
38
+	return [self createRootView:componentOptions.name rootViewId:componentOptions.componentId];
39
+}
40
+
37 41
 @end

+ 7
- 8
lib/ios/RNNRootViewController.m View File

@@ -44,7 +44,6 @@
44 44
 -(void)viewWillAppear:(BOOL)animated{
45 45
 	[super viewWillAppear:animated];
46 46
 	[self.options applyOn:self];
47
-	[self optionsUpdated];
48 47
 }
49 48
 
50 49
 -(void)viewDidAppear:(BOOL)animated {
@@ -76,10 +75,10 @@
76 75
 }
77 76
 
78 77
 - (void)setCustomNavigationTitleView {
79
-	if (self.options.topBar.title.component) {
80
-		RCTRootView *reactView = (RCTRootView*)[_creator createRootView:self.options.topBar.title.component rootViewId:self.options.topBar.title.component];
78
+	if (self.options.topBar.title.component.name) {
79
+		RCTRootView *reactView = (RCTRootView*)[_creator createRootViewFromComponentOptions:self.options.topBar.title.component];
81 80
 
82
-		RNNCustomTitleView *titleView = [[RNNCustomTitleView alloc] initWithFrame:self.navigationController.navigationBar.bounds subView:reactView alignment:self.options.topBar.title.componentAlignment];
81
+		RNNCustomTitleView *titleView = [[RNNCustomTitleView alloc] initWithFrame:self.navigationController.navigationBar.bounds subView:reactView alignment:self.options.topBar.title.component.alignment];
83 82
         reactView.backgroundColor = UIColor.clearColor;
84 83
 		titleView.backgroundColor = UIColor.clearColor;
85 84
 		self.navigationItem.titleView = titleView;
@@ -89,8 +88,8 @@
89 88
 }
90 89
 
91 90
 - (void)setCustomNavigationBarView {
92
-	if (self.options.topBar.componentName) {
93
-		RCTRootView *reactView = (RCTRootView*)[_creator createRootView:self.options.topBar.componentName rootViewId:@"navBar"];
91
+	if (self.options.topBar.component.name) {
92
+		RCTRootView *reactView = (RCTRootView*)[_creator createRootViewFromComponentOptions:self.options.topBar.component];
94 93
 
95 94
 		RNNCustomTitleView *titleView = [[RNNCustomTitleView alloc] initWithFrame:self.navigationController.navigationBar.bounds subView:reactView alignment:@"fill"];
96 95
 		reactView.backgroundColor = UIColor.clearColor;
@@ -102,8 +101,8 @@
102 101
 }
103 102
 
104 103
 - (void)setCustomNavigationComponentBackground {
105
-	if (self.options.topBar.background.component) {
106
-		RCTRootView *reactView = (RCTRootView*)[_creator createRootView:self.options.topBar.background.component rootViewId:@"navBarBackground"];
104
+	if (self.options.topBar.background.component.name) {
105
+		RCTRootView *reactView = (RCTRootView*)[_creator createRootViewFromComponentOptions:self.options.topBar.background.component];
107 106
 
108 107
 		RNNCustomTitleView *titleView = [[RNNCustomTitleView alloc] initWithFrame:self.navigationController.navigationBar.bounds subView:reactView alignment:@"fill"];
109 108
 		[self.navigationController.navigationBar insertSubview:titleView atIndex:1];

+ 3
- 1
lib/ios/RNNRootViewCreator.h View File

@@ -1,10 +1,12 @@
1 1
 
2 2
 #import <UIKit/UIKit.h>
3
-
3
+#import "RNNComponentOptions.h"
4 4
 
5 5
 @protocol RNNRootViewCreator
6 6
 
7 7
 -(UIView*)createRootView:(NSString*)name rootViewId:(NSString*)rootViewId;
8 8
 
9
+-(UIView*)createRootViewFromComponentOptions:(RNNComponentOptions*)componentOptions;
10
+
9 11
 @end
10 12
 

+ 2
- 1
lib/ios/RNNTitleOptions.h View File

@@ -1,5 +1,6 @@
1 1
 #import "RNNOptions.h"
2 2
 #import "RNNSubtitleOptions.h"
3
+#import "RNNComponentOptions.h"
3 4
 
4 5
 @interface RNNTitleOptions : RNNOptions
5 6
 
@@ -8,7 +9,7 @@
8 9
 @property (nonatomic, strong) NSNumber* color;
9 10
 @property (nonatomic, strong) NSString* fontFamily;
10 11
 
11
-@property (nonatomic, strong) NSString* component;
12
+@property (nonatomic, strong) RNNComponentOptions* component;
12 13
 @property (nonatomic, strong) NSString* componentAlignment;
13 14
 
14 15
 @property (nonatomic, strong) RNNSubtitleOptions* subtitle;

+ 2
- 1
lib/ios/RNNTopBarOptions.h View File

@@ -2,6 +2,7 @@
2 2
 #import "RNNTitleOptions.h"
3 3
 #import "RNNSubtitleOptions.h"
4 4
 #import "RNNBackgroundOptions.h"
5
+#import "RNNComponentOptions.h"
5 6
 
6 7
 @interface RNNTopBarOptions : RNNOptions
7 8
 
@@ -26,6 +27,6 @@
26 27
 @property (nonatomic, strong) NSString* backButtonTitle;
27 28
 @property (nonatomic, strong) NSNumber* hideBackButtonTitle;
28 29
 
29
-@property (nonatomic, strong) NSString* componentName;
30
+@property (nonatomic, strong) RNNComponentOptions* component;
30 31
 
31 32
 @end

+ 1
- 3
lib/ios/RNNTopBarOptions.m View File

@@ -15,9 +15,7 @@ extern const NSInteger BLUR_TOPBAR_TAG;
15 15
 
16 16
 - (instancetype)initWithDict:(NSDictionary *)dict {
17 17
 	self = [super initWithDict:dict];
18
-	self.title = [[RNNTitleOptions alloc] initWithDict:dict[@"title"]];
19
-	self.subtitle = [[RNNSubtitleOptions alloc] initWithDict:dict[@"subtitle"]];
20
-	self.background = [[RNNBackgroundOptions alloc] initWithDict:dict[@"background"]];
18
+	
21 19
 	self.title.subtitle = self.subtitle;
22 20
 	
23 21
 	return self;

+ 1
- 1
lib/ios/RNNTransitionsOptions.m View File

@@ -4,7 +4,7 @@
4 4
 
5 5
 - (instancetype)initWithDict:(NSDictionary *)dict {
6 6
 	self = [super init];
7
-
7
+	
8 8
 	[self mergeWith:dict];
9 9
 	
10 10
 	return self;

+ 8
- 0
lib/ios/ReactNativeNavigation.xcodeproj/project.pbxproj View File

@@ -63,6 +63,8 @@
63 63
 		390AD478200F499D00A8250D /* RNNSwizzles.m in Sources */ = {isa = PBXBuildFile; fileRef = 390AD476200F499D00A8250D /* RNNSwizzles.m */; };
64 64
 		5016E8EF20209690009D4F7C /* RNNCustomTitleView.h in Headers */ = {isa = PBXBuildFile; fileRef = 5016E8ED2020968F009D4F7C /* RNNCustomTitleView.h */; };
65 65
 		5016E8F020209690009D4F7C /* RNNCustomTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5016E8EE2020968F009D4F7C /* RNNCustomTitleView.m */; };
66
+		50175CD1207A2AA1004FE91B /* RNNComponentOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 50175CCF207A2AA1004FE91B /* RNNComponentOptions.h */; };
67
+		50175CD2207A2AA1004FE91B /* RNNComponentOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 50175CD0207A2AA1004FE91B /* RNNComponentOptions.m */; };
66 68
 		50415CBA20553B8E00BB682E /* RNNScreenTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 50415CB820553B8E00BB682E /* RNNScreenTransition.h */; };
67 69
 		50415CBB20553B8E00BB682E /* RNNScreenTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 50415CB920553B8E00BB682E /* RNNScreenTransition.m */; };
68 70
 		50451D052042DAEB00695F00 /* RNNPushAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 50451D032042DAEB00695F00 /* RNNPushAnimation.h */; };
@@ -256,6 +258,8 @@
256 258
 		390AD476200F499D00A8250D /* RNNSwizzles.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNSwizzles.m; sourceTree = "<group>"; };
257 259
 		5016E8ED2020968F009D4F7C /* RNNCustomTitleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNCustomTitleView.h; sourceTree = "<group>"; };
258 260
 		5016E8EE2020968F009D4F7C /* RNNCustomTitleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNCustomTitleView.m; sourceTree = "<group>"; };
261
+		50175CCF207A2AA1004FE91B /* RNNComponentOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNComponentOptions.h; sourceTree = "<group>"; };
262
+		50175CD0207A2AA1004FE91B /* RNNComponentOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNComponentOptions.m; sourceTree = "<group>"; };
259 263
 		50415CB820553B8E00BB682E /* RNNScreenTransition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNScreenTransition.h; sourceTree = "<group>"; };
260 264
 		50415CB920553B8E00BB682E /* RNNScreenTransition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNScreenTransition.m; sourceTree = "<group>"; };
261 265
 		50451D032042DAEB00695F00 /* RNNPushAnimation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNPushAnimation.h; sourceTree = "<group>"; };
@@ -491,6 +495,8 @@
491 495
 				504AFE631FFE53070076E904 /* RNNOptions.m */,
492 496
 				E83BAD691F27362500A9F3DD /* RNNNavigationOptions.h */,
493 497
 				E83BAD6A1F27363A00A9F3DD /* RNNNavigationOptions.m */,
498
+				50175CCF207A2AA1004FE91B /* RNNComponentOptions.h */,
499
+				50175CD0207A2AA1004FE91B /* RNNComponentOptions.m */,
494 500
 				A7626BFE1FC2FB6700492FB8 /* RNNTopBarOptions.h */,
495 501
 				A7626BFC1FC2FB2C00492FB8 /* RNNTopBarOptions.m */,
496 502
 				50570B242061473D006A1B5C /* RNNTitleOptions.h */,
@@ -794,6 +800,7 @@
794 800
 				E8A5CD521F464F0400E89D0D /* RNNAnimator.h in Headers */,
795 801
 				50451D0D2042F70900695F00 /* RNNTransition.h in Headers */,
796 802
 				507F43F81FF525B500D9425B /* RNNSegmentedControl.h in Headers */,
803
+				50175CD1207A2AA1004FE91B /* RNNComponentOptions.h in Headers */,
797 804
 			);
798 805
 			runOnlyForDeploymentPostprocessing = 0;
799 806
 		};
@@ -933,6 +940,7 @@
933 940
 				261F0E651E6EC94900989DE2 /* RNNModalManager.m in Sources */,
934 941
 				E8A5CD631F49114F00E89D0D /* RNNElement.m in Sources */,
935 942
 				504AFE751FFFF0540076E904 /* RNNTopTabsOptions.m in Sources */,
943
+				50175CD2207A2AA1004FE91B /* RNNComponentOptions.m in Sources */,
936 944
 				7BEF0D1D1E43771B003E96B0 /* RNNLayoutNode.m in Sources */,
937 945
 				7BA500781E254908001B9E1B /* RNNSplashScreen.m in Sources */,
938 946
 				504AFE651FFE53070076E904 /* RNNOptions.m in Sources */,

+ 1
- 1
lib/src/commands/Commands.test.ts View File

@@ -314,7 +314,7 @@ describe('Commands', () => {
314 314
     beforeEach(() => {
315 315
       cb = jest.fn();
316 316
       const mockParser = { parse: () => 'parsed' };
317
-      const mockCrawler = { crawl: (x) => x };
317
+      const mockCrawler = { crawl: (x) => x, processOptions: (x) => x };
318 318
       commandsObserver.register(cb);
319 319
       uut = new Commands(mockCommandsSender, mockParser, mockCrawler, commandsObserver);
320 320
     });

+ 2
- 6
lib/src/commands/Commands.ts View File

@@ -1,16 +1,12 @@
1 1
 import * as _ from 'lodash';
2
-import { OptionsProcessor } from './OptionsProcessor';
3 2
 import { CommandsObserver } from '../events/CommandsObserver';
4 3
 
5 4
 export class Commands {
6
-  private optionsProcessor: OptionsProcessor;
7
-
8 5
   constructor(
9 6
     private readonly nativeCommandsSender,
10 7
     private readonly layoutTreeParser,
11 8
     private readonly layoutTreeCrawler,
12 9
     private readonly commandsObserver: CommandsObserver) {
13
-    this.optionsProcessor = new OptionsProcessor(this.layoutTreeCrawler.store);
14 10
   }
15 11
 
16 12
   public setRoot(simpleApi) {
@@ -25,7 +21,7 @@ export class Commands {
25 21
 
26 22
   public setDefaultOptions(options) {
27 23
     const input = _.cloneDeep(options);
28
-    this.optionsProcessor.processOptions(input);
24
+    this.layoutTreeCrawler.processOptions(input);
29 25
 
30 26
     this.nativeCommandsSender.setDefaultOptions(input);
31 27
     this.commandsObserver.notify('setDefaultOptions', { options });
@@ -33,7 +29,7 @@ export class Commands {
33 29
 
34 30
   public setOptions(componentId, options) {
35 31
     const input = _.cloneDeep(options);
36
-    this.optionsProcessor.processOptions(input);
32
+    this.layoutTreeCrawler.processOptions(input);
37 33
 
38 34
     this.nativeCommandsSender.setOptions(componentId, input);
39 35
     this.commandsObserver.notify('setOptions', { componentId, options });

+ 7
- 2
lib/src/commands/LayoutTreeCrawler.ts View File

@@ -20,7 +20,8 @@ export class LayoutTreeCrawler {
20 20
     private readonly uniqueIdProvider: any,
21 21
     public readonly store: any) {
22 22
     this.crawl = this.crawl.bind(this);
23
-    this.optionsProcessor = new OptionsProcessor(store);
23
+    this.processOptions = this.processOptions.bind(this);
24
+    this.optionsProcessor = new OptionsProcessor(store, uniqueIdProvider);
24 25
   }
25 26
 
26 27
   crawl(node: LayoutNode): void {
@@ -31,10 +32,14 @@ export class LayoutTreeCrawler {
31 32
     if (node.type === LayoutType.Component) {
32 33
       this._handleComponent(node);
33 34
     }
34
-    this.optionsProcessor.processOptions(node.data.options);
35
+    this.processOptions(node.data.options);
35 36
     _.forEach(node.children, this.crawl);
36 37
   }
37 38
 
39
+  processOptions(options) {
40
+    this.optionsProcessor.processOptions(options);
41
+  }
42
+
38 43
   _handleComponent(node) {
39 44
     this._assertComponentDataName(node);
40 45
     this._savePropsToStore(node);

+ 1
- 1
lib/src/commands/LayoutType.test.ts View File

@@ -1,6 +1,6 @@
1 1
 import { LayoutType, isLayoutType } from './LayoutType';
2 2
 
3
-describe.only('LayoutType', () => {
3
+describe('LayoutType', () => {
4 4
   it('is an enum', () => {
5 5
     expect(LayoutType.Component).toEqual('Component');
6 6
     expect(LayoutType.Stack).toEqual('Stack');

+ 38
- 1
lib/src/commands/OptionsProcessor.test.ts View File

@@ -1,5 +1,7 @@
1 1
 import { OptionsProcessor } from './OptionsProcessor';
2
+import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
2 3
 import { Store } from '../components/Store';
4
+import * as _ from 'lodash';
3 5
 
4 6
 describe('navigation options', () => {
5 7
   let uut: OptionsProcessor;
@@ -8,7 +10,7 @@ describe('navigation options', () => {
8 10
   beforeEach(() => {
9 11
     options = {};
10 12
     store = new Store();
11
-    uut = new OptionsProcessor(store);
13
+    uut = new OptionsProcessor(store, new UniqueIdProvider());
12 14
   });
13 15
 
14 16
   it('processes colors into numeric AARRGGBB', () => {
@@ -111,6 +113,41 @@ describe('navigation options', () => {
111 113
     expect(store.getPropsForId('1')).toEqual(passProps);
112 114
   });
113 115
 
116
+  it('passProps for custom component', () => {
117
+    const passProps = { color: '#ff0000', some: 'thing' };
118
+    options.component = { passProps, name: 'a' };
119
+
120
+    uut.processOptions({ o: options });
121
+
122
+    expect(store.getPropsForId(options.component.componentId)).toEqual(passProps);
123
+    expect(Object.keys(options.component)).not.toContain('passProps');
124
+  });
125
+
126
+  it('generate component id for component in options', () => {
127
+    options.component = { name: 'a' };
128
+
129
+    uut.processOptions({ o: options });
130
+
131
+    expect(options.component.componentId).toBeDefined();
132
+  });
133
+
134
+  it('passProps from options are not processed', () => {
135
+    const passProps = { color: '#ff0000', some: 'thing' };
136
+    const clonedProps = _.cloneDeep(passProps);
137
+    options.component = { passProps, name: 'a' };
138
+
139
+    uut.processOptions(options);
140
+    expect(store.getPropsForId(options.component.componentId)).toEqual(clonedProps);
141
+  });
142
+
143
+  it('pass supplied componentId for component in options', () => {
144
+    options.component = { name: 'a', id: 'Component1' };
145
+
146
+    uut.processOptions({ o: options });
147
+
148
+    expect(options.component.componentId).toEqual('Component1');
149
+  });
150
+
114 151
   it('passProps must be with id next to it', () => {
115 152
     const passProps = { prop: 'prop' };
116 153
     options.rightButtons = [{ passProps }];

+ 13
- 2
lib/src/commands/OptionsProcessor.ts View File

@@ -3,17 +3,18 @@ import { processColor } from 'react-native';
3 3
 import * as resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
4 4
 
5 5
 export class OptionsProcessor {
6
-  constructor(public store) { }
6
+  constructor(public store, public uniqueIdProvider) { }
7 7
 
8 8
   public processOptions(options) {
9 9
     _.forEach(options, (value, key) => {
10 10
       if (!value) { return; }
11 11
 
12
+      this.processComponent(key, value, options);
12 13
       this.processColor(key, value, options);
13 14
       this.processImage(key, value, options);
14 15
       this.processButtonsPassProps(key, value);
15 16
 
16
-      if (_.isObject(value) || _.isArray(value)) {
17
+      if (!_.isEqual(key, 'passProps') && (_.isObject(value) || _.isArray(value))) {
17 18
         this.processOptions(value);
18 19
       }
19 20
     });
@@ -40,4 +41,14 @@ export class OptionsProcessor {
40 41
       });
41 42
     }
42 43
   }
44
+
45
+  private processComponent(key, value, options) {
46
+    if (_.isEqual(key, 'component')) {
47
+      value.componentId = value.id ? value.id : this.uniqueIdProvider.generate('CustomComponent');
48
+      if (value.passProps) {
49
+        this.store.setPropsForId(value.componentId, value.passProps);
50
+      }
51
+      options[key] = _.omit(value, 'passProps');
52
+    }
53
+  }
43 54
 }

+ 13
- 4
playground/src/screens/OptionsScreen.js View File

@@ -32,7 +32,12 @@ class OptionsScreen extends Component {
32 32
           alignment: 'center'
33 33
         },
34 34
         background: {
35
-          component: 'TopBarBackground'
35
+          component: {
36
+            name: 'TopBarBackground',
37
+            passProps: {
38
+              color: '#bbdefb'
39
+            }
40
+          }
36 41
         },
37 42
         ...Platform.select({
38 43
           android: { drawBehind: true },
@@ -49,7 +54,9 @@ class OptionsScreen extends Component {
49 54
           {
50 55
             id: CUSTOM_BUTTON2,
51 56
             testID: CUSTOM_BUTTON2,
52
-            component: 'CustomRoundedButton'
57
+            component: {
58
+              name: 'CustomRoundedButton'
59
+            }
53 60
           },
54 61
           {
55 62
             id: BUTTON_ONE,
@@ -263,8 +270,10 @@ class OptionsScreen extends Component {
263 270
     Navigation.setOptions(this.props.componentId, {
264 271
       topBar: {
265 272
         title: {
266
-          component: 'navigation.playground.CustomTopBar',
267
-          componentAlignment: 'center'
273
+          component: {
274
+            name: 'navigation.playground.CustomTopBar',
275
+            alignment: 'center'
276
+          }
268 277
         }
269 278
       }
270 279
     });

+ 2
- 3
playground/src/screens/TopBarBackground.js View File

@@ -10,7 +10,7 @@ class TopBarBackground extends Component {
10 10
   constructor(props) {
11 11
     super(props);
12 12
     this.state = {};
13
-    this.dots = new Array(55).fill('').map((ignored, i) => <View key={'dot' + i} style={styles.dot}/>);
13
+    this.dots = new Array(55).fill('').map((ignored, i) => <View key={'dot' + i} style={[styles.dot, {backgroundColor: this.props.color}]}/>);
14 14
   }
15 15
 
16 16
   componentDidAppear() {
@@ -51,7 +51,6 @@ const styles = StyleSheet.create({
51 51
     height: 16,
52 52
     width: 16,
53 53
     borderRadius: 8,
54
-    margin: 4,
55
-    backgroundColor: '#bbdefb'
54
+    margin: 4
56 55
   }
57 56
 });