Browse Source

External component (#2846)

* External Component implementation

* Limit External component e2e to Android for now
Guy Carmeli 6 years ago
parent
commit
4c8742f7bd
No account linked to committer's email address
34 changed files with 441 additions and 84 deletions
  1. 3
    3
      e2e/ScreenStack.test.js
  2. 6
    11
      lib/android/app/src/main/java/com/reactnativenavigation/NavigationActivity.java
  3. 20
    0
      lib/android/app/src/main/java/com/reactnativenavigation/NavigationApplication.java
  4. 28
    9
      lib/android/app/src/main/java/com/reactnativenavigation/parse/ExternalComponent.java
  5. 20
    2
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java
  6. 1
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutNode.java
  7. 0
    5
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java
  8. 0
    1
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/FabOptionsPresenter.java
  9. 9
    6
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java
  10. 18
    6
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java
  11. 3
    3
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java
  12. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/SideMenuController.java
  13. 5
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java
  14. 3
    3
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  15. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabFinder.java
  16. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java
  17. 7
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponent.java
  18. 9
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentCreator.java
  19. 41
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java
  20. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java
  21. 42
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/ExternalComponentLayout.java
  22. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/views/StackLayout.java
  23. 2
    16
      lib/android/app/src/test/java/com/reactnativenavigation/parse/OptionsTest.java
  24. 61
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ExternalComponentViewControllerTest.java
  25. 33
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/externalcomponent/FragmentCreatorMock.java
  26. 8
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/externalcomponent/SomeFragment.java
  27. 1
    1
      lib/src/commands/LayoutTreeParser.ts
  28. 49
    0
      playground/android/app/src/main/java/com/reactnativenavigation/playground/FragmentComponent.java
  29. 15
    0
      playground/android/app/src/main/java/com/reactnativenavigation/playground/FragmentCreator.java
  30. 19
    0
      playground/android/app/src/main/java/com/reactnativenavigation/playground/FragmentScreen.java
  31. 10
    4
      playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.java
  32. 13
    0
      playground/android/app/src/main/res/layout/fragment_screen.xml
  33. 5
    2
      playground/src/screens/WelcomeScreen.js
  34. 1
    1
      playground/src/testIDs.js

+ 3
- 3
e2e/ScreenStack.test.js View File

69
     await expect(elementByLabel('Screen 1')).toBeVisible();
69
     await expect(elementByLabel('Screen 1')).toBeVisible();
70
   });
70
   });
71
 
71
 
72
-  it(':ios: push native component with options', async () => {
73
-    await elementById(testIDs.PUSH_NATIVE_COMPONENT_BUTTON).tap();
74
-    await expect(elementById('TestLabel')).toBeVisible();
72
+  it(':android: push external component with options', async () => {
73
+    await elementById(testIDs.PUSH_EXTERNAL_COMPONENT_BUTTON).tap();
74
+    await expect(elementByLabel('This is an external component')).toBeVisible();
75
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
75
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
76
   });
76
   });
77
 });
77
 });

+ 6
- 11
lib/android/app/src/main/java/com/reactnativenavigation/NavigationActivity.java View File

1
 package com.reactnativenavigation;
1
 package com.reactnativenavigation;
2
 
2
 
3
-import android.os.*;
4
-import android.support.annotation.*;
5
-import android.support.v7.app.*;
6
-import android.view.*;
7
-import android.widget.*;
3
+import android.os.Bundle;
4
+import android.support.annotation.Nullable;
5
+import android.support.v7.app.AppCompatActivity;
6
+import android.view.KeyEvent;
8
 
7
 
9
-import com.facebook.react.modules.core.*;
10
-import com.reactnativenavigation.viewcontrollers.*;
8
+import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
9
+import com.reactnativenavigation.viewcontrollers.Navigator;
11
 
10
 
12
 public class NavigationActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
11
 public class NavigationActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
13
 	private Navigator navigator;
12
 	private Navigator navigator;
63
 	public Navigator getNavigator() {
62
 	public Navigator getNavigator() {
64
 		return navigator;
63
 		return navigator;
65
 	}
64
 	}
66
-
67
-	public void toast(final String text) {
68
-		Toast.makeText(this, text, Toast.LENGTH_LONG).show();
69
-	}
70
 }
65
 }

+ 20
- 0
lib/android/app/src/main/java/com/reactnativenavigation/NavigationApplication.java View File

7
 import com.facebook.react.ReactNativeHost;
7
 import com.facebook.react.ReactNativeHost;
8
 import com.facebook.react.ReactPackage;
8
 import com.facebook.react.ReactPackage;
9
 import com.reactnativenavigation.react.ReactGateway;
9
 import com.reactnativenavigation.react.ReactGateway;
10
+import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
10
 
11
 
12
+import java.util.HashMap;
11
 import java.util.List;
13
 import java.util.List;
14
+import java.util.Map;
12
 
15
 
13
 public abstract class NavigationApplication extends Application implements ReactApplication {
16
 public abstract class NavigationApplication extends Application implements ReactApplication {
14
 
17
 
15
 	private ReactGateway reactGateway;
18
 	private ReactGateway reactGateway;
16
 	public static NavigationApplication instance;
19
 	public static NavigationApplication instance;
20
+	final Map<String, ExternalComponentCreator> externalComponents = new HashMap<>();
17
 
21
 
18
 	@Override
22
 	@Override
19
 	public void onCreate() {
23
 	public void onCreate() {
52
      */
56
      */
53
 	@Nullable
57
 	@Nullable
54
 	public abstract List<ReactPackage> createAdditionalReactPackages();
58
 	public abstract List<ReactPackage> createAdditionalReactPackages();
59
+
60
+    /**
61
+     * Register a native View which can be displayed using the given {@code name}
62
+     * @param name Unique name used to register the native view
63
+     * @param creator Used to create the view at runtime
64
+     */
65
+    public void registerExternalComponent(String name, ExternalComponentCreator creator) {
66
+        if (externalComponents.containsKey(name)) {
67
+            throw new RuntimeException("A component has already been registered with this name: " + name);
68
+        }
69
+        externalComponents.put(name, creator);
70
+    }
71
+
72
+    public final Map<String, ExternalComponentCreator> getExternalComponents() {
73
+        return externalComponents;
74
+    }
55
 }
75
 }

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

4
 import com.reactnativenavigation.parse.params.Text;
4
 import com.reactnativenavigation.parse.params.Text;
5
 import com.reactnativenavigation.parse.parsers.TextParser;
5
 import com.reactnativenavigation.parse.parsers.TextParser;
6
 
6
 
7
+import org.json.JSONException;
7
 import org.json.JSONObject;
8
 import org.json.JSONObject;
8
 
9
 
9
 public class ExternalComponent {
10
 public class ExternalComponent {
10
 
11
 
11
-    public Text classCreator = new NullText();
12
+    public Text name = new NullText();
13
+    public JSONObject passProps = new JSONObject();
12
 
14
 
13
     public static ExternalComponent parse(JSONObject json) {
15
     public static ExternalComponent parse(JSONObject json) {
14
         ExternalComponent options = new ExternalComponent();
16
         ExternalComponent options = new ExternalComponent();
16
             return options;
18
             return options;
17
         }
19
         }
18
 
20
 
19
-        options.classCreator = TextParser.parse(json, "classCreator");
20
-        if (!options.classCreator.hasValue()) {
21
-            throw new RuntimeException("ExternalClass must declare classCreator - a fully qualified method name");
21
+        options.name = TextParser.parse(json, "name");
22
+        if (!options.name.hasValue()) {
23
+            throw new RuntimeException("ExternalComponent must have a name");
22
         }
24
         }
23
-
25
+        options.passProps = parsePassProps(json);
24
         return options;
26
         return options;
25
     }
27
     }
26
 
28
 
29
+    private static JSONObject parsePassProps(JSONObject json) {
30
+        if (json.has("passProps")) {
31
+            try {
32
+                return json.getJSONObject("passProps");
33
+            } catch (JSONException e) {
34
+                e.printStackTrace();
35
+            }
36
+        }
37
+        return new JSONObject();
38
+    }
39
+
27
     public void mergeWith(ExternalComponent other) {
40
     public void mergeWith(ExternalComponent other) {
28
-        if (other.classCreator.hasValue()) {
29
-            classCreator = other.classCreator;
41
+        if (other.name.hasValue()) {
42
+            name = other.name;
43
+        }
44
+        if (other.passProps.length() > 0) {
45
+            passProps = other.passProps;
30
         }
46
         }
31
     }
47
     }
32
 
48
 
33
     public void mergeWithDefault(ExternalComponent defaultOptions) {
49
     public void mergeWithDefault(ExternalComponent defaultOptions) {
34
-        if (!classCreator.hasValue()) {
35
-            classCreator = defaultOptions.classCreator;
50
+        if (!name.hasValue()) {
51
+            name = defaultOptions.name;
52
+        }
53
+        if (passProps.length() == 0) {
54
+            passProps = defaultOptions.passProps;
36
         }
55
         }
37
     }
56
     }
38
 }
57
 }

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

