Browse Source

V2 overlay (#2105)

* V2 overlay skeleton (#2097)

* skeleton

* basic test

* remove useless files

* remove useles files

* semicolon

* refactoring

* rm useless files

* alert dialog more styles

* hot fix

* tests

* e2e with own alert

* fix test

* fix ios e2e

* custom dialog WIP

* WIP

* refactor custom dialog

* fix e2e

* gitignore

* fix js tests

* remove hardcode

* snackbar

* drop FAB for now

* fix test
Roman Kozlov 6 years ago
parent
commit
9de8bf4540
31 changed files with 518 additions and 84 deletions
  1. 11
    8
      .gitignore
  2. 8
    2
      AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/OverlayTest.java
  3. 3
    4
      README.md
  4. 2
    2
      e2e/TopLevelApi.test.js
  5. 46
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/ButtonOptions.java
  6. 10
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java
  7. 2
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutNode.java
  8. 59
    3
      lib/android/app/src/main/java/com/reactnativenavigation/parse/OverlayOptions.java
  9. 8
    7
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayPresenter.java
  10. 28
    3
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java
  11. 7
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java
  12. 28
    8
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/AlertOverlay.java
  13. 18
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/CustomOverlay.java
  14. 27
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/DialogViewController.java
  15. 0
    21
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/FabOverlay.java
  16. 6
    6
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayFactory.java
  17. 4
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayInterface.java
  18. 70
    3
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/SnackbarOverlay.java
  19. 29
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/CustomDialog.java
  20. 14
    0
      lib/android/app/src/test/java/com/reactnativenavigation/parse/OverlayOptionsTest.java
  21. 4
    0
      lib/src/Navigation.js
  22. 5
    0
      lib/src/adapters/NativeCommandsSender.js
  23. 15
    3
      lib/src/commands/Commands.js
  24. 13
    0
      lib/src/commands/Commands.test.js
  25. 8
    0
      lib/src/commands/LayoutTreeParser.js
  26. 6
    0
      lib/src/commands/LayoutTreeParser.test.js
  27. 2
    1
      lib/src/commands/LayoutTypes.js
  28. 47
    0
      playground/src/containers/CustomDialog.js
  29. 21
    5
      playground/src/containers/LifecycleScreen.js
  30. 15
    3
      playground/src/containers/OptionsScreen.js
  31. 2
    0
      playground/src/containers/index.js

+ 11
- 8
.gitignore View File

@@ -210,16 +210,19 @@ npm-debug.log
210 210
 # BUCK
211 211
 buck-out/
212 212
 \.buckd/
213
+
214
+#IDE files
213 215
 android/app/libs
214 216
 android/keystores/debug.keystore
215
-AndroidE2E/app/.settings
216
-AndroidE2E/.settings
217
-AndroidE2E/app/.project
218
-AndroidE2E/app/.classpath
219 217
 AndroidE2E/.project
220
-lib/android/.settings
218
+AndroidE2E/app/.project
221 219
 lib/android/.project
222
-lib/android/app/.settings
223
-playground/android/.settings
224 220
 playground/android/.project
225
-playground/android/app/.settings
221
+AndroidE2E/app/.classpath
222
+AndroidE2E/app/.settings/
223
+AndroidE2E/app/bin/
224
+lib/android/.settings/
225
+lib/android/app/.settings/
226
+playground/android/.settings/
227
+playground/android/app/.settings/
228
+AndroidE2E/.settings/

+ 8
- 2
AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/OverlayTest.java View File

@@ -11,10 +11,16 @@ public class OverlayTest extends BaseTest {
11 11
 	@Test
12 12
 	public void testOverlayAlertAppear() throws Exception {
13 13
 		elementByText("PUSH OPTIONS SCREEN").click();
14
-		elementByText("SHOW ALERT").click();
15
-		assertExists(By.text("test!"));
14
+		elementByText("SHOW CUSTOM ALERT").click();
15
+		assertExists(By.text("Test view"));
16 16
 		elementByText("OK").click();
17 17
 		assertExists(By.text("Static Title"));
18
+	}
18 19
 
20
+	@Test
21
+	public void testSnackbarAppear() throws Exception {
22
+		elementByText("PUSH OPTIONS SCREEN").click();
23
+		elementByText("SHOW SNACKBAR").click();
24
+		assertExists(By.text("Test!"));
19 25
 	}
20 26
 }

+ 3
- 4
README.md View File

@@ -65,12 +65,11 @@ v2 is written in Test Driven Development. We have a test for every feature inclu
65 65
 | resetTo             |   ✅        |	✅|
66 66
 | showModal              |  ✅        |	✅|
67 67
 | dismissModal           |     ✅       |	✅|
68
-| showOverlay             |  [Contribute](/docs/docs/CONTRIBUTING.md)         |	WIP @cool04ek |
69
-| dismissOverlay             |  [Contribute](/docs/docs/CONTRIBUTING.md)         |	WIP @cool04ek |
68
+| showOverlay             |  [Contribute](/docs/docs/CONTRIBUTING.md)         |	 |
69
+| dismissOverlay             |  [Contribute](/docs/docs/CONTRIBUTING.md)         |	 |
70 70
 | customTransition            |   ✅        |	[Contribute](/docs/docs/CONTRIBUTING.md) |
71 71
 | Screen Visibility        | ✅     |✅|
72
-| async commands (await push)     |  [Contribute](/docs/docs/CONTRIBUTING.md)        | ✅   |
73
-
72
+| async commands (await push)     |  [Contribute](/docs/docs/CONTRIBUTING.md)        |[Contribute](/docs/docs/CONTRIBUTING.md)   |
74 73
 
75 74
 ### Navigation Options
76 75
 

+ 2
- 2
e2e/TopLevelApi.test.js View File

@@ -35,9 +35,9 @@ describe('top level api', () => {
35 35
     await elementByLabel('Push Lifecycle Screen').tap();
36 36
     await expect(elementByLabel('didAppear')).toBeVisible();
37 37
     await Utils.tapBackIos();
38
-    await expect(elementByLabel('didDisappear')).toBeVisible();
39
-    await element(by.traits(['button']).and(by.label('OK'))).atIndex(0).tap();
40 38
     await expect(elementByLabel('componentWillUnmount')).toBeVisible();
39
+    await element(by.traits(['button']).and(by.label('OK'))).atIndex(0).tap();
40
+    await expect(elementByLabel('didDisappear')).toBeVisible();
41 41
   });
42 42
 });
