Browse Source

V2 fab (#2680)

* basic fab

* fab menu

* fab menu clicks

* pop lifecycle

* options lifecycle

* refactoring

* refactor

* parse unit tests

* refacotr

* fix test

* align

* fab unit tests

* more tests

* size

* hide on scroll in progress

* options

* menu collapse

* parser test

* merge v2

* refactor

* refactor

* more refactor

* more refactor

* fix

* more refactoring

* refactor

* refactor

* fix

* fix

* fixed undefined key

* refactor

* refactor

* refactort
Roman Kozlov 6 years ago
parent
commit
a84b879cde
23 changed files with 822 additions and 34 deletions
  1. 2
    0
      lib/android/app/build.gradle
  2. 7
    0
      lib/android/app/src/main/java/com/reactnativenavigation/anim/FabAnimator.java
  3. 47
    0
      lib/android/app/src/main/java/com/reactnativenavigation/anim/FabCollapseBehaviour.java
  4. 1
    0
      lib/android/app/src/main/java/com/reactnativenavigation/interfaces/ScrollEventListener.java
  5. 124
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/FabOptions.java
  6. 13
    8
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java
  7. 231
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/FabOptionsPresenter.java
  8. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java
  9. 11
    7
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java
  10. 3
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  11. 75
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/Fab.java
  12. 53
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/FabMenu.java
  13. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/StackLayout.java
  14. 4
    0
      lib/android/app/src/main/res/values/dimen.xml
  15. 45
    0
      lib/android/app/src/test/java/com/reactnativenavigation/parse/OptionsTest.java
  16. 136
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/FloatingActionButtonTest.java
  17. 4
    2
      lib/ios/RNNNavigationOptions.m
  18. 1
    0
      lib/ios/RNNOptions.h
  19. 1
    1
      lib/ios/RNNOptions.m
  20. 33
    15
      playground/src/screens/OptionsScreen.js
  21. 12
    0
      playground/src/screens/ScrollViewScreen.js
  22. 8
    0
      playground/src/screens/TopTabScreen.js
  23. 1
    1
      playground/src/testIDs.js

+ 2
- 0
lib/android/app/build.gradle View File

@@ -61,6 +61,8 @@ dependencies {
61 61
     implementation 'com.android.support:appcompat-v7:25.4.0'
62 62
     implementation 'com.android.support:support-v4:25.4.0'
63 63
     implementation 'com.aurelhubert:ahbottomnavigation:2.1.0'
64
+    implementation 'com.github.clans:fab:1.6.4'
65
+
64 66
 
65 67
     // node_modules
66 68
     //noinspection GradleDynamicVersion

+ 7
- 0
lib/android/app/src/main/java/com/reactnativenavigation/anim/FabAnimator.java View File

@@ -0,0 +1,7 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+
4
+public interface FabAnimator {
5
+    void show();
6
+    void hide();
7
+}

+ 47
- 0
lib/android/app/src/main/java/com/reactnativenavigation/anim/FabCollapseBehaviour.java View File

@@ -0,0 +1,47 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+
4
+import android.support.annotation.NonNull;
5
+
6
+import com.reactnativenavigation.interfaces.ScrollEventListener;
7
+
8
+public class FabCollapseBehaviour implements ScrollEventListener.OnScrollListener, ScrollEventListener.OnDragListener {
9
+
10
+    private FabAnimator fabAnimator;
11
+    private ScrollEventListener scrollEventListener;
12
+
13
+    public FabCollapseBehaviour(FabAnimator fabAnimator) {
14
+        this.fabAnimator = fabAnimator;
15
+    }
16
+
17
+    public void enableCollapse(@NonNull ScrollEventListener scrollEventListener) {
18
+        this.scrollEventListener = scrollEventListener;
19
+        this.scrollEventListener.register(null, this, this);
20
+    }
21
+
22
+    public void disableCollapse() {
23
+        if (scrollEventListener != null) {
24
+            scrollEventListener.unregister();
25
+        }
26
+    }
27
+
28
+    @Override
29
+    public void onScrollUp(float nextTranslation) {
30
+        //empty
31
+    }
32
+
33
+    @Override
34
+    public void onScrollDown(float nextTranslation) {
35
+        //empty
36
+    }
37
+
38
+    @Override
39
+    public void onShow() {
40
+        fabAnimator.show();
41
+    }
42
+
43
+    @Override
44
+    public void onHide() {
45
+        fabAnimator.hide();
46
+    }
47
+}

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

@@ -79,6 +79,7 @@ public class ScrollEventListener implements EventDispatcherListener {
79 79
     private void onVerticalScroll(int scrollY, int oldScrollY) {
80 80
         if (scrollY < 0) return;
81 81
         if (!dragStarted) return;
82
+        if (view == null) return;
82 83
 
83 84
         final int scrollDiff = calcScrollDiff(scrollY, oldScrollY, view.getMeasuredHeight());
84 85
         final float translationY = view.getTranslationY() - scrollDiff;

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

@@ -0,0 +1,124 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+
4
+import com.reactnativenavigation.parse.params.Bool;
5
+import com.reactnativenavigation.parse.params.NullBool;
6
+import com.reactnativenavigation.parse.parsers.BoolParser;
7
+
8
+import org.json.JSONArray;
9
+import org.json.JSONObject;
10
+
11
+import java.util.ArrayList;
12
+
13
+public class FabOptions implements DEFAULT_VALUES {
14
+
15
+    public static FabOptions parse(JSONObject json) {
16
+        FabOptions options = new FabOptions();
17
+        if (json == null) return options;
18
+
19
+        options.id = TextParser.parse(json, "id");
20
+        options.backgroundColor = ColorParser.parse(json, "backgroundColor");
21
+        options.clickColor = ColorParser.parse(json, "clickColor");
22
+        options.rippleColor = ColorParser.parse(json, "rippleColor");
23
+        options.visible = BoolParser.parse(json, "visible");
24
+        if (json.has("icon")) {
25
+            options.icon = TextParser.parse(json.optJSONObject("icon"), "uri");
26
+        }
27
+        if (json.has("actions")) {
28
+            JSONArray fabsArray = json.optJSONArray("actions");
29
+            for (int i = 0; i < fabsArray.length(); i++) {
30
+                options.actionsArray.add(FabOptions.parse(fabsArray.optJSONObject(i)));
31
+            }
32
+        }
33
+        options.alignHorizontally = TextParser.parse(json, "alignHorizontally");
34
+        options.alignVertically = TextParser.parse(json, "alignVertically");
35
+        options.hideOnScroll = BoolParser.parse(json, "hideOnScroll");
36
+        options.size = TextParser.parse(json, "size");
37
+
38
+        return options;
39
+    }
40
+
41
+    public Text id = new NullText();
42
+    public Color backgroundColor = new NullColor();
43
+    public Color clickColor = new NullColor();
44
+    public Color rippleColor = new NullColor();
45
+    public Text icon = new NullText();
46
+    public Bool visible = new NullBool();
47
+    public ArrayList<FabOptions> actionsArray = new ArrayList<>();
48
+    public Text alignHorizontally = new NullText();
49
+    public Text alignVertically = new NullText();
50
+    public Bool hideOnScroll = new NullBool();
51
+    public Text size = new NullText();
52
+
53
+    void mergeWith(final FabOptions other) {
54
+        if (other.id.hasValue()) {
55
+            id = other.id;
56
+        }
57
+        if (other.backgroundColor.hasValue()) {
58
+            backgroundColor = other.backgroundColor;
59
+        }
60
+        if (other.clickColor.hasValue()) {
61
+            clickColor = other.clickColor;
62
+        }
63
+        if (other.rippleColor.hasValue()) {
64
+            rippleColor = other.rippleColor;
65
+        }
66
+        if (other.visible.hasValue()) {
67
+            visible = other.visible;
68
+        }
69
+        if (other.icon.hasValue()) {
70
+            icon = other.icon;
71
+        }
72
+        if (other.actionsArray.size() > 0) {
73
+            actionsArray = other.actionsArray;
74
+        }
75
+        if (other.alignVertically.hasValue()) {
76
+            alignVertically = other.alignVertically;
77
+        }
78
+        if (other.alignHorizontally.hasValue()) {
79
+            alignHorizontally = other.alignHorizontally;
80
+        }
81
+        if (other.hideOnScroll.hasValue()) {
82
+            hideOnScroll = other.hideOnScroll;
83
+        }
84
+        if (other.size.hasValue()) {
85
+            size = other.size;
86
+        }
87
+    }
88
+
89
+    void mergeWithDefault(FabOptions defaultOptions) {
90
+        if (!id.hasValue()) {
91
+            id = defaultOptions.id;
92
+        }
93
+        if (!backgroundColor.hasValue()) {
94
+            backgroundColor = defaultOptions.backgroundColor;
95
+        }
96
+        if (!clickColor.hasValue()) {
97
+            clickColor = defaultOptions.clickColor;
98
+        }
99
+        if (!rippleColor.hasValue()) {
100
+            rippleColor = defaultOptions.rippleColor;
101
+        }
102
+        if (!visible.hasValue()) {
103
+            visible = defaultOptions.visible;
104
+        }
105
+        if (!icon.hasValue()) {
106
+            icon = defaultOptions.icon;
107
+        }
108
+        if (actionsArray.size() == 0) {
109
+            actionsArray = defaultOptions.actionsArray;
110
+        }
111
+        if (!alignHorizontally.hasValue()) {
112
+            alignHorizontally = defaultOptions.alignHorizontally;
113
+        }
114
+        if (!alignVertically.hasValue()) {
115
+            alignVertically = defaultOptions.alignVertically;
116
+        }
117
+        if (!hideOnScroll.hasValue()) {
118
+            hideOnScroll = defaultOptions.hideOnScroll;
119
+        }
120
+        if (!size.hasValue()) {
121
+            size = defaultOptions.size;
122
+        }
123
+    }
124
+}

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

@@ -14,20 +14,21 @@ public class Options implements DEFAULT_VALUES {
14 14
         return parse(typefaceManager, json, new Options());
15 15
     }
16 16
 
17
-	@NonNull
18
-	public static Options parse(TypefaceLoader typefaceManager, JSONObject json, @NonNull Options defaultOptions) {
19
-		Options result = new Options();
20
-		if (json == null) return result;
17
+    @NonNull
18
+    public static Options parse(TypefaceLoader typefaceManager, JSONObject json, @NonNull Options defaultOptions) {
19
+        Options result = new Options();
20
+        if (json == null) return result;
21 21
 
22
-		result.topBarOptions = TopBarOptions.parse(typefaceManager, json.optJSONObject("topBar"));
23
-		result.topTabsOptions = TopTabsOptions.parse(json.optJSONObject("topTabs"));
22
+        result.topBarOptions = TopBarOptions.parse(typefaceManager, json.optJSONObject("topBar"));
23
+        result.topTabsOptions = TopTabsOptions.parse(json.optJSONObject("topTabs"));
24 24
         result.topTabOptions = TopTabOptions.parse(typefaceManager, json.optJSONObject("topTab"));
25 25
         result.bottomTabOptions = BottomTabOptions.parse(json.optJSONObject("bottomTab"));
26 26
         result.bottomTabsOptions = BottomTabsOptions.parse(json.optJSONObject("bottomTabs"));
27 27
         result.overlayOptions = OverlayOptions.parse(json.optJSONObject("overlay"));
28
+        result.fabOptions = FabOptions.parse(json.optJSONObject("fab"));
28 29
 
29
-		return result.withDefaultOptions(defaultOptions);
30
-	}
30
+        return result.withDefaultOptions(defaultOptions);
31
+    }
31 32
 
32 33
     @NonNull public TopBarOptions topBarOptions = new TopBarOptions();
33 34
     @NonNull public TopTabsOptions topTabsOptions = new TopTabsOptions();
@@ -35,6 +36,7 @@ public class Options implements DEFAULT_VALUES {
35 36
     @NonNull public BottomTabOptions bottomTabOptions = new BottomTabOptions();
36 37
     @NonNull public BottomTabsOptions bottomTabsOptions = new BottomTabsOptions();
37 38
     @NonNull public OverlayOptions overlayOptions = new OverlayOptions();
39
+    @NonNull public FabOptions fabOptions = new FabOptions();
38 40
 
39 41
     void setTopTabIndex(int i) {
40 42
         topTabOptions.tabIndex = i;
@@ -49,6 +51,7 @@ public class Options implements DEFAULT_VALUES {
49 51
         result.bottomTabOptions.mergeWith(bottomTabOptions);
50 52
         result.bottomTabsOptions.mergeWith(bottomTabsOptions);
51 53
         result.overlayOptions = overlayOptions;
54
+        result.fabOptions.mergeWith(fabOptions);
52 55
         return result;
53 56
     }
54 57
 
@@ -60,6 +63,7 @@ public class Options implements DEFAULT_VALUES {
60 63
         result.topTabOptions.mergeWith(other.topTabOptions);
61 64
         result.bottomTabOptions.mergeWith(other.bottomTabOptions);
62 65
         result.bottomTabsOptions.mergeWith(other.bottomTabsOptions);
66
+        result.fabOptions.mergeWith(other.fabOptions);
63 67
         return result;
64 68
     }
65 69
 
@@ -69,6 +73,7 @@ public class Options implements DEFAULT_VALUES {
69 73
         topTabsOptions.mergeWithDefault(other.topTabsOptions);
70 74
         bottomTabOptions.mergeWithDefault(other.bottomTabOptions);
71 75
         bottomTabsOptions.mergeWithDefault(other.bottomTabsOptions);
76
+        fabOptions.mergeWithDefault(other.fabOptions);
72 77
         return this;
73 78
     }
74 79
 

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

@@ -0,0 +1,231 @@
1
+package com.reactnativenavigation.presentation;
2
+
3
+
4
+import android.support.annotation.NonNull;
5
+import android.view.Gravity;
6
+import android.view.View;
7
+import android.view.ViewGroup;
8
+import android.widget.FrameLayout;
9
+import android.widget.RelativeLayout;
10
+
11
+import com.reactnativenavigation.R;
12
+import com.reactnativenavigation.parse.FabOptions;
13
+import com.reactnativenavigation.parse.Options;
14
+import com.reactnativenavigation.views.Fab;
15
+import com.reactnativenavigation.views.FabMenu;
16
+import com.reactnativenavigation.views.ReactComponent;
17
+
18
+import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
19
+import static android.widget.RelativeLayout.ALIGN_PARENT_LEFT;
20
+import static android.widget.RelativeLayout.ALIGN_PARENT_RIGHT;
21
+import static android.widget.RelativeLayout.ALIGN_PARENT_TOP;
22
+import static com.github.clans.fab.FloatingActionButton.SIZE_MINI;
23
+import static com.github.clans.fab.FloatingActionButton.SIZE_NORMAL;
24
+
25
+public class FabOptionsPresenter {
26
+    private ViewGroup viewGroup;
27
+    private ReactComponent component;
28
+
29
+    private Fab fab;
30
+    private FabMenu fabMenu;
31
+
32
+    public void applyOptions(FabOptions options, @NonNull ReactComponent component, @NonNull ViewGroup viewGroup) {
33
+        this.viewGroup = viewGroup;
34
+        this.component = component;
35
+
36
+        if (options.id.hasValue()) {
37
+            if (fabMenu != null && fabMenu.getFabId().equals(options.id.get())) {
38
+                setParams(fabMenu, options);
39
+                fabMenu.bringToFront();
40
+                applyFabMenuOptions(fabMenu, options);
41
+            } else if (fab != null && fab.getFabId().equals(options.id.get())) {
42
+                setParams(fab, options);
43
+                fab.bringToFront();
44
+                applyFabOptions(fab, options);
45
+                fab.setOnClickListener(v -> component.sendOnNavigationButtonPressed(options.id.get()));
46
+            } else {
47
+                createFab(options);
48
+            }
49
+        } else {
50
+            removeFab();
51
+            removeFabMenu();
52
+        }
53
+    }
54
+
55
+    private void createFab(FabOptions options) {
56
+        if (options.actionsArray.size() > 0) {
57
+            fabMenu = new FabMenu(viewGroup.getContext(), options.id.get());
58
+            setParams(fabMenu, options);
59
+            applyFabMenuOptions(fabMenu, options);
60
+            viewGroup.addView(fabMenu);
61
+        } else {
62
+            fab = new Fab(viewGroup.getContext(), options.id.get());
63
+            setParams(fab, options);
64
+            applyFabOptions(fab, options);
65
+            fab.setOnClickListener(v -> component.sendOnNavigationButtonPressed(options.id.get()));
66
+            viewGroup.addView(fab);
67
+        }
68
+    }
69
+
70
+    private void removeFabMenu() {
71
+        if (fabMenu != null) {
72
+            fabMenu.hideMenuButton(true);
73
+            viewGroup.removeView(fabMenu);
74
+            fabMenu = null;
75
+        }
76
+    }
77
+
78
+    private void removeFab() {
79
+        if (fab != null) {
80
+            fab.hide(true);
81
+            viewGroup.removeView(fab);
82
+            fab = null;
83
+        }
84
+    }
85
+
86
+    private void setParams(View fab, FabOptions options) {
87
+        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
88
+        if (viewGroup instanceof RelativeLayout) {
89
+            RelativeLayout.LayoutParams layoutParamsRelative = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
90
+            layoutParamsRelative.bottomMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
91
+            layoutParamsRelative.rightMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
92
+            layoutParamsRelative.leftMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
93
+            layoutParamsRelative.topMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
94
+            if (options.alignHorizontally.hasValue()) {
95
+                if ("right".equals(options.alignHorizontally.get())) {
96
+                    layoutParamsRelative.removeRule(ALIGN_PARENT_LEFT);
97
+                    layoutParamsRelative.addRule(ALIGN_PARENT_RIGHT);
98
+                }
99
+                if ("left".equals(options.alignHorizontally.get())) {
100
+                    layoutParamsRelative.removeRule(ALIGN_PARENT_RIGHT);
101
+                    layoutParamsRelative.addRule(ALIGN_PARENT_LEFT);
102
+                }
103
+            } else {
104
+                layoutParamsRelative.addRule(ALIGN_PARENT_RIGHT);
105
+            }
106
+            if (options.alignVertically.hasValue()) {
107
+                if ("top".equals(options.alignVertically.get())) {
108
+                    layoutParamsRelative.removeRule(ALIGN_PARENT_BOTTOM);
109
+                    layoutParamsRelative.addRule(ALIGN_PARENT_TOP);
110
+                }
111
+                if ("bottom".equals(options.alignVertically.get())) {
112
+                    layoutParamsRelative.removeRule(ALIGN_PARENT_TOP);
113
+                    layoutParamsRelative.addRule(ALIGN_PARENT_BOTTOM);
114
+                }
115
+            } else {
116
+                layoutParamsRelative.addRule(ALIGN_PARENT_BOTTOM);
117
+            }
118
+            layoutParams = layoutParamsRelative;
119
+        }
120
+        if (viewGroup instanceof FrameLayout) {
121
+            FrameLayout.LayoutParams layoutParamsFrame = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
122
+            layoutParamsFrame.bottomMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
123
+            layoutParamsFrame.rightMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
124
+            layoutParamsFrame.leftMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
125
+            layoutParamsFrame.topMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
126
+            if (options.alignHorizontally.hasValue()) {
127
+                if ("right".equals(options.alignHorizontally.get())) {
128
+                    removeGravityParam(layoutParamsFrame, Gravity.LEFT);
129
+                    setGravityParam(layoutParamsFrame, Gravity.RIGHT);
130
+                }
131
+                if ("left".equals(options.alignHorizontally.get())) {
132
+                    removeGravityParam(layoutParamsFrame, Gravity.RIGHT);
133
+                    setGravityParam(layoutParamsFrame, Gravity.LEFT);
134
+                }
135
+            } else {
136
+                setGravityParam(layoutParamsFrame, Gravity.RIGHT);
137
+            }
138
+            if (options.alignVertically.hasValue()) {
139
+                if ("top".equals(options.alignVertically.get())) {
140
+                    removeGravityParam(layoutParamsFrame, Gravity.BOTTOM);
141
+                    setGravityParam(layoutParamsFrame, Gravity.TOP);
142
+                }
143
+                if ("bottom".equals(options.alignVertically.get())) {
144
+                    removeGravityParam(layoutParamsFrame, Gravity.TOP);
145
+                    setGravityParam(layoutParamsFrame, Gravity.BOTTOM);
146
+                }
147
+            } else {
148
+                setGravityParam(layoutParamsFrame, Gravity.BOTTOM);
149
+            }
150
+            layoutParams = layoutParamsFrame;
151
+        }
152
+        fab.setLayoutParams(layoutParams);
153
+    }
154
+
155
+    private void applyFabOptions(Fab fab, FabOptions options) {
156
+        if (options.visible.isTrueOrUndefined()) {
157
+            fab.show(true);
158
+        }
159
+        if (options.visible.isFalse()) {
160
+            fab.hide(true);
161
+        }
162
+        if (options.backgroundColor.hasValue()) {
163
+            fab.setColorNormal(options.backgroundColor.get());
164
+        }
165
+        if (options.clickColor.hasValue()) {
166
+            fab.setColorPressed(options.clickColor.get());
167
+        }
168
+        if (options.rippleColor.hasValue()) {
169
+            fab.setColorRipple(options.rippleColor.get());
170
+        }
171
+        if (options.icon.hasValue()) {
172
+            fab.applyIcon(options.icon.get());
173
+        }
174
+        if (options.size.hasValue()) {
175
+            fab.setButtonSize("mini".equals(options.size.get()) ? SIZE_MINI : SIZE_NORMAL);
176
+        }
177
+        if (options.hideOnScroll.isTrue()) {
178
+            fab.enableCollapse(component.getScrollEventListener());
179
+        }
180
+        if (options.hideOnScroll.isFalseOrUndefined()) {
181
+            fab.disableCollapse();
182
+        }
183
+    }
184
+
185
+    private void applyFabMenuOptions(FabMenu fabMenu, FabOptions options) {
186
+        if (options.visible.isTrueOrUndefined()) {
187
+            fabMenu.showMenuButton(true);
188
+        }
189
+        if (options.visible.isFalse()) {
190
+            fabMenu.hideMenuButton(true);
191
+        }
192
+
193
+        if (options.backgroundColor.hasValue()) {
194
+            fabMenu.setMenuButtonColorNormal(options.backgroundColor.get());
195
+        }
196
+        if (options.clickColor.hasValue()) {
197
+            fabMenu.setMenuButtonColorPressed(options.clickColor.get());
198
+        }
199
+        if (options.rippleColor.hasValue()) {
200
+            fabMenu.setMenuButtonColorRipple(options.rippleColor.get());
201
+        }
202
+        for (Fab fabStored : fabMenu.getActions()) {
203
+            fabMenu.removeMenuButton(fabStored);
204
+        }
205
+        fabMenu.getActions().clear();
206
+        for (FabOptions fabOption : options.actionsArray) {
207
+            Fab fab = new Fab(viewGroup.getContext(), fabOption.id.get());
208
+            applyFabOptions(fab, fabOption);
209
+            fab.setOnClickListener(v -> component.sendOnNavigationButtonPressed(options.id.get()));
210
+
211
+            fabMenu.getActions().add(fab);
212
+            fabMenu.addMenuButton(fab);
213
+        }
214
+        if (options.hideOnScroll.isTrue()) {
215
+            fabMenu.enableCollapse(component.getScrollEventListener());
216
+        }
217
+        if (options.hideOnScroll.isFalseOrUndefined()) {
218
+            fabMenu.disableCollapse();
219
+        }
220
+    }
221
+
222
+    private void setGravityParam(FrameLayout.LayoutParams params, int gravityParam) {
223
+        params.gravity = params.gravity | gravityParam;
224
+    }
225
+
226
+    private void removeGravityParam(FrameLayout.LayoutParams params, int gravityParam) {
227
+        if ((params.gravity & gravityParam) == gravityParam) {
228
+            params.gravity = params.gravity & ~gravityParam;
229
+        }
230
+    }
231
+}

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

@@ -1,10 +1,15 @@
1 1
 package com.reactnativenavigation.presentation;
2 2
 
3
+import android.view.View;
4
+
5
+import com.github.clans.fab.FloatingActionButton;
3 6
 import com.reactnativenavigation.parse.Button;
7
+import com.reactnativenavigation.parse.FabOptions;
4 8
 import com.reactnativenavigation.parse.Options;
5 9
 import com.reactnativenavigation.parse.TopBarOptions;
6 10
 import com.reactnativenavigation.parse.TopTabOptions;
7 11
 import com.reactnativenavigation.parse.TopTabsOptions;
12
+import com.reactnativenavigation.views.Fab;
8 13
 import com.reactnativenavigation.views.ReactComponent;
9 14
 import com.reactnativenavigation.views.TopBar;
10 15
 

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

@@ -27,7 +27,7 @@ public class StackController extends ParentController <StackLayout> {
27 27
     private StackLayout stackLayout;
28 28
 
29 29
     public StackController(final Activity activity, String id, Options initialOptions) {
30
-		super(activity, id, initialOptions);
30
+        super(activity, id, initialOptions);
31 31
         animator = new NavigationAnimator(activity);
32 32
     }
33 33
 
@@ -36,6 +36,9 @@ public class StackController extends ParentController <StackLayout> {
36 36
         return stackLayout.getTopBar();
37 37
     }
38 38
 
39
+    @RestrictTo(RestrictTo.Scope.TESTS)
40
+    StackLayout getStackLayout() {return stackLayout;}
41
+
39 42
     @Override
40 43
     public void applyOptions(Options options, ReactComponent component) {
41 44
         super.applyOptions(options, component);
@@ -43,6 +46,7 @@ public class StackController extends ParentController <StackLayout> {
43 46
         applyOnParentController(parentController ->
44 47
                 ((ParentController) parentController).applyOptions(this.options.copy().clearTopBarOptions(), component)
45 48
         );
49
+        fabOptionsPresenter.applyOptions(options.fabOptions, component, stackLayout);
46 50
     }
47 51
 
48 52
     @Override
@@ -66,22 +70,22 @@ public class StackController extends ParentController <StackLayout> {
66 70
     }
67 71
 
68 72
     public void animatePush(final ViewController child, final Promise promise) {
69
-		final ViewController toRemove = stack.peek();
73
+        final ViewController toRemove = stack.peek();
70 74
 
71 75
 		child.setParentController(this);
72 76
 		stack.push(child.getId(), child);
73 77
 		View enteringView = child.getView();
74 78
 		getView().addView(enteringView, MATCH_PARENT, MATCH_PARENT);
75 79
 
76
-		if (toRemove != null) {
80
+        if (toRemove != null) {
77 81
             animator.animatePush(enteringView, () -> {
78 82
                 getView().removeView(toRemove.getView());
79 83
                 promise.resolve(child.getId());
80 84
             });
81
-		} else {
82
-			promise.resolve(child.getId());
83
-		}
84
-	}
85
+        } else {
86
+            promise.resolve(child.getId());
87
+        }
88
+    }
85 89
 
86 90
     void pop(final Promise promise) {
87 91
         if (!canPop()) {

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

@@ -11,6 +11,7 @@ import android.view.ViewManager;
11 11
 import android.view.ViewTreeObserver;
12 12
 
13 13
 import com.reactnativenavigation.parse.Options;
14
+import com.reactnativenavigation.presentation.FabOptionsPresenter;
14 15
 import com.reactnativenavigation.utils.CompatUtils;
15 16
 import com.reactnativenavigation.utils.StringUtils;
16 17
 import com.reactnativenavigation.utils.Task;
@@ -40,10 +41,12 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
40 41
     private boolean isShown;
41 42
     private boolean isDestroyed;
42 43
     private ViewVisibilityListener viewVisibilityListener = new ViewVisibilityListenerAdapter();
44
+    protected FabOptionsPresenter fabOptionsPresenter;
43 45
 
44 46
     public ViewController(Activity activity, String id, Options initialOptions) {
45 47
         this.activity = activity;
46 48
         this.id = id;
49
+        fabOptionsPresenter = new FabOptionsPresenter();
47 50
         this.initialOptions = initialOptions;
48 51
         options = initialOptions.copy();
49 52
     }

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

@@ -0,0 +1,75 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.content.Context;
4
+import android.graphics.drawable.Drawable;
5
+import android.support.annotation.NonNull;
6
+
7
+import com.github.clans.fab.FloatingActionButton;
8
+import com.reactnativenavigation.anim.FabAnimator;
9
+import com.reactnativenavigation.anim.FabCollapseBehaviour;
10
+import com.reactnativenavigation.interfaces.ScrollEventListener;
11
+import com.reactnativenavigation.utils.ImageLoader;
12
+
13
+
14
+public class Fab extends FloatingActionButton implements FabAnimator {
15
+
16
+    private String id = "";
17
+    private FabCollapseBehaviour collapseBehaviour;
18
+
19
+    public Fab(Context context, String id) {
20
+        super(context);
21
+        collapseBehaviour = new FabCollapseBehaviour(this);
22
+        this.id = id;
23
+    }
24
+
25
+    public void applyIcon(String icon) {
26
+        new ImageLoader().loadIcon(getContext(), icon, new ImageLoader.ImageLoadingListener() {
27
+            @Override
28
+            public void onComplete(@NonNull Drawable drawable) {
29
+                setImageDrawable(drawable);
30
+            }
31
+
32
+            @Override
33
+            public void onError(Throwable error) {
34
+                error.printStackTrace();
35
+            }
36
+        });
37
+    }
38
+
39
+    @Override
40
+    public boolean equals(Object o) {
41
+        if (this == o) return true;
42
+        if (o == null || getClass() != o.getClass()) return false;
43
+
44
+        Fab fab = (Fab) o;
45
+
46
+        return id.equals(fab.id);
47
+    }
48
+
49
+    @Override
50
+    public int hashCode() {
51
+        return id.hashCode();
52
+    }
53
+
54
+    @Override
55
+    public void show() {
56
+        show(true);
57
+    }
58
+
59
+    @Override
60
+    public void hide() {
61
+        hide(true);
62
+    }
63
+
64
+    public void enableCollapse(@NonNull ScrollEventListener scrollEventListener) {
65
+        collapseBehaviour.enableCollapse(scrollEventListener);
66
+    }
67
+
68
+    public void disableCollapse() {
69
+        collapseBehaviour.disableCollapse();
70
+    }
71
+
72
+    public String getFabId() {
73
+        return id;
74
+    }
75
+}

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

@@ -0,0 +1,53 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.content.Context;
4
+
5
+import com.github.clans.fab.FloatingActionMenu;
6
+import com.reactnativenavigation.anim.FabAnimator;
7
+import com.reactnativenavigation.anim.FabCollapseBehaviour;
8
+import com.reactnativenavigation.interfaces.ScrollEventListener;
9
+
10
+import java.util.HashSet;
11
+
12
+
13
+public class FabMenu extends FloatingActionMenu implements FabAnimator {
14
+
15
+    private String id = "";
16
+    private HashSet<Fab> actions = new HashSet<>();
17
+
18
+    private FabCollapseBehaviour collapseBehaviour;
19
+
20
+    public FabMenu(Context context, String id) {
21
+        super(context);
22
+        this.id = id;
23
+        collapseBehaviour = new FabCollapseBehaviour(this);
24
+        onFinishInflate();
25
+        setOnMenuButtonClickListener(v -> toggle(true));
26
+    }
27
+
28
+    @Override
29
+    public void show() {
30
+        showMenu(true);
31
+    }
32
+
33
+    @Override
34
+    public void hide() {
35
+        hideMenu(true);
36
+    }
37
+
38
+    public void enableCollapse(ScrollEventListener scrollEventListener) {
39
+        collapseBehaviour.enableCollapse(scrollEventListener);
40
+    }
41
+
42
+    public void disableCollapse() {
43
+        collapseBehaviour.disableCollapse();
44
+    }
45
+
46
+    public HashSet<Fab> getActions() {
47
+        return actions;
48
+    }
49
+
50
+    public String getFabId() {
51
+        return id;
52
+    }
53
+}

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

@@ -3,9 +3,14 @@ package com.reactnativenavigation.views;
3 3
 import android.content.Context;
4 4
 import android.support.annotation.RestrictTo;
5 5
 import android.support.v4.view.ViewPager;
6
+import android.util.Log;
7
+import android.view.Gravity;
8
+import android.view.View;
6 9
 import android.widget.RelativeLayout;
7 10
 
11
+import com.github.clans.fab.FloatingActionButton;
8 12
 import com.reactnativenavigation.parse.Options;
13
+import com.reactnativenavigation.presentation.FabOptionsPresenter;
9 14
 import com.reactnativenavigation.presentation.OptionsPresenter;
10 15
 import com.reactnativenavigation.utils.CompatUtils;
11 16
 

+ 4
- 0
lib/android/app/src/main/res/values/dimen.xml View File

@@ -0,0 +1,4 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<resources>
3
+    <dimen name="margin">16dp</dimen>
4
+</resources>

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

@@ -17,7 +17,15 @@ import static org.assertj.core.api.Java6Assertions.*;
17 17
 public class OptionsTest extends BaseTest {
18 18
 
19 19
     private static final String TITLE = "the title";
20
+    private static final String FAB_ID = "FAB";
21
+    private static final String FAB_ALIGN_HORIZONTALLY = "right";
22
+    private static final String FAB_ALIGN_VERTICALLY = "bottom";
20 23
     private static final int TOP_BAR_BACKGROUND_COLOR = 0xff123456;
24
+    private static final int FAB_BACKGROUND_COLOR = android.graphics.Color.BLUE;
25
+    private static final int FAB_CLICK_COLOR = android.graphics.Color.RED;
26
+    private static final int FAB_RIPPLE_COLOR = android.graphics.Color.GREEN;
27
+    private static final Boolean FAB_VISIBLE = true;
28
+    private static final Boolean FAB_HIDE_ON_SCROLL = true;
21 29
     private static final int TOP_BAR_TEXT_COLOR = 0xff123456;
22 30
     private static final int TOP_BAR_FONT_SIZE = 18;
23 31
     private static final String TOP_BAR_FONT_FAMILY = "HelveticaNeue-CondensedBold";
@@ -47,6 +55,7 @@ public class OptionsTest extends BaseTest {
47 55
     public void parsesJson() throws Exception {
48 56
         JSONObject json = new JSONObject()
49 57
                 .put("topBar", createTopBar(TOP_BAR_VISIBLE.get()))
58
+                .put("fab", createFab())
50 59
                 .put("bottomTabs", createBottomTabs());
51 60
         Options result = Options.parse(mockLoader, json);
52 61
         assertResult(result);
@@ -65,6 +74,14 @@ public class OptionsTest extends BaseTest {
65 74
         assertThat(result.bottomTabsOptions.visible.get()).isEqualTo(BOTTOM_TABS_VISIBLE.get());
66 75
         assertThat(result.bottomTabsOptions.currentTabId.get()).isEqualTo(BOTTOM_TABS_CURRENT_TAB_ID);
67 76
         assertThat(result.bottomTabsOptions.currentTabIndex).isEqualTo(BOTTOM_TABS_CURRENT_TAB_INDEX);
77
+        assertThat(result.fabOptions.id.get()).isEqualTo(FAB_ID);
78
+        assertThat(result.fabOptions.backgroundColor.get()).isEqualTo(FAB_BACKGROUND_COLOR);
79
+        assertThat(result.fabOptions.clickColor.get()).isEqualTo(FAB_CLICK_COLOR);
80
+        assertThat(result.fabOptions.rippleColor.get()).isEqualTo(FAB_RIPPLE_COLOR);
81
+        assertThat(result.fabOptions.visible.get()).isEqualTo(FAB_VISIBLE);
82
+        assertThat(result.fabOptions.hideOnScroll.get()).isEqualTo(FAB_HIDE_ON_SCROLL);
83
+        assertThat(result.fabOptions.alignVertically.get()).isEqualTo(FAB_ALIGN_VERTICALLY);
84
+        assertThat(result.fabOptions.alignHorizontally.get()).isEqualTo(FAB_ALIGN_HORIZONTALLY);
68 85
     }
69 86
 
70 87
     @NonNull
@@ -89,6 +106,32 @@ public class OptionsTest extends BaseTest {
89 106
                 .put("hideOnScroll", TOP_BAR_HIDE_ON_SCROLL.get());
90 107
     }
91 108
 
109
+    @NonNull
110
+    private JSONObject createFab() throws JSONException {
111
+        return new JSONObject()
112
+                .put("id", FAB_ID)
113
+                .put("backgroundColor", FAB_BACKGROUND_COLOR)
114
+                .put("clickColor", FAB_CLICK_COLOR)
115
+                .put("rippleColor", FAB_RIPPLE_COLOR)
116
+                .put("alignHorizontally", FAB_ALIGN_HORIZONTALLY)
117
+                .put("alignVertically", FAB_ALIGN_VERTICALLY)
118
+                .put("hideOnScroll", FAB_HIDE_ON_SCROLL)
119
+                .put("visible", FAB_VISIBLE);
120
+    }
121
+
122
+    @NonNull
123
+    private JSONObject createOtherFab() throws JSONException {
124
+        return new JSONObject()
125
+                .put("id", "FAB")
126
+                .put("backgroundColor", FAB_BACKGROUND_COLOR)
127
+                .put("clickColor", FAB_CLICK_COLOR)
128
+                .put("rippleColor", FAB_RIPPLE_COLOR)
129
+                .put("alignHorizontally", FAB_ALIGN_HORIZONTALLY)
130
+                .put("alignVertically", FAB_ALIGN_VERTICALLY)
131
+                .put("hideOnScroll", FAB_HIDE_ON_SCROLL)
132
+                .put("visible", FAB_VISIBLE);
133
+    }
134
+
92 135
     @NonNull
93 136
     private JSONObject createOtherTopBar() throws JSONException {
94 137
         return new JSONObject()
@@ -132,6 +175,7 @@ public class OptionsTest extends BaseTest {
132 175
     public void mergeDefaultOptions() throws Exception {
133 176
         JSONObject json = new JSONObject();
134 177
         json.put("topBar", createTopBar(TOP_BAR_VISIBLE.get()));
178
+        json.put("fab", createFab());
135 179
         json.put("bottomTabs", createBottomTabs());
136 180
         Options defaultOptions = Options.parse(mockLoader, json);
137 181
         Options options = new Options();
@@ -143,6 +187,7 @@ public class OptionsTest extends BaseTest {
143 187
     public void mergedDefaultOptionsDontOverrideGivenOptions() throws Exception {
144 188
         JSONObject defaultJson = new JSONObject()
145 189
                 .put("topBar", createOtherTopBar())
190
+                .put("fab", createOtherFab())
146 191
                 .put("bottomTabs", createOtherTabBar());
147 192
         Options defaultOptions = Options.parse(mockLoader, defaultJson);
148 193
 

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

@@ -0,0 +1,136 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.support.annotation.NonNull;
5
+import android.view.View;
6
+
7
+import com.reactnativenavigation.BaseTest;
8
+import com.reactnativenavigation.mocks.MockPromise;
9
+import com.reactnativenavigation.mocks.SimpleViewController;
10
+import com.reactnativenavigation.parse.FabOptions;
11
+import com.reactnativenavigation.parse.Options;
12
+import com.reactnativenavigation.parse.Text;
13
+import com.reactnativenavigation.views.Fab;
14
+import com.reactnativenavigation.views.FabMenu;
15
+import com.reactnativenavigation.views.StackLayout;
16
+
17
+import org.junit.Test;
18
+
19
+import static org.assertj.core.api.Java6Assertions.assertThat;
20
+import static org.mockito.Mockito.spy;
21
+import static org.mockito.Mockito.verify;
22
+
23
+public class FloatingActionButtonTest extends BaseTest {
24
+
25
+    private final static int CHILD_FAB_COUNT = 3;
26
+
27
+    private StackController stackController;
28
+    private SimpleViewController childFab;
29
+    private SimpleViewController childNoFab;
30
+    private Activity activity;
31
+
32
+    @Override
33
+    public void beforeEach() {
34
+        super.beforeEach();
35
+        activity = newActivity();
36
+        stackController = new StackController(activity, "stackController", new Options());
37
+        Options options = getOptionsWithFab();
38
+        childFab = new SimpleViewController(activity, "child1", options);
39
+        childNoFab = new SimpleViewController(activity, "child2", new Options());
40
+    }
41
+
42
+    @NonNull
43
+    private Options getOptionsWithFab() {
44
+        Options options = new Options();
45
+        FabOptions fabOptions = new FabOptions();
46
+        fabOptions.id = new Text("FAB");
47
+        options.fabOptions = fabOptions;
48
+        return options;
49
+    }
50
+
51
+    @NonNull
52
+    private Options getOptionsWithFabActions() {
53
+        Options options = new Options();
54
+        FabOptions fabOptions = new FabOptions();
55
+        fabOptions.id = new Text("FAB");
56
+        for (int i = 0; i < CHILD_FAB_COUNT; i++) {
57
+            FabOptions childOptions = new FabOptions();
58
+            childOptions.id = new Text("fab" + i);
59
+            fabOptions.actionsArray.add(childOptions);
60
+        }
61
+        options.fabOptions = fabOptions;
62
+        return options;
63
+    }
64
+
65
+    private boolean hasFab() {
66
+        StackLayout stackLayout = stackController.getStackLayout();
67
+        for (int i = 0; i < stackLayout.getChildCount(); i++) {
68
+            if (stackLayout.getChildAt(i) instanceof Fab) {
69
+                return true;
70
+            }
71
+            if (stackLayout.getChildAt(i) instanceof FabMenu) {
72
+                return true;
73
+            }
74
+        }
75
+        return false;
76
+    }
77
+
78
+    @Test
79
+    public void showOnPush() throws Exception {
80
+        stackController.push(childFab, new MockPromise());
81
+        childFab.onViewAppeared();
82
+        assertThat(hasFab()).isTrue();
83
+    }
84
+
85
+    @Test
86
+    public void hideOnPush() throws Exception {
87
+        stackController.push(childFab, new MockPromise());
88
+        childFab.onViewAppeared();
89
+        assertThat(hasFab()).isTrue();
90
+        stackController.push(childNoFab, new MockPromise());
91
+        childNoFab.onViewAppeared();
92
+        assertThat(hasFab()).isFalse();
93
+    }
94
+
95
+    @Test
96
+    public void hideOnPop() throws Exception {
97
+        stackController.push(childNoFab, new MockPromise());
98
+        stackController.push(childFab, new MockPromise());
99
+        childFab.onViewAppeared();
100
+        assertThat(hasFab()).isTrue();
101
+        stackController.pop(new MockPromise());
102
+        childNoFab.onViewAppeared();
103
+        assertThat(hasFab()).isFalse();
104
+    }
105
+
106
+    @Test
107
+    public void showOnPop() throws Exception {
108
+        stackController.push(childFab, new MockPromise());
109
+        stackController.push(childNoFab, new MockPromise());
110
+        childNoFab.onViewAppeared();
111
+        assertThat(hasFab()).isFalse();
112
+        stackController.pop(new MockPromise());
113
+        childFab.onViewAppeared();
114
+        assertThat(hasFab()).isTrue();
115
+    }
116
+
117
+    @Test
118
+    public void hasChildren() throws Exception {
119
+        childFab = new SimpleViewController(activity, "child1", getOptionsWithFabActions());
120
+        stackController.push(childFab, new MockPromise());
121
+        childFab.onViewAppeared();
122
+        assertThat(hasFab()).isTrue();
123
+        assertThat(containsActions()).isTrue();
124
+    }
125
+
126
+    private boolean containsActions() {
127
+        StackLayout stackLayout = stackController.getStackLayout();
128
+        for (int i = 0; i < stackLayout.getChildCount(); i++) {
129
+            View child = stackLayout.getChildAt(i);
130
+            if (child instanceof FabMenu) {
131
+                return ((FabMenu) child).getChildCount() == CHILD_FAB_COUNT + 2;
132
+            }
133
+        }
134
+        return false;
135
+    }
136
+}

+ 4
- 2
lib/ios/RNNNavigationOptions.m View File

@@ -39,12 +39,14 @@ const NSInteger TOP_BAR_TRANSPARENT_TAG = 78264803;
39 39
 
40 40
 -(void)mergeWith:(NSDictionary *)otherOptions {
41 41
 	for (id key in otherOptions) {
42
-		if ([[self valueForKey:key] isKindOfClass:[RNNOptions class]]) {
42
+		if ([self hasProperty:key]) {
43
+			if ([[self valueForKey:key] isKindOfClass:[RNNOptions class]]) {
43 44
 			RNNOptions* options = [self valueForKey:key];
44 45
 			[options mergeWith:[otherOptions objectForKey:key]];
45 46
 		} else {
46 47
 			[self setValue:[otherOptions objectForKey:key] forKey:key];
47
-		} 
48
+		} 		
49
+		}
48 50
 	}
49 51
 }
50 52
 

+ 1
- 0
lib/ios/RNNOptions.h View File

@@ -17,5 +17,6 @@
17 17
 - (instancetype)initWithDict:(NSDictionary*)dict;
18 18
 - (void)mergeWith:(NSDictionary*)otherOptions;
19 19
 - (void)applyOn:(UIViewController *)viewController defaultOptions:(RNNOptions*)defaultOptions;
20
+- (BOOL)hasProperty:(NSString*)propName;
20 21
 
21 22
 @end

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

@@ -22,7 +22,7 @@
22 22
 	}
23 23
 }
24 24
 
25
--(BOOL)hasProperty:(NSString*)propName {
25
+- (BOOL)hasProperty:(NSString*)propName {
26 26
 	return [self respondsToSelector:NSSelectorFromString(propName)];
27 27
 }
28 28
 

+ 33
- 15
playground/src/screens/OptionsScreen.js View File

@@ -9,6 +9,7 @@ const testIDs = require('../testIDs');
9 9
 const BUTTON_ONE = 'buttonOne';
10 10
 const BUTTON_TWO = 'buttonTwo';
11 11
 const BUTTON_LEFT = 'buttonLeft';
12
+const FAB = 'fab';
12 13
 
13 14
 class OptionsScreen extends Component {
14 15
   static get options() {
@@ -39,6 +40,28 @@ class OptionsScreen extends Component {
39 40
           title: 'Left',
40 41
           buttonColor: 'purple'
41 42
         }]
43
+      },
44
+      fab: {
45
+        id: FAB,
46
+        backgroundColor: 'orange',
47
+        clickColor: 'orange',
48
+        rippleColor: 'red',
49
+        alignHorizontally: 'left',
50
+        actions: [
51
+          {
52
+            id: 'fab1',
53
+            backgroundColor: 'blue',
54
+            clickColor: 'blue',
55
+            rippleColor: 'aquamarine',
56
+          },
57
+          {
58
+            id: 'fab2',
59
+            backgroundColor: 'blueviolet',
60
+            clickColor: 'blueviolet',
61
+            size: 'mini',
62
+            rippleColor: 'aquamarine',
63
+          }
64
+        ]
42 65
       }
43 66
     };
44 67
   }
@@ -54,6 +77,7 @@ class OptionsScreen extends Component {
54 77
     this.onClickCustomTranstition = this.onClickCustomTranstition.bind(this);
55 78
     this.onClickShowOverlay = this.onClickShowOverlay.bind(this);
56 79
     this.onClickPushDefaultOptionsScreen = this.onClickPushDefaultOptionsScreen.bind(this);
80
+    this.onClickFab = this.onClickFab.bind(this);
57 81
   }
58 82
 
59 83
   render() {
@@ -67,8 +91,9 @@ class OptionsScreen extends Component {
67 91
         <Button title='Top Bar Opaque' onPress={this.onClickTopBarOpaque} />
68 92
         <Button title='scrollView Screen' testID={testIDs.SCROLLVIEW_SCREEN_BUTTON} onPress={this.onClickScrollViewScreen} />
69 93
         <Button title='Custom Transition' testID={testIDs.CUSTOM_TRANSITION_BUTTON} onPress={this.onClickCustomTranstition} />
70
-        <Button title='Show custom alert' testID={testIDs.SHOW_CUSTOM_ALERT_BUTTON} onPress={this.onClickAlert} />
71
-        <Button title='Show snackbar' testID={testIDs.SHOW_SNACKBAR_BUTTON} onPress={this.onClickSnackbar} />
94
+        {Platform.OS === 'android' ?
95
+          <Button title='Hide fab' testID={testIDs.HIDE_FAB} onPress={this.onClickFab} />
96
+          : null}
72 97
         <Button title='Show overlay' testID={testIDs.SHOW_OVERLAY_BUTTON} onPress={() => this.onClickShowOverlay(true)} />
73 98
         <Button title='Show touch through overlay' testID={testIDs.SHOW_TOUCH_THROUGH_OVERLAY_BUTTON} onPress={() => this.onClickShowOverlay(false)} />
74 99
         <Button title='Push Default Options Screen' testID={testIDs.PUSH_DEFAULT_OPTIONS_BUTTON} onPress={this.onClickPushDefaultOptionsScreen} />
@@ -180,19 +205,12 @@ class OptionsScreen extends Component {
180 205
     });
181 206
   }
182 207
 
183
-  onClickAlert() {
184
-    Navigation.showOverlay('custom', 'navigation.playground.CustomDialog');
185
-  }
186
-
187
-  onClickSnackbar() {
188
-    Navigation.showOverlay('snackbar', {
189
-      text: 'Test!',
190
-      // textColor: 'red',
191
-      // backgroundColor: 'green',
192
-      duration: 'long',
193
-      button: {
194
-        text: 'Action',
195
-        textColor: 'blue'
208
+  onClickFab() {
209
+    Navigation.setOptions(this.props.componentId, {
210
+      fab: {
211
+        id: FAB,
212
+        visible: false
213
+        // backgroundColor: 'green'
196 214
       }
197 215
     });
198 216
   }

+ 12
- 0
playground/src/screens/ScrollViewScreen.js View File

@@ -6,6 +6,8 @@ const { StyleSheet, ScrollView, View, Button, Platform } = require('react-native
6 6
 const { Navigation } = require('react-native-navigation');
7 7
 const testIDs = require('../testIDs');
8 8
 
9
+const FAB = 'fab';
10
+
9 11
 class ScrollViewScreen extends Component {
10 12
   static get options() {
11 13
     return {
@@ -15,6 +17,13 @@ class ScrollViewScreen extends Component {
15 17
         textColor: 'black',
16 18
         textFontSize: 16,
17 19
         testID: testIDs.TOP_BAR_ELEMENT
20
+      },
21
+      fab: {
22
+        id: FAB,
23
+        backgroundColor: 'blue',
24
+        clickColor: 'blue',
25
+        rippleColor: 'aquamarine',
26
+        hideOnScroll: true
18 27
       }
19 28
     };
20 29
   }
@@ -49,6 +58,9 @@ class ScrollViewScreen extends Component {
49 58
     Navigation.setOptions(this.props.componentId, {
50 59
       topBar: {
51 60
         hideOnScroll: this.state.topBarHideOnScroll
61
+      },
62
+      fab: {
63
+        hideOnScroll: !this.state.topBarHideOnScroll
52 64
       }
53 65
     });
54 66
   }

+ 8
- 0
playground/src/screens/TopTabScreen.js View File

@@ -2,6 +2,8 @@ const React = require('react');
2 2
 const { PureComponent } = require('react');
3 3
 const { View, Text } = require('react-native');
4 4
 
5
+const FAB = 'fab';
6
+
5 7
 class TopTabScreen extends PureComponent {
6 8
   static get options() {
7 9
     return {
@@ -9,6 +11,12 @@ class TopTabScreen extends PureComponent {
9 11
         textColor: 'black',
10 12
         textFontSize: 16,
11 13
         textFontFamily: 'HelveticaNeue-Italic'
14
+      },
15
+      fab: {
16
+        id: FAB,
17
+        backgroundColor: 'blue',
18
+        clickColor: 'blue',
19
+        rippleColor: 'aquamarine'
12 20
       }
13 21
     };
14 22
   }

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

@@ -35,7 +35,7 @@ module.exports = {
35 35
   SHOW_TOP_BAR_BUTTON: `SHOW_TOP_BAR_BUTTON`,
36 36
   HIDE_TOP_BAR_BUTTON: `HIDE_TOP_BAR_BUTTON`,
37 37
   SCROLLVIEW_SCREEN_BUTTON: `SCROLLVIEW_SCREEN_BUTTON`,
38
-  SHOW_CUSTOM_ALERT_BUTTON: `SHOW_CUSTOM_ALERT_BUTTON`,
38
+  HIDE_FAB: `HIDE_FAB`,
39 39
   SHOW_SNACKBAR_BUTTON: `SHOW_SNACKBAR_BUTTON`,
40 40
   TOGGLE_TOP_BAR_HIDE_ON_SCROLL: `TOGGLE_TOP_BAR_HIDE_ON_SCROLL`,
41 41
   SET_TAB_BADGE_BUTTON: `SET_TAB_BADGE_BUTTON`,