6
 import com.reactnativenavigation.utils.ImageLoader;
6
 import com.reactnativenavigation.utils.ImageLoader;
7
 import com.reactnativenavigation.utils.NoOpPromise;
7
 import com.reactnativenavigation.utils.NoOpPromise;
8
 import com.reactnativenavigation.utils.TypefaceLoader;
8
 import com.reactnativenavigation.utils.TypefaceLoader;
9
-import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController;
10
 import com.reactnativenavigation.viewcontrollers.ComponentViewController;
9
 import com.reactnativenavigation.viewcontrollers.ComponentViewController;
11
 import com.reactnativenavigation.viewcontrollers.SideMenuController;
10
 import com.reactnativenavigation.viewcontrollers.SideMenuController;
12
 import com.reactnativenavigation.viewcontrollers.StackController;
11
 import com.reactnativenavigation.viewcontrollers.StackController;
13
 import com.reactnativenavigation.viewcontrollers.ViewController;
12
 import com.reactnativenavigation.viewcontrollers.ViewController;
13
+import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController;
14
+import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
15
+import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentViewController;
14
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsController;
16
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsController;
15
 import com.reactnativenavigation.views.ComponentViewCreator;
17
 import com.reactnativenavigation.views.ComponentViewCreator;
16
 import com.reactnativenavigation.views.TopTabsLayoutCreator;
18
 import com.reactnativenavigation.views.TopTabsLayoutCreator;
17
 
19
 
18
 import java.util.ArrayList;
20
 import java.util.ArrayList;
19
 import java.util.List;
21
 import java.util.List;
22
+import java.util.Map;
20
 
23
 
21
 public class LayoutFactory {
24
 public class LayoutFactory {
22
 
25
 
23
 	private final Activity activity;
26
 	private final Activity activity;
24
 	private final ReactInstanceManager reactInstanceManager;
27
 	private final ReactInstanceManager reactInstanceManager;
28
+    private Map<String, ExternalComponentCreator> externalComponentCreators;
25
     private Options defaultOptions;
29
     private Options defaultOptions;
26
     private final TypefaceLoader typefaceManager;
30
     private final TypefaceLoader typefaceManager;
27
 
31
 
28
-    public LayoutFactory(Activity activity, final ReactInstanceManager reactInstanceManager, Options defaultOptions) {
32
+    public LayoutFactory(Activity activity, final ReactInstanceManager reactInstanceManager, Map<String, ExternalComponentCreator> externalComponentCreators, Options defaultOptions) {
29
 		this.activity = activity;
33
 		this.activity = activity;
30
 		this.reactInstanceManager = reactInstanceManager;
34
 		this.reactInstanceManager = reactInstanceManager;
35
+        this.externalComponentCreators = externalComponentCreators;
31
         this.defaultOptions = defaultOptions;
36
         this.defaultOptions = defaultOptions;
32
         typefaceManager = new TypefaceLoader(activity);
37
         typefaceManager = new TypefaceLoader(activity);
33
     }
38
     }
36
 		switch (node.type) {
41
 		switch (node.type) {
37
 			case Component:
42
 			case Component:
38
 				return createComponent(node);
43
 				return createComponent(node);
44
+            case ExternalComponent:
45
+                return createExternalComponent(node);
39
 			case Stack:
46
 			case Stack:
40
 				return createStack(node);
47
 				return createStack(node);
41
 			case BottomTabs:
48
 			case BottomTabs:
101
         );
108
         );
102
 	}
109
 	}
103
 
110
 