43 43
 

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

@@ -0,0 +1,46 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+
4
+import android.support.annotation.ColorInt;
5
+
6
+import org.json.JSONObject;
7
+
8
+import static com.reactnativenavigation.parse.OverlayOptions.NO_COLOR;
9
+
10
+public class ButtonOptions {
11
+
12
+	public static ButtonOptions parse(JSONObject json) {
13
+		ButtonOptions options = new ButtonOptions();
14
+		if (json == null) return new ButtonOptions();
15
+
16
+		//TODO: parse
17
+		options.text = json.optString("text");
18
+		options.action = json.optString("action");
19
+		options.visible = json.optBoolean("visible", true);
20
+		options.textColor = json.optInt("textColor", NO_COLOR);
21
+
22
+		return options;
23
+	}
24
+
25
+	private String text = "";
26
+	private String action;
27
+	private boolean visible = false;
28
+	@ColorInt
29
+	private int textColor;
30
+
31
+	public String getText() {
32
+		return text;
33
+	}
34
+
35
+	public String getAction() {
36
+		return action;
37
+	}
38
+
39
+	public boolean isVisible() {
40
+		return visible;
41
+	}
42
+
43
+	public int getTextColor() {
44
+		return textColor;
45
+	}
46
+}

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

@@ -9,6 +9,7 @@ import com.reactnativenavigation.viewcontrollers.ContainerViewController;
9 9
 import com.reactnativenavigation.viewcontrollers.SideMenuController;
10 10
 import com.reactnativenavigation.viewcontrollers.StackController;
11 11
 import com.reactnativenavigation.viewcontrollers.ViewController;
12
+import com.reactnativenavigation.viewcontrollers.overlay.DialogViewController;
12 13
 import com.reactnativenavigation.views.TopbarContainerViewCreator;
13 14
 
14 15
 import java.util.ArrayList;
@@ -40,6 +41,8 @@ public class LayoutFactory {
40 41
 				return createSideMenuLeft(node);
41 42
 			case SideMenuRight:
42 43
 				return createSideMenuRight(node);
44
+			case CustomDialog:
45
+				return createDialogContainer(node);
43 46
 			default:
44 47
 				throw new IllegalArgumentException("Invalid node type: " + node.type);
45 48
 		}
@@ -103,4 +106,11 @@ public class LayoutFactory {
103 106
 		tabsContainer.setTabs(tabs);
104 107
 		return tabsContainer;
105 108
 	}
109
+
110
+	private ViewController createDialogContainer(LayoutNode node) {
111
+		String id = node.id;
112
+		String name = node.data.optString("name");
113
+		ReactContainerViewCreator creator = new ReactContainerViewCreator(reactInstanceManager);
114
+		return new DialogViewController(activity, id, name, creator);
115
+	}
106 116
 }

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

@@ -13,7 +13,8 @@ public class LayoutNode {
13 13
 		SideMenuRoot,
14 14
 		SideMenuCenter,
15 15
 		SideMenuLeft,
16
-		SideMenuRight
16
+		SideMenuRight,
17
+		CustomDialog
17 18
 	}
18 19
 
19 20
 	public final String id;

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

@@ -1,24 +1,52 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3 3
 
4
+import android.support.annotation.ColorInt;
5
+
6
+import com.reactnativenavigation.viewcontrollers.ViewController;
7
+
4 8
 import org.json.JSONObject;
5 9
 
