Browse Source

Top bar buttons android (#2876)

* TopBar custom buttons initial commit

* f

* Consolidate TopBarButtonController and TopBarButton

* Move Toolbar related stuff to TitleBar

* TitleBarTest

* More tests
Guy Carmeli 6 years ago
parent
commit
21d2103475
No account linked to committer's email address
34 changed files with 838 additions and 323 deletions
  1. 2
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java
  2. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java
  3. 10
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Button.java
  4. 7
    2
      lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java
  5. 22
    0
      lib/android/app/src/main/java/com/reactnativenavigation/react/TopBarReactButtonView.java
  6. 16
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ImageLoadingListenerAdapter.java
  7. 12
    10
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java
  8. 192
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TopBarButtonController.java
  9. 3
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  10. 2
    1
      lib/android/app/src/main/java/com/reactnativenavigation/views/ComponentLayout.java
  11. 5
    7
      lib/android/app/src/main/java/com/reactnativenavigation/views/StackLayout.java
  12. 140
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TitleBar.java
  13. 0
    176
      lib/android/app/src/main/java/com/reactnativenavigation/views/TitleBarButton.java
  14. 23
    81
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java
  15. 21
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopBarButtonCreator.java
  16. 2
    1
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsViewPager.java
  17. 2
    2
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestComponentLayout.java
  18. 28
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TopBarButtonCreatorMock.java
  19. 4
    3
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/BottomTabsControllerTest.java
  20. 2
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ComponentViewControllerTest.java
  21. 4
    4
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/FloatingActionButtonTest.java
  22. 4
    3
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java
  23. 5
    4
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java
  24. 3
    2
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java
  25. 7
    6
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java
  26. 124
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TitleBarTest.java
  27. 57
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarButtonControllerTest.java
  28. 3
    2
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java
  29. 2
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java
  30. 8
    6
      lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarTest.java
  31. 55
    0
      playground/src/screens/CustomRoundedButton.js
  32. 46
    0
      playground/src/screens/CustomTextButton.js
  33. 21
    7
      playground/src/screens/OptionsScreen.js
  34. 4
    0
      playground/src/screens/index.js

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

@@ -15,6 +15,7 @@ import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalCompo
15 15
 import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentViewController;
16 16
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsController;
17 17
 import com.reactnativenavigation.views.ComponentViewCreator;
18
+import com.reactnativenavigation.views.TopBarButtonCreator;
18 19
 import com.reactnativenavigation.views.TopTabsLayoutCreator;
19 20
 
20 21
 import java.util.ArrayList;
@@ -117,7 +118,7 @@ public class LayoutFactory {
117 118
     }
118 119
 
119 120
 	private ViewController createStack(LayoutNode node) {
120
-        StackController stackController = new StackController(activity, node.id, getOptions(node));
121
+        StackController stackController = new StackController(activity, new TopBarButtonCreator(reactInstanceManager), node.id, getOptions(node));
121 122
         addChildrenToStack(node.children, stackController);
122 123
         return stackController;
123 124
 	}

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

@@ -55,8 +55,8 @@ public class TopBarOptions implements DEFAULT_VALUES {
55 55
     public Bool animate = new NullBool();
56 56
     public Bool hideOnScroll = new NullBool();
57 57
     public Bool drawBehind = new NullBool();
58
-    public ArrayList<Button> leftButtons;
59
-    public ArrayList<Button> rightButtons;
58
+    @Nullable public ArrayList<Button> leftButtons;
59
+    @Nullable public ArrayList<Button> rightButtons;
60 60
 
61 61
     void mergeWith(final TopBarOptions other) {
62 62
         if (other.testId.hasValue()) {

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

@@ -24,6 +24,7 @@ public class Button {
24 24
 	private Text buttonFontWeight = new NullText();
25 25
 	public Text icon = new NullText();
26 26
 	public Text testId = new NullText();
27
+    public Text component = new NullText();
27 28
 
28 29
 	private static Button parseJson(JSONObject json)  {
29 30
 		Button button = new Button();
@@ -36,6 +37,7 @@ public class Button {
36 37
 		button.buttonFontSize = json.optInt("buttonFontSize", NO_INT_VALUE);
37 38
 		button.buttonFontWeight = TextParser.parse(json, "buttonFontWeight");
38 39
         button.testId = TextParser.parse(json, "testID");
40
+        button.component = TextParser.parse(json, "component");
39 41
 
40 42
 		if (json.has("icon")) {
41 43
 			button.icon = TextParser.parse(json.optJSONObject("icon"), "uri");
@@ -60,6 +62,14 @@ public class Button {
60 62
 		return buttons;
61 63
 	}
62 64
 
65
+    public boolean hasComponent() {
66
+        return component.hasValue();
67
+    }
68
+
69
+    public boolean hasIcon() {
70
+        return icon.hasValue();
71
+    }
72
+
63 73
 	private static int parseShowAsAction(JSONObject json) {
64 74
 	    final Text showAsAction = TextParser.parse(json, "showAsAction");
65 75
 		if (!showAsAction.hasValue()) {

+ 7
- 2
lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java View File

@@ -3,8 +3,8 @@ 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.support.annotation.RestrictTo;
6 7
 import android.view.MotionEvent;
7
-import android.view.View;
8 8
 
9 9
 import com.facebook.react.ReactInstanceManager;
10 10
 import com.facebook.react.ReactRootView;
@@ -49,7 +49,7 @@ public class ReactView extends ReactRootView implements IReactView {
49 49
 	}
50 50
 
51 51
 	@Override
52
-	public View asView() {
52
+	public ReactView asView() {
53 53
 		return this;
54 54
 	}
55 55
 
@@ -87,4 +87,9 @@ public class ReactView extends ReactRootView implements IReactView {
87 87
         ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
88 88
         return reactContext == null ? null : reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
89 89
     }
90
+
91
+    @RestrictTo(RestrictTo.Scope.TESTS)
92
+    public String getComponentName() {
93
+        return componentName;
94
+    }
90 95
 }

+ 22
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/TopBarReactButtonView.java View File

@@ -0,0 +1,22 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import android.annotation.SuppressLint;
4
+import android.content.Context;
5
+
6
+import com.facebook.react.ReactInstanceManager;
7
+
8
+@SuppressLint("ViewConstructor")
9
+public class TopBarReactButtonView extends ReactView {
10
+
11
+    public TopBarReactButtonView(Context context, ReactInstanceManager reactInstanceManager, String componentId, String componentName) {
12
+        super(context, reactInstanceManager, componentId, componentName);
13
+    }
14
+
15
+    @Override
16
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
17
+        super.onMeasure(
18
+                getChildCount() > 0 ? MeasureSpec.makeMeasureSpec(getChildAt(0).getWidth(), MeasureSpec.EXACTLY) : widthMeasureSpec,
19
+                heightMeasureSpec
20
+        );
21
+    }
22
+}

+ 16
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/ImageLoadingListenerAdapter.java View File

@@ -0,0 +1,16 @@
1
+package com.reactnativenavigation.utils;
2
+
3
+import android.graphics.drawable.Drawable;
4
+import android.support.annotation.NonNull;
5
+
6
+public class ImageLoadingListenerAdapter implements ImageLoader.ImageLoadingListener {
7
+    @Override
8
+    public void onComplete(@NonNull Drawable drawable) {
9
+
10
+    }
11
+
12
+    @Override
13
+    public void onError(Throwable error) {
14
+        error.printStackTrace();
15
+    }
16
+}

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

@@ -25,20 +25,14 @@ public class StackController extends ParentController <StackLayout> {
25 25
     private static final NoOpPromise NO_OP = new NoOpPromise();
26 26
     private final IdStack<ViewController> stack = new IdStack<>();
27 27
     private final NavigationAnimator animator;
28
+    private ReactViewCreator topBarButtonCreator;
28 29
 
29
-    public StackController(final Activity activity, String id, Options initialOptions) {
30
+    public StackController(final Activity activity, ReactViewCreator topBarButtonCreator, String id, Options initialOptions) {
30 31
         super(activity, id, initialOptions);
31 32
         animator = new NavigationAnimator(activity);
33
+        this.topBarButtonCreator = topBarButtonCreator;
32 34
     }
33 35
 
34
-    @RestrictTo(RestrictTo.Scope.TESTS)
35
-    TopBar getTopBar() {
36
-        return getView().getTopBar();
37
-    }
38
-
39
-    @RestrictTo(RestrictTo.Scope.TESTS)
40
-    StackLayout getStackLayout() {return getView();}
41
-
42 36
     public void applyOptions(Options options) {
43 37
         super.applyOptions(options);
44 38
         getView().applyOptions(options);
@@ -208,7 +202,7 @@ public class StackController extends ParentController <StackLayout> {
208 202
     @NonNull
209 203
     @Override
210 204
     protected StackLayout createView() {
211
-        return new StackLayout(getActivity(), this::sendOnNavigationButtonPressed);
205
+        return new StackLayout(getActivity(), topBarButtonCreator, this::sendOnNavigationButtonPressed);
212 206
     }
213 207
 
214 208
 	@NonNull
@@ -226,4 +220,12 @@ public class StackController extends ParentController <StackLayout> {
226 220
     public void clearTopTabs() {
227 221
         getView().clearTopTabs();
228 222
     }
223
+
224
+    @RestrictTo(RestrictTo.Scope.TESTS)
225
+    TopBar getTopBar() {
226
+        return getView().getTopBar();
227
+    }
228
+
229
+    @RestrictTo(RestrictTo.Scope.TESTS)
230
+    StackLayout getStackLayout() {return getView();}
229 231
 }

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

@@ -0,0 +1,192 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.graphics.Color;
5
+import android.graphics.drawable.Drawable;
6
+import android.support.annotation.NonNull;
7
+import android.support.v7.widget.ActionMenuView;
8
+import android.support.v7.widget.Toolbar;
9
+import android.text.Spannable;
10
+import android.text.SpannableString;
11
+import android.text.style.AbsoluteSizeSpan;
12
+import android.util.Log;
13
+import android.view.MenuItem;
14
+import android.view.View;
15
+import android.widget.ImageButton;
16
+import android.widget.TextView;
17
+
18
+import com.reactnativenavigation.parse.Options;
19
+import com.reactnativenavigation.parse.params.Button;
20
+import com.reactnativenavigation.parse.params.Text;
21
+import com.reactnativenavigation.react.TopBarReactButtonView;
22
+import com.reactnativenavigation.utils.ArrayUtils;
23
+import com.reactnativenavigation.utils.ImageLoader;
24
+import com.reactnativenavigation.utils.ImageLoadingListenerAdapter;
25
+import com.reactnativenavigation.utils.UiUtils;
26
+import com.reactnativenavigation.utils.ViewUtils;
27
+
28
+import java.util.ArrayList;
29
+import java.util.List;
30
+
31
+public class TopBarButtonController extends ViewController<TopBarReactButtonView> implements MenuItem.OnMenuItemClickListener {
32
+    public interface OnClickListener {
33
+        void onPress(String buttonId);
34
+    }
35
+
36
+    private final Button button;
37
+    private final ReactViewCreator viewCreator;
38
+    private TopBarButtonController.OnClickListener onPressListener;
39
+    private Drawable icon;
40
+
41
+    public TopBarButtonController(Activity activity, Button button, ReactViewCreator viewCreator, TopBarButtonController.OnClickListener onClickListener) {
42
+        super(activity, button.id, new Options());
43
+        this.button = button;
44
+        this.viewCreator = viewCreator;
45
+        this.onPressListener = onClickListener;
46
+    }
47
+
48
+    @Override
49
+    public void onViewAppeared() {
50
+        view.sendComponentStart();
51
+    }
52
+
53
+    @Override
54
+    public void onViewDisappear() {
55
+        view.sendComponentStop();
56
+    }
57
+
58
+    @Override
59
+    public void sendOnNavigationButtonPressed(String buttonId) {
60
+        getView().sendOnNavigationButtonPressed(buttonId);
61
+    }
62
+
63
+    @NonNull
64
+    @Override
65
+    protected TopBarReactButtonView createView() {
66
+        view = (TopBarReactButtonView) viewCreator.create(getActivity(), getId(), button.component.get());
67
+        return (TopBarReactButtonView) view.asView();
68
+    }
69
+
70
+    @Override
71
+    public boolean onMenuItemClick(MenuItem item) {
72
+        onPressListener.onPress(button.id);
73
+        return true;
74
+    }
75
+
76
+    public void applyNavigationIcon(Toolbar toolbar) {
77
+        if (!button.hasIcon()) {
78
+            Log.w("RNN", "Left button needs to have an icon");
79
+            return;
80
+        }
81
+
82
+        new ImageLoader().loadIcon(toolbar.getContext(), button.icon.get(), new ImageLoader.ImageLoadingListener() {
83
+            @Override
84
+            public void onComplete(@NonNull Drawable drawable) {
85
+                icon = drawable;
86
+                setIconColor(drawable);
87
+                toolbar.setNavigationOnClickListener(view -> onPressListener.onPress(button.id));
88
+                toolbar.setNavigationIcon(icon);
89
+                setLeftButtonTestId(toolbar);
90
+            }
91
+
92
+            @Override
93
+            public void onError(Throwable error) {
94
+                error.printStackTrace();
95
+            }
96
+        });
97
+    }
98
+
99
+    private void setLeftButtonTestId(Toolbar toolbar) {
100
+        if (!button.testId.hasValue()) return;
101
+        toolbar.post(() -> {
102
+            ImageButton leftButton = ViewUtils.findChildByClass(toolbar, ImageButton.class);
103
+            if (leftButton != null) {
104
+                leftButton.setTag(button.testId.get());
105
+            }
106
+        });
107
+    }
108
+
109
+    public void addToMenu(Toolbar toolbar) {
110
+        MenuItem menuItem = toolbar.getMenu().add(button.title.get(""));
111
+        menuItem.setShowAsAction(button.showAsAction);
112
+        menuItem.setEnabled(button.enabled.isTrueOrUndefined());
113
+        menuItem.setOnMenuItemClickListener(this);
114
+        if (button.hasComponent()) {
115
+            menuItem.setActionView(getView());
116
+        } else {
117
+            if (button.hasIcon()) {
118
+                loadIcon(new ImageLoadingListenerAdapter() {
119
+                    @Override
120
+                    public void onComplete(@NonNull Drawable icon) {
121
+                        TopBarButtonController.this.icon = icon;
122
+                        setIconColor(icon);
123
+                        menuItem.setIcon(icon);
124
+                    }
125
+                });
126
+            } else {
127
+                setTextColor(toolbar);
128
+                setFontSize(menuItem);
129
+            }
130
+        }
131
+        setTestId(toolbar, button.testId);
132
+    }
133
+
134
+    private void loadIcon(ImageLoader.ImageLoadingListener callbacks) {
135
+        new ImageLoader().loadIcon(getActivity(), button.icon.get(), callbacks);
136
+    }
137
+
138
+    private void setIconColor(Drawable icon) {
139
+        if (button.enabled.isTrueOrUndefined()) {
140
+            UiUtils.tintDrawable(icon, button.buttonColor);
141
+            return;
142
+        }
143
+        if (button.disableIconTint.isTrue()) {
144
+            UiUtils.tintDrawable(icon, button.buttonColor);
145
+        } else {
146
+            UiUtils.tintDrawable(icon, Color.LTGRAY);
147
+        }
148
+    }
149
+
150
+    private void setTextColor(Toolbar toolbar) {
151
+        UiUtils.runOnPreDrawOnce(toolbar, () -> {
152
+            ArrayList<View> outViews = findActualTextViewInMenuByText(toolbar);
153
+            setTextColorForFoundButtonViews(outViews);
154
+        });
155
+    }
156
+
157
+    @NonNull
158
+    private ArrayList<View> findActualTextViewInMenuByText(Toolbar toolbar) {
159
+        ArrayList<View> outViews = new ArrayList<>();
160
+        toolbar.findViewsWithText(outViews, button.title.get(), View.FIND_VIEWS_WITH_TEXT);
161
+        return outViews;
162
+    }
163
+
164
+    private void setTextColorForFoundButtonViews(ArrayList<View> buttons) {
165
+        for (View button : buttons) {
166
+            ((TextView) button).setTextColor(this.button.buttonColor);
167
+        }
168
+    }
169
+
170
+    private void setFontSize(MenuItem menuItem) {
171
+        SpannableString spanString = new SpannableString(button.title.get());
172
+        spanString.setSpan(new AbsoluteSizeSpan(button.buttonFontSize, true), 0, button.title.get().length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
173
+        menuItem.setTitleCondensed(spanString);
174
+    }
175
+
176
+    private void setTestId(Toolbar toolbar, Text testId) {
177
+        if (!testId.hasValue()) return;
178
+        UiUtils.runOnPreDrawOnce(toolbar, () -> {
179
+            ActionMenuView buttonsLayout = ViewUtils.findChildByClass(toolbar, ActionMenuView.class);
180
+            List<TextView> buttons = ViewUtils.findChildrenByClass(buttonsLayout, TextView.class);
181
+            for (TextView view : buttons) {
182
+                if (button.title.hasValue() && button.title.get().equals(view.getText().toString())) {
183
+                    view.setTag(testId.get());
184
+                } else if (button.icon.hasValue() && ArrayUtils.contains(view.getCompoundDrawables(), icon)) {
185
+                    view.setTag(testId.get());
186
+                }
187
+            }
188
+        });
189
+    }
190
+
191
+
192
+}

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

@@ -113,7 +113,9 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
113 113
                 throw new RuntimeException("Tried to create view after it has already been destroyed");
114 114
             }
115 115
             view = createView();
116
-            view.setId(CompatUtils.generateViewId());
116
+            if (view.getId() < 0) {
117
+                view.setId(CompatUtils.generateViewId());
118
+            }
117 119
             view.getViewTreeObserver().addOnGlobalLayoutListener(this);
118 120
         }
119 121
         return view;

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

@@ -11,13 +11,14 @@ import com.reactnativenavigation.interfaces.ScrollEventListener;
11 11
 import com.reactnativenavigation.parse.Options;
12 12
 import com.reactnativenavigation.presentation.ComponentOptionsPresenter;
13 13
 import com.reactnativenavigation.viewcontrollers.IReactView;
14
+import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
14 15
 import com.reactnativenavigation.views.touch.OverlayTouchDelegate;
15 16
 
16 17
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
17 18
 import static android.widget.RelativeLayout.BELOW;
18 19
 
19 20
 @SuppressLint("ViewConstructor")
20
-public class ComponentLayout extends FrameLayout implements ReactComponent, TitleBarButton.OnClickListener {
21
+public class ComponentLayout extends FrameLayout implements ReactComponent, TopBarButtonController.OnClickListener {
21 22
 
22 23
     private IReactView reactView;
23 24
     private final OverlayTouchDelegate touchDelegate;

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

@@ -9,6 +9,8 @@ import android.widget.RelativeLayout;
9 9
 import com.reactnativenavigation.parse.Options;
10 10
 import com.reactnativenavigation.presentation.OptionsPresenter;
11 11
 import com.reactnativenavigation.utils.CompatUtils;
12
+import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
13
+import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
12 14
 
13 15
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
14 16
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -17,16 +19,12 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
17 19
 public class StackLayout extends RelativeLayout {
18 20
     private TopBar topBar;
19 21
 
20
-    public StackLayout(Context context, TitleBarButton.OnClickListener topBarButtonClickListener) {
22
+    public StackLayout(Context context, ReactViewCreator topBarButtonCreator, TopBarButtonController.OnClickListener topBarButtonClickListener) {
21 23
         super(context);
22
-        topBar = new TopBar(context, topBarButtonClickListener, this);
24
+        topBar = new TopBar(context, topBarButtonCreator, topBarButtonClickListener, this);
23 25
         topBar.setId(CompatUtils.generateViewId());
24
-        createLayout();
25
-        setContentDescription("StackLayout");
26
-    }
27
-
28
-    void createLayout() {
29 26
         addView(topBar, MATCH_PARENT, WRAP_CONTENT);
27
+        setContentDescription("StackLayout");
30 28
     }
31 29
 
32 30
     public void applyOptions(Options options) {

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

@@ -0,0 +1,140 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.annotation.SuppressLint;
4
+import android.app.Activity;
5
+import android.content.Context;
6
+import android.graphics.Typeface;
7
+import android.support.annotation.Nullable;
8
+import android.support.v7.widget.Toolbar;
9
+import android.util.Log;
10
+import android.view.View;
11
+import android.view.ViewGroup;
12
+import android.widget.TextView;
13
+
14
+import com.reactnativenavigation.parse.params.Button;
15
+import com.reactnativenavigation.parse.params.Color;
16
+import com.reactnativenavigation.parse.params.Fraction;
17
+import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
18
+import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
19
+
20
+import java.util.ArrayList;
21
+import java.util.List;
22
+
23
+@SuppressLint("ViewConstructor")
24
+public class TitleBar extends Toolbar {
25
+    private final ReactViewCreator buttonCreator;
26
+    private final TopBarButtonController.OnClickListener onClickListener;
27
+    private final List<TopBarButtonController> rightButtonControllers = new ArrayList<>();
28
+    private TopBarButtonController leftButtonController;
29
+
30
+    public TitleBar(Context context, ReactViewCreator buttonCreator, TopBarButtonController.OnClickListener onClickListener) {
31
+        super(context);
32
+        this.buttonCreator = buttonCreator;
33
+        this.onClickListener = onClickListener;
34
+        getMenu();
35
+        setContentDescription("titleBar");
36
+    }
37
+
38
+    public String getTitle() {
39
+        return super.getTitle() == null ? "" : (String) super.getTitle();
40
+    }
41
+
42
+    void setTitleTextColor(Color color) {
43
+        if (color.hasValue()) setTitleTextColor(color.get());
44
+    }
45
+
46
+    void setBackgroundColor(Color color) {
47
+        if (color.hasValue()) setBackgroundColor(color.get());
48
+    }
49
+
50
+    void setTitleFontSize(Fraction size) {
51
+        TextView titleTextView = getTitleTextView();
52
+        if (titleTextView != null && size.hasValue()) {
53
+            titleTextView.setTextSize(size.get());
54
+        }
55
+    }
56
+
57
+    void setTitleTypeface(Typeface typeface) {
58
+        TextView titleTextView = getTitleTextView();
59
+        if (titleTextView != null) {
60
+            titleTextView.setTypeface(typeface);
61
+        }
62
+    }
63
+
64
+    TextView getTitleTextView() {
65
+        return findTextView(this);
66
+    }
67
+
68
+    public void clear() {
69
+        setTitle(null);
70
+        clearRightButtons();
71
+        clearLeftButton();
72
+    }
73
+
74
+    private void clearLeftButton() {
75
+        setNavigationIcon(null);
76
+        if (leftButtonController != null) {
77
+            leftButtonController.destroy();
78
+            leftButtonController = null;
79
+        }
80
+    }
81
+
82
+    private void clearRightButtons() {
83
+        for (TopBarButtonController button : rightButtonControllers) {
84
+            button.destroy();
85
+        }
86
+        rightButtonControllers.clear();
87
+        getMenu().clear();
88
+    }
89
+
90
+    public void setButtons(List<Button> leftButtons, List<Button> rightButtons) {
91
+        setLeftButtons(leftButtons);
92
+        setRightButtons(rightButtons);
93
+    }
94
+
95
+    private void setLeftButtons(List<Button> leftButtons) {
96
+        if (leftButtons == null) return;
97
+        if (leftButtons.isEmpty()) {
98
+            clearLeftButton();
99
+            return;
100
+        }
101
+        if (leftButtons.size() > 1) {
102
+            Log.w("RNN", "Use a custom TopBar to have more than one left button");
103
+        }
104
+        setLeftButton(leftButtons.get(0));
105
+    }
106
+
107
+    private void setLeftButton(final Button button) {
108
+        TopBarButtonController controller = createButtonController(button);
109
+        leftButtonController = controller;
110
+        controller.applyNavigationIcon(this);
111
+    }
112
+
113
+    public void setRightButtons(List<Button> rightButtons) {
114
+        if (rightButtons == null) return;
115
+        clearRightButtons();
116
+        for (Button button : rightButtons) {
117
+            TopBarButtonController controller = createButtonController(button);
118
+            rightButtonControllers.add(controller);
119
+            controller.addToMenu(this);
120
+        }
121
+    }
122
+
123
+    public TopBarButtonController createButtonController(Button button) {
124
+        return new TopBarButtonController((Activity) getContext(), button, buttonCreator, onClickListener);
125
+    }
126
+
127
+    @Nullable
128
+    private TextView findTextView(ViewGroup root) {
129
+        for (int i = 0; i < root.getChildCount(); i++) {
130
+            View view = root.getChildAt(i);
131
+            if (view instanceof ViewGroup) {
132
+                view = findTextView((ViewGroup) view);
133
+            }
134
+            if (view instanceof TextView) {
135
+                return (TextView) view;
136
+            }
137
+        }
138
+        return null;
139
+    }
140
+}

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

@@ -1,176 +0,0 @@
1
-package com.reactnativenavigation.views;
2
-
3
-import android.content.Context;
4
-import android.graphics.Color;
5
-import android.graphics.drawable.Drawable;
6
-import android.support.annotation.NonNull;
7
-import android.support.v7.widget.ActionMenuView;
8
-import android.support.v7.widget.Toolbar;
9
-import android.text.Spannable;
10
-import android.text.SpannableString;
11
-import android.text.style.AbsoluteSizeSpan;
12
-import android.util.Log;
13
-import android.view.Menu;
14
-import android.view.MenuItem;
15
-import android.view.View;
16
-import android.widget.ImageButton;
17
-import android.widget.TextView;
18
-
19
-import com.reactnativenavigation.parse.params.Button;
20
-import com.reactnativenavigation.parse.params.Text;
21
-import com.reactnativenavigation.utils.ArrayUtils;
22
-import com.reactnativenavigation.utils.ImageLoader;
23
-import com.reactnativenavigation.utils.UiUtils;
24
-import com.reactnativenavigation.utils.ViewUtils;
25
-
26
-import java.util.ArrayList;
27
-import java.util.List;
28
-
29
-public class TitleBarButton implements MenuItem.OnMenuItemClickListener {
30
-    public interface OnClickListener {
31
-        void onPress(String buttonId);
32
-    }
33
-
34
-	private Toolbar toolbar;
35
-	private final Button button;
36
-	private Drawable icon;
37
-    private OnClickListener onPressListener;
38
-
39
-    TitleBarButton(Toolbar toolbar, Button button, OnClickListener onPressListener) {
40
-        this.onPressListener = onPressListener;
41
-		this.toolbar = toolbar;
42
-		this.button = button;
43
-	}
44
-
45
-	void addToMenu(Context context, final Menu menu) {
46
-		MenuItem menuItem = menu.add(button.title.get(""));
47
-		menuItem.setShowAsAction(button.showAsAction);
48
-		menuItem.setEnabled(button.enabled.isTrueOrUndefined());
49
-		menuItem.setOnMenuItemClickListener(this);
50
-
51
-		if (hasIcon()) {
52
-			applyIcon(context, menuItem);
53
-		} else {
54
-			setTextColor();
55
-			setFontSize(menuItem);
56
-		}
57
-
58
-        setTestId(button.testId);
59
-    }
60
-
61
-    void applyNavigationIcon(Context context) {
62
-		if (!hasIcon()) {
63
-			Log.w("RNN", "Left button needs to have an icon");
64
-			return;
65
-		}
66
-
67
-		new ImageLoader().loadIcon(context, button.icon.get(), new ImageLoader.ImageLoadingListener() {
68
-			@Override
69
-			public void onComplete(@NonNull Drawable drawable) {
70
-				icon = drawable;
71
-                setIconColor();
72
-                setNavigationClickListener();
73
-                toolbar.setNavigationIcon(icon);
74
-                setLeftButtonTestId();
75
-            }
76
-
77
-			@Override
78
-			public void onError(Throwable error) {
79
-				error.printStackTrace();
80
-			}
81
-		});
82
-	}
83
-
84
-    private void setLeftButtonTestId() {
85
-        if (!button.testId.hasValue()) return;
86
-        toolbar.post(() -> {
87
-            ImageButton leftButton = ViewUtils.findChildByClass(toolbar, ImageButton.class);
88
-            if (leftButton != null) {
89
-                leftButton.setTag(button.testId.get());
90
-            }
91
-        });
92
-    }
93
-
94
-    private void applyIcon(Context context, final MenuItem menuItem) {
95
-        new ImageLoader().loadIcon(context, button.icon.get(), new ImageLoader.ImageLoadingListener() {
96
-			@Override
97
-			public void onComplete(@NonNull Drawable drawable) {
98
-				icon = drawable;
99
-                menuItem.setIcon(icon);
100
-                setIconColor();
101
-			}
102
-
103
-			@Override
104
-			public void onError(Throwable error) {
105
-				error.printStackTrace();
106
-			}
107
-		});
108
-	}
109
-
110
-	private void setIconColor() {
111
-		if (button.enabled.isTrueOrUndefined()) {
112
-			UiUtils.tintDrawable(icon, button.buttonColor);
113
-			return;
114
-		}
115
-		if (button.disableIconTint.isTrue()) {
116
-			UiUtils.tintDrawable(icon, button.buttonColor);
117
-		} else {
118
-			UiUtils.tintDrawable(icon, Color.LTGRAY);
119
-		}
120
-	}
121
-
122
-	private void setTextColor() {
123
-		UiUtils.runOnPreDrawOnce(this.toolbar, () -> {
124
-            ArrayList<View> outViews = findActualTextViewInMenuByText();
125
-            setTextColorForFoundButtonViews(outViews);
126
-        });
127
-	}
128
-
129
-	private void setFontSize(MenuItem menuItem) {
130
-		SpannableString spanString = new SpannableString(button.title.get());
131
-		spanString.setSpan(new AbsoluteSizeSpan(button.buttonFontSize, true), 0, button.title.get().length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
132
-		menuItem.setTitleCondensed(spanString);
133
-	}
134
-
135
-	private void setNavigationClickListener() {
136
-		toolbar.setNavigationOnClickListener(view -> onPressListener.onPress(button.id));
137
-	}
138
-
139
-	@Override
140
-	public boolean onMenuItemClick(MenuItem menuItem) {
141
-		onPressListener.onPress(button.id);
142
-		return true;
143
-	}
144
-
145
-	private void setTextColorForFoundButtonViews(ArrayList<View> buttons) {
146
-		for (View button : buttons) {
147
-			((TextView) button).setTextColor(this.button.buttonColor);
148
-		}
149
-	}
150
-
151
-	private boolean hasIcon() {
152
-		return button.icon.hasValue();
153
-	}
154
-
155
-    private void setTestId(Text testId) {
156
-        if (!testId.hasValue()) return;
157
-        UiUtils.runOnPreDrawOnce(this.toolbar, () -> {
158
-            ActionMenuView buttonsLayout = ViewUtils.findChildByClass(toolbar, ActionMenuView.class);
159
-            List<TextView> buttons = ViewUtils.findChildrenByClass(buttonsLayout, TextView.class);
160
-            for (TextView view : buttons) {
161
-                if (button.title.hasValue() && button.title.get().equals(view.getText().toString())) {
162
-                    view.setTag(testId.get());
163
-                } else if (button.icon.hasValue() && ArrayUtils.contains(view.getCompoundDrawables(), icon)) {
164
-                    view.setTag(testId.get());
165
-                }
166
-            }
167
-        });
168
-    }
169
-
170
-    @NonNull
171
-    private ArrayList<View> findActualTextViewInMenuByText() {
172
-        ArrayList<View> outViews = new ArrayList<>();
173
-        this.toolbar.findViewsWithText(outViews, button.title.get(), View.FIND_VIEWS_WITH_TEXT);
174
-        return outViews;
175
-    }
176
-}

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

@@ -3,15 +3,12 @@ package com.reactnativenavigation.views;
3 3
 import android.annotation.SuppressLint;
4 4
 import android.content.Context;
5 5
 import android.graphics.Typeface;
6
-import android.support.annotation.Nullable;
6
+import android.support.annotation.RestrictTo;
7 7
 import android.support.annotation.VisibleForTesting;
8 8
 import android.support.design.widget.AppBarLayout;
9 9
 import android.support.v4.view.ViewPager;
10 10
 import android.support.v7.widget.Toolbar;
11
-import android.util.Log;
12
-import android.view.Menu;
13 11
 import android.view.View;
14
-import android.view.ViewGroup;
15 12
 import android.widget.TextView;
16 13
 
17 14
 import com.reactnativenavigation.anim.TopBarAnimator;
@@ -22,41 +19,43 @@ import com.reactnativenavigation.parse.params.Button;
22 19
 import com.reactnativenavigation.parse.params.Color;
23 20
 import com.reactnativenavigation.parse.params.Fraction;
24 21
 import com.reactnativenavigation.parse.params.Number;
22
+import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
23
+import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
25 24
 
26
-import java.util.ArrayList;
25
+import java.util.List;
27 26
 
28 27
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
29 28
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
30 29
 
31 30
 @SuppressLint("ViewConstructor")
32 31
 public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAwareView {
33
-    private final Toolbar titleBar;
34
-    private TitleBarButton.OnClickListener onClickListener;
32
+    private final TitleBar titleBar;
35 33
     private final TopBarCollapseBehavior collapsingBehavior;
36 34
     private TopBarAnimator animator;
37 35
     private TopTabs topTabs;
38 36
     private StackLayout parentView;
39 37
 
40
-    public TopBar(final Context context, TitleBarButton.OnClickListener onClickListener, StackLayout parentView) {
38
+    public TopBar(final Context context, ReactViewCreator buttonCreator, TopBarButtonController.OnClickListener onClickListener, StackLayout parentView) {
41 39
         super(context);
42
-        this.onClickListener = onClickListener;
43 40
         collapsingBehavior = new TopBarCollapseBehavior(this);
44
-        titleBar = new Toolbar(context);
45
-        titleBar.getMenu();
46 41
         topTabs = new TopTabs(getContext());
47
-        this.animator = new TopBarAnimator(this);
42
+        animator = new TopBarAnimator(this);
48 43
         this.parentView = parentView;
44
+        titleBar = createTitleBar(context, buttonCreator, onClickListener);
49 45
         addView(titleBar);
50
-        titleBar.setContentDescription("titleBar");
51 46
         setContentDescription("TopBar");
52 47
     }
53 48
 
49
+    protected TitleBar createTitleBar(Context context, ReactViewCreator buttonCreator, TopBarButtonController.OnClickListener onClickListener) {
50
+        return new TitleBar(context, buttonCreator, onClickListener);
51
+    }
52
+
54 53
     public void setTitle(String title) {
55 54
         titleBar.setTitle(title);
56 55
     }
57 56
 
58 57
     public String getTitle() {
59
-        return titleBar.getTitle() != null ? titleBar.getTitle().toString() : "";
58
+        return titleBar.getTitle();
60 59
     }
61 60
 
62 61
     public void setTestId(String testId) {
@@ -64,21 +63,15 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
64 63
     }
65 64
 
66 65
     public void setTitleTextColor(Color color) {
67
-        if (color.hasValue()) titleBar.setTitleTextColor(color.get());
66
+        titleBar.setTitleTextColor(color);
68 67
     }
69 68
 
70 69
     public void setTitleFontSize(Fraction size) {
71
-        TextView titleTextView = getTitleTextView();
72
-        if (titleTextView != null && size.hasValue()) {
73
-            titleTextView.setTextSize(size.get());
74
-        }
70
+        titleBar.setTitleFontSize(size);
75 71
     }
76 72
 
77 73
     public void setTitleTypeface(Typeface typeface) {
78
-        TextView titleTextView = getTitleTextView();
79
-        if (titleTextView != null) {
80
-            titleTextView.setTypeface(typeface);
81
-        }
74
+        titleBar.setTitleTypeface(typeface);
82 75
     }
83 76
 
84 77
     public void setTopTabFontFamily(int tabIndex, Typeface fontFamily) {
@@ -97,66 +90,17 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
97 90
         topTabs.setVisibility(this, visible);
98 91
     }
99 92
 
100
-    public void setButtons(ArrayList<Button> leftButtons, ArrayList<Button> rightButtons) {
101
-        setLeftButtons(leftButtons);
102
-        setRightButtons(rightButtons);
93
+    public void setButtons(List<Button> leftButtons, List<Button> rightButtons) {
94
+        titleBar.setButtons(leftButtons, rightButtons);
103 95
     }
104 96
 
97
+    @RestrictTo(RestrictTo.Scope.TESTS)
105 98
     public TextView getTitleTextView() {
106
-        return findTextView(titleBar);
99
+        return titleBar.getTitleTextView();
107 100
     }
108 101
 
109 102
     public void setBackgroundColor(Color color) {
110
-        if (color.hasValue()) titleBar.setBackgroundColor(color.get());
111
-    }
112
-
113
-    @Nullable
114
-    private TextView findTextView(ViewGroup root) {
115
-        for (int i = 0; i < root.getChildCount(); i++) {
116
-            View view = root.getChildAt(i);
117
-            if (view instanceof ViewGroup) {
118
-                view = findTextView((ViewGroup) view);
119
-            }
120
-            if (view instanceof TextView) {
121
-                return (TextView) view;
122
-            }
123
-        }
124
-        return null;
125
-    }
126
-
127
-    private void setLeftButtons(ArrayList<Button> leftButtons) {
128
-        if (leftButtons == null) return;
129
-        if (leftButtons.isEmpty()) {
130
-            titleBar.setNavigationIcon(null);
131
-            return;
132
-        }
133
-
134
-        if (leftButtons.size() > 1) {
135
-            Log.w("RNN", "Use a custom TopBar to have more than one left button");
136
-        }
137
-
138
-        Button leftButton = leftButtons.get(0);
139
-        setLeftButton(leftButton);
140
-    }
141
-
142
-    private void setLeftButton(final Button button) {
143
-        TitleBarButton leftBarButton = new TitleBarButton(this.titleBar, button, onClickListener);
144
-        leftBarButton.applyNavigationIcon(getContext());
145
-    }
146
-
147
-    private void setRightButtons(ArrayList<Button> rightButtons) {
148
-        if (rightButtons == null || rightButtons.size() == 0) {
149
-            return;
150
-        }
151
-
152
-        Menu menu = getTitleBar().getMenu();
153
-        menu.clear();
154
-
155
-        for (int i = 0; i < rightButtons.size(); i++) {
156
-            Button button = rightButtons.get(i);
157
-            TitleBarButton titleBarButton = new TitleBarButton(this.titleBar, button, onClickListener);
158
-            titleBarButton.addToMenu(getContext(), menu);
159
-        }
103
+        titleBar.setBackgroundColor(color);
160 104
     }
161 105
 
162 106
     public Toolbar getTitleBar() {
@@ -210,16 +154,14 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
210 154
     }
211 155
 
212 156
     public void clear() {
213
-        titleBar.setTitle(null);
214
-        titleBar.setNavigationIcon(null);
215
-        titleBar.getMenu().clear();
157
+        titleBar.clear();
216 158
     }
217 159
 
218 160
     public void clearTopTabs() {
219 161
         topTabs.clear(this);
220 162
     }
221 163
 
222
-    @VisibleForTesting()
164
+    @VisibleForTesting
223 165
     public TopTabs getTopTabs() {
224 166
         return topTabs;
225 167
     }

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

@@ -0,0 +1,21 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.app.Activity;
4
+
5
+import com.facebook.react.ReactInstanceManager;
6
+import com.reactnativenavigation.react.TopBarReactButtonView;
7
+import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
8
+
9
+public class TopBarButtonCreator implements ReactViewCreator {
10
+
11
+    private ReactInstanceManager instanceManager;
12
+
13
+    public TopBarButtonCreator(ReactInstanceManager instanceManager) {
14
+        this.instanceManager = instanceManager;
15
+	}
16
+
17
+	@Override
18
+	public TopBarReactButtonView create(Activity activity, String componentId, String componentName) {
19
+        return new TopBarReactButtonView(activity, instanceManager, componentId, componentName);
20
+    }
21
+}

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

@@ -9,6 +9,7 @@ import android.widget.RelativeLayout;
9 9
 
10 10
 import com.reactnativenavigation.parse.Options;
11 11
 import com.reactnativenavigation.viewcontrollers.IReactView;
12
+import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
12 13
 import com.reactnativenavigation.viewcontrollers.ViewController;
13 14
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsAdapter;
14 15
 
@@ -18,7 +19,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
18 19
 import static android.widget.RelativeLayout.BELOW;
19 20
 
20 21
 @SuppressLint("ViewConstructor")
21
-public class TopTabsViewPager extends ViewPager implements Component, TitleBarButton.OnClickListener {
22
+public class TopTabsViewPager extends ViewPager implements Component, TopBarButtonController.OnClickListener {
22 23
 
23 24
     private static final int OFFSCREEN_PAGE_LIMIT = 99;
24 25
     private List<ViewController> tabs;

+ 2
- 2
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestComponentLayout.java View File

@@ -7,10 +7,10 @@ import android.view.View;
7 7
 import com.reactnativenavigation.interfaces.ScrollEventListener;
8 8
 import com.reactnativenavigation.parse.Options;
9 9
 import com.reactnativenavigation.viewcontrollers.IReactView;
10
+import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
10 11
 import com.reactnativenavigation.views.ComponentLayout;
11
-import com.reactnativenavigation.views.TitleBarButton;
12 12
 
13
-public class TestComponentLayout extends ComponentLayout implements TitleBarButton.OnClickListener {
13
+public class TestComponentLayout extends ComponentLayout implements TopBarButtonController.OnClickListener {
14 14
 
15 15
     private IReactView reactView;
16 16
 

+ 28
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TopBarButtonCreatorMock.java View File

@@ -0,0 +1,28 @@
1
+package com.reactnativenavigation.mocks;
2
+
3
+import android.app.Activity;
4
+
5
+import com.facebook.react.ReactInstanceManager;
6
+import com.reactnativenavigation.react.TopBarReactButtonView;
7
+import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
8
+
9
+import static org.mockito.Mockito.mock;
10
+
11
+public class TopBarButtonCreatorMock implements ReactViewCreator {
12
+
13
+    @Override
14
+    public TopBarReactButtonView create(Activity activity, String componentId, String componentName) {
15
+        final ReactInstanceManager reactInstanceManager = mock(ReactInstanceManager.class);
16
+        return new TopBarReactButtonView(activity, reactInstanceManager, componentId, componentName) {
17
+            @Override
18
+            public void sendComponentStart() {
19
+
20
+            }
21
+
22
+            @Override
23
+            public void sendComponentStop() {
24
+
25
+            }
26
+        };
27
+    }
28
+}

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

@@ -8,8 +8,9 @@ import com.reactnativenavigation.BaseTest;
8 8
 import com.reactnativenavigation.mocks.ImageLoaderMock;
9 9
 import com.reactnativenavigation.mocks.MockPromise;
10 10
 import com.reactnativenavigation.mocks.SimpleViewController;
11
-import com.reactnativenavigation.parse.params.Color;
11
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
12 12
 import com.reactnativenavigation.parse.Options;
13
+import com.reactnativenavigation.parse.params.Color;
13 14
 import com.reactnativenavigation.parse.params.Number;
14 15
 import com.reactnativenavigation.utils.ImageLoader;
15 16
 import com.reactnativenavigation.utils.OptionHelper;
@@ -99,7 +100,7 @@ public class BottomTabsControllerTest extends BaseTest {
99 100
     public void findControllerById_ReturnsSelfOrChildren() throws Exception {
100 101
         assertThat(uut.findControllerById("123")).isNull();
101 102
         assertThat(uut.findControllerById(uut.getId())).isEqualTo(uut);
102
-        StackController inner = new StackController(activity, "inner", tabOptions);
103
+        StackController inner = new StackController(activity, new TopBarButtonCreatorMock(), "inner", tabOptions);
103 104
         inner.animatePush(child1, new MockPromise());
104 105
         assertThat(uut.findControllerById(child1.getId())).isNull();
105 106
         uut.setTabs(Collections.singletonList(inner));
@@ -130,7 +131,7 @@ public class BottomTabsControllerTest extends BaseTest {
130 131
         uut.setTabs(tabs);
131 132
         uut.ensureViewIsCreated();
132 133
 
133
-        StackController stack = spy(new StackController(activity, "stack", new Options()));
134
+        StackController stack = spy(new StackController(activity, new TopBarButtonCreatorMock(), "stack", new Options()));
134 135
         stack.ensureViewIsCreated();
135 136
         stack.push(uut, new MockPromise());
136 137
 

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

@@ -5,6 +5,7 @@ import android.app.Activity;
5 5
 import com.reactnativenavigation.BaseTest;
6 6
 import com.reactnativenavigation.mocks.TestComponentLayout;
7 7
 import com.reactnativenavigation.mocks.TestReactView;
8
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
8 9
 import com.reactnativenavigation.parse.Options;
9 10
 import com.reactnativenavigation.views.StackLayout;
10 11
 
@@ -25,7 +26,7 @@ public class ComponentViewControllerTest extends BaseTest {
25 26
         super.beforeEach();
26 27
         Activity activity = newActivity();
27 28
         view = spy(new TestComponentLayout(activity, new TestReactView(activity)));
28
-        ParentController<StackLayout> parentController = new StackController(activity, "stack", new Options());
29
+        ParentController<StackLayout> parentController = new StackController(activity, new TopBarButtonCreatorMock(), "stack", new Options());
29 30
         uut = new ComponentViewController(activity, "componentId1", "componentName", (activity1, componentId, componentName) -> view, new Options());
30 31
         uut.setParentController(parentController);
31 32
         parentController.ensureViewIsCreated();

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

@@ -3,10 +3,12 @@ package com.reactnativenavigation.viewcontrollers;
3 3
 import android.app.Activity;
4 4
 import android.support.annotation.NonNull;
5 5
 import android.view.View;
6
+import android.view.ViewGroup;
6 7
 
7 8
 import com.reactnativenavigation.BaseTest;
8 9
 import com.reactnativenavigation.mocks.MockPromise;
9 10
 import com.reactnativenavigation.mocks.SimpleViewController;
11
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
10 12
 import com.reactnativenavigation.parse.FabOptions;
11 13
 import com.reactnativenavigation.parse.Options;
12 14
 import com.reactnativenavigation.parse.params.Text;
@@ -17,8 +19,6 @@ import com.reactnativenavigation.views.StackLayout;
17 19
 import org.junit.Test;
18 20
 
19 21
 import static org.assertj.core.api.Java6Assertions.assertThat;
20
-import static org.mockito.Mockito.spy;
21
-import static org.mockito.Mockito.verify;
22 22
 
23 23
 public class FloatingActionButtonTest extends BaseTest {
24 24
 
@@ -33,7 +33,7 @@ public class FloatingActionButtonTest extends BaseTest {
33 33
     public void beforeEach() {
34 34
         super.beforeEach();
35 35
         activity = newActivity();
36
-        stackController = new StackController(activity, "stackController", new Options());
36
+        stackController = new StackController(activity, new TopBarButtonCreatorMock(), "stackController", new Options());
37 37
         Options options = getOptionsWithFab();
38 38
         childFab = new SimpleViewController(activity, "child1", options);
39 39
         childNoFab = new SimpleViewController(activity, "child2", new Options());
@@ -128,7 +128,7 @@ public class FloatingActionButtonTest extends BaseTest {
128 128
         for (int i = 0; i < stackLayout.getChildCount(); i++) {
129 129
             View child = stackLayout.getChildAt(i);
130 130
             if (child instanceof FabMenu) {
131
-                return ((FabMenu) child).getChildCount() == CHILD_FAB_COUNT + 2;
131
+                return ((ViewGroup) child).getChildCount() == CHILD_FAB_COUNT + 2;
132 132
             }
133 133
         }
134 134
         return false;

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

@@ -8,6 +8,7 @@ import com.reactnativenavigation.mocks.ImageLoaderMock;
8 8
 import com.reactnativenavigation.mocks.MockPromise;
9 9
 import com.reactnativenavigation.mocks.SimpleComponentViewController;
10 10
 import com.reactnativenavigation.mocks.SimpleViewController;
11
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
11 12
 import com.reactnativenavigation.parse.Options;
12 13
 import com.reactnativenavigation.parse.params.Text;
13 14
 import com.reactnativenavigation.utils.CompatUtils;
@@ -45,7 +46,7 @@ public class NavigatorTest extends BaseTest {
45 46
         imageLoaderMock = ImageLoaderMock.mock();
46 47
         activity = newActivity();
47 48
         uut = new Navigator(activity);
48
-        parentController = spy(new StackController(activity, "stack", new Options()));
49
+        parentController = spy(new StackController(activity, new TopBarButtonCreatorMock(), "stack", new Options()));
49 50
         parentController.ensureViewIsCreated();
50 51
         child1 = new SimpleViewController(activity, "child1", tabOptions);
51 52
         child2 = new SimpleViewController(activity, "child2", tabOptions);
@@ -247,7 +248,7 @@ public class NavigatorTest extends BaseTest {
247 248
 
248 249
     @NonNull
249 250
     private StackController newStack() {
250
-        return new StackController(activity, "stack" + CompatUtils.generateViewId(), tabOptions);
251
+        return new StackController(activity, new TopBarButtonCreatorMock(), "stack" + CompatUtils.generateViewId(), tabOptions);
251 252
     }
252 253
 
253 254
     @Test
@@ -324,7 +325,7 @@ public class NavigatorTest extends BaseTest {
324 325
 
325 326
     @Test
326 327
     public void pushedStackCanBePopped() throws Exception {
327
-        StackController parent = new StackController(activity, "someStack", new Options());
328
+        StackController parent = new StackController(activity, new TopBarButtonCreatorMock(), "someStack", new Options());
328 329
         parent.ensureViewIsCreated();
329 330
         uut.setRoot(parent, new MockPromise());
330 331
         parent.push(parentController, new MockPromise());

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

@@ -10,10 +10,11 @@ import com.reactnativenavigation.BaseTest;
10 10
 import com.reactnativenavigation.mocks.MockPromise;
11 11
 import com.reactnativenavigation.mocks.TestComponentLayout;
12 12
 import com.reactnativenavigation.mocks.TestReactView;
13
-import com.reactnativenavigation.parse.params.Fraction;
13
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
14 14
 import com.reactnativenavigation.parse.Options;
15
-import com.reactnativenavigation.parse.params.Text;
16 15
 import com.reactnativenavigation.parse.params.Bool;
16
+import com.reactnativenavigation.parse.params.Fraction;
17
+import com.reactnativenavigation.parse.params.Text;
17 18
 
18 19
 import org.junit.Test;
19 20
 
@@ -44,7 +45,7 @@ public class OptionsApplyingTest extends BaseTest {
44 45
                 (activity1, componentId, componentName) -> view,
45 46
                 initialNavigationOptions
46 47
         );
47
-        stackController = new StackController(activity, "stack", new Options());
48
+        stackController = new StackController(activity, new TopBarButtonCreatorMock(), "stack", new Options());
48 49
         stackController.ensureViewIsCreated();
49 50
         uut.setParentController(stackController);
50 51
     }
@@ -61,7 +62,7 @@ public class OptionsApplyingTest extends BaseTest {
61 62
     @Test
62 63
     public void initialOptionsAppliedOnAppear() throws Exception {
63 64
         uut.options.topBarOptions.title = new Text("the title");
64
-        StackController stackController = new StackController(activity, "stackId", new Options());
65
+        StackController stackController = new StackController(activity, new TopBarButtonCreatorMock(), "stackId", new Options());
65 66
         stackController.animatePush(uut, new MockPromise() {});
66 67
         assertThat(stackController.getTopBar().getTitle()).isEmpty();
67 68
 

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

@@ -8,6 +8,7 @@ import android.widget.FrameLayout;
8 8
 import com.reactnativenavigation.BaseTest;
9 9
 import com.reactnativenavigation.mocks.MockPromise;
10 10
 import com.reactnativenavigation.mocks.SimpleViewController;
11
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
11 12
 import com.reactnativenavigation.parse.Options;
12 13
 import com.reactnativenavigation.parse.params.Text;
13 14
 import com.reactnativenavigation.views.ReactComponent;
@@ -87,7 +88,7 @@ public class ParentControllerTest extends BaseTest {
87 88
 
88 89
     @Test
89 90
     public void findControllerById_Recursive() throws Exception {
90
-        StackController stackController = new StackController(activity, "stack", new Options());
91
+        StackController stackController = new StackController(activity, new TopBarButtonCreatorMock(), "stack", new Options());
91 92
         SimpleViewController child1 = new SimpleViewController(activity, "child1", new Options());
92 93
         SimpleViewController child2 = new SimpleViewController(activity, "child2", new Options());
93 94
         stackController.animatePush(child1, new MockPromise());
@@ -109,7 +110,7 @@ public class ParentControllerTest extends BaseTest {
109 110
 
110 111
     @Test
111 112
     public void optionsAreClearedWhenChildIsAppeared() throws Exception {
112
-        StackController stackController = spy(new StackController(activity, "stack", new Options()));
113
+        StackController stackController = spy(new StackController(activity, new TopBarButtonCreatorMock(), "stack", new Options()));
113 114
         SimpleViewController child1 = new SimpleViewController(activity, "child1", new Options());
114 115
         stackController.animatePush(child1, new MockPromise());
115 116
 

+ 7
- 6
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java View File

@@ -7,6 +7,7 @@ import android.view.View;
7 7
 import com.reactnativenavigation.BaseTest;
8 8
 import com.reactnativenavigation.mocks.MockPromise;
9 9
 import com.reactnativenavigation.mocks.SimpleViewController;
10
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
10 11
 import com.reactnativenavigation.parse.Options;
11 12
 import com.reactnativenavigation.parse.params.Bool;
12 13
 import com.reactnativenavigation.parse.params.Text;
@@ -38,7 +39,7 @@ public class StackControllerTest extends BaseTest {
38 39
     public void beforeEach() {
39 40
         super.beforeEach();
40 41
         activity = newActivity();
41
-        uut = new StackController(activity, "uut", new Options());
42
+        uut = new StackController(activity, new TopBarButtonCreatorMock(), "uut", new Options());
42 43
         child1 = spy(new SimpleViewController(activity, "child1", new Options()));
43 44
         child2 = spy(new SimpleViewController(activity, "child2", new Options()));
44 45
         child3 = spy(new SimpleViewController(activity, "child3", new Options()));
@@ -94,7 +95,7 @@ public class StackControllerTest extends BaseTest {
94 95
     @Test
95 96
     public void pop_layoutHandlesChildWillDisappear() throws Exception {
96 97
         final StackLayout[] stackLayout = new StackLayout[1];
97
-        uut = new StackController(activity, "uut", new Options()) {
98
+        uut = new StackController(activity, new TopBarButtonCreatorMock(), "uut", new Options()) {
98 99
             @NonNull
99 100
             @Override
100 101
             protected StackLayout createView() {
@@ -133,7 +134,7 @@ public class StackControllerTest extends BaseTest {
133 134
         uut.animatePush(child1, new MockPromise());
134 135
         assertThat(child1.getParentController()).isEqualTo(uut);
135 136
 
136
-        StackController anotherNavController = new StackController(activity, "another", new Options());
137
+        StackController anotherNavController = new StackController(activity, new TopBarButtonCreatorMock(), "another", new Options());
137 138
         anotherNavController.animatePush(child2, new MockPromise());
138 139
         assertThat(child2.getParentController()).isEqualTo(anotherNavController);
139 140
     }
@@ -313,7 +314,7 @@ public class StackControllerTest extends BaseTest {
313 314
 
314 315
     @Test
315 316
     public void findControllerById_Deeply() throws Exception {
316
-        StackController stack = new StackController(activity, "stack2", new Options());
317
+        StackController stack = new StackController(activity, new TopBarButtonCreatorMock(), "stack2", new Options());
317 318
         stack.animatePush(child2, new MockPromise());
318 319
         uut.animatePush(stack, new MockPromise());
319 320
         assertThat(uut.findControllerById(child2.getId())).isEqualTo(child2);
@@ -408,7 +409,7 @@ public class StackControllerTest extends BaseTest {
408 409
 
409 410
     @Test
410 411
     public void stackCanBePushed() throws Exception {
411
-        StackController parent = new StackController(activity, "someStack", new Options());
412
+        StackController parent = new StackController(activity, new TopBarButtonCreatorMock(), "someStack", new Options());
412 413
         parent.ensureViewIsCreated();
413 414
         parent.push(uut, new MockPromise());
414 415
         uut.onViewAppeared();
@@ -417,7 +418,7 @@ public class StackControllerTest extends BaseTest {
417 418
 
418 419
     @Test
419 420
     public void applyOptions_applyOnlyOnFirstStack() throws Exception {
420
-        StackController parent = spy(new StackController(activity, "someStack", new Options()));
421
+        StackController parent = spy(new StackController(activity, new TopBarButtonCreatorMock(), "someStack", new Options()));
421 422
         parent.ensureViewIsCreated();
422 423
         parent.push(uut, new MockPromise());
423 424
 

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

@@ -0,0 +1,124 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+
5
+import com.reactnativenavigation.BaseTest;
6
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
7
+import com.reactnativenavigation.parse.params.Button;
8
+import com.reactnativenavigation.parse.params.Text;
9
+import com.reactnativenavigation.react.ReactView;
10
+import com.reactnativenavigation.views.TitleBar;
11
+
12
+import org.junit.Test;
13
+
14
+import java.util.ArrayList;
15
+import java.util.Arrays;
16
+import java.util.Collections;
17
+import java.util.HashMap;
18
+import java.util.List;
19
+import java.util.Map;
20
+
21
+import static org.assertj.core.api.Java6Assertions.assertThat;
22
+import static org.mockito.Mockito.spy;
23
+import static org.mockito.Mockito.times;
24
+import static org.mockito.Mockito.verify;
25
+
26
+public class TitleBarTest extends BaseTest {
27
+
28
+    private TitleBar uut;
29
+    private Button leftButton;
30
+    private Button textButton;
31
+    private Button customButton;
32
+    private Map<String, TopBarButtonController> buttonControllers;
33
+
34
+    @Override
35
+    public void beforeEach() {
36
+        final TopBarButtonCreatorMock buttonCreator = new TopBarButtonCreatorMock();
37
+        final Activity activity = newActivity();
38
+        createButtons();
39
+        buttonControllers = new HashMap<>();
40
+        uut = spy(new TitleBar(activity, buttonCreator, (buttonId -> {})) {
41
+            @Override
42
+            public TopBarButtonController createButtonController(Button button) {
43
+                TopBarButtonController controller = spy(super.createButtonController(button));
44
+                buttonControllers.put(button.id, controller);
45
+                return controller;
46
+            }
47
+        });
48
+    }
49
+
50
+    private void createButtons() {
51
+        leftButton = new Button();
52
+        leftButton.id = "back";
53
+        leftButton.title = new Text("jfjf");
54
+
55
+        textButton = new Button();
56
+        textButton.id = "textButton";
57
+        textButton.title = new Text("Btn");
58
+
59
+        customButton = new Button();
60
+        customButton.id = "customBtn";
61
+        customButton.component = new Text("com.rnn.customBtn");
62
+    }
63
+
64
+    @Test
65
+    public void setButton_setsTextButton() {
66
+        uut.setButtons(leftButton(leftButton), rightButtons(textButton));
67
+        assertThat(uut.getMenu().getItem(0).getTitle()).isEqualTo(textButton.title.get());
68
+    }
69
+
70
+    @Test
71
+    public void setButton_setsCustomButton() {
72
+        uut.setButtons(leftButton(leftButton), rightButtons(customButton));
73
+        ReactView btnView = (ReactView) uut.getMenu().getItem(0).getActionView();
74
+        assertThat(btnView.getComponentName()).isEqualTo(customButton.component.get());
75
+    }
76
+
77
+    @Test
78
+    public void destroy_destroysButtonControllers() throws Exception {
79
+        uut.setButtons(leftButton(leftButton), rightButtons(customButton, textButton));
80
+        uut.clear();
81
+        for (TopBarButtonController controller : buttonControllers.values()) {
82
+            verify(controller, times(1)).destroy();
83
+        }
84
+    }
85
+
86
+    @Test
87
+    public void setRightButtons_destroysRightButtons() throws Exception {
88
+        uut.setButtons(leftButton(leftButton), rightButtons(customButton));
89
+        uut.setButtons(leftButton(leftButton), rightButtons(textButton));
90
+        verify(buttonControllers.get(customButton.id), times(1)).destroy();
91
+    }
92
+
93
+    @Test
94
+    public void setRightButtons_onlyDestroysRightButtons() throws Exception {
95
+        uut.setButtons(leftButton(leftButton), rightButtons(customButton));
96
+        uut.setButtons(null, rightButtons(textButton));
97
+        verify(buttonControllers.get(leftButton.id), times(0)).destroy();
98
+    }
99
+
100
+    @Test
101
+    public void setRightButtons_emptyButtonsListClearsRightButtons() throws Exception {
102
+        uut.setButtons(new ArrayList<>(), rightButtons(customButton, textButton));
103
+        uut.setButtons(new ArrayList<>(), new ArrayList<>());
104
+        for (TopBarButtonController controller : buttonControllers.values()) {
105
+            verify(controller, times(1)).destroy();
106
+        }
107
+        assertThat(uut.getMenu().size()).isEqualTo(0);
108
+    }
109
+
110
+    @Test
111
+    public void setLeftButtons_emptyButtonsListClearsLeftButton() throws Exception {
112
+        uut.setButtons(leftButton(leftButton), rightButtons(customButton));
113
+        uut.setButtons(new ArrayList<>(), rightButtons(textButton));
114
+        verify(buttonControllers.get(leftButton.id), times(1)).destroy();
115
+    }
116
+
117
+    private List<Button> leftButton(Button leftButton) {
118
+        return Collections.singletonList(leftButton);
119
+    }
120
+
121
+    private List<Button> rightButtons(Button... buttons) {
122
+        return Arrays.asList(buttons);
123
+    }
124
+}

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

@@ -0,0 +1,57 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.support.annotation.NonNull;
5
+
6
+import com.reactnativenavigation.BaseTest;
7
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
8
+import com.reactnativenavigation.parse.Options;
9
+import com.reactnativenavigation.parse.params.Button;
10
+import com.reactnativenavigation.parse.params.Text;
11
+
12
+import org.junit.Ignore;
13
+import org.junit.Test;
14
+
15
+import static org.mockito.Mockito.spy;
16
+import static org.mockito.Mockito.times;
17
+import static org.mockito.Mockito.verify;
18
+
19
+public class TopBarButtonControllerTest extends BaseTest {
20
+
21
+    private TopBarButtonController uut;
22
+    private StackController stackController;
23
+
24
+    @Override
25
+    public void beforeEach() {
26
+        Button button = createButton();
27
+        final Activity activity = newActivity();
28
+
29
+        TopBarButtonCreatorMock buttonCreatorMock = new TopBarButtonCreatorMock();
30
+        uut = spy(new TopBarButtonController(activity, button, buttonCreatorMock, (buttonId) -> {}));
31
+        stackController = spy(new StackController(activity, buttonCreatorMock, "stack", new Options()));
32
+
33
+    }
34
+
35
+    @Test
36
+    public void buttonDoesNotClearStackOptionsOnAppear() throws Exception {
37
+        uut.ensureViewIsCreated();
38
+        uut.onViewAppeared();
39
+        verify(stackController, times(0)).clearOptions();
40
+    }
41
+
42
+    @Test @Ignore
43
+    public void destroy_buttonIsDestroyedWhenStackIsDestroyed() throws Exception {
44
+        uut.ensureViewIsCreated();
45
+        uut.onViewAppeared();
46
+        stackController.destroy();
47
+        verify(uut, times(1)).destroy();
48
+    }
49
+
50
+    @NonNull
51
+    private Button createButton() {
52
+        Button button = new Button();
53
+        button.id = "btnId";
54
+        button.component = new Text("com.example.customBtn");
55
+        return button;
56
+    }
57
+}

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

@@ -8,6 +8,7 @@ import com.reactnativenavigation.BaseTest;
8 8
 import com.reactnativenavigation.mocks.MockPromise;
9 9
 import com.reactnativenavigation.mocks.TestComponentViewCreator;
10 10
 import com.reactnativenavigation.mocks.TestReactView;
11
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
11 12
 import com.reactnativenavigation.parse.Options;
12 13
 import com.reactnativenavigation.parse.params.Text;
13 14
 import com.reactnativenavigation.utils.ViewHelper;
@@ -59,7 +60,7 @@ public class TopTabsViewControllerTest extends BaseTest {
59 60
         uut = spy(new TopTabsController(activity, "componentId", tabControllers, layoutCreator, options));
60 61
         tabControllers.forEach(viewController -> viewController.setParentController(uut));
61 62
 
62
-        parentController = spy(new StackController(activity, "stackId", new Options()));
63
+        parentController = spy(new StackController(activity, new TopBarButtonCreatorMock(), "stackId", new Options()));
63 64
         parentController.push(uut, new MockPromise());
64 65
         uut.setParentController(parentController);
65 66
     }
@@ -214,7 +215,7 @@ public class TopTabsViewControllerTest extends BaseTest {
214 215
     public void applyOptions_tabsAreRemovedAfterViewDisappears() throws Exception {
215 216
         parentController.getView().removeAllViews();
216 217
 
217
-        StackController stackController = spy(new StackController(activity, "stack", new Options()));
218
+        StackController stackController = spy(new StackController(activity, new TopBarButtonCreatorMock(), "stack", new Options()));
218 219
         ComponentViewController first = new ComponentViewController(
219 220
                 activity,
220 221
                 "firstScreen",

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

@@ -9,6 +9,7 @@ import android.widget.LinearLayout;
9 9
 import com.reactnativenavigation.BaseTest;
10 10
 import com.reactnativenavigation.mocks.MockPromise;
11 11
 import com.reactnativenavigation.mocks.SimpleViewController;
12
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
12 13
 import com.reactnativenavigation.parse.Options;
13 14
 
14 15
 import org.assertj.android.api.Assertions;
@@ -65,7 +66,7 @@ public class ViewControllerTest extends BaseTest {
65 66
     @Test
66 67
     public void holdsAReferenceToStackControllerOrNull() throws Exception {
67 68
         assertThat(uut.getParentController()).isNull();
68
-        StackController nav = new StackController(activity, "stack", new Options());
69
+        StackController nav = new StackController(activity, new TopBarButtonCreatorMock(), "stack", new Options());
69 70
         nav.animatePush(uut, new MockPromise());
70 71
         assertThat(uut.getParentController()).isEqualTo(nav);
71 72
     }

+ 8
- 6
lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarTest.java View File

@@ -5,11 +5,13 @@ import android.view.MenuItem;
5 5
 
6 6
 import com.reactnativenavigation.BaseTest;
7 7
 import com.reactnativenavigation.anim.TopBarAnimator;
8
-import com.reactnativenavigation.parse.params.Button;
9
-import com.reactnativenavigation.parse.params.Text;
8
+import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
10 9
 import com.reactnativenavigation.parse.params.Bool;
10
+import com.reactnativenavigation.parse.params.Button;
11 11
 import com.reactnativenavigation.parse.params.NullBool;
12
+import com.reactnativenavigation.parse.params.Text;
12 13
 import com.reactnativenavigation.utils.TitleBarHelper;
14
+import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
13 15
 
14 16
 import org.junit.Test;
15 17
 
@@ -26,18 +28,18 @@ public class TopBarTest extends BaseTest {
26 28
     private TopBarAnimator animator;
27 29
     private ArrayList<Button> leftButton;
28 30
     private ArrayList<Button> rightButtons;
29
-    private TitleBarButton.OnClickListener onClickListener;
31
+    private TopBarButtonController.OnClickListener onClickListener;
30 32
 
31 33
     @Override
32 34
     public void beforeEach() {
33
-        onClickListener = spy(new TitleBarButton.OnClickListener() {
35
+        onClickListener = spy(new TopBarButtonController.OnClickListener() {
34 36
             @Override
35 37
             public void onPress(String buttonId) {
36 38
                 Log.i("TopBarTest", "onPress: " + buttonId);
37 39
             }
38 40
         });
39
-        StackLayout parent = new StackLayout(newActivity(), this.onClickListener);
40
-        uut = new TopBar(newActivity(), this.onClickListener, parent);
41
+        StackLayout parent = new StackLayout(newActivity(), new TopBarButtonCreatorMock(), this.onClickListener);
42
+        uut = new TopBar(newActivity(), new TopBarButtonCreatorMock(), this.onClickListener, parent);
41 43
         animator = spy(new TopBarAnimator(uut));
42 44
         uut.setAnimator(animator);
43 45
         leftButton = createLeftButton();

+ 55
- 0
playground/src/screens/CustomRoundedButton.js View File

@@ -0,0 +1,55 @@
1
+const React = require('react');
2
+const { Component } = require('react');
3
+const {
4
+  StyleSheet,
5
+  View,
6
+  TouchableOpacity,
7
+  Text,
8
+  Alert,
9
+  Platform
10
+} = require('react-native');
11
+
12
+class CustomRoundedButton extends Component {
13
+
14
+  constructor(props) {
15
+    super(props);
16
+    this.state = {};
17
+  }
18
+
19
+  render() {
20
+    return (
21
+      <View style={styles.container} key={'guyguy'}>
22
+        <View style={styles.button}>
23
+          <TouchableOpacity onPress={() => Alert.alert(this.props.title, 'Thanks for that :)')}>
24
+            <Text style={styles.text}>Hi:)</Text>
25
+          </TouchableOpacity>
26
+        </View>
27
+      </View>
28
+    );
29
+  }
30
+}
31
+
32
+module.exports = CustomRoundedButton;
33
+
34
+const styles = StyleSheet.create({
35
+  container: {
36
+    flex: 1,
37
+    width: 48,
38
+    height: 48,
39
+    backgroundColor: 'transparent',
40
+    flexDirection: 'column',
41
+    justifyContent: 'center',
42
+    alignItems: 'center'
43
+  },
44
+  button: {
45
+    width: 40,
46
+    height: 40,
47
+    borderRadius: 20,
48
+    backgroundColor: 'red',
49
+    justifyContent: 'center',
50
+  },
51
+  text: {
52
+    color: 'black',
53
+    alignSelf: 'center'
54
+  }
55
+});

+ 46
- 0
playground/src/screens/CustomTextButton.js View File

@@ -0,0 +1,46 @@
1
+const React = require('react');
2
+const { Component } = require('react');
3
+const {
4
+  StyleSheet,
5
+  View,
6
+  TouchableOpacity,
7
+  Text,
8
+  Alert,
9
+  Platform
10
+} = require('react-native');
11
+
12
+class CustomTextButton extends Component {
13
+
14
+  constructor(props) {
15
+    super(props);
16
+    this.state = {};
17
+  }
18
+
19
+  render() {
20
+    return (
21
+      <View style={styles.container}>
22
+        <TouchableOpacity stye={styles.button} onPress={() => Alert.alert(this.props.title, 'Thanks for that :)')}>
23
+          <Text style={styles.text}>Press Me</Text>
24
+        </TouchableOpacity>
25
+      </View>
26
+    );
27
+  }
28
+}
29
+
30
+module.exports = CustomTextButton;
31
+
32
+const styles = StyleSheet.create({
33
+  container: {
34
+    flex: 1,
35
+    width: 60,
36
+    flexDirection: 'column',
37
+    justifyContent: 'center',
38
+  },
39
+  button: {
40
+    flex: 1,
41
+    flexDirection: 'column',
42
+  },
43
+  text: {
44
+    color: 'black',
45
+  }
46
+});

+ 21
- 7
playground/src/screens/OptionsScreen.js View File

@@ -8,6 +8,8 @@ const testIDs = require('../testIDs');
8 8
 
9 9
 const BUTTON_ONE = 'buttonOne';
10 10
 const BUTTON_TWO = 'buttonTwo';
11
+const CUSTOM_BUTTON = 'customButton';
12
+const CUSTOM_BUTTON2 = 'customButton2';
11 13
 const BUTTON_LEFT = 'buttonLeft';
12 14
 const FAB = 'fab';
13 15
 
@@ -26,13 +28,25 @@ class OptionsScreen extends Component {
26 28
         textFontSize: 16,
27 29
         textFontFamily: 'HelveticaNeue-Italic',
28 30
         testID: testIDs.TOP_BAR_ELEMENT,
29
-        rightButtons: [{
30
-          id: BUTTON_ONE,
31
-          testID: BUTTON_ONE,
32
-          title: 'One',
33
-          buttonFontSize: 28,
34
-          buttonColor: 'red'
35
-        }],
31
+        rightButtons: [
32
+          {
33
+            id: CUSTOM_BUTTON,
34
+            testID: CUSTOM_BUTTON,
35
+            component: 'CustomTextButton'
36
+          },
37
+          {
38
+            id: CUSTOM_BUTTON2,
39
+            testID: CUSTOM_BUTTON2,
40
+            component: 'CustomRoundedButton'
41
+          },
42
+          {
43
+            id: BUTTON_ONE,
44
+            testID: BUTTON_ONE,
45
+            title: 'One',
46
+            buttonFontSize: 28,
47
+            buttonColor: 'red'
48
+          }
49
+        ],
36 50
         leftButtons: [{
37 51
           id: BUTTON_LEFT,
38 52
           testID: BUTTON_LEFT,

+ 4
- 0
playground/src/screens/index.js View File

@@ -19,6 +19,8 @@ const TopTabOptionsScreen = require('./TopTabOptionsScreen');
19 19
 const CustomTopBar = require('./CustomTopBar');
20 20
 const Alert = require('./Alert');
21 21
 const BackHandlerModalScreen = require('./BackHandlerModalScreen');
22
+const CustomTextButton = require('./CustomTextButton');
23
+const CustomRoundedButton = require('./CustomRoundedButton');
22 24
 
23 25
 function registerScreens() {
24 26
   Navigation.registerComponent(`navigation.playground.CustomTransitionDestination`, () => CustomTransitionDestination);
@@ -41,6 +43,8 @@ function registerScreens() {
41 43
   Navigation.registerComponent('navigation.playground.CustomTopBar', () => CustomTopBar);
42 44
   Navigation.registerComponent('navigation.playground.alert', () => Alert);
43 45
   Navigation.registerComponent('navigation.playground.BackHandlerModalScreen', () => BackHandlerModalScreen);
46
+  Navigation.registerComponent('CustomTextButton', () => CustomTextButton);
47
+  Navigation.registerComponent('CustomRoundedButton', () => CustomRoundedButton);
44 48
 }
45 49
 
46 50
 module.exports = {