111
+    private ViewController createExternalComponent(LayoutNode node) {
112
+        final Options options = Options.parse(typefaceManager, node.getNavigationOptions(), defaultOptions);
113
+        ExternalComponent externalComponent = ExternalComponent.parse(node.data);
114
+        return new ExternalComponentViewController(activity,
115
+                node.id,
116
+                externalComponent,
117
+                externalComponentCreators.get(externalComponent.name.get()),
118
+                options
119
+        );
120
+    }
121
+
104
 	private ViewController createStack(LayoutNode node) {
122
 	private ViewController createStack(LayoutNode node) {
105
         final Options options = Options.parse(typefaceManager, node.getNavigationOptions(), defaultOptions);
123
         final Options options = Options.parse(typefaceManager, node.getNavigationOptions(), defaultOptions);
106
 		StackController stackController = new StackController(activity, node.id, options);
124
 		StackController stackController = new StackController(activity, node.id, options);

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

8
 public class LayoutNode {
8
 public class LayoutNode {
9
 	public enum Type {
9
 	public enum Type {
10
 		Component,
10
 		Component,
11
+        ExternalComponent,
11
 		Stack,
12
 		Stack,
12
 		BottomTabs,
13
 		BottomTabs,
13
 		SideMenuRoot,
14
 		SideMenuRoot,

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

29
         result.fabOptions = FabOptions.parse(json.optJSONObject("fab"));
29
         result.fabOptions = FabOptions.parse(json.optJSONObject("fab"));
30
         result.animationsOptions = AnimationsOptions.parse(json.optJSONObject("animations"));
30
         result.animationsOptions = AnimationsOptions.parse(json.optJSONObject("animations"));
31
         result.sideMenuRootOptions = SideMenuRootOptions.parse(json.optJSONObject("sideMenu"));
31
         result.sideMenuRootOptions = SideMenuRootOptions.parse(json.optJSONObject("sideMenu"));
32
-        result.externalComponent = ExternalComponent.parse(json.optJSONObject("externalComponent"));
33
 
32
 
34
         return result.withDefaultOptions(defaultOptions);
33
         return result.withDefaultOptions(defaultOptions);
35
     }
34
     }
44
     @NonNull public FabOptions fabOptions = new FabOptions();
43
     @NonNull public FabOptions fabOptions = new FabOptions();
45
     @NonNull public AnimationsOptions animationsOptions = new AnimationsOptions();
44
     @NonNull public AnimationsOptions animationsOptions = new AnimationsOptions();
46
     @NonNull public SideMenuRootOptions sideMenuRootOptions = new SideMenuRootOptions();
45
     @NonNull public SideMenuRootOptions sideMenuRootOptions = new SideMenuRootOptions();
47
-    @NonNull public ExternalComponent externalComponent = new ExternalComponent();
48
 
46
 
49
     void setTopTabIndex(int i) {
47
     void setTopTabIndex(int i) {
50
         topTabOptions.tabIndex = i;
48
         topTabOptions.tabIndex = i;
63
         result.fabOptions.mergeWith(fabOptions);
61
         result.fabOptions.mergeWith(fabOptions);
64
         result.animationsOptions.mergeWith(animationsOptions);
62
         result.animationsOptions.mergeWith(animationsOptions);
65
         result.sideMenuRootOptions.mergeWith(sideMenuRootOptions);
63
         result.sideMenuRootOptions.mergeWith(sideMenuRootOptions);
66
-        result.externalComponent.mergeWith(externalComponent);
67
         return result;
64
         return result;
68
     }
65
     }
69
 
66
 
79
         result.fabOptions.mergeWith(other.fabOptions);
76
         result.fabOptions.mergeWith(other.fabOptions);
80
         result.animationsOptions.mergeWith(other.animationsOptions);
77
         result.animationsOptions.mergeWith(other.animationsOptions);
81
         result.sideMenuRootOptions.mergeWith(other.sideMenuRootOptions);
78
         result.sideMenuRootOptions.mergeWith(other.sideMenuRootOptions);
82
-        result.externalComponent.mergeWith(other.externalComponent);
83
         return result;
79
         return result;
84
     }
80
     }
85
 
81
 
93
         fabOptions.mergeWithDefault(other.fabOptions);
89
         fabOptions.mergeWithDefault(other.fabOptions);
94
         animationsOptions.mergeWithDefault(other.animationsOptions);
90
         animationsOptions.mergeWithDefault(other.animationsOptions);
95
         sideMenuRootOptions.mergeWithDefault(other.sideMenuRootOptions);
91
         sideMenuRootOptions.mergeWithDefault(other.sideMenuRootOptions);
96
-        externalComponent.mergeWithDefault(other.externalComponent);
97
         return this;
92
         return this;
98
     }
93
     }
99
 
94
 

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

10
 
10
 
11
 import com.reactnativenavigation.R;
11
 import com.reactnativenavigation.R;
12
 import com.reactnativenavigation.parse.FabOptions;
12
 import com.reactnativenavigation.parse.FabOptions;
13
-import com.reactnativenavigation.parse.Options;
14
 import com.reactnativenavigation.views.Fab;
13
 import com.reactnativenavigation.views.Fab;
15
 import com.reactnativenavigation.views.FabMenu;
14
 import com.reactnativenavigation.views.FabMenu;
16
 import com.reactnativenavigation.views.ReactComponent;
15
 import com.reactnativenavigation.views.ReactComponent;

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

2
 
2
 
3
 import android.app.Activity;
3
 import android.app.Activity;
4
 
4
 
5
-import com.reactnativenavigation.parse.OrientationOptions;
6
-import com.reactnativenavigation.parse.params.Button;
7
 import com.reactnativenavigation.parse.Options;
5
 import com.reactnativenavigation.parse.Options;
6
+import com.reactnativenavigation.parse.OrientationOptions;
8
 import com.reactnativenavigation.parse.TopBarOptions;
7
 import com.reactnativenavigation.parse.TopBarOptions;
9
 import com.reactnativenavigation.parse.TopTabOptions;
8
 import com.reactnativenavigation.parse.TopTabOptions;
10
 import com.reactnativenavigation.parse.TopTabsOptions;
9
 import com.reactnativenavigation.parse.TopTabsOptions;
11
-import com.reactnativenavigation.views.ReactComponent;
10
+import com.reactnativenavigation.parse.params.Button;
11
+import com.reactnativenavigation.viewcontrollers.IReactView;
12
+import com.reactnativenavigation.views.Component;
12
 import com.reactnativenavigation.views.TopBar;
13
 import com.reactnativenavigation.views.TopBar;
13
 
14
 
14
 import java.util.ArrayList;
15
 import java.util.ArrayList;
15
 
16
 
16
 public class OptionsPresenter {
17
 public class OptionsPresenter {
17
     private TopBar topBar;
18
     private TopBar topBar;
18
-    private ReactComponent component;
19
+    private Component component;
19
 
20
 
20
-    public OptionsPresenter(TopBar topBar, ReactComponent component) {
21
+    public OptionsPresenter(TopBar topBar, Component component) {
21
         this.topBar = topBar;
22
         this.topBar = topBar;
22
         this.component = component;
23
         this.component = component;
23
     }
24
     }
59
         }
60
         }
60
 
61
 
61
         if (options.hideOnScroll.isTrue()) {
62
         if (options.hideOnScroll.isTrue()) {
62
-            topBar.enableCollapse(component.getScrollEventListener());
63
+            if (component instanceof IReactView) {
64
+                topBar.enableCollapse(((IReactView) component).getScrollEventListener());
65
+            }
63
         } else if (options.hideOnScroll.isTrue()) {
66
         } else if (options.hideOnScroll.isTrue()) {
64
             topBar.disableCollapse();
67
             topBar.disableCollapse();
65
         }
68
         }

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

9
 import com.facebook.react.bridge.ReactMethod;
9
 import com.facebook.react.bridge.ReactMethod;
10
 import com.facebook.react.bridge.ReadableMap;
10
 import com.facebook.react.bridge.ReadableMap;
11
 import com.reactnativenavigation.NavigationActivity;
11
 import com.reactnativenavigation.NavigationActivity;
12
+import com.reactnativenavigation.NavigationApplication;
12
 import com.reactnativenavigation.parse.LayoutFactory;
13
 import com.reactnativenavigation.parse.LayoutFactory;
13
 import com.reactnativenavigation.parse.LayoutNode;
14
 import com.reactnativenavigation.parse.LayoutNode;
14
-import com.reactnativenavigation.parse.parsers.LayoutNodeParser;
15
 import com.reactnativenavigation.parse.Options;
15
 import com.reactnativenavigation.parse.Options;