6 10
 public class OverlayOptions {
7 11
 
8
-	public static OverlayOptions parse(JSONObject json) {
9
-		if (json == null) return new OverlayOptions();
12
+	public static final int NO_COLOR = -1;
10 13
 
14
+	public static OverlayOptions parse(JSONObject json) {
11 15
 		OverlayOptions options = new OverlayOptions();
16
+		if (json == null) return options;
12 17
 
13
-		//TODO: parse
14 18
 		options.title = json.optString("title");
15 19
 		options.text = json.optString("text");
20
+		options.positiveButton = ButtonOptions.parse(json.optJSONObject("positiveButton"));
21
+		options.negativeButton = ButtonOptions.parse(json.optJSONObject("negativeButton"));
22
+		options.button = ButtonOptions.parse(json.optJSONObject("button"));
23
+		options.textColor = json.optInt("textColor", NO_COLOR);
24
+		options.duration = json.optString("duration");
25
+		options.backgroundColor = json.optInt("backgroundColor", NO_COLOR);
26
+
27
+		return options;
28
+	}
29
+
30
+	public static OverlayOptions create(ViewController containerView) {
31
+		OverlayOptions options = new OverlayOptions();
32
+		if (containerView == null) return options;
16 33
 
34
+		options.customView = containerView;
17 35
 		return options;
18 36
 	}
19 37
 
20 38
 	private String title = "";
21 39
 	private String text = "";
40
+	private ButtonOptions positiveButton;
41
+	private ButtonOptions negativeButton;
42
+	private ButtonOptions button;
43
+	@ColorInt
44
+	private int textColor;
45
+	private String duration;
46
+	@ColorInt
47
+	private int backgroundColor;
48
+
49
+	private ViewController customView;
22 50
 
23 51
 	public String getText() {
24 52
 		return text;
@@ -27,4 +55,32 @@ public class OverlayOptions {
27 55
 	public String getTitle() {
28 56
 		return title;
29 57
 	}
58
+
59
+	public ButtonOptions getPositiveButton() {
60
+		return positiveButton;
61
+	}
62
+
63
+	public ButtonOptions getNegativeButton() {
64
+		return negativeButton;
65
+	}
66
+
67
+	public ViewController getCustomView() {
68
+		return customView;
69
+	}
70
+
71
+	public ButtonOptions getButton() {
72
+		return button;
73
+	}
74
+
75
+	public int getTextColor() {
76
+		return textColor;
77
+	}
78
+
79
+	public String getDuration() {
80
+		return duration;
81
+	}
82
+
83
+	public int getBackgroundColor() {
84
+		return backgroundColor;
85
+	}
30 86
 }

+ 8
- 7
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayPresenter.java View File

@@ -4,23 +4,24 @@ package com.reactnativenavigation.presentation;
4 4
 import android.content.Context;
5 5
 
6 6
 import com.reactnativenavigation.parse.OverlayOptions;
7
-import com.reactnativenavigation.viewcontrollers.overlay.AlertOverlay;
8
-import com.reactnativenavigation.viewcontrollers.overlay.CustomOverlay;
9
-import com.reactnativenavigation.viewcontrollers.overlay.FabOverlay;
10
-import com.reactnativenavigation.viewcontrollers.overlay.OverlayFabric;
7
+import com.reactnativenavigation.viewcontrollers.ViewController;
8
+import com.reactnativenavigation.viewcontrollers.overlay.OverlayFactory;
11 9
 import com.reactnativenavigation.viewcontrollers.overlay.OverlayInterface;
12
-import com.reactnativenavigation.viewcontrollers.overlay.SnackbarOverlay;
13 10
 
14 11
 public class OverlayPresenter {
15 12
 
16 13
 	private OverlayInterface overlay;
17 14
 
18
-	public OverlayPresenter(Context context, String type, OverlayOptions options) {
19
-		this.overlay = OverlayFabric.create(type, context, options);
15
+	public OverlayPresenter(ViewController viewController, String type, OverlayOptions options) {
16
+		this.overlay = OverlayFactory.create(type, viewController, options);
20 17
 	}
21 18
 
22 19
 
23 20
 	public void show() {
24 21
 		overlay.show();
25 22
 	}
23
+
24
+	public void dismiss() {
25
+		overlay.dismiss();
26
+	}
26 27
 }

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

@@ -16,8 +16,10 @@ import com.reactnativenavigation.parse.JSONParser;
16 16
 import com.reactnativenavigation.parse.LayoutNodeParser;
17 17
 import com.reactnativenavigation.parse.OverlayOptions;
18 18
 import com.reactnativenavigation.utils.UiThread;
19
+import com.reactnativenavigation.viewcontrollers.ContainerViewController;
19 20
 import com.reactnativenavigation.viewcontrollers.Navigator;
20 21
 import com.reactnativenavigation.viewcontrollers.ViewController;
22
+import com.reactnativenavigation.viewcontrollers.overlay.OverlayFactory;
21 23
 
22 24
 import org.json.JSONObject;
23 25
 
@@ -133,12 +135,35 @@ public class NavigationModule extends ReactContextBaseJavaModule {
133 135
 	}
134 136
 
135 137
 	@ReactMethod
136
-	public void showOverlay(final String type, final ReadableMap options, final Promise promise) {
137
-		final OverlayOptions overlayOptions = OverlayOptions.parse(JSONParser.parse(options));
138
+	public void showOverlay(final String type, final ReadableMap data, final Promise promise) {
139
+		if (OverlayFactory.Overlay.create(type) == OverlayFactory.Overlay.CustomDialog) {
140
+			final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(data));
141
+			handle(new Runnable() {
142
+				@Override
143
+				public void run() {
144
+					ViewController viewController = newLayoutFactory().create(layoutTree);
145
+					navigator().showOverlay(type, OverlayOptions.create(viewController), promise);
146
+				}
147
+			});
148
+		} else {
149
+			final OverlayOptions overlayOptions = OverlayOptions.parse(JSONParser.parse(data));
150
+			handle(new Runnable() {
151
+				@Override
152
+				public void run() {
153
+					navigator().showOverlay(type, overlayOptions, promise);
154
+				}
155
+			});
156
+		}
157
+
158
+
159
+	}
160
+
161
+	@ReactMethod
162
+	public void dismissOverlay() {
138 163
 		handle(new Runnable() {
139 164
 			@Override
140 165
 			public void run() {
141
-				navigator().showOverlay(type, overlayOptions, promise);
166
+				navigator().dismissOverlay();
142 167
 			}
143 168
 		});
144 169
 	}

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

@@ -21,6 +21,7 @@ public class Navigator extends ParentController {
21 21
 
22 22
 	private final ModalStack modalStack = new ModalStack();
23 23
 	private ViewController root;
24
+	private OverlayPresenter overlayPresenter;
24 25
 
25 26
 	public Navigator(final Activity activity) {
26 27
 		super(activity, "navigator" + CompatUtils.generateViewId());
@@ -170,10 +171,15 @@ public class Navigator extends ParentController {
170 171
 	}
171 172
 
172 173
 	public void showOverlay(String type, OverlayOptions options, Promise promise) {
173
-		new OverlayPresenter(getActivity(), type, options).show();
174
+		overlayPresenter = new OverlayPresenter(root, type, options);
175
+		overlayPresenter.show();
174 176
 		promise.resolve(true);
175 177
 	}
176 178
 
179
+	public void dismissOverlay() {
180
+		overlayPresenter.dismiss();
181
+	}
182
+
177 183
 	static void rejectPromise(Promise promise) {
178 184
 		if (promise != null) {
179 185
 			promise.reject(new Throwable("Nothing to pop"));

+ 28
- 8
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/AlertOverlay.java View File

@@ -6,23 +6,36 @@ import android.content.DialogInterface;
6 6
 import android.support.v7.app.AlertDialog;
7 7
 
8 8
 import com.reactnativenavigation.parse.OverlayOptions;
9
+import com.reactnativenavigation.viewcontrollers.ViewController;
9 10
 
10 11
 public class AlertOverlay implements OverlayInterface {
11 12
 
12 13
 	private AlertDialog dialog;
13 14
 
14 15
 	@Override
15
-	public AlertOverlay create(Context context, OverlayOptions options) {
16
-		AlertDialog.Builder builder = new AlertDialog.Builder(context);
16
+	public AlertOverlay create(ViewController viewController, final OverlayOptions options) {
17
+		AlertDialog.Builder builder = new AlertDialog.Builder(viewController.getActivity());
17 18
 
18 19
 		builder.setTitle(options.getTitle());
19 20
 		builder.setMessage(options.getText());
20
-		builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
21
-			@Override
22
-			public void onClick(DialogInterface dialog, int which) {
23
-				dialog.dismiss();
24
-			}
25
-		});
21
+		if (options.getPositiveButton().isVisible()) {
22
+			builder.setPositiveButton(options.getPositiveButton().getText(), new DialogInterface.OnClickListener() {
23
+				@Override
24
+				public void onClick(DialogInterface dialog, int which) {
25
+					//TODO: perform action options.getPositiveButton().getAction();
26
+					dialog.dismiss();
27
+				}
28
+			});
29
+		}
30
+		if (options.getNegativeButton().isVisible()) {
31
+			builder.setNegativeButton(options.getNegativeButton().getText(), new DialogInterface.OnClickListener() {
32
+				@Override
33
+				public void onClick(DialogInterface dialog, int which) {
34
+					//TODO: perform action options.getNegativeButton().getAction();
35
+					dialog.dismiss();
36
+				}
37
+			});
38
+		}
26 39
 		dialog = builder.create();
27 40
 
28 41
 		return this;
@@ -32,4 +45,11 @@ public class AlertOverlay implements OverlayInterface {
32 45
 	public void show() {
33 46
 		dialog.show();
34 47
 	}
48
+
49
+	@Override
50
+	public void dismiss() {
51
+		if (dialog != null) {
52
+			dialog.dismiss();
53
+		}
54
+	}
35 55
 }

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

@@ -4,17 +4,33 @@ package com.reactnativenavigation.viewcontrollers.overlay;
4 4
 import android.content.Context;
5 5
 
6 6
 import com.reactnativenavigation.parse.OverlayOptions;
7
+import com.reactnativenavigation.viewcontrollers.ContainerViewController;
8
+import com.reactnativenavigation.viewcontrollers.ViewController;
9
+import com.reactnativenavigation.views.CustomDialog;
7 10
 
8 11
 public class CustomOverlay implements OverlayInterface {
9 12
 
13
+	private CustomDialog dialog;
14
+
10 15
 	@Override
11
-	public CustomOverlay create(Context context, OverlayOptions options) {
16
+	public CustomOverlay create(ViewController root, OverlayOptions options) {
12 17
 		//TODO; implement
18
+
19
+		ViewController viewController = options.getCustomView();
20
+		dialog = new CustomDialog(root.getActivity(), viewController.getView());
21
+
13 22
 		return this;
14 23
 	}
15 24
 
16 25
 	@Override
17 26
 	public void show() {
18
-		//TODO; implement
27
+		dialog.show();
28
+	}
29
+
30
+	@Override
31
+	public void dismiss() {
32
+		if (dialog != null) {
33
+			dialog.dismiss();
34
+		}
19 35
 	}
20 36
 }

+ 27
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/DialogViewController.java View File

@@ -0,0 +1,27 @@
1
+package com.reactnativenavigation.viewcontrollers.overlay;
2
+
3
+import android.app.Activity;
4
+import android.support.annotation.NonNull;
5
+import android.view.View;
6
+
7
+import com.reactnativenavigation.viewcontrollers.ContainerViewController;
8
+import com.reactnativenavigation.viewcontrollers.ViewController;
9
+
10
+
11
+public class DialogViewController extends ViewController {
12
+
13
+	private ContainerViewController.ContainerViewCreator viewCreator;
14
+	private String containerName;
15
+
16
+	public DialogViewController(Activity activity, String id, String containerName, ContainerViewController.ContainerViewCreator viewCreator) {
17
+		super(activity, id);
18
+		this.viewCreator = viewCreator;
19
+		this.containerName = containerName;
20
+	}
21
+
22
+	@NonNull
23
+	@Override
24
+	protected View createView() {
25
+		return viewCreator.create(getActivity(), getId(), containerName).asView();
26
+	}
27
+}

+ 0
- 21
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/FabOverlay.java View File

@@ -1,21 +0,0 @@
1
-package com.reactnativenavigation.viewcontrollers.overlay;
2
-
3
-
4
-import android.content.Context;
5
-
6
-import com.reactnativenavigation.parse.OverlayOptions;
7
-
8
-public class FabOverlay implements OverlayInterface {
9
-
10
-
11
-	@Override
12
-	public FabOverlay create(Context context, OverlayOptions options) {
13
-		//TODO; implement
14
-		return this;
15
-	}
16
-
17
-	@Override
18
-	public void show() {
19
-		//TODO; implement
20
-	}
21
-}

lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayFabric.java → lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayFactory.java View File

@@ -4,13 +4,13 @@ package com.reactnativenavigation.viewcontrollers.overlay;
4 4
 import android.content.Context;
5 5
 
6 6
 import com.reactnativenavigation.parse.OverlayOptions;
7
+import com.reactnativenavigation.viewcontrollers.ViewController;
7 8
 
8
-public class OverlayFabric {
9
+public class OverlayFactory {
9 10
 
10
-	private enum Overlay {
11
+	public enum Overlay {
11 12
 		AlertDialog("alert", new AlertOverlay()),
12 13
 		Snackbar("snackbar", new SnackbarOverlay()),
13
-		Fab("fab", new FabOverlay()),
14 14
 		CustomDialog("custom", new CustomOverlay());
15 15
 
16 16
 		private String name;
@@ -27,12 +27,12 @@ public class OverlayFabric {
27 27
 					return overlay;
28 28
 				}
29 29
 			}
30
-			return CustomDialog;
30
+			return AlertDialog;
31 31
 		}
32 32
 	}
33 33
 
34
-	public static OverlayInterface create(String type, Context context, OverlayOptions options) {
35
-		return Overlay.create(type).overlayInstance.create(context, options);
34
+	public static OverlayInterface create(String type, ViewController viewController, OverlayOptions options) {
35
+		return Overlay.create(type).overlayInstance.create(viewController, options);
36 36
 	}
37 37
 
38 38
 }

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

@@ -1,11 +1,14 @@
1 1
 package com.reactnativenavigation.viewcontrollers.overlay;
2 2
 
3 3
 
4
+import android.app.Activity;
4 5
 import android.content.Context;
5 6
 
6 7
 import com.reactnativenavigation.parse.OverlayOptions;
8
+import com.reactnativenavigation.viewcontrollers.ViewController;
7 9
 
8 10
 public interface OverlayInterface {
9
-	OverlayInterface create(Context context, OverlayOptions options);
11
+	OverlayInterface create(ViewController viewController, OverlayOptions options);
10 12
 	void show();
13
+	void dismiss();
11 14
 }

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

@@ -1,21 +1,88 @@
1 1
 package com.reactnativenavigation.viewcontrollers.overlay;
2 2
 
3 3
 
4
+import android.app.Activity;
4 5
 import android.content.Context;
6
+import android.support.design.widget.Snackbar;
7
+import android.text.SpannableStringBuilder;
8
+import android.text.Spanned;
9
+import android.text.style.ForegroundColorSpan;
10
+import android.view.View;
5 11
 
6 12
 import com.reactnativenavigation.parse.OverlayOptions;
13
+import com.reactnativenavigation.viewcontrollers.ViewController;
14
+
15
+import static com.reactnativenavigation.parse.OverlayOptions.NO_COLOR;
7 16
 
8 17
 public class SnackbarOverlay implements OverlayInterface {
9 18
 
19
+	private Snackbar snackbar;
20
+	private OverlayOptions options;
10 21
 
11 22
 	@Override
12
-	public SnackbarOverlay create(Context context, OverlayOptions options) {
13
-		//TODO; implement
23
+	public SnackbarOverlay create(ViewController viewController, OverlayOptions options) {
24
+		this.options = options;
25
+
26
+		init(viewController);
27
+		setAction();
28
+		setStyle();
29
+
14 30
 		return this;
15 31
 	}
16 32
 
33
+	private void init(ViewController viewController) {
34
+		snackbar = Snackbar.make(viewController.getView(),
35
+				options.getTextColor() == NO_COLOR ? options.getText() : getStyledText(),
36
+				getDuration(options.getDuration()));
37
+	}
38
+
39
+	private Spanned getStyledText() {
40
+		SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(options.getText());
41
+		spannableStringBuilder.setSpan(new ForegroundColorSpan(options.getTextColor()), 0,
42
+				spannableStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
43
+		return spannableStringBuilder;
44
+	}
45
+
17 46
 	@Override
18 47
 	public void show() {
19
-		//TODO; implement
48
+		snackbar.show();
49
+	}
50
+
51
+	@Override
52
+	public void dismiss() {
53
+		snackbar.dismiss();
54
+	}
55
+
56
+	private int getDuration(String duration) {
57
+		switch (duration) {
58
+			case "short":
59
+				return Snackbar.LENGTH_SHORT;
60
+			case "long":
61
+				return Snackbar.LENGTH_LONG;
62
+			case "indefinite":
63
+				return Snackbar.LENGTH_INDEFINITE;
64
+			default:
65
+				return Snackbar.LENGTH_SHORT;
66
+		}
67
+	}
68
+
69
+	private void setAction() {
70
+		if (options.getButton() != null) {
71
+			snackbar.setAction(options.getButton().getText(), new View.OnClickListener() {
72
+				@Override
73
+				public void onClick(View v) {
74
+					//TODO: perform action
75
+				}
76
+			});
77
+		}
78
+	}
79
+
80
+	private void setStyle() {
81
+		if (options.getButton() != null && options.getButton().getTextColor() != NO_COLOR) {
82
+			snackbar.setActionTextColor(options.getButton().getTextColor());
83
+		}
84
+		if (options.getBackgroundColor() != NO_COLOR) {
85
+			snackbar.getView().setBackgroundColor(options.getBackgroundColor());
86
+		}
20 87
 	}
21 88
 }

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

@@ -0,0 +1,29 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.app.Dialog;
4
+import android.content.Context;
5
+import android.support.annotation.NonNull;
6
+import android.util.Log;
7
+import android.view.View;
8
+import android.view.ViewGroup;
9
+import android.view.ViewTreeObserver;
10
+import android.view.WindowManager;
11
+
12
+import com.facebook.react.ReactRootView;
13
+
14
+
15
+public class CustomDialog extends Dialog {
16
+
17
+	private View container;
18
+
19
+	public CustomDialog(@NonNull Context context, @NonNull View container) {
20
+		super(context);
21
+
22
+		this.container = container;
23
+		init();
24
+	}
25
+
26
+	private void init() {
27
+		addContentView(container, new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
28
+	}
29
+}

+ 14
- 0
lib/android/app/src/test/java/com/reactnativenavigation/parse/OverlayOptionsTest.java View File

@@ -1,5 +1,7 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3
+import android.graphics.Color;
4
+
3 5
 import com.reactnativenavigation.BaseTest;
4 6
 
5 7
 import org.json.JSONObject;
@@ -20,10 +22,22 @@ public class OverlayOptionsTest extends BaseTest {
20 22
 		JSONObject json = new JSONObject();
21 23
 		json.put("title", "the title");
22 24
 		json.put("text", "the text");
25
+		JSONObject nestedButton = new JSONObject();
26
+		nestedButton.put("text", "OK");
27
+		nestedButton.put("action", "action");
28
+		json.put("positiveButton", nestedButton);
29
+		json.put("textColor", Color.RED);
30
+		json.put("duration", "short");
31
+		json.put("backgroundColor", Color.RED);
23 32
 
24 33
 		OverlayOptions result = OverlayOptions.parse(json);
25 34
 		assertThat(result.getTitle()).isEqualTo("the title");
26 35
 		assertThat(result.getText()).isEqualTo("the text");
36
+		assertThat(result.getPositiveButton().getText()).isEqualTo("OK");
37
+		assertThat(result.getPositiveButton().getAction()).isEqualTo("action");
38
+		assertThat(result.getTextColor()).isEqualTo(Color.RED);
39
+		assertThat(result.getDuration()).isEqualTo("short");
40
+		assertThat(result.getBackgroundColor()).isEqualTo(Color.RED);
27 41
 	}
28 42
 
29 43
 	@Test

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

@@ -71,6 +71,10 @@ class Navigation {
71 71
   showOverlay(type, options) {
72 72
     return this.commands.showOverlay(type, options);
73 73
   }
74
+
75
+  dismissOverlay() {
76
+    return this.commands.dismissOverlay();
77
+  }
74 78
 }
75 79
 
76 80
 const singleton = new Navigation();

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

@@ -44,6 +44,11 @@ class NativeCommandsSender {
44 44
   showOverlay(type, options) {
45 45
     return this.nativeCommandsModule.showOverlay(type, options);
46 46
   }
47
+
48
+  dismissOverlay() {
49
+    this.nativeCommandsModule.dismissOverlay();
50
+    return Promise.resolve(true);
51
+  }
47 52
 }
48 53
 
49 54
 module.exports = NativeCommandsSender;

+ 15
- 3
lib/src/commands/Commands.js View File

@@ -56,9 +56,21 @@ class Commands {
56 56
   }
57 57
 
58 58
   showOverlay(type, options) {
59
-    const input = _.cloneDeep(options);
60
-    OptionsProcessor.processOptions(input);
61
-    return this.nativeCommandsSender.showOverlay(type, input);
59
+    let promise;
60
+    if (type === 'custom') {
61
+      const layout = this.layoutTreeParser.createDialogContainer({ name: options });
62
+      this.layoutTreeCrawler.crawl(layout);
63
+      promise = this.nativeCommandsSender.showOverlay(type, layout);
64
+    } else {
65
+      const input = _.cloneDeep(options);
66
+      OptionsProcessor.processOptions(input);
67
+      promise = this.nativeCommandsSender.showOverlay(type, input);
68
+    }
69
+    return promise;
70
+  }
71
+
72
+  dismissOverlay() {
73
+    return this.nativeCommandsSender.dismissOverlay();
62 74
   }
63 75
 }
64 76
 

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

@@ -229,5 +229,18 @@ describe('Commands', () => {
229 229
       uut.showOverlay('alert', obj);
230 230
       expect(mockCommandsSender.showOverlay.mock.calls[0][1]).not.toBe(obj);
231 231
     });
232
+
233
+    it('resolves with the parsed layout', async () => {
234
+      mockCommandsSender.showOverlay.mockReturnValue(Promise.resolve('result'));
235
+      const result = await uut.showOverlay('custom', 'com.example.MyScreen');
236
+      expect(result).toEqual('result');
237
+    });
238
+  });
239
+
240
+  describe('dismissOverlay', () => {
241
+    it('check promis returns true', () => {
242
+      uut.dismissOverlay();
243
+      expect(mockCommandsSender.dismissOverlay).toHaveBeenCalledTimes(1);
244
+    });
232 245
   });
233 246
 });

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

@@ -23,6 +23,14 @@ class LayoutTreeParser {
23 23
     };
24 24
   }
25 25
 
26
+  createDialogContainer(data) {
27
+    return {
28
+      type: LayoutTypes.CustomDialog,
29
+      data,
30
+      children: []
31
+    };
32
+  }
33
+
26 34
   _createTabs(tabs) {
27 35
     return {
28 36
       type: LayoutTypes.BottomTabs,

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

@@ -292,4 +292,10 @@ describe('LayoutTreeParser', () => {
292 292
       expect(uut.createContainer({ foo: 'bar' })).toEqual({ type: 'Container', data: { foo: 'bar' }, children: [] });
293 293
     });
294 294
   });
295
+
296
+  describe('createDialogCOntainer', () => {
297
+    it('creates dialog container object with passed data', () => {
298
+      expect(uut.createDialogContainer({ foo: 'bar' })).toEqual({ type: 'CustomDialog', data: { foo: 'bar' }, children: [] });
299
+    });
300
+  });
295 301
 });

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

@@ -5,5 +5,6 @@ module.exports = {
5 5
   SideMenuRoot: 'SideMenuRoot',
6 6
   SideMenuCenter: 'SideMenuCenter',
7 7
   SideMenuLeft: 'SideMenuLeft',
8
-  SideMenuRight: 'SideMenuRight'
8
+  SideMenuRight: 'SideMenuRight',
9
+  CustomDialog: 'CustomDialog'
9 10
 };

+ 47
- 0
playground/src/containers/CustomDialog.js View File

@@ -0,0 +1,47 @@
1
+const React = require('react');
2
+const { PureComponent } = require('react');
3
+
4
+const { View, Text, Button } = require('react-native');
5
+const Navigation = require('react-native-navigation');
6
+
7
+class CustomDialog extends PureComponent {
8
+
9
+  render() {
10
+    return (
11
+      <View style={styles.root}>
12
+        <Text style={styles.h1}>Test view</Text>
13
+        <Button title="OK" onPress={this.onCLickOk} />
14
+      </View>
15
+    );
16
+  }
17
+
18
+  onCLickOk() {
19
+    Navigation.dismissOverlay();
20
+  }
21
+}
22
+
23
+const styles = {
24
+  root: {
25
+    flex: 1,
26
+    backgroundColor: 'green',
27
+    justifyContent: 'center',
28
+    alignItems: 'center'
29
+  },
30
+  h1: {
31
+    fontSize: 24,
32
+    textAlign: 'center',
33
+    margin: 10
34
+  },
35
+  h2: {
36
+    fontSize: 12,
37
+    textAlign: 'center',
38
+    margin: 10
39
+  },
40
+  footer: {
41
+    fontSize: 10,
42
+    color: '#888',
43
+    marginTop: 10
44
+  }
45
+};
46
+
47
+module.exports = CustomDialog;

+ 21
- 5
playground/src/containers/LifecycleScreen.js View File

@@ -1,7 +1,7 @@
1 1
 const React = require('react');
2 2
 const { Component } = require('react');
3 3
 
4
-const { View, Text, Button } = require('react-native');
4
+const { View, Text, Button, Platform } = require('react-native');
5 5
 
6 6
 const Navigation = require('react-native-navigation');
7 7
 
@@ -19,13 +19,29 @@ class LifecycleScreen extends Component {
19 19
   }
20 20
 
21 21
   didDisappear() {
22
-    setTimeout(() => {
22
+    if (Platform.OS === 'ios') {
23 23
       alert('didDisappear'); // eslint-disable-line no-alert
24
-    }, 1000);
24
+    } else {
25
+      Navigation.showOverlay('alert', {
26
+        text: 'didDisappear',
27
+        positiveButton: {
28
+          text: 'OK'
29
+        }
30
+      });
31
+    }
25 32
   }
26 33
 
27 34
   componentWillUnmount() {
28
-    alert('componentWillUnmount'); // eslint-disable-line no-alert
35
+    if (Platform.OS === 'ios') {
36
+      alert('componentWillUnmount'); // eslint-disable-line no-alert
37
+    } else {
38
+      Navigation.showOverlay('alert', {
39
+        text: 'componentWillUnmount',
40
+        positiveButton: {
41
+          text: 'OK'
42
+        }
43
+      });
44
+    }
29 45
   }
30 46
 
31 47
   onNavigationButtonPressed(id) {
@@ -37,7 +53,7 @@ class LifecycleScreen extends Component {
37 53
       <View style={styles.root}>
38 54
         <Text style={styles.h1}>{`Lifecycle Screen`}</Text>
39 55
         <Text style={styles.h1}>{this.state.text}</Text>
40
-        <Button title="Push to test didDisappear" onPress={this.onClickPush}/>
56
+        <Button title="Push to test didDisappear" onPress={this.onClickPush} />
41 57
         <Text style={styles.footer}>{`this.props.containerId = ${this.props.containerId}`}</Text>
42 58
       </View>
43 59
     );

+ 15
- 3
playground/src/containers/OptionsScreen.js View File

@@ -50,7 +50,8 @@ class OptionsScreen extends Component {
50 50
         <Button title="Show Top Bar" onPress={this.onClickShowTopBar} />
51 51
         <Button title="Hide Top Bar" onPress={this.onClickHideTopBar} />
52 52
         <Button title="scrollView Screen" onPress={this.onClickScrollViewScreen} />
53
-        <Button title="Show alert" onPress={this.onClickAlert} />
53
+        <Button title="Show custom alert" onPress={this.onClickAlert} />
54
+        <Button title="Show snackbar" onPress={this.onClickSnackbar} />
54 55
         <Text style={styles.footer}>{`this.props.containerId = ${this.props.containerId}`}</Text>
55 56
       </View>
56 57
     );
@@ -122,8 +123,19 @@ class OptionsScreen extends Component {
122 123
   }
123 124
 
124 125
   onClickAlert() {
125
-    Navigation.showOverlay('alert', {
126
-      text: 'test!'
126
+    Navigation.showOverlay('custom', 'navigation.playground.CustomDialog');
127
+  }
128
+
129
+  onClickSnackbar() {
130
+    Navigation.showOverlay('snackbar', {
131
+      text: 'Test!',
132
+      // textColor: 'red',
133
+      // backgroundColor: 'green',
134
+      duration: 'long',
135
+      button: {
136
+        text: 'Action',
137
+        textColor: 'blue'
138
+      }
127 139
     });
128 140
   }
129 141
 }

+ 2
- 0
playground/src/containers/index.js View File

@@ -8,6 +8,7 @@ const OptionsScreen = require('./OptionsScreen');
8 8
 const OrientationSelectScreen = require('./OrientationSelectScreen');
9 9
 const OrientationDetectScreen = require('./OrientationDetectScreen');
10 10
 const ScrollViewScreen = require('./ScrollViewScreen');
11
+const CustomDialog = require('./CustomDialog');
11 12
 
12 13
 function registerContainers() {
13 14
   Navigation.registerContainer(`navigation.playground.ScrollViewScreen`, () => ScrollViewScreen);
@@ -19,6 +20,7 @@ function registerContainers() {
19 20
   Navigation.registerContainer(`navigation.playground.OptionsScreen`, () => OptionsScreen);
20 21
   Navigation.registerContainer(`navigation.playground.OrientationSelectScreen`, () => OrientationSelectScreen);
21 22
   Navigation.registerContainer(`navigation.playground.OrientationDetectScreen`, () => OrientationDetectScreen);
23
+  Navigation.registerContainer('navigation.playground.CustomDialog', () => CustomDialog);
22 24
 }
23 25
 
24 26
 module.exports = {