Procházet zdrojové kódy

Android overlay (#2557)

* Initial overlay implementation on Android

* Move logic to touch delegate

* Implement dismiss Overlay

* Overlay e2e

* fix iOS e2e
Guy Carmeli před 6 roky
rodič
revize
3150c39335
No account linked to committer's email address
26 změnil soubory, kde provedl 264 přidání a 418 odebrání
  1. 14
    16
      AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/OverlayTest.java
  2. 0
    46
      lib/android/app/src/main/java/com/reactnativenavigation/parse/ButtonOptions.java
  3. 1
    2
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java
  4. 2
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java
  5. 7
    78
      lib/android/app/src/main/java/com/reactnativenavigation/parse/OverlayOptions.java
  6. 22
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayManager.java
  7. 0
    27
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayPresenter.java
  8. 8
    6
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java
  9. 10
    1
      lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java
  10. 3
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java
  11. 6
    9
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java
  12. 0
    55
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/AlertOverlay.java
  13. 0
    35
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayFactory.java
  14. 0
    88
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/SnackbarOverlay.java
  15. 16
    2
      lib/android/app/src/main/java/com/reactnativenavigation/views/ComponentLayout.java
  16. 6
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTab.java
  17. 41
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/touch/OverlayTouchDelegate.java
  18. 59
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleOverlay.java
  19. 6
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestComponentLayout.java
  20. 5
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TopTabLayoutMock.java
  21. 0
    47
      lib/android/app/src/test/java/com/reactnativenavigation/parse/OverlayOptionsTest.java
  22. 47
    0
      lib/android/app/src/test/java/com/reactnativenavigation/views/TouchDelegateTest.java
  23. 1
    1
      lib/ios/RNNOverlayOptions.h
  24. 2
    2
      lib/ios/RNNOverlayOptions.m
  25. 6
    1
      playground/src/screens/CustomDialog.js
  26. 2
    2
      playground/src/screens/OptionsScreen.js

+ 14
- 16
AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/OverlayTest.java Zobrazit soubor

@@ -1,20 +1,18 @@
1 1
 package com.reactnativenavigation.e2e.androide2e;
2 2
 
3
-public class OverlayTest {//extends BaseTest {
3
+import android.support.test.uiautomator.By;
4 4
 
5
-//	@Test
6
-//	public void testOverlayAlertAppear() throws Exception {
7
-//		elementByText("PUSH OPTIONS SCREEN").click();
8
-//		elementByText("SHOW CUSTOM ALERT").click();
9
-//		assertExists(By.text("Test view"));
10
-//		elementByText("OK").click();
11
-//		assertExists(By.text("Static Title"));
12
-//	}
13
-//
14
-//	@Test
15
-//	public void testSnackbarAppear() throws Exception {
16
-//		elementByText("PUSH OPTIONS SCREEN").click();
17
-//		elementByText("SHOW SNACKBAR").click();
18
-//		assertExists(By.text("Test!"));
19
-//	}
5
+import org.junit.Test;
6
+
7
+public class OverlayTest extends BaseTest {
8
+
9
+	@Test
10
+	public void testOverlayAlertAppear() throws Exception {
11
+		elementByText("PUSH OPTIONS SCREEN").click();
12
+        elementByText("SHOW OVERLAY").click();
13
+		assertExists(By.text("Test view"));
14
+		elementByText("OK").click();
15
+		assertExists(By.text("Overlay disappeared"));
16
+        elementByText("OK").click();
17
+	}
20 18
 }

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

@@ -1,46 +0,0 @@
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
-}

+ 1
- 2
lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java Zobrazit soubor

@@ -10,7 +10,6 @@ import com.reactnativenavigation.viewcontrollers.ComponentViewController;
10 10
 import com.reactnativenavigation.viewcontrollers.SideMenuController;
11 11
 import com.reactnativenavigation.viewcontrollers.StackController;
12 12
 import com.reactnativenavigation.viewcontrollers.ViewController;
13
-import com.reactnativenavigation.viewcontrollers.toptabs.TopTabController;
14 13
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsController;
15 14
 import com.reactnativenavigation.views.ComponentViewCreator;
16 15
 import com.reactnativenavigation.views.TopTabsLayoutCreator;
@@ -91,7 +90,7 @@ public class LayoutFactory {
91 90
 	private ViewController createComponent(LayoutNode node) {
92 91
 		String id = node.id;
93 92
 		String name = node.data.optString("name");
94
-		Options options = Options.parse(typefaceManager, node.data.optJSONObject("options"), defaultOptions);
93
+		Options options = Options.parse(typefaceManager, node.getNavigationOptions(), defaultOptions);
95 94
 		return new ComponentViewController(activity,
96 95
                 id,
97 96
                 name,

+ 2
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java Zobrazit soubor

@@ -36,6 +36,7 @@ public class Options implements DEFAULT_VALUES {
36 36
 		result.topTabsOptions = TopTabsOptions.parse(json.optJSONObject("topTabs"));
37 37
         result.topTabOptions = TopTabOptions.parse(typefaceManager, json.optJSONObject("topTab"));
38 38
 		result.bottomTabsOptions = BottomTabsOptions.parse(json.optJSONObject("bottomTabs"));
39
+        result.overlayOptions = OverlayOptions.parse(json.optJSONObject("overlay"));
39 40
 
40 41
 		return result.withDefaultOptions(defaultOptions);
41 42
 	}
@@ -44,6 +45,7 @@ public class Options implements DEFAULT_VALUES {
44 45
     @NonNull public TopTabsOptions topTabsOptions = new TopTabsOptions();
45 46
     @NonNull public TopTabOptions topTabOptions = new TopTabOptions();
46 47
     @NonNull public BottomTabsOptions bottomTabsOptions = new BottomTabsOptions();
48
+    @NonNull public OverlayOptions overlayOptions = new OverlayOptions();
47 49
 
48 50
     void setTopTabIndex(int i) {
49 51
         topTabOptions.tabIndex = i;

+ 7
- 78
lib/android/app/src/main/java/com/reactnativenavigation/parse/OverlayOptions.java Zobrazit soubor

@@ -1,86 +1,15 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3
-
4
-import android.support.annotation.ColorInt;
5
-
6
-import com.reactnativenavigation.viewcontrollers.ViewController;
7
-
8 3
 import org.json.JSONObject;
9 4
 
10 5
 public class OverlayOptions {
6
+    public Options.BooleanOptions interceptTouchOutside = Options.BooleanOptions.False;
11 7
 
12
-	public static final int NO_COLOR = -1;
13
-
14
-	public static OverlayOptions parse(JSONObject json) {
15
-		OverlayOptions options = new OverlayOptions();
16
-		if (json == null) return options;
17
-
18
-		options.title = json.optString("title");
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 componentView) {
31
-		OverlayOptions options = new OverlayOptions();
32
-		if (componentView == null) return options;
33
-
34
-		options.customView = componentView;
35
-		return options;
36
-	}
37
-
38
-	private String title = "";
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;
50
-
51
-	public String getText() {
52
-		return text;
53
-	}
54
-
55
-	public String getTitle() {
56
-		return title;
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
-	}
8
+    public static OverlayOptions parse(JSONObject json) {
9
+        OverlayOptions options = new OverlayOptions();
10
+        if (json == null) return options;
82 11
 
83
-	public int getBackgroundColor() {
84
-		return backgroundColor;
85
-	}
12
+        options.interceptTouchOutside = Options.BooleanOptions.parse(json.optString("interceptTouchOutside", ""));
13
+        return options;
14
+    }
86 15
 }

+ 22
- 0
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayManager.java Zobrazit soubor

@@ -0,0 +1,22 @@
1
+package com.reactnativenavigation.presentation;
2
+
3
+import android.view.View;
4
+import android.view.ViewGroup;
5
+
6
+import com.reactnativenavigation.viewcontrollers.ViewController;
7
+
8
+import java.util.HashMap;
9
+
10
+public class OverlayManager {
11
+    private final HashMap<String, Integer> overlayRegistry = new HashMap<>();
12
+
13
+    public void show(ViewGroup root, ViewController overlay) {
14
+        View view = overlay.getView();
15
+        overlayRegistry.put(overlay.getId(), view.getId());
16
+        root.addView(view);
17
+    }
18
+
19
+    public void dismiss(ViewGroup root, String componentId) {
20
+        root.removeView(root.findViewById(overlayRegistry.get(componentId)));
21
+    }
22
+}

+ 0
- 27
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayPresenter.java Zobrazit soubor

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

+ 8
- 6
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java Zobrazit soubor

@@ -14,7 +14,6 @@ import com.reactnativenavigation.parse.LayoutFactory;
14 14
 import com.reactnativenavigation.parse.LayoutNode;
15 15
 import com.reactnativenavigation.parse.LayoutNodeParser;
16 16
 import com.reactnativenavigation.parse.Options;
17
-import com.reactnativenavigation.parse.OverlayOptions;
18 17
 import com.reactnativenavigation.utils.TypefaceLoader;
19 18
 import com.reactnativenavigation.utils.UiThread;
20 19
 import com.reactnativenavigation.viewcontrollers.Navigator;
@@ -100,14 +99,17 @@ public class NavigationModule extends ReactContextBaseJavaModule {
100 99
 	}
101 100
 
102 101
 	@ReactMethod
103
-	public void showOverlay(final String type, final ReadableMap data, final Promise promise) {
104
-        final OverlayOptions overlayOptions = OverlayOptions.parse(JSONParser.parse(data));
105
-        handle(() -> navigator().showOverlay(type, overlayOptions, promise));
102
+	public void showOverlay(final ReadableMap rawLayoutTree) {
103
+        final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
104
+        handle(() -> {
105
+            final ViewController viewController = newLayoutFactory().create(layoutTree);
106
+            navigator().showOverlay(viewController);
107
+        });
106 108
 	}
107 109
 
108 110
 	@ReactMethod
109
-	public void dismissOverlay() {
110
-		handle(() -> navigator().dismissOverlay());
111
+	public void dismissOverlay(final String componentId) {
112
+		handle(() -> navigator().dismissOverlay(componentId));
111 113
 	}
112 114
 
113 115
 	private NavigationActivity activity() {

+ 10
- 1
lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java Zobrazit soubor

@@ -3,11 +3,13 @@ package com.reactnativenavigation.react;
3 3
 import android.annotation.SuppressLint;
4 4
 import android.content.Context;
5 5
 import android.os.Bundle;
6
+import android.view.MotionEvent;
6 7
 import android.view.View;
7 8
 
8 9
 import com.facebook.react.ReactInstanceManager;
9 10
 import com.facebook.react.ReactRootView;
10 11
 import com.facebook.react.bridge.ReactContext;
12
+import com.facebook.react.uimanager.JSTouchDispatcher;
11 13
 import com.facebook.react.uimanager.UIManagerModule;
12 14
 import com.facebook.react.uimanager.events.EventDispatcher;
13 15
 import com.reactnativenavigation.interfaces.ScrollEventListener;
@@ -20,12 +22,14 @@ public class ReactView extends ReactRootView implements ComponentViewController.
20 22
 	private final String componentId;
21 23
 	private final String componentName;
22 24
 	private boolean isAttachedToReactInstance = false;
25
+    private final JSTouchDispatcher jsTouchDispatcher;
23 26
 
24
-	public ReactView(final Context context, ReactInstanceManager reactInstanceManager, String componentId, String componentName) {
27
+    public ReactView(final Context context, ReactInstanceManager reactInstanceManager, String componentId, String componentName) {
25 28
 		super(context);
26 29
 		this.reactInstanceManager = reactInstanceManager;
27 30
 		this.componentId = componentId;
28 31
 		this.componentName = componentName;
32
+		jsTouchDispatcher = new JSTouchDispatcher(this);
29 33
 		start();
30 34
 	}
31 35
 
@@ -74,6 +78,11 @@ public class ReactView extends ReactRootView implements ComponentViewController.
74 78
         return new ScrollEventListener(getEventDispatcher());
75 79
     }
76 80
 
81
+    @Override
82
+    public void dispatchTouchEventToJs(MotionEvent event) {
83
+        jsTouchDispatcher.handleTouchEvent(event, getEventDispatcher());
84
+    }
85
+
77 86
     public EventDispatcher getEventDispatcher() {
78 87
         ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
79 88
         return reactContext == null ? null : reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();

+ 3
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java Zobrazit soubor

@@ -31,6 +31,8 @@ public class ComponentViewController extends ViewController implements Navigatio
31 31
         void sendOnNavigationButtonPressed(String buttonId);
32 32
 
33 33
         ScrollEventListener getScrollEventListener();
34
+
35
+        void dispatchTouchEventToJs(MotionEvent event);
34 36
     }
35 37
 
36 38
     private final String componentName;
@@ -60,6 +62,7 @@ public class ComponentViewController extends ViewController implements Navigatio
60 62
     public void onViewAppeared() {
61 63
         super.onViewAppeared();
62 64
         ensureViewIsCreated();
65
+        component.applyOptions(options);
63 66
         applyOnParentController(parentController -> {
64 67
             parentController.clearOptions();
65 68
             parentController.applyOptions(options, component);

+ 6
- 9
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java Zobrazit soubor

@@ -8,9 +8,8 @@ import android.widget.FrameLayout;
8 8
 
9 9
 import com.facebook.react.bridge.Promise;
10 10
 import com.reactnativenavigation.parse.Options;
11
-import com.reactnativenavigation.parse.OverlayOptions;
12 11
 import com.reactnativenavigation.presentation.NavigationOptionsListener;
13
-import com.reactnativenavigation.presentation.OverlayPresenter;
12
+import com.reactnativenavigation.presentation.OverlayManager;
14 13
 import com.reactnativenavigation.utils.CompatUtils;
15 14
 import com.reactnativenavigation.utils.NoOpPromise;
16 15
 
@@ -22,7 +21,7 @@ public class Navigator extends ParentController {
22 21
     private static final NoOpPromise NO_OP = new NoOpPromise();
23 22
     private final ModalStack modalStack = new ModalStack();
24 23
 	private ViewController root;
25
-	private OverlayPresenter overlayPresenter;
24
+    private OverlayManager overlayManager = new OverlayManager();
26 25
     private Options defaultOptions = new Options();
27 26
 
28 27
     public Navigator(final Activity activity) {
@@ -131,14 +130,12 @@ public class Navigator extends ParentController {
131 130
 		modalStack.dismissAll(promise);
132 131
 	}
133 132
 
134
-	public void showOverlay(String type, OverlayOptions options, Promise promise) {
135
-		overlayPresenter = new OverlayPresenter(root, type, options);
136
-		overlayPresenter.show();
137
-		promise.resolve(true);
133
+	public void showOverlay(ViewController overlay) {
134
+        overlayManager.show((ViewGroup) root.getView(), overlay);
138 135
 	}
139 136
 
140
-	public void dismissOverlay() {
141
-		overlayPresenter.dismiss();
137
+	public void dismissOverlay(final String componentId) {
138
+		overlayManager.dismiss((ViewGroup) root.getView(), componentId);
142 139
 	}
143 140
 
144 141
 	static void rejectPromise(Promise promise) {

+ 0
- 55
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/AlertOverlay.java Zobrazit soubor

@@ -1,55 +0,0 @@
1
-package com.reactnativenavigation.viewcontrollers.overlay;
2
-
3
-
4
-import android.content.Context;
5
-import android.content.DialogInterface;
6
-import android.support.v7.app.AlertDialog;
7
-
8
-import com.reactnativenavigation.parse.OverlayOptions;
9
-import com.reactnativenavigation.viewcontrollers.ViewController;
10
-
11
-public class AlertOverlay implements OverlayInterface {
12
-
13
-	private AlertDialog dialog;
14
-
15
-	@Override
16
-	public AlertOverlay create(ViewController viewController, final OverlayOptions options) {
17
-		AlertDialog.Builder builder = new AlertDialog.Builder(viewController.getActivity());
18
-
19
-		builder.setTitle(options.getTitle());
20
-		builder.setMessage(options.getText());
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
-		}
39
-		dialog = builder.create();
40
-
41
-		return this;
42
-	}
43
-
44
-	@Override
45
-	public void show() {
46
-		dialog.show();
47
-	}
48
-
49
-	@Override
50
-	public void dismiss() {
51
-		if (dialog != null) {
52
-			dialog.dismiss();
53
-		}
54
-	}
55
-}

+ 0
- 35
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayFactory.java Zobrazit soubor

@@ -1,35 +0,0 @@
1
-package com.reactnativenavigation.viewcontrollers.overlay;
2
-
3
-
4
-import com.reactnativenavigation.parse.OverlayOptions;
5
-import com.reactnativenavigation.viewcontrollers.ViewController;
6
-
7
-public class OverlayFactory {
8
-
9
-	public enum Overlay {
10
-		AlertDialog("alert", new AlertOverlay()),
11
-		Snackbar("snackbar", new SnackbarOverlay());
12
-
13
-		private String name;
14
-		private OverlayInterface overlayInstance;
15
-
16
-		Overlay(String name, OverlayInterface overlayInstance) {
17
-			this.name = name;
18
-			this.overlayInstance = overlayInstance;
19
-		}
20
-
21
-		public static Overlay create(String type) {
22
-			for (Overlay overlay : values()) {
23
-				if (overlay.name.equals(type)) {
24
-					return overlay;
25
-				}
26
-			}
27
-			return AlertDialog;
28
-		}
29
-	}
30
-
31
-	public static OverlayInterface create(String type, ViewController viewController, OverlayOptions options) {
32
-		return Overlay.create(type).overlayInstance.create(viewController, options);
33
-	}
34
-
35
-}

+ 0
- 88
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/overlay/SnackbarOverlay.java Zobrazit soubor

@@ -1,88 +0,0 @@
1
-package com.reactnativenavigation.viewcontrollers.overlay;
2
-
3
-
4
-import android.app.Activity;
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;
11
-
12
-import com.reactnativenavigation.parse.OverlayOptions;
13
-import com.reactnativenavigation.viewcontrollers.ViewController;
14
-
15
-import static com.reactnativenavigation.parse.OverlayOptions.NO_COLOR;
16
-
17
-public class SnackbarOverlay implements OverlayInterface {
18
-
19
-	private Snackbar snackbar;
20
-	private OverlayOptions options;
21
-
22
-	@Override
23
-	public SnackbarOverlay create(ViewController viewController, OverlayOptions options) {
24
-		this.options = options;
25
-
26
-		init(viewController);
27
-		setAction();
28
-		setStyle();
29
-
30
-		return this;
31
-	}
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
-
46
-	@Override
47
-	public void show() {
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
-		}
87
-	}
88
-}

+ 16
- 2
lib/android/app/src/main/java/com/reactnativenavigation/views/ComponentLayout.java Zobrazit soubor

@@ -2,6 +2,7 @@ package com.reactnativenavigation.views;
2 2
 
3 3
 import android.annotation.SuppressLint;
4 4
 import android.content.Context;
5
+import android.view.MotionEvent;
5 6
 import android.view.View;
6 7
 import android.widget.FrameLayout;
7 8
 import android.widget.RelativeLayout;
@@ -9,6 +10,7 @@ import android.widget.RelativeLayout;
9 10
 import com.reactnativenavigation.interfaces.ScrollEventListener;
10 11
 import com.reactnativenavigation.parse.Options;
11 12
 import com.reactnativenavigation.viewcontrollers.ComponentViewController.IReactView;
13
+import com.reactnativenavigation.views.touch.OverlayTouchDelegate;
12 14
 
13 15
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
14 16
 import static android.widget.RelativeLayout.BELOW;
@@ -17,11 +19,13 @@ import static android.widget.RelativeLayout.BELOW;
17 19
 public class ComponentLayout extends FrameLayout implements ReactComponent, TitleBarButton.OnClickListener {
18 20
 
19 21
     private IReactView reactView;
22
+    private final OverlayTouchDelegate touchDelegate;
20 23
 
21
-	public ComponentLayout(Context context, IReactView reactView) {
24
+    public ComponentLayout(Context context, IReactView reactView) {
22 25
 		super(context);
23 26
 		this.reactView = reactView;
24 27
         addView(reactView.asView(), MATCH_PARENT, MATCH_PARENT);
28
+        touchDelegate = new OverlayTouchDelegate(reactView);
25 29
     }
26 30
 
27 31
     @Override
@@ -51,7 +55,7 @@ public class ComponentLayout extends FrameLayout implements ReactComponent, Titl
51 55
 
52 56
     @Override
53 57
     public void applyOptions(Options options) {
54
-
58
+        touchDelegate.setInterceptTouchOutside(options.overlayOptions.interceptTouchOutside == Options.BooleanOptions.True);
55 59
     }
56 60
 
57 61
     @Override
@@ -64,6 +68,11 @@ public class ComponentLayout extends FrameLayout implements ReactComponent, Titl
64 68
         return reactView.getScrollEventListener();
65 69
     }
66 70
 
71
+    @Override
72
+    public void dispatchTouchEventToJs(MotionEvent event) {
73
+        reactView.dispatchTouchEventToJs(event);
74
+    }
75
+
67 76
     @Override
68 77
     public void drawBehindTopBar() {
69 78
         RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) reactView.asView().getLayoutParams();
@@ -82,4 +91,9 @@ public class ComponentLayout extends FrameLayout implements ReactComponent, Titl
82 91
     public void onPress(String buttonId) {
83 92
         reactView.sendOnNavigationButtonPressed(buttonId);
84 93
     }
94
+
95
+    @Override
96
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
97
+        return touchDelegate.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
98
+    }
85 99
 }

+ 6
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/TopTab.java Zobrazit soubor

@@ -2,6 +2,7 @@ package com.reactnativenavigation.views;
2 2
 
3 3
 import android.annotation.SuppressLint;
4 4
 import android.content.Context;
5
+import android.view.MotionEvent;
5 6
 import android.view.View;
6 7
 import android.widget.FrameLayout;
7 8
 
@@ -53,4 +54,9 @@ public class TopTab extends FrameLayout implements IReactView {
53 54
     public ScrollEventListener getScrollEventListener() {
54 55
         return reactView.getScrollEventListener();
55 56
     }
57
+
58
+    @Override
59
+    public void dispatchTouchEventToJs(MotionEvent event) {
60
+        reactView.dispatchTouchEventToJs(event);
61
+    }
56 62
 }

+ 41
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/touch/OverlayTouchDelegate.java Zobrazit soubor

@@ -0,0 +1,41 @@
1
+package com.reactnativenavigation.views.touch;
2
+
3
+import android.graphics.Rect;
4
+import android.support.annotation.VisibleForTesting;
5
+import android.view.MotionEvent;
6
+import android.view.ViewGroup;
7
+
8
+import com.reactnativenavigation.viewcontrollers.ComponentViewController.IReactView;
9
+
10
+public class OverlayTouchDelegate {
11
+    private final Rect hitRect = new Rect();
12
+    private IReactView reactView;
13
+    private boolean interceptTouchOutside;
14
+
15
+    public OverlayTouchDelegate(IReactView reactView) {
16
+        this.reactView = reactView;
17
+    }
18
+
19
+    public boolean onInterceptTouchEvent(MotionEvent event) {
20
+        return interceptTouchOutside && isDown(event) && handleDown(event);
21
+    }
22
+
23
+    private boolean isDown(MotionEvent event) {
24
+        return event.getActionMasked() == MotionEvent.ACTION_DOWN;
25
+    }
26
+
27
+    @VisibleForTesting
28
+    public boolean handleDown(MotionEvent event) {
29
+        ((ViewGroup) reactView.asView()).getChildAt(0).getHitRect(hitRect);
30
+        reactView.dispatchTouchEventToJs(event);
31
+        return isTouchOutside(event);
32
+    }
33
+
34
+    private boolean isTouchOutside(MotionEvent ev) {
35
+        return !hitRect.contains((int) ev.getRawX(), (int) ev.getRawY());
36
+    }
37
+
38
+    public void setInterceptTouchOutside(boolean interceptTouchOutside) {
39
+        this.interceptTouchOutside = interceptTouchOutside;
40
+    }
41
+}

+ 59
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleOverlay.java Zobrazit soubor

@@ -0,0 +1,59 @@
1
+package com.reactnativenavigation.mocks;
2
+
3
+import android.content.Context;
4
+import android.view.MotionEvent;
5
+import android.view.View;
6
+import android.widget.FrameLayout;
7
+import android.widget.RelativeLayout;
8
+
9
+import com.reactnativenavigation.interfaces.ScrollEventListener;
10
+import com.reactnativenavigation.viewcontrollers.ComponentViewController;
11
+
12
+public class SimpleOverlay extends RelativeLayout implements ComponentViewController.IReactView {
13
+    public SimpleOverlay(Context context) {
14
+        super(context);
15
+    }
16
+
17
+    @Override
18
+    public boolean isReady() {
19
+        return true;
20
+    }
21
+
22
+    @Override
23
+    public View asView() {
24
+        FrameLayout root = new FrameLayout(getContext());
25
+        FrameLayout overlay = new FrameLayout(getContext());
26
+        root.addView(overlay);
27
+        return root;
28
+    }
29
+
30
+    @Override
31
+    public void destroy() {
32
+
33
+    }
34
+
35
+    @Override
36
+    public void sendComponentStart() {
37
+
38
+    }
39
+
40
+    @Override
41
+    public void sendComponentStop() {
42
+
43
+    }
44
+
45
+    @Override
46
+    public void sendOnNavigationButtonPressed(String buttonId) {
47
+
48
+    }
49
+
50
+    @Override
51
+    public ScrollEventListener getScrollEventListener() {
52
+        return null;
53
+    }
54
+
55
+    @Override
56
+    public void dispatchTouchEventToJs(MotionEvent event) {
57
+
58
+    }
59
+}

+ 6
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestComponentLayout.java Zobrazit soubor

@@ -1,6 +1,7 @@
1 1
 package com.reactnativenavigation.mocks;
2 2
 
3 3
 import android.content.Context;
4
+import android.view.MotionEvent;
4 5
 import android.view.View;
5 6
 import android.view.ViewGroup;
6 7
 import android.widget.RelativeLayout;
@@ -72,6 +73,11 @@ public class TestComponentLayout extends RelativeLayout implements ReactComponen
72 73
         return null;
73 74
     }
74 75
 
76
+    @Override
77
+    public void dispatchTouchEventToJs(MotionEvent event) {
78
+
79
+    }
80
+
75 81
     @Override
76 82
     public void onPress(String buttonId) {
77 83
 

+ 5
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TopTabLayoutMock.java Zobrazit soubor

@@ -46,4 +46,9 @@ public class TopTabLayoutMock extends View implements ComponentViewController.IR
46 46
     public ScrollEventListener getScrollEventListener() {
47 47
         return null;
48 48
     }
49
+
50
+    @Override
51
+    public void dispatchTouchEventToJs(MotionEvent event) {
52
+
53
+    }
49 54
 }

+ 0
- 47
lib/android/app/src/test/java/com/reactnativenavigation/parse/OverlayOptionsTest.java Zobrazit soubor

@@ -1,47 +0,0 @@
1
-package com.reactnativenavigation.parse;
2
-
3
-import android.graphics.Color;
4
-
5
-import com.reactnativenavigation.*;
6
-
7
-import org.json.*;
8
-import org.junit.*;
9
-
10
-import static org.assertj.core.api.Java6Assertions.*;
11
-
12
-public class OverlayOptionsTest extends BaseTest {
13
-
14
-    @Test
15
-    public void parsesNullAsDefaultEmptyOptions() throws Exception {
16
-        assertThat(OverlayOptions.parse(null)).isNotNull();
17
-    }
18
-
19
-    @Test
20
-    public void parsesJson() throws Exception {
21
-        JSONObject json = new JSONObject();
22
-        json.put("title", "the title");
23
-        json.put("text", "the text");
24
-        JSONObject nestedButton = new JSONObject();
25
-        nestedButton.put("text", "OK");
26
-        nestedButton.put("action", "action");
27
-        json.put("positiveButton", nestedButton);
28
-        json.put("textColor", Color.RED);
29
-        json.put("duration", "short");
30
-        json.put("backgroundColor", Color.RED);
31
-
32
-        OverlayOptions result = OverlayOptions.parse(json);
33
-        assertThat(result.getTitle()).isEqualTo("the title");
34
-        assertThat(result.getText()).isEqualTo("the text");
35
-        assertThat(result.getPositiveButton().getText()).isEqualTo("OK");
36
-        assertThat(result.getPositiveButton().getAction()).isEqualTo("action");
37
-        assertThat(result.getTextColor()).isEqualTo(Color.RED);
38
-        assertThat(result.getDuration()).isEqualTo("short");
39
-        assertThat(result.getBackgroundColor()).isEqualTo(Color.RED);
40
-    }
41
-
42
-    @Test
43
-    public void defaultEmptyOptions() throws Exception {
44
-        OverlayOptions uut = new OverlayOptions();
45
-        assertThat(uut.getTitle()).isEmpty();
46
-    }
47
-}

+ 47
- 0
lib/android/app/src/test/java/com/reactnativenavigation/views/TouchDelegateTest.java Zobrazit soubor

@@ -0,0 +1,47 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.view.MotionEvent;
4
+
5
+import com.reactnativenavigation.BaseTest;
6
+import com.reactnativenavigation.mocks.SimpleOverlay;
7
+import com.reactnativenavigation.views.touch.OverlayTouchDelegate;
8
+
9
+import org.junit.Test;
10
+
11
+import static org.mockito.Mockito.spy;
12
+import static org.mockito.Mockito.times;
13
+import static org.mockito.Mockito.verify;
14
+
15
+public class TouchDelegateTest extends BaseTest {
16
+    private OverlayTouchDelegate uut;
17
+    private final int x = 10;
18
+    private final int y = 10;
19
+    private final MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, x, y, 0);
20
+    private final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, x, y, 0);
21
+
22
+    @Override
23
+    public void beforeEach() {
24
+        super.beforeEach();
25
+        uut = spy(new OverlayTouchDelegate(new SimpleOverlay(newActivity())));
26
+    }
27
+
28
+    @Test
29
+    public void downEventIsHandled() throws Exception {
30
+        uut.setInterceptTouchOutside(true);
31
+        uut.onInterceptTouchEvent(downEvent);
32
+        verify(uut, times(1)).handleDown(downEvent);
33
+    }
34
+
35
+    @Test
36
+    public void onlyDownEventIsHandled() throws Exception {
37
+        uut.setInterceptTouchOutside(true);
38
+        uut.onInterceptTouchEvent(upEvent);
39
+        verify(uut, times(0)).handleDown(upEvent);
40
+    }
41
+
42
+    @Test
43
+    public void downIsHandledOnlyIfInterceptTouchOutsideIsTrue() throws Exception {
44
+        uut.onInterceptTouchEvent(downEvent);
45
+        verify(uut, times(0)).handleDown(downEvent);
46
+    }
47
+}

+ 1
- 1
lib/ios/RNNOverlayOptions.h Zobrazit soubor

@@ -2,6 +2,6 @@
2 2
 
3 3
 @interface RNNOverlayOptions : RNNOptions
4 4
 
5
-@property (nonatomic, strong) NSNumber* interceptTouches;
5
+@property (nonatomic, strong) NSNumber* interceptTouchOutside;
6 6
 
7 7
 @end

+ 2
- 2
lib/ios/RNNOverlayOptions.m Zobrazit soubor

@@ -4,9 +4,9 @@
4 4
 @implementation RNNOverlayOptions
5 5
 
6 6
 - (void)applyOn:(UIViewController *)viewController {
7
-	if (self.interceptTouches) {
7
+	if (self.interceptTouchOutside) {
8 8
 		RCTRootView* rootView = (RCTRootView*)viewController.view;
9
-		rootView.passThroughTouches = ![self.interceptTouches boolValue];
9
+		rootView.passThroughTouches = ![self.interceptTouchOutside boolValue];
10 10
 	}
11 11
 }
12 12
 

+ 6
- 1
playground/src/screens/CustomDialog.js Zobrazit soubor

@@ -1,7 +1,7 @@
1 1
 const React = require('react');
2 2
 const { PureComponent } = require('react');
3 3
 
4
-const { View, Text, Button } = require('react-native');
4
+const { Text, Button, View, Alert, Platform } = require('react-native');
5 5
 const Navigation = require('react-native-navigation');
6 6
 
7 7
 const testIDs = require('../testIDs');
@@ -17,6 +17,11 @@ class CustomDialog extends PureComponent {
17 17
     );
18 18
   }
19 19
 
20
+  didDisappear() {
21
+    if (Platform.OS === 'android') {
22
+      Alert.alert('Overlay disappeared');
23
+    }
24
+  }
20 25
   onCLickOk() {
21 26
     Navigation.dismissOverlay(this.props.componentId);
22 27
   }

+ 2
- 2
playground/src/screens/OptionsScreen.js Zobrazit soubor

@@ -194,13 +194,13 @@ class OptionsScreen extends Component {
194 194
     });
195 195
   }
196 196
 
197
-  onClickShowOverlay(interceptTouches) {
197
+  onClickShowOverlay(interceptTouchOutside) {
198 198
     Navigation.showOverlay({
199 199
       component: {
200 200
         name: 'navigation.playground.CustomDialog',
201 201
         options: {
202 202
           overlay: {
203
-            interceptTouches
203
+            interceptTouchOutside
204 204
           }
205 205
         }
206 206
       }