16
+import com.reactnativenavigation.parse.parsers.LayoutNodeParser;
16
 import com.reactnativenavigation.utils.TypefaceLoader;
17
 import com.reactnativenavigation.utils.TypefaceLoader;
17
 import com.reactnativenavigation.utils.UiThread;
18
 import com.reactnativenavigation.utils.UiThread;
18
 import com.reactnativenavigation.viewcontrollers.Navigator;
19
 import com.reactnativenavigation.viewcontrollers.Navigator;
19
 import com.reactnativenavigation.viewcontrollers.ViewController;
20
 import com.reactnativenavigation.viewcontrollers.ViewController;
21
+import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
20
 
22
 
21
 import org.json.JSONObject;
23
 import org.json.JSONObject;
22
 
24
 
25
+import java.util.Map;
26
+
23
 public class NavigationModule extends ReactContextBaseJavaModule {
27
 public class NavigationModule extends ReactContextBaseJavaModule {
24
 	private static final String NAME = "RNNBridgeModule";
28
 	private static final String NAME = "RNNBridgeModule";
25
 	private final ReactInstanceManager reactInstanceManager;
29
 	private final ReactInstanceManager reactInstanceManager;
113
 		handle(() -> navigator().dismissOverlay(componentId));
117
 		handle(() -> navigator().dismissOverlay(componentId));
114
 	}
118
 	}
115
 
119
 
116
-	private NavigationActivity activity() {
117
-		return (NavigationActivity) getCurrentActivity();
118
-	}
119
-
120
 	private Navigator navigator() {
120
 	private Navigator navigator() {
121
 		return activity().getNavigator();
121
 		return activity().getNavigator();
122
 	}
122
 	}
123
 
123
 
124
 	@NonNull
124
 	@NonNull
125
 	private LayoutFactory newLayoutFactory() {
125
 	private LayoutFactory newLayoutFactory() {
126
-		return new LayoutFactory(activity(), reactInstanceManager, navigator().getDefaultOptions());
126
+		return new LayoutFactory(activity(),
127
+                reactInstanceManager,
128
+                externalComponentCreator(),
129
+                navigator().getDefaultOptions()
130
+        );
127
 	}
131
 	}
128
 
132
 
133
+	private Map<String, ExternalComponentCreator> externalComponentCreator() {
134
+        return ((NavigationApplication) activity().getApplication()).getExternalComponents();
135
+    }
136
+
129
 	private void handle(Runnable task) {
137
 	private void handle(Runnable task) {
130
 		if (activity() == null || activity().isFinishing()) return;
138
 		if (activity() == null || activity().isFinishing()) return;
131
 		UiThread.post(task);
139
 		UiThread.post(task);
132
 	}
140
 	}
141
+
142
+    private NavigationActivity activity() {
143
+        return (NavigationActivity) getCurrentActivity();
144
+    }
133
 }
145
 }

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

8
 import android.view.ViewGroup;
8
 import android.view.ViewGroup;
9
 
9
 
10
 import com.reactnativenavigation.parse.Options;
10
 import com.reactnativenavigation.parse.Options;
11
-import com.reactnativenavigation.views.ReactComponent;
11
+import com.reactnativenavigation.views.Component;
12
 
12
 
13
 import java.util.Collection;
13
 import java.util.Collection;
14
 
14
 
46
 	}
46
 	}
47
 
47
 
48
 	@Override
48
 	@Override
49
-    public boolean containsComponent(ReactComponent component) {
49
+    public boolean containsComponent(Component component) {
50
         if (super.containsComponent(component)) {
50
         if (super.containsComponent(component)) {
51
             return true;
51
             return true;
52
         }
52
         }
57
     }
57
     }
58
 
58
 
59
     @CallSuper
59
     @CallSuper
60
-    public void applyOptions(Options options, ReactComponent childComponent) {
60
+    public void applyOptions(Options options, Component childComponent) {
61
         mergeChildOptions(options);
61
         mergeChildOptions(options);
62
     }
62
     }
63
 
63
 

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

9
 import com.reactnativenavigation.parse.Options;
9
 import com.reactnativenavigation.parse.Options;
10
 import com.reactnativenavigation.presentation.NavigationOptionsListener;
10
 import com.reactnativenavigation.presentation.NavigationOptionsListener;
11
 import com.reactnativenavigation.presentation.SideMenuOptionsPresenter;
11
 import com.reactnativenavigation.presentation.SideMenuOptionsPresenter;
12
-import com.reactnativenavigation.views.ReactComponent;
12
+import com.reactnativenavigation.views.Component;
13
 
13
 
14
 import java.util.ArrayList;
14
 import java.util.ArrayList;
15
 import java.util.Collection;
15
 import java.util.Collection;
49
 	}
49
 	}
50
 
50
 
51
     @Override
51
     @Override
52
-    public void applyOptions(Options options, ReactComponent childComponent) {
52
+    public void applyOptions(Options options, Component childComponent) {
53
         super.applyOptions(options, childComponent);
53
         super.applyOptions(options, childComponent);
54
         applyOnParentController(parentController ->
54
         applyOnParentController(parentController ->
55
                 ((ParentController) parentController).applyOptions(this.options, childComponent)
55
                 ((ParentController) parentController).applyOptions(this.options, childComponent)

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

10
 import com.reactnativenavigation.anim.NavigationAnimator;
10
 import com.reactnativenavigation.anim.NavigationAnimator;
11
 import com.reactnativenavigation.parse.Options;
11
 import com.reactnativenavigation.parse.Options;
12
 import com.reactnativenavigation.utils.NoOpPromise;
12
 import com.reactnativenavigation.utils.NoOpPromise;
13
+import com.reactnativenavigation.views.Component;
13
 import com.reactnativenavigation.views.ReactComponent;
14
 import com.reactnativenavigation.views.ReactComponent;
14
 import com.reactnativenavigation.views.StackLayout;
15
 import com.reactnativenavigation.views.StackLayout;
15
 import com.reactnativenavigation.views.TopBar;
16
 import com.reactnativenavigation.views.TopBar;
44
     }
45
     }
45
 
46
 
46
     @Override
47
     @Override
47
-    public void applyOptions(Options options, ReactComponent component) {
48
+    public void applyOptions(Options options, Component component) {
48
         super.applyOptions(options, component);
49
         super.applyOptions(options, component);
49
         getView().applyOptions(this.options, component);
50
         getView().applyOptions(this.options, component);
50
         applyOnParentController(parentController ->
51
         applyOnParentController(parentController ->
51
                 ((ParentController) parentController).applyOptions(this.options.copy().clearTopBarOptions(), component)
52
                 ((ParentController) parentController).applyOptions(this.options.copy().clearTopBarOptions(), component)
52
         );
53
         );
53
-        fabOptionsPresenter.applyOptions(options.fabOptions, component, getView());
54
+        if (component instanceof ReactComponent) {
55
+            fabOptionsPresenter.applyOptions(options.fabOptions, (ReactComponent) component, getView());
56
+        }
54
         animator.setOptions(options.animationsOptions);
57
         animator.setOptions(options.animationsOptions);
55
     }
58
     }
56
 
59
 

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

15
 import com.reactnativenavigation.utils.CompatUtils;
15
 import com.reactnativenavigation.utils.CompatUtils;
16
 import com.reactnativenavigation.utils.StringUtils;
16
 import com.reactnativenavigation.utils.StringUtils;
17
 import com.reactnativenavigation.utils.Task;
17
 import com.reactnativenavigation.utils.Task;
18
-import com.reactnativenavigation.views.ReactComponent;
18
+import com.reactnativenavigation.views.Component;
19
 
19
 
20
 public abstract class ViewController<T extends ViewGroup> implements ViewTreeObserver.OnGlobalLayoutListener {
20
 public abstract class ViewController<T extends ViewGroup> implements ViewTreeObserver.OnGlobalLayoutListener {
21
 
21
 
132
         return isSameId(id) ? this : null;
132
         return isSameId(id) ? this : null;
133
     }
133
     }
134
 
134
 
135
-    public boolean containsComponent(ReactComponent component) {
135
+    public boolean containsComponent(Component component) {
136
         return getView().equals(component);
136
         return getView().equals(component);
137
     }
137
     }
138
 
138
 
153
         applyOptions(options);
153
         applyOptions(options);
154
         applyOnParentController(parentController -> {
154
         applyOnParentController(parentController -> {
155
             parentController.clearOptions();
155
             parentController.clearOptions();
156
-            if (getView() instanceof ReactComponent) parentController.applyOptions(options, (ReactComponent) getView());
156
+            if (getView() instanceof Component) parentController.applyOptions(options, (Component) getView());
157
         });
157
         });
158
     }
158
     }
159
 
159
 

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

3
 import android.support.annotation.IntRange;
3
 import android.support.annotation.IntRange;
4
 
4
 
5
 import com.reactnativenavigation.viewcontrollers.ViewController;
5
 import com.reactnativenavigation.viewcontrollers.ViewController;
6
-import com.reactnativenavigation.views.ReactComponent;
6
+import com.reactnativenavigation.views.Component;
7
 
7
 
8
 import java.util.List;
8
 import java.util.List;
9
 
9
 
11
     private List<ViewController> tabs;
11
     private List<ViewController> tabs;
12
 
12
 
13
     @IntRange(from = -1)
13
     @IntRange(from = -1)
14
-    public int findByComponent(ReactComponent component) {
14
+    int findByComponent(Component component) {
15
         for (int i = 0; i < tabs.size(); i++) {
15
         for (int i = 0; i < tabs.size(); i++) {
16
             if (tabs.get(i).containsComponent(component)) {
16
             if (tabs.get(i).containsComponent(component)) {
17
                 return i;
17
                 return i;

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

16
 import com.reactnativenavigation.viewcontrollers.ParentController;
16
 import com.reactnativenavigation.viewcontrollers.ParentController;
17
 import com.reactnativenavigation.viewcontrollers.ViewController;
17
 import com.reactnativenavigation.viewcontrollers.ViewController;
18
 import com.reactnativenavigation.views.BottomTabs;
18
 import com.reactnativenavigation.views.BottomTabs;
19
-import com.reactnativenavigation.views.ReactComponent;
19
+import com.reactnativenavigation.views.Component;
20
 
20
 
21
 import java.util.ArrayList;
21
 import java.util.ArrayList;
22
 import java.util.Collection;
22
 import java.util.Collection;
57
     }
57
     }
58
 
58
 
59
     @Override
59
     @Override
60
-    public void applyOptions(Options options, ReactComponent childComponent) {
60
+    public void applyOptions(Options options, Component childComponent) {
61
         super.applyOptions(options, childComponent);
61
         super.applyOptions(options, childComponent);
62
         int tabIndex = bottomTabFinder.findByComponent(childComponent);
62
         int tabIndex = bottomTabFinder.findByComponent(childComponent);
63
         if (tabIndex >= 0) new BottomTabsOptionsPresenter(bottomTabs, bottomTabFinder).present(this.options, tabIndex);
63
         if (tabIndex >= 0) new BottomTabsOptionsPresenter(bottomTabs, bottomTabFinder).present(this.options, tabIndex);

+ 7
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponent.java View File

1
+package com.reactnativenavigation.viewcontrollers.externalcomponent;
2
+
3
+import android.view.View;
4
+
5
+public interface ExternalComponent {
6
+    View asView();
7
+}

+ 9
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentCreator.java View File

1
+package com.reactnativenavigation.viewcontrollers.externalcomponent;
2
+
3
+import android.support.v4.app.FragmentActivity;
4
+
5
+import org.json.JSONObject;
6
+
7
+public interface ExternalComponentCreator {
8
+    ExternalComponent create(FragmentActivity activity, JSONObject props);
9
+}

+ 41
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java View File

1
+package com.reactnativenavigation.viewcontrollers.externalcomponent;
2
+
3
+import android.app.Activity;
4
+import android.support.v4.app.FragmentActivity;
5
+
6
+import com.reactnativenavigation.parse.ExternalComponent;
7
+import com.reactnativenavigation.parse.Options;
8
+import com.reactnativenavigation.viewcontrollers.ViewController;
9
+import com.reactnativenavigation.views.ExternalComponentLayout;
10
+
11
+public class ExternalComponentViewController extends ViewController<ExternalComponentLayout> {
12
+    private final ExternalComponent externalComponent;
13
+    private final ExternalComponentCreator componentCreator;
14
+
15
+    public ExternalComponentViewController(Activity activity, String id, ExternalComponent externalComponent, ExternalComponentCreator componentCreator, Options initialOptions) {
16
+        super(activity, id, initialOptions);
17
+        this.externalComponent = externalComponent;
18
+        this.componentCreator = componentCreator;
19
+    }
20
+
21
+    @Override
22
+    public void applyOptions(Options options) {
23
+        getView().applyOptions(options);
24
+    }
25
+
26
+    @Override
27
+    protected ExternalComponentLayout createView() {
28
+        ExternalComponentLayout content = new ExternalComponentLayout(getActivity());
29
+        content.addView(componentCreator.create(getActivity(), externalComponent.passProps).asView());
30
+        return content;
31
+    }
32
+
33
+    @Override
34
+    public void sendOnNavigationButtonPressed(String buttonId) {
35
+
36
+    }
37
+
38
+    public FragmentActivity getActivity() {
39
+        return (FragmentActivity) super.getActivity();
40
+    }
41
+}

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

10
 import com.reactnativenavigation.viewcontrollers.ParentController;
10
 import com.reactnativenavigation.viewcontrollers.ParentController;
11
 import com.reactnativenavigation.viewcontrollers.ViewController;
11
 import com.reactnativenavigation.viewcontrollers.ViewController;
12
 import com.reactnativenavigation.viewcontrollers.ViewVisibilityListenerAdapter;
12
 import com.reactnativenavigation.viewcontrollers.ViewVisibilityListenerAdapter;
13
-import com.reactnativenavigation.views.ReactComponent;
13
+import com.reactnativenavigation.views.Component;
14
 import com.reactnativenavigation.views.TopTabsLayoutCreator;
14
 import com.reactnativenavigation.views.TopTabsLayoutCreator;
15
 import com.reactnativenavigation.views.TopTabsViewPager;
15
 import com.reactnativenavigation.views.TopTabsViewPager;
16
 
16
 
79
     }
79
     }
80
 
80
 
81
     @Override
81
     @Override
82
-    public void applyOptions(Options options, ReactComponent childComponent) {
82
+    public void applyOptions(Options options, Component childComponent) {
83
         super.applyOptions(options, childComponent);
83
         super.applyOptions(options, childComponent);
84
         applyOnParentController(parentController -> {
84
         applyOnParentController(parentController -> {
85
                 Options opt = this.options.copy();
85
                 Options opt = this.options.copy();

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

1
+package com.reactnativenavigation.views;
2
+
3
+import android.annotation.SuppressLint;
4
+import android.content.Context;
5
+import android.widget.FrameLayout;
6
+import android.widget.RelativeLayout;
7
+
8
+import com.reactnativenavigation.parse.Options;
9
+import com.reactnativenavigation.presentation.ComponentOptionsPresenter;
10
+
11
+import static android.widget.RelativeLayout.BELOW;
12
+
13
+@SuppressLint("ViewConstructor")
14
+public class ExternalComponentLayout extends FrameLayout implements Component {
15
+    public ExternalComponentLayout(Context context) {
16
+		super(context);
17
+        setContentDescription("ExternalComponentLayout");
18
+    }
19
+
20
+    @Override
21
+    public void applyOptions(Options options) {
22
+        new ComponentOptionsPresenter(this).present(options);
23
+    }
24
+
25
+    @Override
26
+    public void drawBehindTopBar() {
27
+        if (getParent() instanceof RelativeLayout) {
28
+            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
29
+            layoutParams.removeRule(BELOW);
30
+            setLayoutParams(layoutParams);
31
+        }
32
+    }
33
+
34
+    @Override
35
+    public void drawBelowTopBar(TopBar topBar) {
36
+        if (getParent() instanceof RelativeLayout) {
37
+            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
38
+            layoutParams.addRule(BELOW, topBar.getId());
39
+            setLayoutParams(layoutParams);
40
+        }
41
+    }
42
+}

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

33
         new OptionsPresenter(topBar).applyOrientation(options.orientationOptions);
33
         new OptionsPresenter(topBar).applyOrientation(options.orientationOptions);
34
     }
34
     }
35
 
35
 
36
-    public void applyOptions(Options options, ReactComponent component) {
36
+    public void applyOptions(Options options, Component component) {
37
         new OptionsPresenter(topBar, component).applyOptions(options);
37
         new OptionsPresenter(topBar, component).applyOptions(options);
38
     }
38
     }
39
 
39
 

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

39
     private static final String BOTTOM_TABS_BADGE = "3";
39
     private static final String BOTTOM_TABS_BADGE = "3";
40
     private static final String BOTTOM_TABS_CURRENT_TAB_ID = "ComponentId";
40
     private static final String BOTTOM_TABS_CURRENT_TAB_ID = "ComponentId";
41
     private static final Number BOTTOM_TABS_CURRENT_TAB_INDEX = new Number(1);
41
     private static final Number BOTTOM_TABS_CURRENT_TAB_INDEX = new Number(1);
42
-    private static final String EXTERNAL_CLASS_CREATOR = "com.rnn.creators.Creator.createFragment";
43
     private TypefaceLoader mockLoader;
42
     private TypefaceLoader mockLoader;
44
 
43
 
45
     @Override
44
     @Override
58
         JSONObject json = new JSONObject()
57
         JSONObject json = new JSONObject()
59
                 .put("topBar", createTopBar(TOP_BAR_VISIBLE.get()))
58
                 .put("topBar", createTopBar(TOP_BAR_VISIBLE.get()))
60
                 .put("fab", createFab())
59
                 .put("fab", createFab())
61
-                .put("externalComponent", createExternalComponent())
62
                 .put("bottomTabs", createBottomTabs());
60
                 .put("bottomTabs", createBottomTabs());
63
         Options result = Options.parse(mockLoader, json);
61
         Options result = Options.parse(mockLoader, json);
64
         assertResult(result);
62
         assertResult(result);
85
         assertThat(result.fabOptions.hideOnScroll.get()).isEqualTo(FAB_HIDE_ON_SCROLL);
83
         assertThat(result.fabOptions.hideOnScroll.get()).isEqualTo(FAB_HIDE_ON_SCROLL);
86
         assertThat(result.fabOptions.alignVertically.get()).isEqualTo(FAB_ALIGN_VERTICALLY);
84
         assertThat(result.fabOptions.alignVertically.get()).isEqualTo(FAB_ALIGN_VERTICALLY);
87
         assertThat(result.fabOptions.alignHorizontally.get()).isEqualTo(FAB_ALIGN_HORIZONTALLY);
85
         assertThat(result.fabOptions.alignHorizontally.get()).isEqualTo(FAB_ALIGN_HORIZONTALLY);
88
-        assertThat(result.externalComponent.classCreator.get()).isEqualTo(EXTERNAL_CLASS_CREATOR);
89
     }
86
     }
90
 
87
 
91
     @NonNull
88
     @NonNull
123
                 .put("visible", FAB_VISIBLE);
120
                 .put("visible", FAB_VISIBLE);
124
     }
121
     }
125
 
122
 
126
-    private JSONObject createExternalComponent() throws JSONException {
127
-        return new JSONObject()
128
-                .put("classCreator", EXTERNAL_CLASS_CREATOR);
129
-    }
130
-
131
     @NonNull
123
     @NonNull
132
     private JSONObject createOtherFab() throws JSONException {
124
     private JSONObject createOtherFab() throws JSONException {
133
         return new JSONObject()
125
         return new JSONObject()
162
                 .put("tabBadge", BOTTOM_TABS_BADGE);
154
                 .put("tabBadge", BOTTOM_TABS_BADGE);
163
     }
155
     }
164
 
156
 
165
-    private JSONObject createOtherExternalClass() {
166
-        return new JSONObject();
167
-    }
168
-
169
     @Test
157
     @Test
170
     public void mergeDoesNotMutate() throws Exception {
158
     public void mergeDoesNotMutate() throws Exception {
171
         JSONObject json1 = new JSONObject();
159
         JSONObject json1 = new JSONObject();
189
         JSONObject json = new JSONObject()
177
         JSONObject json = new JSONObject()
190
                 .put("topBar", createTopBar(TOP_BAR_VISIBLE.get()))
178
                 .put("topBar", createTopBar(TOP_BAR_VISIBLE.get()))
191
                 .put("fab", createFab())
179
                 .put("fab", createFab())
192
-                .put("bottomTabs", createBottomTabs())
193
-                .put("externalComponent", createExternalComponent());
180
+                .put("bottomTabs", createBottomTabs());
194
         Options defaultOptions = Options.parse(mockLoader, json);
181
         Options defaultOptions = Options.parse(mockLoader, json);
195
         Options options = new Options();
182
         Options options = new Options();
196
 
183
 
202
         JSONObject defaultJson = new JSONObject()
189
         JSONObject defaultJson = new JSONObject()
203
                 .put("topBar", createOtherTopBar())
190
                 .put("topBar", createOtherTopBar())
204
                 .put("fab", createOtherFab())
191
                 .put("fab", createOtherFab())
205
-                .put("bottomTabs", createOtherBottomTabs())
206
-                .put("externalComponent", createExternalComponent());
192
+                .put("bottomTabs", createOtherBottomTabs());
207
         Options defaultOptions = Options.parse(mockLoader, defaultJson);
193
         Options defaultOptions = Options.parse(mockLoader, defaultJson);
208
 
194
 
209
         JSONObject json = new JSONObject()
195
         JSONObject json = new JSONObject()

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

1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.support.v4.app.FragmentActivity;
5
+import android.widget.FrameLayout;
6
+
7
+import com.reactnativenavigation.BaseTest;
8
+import com.reactnativenavigation.parse.ExternalComponent;
9
+import com.reactnativenavigation.parse.Options;
10
+import com.reactnativenavigation.parse.params.Text;
11
+import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentViewController;
12
+import com.reactnativenavigation.viewcontrollers.externalcomponent.FragmentCreatorMock;
13
+import com.reactnativenavigation.views.ExternalComponentLayout;
14
+
15
+import org.json.JSONObject;
16
+import org.junit.Test;
17
+
18
+import static org.assertj.core.api.Java6Assertions.assertThat;
19
+import static org.mockito.Mockito.spy;
20
+import static org.mockito.Mockito.times;
21
+import static org.mockito.Mockito.verify;
22
+
23
+public class ExternalComponentViewControllerTest extends BaseTest {
24
+    private ExternalComponentViewController uut;
25
+    private FragmentCreatorMock componentCreator;
26
+    private Activity activity;
27
+    private ExternalComponent ec;
28
+
29
+    @Override
30
+    public void beforeEach() {
31
+        componentCreator = spy(new FragmentCreatorMock());
32
+        activity = newActivity();
33
+        ec = createExternalComponent();
34
+        uut = spy(new ExternalComponentViewController(activity,
35
+                "fragmentId",
36
+                ec,
37
+                componentCreator,
38
+                new Options())
39
+        );
40
+    }
41
+
42
+    private ExternalComponent createExternalComponent() {
43
+        ExternalComponent component = new ExternalComponent();
44
+        component.name = new Text("fragmentComponent");
45
+        component.passProps = new JSONObject();
46
+        return component;
47
+    }
48
+
49
+    @Test
50
+    public void createView_returnsFrameLayout() throws Exception {
51
+        ExternalComponentLayout view = uut.getView();
52
+        assertThat(FrameLayout.class.isAssignableFrom(view.getClass())).isTrue();
53
+    }
54
+
55
+    @Test
56
+    public void createView_createsExternalComponent() throws Exception {
57
+        ExternalComponentLayout view = uut.getView();
58
+        verify(componentCreator, times(1)).create((FragmentActivity) activity, ec.passProps);
59
+        assertThat(view.getChildCount()).isGreaterThan(0);
60
+    }
61
+}

+ 33
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/externalcomponent/FragmentCreatorMock.java View File

1
+package com.reactnativenavigation.viewcontrollers.externalcomponent;
2
+
3
+import android.app.Activity;
4
+import android.app.FragmentManager;
5
+import android.app.FragmentTransaction;
6
+import android.support.v4.app.FragmentActivity;
7
+import android.widget.FrameLayout;
8
+
9
+import com.reactnativenavigation.R;
10
+
11
+import org.json.JSONObject;
12
+
13
+public class FragmentCreatorMock implements ExternalComponentCreator {
14
+    @Override
15
+    public ExternalComponent create(FragmentActivity activity, JSONObject props) {
16
+        return createContent(activity);
17
+    }
18
+
19
+    private ExternalComponent createContent(Activity activity) {
20
+        FrameLayout content = new FrameLayout(activity) {
21
+            @Override
22
+            protected void onAttachedToWindow() {
23
+                super.onAttachedToWindow();
24
+                FragmentManager fm = activity.getFragmentManager();
25
+                FragmentTransaction ft = fm.beginTransaction();
26
+                ft.add(R.id.fragment_screen_content, new SomeFragment());
27
+                ft.commitAllowingStateLoss();
28
+            }
29
+        };
30
+        content.setId(R.id.fragment_screen_content);
31
+        return () -> content;
32
+    }
33
+}

+ 8
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/externalcomponent/SomeFragment.java View File

1
+package com.reactnativenavigation.viewcontrollers.externalcomponent;
2
+
3
+import android.app.Fragment;
4
+
5
+public class SomeFragment extends Fragment {
6
+    public SomeFragment() {
7
+    }
8
+}

+ 1
- 1
lib/src/commands/LayoutTreeParser.ts View File

103
     return {
103
     return {
104
       id: api.id,
104
       id: api.id,
105
       type: LayoutType.ExternalComponent,
105
       type: LayoutType.ExternalComponent,
106
-      data: { name: api.className, options: api.options, passProps: api.passProps },
106
+      data: { name: api.name, options: api.options, passProps: api.passProps },
107
       children: []
107
       children: []
108
     };
108
     };
109
   }
109
   }

+ 49
- 0
playground/android/app/src/main/java/com/reactnativenavigation/playground/FragmentComponent.java View File

1
+package com.reactnativenavigation.playground;
2
+
3
+import android.os.Bundle;
4
+import android.support.annotation.NonNull;
5
+import android.support.v4.app.FragmentActivity;
6
+import android.support.v4.app.FragmentManager;
7
+import android.support.v4.app.FragmentTransaction;
8
+import android.view.View;
9
+import android.widget.FrameLayout;
10
+
11
+import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponent;
12
+
13
+import org.json.JSONObject;
14
+
15
+public class FragmentComponent implements ExternalComponent {
16
+    private final FrameLayout content;
17
+
18
+    FragmentComponent(FragmentActivity activity, JSONObject props) {
19
+        content = new FrameLayout(activity) {
20
+            @Override
21
+            protected void onAttachedToWindow() {
22
+                super.onAttachedToWindow();
23
+                addFragmentAfterContainerIsAttached(activity, props);
24
+            }
25
+        };
26
+        content.setId(R.id.fragment_screen_content);
27
+    }
28
+
29
+    private void addFragmentAfterContainerIsAttached(FragmentActivity activity, JSONObject props) {
30
+        FragmentManager fm = activity.getSupportFragmentManager();
31
+        FragmentTransaction transaction = fm.beginTransaction();
32
+        transaction.add(R.id.fragment_screen_content, createFragment(props));
33
+        transaction.commitAllowingStateLoss();
34
+    }
35
+
36
+    @NonNull
37
+    private FragmentScreen createFragment(JSONObject props) {
38
+        FragmentScreen fragment = new FragmentScreen();
39
+        Bundle args = new Bundle();
40
+        args.putString("text", props.optString("text", ""));
41
+        fragment.setArguments(args);
42
+        return fragment;
43
+    }
44
+
45
+    @Override
46
+    public View asView() {
47
+        return content;
48
+    }
49
+}

+ 15
- 0
playground/android/app/src/main/java/com/reactnativenavigation/playground/FragmentCreator.java View File

1
+package com.reactnativenavigation.playground;
2
+
3
+import android.support.v4.app.FragmentActivity;
4
+
5
+import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponent;
6
+import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
7
+
8
+import org.json.JSONObject;
9
+
10
+public class FragmentCreator implements ExternalComponentCreator {
11
+    @Override
12
+    public ExternalComponent create(FragmentActivity activity, JSONObject props) {
13
+        return new FragmentComponent(activity, props);
14
+    }
15
+}

+ 19
- 0
playground/android/app/src/main/java/com/reactnativenavigation/playground/FragmentScreen.java View File

1
+package com.reactnativenavigation.playground;
2
+
3
+import android.os.Bundle;
4
+import android.support.annotation.Nullable;
5
+import android.support.v4.app.Fragment;
6
+import android.view.LayoutInflater;
7
+import android.view.View;
8
+import android.view.ViewGroup;
9
+import android.widget.TextView;
10
+
11
+public class FragmentScreen extends Fragment {
12
+    @Nullable
13
+    @Override
14
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
15
+        View view = inflater.inflate(R.layout.fragment_screen, container, false);
16
+        ((TextView) view.findViewById(R.id.text)).setText(getArguments().getString("text"));
17
+        return view;
18
+    }
19
+}

+ 10
- 4
playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.java View File

1
 package com.reactnativenavigation.playground;
1
 package com.reactnativenavigation.playground;
2
 
2
 
3
-import android.support.annotation.*;
3
+import android.support.annotation.Nullable;
4
 
4
 
5
-import com.facebook.react.*;
6
-import com.reactnativenavigation.*;
5
+import com.facebook.react.ReactPackage;
6
+import com.reactnativenavigation.NavigationApplication;
7
 
7
 
8
-import java.util.*;
8
+import java.util.List;
9
 
9
 
10
 public class MainApplication extends NavigationApplication {
10
 public class MainApplication extends NavigationApplication {
11
 
11
 
12
+    @Override
13
+    public void onCreate() {
14
+        super.onCreate();
15
+        registerExternalComponent("RNNCustomComponent", new FragmentCreator());
16
+    }
17
+
12
     @Override
18
     @Override
13
     public boolean isDebug() {
19
     public boolean isDebug() {
14
         return BuildConfig.DEBUG;
20
         return BuildConfig.DEBUG;

+ 13
- 0
playground/android/app/src/main/res/layout/fragment_screen.xml View File

1
+<?xml version="1.0" encoding="utf-8"?>
2
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+                android:layout_width="match_parent"
4
+                android:layout_height="match_parent"
5
+                android:background="@android:color/holo_green_light">
6
+
7
+    <TextView
8
+        android:id="@+id/text"
9
+        android:layout_width="wrap_content"
10
+        android:layout_height="wrap_content"
11
+        android:layout_centerInParent="true"
12
+        android:text="@string/app_name"/>
13
+</RelativeLayout>

+ 5
- 2
playground/src/screens/WelcomeScreen.js View File

41
         <Button title='Static Lifecycle Events' testID={testIDs.PUSH_STATIC_LIFECYCLE_BUTTON} onPress={this.onClickShowStaticLifecycleOverlay} />
41
         <Button title='Static Lifecycle Events' testID={testIDs.PUSH_STATIC_LIFECYCLE_BUTTON} onPress={this.onClickShowStaticLifecycleOverlay} />
42
         <Button title='Push' testID={testIDs.PUSH_BUTTON} onPress={this.onClickPush} />
42
         <Button title='Push' testID={testIDs.PUSH_BUTTON} onPress={this.onClickPush} />
43
         <Button title='Push Options Screen' testID={testIDs.PUSH_OPTIONS_BUTTON} onPress={this.onClickPushOptionsScreen} />
43
         <Button title='Push Options Screen' testID={testIDs.PUSH_OPTIONS_BUTTON} onPress={this.onClickPushOptionsScreen} />
44
-        <Button title='Push Native Component' testID={testIDs.PUSH_NATIVE_COMPONENT_BUTTON} onPress={this.onClickPushExternalComponent} />
44
+        <Button title='Push External Component' testID={testIDs.PUSH_EXTERNAL_COMPONENT_BUTTON} onPress={this.onClickPushExternalComponent} />
45
         {Platform.OS === 'android' && <Button title='Push Top Tabs screen' testID={testIDs.PUSH_TOP_TABS_BUTTON} onPress={this.onClickPushTopTabsScreen} />}
45
         {Platform.OS === 'android' && <Button title='Push Top Tabs screen' testID={testIDs.PUSH_TOP_TABS_BUTTON} onPress={this.onClickPushTopTabsScreen} />}
46
         {Platform.OS === 'android' && <Button title='Back Handler' testID={testIDs.BACK_HANDLER_BUTTON} onPress={this.onClickBackHandler} />}
46
         {Platform.OS === 'android' && <Button title='Back Handler' testID={testIDs.BACK_HANDLER_BUTTON} onPress={this.onClickBackHandler} />}
47
         <Button title='Show Modal' testID={testIDs.SHOW_MODAL_BUTTON} onPress={this.onClickShowModal} />
47
         <Button title='Show Modal' testID={testIDs.SHOW_MODAL_BUTTON} onPress={this.onClickShowModal} />
239
   async onClickPushExternalComponent() {
239
   async onClickPushExternalComponent() {
240
     await Navigation.push(this.props.componentId, {
240
     await Navigation.push(this.props.componentId, {
241
       externalComponent: {
241
       externalComponent: {
242
-        className: 'RNNCustomViewController',
242
+        name: 'RNNCustomComponent',
243
+        passProps: {
244
+          text: 'This is an external component'
245
+        },
243
         options: {
246
         options: {
244
           topBar: {
247
           topBar: {
245
             title: 'pushed',
248
             title: 'pushed',

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

53
   OK_BUTTON: `OK_BUTTON`,
53
   OK_BUTTON: `OK_BUTTON`,
54
   MODAL_WITH_STACK_BUTTON: `MODAL_WITH_STACK_BUTTON`,
54
   MODAL_WITH_STACK_BUTTON: `MODAL_WITH_STACK_BUTTON`,
55
   CUSTOM_TRANSITION_BUTTON: `CUSTOM_TRANSITION_BUTTON`,
55
   CUSTOM_TRANSITION_BUTTON: `CUSTOM_TRANSITION_BUTTON`,
56
-  PUSH_NATIVE_COMPONENT_BUTTON: `PUSH_NATIVE_COMPONENT_BUTTON`,
56
+  PUSH_EXTERNAL_COMPONENT_BUTTON: `PUSH_EXTERNAL_COMPONENT_BUTTON`,
57
 
57
 
58
   // Elements
58
   // Elements
59
   SCROLLVIEW_ELEMENT: `SCROLLVIEW_ELEMENT`,
59
   SCROLLVIEW_ELEMENT: `SCROLLVIEW_ELEMENT`,