Browse Source

Refactor right buttons (#6090)

Refactor right buttons

This pr started with an attempt to eliminate button flickering entirely when updating buttons which contain react components via mergeOptions. While the attempt wasn't 100% successful, I dit get some insights in the process.

* It's not possible to add buttons at specific indices. Buttons contain an order property which determines the order of the button in the TopBar. To somewhat overcome this limitation, we can let users control button order via options.

* When a right most component button is replaced with another component button, the rest of the buttons shift to the right since the newly added component isn't measured when it's added to the menu and its width is zero. 
We usually handle this situation with the `waitForRender` option but since buttons are measured with `MeasureSpec.UNSPECIFIED`, their dimensions are zero. Because of this, I've added options to set button dimensions.

* When updating buttons via mergeOptions, if a component button is already added to the menu with the same order we will not remove and added it again. This mitigates flickering in some situations.

* Textual button style properties were applied by traversing the view hierarchy and searching for the TextView corresponding to the button and updating its styles directly.
There was an inherent bug in this logic where if two buttons contained the same text, styles could have been applied to the wrong TextView. We now apply styles directly on the button using spans.
Guy Carmeli 4 years ago
parent
commit
42a6917eee
No account linked to committer's email address
29 changed files with 652 additions and 378 deletions
  1. 14
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Component.java
  2. 12
    5
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Button.java
  3. 4
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Text.java
  4. 54
    47
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/StackPresenter.java
  5. 6
    66
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ButtonPresenter.java
  6. 27
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ButtonSpan.kt
  7. 42
    2
      lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java
  8. 4
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/Functions.java
  9. 17
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/IdFactory.kt
  10. 4
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ObjectUtils.java
  11. 32
    15
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarButtonController.java
  12. 37
    4
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarController.java
  13. 37
    14
      lib/android/app/src/main/java/com/reactnativenavigation/views/titlebar/TitleBar.java
  14. 4
    5
      lib/android/app/src/main/java/com/reactnativenavigation/views/titlebar/TitleBarButtonCreator.java
  15. 20
    3
      lib/android/app/src/main/java/com/reactnativenavigation/views/titlebar/TitleBarReactButtonView.java
  16. 28
    11
      lib/android/app/src/main/java/com/reactnativenavigation/views/topbar/TopBar.java
  17. 2
    2
      lib/android/app/src/test/java/com/reactnativenavigation/TestUtils.java
  18. 9
    4
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TitleBarButtonCreatorMock.java
  19. 4
    12
      lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleBarHelper.java
  20. 63
    37
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackPresenterTest.java
  21. 61
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TitleBarButtonControllerTest.java
  22. 0
    77
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TitleBarTest.java
  23. 5
    68
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarButtonControllerTest.java
  24. 144
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarControllerTest.java
  25. 2
    2
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java
  26. 8
    0
      lib/src/interfaces/Options.ts
  27. 8
    2
      playground/src/screens/OptionsScreen.js
  28. 2
    1
      playground/src/screens/RoundedButton.js
  29. 2
    0
      playground/src/testIDs.js

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

2
 
2
 
3
 import com.reactnativenavigation.parse.params.Bool;
3
 import com.reactnativenavigation.parse.params.Bool;
4
 import com.reactnativenavigation.parse.params.NullBool;
4
 import com.reactnativenavigation.parse.params.NullBool;
5
+import com.reactnativenavigation.parse.params.NullNumber;
5
 import com.reactnativenavigation.parse.params.NullText;
6
 import com.reactnativenavigation.parse.params.NullText;
7
+import com.reactnativenavigation.parse.params.Number;
6
 import com.reactnativenavigation.parse.params.Text;
8
 import com.reactnativenavigation.parse.params.Text;
7
 import com.reactnativenavigation.parse.parsers.BoolParser;
9
 import com.reactnativenavigation.parse.parsers.BoolParser;
10
+import com.reactnativenavigation.parse.parsers.NumberParser;
8
 import com.reactnativenavigation.parse.parsers.TextParser;
11
 import com.reactnativenavigation.parse.parsers.TextParser;
9
 
12
 
10
 import org.json.JSONObject;
13
 import org.json.JSONObject;
18
         result.componentId = TextParser.parse(json, "componentId");
21
         result.componentId = TextParser.parse(json, "componentId");
19
         result.alignment = Alignment.fromString(TextParser.parse(json, "alignment").get(""));
22
         result.alignment = Alignment.fromString(TextParser.parse(json, "alignment").get(""));
20
         result.waitForRender = BoolParser.parse(json, "waitForRender");
23
         result.waitForRender = BoolParser.parse(json, "waitForRender");
24
+        result.width = NumberParser.parse(json, "width");
25
+        result.height = NumberParser.parse(json, "height");
21
 
26
 
22
         return result;
27
         return result;
23
     }
28
     }
26
     public Text componentId = new NullText();
31
     public Text componentId = new NullText();
27
     public Alignment alignment = Alignment.Default;
32
     public Alignment alignment = Alignment.Default;
28
     public Bool waitForRender = new NullBool();
33
     public Bool waitForRender = new NullBool();
34
+    public Number width = new NullNumber();
35
+    public Number height = new NullNumber();
29
 
36
 
30
     void mergeWith(Component other) {
37
     void mergeWith(Component other) {
31
         if (other.componentId.hasValue()) componentId = other.componentId;
38
         if (other.componentId.hasValue()) componentId = other.componentId;
32
         if (other.name.hasValue()) name = other.name;
39
         if (other.name.hasValue()) name = other.name;
33
         if (other.waitForRender.hasValue()) waitForRender = other.waitForRender;
40
         if (other.waitForRender.hasValue()) waitForRender = other.waitForRender;
34
         if (other.alignment != Alignment.Default) alignment = other.alignment;
41
         if (other.alignment != Alignment.Default) alignment = other.alignment;
42
+        if (other.width.hasValue()) width = other.width;
43
+        if (other.height.hasValue()) height = other.height;
35
     }
44
     }
36
 
45
 
37
     public void mergeWithDefault(Component defaultOptions) {
46
     public void mergeWithDefault(Component defaultOptions) {
39
         if (!name.hasValue()) name = defaultOptions.name;
48
         if (!name.hasValue()) name = defaultOptions.name;
40
         if (!waitForRender.hasValue()) waitForRender = defaultOptions.waitForRender;
49
         if (!waitForRender.hasValue()) waitForRender = defaultOptions.waitForRender;
41
         if (alignment == Alignment.Default) alignment = defaultOptions.alignment;
50
         if (alignment == Alignment.Default) alignment = defaultOptions.alignment;
51
+        if (!width.hasValue()) width = defaultOptions.width;
52
+        if (!height.hasValue()) height = defaultOptions.height;
42
     }
53
     }
43
 
54
 
44
     public boolean hasValue() {
55
     public boolean hasValue() {
49
         return name.equals(other.name) &&
60
         return name.equals(other.name) &&
50
                componentId.equals(other.componentId) &&
61
                componentId.equals(other.componentId) &&
51
                alignment.equals(other.alignment) &&
62
                alignment.equals(other.alignment) &&
52
-               waitForRender.equals(other.waitForRender);
63
+               waitForRender.equals(other.waitForRender) &&
64
+               width.equals(other.width) &&
65
+               height.equals(other.height);
53
     }
66
     }
54
 }
67
 }

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

6
 import com.reactnativenavigation.parse.Component;
6
 import com.reactnativenavigation.parse.Component;
7
 import com.reactnativenavigation.parse.parsers.BoolParser;
7
 import com.reactnativenavigation.parse.parsers.BoolParser;
8
 import com.reactnativenavigation.parse.parsers.ColorParser;
8
 import com.reactnativenavigation.parse.parsers.ColorParser;
9
-import com.reactnativenavigation.parse.parsers.NumberParser;
9
+import com.reactnativenavigation.parse.parsers.FractionParser;
10
 import com.reactnativenavigation.parse.parsers.TextParser;
10
 import com.reactnativenavigation.parse.parsers.TextParser;
11
 import com.reactnativenavigation.utils.CompatUtils;
11
 import com.reactnativenavigation.utils.CompatUtils;
12
+import com.reactnativenavigation.utils.IdFactory;
12
 import com.reactnativenavigation.utils.TypefaceLoader;
13
 import com.reactnativenavigation.utils.TypefaceLoader;
13
 
14
 
14
 import org.json.JSONArray;
15
 import org.json.JSONArray;
19
 
20
 
20
 import androidx.annotation.Nullable;
21
 import androidx.annotation.Nullable;
21
 
22
 
23
+import static com.reactnativenavigation.utils.ObjectUtils.take;
24
+
22
 public class Button {
25
 public class Button {
23
     public String instanceId = "btn" + CompatUtils.generateViewId();
26
     public String instanceId = "btn" + CompatUtils.generateViewId();
24
 
27
 
25
-    @Nullable public String id;
28
+    public String id = "btn" + CompatUtils.generateViewId();
26
     public Text accessibilityLabel = new NullText();
29
     public Text accessibilityLabel = new NullText();
27
     public Text text = new NullText();
30
     public Text text = new NullText();
28
     public Bool enabled = new NullBool();
31
     public Bool enabled = new NullBool();
30
     public Number showAsAction = new NullNumber();
33
     public Number showAsAction = new NullNumber();
31
     public Colour color = new NullColor();
34
     public Colour color = new NullColor();
32
     public Colour disabledColor = new NullColor();
35
     public Colour disabledColor = new NullColor();
33
-    public Number fontSize = new NullNumber();
36
+    public Fraction fontSize = new NullFraction();
34
     private Text fontWeight = new NullText();
37
     private Text fontWeight = new NullText();
35
     @Nullable public Typeface fontFamily;
38
     @Nullable public Typeface fontFamily;
36
     public Text icon = new NullText();
39
     public Text icon = new NullText();
56
 
59
 
57
     private static Button parseJson(JSONObject json, TypefaceLoader typefaceManager) {
60
     private static Button parseJson(JSONObject json, TypefaceLoader typefaceManager) {
58
         Button button = new Button();
61
         Button button = new Button();
59
-        button.id = json.optString("id");
62
+        button.id = take(json.optString("id"), "btn" + CompatUtils.generateViewId());
60
         button.accessibilityLabel = TextParser.parse(json, "accessibilityLabel");
63
         button.accessibilityLabel = TextParser.parse(json, "accessibilityLabel");
61
         button.text = TextParser.parse(json, "text");
64
         button.text = TextParser.parse(json, "text");
62
         button.enabled = BoolParser.parse(json, "enabled");
65
         button.enabled = BoolParser.parse(json, "enabled");
64
         button.showAsAction = parseShowAsAction(json);
67
         button.showAsAction = parseShowAsAction(json);
65
         button.color = ColorParser.parse(json, "color");
68
         button.color = ColorParser.parse(json, "color");
66
         button.disabledColor = ColorParser.parse(json, "disabledColor");
69
         button.disabledColor = ColorParser.parse(json, "disabledColor");
67
-        button.fontSize = NumberParser.parse(json, "fontSize");
70
+        button.fontSize = FractionParser.parse(json, "fontSize");
68
         button.fontFamily = typefaceManager.getTypeFace(json.optString("fontFamily", ""));
71
         button.fontFamily = typefaceManager.getTypeFace(json.optString("fontFamily", ""));
69
         button.fontWeight = TextParser.parse(json, "fontWeight");
72
         button.fontWeight = TextParser.parse(json, "fontWeight");
70
         button.testId = TextParser.parse(json, "testID");
73
         button.testId = TextParser.parse(json, "testID");
116
         return icon.hasValue();
119
         return icon.hasValue();
117
     }
120
     }
118
 
121
 
122
+    public int getIntId() {
123
+        return IdFactory.Companion.get(component.componentId.get(id));
124
+    }
125
+
119
     private static Number parseShowAsAction(JSONObject json) {
126
     private static Number parseShowAsAction(JSONObject json) {
120
         final Text showAsAction = TextParser.parse(json, "showAsAction");
127
         final Text showAsAction = TextParser.parse(json, "showAsAction");
121
         if (!showAsAction.hasValue()) {
128
         if (!showAsAction.hasValue()) {

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

7
         super(value);
7
         super(value);
8
     }
8
     }
9
 
9
 
10
+    public int length() {
11
+        return hasValue() ? value.length() : 0;
12
+    }
13
+
10
     @NonNull
14
     @NonNull
11
     @Override
15
     @Override
12
     public String toString() {
16
     public String toString() {

+ 54
- 47
lib/android/app/src/main/java/com/reactnativenavigation/presentation/StackPresenter.java View File

25
 import com.reactnativenavigation.utils.StatusBarUtils;
25
 import com.reactnativenavigation.utils.StatusBarUtils;
26
 import com.reactnativenavigation.utils.UiUtils;
26
 import com.reactnativenavigation.utils.UiUtils;
27
 import com.reactnativenavigation.viewcontrollers.IReactView;
27
 import com.reactnativenavigation.viewcontrollers.IReactView;
28
-import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
29
 import com.reactnativenavigation.viewcontrollers.TitleBarButtonController;
28
 import com.reactnativenavigation.viewcontrollers.TitleBarButtonController;
30
 import com.reactnativenavigation.viewcontrollers.TitleBarReactViewController;
29
 import com.reactnativenavigation.viewcontrollers.TitleBarReactViewController;
31
 import com.reactnativenavigation.viewcontrollers.ViewController;
30
 import com.reactnativenavigation.viewcontrollers.ViewController;
33
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
32
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
34
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarBackgroundViewController;
33
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarBackgroundViewController;
35
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
34
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
35
+import com.reactnativenavigation.views.titlebar.TitleBarButtonCreator;
36
 import com.reactnativenavigation.views.titlebar.TitleBarReactViewCreator;
36
 import com.reactnativenavigation.views.titlebar.TitleBarReactViewCreator;
37
 import com.reactnativenavigation.views.topbar.TopBar;
37
 import com.reactnativenavigation.views.topbar.TopBar;
38
 import com.reactnativenavigation.views.topbar.TopBarBackgroundViewCreator;
38
 import com.reactnativenavigation.views.topbar.TopBarBackgroundViewCreator;
44
 import java.util.List;
44
 import java.util.List;
45
 import java.util.Map;
45
 import java.util.Map;
46
 
46
 
47
+import androidx.annotation.NonNull;
47
 import androidx.annotation.Nullable;
48
 import androidx.annotation.Nullable;
48
 import androidx.annotation.RestrictTo;
49
 import androidx.annotation.RestrictTo;
49
 import androidx.appcompat.widget.Toolbar;
50
 import androidx.appcompat.widget.Toolbar;
50
 
51
 
51
 import static com.reactnativenavigation.utils.CollectionUtils.*;
52
 import static com.reactnativenavigation.utils.CollectionUtils.*;
52
 import static com.reactnativenavigation.utils.ObjectUtils.perform;
53
 import static com.reactnativenavigation.utils.ObjectUtils.perform;
54
+import static com.reactnativenavigation.utils.ObjectUtils.take;
53
 
55
 
54
 public class StackPresenter {
56
 public class StackPresenter {
55
     private static final int DEFAULT_TITLE_COLOR = Color.BLACK;
57
     private static final int DEFAULT_TITLE_COLOR = Color.BLACK;
66
     private TitleBarButtonController.OnClickListener onClickListener;
68
     private TitleBarButtonController.OnClickListener onClickListener;
67
     private final RenderChecker renderChecker;
69
     private final RenderChecker renderChecker;
68
     private final TopBarBackgroundViewCreator topBarBackgroundViewCreator;
70
     private final TopBarBackgroundViewCreator topBarBackgroundViewCreator;
69
-    private final ReactViewCreator buttonCreator;
71
+    private final TitleBarButtonCreator buttonCreator;
70
     private Options defaultOptions;
72
     private Options defaultOptions;
71
 
73
 
72
     private List<TitleBarButtonController> currentRightButtons = new ArrayList<>();
74
     private List<TitleBarButtonController> currentRightButtons = new ArrayList<>();
79
     public StackPresenter(Activity activity,
81
     public StackPresenter(Activity activity,
80
                           TitleBarReactViewCreator titleViewCreator,
82
                           TitleBarReactViewCreator titleViewCreator,
81
                           TopBarBackgroundViewCreator topBarBackgroundViewCreator,
83
                           TopBarBackgroundViewCreator topBarBackgroundViewCreator,
82
-                          ReactViewCreator buttonCreator,
84
+                          TitleBarButtonCreator buttonCreator,
83
                           IconResolver iconResolver,
85
                           IconResolver iconResolver,
84
                           RenderChecker renderChecker,
86
                           RenderChecker renderChecker,
85
                           Options defaultOptions) {
87
                           Options defaultOptions) {
268
     }
270
     }
269
 
271
 
270
     private void applyButtons(TopBarOptions options, ViewController child) {
272
     private void applyButtons(TopBarOptions options, ViewController child) {
271
-        List<Button> rightButtons = mergeButtonsWithColor(options.buttons.right, options.rightButtonColor, options.rightButtonDisabledColor);
272
-        List<Button> leftButtons = mergeButtonsWithColor(options.buttons.left, options.leftButtonColor, options.leftButtonDisabledColor);
273
-
274
-        if (rightButtons != null) {
275
-            List<TitleBarButtonController> rightButtonControllers = getOrCreateButtonControllers(componentRightButtons.get(child.getView()), rightButtons);
273
+        if (options.buttons.right != null) {
274
+            List<Button> rightButtons = mergeButtonsWithColor(options.buttons.right, options.rightButtonColor, options.rightButtonDisabledColor);
275
+            List<TitleBarButtonController> rightButtonControllers = getOrCreateButtonControllersByInstanceId(componentRightButtons.get(child.getView()), rightButtons);
276
             componentRightButtons.put(child.getView(), keyBy(rightButtonControllers, TitleBarButtonController::getButtonInstanceId));
276
             componentRightButtons.put(child.getView(), keyBy(rightButtonControllers, TitleBarButtonController::getButtonInstanceId));
277
             if (!CollectionUtils.equals(currentRightButtons, rightButtonControllers)) {
277
             if (!CollectionUtils.equals(currentRightButtons, rightButtonControllers)) {
278
                 currentRightButtons = rightButtonControllers;
278
                 currentRightButtons = rightButtonControllers;
279
-                topBar.setRightButtons(rightButtonControllers);
279
+                topBarController.applyRightButtons(currentRightButtons);
280
             }
280
             }
281
         } else {
281
         } else {
282
             currentRightButtons = null;
282
             currentRightButtons = null;
283
             topBar.clearRightButtons();
283
             topBar.clearRightButtons();
284
         }
284
         }
285
 
285
 
286
-        if (leftButtons != null) {
287
-            List<TitleBarButtonController> leftButtonControllers = getOrCreateButtonControllers(componentLeftButtons.get(child.getView()), leftButtons);
286
+        if (options.buttons.left != null) {
287
+            List<Button> leftButtons = mergeButtonsWithColor(options.buttons.left, options.leftButtonColor, options.leftButtonDisabledColor);
288
+            List<TitleBarButtonController> leftButtonControllers = getOrCreateButtonControllersByInstanceId(componentLeftButtons.get(child.getView()), leftButtons);
288
             componentLeftButtons.put(child.getView(), keyBy(leftButtonControllers, TitleBarButtonController::getButtonInstanceId));
289
             componentLeftButtons.put(child.getView(), keyBy(leftButtonControllers, TitleBarButtonController::getButtonInstanceId));
289
-            topBar.setLeftButtons(leftButtonControllers);
290
+            topBarController.setLeftButtons(leftButtonControllers);
290
         } else {
291
         } else {
291
             topBar.clearLeftButtons();
292
             topBar.clearLeftButtons();
292
         }
293
         }
298
         topBar.setOverflowButtonColor(options.rightButtonColor.get(Color.BLACK));
299
         topBar.setOverflowButtonColor(options.rightButtonColor.get(Color.BLACK));
299
     }
300
     }
300
 
301
 
301
-    private List<TitleBarButtonController> getOrCreateButtonControllers(@Nullable Map<String, TitleBarButtonController> currentButtons, @Nullable List<Button> buttons) {
302
+    private List<TitleBarButtonController> getOrCreateButtonControllersByInstanceId(@Nullable Map<String, TitleBarButtonController> currentButtons, @Nullable List<Button> buttons) {
302
         if (buttons == null) return null;
303
         if (buttons == null) return null;
303
         Map<String, TitleBarButtonController> result = new LinkedHashMap<>();
304
         Map<String, TitleBarButtonController> result = new LinkedHashMap<>();
305
+        forEach(buttons, b -> result.put(b.instanceId, getOrDefault(currentButtons, b.instanceId, () -> createButtonController(b))));
306
+        return new ArrayList<>(result.values());
307
+    }
308
+
309
+    private List<TitleBarButtonController> getOrCreateButtonControllersById(@Nullable Map<String, TitleBarButtonController> currentButtons,@NonNull List<Button> buttons) {
310
+        ArrayList result = new ArrayList<TitleBarButtonController>();
304
         for (Button b : buttons) {
311
         for (Button b : buttons) {
305
-            result.put(b.instanceId, currentButtons != null && currentButtons.containsKey(b.instanceId) ? currentButtons.get(b.instanceId) : createButtonController(b));
312
+            result.add(take(first(perform(currentButtons, null, Map::values), button -> button.getId().equals(b.id)), createButtonController(b)));
306
         }
313
         }
307
-        return new ArrayList<>(result.values());
314
+        return result;
308
     }
315
     }
309
 
316
 
310
     private TitleBarButtonController createButtonController(Button button) {
317
     private TitleBarButtonController createButtonController(Button button) {
311
         TitleBarButtonController controller = new TitleBarButtonController(activity,
318
         TitleBarButtonController controller = new TitleBarButtonController(activity,
312
                 iconResolver,
319
                 iconResolver,
313
-                new ButtonPresenter(topBar.getTitleBar(), button),
320
+                new ButtonPresenter(button),
314
                 button,
321
                 button,
315
                 buttonCreator,
322
                 buttonCreator,
316
                 onClickListener
323
                 onClickListener
354
     }
361
     }
355
 
362
 
356
     private void mergeButtons(TopBarOptions options, TopBarButtons buttons, View child) {
363
     private void mergeButtons(TopBarOptions options, TopBarButtons buttons, View child) {
357
-        List<Button> rightButtons = mergeButtonsWithColor(buttons.right, options.rightButtonColor, options.rightButtonDisabledColor);
358
-        List<Button> leftButtons = mergeButtonsWithColor(buttons.left, options.leftButtonColor, options.leftButtonDisabledColor);
364
+        mergeRightButtons(options, buttons, child);
365
+        mergeLeftButton(options, buttons, child);
366
+        mergeBackButton(buttons);
367
+    }
359
 
368
 
360
-        List<TitleBarButtonController> rightButtonControllers = getOrCreateButtonControllers(componentRightButtons.get(child), rightButtons);
361
-        List<TitleBarButtonController> leftButtonControllers = getOrCreateButtonControllers(componentLeftButtons.get(child), leftButtons);
369
+    private void mergeRightButtons(TopBarOptions options, TopBarButtons buttons, View child) {
370
+        if (buttons.right == null) return;
371
+        List<Button> rightButtons = mergeButtonsWithColor(buttons.right, options.rightButtonColor, options.rightButtonDisabledColor);
372
+        List<TitleBarButtonController> toMerge = getOrCreateButtonControllersById(componentRightButtons.get(child), rightButtons);
373
+        List<TitleBarButtonController> toRemove = difference(currentRightButtons, toMerge, TitleBarButtonController::equals);
374
+        forEach(toRemove, TitleBarButtonController::destroy);
362
 
375
 
363
-        if (rightButtonControllers != null) {
364
-            Map previousRightButtons = componentRightButtons.put(child, keyBy(rightButtonControllers, TitleBarButtonController::getButtonInstanceId));
365
-            if (previousRightButtons != null) forEach(previousRightButtons.values(), TitleBarButtonController::destroy);
366
-        }
367
-        if (leftButtonControllers != null) {
368
-            Map previousLeftButtons = componentLeftButtons.put(child, keyBy(leftButtonControllers, TitleBarButtonController::getButtonInstanceId));
369
-            if (previousLeftButtons != null) forEach(previousLeftButtons.values(), TitleBarButtonController::destroy);
376
+        if (!CollectionUtils.equals(currentRightButtons, toMerge)) {
377
+            topBarController.mergeRightButtons(toMerge, toRemove);
378
+            currentRightButtons = toMerge;
370
         }
379
         }
380
+        if (options.rightButtonColor.hasValue()) topBar.setOverflowButtonColor(options.rightButtonColor.get());
381
+    }
371
 
382
 
372
-        if (buttons.right != null) {
373
-            if (!CollectionUtils.equals(currentRightButtons, rightButtonControllers)) {
374
-                currentRightButtons = rightButtonControllers;
375
-                topBar.setRightButtons(rightButtonControllers);
376
-            }
377
-        }
378
-        if (buttons.left != null) topBar.setLeftButtons(leftButtonControllers);
383
+    private void mergeLeftButton(TopBarOptions options, TopBarButtons buttons, View child) {
384
+        if (buttons.left == null) return;
385
+        List<Button> leftButtons = mergeButtonsWithColor(buttons.left, options.leftButtonColor, options.leftButtonDisabledColor);
386
+        List<TitleBarButtonController> toMerge = getOrCreateButtonControllersById(componentLeftButtons.get(child), leftButtons);
387
+        componentLeftButtons.put(child, keyBy(toMerge, TitleBarButtonController::getButtonInstanceId));
388
+        topBarController.setLeftButtons(toMerge);
389
+    }
390
+
391
+    private void mergeBackButton(TopBarButtons buttons) {
379
         if (buttons.back.hasValue()) {
392
         if (buttons.back.hasValue()) {
380
             if (buttons.back.visible.isFalse()) {
393
             if (buttons.back.visible.isFalse()) {
381
                 topBar.clearLeftButtons();
394
                 topBar.clearLeftButtons();
383
                 topBar.setBackButton(createButtonController(buttons.back));
396
                 topBar.setBackButton(createButtonController(buttons.back));
384
             }
397
             }
385
         }
398
         }
386
-
387
-        if (options.rightButtonColor.hasValue()) topBar.setOverflowButtonColor(options.rightButtonColor.get());
388
     }
399
     }
389
 
400
 
390
-    @Nullable
391
-    private List<Button> mergeButtonsWithColor(List<Button> buttons, Colour buttonColor, Colour disabledColor) {
392
-        List<Button> result = null;
393
-        if (buttons != null) {
394
-            result = new ArrayList<>();
395
-            for (Button button : buttons) {
396
-                Button copy = button.copy();
397
-                if (!button.color.hasValue()) copy.color = buttonColor;
398
-                if (!button.disabledColor.hasValue()) copy.disabledColor = disabledColor;
399
-                result.add(copy);
400
-            }
401
+    private List<Button> mergeButtonsWithColor(@NonNull List<Button> buttons, Colour buttonColor, Colour disabledColor) {
402
+        List<Button> result = new ArrayList<>();
403
+        for (Button button : buttons) {
404
+            Button copy = button.copy();
405
+            if (!button.color.hasValue()) copy.color = buttonColor;
406
+            if (!button.disabledColor.hasValue()) copy.disabledColor = disabledColor;
407
+            result.add(copy);
401
         }
408
         }
402
         return result;
409
         return result;
403
     }
410
     }

+ 6
- 66
lib/android/app/src/main/java/com/reactnativenavigation/utils/ButtonPresenter.java View File

1
 package com.reactnativenavigation.utils;
1
 package com.reactnativenavigation.utils;
2
 
2
 
3
-import android.graphics.Color;
4
 import android.graphics.PorterDuff;
3
 import android.graphics.PorterDuff;
5
 import android.graphics.PorterDuffColorFilter;
4
 import android.graphics.PorterDuffColorFilter;
6
-import android.graphics.Typeface;
7
 import android.graphics.drawable.Drawable;
5
 import android.graphics.drawable.Drawable;
8
-import androidx.annotation.NonNull;
9
-import androidx.appcompat.widget.ActionMenuView;
10
-import androidx.appcompat.widget.Toolbar;
11
-import android.text.Spannable;
12
 import android.text.SpannableString;
6
 import android.text.SpannableString;
13
-import android.text.style.AbsoluteSizeSpan;
14
-import android.view.MenuItem;
15
-import android.view.View;
16
-import android.widget.TextView;
17
 
7
 
18
 import com.reactnativenavigation.parse.params.Button;
8
 import com.reactnativenavigation.parse.params.Button;
19
 
9
 
20
-import java.util.ArrayList;
10
+import static android.text.Spanned.SPAN_EXCLUSIVE_INCLUSIVE;
21
 
11
 
22
 public class ButtonPresenter {
12
 public class ButtonPresenter {
23
-    private final Toolbar toolbar;
24
-    private final ActionMenuView actionMenuView;
25
     private Button button;
13
     private Button button;
26
 
14
 
27
-    public ButtonPresenter(Toolbar toolbar, Button button) {
28
-        this.toolbar = toolbar;
29
-        actionMenuView = ViewUtils.findChildrenByClass(toolbar, ActionMenuView.class).get(0);
15
+    public ButtonPresenter(Button button) {
30
         this.button = button;
16
         this.button = button;
31
     }
17
     }
32
 
18
 
34
         drawable.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_IN));
20
         drawable.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_IN));
35
     }
21
     }
36
 
22
 
37
-    public void setTypeFace(Typeface typeface) {
38
-        UiUtils.runOnPreDrawOnce(toolbar, () -> {
39
-            ArrayList<View> buttons = findActualTextViewInMenu();
40
-            for (View btn : buttons) {
41
-                ((TextView) btn).setTypeface(typeface);
42
-            }
43
-        });
23
+    public SpannableString getStyledText() {
24
+        SpannableString string = new SpannableString(button.text.get(""));
25
+        string.setSpan(new ButtonSpan(button), 0, button.text.length(), SPAN_EXCLUSIVE_INCLUSIVE);
26
+        return string;
44
     }
27
     }
45
-
46
-    public void setFontSize(MenuItem menuItem) {
47
-        SpannableString spanString = new SpannableString(button.text.get());
48
-        if (this.button.fontSize.hasValue())
49
-            spanString.setSpan(
50
-                    new AbsoluteSizeSpan(button.fontSize.get(), true),
51
-                    0,
52
-                    button.text.get().length(),
53
-                    Spannable.SPAN_INCLUSIVE_INCLUSIVE
54
-            );
55
-        menuItem.setTitleCondensed(spanString);
56
-    }
57
-
58
-    public void setTextColor() {
59
-        UiUtils.runOnPreDrawOnce(toolbar, () -> {
60
-            ArrayList<View> buttons = findActualTextViewInMenu();
61
-            for (View btn : buttons) {
62
-                if (button.enabled.isTrueOrUndefined() && button.color.hasValue()) {
63
-                    setEnabledColor((TextView) btn);
64
-                } else if (button.enabled.isFalse()) {
65
-                    setDisabledColor((TextView) btn, button.disabledColor.get(Color.LTGRAY));
66
-                }
67
-            }
68
-        });
69
-    }
70
-
71
-    public void setDisabledColor(TextView btn, int color) {
72
-        btn.setTextColor(color);
73
-    }
74
-
75
-    public void setEnabledColor(TextView btn) {
76
-        btn.setTextColor(button.color.get());
77
-    }
78
-
79
-    @NonNull
80
-    private ArrayList<View> findActualTextViewInMenu() {
81
-        ArrayList<View> outViews = new ArrayList<>();
82
-        if (button.text.hasValue()) {
83
-            actionMenuView.findViewsWithText(outViews, button.text.get(), View.FIND_VIEWS_WITH_TEXT);
84
-        }
85
-        return outViews;
86
-    }
87
-
88
 }
28
 }

+ 27
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/ButtonSpan.kt View File

1
+package com.reactnativenavigation.utils
2
+
3
+import android.graphics.Paint
4
+import android.graphics.Typeface
5
+import android.text.TextPaint
6
+import android.text.style.MetricAffectingSpan
7
+import com.reactnativenavigation.parse.params.Button
8
+import com.reactnativenavigation.parse.params.Colour
9
+import com.reactnativenavigation.parse.params.Fraction
10
+
11
+class ButtonSpan(private val button: Button) : MetricAffectingSpan() {
12
+    override fun updateDrawState(drawState: TextPaint) = apply(drawState)
13
+
14
+    override fun updateMeasureState(paint: TextPaint) = apply(paint)
15
+
16
+    private fun apply(paint: Paint) {
17
+        with(button) {
18
+            val fakeStyle = (paint.typeface?.style ?: 0) and (fontFamily?.style?.inv() ?: 1)
19
+            if (fakeStyle and Typeface.BOLD != 0) paint.isFakeBoldText = true
20
+            if (fakeStyle and Typeface.ITALIC != 0) paint.textSkewX = -0.25f
21
+            if (fontSize.hasValue()) paint.textSize = fontSize.get().toFloat()
22
+            if (color.hasValue()) paint.color = if (enabled.isTrueOrUndefined) color.get() else disabledColor.get()
23
+            paint.typeface = fontFamily
24
+        }
25
+
26
+    }
27
+}

+ 42
- 2
lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java View File

14
 import androidx.annotation.Nullable;
14
 import androidx.annotation.Nullable;
15
 import androidx.core.util.Pair;
15
 import androidx.core.util.Pair;
16
 
16
 
17
+@SuppressWarnings("WeakerAccess")
17
 public class CollectionUtils {
18
 public class CollectionUtils {
18
     public interface Apply<T> {
19
     public interface Apply<T> {
19
         void on(T t);
20
         void on(T t);
20
     }
21
     }
21
 
22
 
23
+    public interface Comparator<T> {
24
+        boolean compare(T a, T b);
25
+    }
26
+
22
     public static boolean isNullOrEmpty(Collection collection) {
27
     public static boolean isNullOrEmpty(Collection collection) {
23
         return collection == null || collection.isEmpty();
28
         return collection == null || collection.isEmpty();
24
     }
29
     }
60
         return result;
65
         return result;
61
     }
66
     }
62
 
67
 
68
+    public static <K, V> V getOrDefault(@Nullable Map<K, V> map, K key, Functions.FuncR<V> defaultValueCreator) {
69
+        if (map == null) return defaultValueCreator.run();
70
+        return map.containsKey(key) ? map.get(key) : defaultValueCreator.run();
71
+    }
72
+
63
     public static <T> List<T> merge(@Nullable Collection<T> a, @Nullable Collection<T> b, @NonNull List<T> defaultValue) {
73
     public static <T> List<T> merge(@Nullable Collection<T> a, @Nullable Collection<T> b, @NonNull List<T> defaultValue) {
64
         List<T> result = merge(a, b);
74
         List<T> result = merge(a, b);
65
         return result == null ? defaultValue : result;
75
         return result == null ? defaultValue : result;
66
     }
76
     }
67
 
77
 
68
-    public static <T> List<T> merge(@Nullable Collection<T> a, @Nullable Collection<T> b) {
78
+    public static <T> ArrayList<T> merge(@Nullable Collection<T> a, @Nullable Collection<T> b) {
69
         if (a == null && b == null) return null;
79
         if (a == null && b == null) return null;
70
-        List<T> result = new ArrayList<>(get(a));
80
+        ArrayList<T> result = new ArrayList<>(get(a));
71
         result.addAll(get(b));
81
         result.addAll(get(b));
72
         return result;
82
         return result;
73
     }
83
     }
74
 
84
 
85
+    /**
86
+     * @return Items in a, that are not in b
87
+     */
88
+    public static <T> List<T> difference(@NonNull Collection<T> a, @Nullable Collection<T> b, Comparator<T> comparator) {
89
+        if (b == null) return new ArrayList<>(a);
90
+        ArrayList<T> results = new ArrayList<>();
91
+        forEach(a, btn -> {
92
+            if (!contains(b, btn, comparator)) results.add(btn);
93
+        });
94
+        return results;
95
+    }
96
+
97
+    private static <T> boolean contains(@NonNull Collection<T> items, T item, Comparator<T> comparator) {
98
+        for (T t : items) {
99
+            if (comparator.compare(t, item)) return true;
100
+        }
101
+        return false;
102
+    }
103
+
75
     public static <T> void forEach(@Nullable Collection<T> items, Apply<T> apply) {
104
     public static <T> void forEach(@Nullable Collection<T> items, Apply<T> apply) {
76
         if (items != null) forEach(new ArrayList<>(items), 0, apply);
105
         if (items != null) forEach(new ArrayList<>(items), 0, apply);
77
     }
106
     }
94
         }
123
         }
95
     }
124
     }
96
 
125
 
126
+    public static <T> void forEachIndexed(@Nullable List<T> items, Functions.Func2<T, Integer> apply) {
127
+        if (items == null) return;
128
+        for (int i = 0; i < items.size(); i++) {
129
+            apply.run(items.get(i), i);
130
+        }
131
+    }
132
+
97
     public static @Nullable <T> T first(@Nullable Collection<T> items, Filter<T> by) {
133
     public static @Nullable <T> T first(@Nullable Collection<T> items, Filter<T> by) {
98
         if (isNullOrEmpty(items)) return null;
134
         if (isNullOrEmpty(items)) return null;
99
         for (T item : items) {
135
         for (T item : items) {
170
         }
206
         }
171
         return result;
207
         return result;
172
     }
208
     }
209
+
210
+    public static @Nullable<T> T safeGet(List<T> items, int index) {
211
+        return index >= 0 && index < items.size() ? items.get(index) : null;
212
+    }
173
 }
213
 }

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

13
         void run(T param);
13
         void run(T param);
14
     }
14
     }
15
 
15
 
16
+    public interface Func2<T, S> {
17
+        void run(T param1, S param2);
18
+    }
19
+
16
     public interface FuncR<T> {
20
     public interface FuncR<T> {
17
         T run();
21
         T run();
18
     }
22
     }

+ 17
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/IdFactory.kt View File

1
+package com.reactnativenavigation.utils
2
+
3
+
4
+class IdFactory {
5
+    companion object {
6
+        private val stringIdToIntId = HashMap<String, Int>()
7
+        private var count = 0
8
+
9
+        fun get(id: String): Int {
10
+            return if (stringIdToIntId.containsKey(id)) {
11
+                stringIdToIntId[id]!!
12
+            } else {
13
+                (++count).apply { stringIdToIntId[id] = count }
14
+            }
15
+        }
16
+    }
17
+}

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

19
         return obj == null ? defaultValue : obj;
19
         return obj == null ? defaultValue : obj;
20
     }
20
     }
21
 
21
 
22
+    public static <T> T getOrCreate(@Nullable T obj, @NonNull Functions.FuncR<T> creator) {
23
+        return obj == null ? creator.run() : obj;
24
+    }
25
+
22
     public static boolean notNull(Object o) {
26
     public static boolean notNull(Object o) {
23
         return o != null;
27
         return o != null;
24
     }
28
     }

+ 32
- 15
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarButtonController.java View File

21
 import com.reactnativenavigation.utils.ViewUtils;
21
 import com.reactnativenavigation.utils.ViewUtils;
22
 import com.reactnativenavigation.viewcontrollers.button.IconResolver;
22
 import com.reactnativenavigation.viewcontrollers.button.IconResolver;
23
 import com.reactnativenavigation.viewcontrollers.viewcontrolleroverlay.ViewControllerOverlay;
23
 import com.reactnativenavigation.viewcontrollers.viewcontrolleroverlay.ViewControllerOverlay;
24
+import com.reactnativenavigation.views.titlebar.TitleBar;
25
+import com.reactnativenavigation.views.titlebar.TitleBarButtonCreator;
24
 import com.reactnativenavigation.views.titlebar.TitleBarReactButtonView;
26
 import com.reactnativenavigation.views.titlebar.TitleBarReactButtonView;
25
 
27
 
26
 import java.util.List;
28
 import java.util.List;
27
 
29
 
28
 import androidx.annotation.NonNull;
30
 import androidx.annotation.NonNull;
31
+import androidx.annotation.Nullable;
29
 import androidx.annotation.RestrictTo;
32
 import androidx.annotation.RestrictTo;
30
 import androidx.appcompat.widget.ActionMenuView;
33
 import androidx.appcompat.widget.ActionMenuView;
31
 import androidx.appcompat.widget.Toolbar;
34
 import androidx.appcompat.widget.Toolbar;
32
 import androidx.core.view.MenuItemCompat;
35
 import androidx.core.view.MenuItemCompat;
33
 
36
 
34
 public class TitleBarButtonController extends ViewController<TitleBarReactButtonView> implements MenuItem.OnMenuItemClickListener {
37
 public class TitleBarButtonController extends ViewController<TitleBarReactButtonView> implements MenuItem.OnMenuItemClickListener {
38
+    @Nullable private MenuItem menuItem;
39
+
35
     public interface OnClickListener {
40
     public interface OnClickListener {
36
         void onPress(String buttonId);
41
         void onPress(String buttonId);
37
     }
42
     }
38
 
43
 
39
     private final IconResolver navigationIconResolver;
44
     private final IconResolver navigationIconResolver;
40
-    private ButtonPresenter optionsPresenter;
45
+    private ButtonPresenter presenter;
41
     private final Button button;
46
     private final Button button;
42
-    private final ReactViewCreator viewCreator;
47
+    private final TitleBarButtonCreator viewCreator;
43
     private TitleBarButtonController.OnClickListener onPressListener;
48
     private TitleBarButtonController.OnClickListener onPressListener;
44
     private Drawable icon;
49
     private Drawable icon;
45
 
50
 
52
         return button.instanceId;
57
         return button.instanceId;
53
     }
58
     }
54
 
59
 
60
+    public int getButtonIntId() {
61
+        return button.getIntId();
62
+    }
63
+
55
     public TitleBarButtonController(Activity activity,
64
     public TitleBarButtonController(Activity activity,
56
                                     IconResolver navigationIconResolver,
65
                                     IconResolver navigationIconResolver,
57
-                                    ButtonPresenter optionsPresenter,
66
+                                    ButtonPresenter presenter,
58
                                     Button button,
67
                                     Button button,
59
-                                    ReactViewCreator viewCreator,
68
+                                    TitleBarButtonCreator viewCreator,
60
                                     OnClickListener onClickListener) {
69
                                     OnClickListener onClickListener) {
61
         super(activity, button.id, new YellowBoxDelegate(), new Options(), new ViewControllerOverlay(activity));
70
         super(activity, button.id, new YellowBoxDelegate(), new Options(), new ViewControllerOverlay(activity));
62
         this.navigationIconResolver = navigationIconResolver;
71
         this.navigationIconResolver = navigationIconResolver;
63
-        this.optionsPresenter = optionsPresenter;
72
+        this.presenter = presenter;
64
         this.button = button;
73
         this.button = button;
65
         this.viewCreator = viewCreator;
74
         this.viewCreator = viewCreator;
66
         this.onPressListener = onClickListener;
75
         this.onPressListener = onClickListener;
96
     @NonNull
105
     @NonNull
97
     @Override
106
     @Override
98
     protected TitleBarReactButtonView createView() {
107
     protected TitleBarReactButtonView createView() {
99
-        view = (TitleBarReactButtonView) viewCreator.create(getActivity(), button.component.componentId.get(), button.component.name.get());
108
+        view = viewCreator.create(getActivity(), button.component);
100
         return (TitleBarReactButtonView) view.asView();
109
         return (TitleBarReactButtonView) view.asView();
101
     }
110
     }
102
 
111
 
106
         return true;
115
         return true;
107
     }
116
     }
108
 
117
 
118
+    public boolean equals(TitleBarButtonController other) {
119
+        if (other == this) return true;
120
+        if (!other.getId().equals(getId())) return false;
121
+        return button.equals(other.button);
122
+    }
123
+
109
     public void applyNavigationIcon(Toolbar toolbar) {
124
     public void applyNavigationIcon(Toolbar toolbar) {
110
         navigationIconResolver.resolve(button, icon -> {
125
         navigationIconResolver.resolve(button, icon -> {
111
             setIconColor(icon);
126
             setIconColor(icon);
126
         });
141
         });
127
     }
142
     }
128
 
143
 
129
-    public void addToMenu(Toolbar toolbar, int position) {
130
-        MenuItem menuItem = toolbar.getMenu().add(Menu.NONE, position, position, button.text.get(""));
144
+    public void addToMenu(TitleBar titleBar, int order) {
145
+        if (button.component.hasValue() && titleBar.containsRightButton(menuItem, order)) return;
146
+        titleBar.getMenu().removeItem(button.getIntId());
147
+        menuItem = titleBar.getMenu().add(Menu.NONE, button.getIntId(), order, presenter.getStyledText());
148
+        applyButtonOptions(titleBar, menuItem);
149
+    }
150
+
151
+    private void applyButtonOptions(TitleBar titleBar, MenuItem menuItem) {
131
         if (button.showAsAction.hasValue()) menuItem.setShowAsAction(button.showAsAction.get());
152
         if (button.showAsAction.hasValue()) menuItem.setShowAsAction(button.showAsAction.get());
132
         menuItem.setEnabled(button.enabled.isTrueOrUndefined());
153
         menuItem.setEnabled(button.enabled.isTrueOrUndefined());
133
         menuItem.setOnMenuItemClickListener(this);
154
         menuItem.setOnMenuItemClickListener(this);
145
                         menuItem.setIcon(icon);
166
                         menuItem.setIcon(icon);
146
                     }
167
                     }
147
                 });
168
                 });
148
-            } else {
149
-                optionsPresenter.setTextColor();
150
-                if (button.fontSize.hasValue()) optionsPresenter.setFontSize(menuItem);
151
-                optionsPresenter.setTypeFace(button.fontFamily);
152
             }
169
             }
153
         }
170
         }
154
-        setTestId(toolbar, button.testId);
171
+        setTestId(titleBar, button.testId);
155
     }
172
     }
156
 
173
 
157
     private void loadIcon(ImageLoader.ImagesLoadingListener callback) {
174
     private void loadIcon(ImageLoader.ImagesLoadingListener callback) {
161
     private void setIconColor(Drawable icon) {
178
     private void setIconColor(Drawable icon) {
162
         if (button.disableIconTint.isTrue()) return;
179
         if (button.disableIconTint.isTrue()) return;
163
         if (button.enabled.isTrueOrUndefined() && button.color.hasValue()) {
180
         if (button.enabled.isTrueOrUndefined() && button.color.hasValue()) {
164
-            optionsPresenter.tint(icon, button.color.get());
181
+            presenter.tint(icon, button.color.get());
165
         } else if (button.enabled.isFalse()) {
182
         } else if (button.enabled.isFalse()) {
166
-            optionsPresenter.tint(icon, button.disabledColor.get(Color.LTGRAY));
183
+            presenter.tint(icon, button.disabledColor.get(Color.LTGRAY));
167
         }
184
         }
168
     }
185
     }
169
 
186
 

+ 37
- 4
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarController.java View File

1
 package com.reactnativenavigation.viewcontrollers.topbar;
1
 package com.reactnativenavigation.viewcontrollers.topbar;
2
 
2
 
3
 import android.content.Context;
3
 import android.content.Context;
4
+import android.graphics.drawable.Drawable;
5
+import android.view.MenuItem;
4
 import android.view.View;
6
 import android.view.View;
5
 
7
 
6
 import com.reactnativenavigation.anim.TopBarAnimator;
8
 import com.reactnativenavigation.anim.TopBarAnimator;
7
 import com.reactnativenavigation.parse.AnimationOptions;
9
 import com.reactnativenavigation.parse.AnimationOptions;
8
-import com.reactnativenavigation.utils.CompatUtils;
10
+import com.reactnativenavigation.viewcontrollers.TitleBarButtonController;
9
 import com.reactnativenavigation.viewcontrollers.TitleBarReactViewController;
11
 import com.reactnativenavigation.viewcontrollers.TitleBarReactViewController;
10
 import com.reactnativenavigation.views.StackLayout;
12
 import com.reactnativenavigation.views.StackLayout;
13
+import com.reactnativenavigation.views.titlebar.TitleBar;
11
 import com.reactnativenavigation.views.topbar.TopBar;
14
 import com.reactnativenavigation.views.topbar.TopBar;
12
 
15
 
16
+import java.util.List;
17
+
13
 import androidx.annotation.VisibleForTesting;
18
 import androidx.annotation.VisibleForTesting;
14
 import androidx.viewpager.widget.ViewPager;
19
 import androidx.viewpager.widget.ViewPager;
15
 
20
 
21
+import static com.reactnativenavigation.utils.CollectionUtils.*;
16
 import static com.reactnativenavigation.utils.ObjectUtils.perform;
22
 import static com.reactnativenavigation.utils.ObjectUtils.perform;
17
 import static com.reactnativenavigation.utils.ViewUtils.isVisible;
23
 import static com.reactnativenavigation.utils.ViewUtils.isVisible;
18
 
24
 
19
 
25
 
20
 public class TopBarController {
26
 public class TopBarController {
21
     private TopBar topBar;
27
     private TopBar topBar;
28
+    private TitleBar titleBar;
22
     private TopBarAnimator animator;
29
     private TopBarAnimator animator;
23
 
30
 
24
-    public TopBarController() {
25
-        animator = new TopBarAnimator();
31
+    public MenuItem getRightButton(int index) {
32
+        return titleBar.getRightButton(index);
26
     }
33
     }
27
 
34
 
28
     public TopBar getView() {
35
     public TopBar getView() {
33
         return perform(topBar, 0, View::getHeight);
40
         return perform(topBar, 0, View::getHeight);
34
     }
41
     }
35
 
42
 
43
+    public int getRightButtonsCount() {
44
+        return topBar.getRightButtonsCount();
45
+    }
46
+
47
+    public Drawable getLeftButton() {
48
+        return titleBar.getNavigationIcon();
49
+    }
50
+
36
     @VisibleForTesting
51
     @VisibleForTesting
37
     public void setAnimator(TopBarAnimator animator) {
52
     public void setAnimator(TopBarAnimator animator) {
38
         this.animator = animator;
53
         this.animator = animator;
39
     }
54
     }
40
 
55
 
56
+    public TopBarController() {
57
+        animator = new TopBarAnimator();
58
+    }
59
+
41
     public TopBar createView(Context context, StackLayout parent) {
60
     public TopBar createView(Context context, StackLayout parent) {
42
         if (topBar == null) {
61
         if (topBar == null) {
43
             topBar = createTopBar(context, parent);
62
             topBar = createTopBar(context, parent);
44
-            topBar.setId(CompatUtils.generateViewId());
63
+            titleBar = topBar.getTitleBar();
45
             animator.bindView(topBar, parent);
64
             animator.bindView(topBar, parent);
46
         }
65
         }
47
         return topBar;
66
         return topBar;
98
     public void setTitleComponent(TitleBarReactViewController component) {
117
     public void setTitleComponent(TitleBarReactViewController component) {
99
         topBar.setTitleComponent(component.getView());
118
         topBar.setTitleComponent(component.getView());
100
     }
119
     }
120
+
121
+    public void applyRightButtons(List<TitleBarButtonController> toAdd) {
122
+        topBar.clearRightButtons();
123
+        forEachIndexed(toAdd, (b, i) -> b.addToMenu(titleBar, (toAdd.size() - i) * 10));
124
+    }
125
+
126
+    public void mergeRightButtons(List<TitleBarButtonController> toAdd, List<TitleBarButtonController> toRemove) {
127
+        forEach(toRemove, btn -> topBar.removeRightButton(btn));
128
+        forEachIndexed(toAdd, (b, i) -> b.addToMenu(titleBar, (toAdd.size() - i) * 10));
129
+    }
130
+
131
+    public void setLeftButtons(List<TitleBarButtonController> leftButtons) {
132
+        titleBar.setLeftButtons(leftButtons);
133
+    }
101
 }
134
 }

+ 37
- 14
lib/android/app/src/main/java/com/reactnativenavigation/views/titlebar/TitleBar.java View File

7
 import android.graphics.Typeface;
7
 import android.graphics.Typeface;
8
 import android.graphics.drawable.Drawable;
8
 import android.graphics.drawable.Drawable;
9
 import android.util.Log;
9
 import android.util.Log;
10
+import android.view.MenuItem;
10
 import android.view.View;
11
 import android.view.View;
11
 import android.view.ViewGroup;
12
 import android.view.ViewGroup;
12
 import android.widget.TextView;
13
 import android.widget.TextView;
18
 import com.reactnativenavigation.utils.ViewUtils;
19
 import com.reactnativenavigation.utils.ViewUtils;
19
 import com.reactnativenavigation.viewcontrollers.TitleBarButtonController;
20
 import com.reactnativenavigation.viewcontrollers.TitleBarButtonController;
20
 
21
 
22
+import java.util.ArrayList;
21
 import java.util.List;
23
 import java.util.List;
22
 
24
 
23
-import javax.annotation.Nullable;
24
-
25
+import androidx.annotation.Nullable;
25
 import androidx.appcompat.widget.ActionMenuView;
26
 import androidx.appcompat.widget.ActionMenuView;
26
 import androidx.appcompat.widget.Toolbar;
27
 import androidx.appcompat.widget.Toolbar;
27
 
28
 
42
     private Boolean isTitleChanged = false;
43
     private Boolean isTitleChanged = false;
43
     private Boolean isSubtitleChanged = false;
44
     private Boolean isSubtitleChanged = false;
44
 
45
 
46
+    public MenuItem getRightButton(int index) {
47
+        return getMenu().getItem(index);
48
+    }
49
+
50
+    public int getRightButtonsCount() {
51
+        return getMenu().size();
52
+    }
53
+
54
+    public List<MenuItem> getRightButtons() {
55
+        List<MenuItem> items = new ArrayList<>();
56
+        for (int i = 0; i < getRightButtonsCount(); i++) {
57
+            items.add(i, getRightButton(i));
58
+        }
59
+        return items;
60
+    }
61
+
45
     public TitleBar(Context context) {
62
     public TitleBar(Context context) {
46
         super(context);
63
         super(context);
47
         getMenu();
64
         getMenu();
110
         subtitleAlignment = alignment;
127
         subtitleAlignment = alignment;
111
     }
128
     }
112
 
129
 
130
+    public boolean containsRightButton(@Nullable MenuItem menuItem, int order) {
131
+        return menuItem != null &&
132
+               getMenu().findItem(menuItem.getItemId()) != null &&
133
+               menuItem.getOrder() == order;
134
+    }
135
+
113
     public void alignTextView(Alignment alignment, TextView view) {
136
     public void alignTextView(Alignment alignment, TextView view) {
114
         if (StringUtils.isEmpty(view.getText())) return;
137
         if (StringUtils.isEmpty(view.getText())) return;
115
         int direction = view.getParent().getLayoutDirection();
138
         int direction = view.getParent().getLayoutDirection();
116
         boolean isRTL = direction == View.LAYOUT_DIRECTION_RTL;
139
         boolean isRTL = direction == View.LAYOUT_DIRECTION_RTL;
117
 
140
 
118
         if (alignment == Alignment.Center) {
141
         if (alignment == Alignment.Center) {
119
-            view.setX((getWidth() - view.getWidth()) / 2);
142
+            view.setX((getWidth() - view.getWidth()) / 2f);
120
         } else if (leftButtonController != null) {
143
         } else if (leftButtonController != null) {
121
             view.setX(isRTL ? (getWidth() - view.getWidth()) - getContentInsetStartWithNavigation() : getContentInsetStartWithNavigation());
144
             view.setX(isRTL ? (getWidth() - view.getWidth()) - getContentInsetStartWithNavigation() : getContentInsetStartWithNavigation());
122
         } else {
145
         } else {
128
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
151
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
129
         super.onLayout(changed, l, t, r, b);
152
         super.onLayout(changed, l, t, r, b);
130
 
153
 
131
-        if(changed || isTitleChanged) {
154
+        if (changed || isTitleChanged) {
132
             TextView title = findTitleTextView();
155
             TextView title = findTitleTextView();
133
             if (title != null) this.alignTextView(titleAlignment, title);
156
             if (title != null) this.alignTextView(titleAlignment, title);
134
             isTitleChanged = false;
157
             isTitleChanged = false;
135
         }
158
         }
136
 
159
 
137
-        if(changed || isSubtitleChanged) {
160
+        if (changed || isSubtitleChanged) {
138
             TextView subtitle = findSubtitleTextView();
161
             TextView subtitle = findSubtitleTextView();
139
             if (subtitle != null) this.alignTextView(subtitleAlignment, subtitle);
162
             if (subtitle != null) this.alignTextView(subtitleAlignment, subtitle);
140
             isSubtitleChanged = false;
163
             isSubtitleChanged = false;
190
         }
213
         }
191
     }
214
     }
192
 
215
 
193
-    private void clearRightButtons() {
216
+    public void clearRightButtons() {
194
         if (getMenu().size() > 0) getMenu().clear();
217
         if (getMenu().size() > 0) getMenu().clear();
195
     }
218
     }
196
 
219
 
216
         button.applyNavigationIcon(this);
239
         button.applyNavigationIcon(this);
217
     }
240
     }
218
 
241
 
219
-    public void setRightButtons(List<TitleBarButtonController> rightButtons) {
220
-        if (rightButtons == null) return;
221
-        clearRightButtons();
222
-        for (int i = 0; i < rightButtons.size(); i++) {
223
-            rightButtons.get(i).addToMenu(this, rightButtons.size() - i - 1);
224
-        }
225
-    }
226
-
227
     public void setHeight(int height) {
242
     public void setHeight(int height) {
228
         int pixelHeight = UiUtils.dpToPx(getContext(), height);
243
         int pixelHeight = UiUtils.dpToPx(getContext(), height);
229
         if (pixelHeight == getLayoutParams().height) return;
244
         if (pixelHeight == getLayoutParams().height) return;
257
             ((ViewGroup) child).setClipChildren(false);
272
             ((ViewGroup) child).setClipChildren(false);
258
         }
273
         }
259
     }
274
     }
275
+
276
+    public void removeRightButton(int buttonId) {
277
+        getMenu().removeItem(buttonId);
278
+    }
279
+
280
+    public boolean containsRightButton(TitleBarButtonController button) {
281
+        return getMenu().findItem(button.getButtonIntId()) != null;
282
+    }
260
 }
283
 }

+ 4
- 5
lib/android/app/src/main/java/com/reactnativenavigation/views/titlebar/TitleBarButtonCreator.java View File

3
 import android.app.Activity;
3
 import android.app.Activity;
4
 
4
 
5
 import com.facebook.react.ReactInstanceManager;
5
 import com.facebook.react.ReactInstanceManager;
6
-import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
6
+import com.reactnativenavigation.parse.Component;
7
 
7
 
8
-public class TitleBarButtonCreator implements ReactViewCreator {
8
+public class TitleBarButtonCreator {
9
 
9
 
10
     private ReactInstanceManager instanceManager;
10
     private ReactInstanceManager instanceManager;
11
 
11
 
13
         this.instanceManager = instanceManager;
13
         this.instanceManager = instanceManager;
14
 	}
14
 	}
15
 
15
 
16
-	@Override
17
-	public TitleBarReactButtonView create(Activity activity, String componentId, String componentName) {
18
-        return new TitleBarReactButtonView(activity, instanceManager, componentId, componentName);
16
+	public TitleBarReactButtonView create(Activity activity, Component component) {
17
+        return new TitleBarReactButtonView(activity, instanceManager, component);
19
     }
18
     }
20
 }
19
 }

+ 20
- 3
lib/android/app/src/main/java/com/reactnativenavigation/views/titlebar/TitleBarReactButtonView.java View File

4
 import android.content.Context;
4
 import android.content.Context;
5
 
5
 
6
 import com.facebook.react.ReactInstanceManager;
6
 import com.facebook.react.ReactInstanceManager;
7
+import com.reactnativenavigation.parse.Component;
8
+import com.reactnativenavigation.parse.params.Number;
7
 import com.reactnativenavigation.react.ReactView;
9
 import com.reactnativenavigation.react.ReactView;
8
 
10
 
11
+import static android.view.View.MeasureSpec.EXACTLY;
12
+import static android.view.View.MeasureSpec.UNSPECIFIED;
13
+import static android.view.View.MeasureSpec.makeMeasureSpec;
14
+import static com.reactnativenavigation.utils.UiUtils.dpToPx;
15
+
9
 @SuppressLint("ViewConstructor")
16
 @SuppressLint("ViewConstructor")
10
 public class TitleBarReactButtonView extends ReactView {
17
 public class TitleBarReactButtonView extends ReactView {
18
+    private final Component component;
11
 
19
 
12
-    public TitleBarReactButtonView(Context context, ReactInstanceManager reactInstanceManager, String componentId, String componentName) {
13
-        super(context, reactInstanceManager, componentId, componentName);
20
+    public TitleBarReactButtonView(Context context, ReactInstanceManager reactInstanceManager, Component component) {
21
+        super(context, reactInstanceManager, component.componentId.get(), component.name.get());
22
+        this.component = component;
14
     }
23
     }
15
 
24
 
16
     @Override
25
     @Override
17
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
26
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
18
-        super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED), heightMeasureSpec);
27
+        super.onMeasure(createSpec(widthMeasureSpec, component.width), createSpec(widthMeasureSpec, component.height));
28
+    }
29
+
30
+    private int createSpec(int measureSpec, Number dimension) {
31
+        if (dimension.hasValue()) {
32
+            return makeMeasureSpec(MeasureSpec.getSize(dpToPx(getContext(), dimension.get())), EXACTLY);
33
+        } else {
34
+            return makeMeasureSpec(MeasureSpec.getSize(measureSpec), UNSPECIFIED);
35
+        }
19
     }
36
     }
20
 }
37
 }

+ 28
- 11
lib/android/app/src/main/java/com/reactnativenavigation/views/topbar/TopBar.java View File

6
 import android.graphics.Typeface;
6
 import android.graphics.Typeface;
7
 import android.os.Build;
7
 import android.os.Build;
8
 import android.view.Gravity;
8
 import android.view.Gravity;
9
+import android.view.MenuItem;
9
 import android.view.View;
10
 import android.view.View;
10
 import android.view.ViewGroup;
11
 import android.view.ViewGroup;
11
 import android.widget.FrameLayout;
12
 import android.widget.FrameLayout;
32
 import androidx.annotation.ColorInt;
33
 import androidx.annotation.ColorInt;
33
 import androidx.annotation.NonNull;
34
 import androidx.annotation.NonNull;
34
 import androidx.annotation.VisibleForTesting;
35
 import androidx.annotation.VisibleForTesting;
35
-import androidx.appcompat.widget.Toolbar;
36
 import androidx.viewpager.widget.ViewPager;
36
 import androidx.viewpager.widget.ViewPager;
37
 
37
 
38
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
38
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
48
     private View component;
48
     private View component;
49
     private float elevation = -1;
49
     private float elevation = -1;
50
 
50
 
51
+    public int getRightButtonsCount() {
52
+        return titleBar.getRightButtonsCount();
53
+    }
54
+
51
     public TopBar(final Context context) {
55
     public TopBar(final Context context) {
52
         super(context);
56
         super(context);
53
         context.setTheme(R.style.TopBar);
57
         context.setTheme(R.style.TopBar);
58
+        setId(CompatUtils.generateViewId());
54
         collapsingBehavior = new TopBarCollapseBehavior(this);
59
         collapsingBehavior = new TopBarCollapseBehavior(this);
55
         topTabs = new TopTabs(getContext());
60
         topTabs = new TopTabs(getContext());
56
         createLayout();
61
         createLayout();
204
         titleBar.setBackButton(backButton);
209
         titleBar.setBackButton(backButton);
205
     }
210
     }
206
 
211
 
207
-    public void setLeftButtons(List<TitleBarButtonController> leftButtons) {
208
-        titleBar.setLeftButtons(leftButtons);
209
-    }
210
-
211
     public void clearLeftButtons() {
212
     public void clearLeftButtons() {
212
         titleBar.setLeftButtons(Collections.emptyList());
213
         titleBar.setLeftButtons(Collections.emptyList());
213
     }
214
     }
214
 
215
 
215
-    public void setRightButtons(List<TitleBarButtonController> rightButtons) {
216
-        titleBar.setRightButtons(rightButtons);
217
-    }
218
-
219
     public void clearRightButtons() {
216
     public void clearRightButtons() {
220
-        titleBar.setRightButtons(Collections.emptyList());
217
+        titleBar.clearRightButtons();
221
     }
218
     }
222
 
219
 
223
     public void setElevation(Double elevation) {
220
     public void setElevation(Double elevation) {
234
         }
231
         }
235
     }
232
     }
236
 
233
 
237
-    public Toolbar getTitleBar() {
234
+    public TitleBar getTitleBar() {
238
         return titleBar;
235
         return titleBar;
239
     }
236
     }
240
 
237
 
238
+    public List<MenuItem> getRightButtons() {
239
+        return titleBar.getRightButtons();
240
+    }
241
+
242
+    public MenuItem getRightButton(int index) {
243
+        return titleBar.getRightButton(getRightButtonsCount() - index - 1);
244
+    }
245
+
241
     public void initTopTabs(ViewPager viewPager) {
246
     public void initTopTabs(ViewPager viewPager) {
242
         topTabs.setVisibility(VISIBLE);
247
         topTabs.setVisibility(VISIBLE);
243
         topTabs.init(viewPager);
248
         topTabs.init(viewPager);
284
     public void setLayoutDirection(LayoutDirection direction) {
289
     public void setLayoutDirection(LayoutDirection direction) {
285
         titleBar.setLayoutDirection(direction.get());
290
         titleBar.setLayoutDirection(direction.get());
286
     }
291
     }
292
+
293
+    public void removeRightButton(TitleBarButtonController button) {
294
+        removeRightButton(button.getButtonIntId());
295
+    }
296
+
297
+    public void removeRightButton(int buttonId) {
298
+        titleBar.removeRightButton(buttonId);
299
+    }
300
+
301
+    public boolean containsRightButton(TitleBarButtonController button) {
302
+        return titleBar.containsRightButton(button);
303
+    }
287
 }
304
 }

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

7
 
7
 
8
 import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
8
 import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
9
 import com.reactnativenavigation.mocks.TopBarBackgroundViewCreatorMock;
9
 import com.reactnativenavigation.mocks.TopBarBackgroundViewCreatorMock;
10
-import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
10
+import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock;
11
 import com.reactnativenavigation.parse.Options;
11
 import com.reactnativenavigation.parse.Options;
12
 import com.reactnativenavigation.parse.params.Bool;
12
 import com.reactnativenavigation.parse.params.Bool;
13
 import com.reactnativenavigation.presentation.RenderChecker;
13
 import com.reactnativenavigation.presentation.RenderChecker;
39
                 .setId("stack")
39
                 .setId("stack")
40
                 .setChildRegistry(new ChildControllersRegistry())
40
                 .setChildRegistry(new ChildControllersRegistry())
41
                 .setTopBarController(topBarController)
41
                 .setTopBarController(topBarController)
42
-                .setStackPresenter(new StackPresenter(activity, new TitleBarReactViewCreatorMock(), new TopBarBackgroundViewCreatorMock(), new TopBarButtonCreatorMock(), new IconResolver(activity, new ImageLoader()), new RenderChecker(), new Options()))
42
+                .setStackPresenter(new StackPresenter(activity, new TitleBarReactViewCreatorMock(), new TopBarBackgroundViewCreatorMock(), new TitleBarButtonCreatorMock(), new IconResolver(activity, new ImageLoader()), new RenderChecker(), new Options()))
43
                 .setInitialOptions(new Options());
43
                 .setInitialOptions(new Options());
44
     }
44
     }
45
 
45
 

lib/android/app/src/test/java/com/reactnativenavigation/mocks/TopBarButtonCreatorMock.java → lib/android/app/src/test/java/com/reactnativenavigation/mocks/TitleBarButtonCreatorMock.java View File

3
 import android.app.Activity;
3
 import android.app.Activity;
4
 
4
 
5
 import com.facebook.react.ReactInstanceManager;
5
 import com.facebook.react.ReactInstanceManager;
6
+import com.reactnativenavigation.parse.Component;
6
 import com.reactnativenavigation.react.events.ComponentType;
7
 import com.reactnativenavigation.react.events.ComponentType;
7
-import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
8
+import com.reactnativenavigation.views.titlebar.TitleBarButtonCreator;
8
 import com.reactnativenavigation.views.titlebar.TitleBarReactButtonView;
9
 import com.reactnativenavigation.views.titlebar.TitleBarReactButtonView;
9
 
10
 
10
 import static org.mockito.Mockito.mock;
11
 import static org.mockito.Mockito.mock;
11
 
12
 
12
-public class TopBarButtonCreatorMock implements ReactViewCreator {
13
+public class TitleBarButtonCreatorMock extends TitleBarButtonCreator {
14
+
15
+    public TitleBarButtonCreatorMock() {
16
+        super(null);
17
+    }
13
 
18
 
14
     @Override
19
     @Override
15
-    public TitleBarReactButtonView create(Activity activity, String componentId, String componentName) {
20
+    public TitleBarReactButtonView create(Activity activity, Component component) {
16
         final ReactInstanceManager reactInstanceManager = mock(ReactInstanceManager.class);
21
         final ReactInstanceManager reactInstanceManager = mock(ReactInstanceManager.class);
17
-        return new TitleBarReactButtonView(activity, reactInstanceManager, componentId, componentName) {
22
+        return new TitleBarReactButtonView(activity, reactInstanceManager, component) {
18
             @Override
23
             @Override
19
             public void sendComponentStart(ComponentType type) {
24
             public void sendComponentStart(ComponentType type) {
20
 
25
 

+ 4
- 12
lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleBarHelper.java View File

3
 import android.app.Activity;
3
 import android.app.Activity;
4
 
4
 
5
 import com.reactnativenavigation.mocks.ImageLoaderMock;
5
 import com.reactnativenavigation.mocks.ImageLoaderMock;
6
-import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
6
+import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock;
7
 import com.reactnativenavigation.parse.Component;
7
 import com.reactnativenavigation.parse.Component;
8
 import com.reactnativenavigation.parse.params.Button;
8
 import com.reactnativenavigation.parse.params.Button;
9
 import com.reactnativenavigation.parse.params.Text;
9
 import com.reactnativenavigation.parse.params.Text;
10
 import com.reactnativenavigation.viewcontrollers.TitleBarButtonController;
10
 import com.reactnativenavigation.viewcontrollers.TitleBarButtonController;
11
 import com.reactnativenavigation.viewcontrollers.button.IconResolver;
11
 import com.reactnativenavigation.viewcontrollers.button.IconResolver;
12
-import com.reactnativenavigation.views.titlebar.TitleBar;
13
-
14
-import androidx.appcompat.view.menu.ActionMenuItemView;
15
-import androidx.appcompat.widget.Toolbar;
16
 
12
 
17
 public class TitleBarHelper {
13
 public class TitleBarHelper {
18
-    public static ActionMenuItemView getRightButton(Toolbar toolbar, int index) {
19
-        return (ActionMenuItemView) ViewUtils.findChildrenByClassRecursive(toolbar, ActionMenuItemView.class).get(toolbar.getMenu().size() - index - 1);
20
-    }
21
-
22
     public static Button textualButton(String text) {
14
     public static Button textualButton(String text) {
23
         Button button = new Button();
15
         Button button = new Button();
24
         button.id = text + CompatUtils.generateViewId();
16
         button.id = text + CompatUtils.generateViewId();
50
     }
42
     }
51
 
43
 
52
 
44
 
53
-    public static TitleBarButtonController createButtonController(Activity activity, TitleBar titleBar, Button button) {
45
+    public static TitleBarButtonController createButtonController(Activity activity, Button button) {
54
         return new TitleBarButtonController(activity,
46
         return new TitleBarButtonController(activity,
55
                 new IconResolver(activity, ImageLoaderMock.mock()),
47
                 new IconResolver(activity, ImageLoaderMock.mock()),
56
-                new ButtonPresenter(titleBar, button),
48
+                new ButtonPresenter(button),
57
                 button,
49
                 button,
58
-                new TopBarButtonCreatorMock(),
50
+                new TitleBarButtonCreatorMock(),
59
                 buttonId -> {}
51
                 buttonId -> {}
60
         );
52
         );
61
     }
53
     }

+ 63
- 37
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackPresenterTest.java View File

14
 import com.reactnativenavigation.mocks.SimpleViewController;
14
 import com.reactnativenavigation.mocks.SimpleViewController;
15
 import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
15
 import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
16
 import com.reactnativenavigation.mocks.TopBarBackgroundViewCreatorMock;
16
 import com.reactnativenavigation.mocks.TopBarBackgroundViewCreatorMock;
17
-import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
17
+import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock;
18
 import com.reactnativenavigation.parse.Alignment;
18
 import com.reactnativenavigation.parse.Alignment;
19
 import com.reactnativenavigation.parse.Component;
19
 import com.reactnativenavigation.parse.Component;
20
 import com.reactnativenavigation.parse.Options;
20
 import com.reactnativenavigation.parse.Options;
57
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
57
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
58
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
58
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
59
 import static com.reactnativenavigation.utils.CollectionUtils.*;
59
 import static com.reactnativenavigation.utils.CollectionUtils.*;
60
+import static java.util.Arrays.asList;
61
+import static java.util.Collections.singletonList;
62
+import static java.util.Objects.requireNonNull;
60
 import static org.assertj.core.api.Java6Assertions.assertThat;
63
 import static org.assertj.core.api.Java6Assertions.assertThat;
61
 import static org.mockito.ArgumentMatchers.any;
64
 import static org.mockito.ArgumentMatchers.any;
62
 import static org.mockito.ArgumentMatchers.anyBoolean;
65
 import static org.mockito.ArgumentMatchers.anyBoolean;
99
             }
102
             }
100
         };
103
         };
101
         renderChecker = spy(new RenderChecker());
104
         renderChecker = spy(new RenderChecker());
102
-        IconResolver iconResolver = new IconResolver(activity, ImageLoaderMock.mock());
103
-        uut = spy(new StackPresenter(activity, titleViewCreator, new TopBarBackgroundViewCreatorMock(), new TopBarButtonCreatorMock(), iconResolver, renderChecker, new Options()));
105
+        uut = spy(new StackPresenter(activity, titleViewCreator, new TopBarBackgroundViewCreatorMock(), new TitleBarButtonCreatorMock(), new IconResolver(activity, ImageLoaderMock.mock()), renderChecker, new Options()));
104
         createTopBarController();
106
         createTopBarController();
105
 
107
 
106
         parent = TestUtils.newStackController(activity)
108
         parent = TestUtils.newStackController(activity)
119
         Options o1 = new Options();
121
         Options o1 = new Options();
120
         o1.topBar.title.component = component(Alignment.Default);
122
         o1.topBar.title.component = component(Alignment.Default);
121
         o1.topBar.background.component = component(Alignment.Default);
123
         o1.topBar.background.component = component(Alignment.Default);
122
-        o1.topBar.buttons.right = new ArrayList<>(Collections.singletonList(componentBtn1));
124
+        o1.topBar.buttons.right = new ArrayList<>(singletonList(componentBtn1));
123
         uut.applyChildOptions(o1, parent, child);
125
         uut.applyChildOptions(o1, parent, child);
124
 
126
 
125
         uut.isRendered(child.getView());
127
         uut.isRendered(child.getView());
192
     @Test
194
     @Test
193
     public void mergeButtons() {
195
     public void mergeButtons() {
194
         uut.mergeChildOptions(EMPTY_OPTIONS, EMPTY_OPTIONS, parent, child);
196
         uut.mergeChildOptions(EMPTY_OPTIONS, EMPTY_OPTIONS, parent, child);
195
-        verify(topBar, times(0)).setRightButtons(any());
196
-        verify(topBar, times(0)).setLeftButtons(any());
197
+        verify(topBarController, times(0)).applyRightButtons(any());
198
+        verify(topBarController, times(0)).setLeftButtons(any());
197
 
199
 
198
         Options options = new Options();
200
         Options options = new Options();
199
 
201
 
201
         button.text = new Text("btn");
203
         button.text = new Text("btn");
202
         options.topBar.buttons.right = new ArrayList<>(Collections.singleton(button));
204
         options.topBar.buttons.right = new ArrayList<>(Collections.singleton(button));
203
         uut.mergeChildOptions(options, EMPTY_OPTIONS, parent, child);
205
         uut.mergeChildOptions(options, EMPTY_OPTIONS, parent, child);
204
-        verify(topBar, times(1)).setRightButtons(any());
206
+        verify(topBarController).mergeRightButtons(any(), any());
205
 
207
 
206
         options.topBar.buttons.left = new ArrayList<>();
208
         options.topBar.buttons.left = new ArrayList<>();
207
         uut.mergeChildOptions(options, EMPTY_OPTIONS, parent, child);
209
         uut.mergeChildOptions(options, EMPTY_OPTIONS, parent, child);
208
-        verify(topBar, times(1)).setLeftButtons(any());
210
+        verify(topBarController).setLeftButtons(any());
209
     }
211
     }
210
 
212
 
211
     @Test
213
     @Test
212
     public void mergeButtons_previousRightButtonsAreDestroyed() {
214
     public void mergeButtons_previousRightButtonsAreDestroyed() {
213
         Options options = new Options();
215
         Options options = new Options();
214
-        options.topBar.buttons.right = new ArrayList<>(Collections.singletonList(componentBtn1));
216
+        options.topBar.buttons.right = new ArrayList<>(singletonList(componentBtn1));
215
         uut.applyChildOptions(options, parent, child);
217
         uut.applyChildOptions(options, parent, child);
216
         List<TitleBarButtonController> initialButtons = uut.getComponentButtons(child.getView());
218
         List<TitleBarButtonController> initialButtons = uut.getComponentButtons(child.getView());
217
         forEach(initialButtons, ViewController::ensureViewIsCreated);
219
         forEach(initialButtons, ViewController::ensureViewIsCreated);
218
 
220
 
219
-        options.topBar.buttons.right = new ArrayList<>(Collections.singletonList(componentBtn2));
221
+        options.topBar.buttons.right = new ArrayList<>(singletonList(componentBtn2));
220
         uut.mergeChildOptions(options, Options.EMPTY, parent, child);
222
         uut.mergeChildOptions(options, Options.EMPTY, parent, child);
221
         for (TitleBarButtonController button : initialButtons) {
223
         for (TitleBarButtonController button : initialButtons) {
222
             assertThat(button.isDestroyed()).isTrue();
224
             assertThat(button.isDestroyed()).isTrue();
224
     }
226
     }
225
 
227
 
226
     @Test
228
     @Test
227
-    public void mergeButtons_mergingRightButtonsOnlyDestroysRightButtons() {
229
+    public void mergeRightButtons_mergingButtonsOnlyDestroysRightButtons() {
228
         Options a = new Options();
230
         Options a = new Options();
229
-        a.topBar.buttons.right = new ArrayList<>(Collections.singletonList(componentBtn1));
230
-        a.topBar.buttons.left = new ArrayList<>(Collections.singletonList(componentBtn2));
231
+        a.topBar.buttons.right = new ArrayList<>(singletonList(componentBtn1));
232
+        a.topBar.buttons.left = new ArrayList<>(singletonList(componentBtn2));
231
         uut.applyChildOptions(a, parent, child);
233
         uut.applyChildOptions(a, parent, child);
232
         List<TitleBarButtonController> initialButtons = uut.getComponentButtons(child.getView());
234
         List<TitleBarButtonController> initialButtons = uut.getComponentButtons(child.getView());
233
         forEach(initialButtons, ViewController::ensureViewIsCreated);
235
         forEach(initialButtons, ViewController::ensureViewIsCreated);
234
 
236
 
235
         Options b = new Options();
237
         Options b = new Options();
236
-        b.topBar.buttons.right = new ArrayList<>(Collections.singletonList(componentBtn2));
238
+        b.topBar.buttons.right = new ArrayList<>(singletonList(componentBtn2));
237
         uut.mergeChildOptions(b, Options.EMPTY, parent, child);
239
         uut.mergeChildOptions(b, Options.EMPTY, parent, child);
238
         assertThat(initialButtons.get(0).isDestroyed()).isTrue();
240
         assertThat(initialButtons.get(0).isDestroyed()).isTrue();
239
         assertThat(initialButtons.get(1).isDestroyed()).isFalse();
241
         assertThat(initialButtons.get(1).isDestroyed()).isFalse();
240
     }
242
     }
241
 
243
 
242
     @Test
244
     @Test
243
-    public void mergeButtons_mergingLeftButtonsOnlyDestroysLeftButtons() {
245
+    public void mergeRightButtons_buttonsAreCreatedOnlyIfNeeded() {
246
+        Options toApply = new Options();
247
+        toApply.topBar.buttons.right = new ArrayList<>(asList(textBtn1, componentBtn1));
248
+        uut.applyChildOptions(toApply, parent, child);
249
+
250
+        ArgumentCaptor<List<TitleBarButtonController>> captor1 = ArgumentCaptor.forClass(List.class);
251
+        verify(topBarController).applyRightButtons(captor1.capture());
252
+        assertThat(topBar.getTitleBar().getMenu().size()).isEqualTo(2);
253
+        List<TitleBarButtonController> appliedButtons = captor1.getValue();
254
+
255
+        Options toMerge = new Options();
256
+        toMerge.topBar.buttons.right = new ArrayList(requireNonNull(map(toApply.topBar.buttons.right, Button::copy)));
257
+        toMerge.topBar.buttons.right.add(1, componentBtn2);
258
+        uut.mergeChildOptions(toMerge, Options.EMPTY, parent, child);
259
+
260
+        assertThat(topBar.getTitleBar().getMenu().size()).isEqualTo(3);
261
+        ArgumentCaptor<List<TitleBarButtonController>> captor2 = ArgumentCaptor.forClass(List.class);
262
+        verify(topBarController).mergeRightButtons(captor2.capture(), any());
263
+        List<TitleBarButtonController> mergedButtons = captor2.getValue();
264
+        assertThat(mergedButtons).hasSize(3);
265
+        assertThat(appliedButtons.get(0)).isEqualTo(mergedButtons.get(0));
266
+        assertThat(appliedButtons.get(1)).isEqualTo(mergedButtons.get(2));
267
+    }
268
+
269
+    @Test
270
+    public void mergeButtons_mergingLeftButtonsDoesNotDestroyRightButtons() {
244
         Options a = new Options();
271
         Options a = new Options();
245
-        a.topBar.buttons.right = new ArrayList<>(Collections.singletonList(componentBtn1));
246
-        a.topBar.buttons.left = new ArrayList<>(Collections.singletonList(componentBtn2));
272
+        a.topBar.buttons.right = new ArrayList<>(singletonList(componentBtn1));
273
+        a.topBar.buttons.left = new ArrayList<>(singletonList(componentBtn2));
247
         uut.applyChildOptions(a, parent, child);
274
         uut.applyChildOptions(a, parent, child);
248
         List<TitleBarButtonController> initialButtons = uut.getComponentButtons(child.getView());
275
         List<TitleBarButtonController> initialButtons = uut.getComponentButtons(child.getView());
249
         forEach(initialButtons, ViewController::ensureViewIsCreated);
276
         forEach(initialButtons, ViewController::ensureViewIsCreated);
250
 
277
 
251
         Options b = new Options();
278
         Options b = new Options();
252
-        b.topBar.buttons.left = new ArrayList<>(Collections.singletonList(componentBtn2));
279
+        b.topBar.buttons.left = new ArrayList<>(singletonList(componentBtn2));
253
         uut.mergeChildOptions(b, Options.EMPTY, parent, child);
280
         uut.mergeChildOptions(b, Options.EMPTY, parent, child);
254
         assertThat(initialButtons.get(0).isDestroyed()).isFalse();
281
         assertThat(initialButtons.get(0).isDestroyed()).isFalse();
255
-        assertThat(initialButtons.get(1).isDestroyed()).isTrue();
256
     }
282
     }
257
 
283
 
258
     @Test
284
     @Test
423
 
449
 
424
         uut.applyChildOptions(options, parent, child);
450
         uut.applyChildOptions(options, parent, child);
425
         ArgumentCaptor<List<TitleBarButtonController>> rightCaptor = ArgumentCaptor.forClass(List.class);
451
         ArgumentCaptor<List<TitleBarButtonController>> rightCaptor = ArgumentCaptor.forClass(List.class);
426
-        verify(topBar).setRightButtons(rightCaptor.capture());
452
+        verify(topBarController).applyRightButtons(rightCaptor.capture());
427
         assertThat(rightCaptor.getValue().get(0).getButton().color.get()).isEqualTo(options.topBar.rightButtonColor.get());
453
         assertThat(rightCaptor.getValue().get(0).getButton().color.get()).isEqualTo(options.topBar.rightButtonColor.get());
428
         assertThat(rightCaptor.getValue().get(1).getButton().color.get()).isEqualTo(options.topBar.rightButtonColor.get());
454
         assertThat(rightCaptor.getValue().get(1).getButton().color.get()).isEqualTo(options.topBar.rightButtonColor.get());
429
         assertThat(rightCaptor.getValue().get(0)).isNotEqualTo(rightButton1);
455
         assertThat(rightCaptor.getValue().get(0)).isNotEqualTo(rightButton1);
430
         assertThat(rightCaptor.getValue().get(1)).isNotEqualTo(rightButton2);
456
         assertThat(rightCaptor.getValue().get(1)).isNotEqualTo(rightButton2);
431
 
457
 
432
         ArgumentCaptor<List<TitleBarButtonController>> leftCaptor = ArgumentCaptor.forClass(List.class);
458
         ArgumentCaptor<List<TitleBarButtonController>> leftCaptor = ArgumentCaptor.forClass(List.class);
433
-        verify(topBar).setLeftButtons(leftCaptor.capture());
459
+        verify(topBarController).setLeftButtons(leftCaptor.capture());
434
         assertThat(leftCaptor.getValue().get(0).getButton().color).isEqualTo(options.topBar.leftButtonColor);
460
         assertThat(leftCaptor.getValue().get(0).getButton().color).isEqualTo(options.topBar.leftButtonColor);
435
         assertThat(leftCaptor.getValue().get(0)).isNotEqualTo(leftButton);
461
         assertThat(leftCaptor.getValue().get(0)).isNotEqualTo(leftButton);
436
     }
462
     }
468
 
494
 
469
         uut.mergeChildOptions(options2, appliedOptions, parent, child);
495
         uut.mergeChildOptions(options2, appliedOptions, parent, child);
470
         ArgumentCaptor<List<TitleBarButtonController>> rightCaptor = ArgumentCaptor.forClass(List.class);
496
         ArgumentCaptor<List<TitleBarButtonController>> rightCaptor = ArgumentCaptor.forClass(List.class);
471
-        verify(topBar, times(1)).setRightButtons(rightCaptor.capture());
497
+        verify(topBarController).mergeRightButtons(rightCaptor.capture(), any());
472
         assertThat(rightCaptor.getValue().get(0).getButton().color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get());
498
         assertThat(rightCaptor.getValue().get(0).getButton().color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get());
473
         assertThat(rightCaptor.getValue().get(1).getButton().color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get());
499
         assertThat(rightCaptor.getValue().get(1).getButton().color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get());
474
         assertThat(rightCaptor.getValue().get(0)).isNotEqualTo(rightButton1);
500
         assertThat(rightCaptor.getValue().get(0)).isNotEqualTo(rightButton1);
475
         assertThat(rightCaptor.getValue().get(1)).isNotEqualTo(rightButton2);
501
         assertThat(rightCaptor.getValue().get(1)).isNotEqualTo(rightButton2);
476
 
502
 
477
         ArgumentCaptor<List<TitleBarButtonController>> leftCaptor = ArgumentCaptor.forClass(List.class);
503
         ArgumentCaptor<List<TitleBarButtonController>> leftCaptor = ArgumentCaptor.forClass(List.class);
478
-        verify(topBar, times(1)).setLeftButtons(leftCaptor.capture());
504
+        verify(topBarController).setLeftButtons(leftCaptor.capture());
479
         assertThat(leftCaptor.getValue().get(0).getButton().color.get()).isEqualTo(appliedOptions.topBar.leftButtonColor.get());
505
         assertThat(leftCaptor.getValue().get(0).getButton().color.get()).isEqualTo(appliedOptions.topBar.leftButtonColor.get());
480
         assertThat(leftCaptor.getValue().get(0)).isNotEqualTo(leftButton);
506
         assertThat(leftCaptor.getValue().get(0)).isNotEqualTo(leftButton);
481
     }
507
     }
500
 
526
 
501
         uut.mergeChildOptions(options2, resolvedOptions, parent, child);
527
         uut.mergeChildOptions(options2, resolvedOptions, parent, child);
502
         ArgumentCaptor<List<TitleBarButtonController>> rightCaptor = ArgumentCaptor.forClass(List.class);
528
         ArgumentCaptor<List<TitleBarButtonController>> rightCaptor = ArgumentCaptor.forClass(List.class);
503
-        verify(topBar).setRightButtons(rightCaptor.capture());
529
+        verify(topBarController).mergeRightButtons(rightCaptor.capture(), any());
504
         assertThat(rightCaptor.getValue().get(0).getButton().color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get());
530
         assertThat(rightCaptor.getValue().get(0).getButton().color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get());
505
         assertThat(rightCaptor.getValue().get(1).getButton().color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get());
531
         assertThat(rightCaptor.getValue().get(1).getButton().color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get());
506
         assertThat(rightCaptor.getValue().get(0)).isNotEqualTo(rightButton1);
532
         assertThat(rightCaptor.getValue().get(0)).isNotEqualTo(rightButton1);
507
         assertThat(rightCaptor.getValue().get(1)).isNotEqualTo(rightButton2);
533
         assertThat(rightCaptor.getValue().get(1)).isNotEqualTo(rightButton2);
508
 
534
 
509
         ArgumentCaptor<List<TitleBarButtonController>> leftCaptor = ArgumentCaptor.forClass(List.class);
535
         ArgumentCaptor<List<TitleBarButtonController>> leftCaptor = ArgumentCaptor.forClass(List.class);
510
-        verify(topBar).setLeftButtons(leftCaptor.capture());
536
+        verify(topBarController).setLeftButtons(leftCaptor.capture());
511
         assertThat(leftCaptor.getValue().get(0).getButton().color.get()).isEqualTo(resolvedOptions.topBar.leftButtonColor.get());
537
         assertThat(leftCaptor.getValue().get(0).getButton().color.get()).isEqualTo(resolvedOptions.topBar.leftButtonColor.get());
512
         assertThat(leftCaptor.getValue().get(0)).isNotEqualTo(leftButton);
538
         assertThat(leftCaptor.getValue().get(0)).isNotEqualTo(leftButton);
513
     }
539
     }
515
     @Test
541
     @Test
516
     public void getButtonControllers_buttonControllersArePassedToTopBar() {
542
     public void getButtonControllers_buttonControllersArePassedToTopBar() {
517
         Options options = new Options();
543
         Options options = new Options();
518
-        options.topBar.buttons.right = new ArrayList<>(Collections.singletonList(textBtn1));
519
-        options.topBar.buttons.left = new ArrayList<>(Collections.singletonList(textBtn1));
544
+        options.topBar.buttons.right = new ArrayList<>(singletonList(textBtn1));
545
+        options.topBar.buttons.left = new ArrayList<>(singletonList(textBtn1));
520
         uut.applyChildOptions(options, parent, child);
546
         uut.applyChildOptions(options, parent, child);
521
 
547
 
522
         ArgumentCaptor<List<TitleBarButtonController>> rightCaptor = ArgumentCaptor.forClass(List.class);
548
         ArgumentCaptor<List<TitleBarButtonController>> rightCaptor = ArgumentCaptor.forClass(List.class);
523
         ArgumentCaptor<List<TitleBarButtonController>> leftCaptor = ArgumentCaptor.forClass(List.class);
549
         ArgumentCaptor<List<TitleBarButtonController>> leftCaptor = ArgumentCaptor.forClass(List.class);
524
-        verify(topBar).setRightButtons(rightCaptor.capture());
525
-        verify(topBar).setLeftButtons(leftCaptor.capture());
550
+        verify(topBarController).applyRightButtons(rightCaptor.capture());
551
+        verify(topBarController).setLeftButtons(leftCaptor.capture());
526
 
552
 
527
         assertThat(rightCaptor.getValue().size()).isOne();
553
         assertThat(rightCaptor.getValue().size()).isOne();
528
         assertThat(leftCaptor.getValue().size()).isOne();
554
         assertThat(leftCaptor.getValue().size()).isOne();
531
     @Test
557
     @Test
532
     public void getButtonControllers_storesButtonsByComponent() {
558
     public void getButtonControllers_storesButtonsByComponent() {
533
         Options options = new Options();
559
         Options options = new Options();
534
-        options.topBar.buttons.right = new ArrayList<>(Collections.singletonList(textBtn1));
535
-        options.topBar.buttons.left = new ArrayList<>(Collections.singletonList(textBtn2));
560
+        options.topBar.buttons.right = new ArrayList<>(singletonList(textBtn1));
561
+        options.topBar.buttons.left = new ArrayList<>(singletonList(textBtn2));
536
         uut.applyChildOptions(options, parent, child);
562
         uut.applyChildOptions(options, parent, child);
537
 
563
 
538
         List<TitleBarButtonController> componentButtons = uut.getComponentButtons(child.getView());
564
         List<TitleBarButtonController> componentButtons = uut.getComponentButtons(child.getView());
544
     @Test
570
     @Test
545
     public void getButtonControllers_createdOnce() {
571
     public void getButtonControllers_createdOnce() {
546
         Options options = new Options();
572
         Options options = new Options();
547
-        options.topBar.buttons.right = new ArrayList<>(Collections.singletonList(textBtn1));
548
-        options.topBar.buttons.left = new ArrayList<>(Collections.singletonList(textBtn2));
573
+        options.topBar.buttons.right = new ArrayList<>(singletonList(textBtn1));
574
+        options.topBar.buttons.left = new ArrayList<>(singletonList(textBtn2));
549
 
575
 
550
         uut.applyChildOptions(options, parent, child);
576
         uut.applyChildOptions(options, parent, child);
551
         List<TitleBarButtonController> buttons1 = uut.getComponentButtons(child.getView());
577
         List<TitleBarButtonController> buttons1 = uut.getComponentButtons(child.getView());
560
     @Test
586
     @Test
561
     public void applyButtons_doesNotDestroyOtherComponentButtons() {
587
     public void applyButtons_doesNotDestroyOtherComponentButtons() {
562
         Options options = new Options();
588
         Options options = new Options();
563
-        options.topBar.buttons.right = new ArrayList<>(Collections.singletonList(componentBtn1));
564
-        options.topBar.buttons.left = new ArrayList<>(Collections.singletonList(componentBtn2));
589
+        options.topBar.buttons.right = new ArrayList<>(singletonList(componentBtn1));
590
+        options.topBar.buttons.left = new ArrayList<>(singletonList(componentBtn2));
565
         uut.applyChildOptions(options, parent, child);
591
         uut.applyChildOptions(options, parent, child);
566
         List<TitleBarButtonController> buttons = uut.getComponentButtons(child.getView());
592
         List<TitleBarButtonController> buttons = uut.getComponentButtons(child.getView());
567
         forEach(buttons, ViewController::ensureViewIsCreated);
593
         forEach(buttons, ViewController::ensureViewIsCreated);
575
     @Test
601
     @Test
576
     public void onChildDestroyed_destroyedButtons() {
602
     public void onChildDestroyed_destroyedButtons() {
577
         Options options = new Options();
603
         Options options = new Options();
578
-        options.topBar.buttons.right = new ArrayList<>(Collections.singletonList(componentBtn1));
579
-        options.topBar.buttons.left = new ArrayList<>(Collections.singletonList(componentBtn2));
604
+        options.topBar.buttons.right = new ArrayList<>(singletonList(componentBtn1));
605
+        options.topBar.buttons.left = new ArrayList<>(singletonList(componentBtn2));
580
         uut.applyChildOptions(options, parent, child);
606
         uut.applyChildOptions(options, parent, child);
581
         List<TitleBarButtonController> buttons = uut.getComponentButtons(child.getView());
607
         List<TitleBarButtonController> buttons = uut.getComponentButtons(child.getView());
582
         forEach(buttons, ViewController::ensureViewIsCreated);
608
         forEach(buttons, ViewController::ensureViewIsCreated);

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

1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.view.MenuItem;
5
+
6
+import com.reactnativenavigation.BaseTest;
7
+import com.reactnativenavigation.mocks.ImageLoaderMock;
8
+import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock;
9
+import com.reactnativenavigation.parse.params.Button;
10
+import com.reactnativenavigation.parse.params.Text;
11
+import com.reactnativenavigation.utils.ButtonPresenter;
12
+import com.reactnativenavigation.viewcontrollers.button.IconResolver;
13
+import com.reactnativenavigation.views.titlebar.TitleBar;
14
+
15
+import org.junit.Test;
16
+import org.mockito.Mockito;
17
+
18
+import static org.assertj.core.api.Java6Assertions.assertThat;
19
+
20
+public class TitleBarButtonControllerTest extends BaseTest {
21
+    private TitleBarButtonController uut;
22
+    private TitleBar titleBar;
23
+
24
+    @Override
25
+    public void beforeEach() {
26
+        Activity activity = newActivity();
27
+        titleBar = new TitleBar(activity);
28
+
29
+        Button button = createComponentButton();
30
+        uut = new TitleBarButtonController(
31
+                activity,
32
+                new IconResolver(activity, ImageLoaderMock.mock()),
33
+                new ButtonPresenter(button),
34
+                button,
35
+                new TitleBarButtonCreatorMock(),
36
+                Mockito.mock(TitleBarButtonController.OnClickListener.class)
37
+        );
38
+    }
39
+
40
+    @Test
41
+    public void addToMenu_componentButtonIsNotRecreatedIfAlreadyAddedWithSameOrder() {
42
+        uut.addToMenu(titleBar, 0);
43
+        MenuItem first = titleBar.getRightButton(0);
44
+
45
+        uut.addToMenu(titleBar, 0);
46
+        MenuItem second = titleBar.getRightButton(0);
47
+        assertThat(first).isEqualTo(second);
48
+
49
+        uut.addToMenu(titleBar, 1);
50
+        MenuItem third = titleBar.getRightButton(0);
51
+        assertThat(third).isNotEqualTo(second);
52
+    }
53
+
54
+    private Button createComponentButton() {
55
+        Button componentButton = new Button();
56
+        componentButton.id = "customBtn";
57
+        componentButton.component.name = new Text("com.rnn.customBtn");
58
+        componentButton.component.componentId = new Text("component4");
59
+        return componentButton;
60
+    }
61
+}

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

6
 
6
 
7
 import com.reactnativenavigation.BaseTest;
7
 import com.reactnativenavigation.BaseTest;
8
 import com.reactnativenavigation.TestUtils;
8
 import com.reactnativenavigation.TestUtils;
9
-import com.reactnativenavigation.parse.params.Button;
10
-import com.reactnativenavigation.parse.params.Text;
11
-import com.reactnativenavigation.react.Constants;
12
-import com.reactnativenavigation.react.ReactView;
13
-import com.reactnativenavigation.utils.CollectionUtils;
14
 import com.reactnativenavigation.views.titlebar.TitleBar;
9
 import com.reactnativenavigation.views.titlebar.TitleBar;
15
 
10
 
16
 import org.junit.Test;
11
 import org.junit.Test;
17
 import org.mockito.Mockito;
12
 import org.mockito.Mockito;
18
 
13
 
19
-import java.util.ArrayList;
20
-import java.util.Arrays;
21
 import java.util.Collections;
14
 import java.util.Collections;
22
-import java.util.List;
23
 
15
 
24
 import androidx.appcompat.widget.ActionMenuView;
16
 import androidx.appcompat.widget.ActionMenuView;
25
 
17
 
26
 import static com.reactnativenavigation.utils.Assertions.assertNotNull;
18
 import static com.reactnativenavigation.utils.Assertions.assertNotNull;
27
-import static com.reactnativenavigation.utils.TitleBarHelper.createButtonController;
28
 import static com.reactnativenavigation.utils.ViewUtils.findChildByClass;
19
 import static com.reactnativenavigation.utils.ViewUtils.findChildByClass;
29
 import static org.assertj.core.api.Java6Assertions.assertThat;
20
 import static org.assertj.core.api.Java6Assertions.assertThat;
30
 import static org.mockito.Mockito.any;
21
 import static org.mockito.Mockito.any;
37
 public class TitleBarTest extends BaseTest {
28
 public class TitleBarTest extends BaseTest {
38
 
29
 
39
     private TitleBar uut;
30
     private TitleBar uut;
40
-    private Button leftButton;
41
-    private Button textButton;
42
-    private Button customButton;
43
     private Activity activity;
31
     private Activity activity;
44
 
32
 
45
     @Override
33
     @Override
46
     public void beforeEach() {
34
     public void beforeEach() {
47
         activity = newActivity();
35
         activity = newActivity();
48
-        createButtons();
49
         uut = spy(new TitleBar(activity));
36
         uut = spy(new TitleBar(activity));
50
     }
37
     }
51
 
38
 
52
-    private void createButtons() {
53
-        leftButton = new Button();
54
-        leftButton.id = Constants.BACK_BUTTON_ID;
55
-
56
-        textButton = new Button();
57
-        textButton.id = "textButton";
58
-        textButton.text = new Text("Btn");
59
-
60
-        customButton = new Button();
61
-        customButton.id = "customBtn";
62
-        customButton.component.name = new Text("com.rnn.customBtn");
63
-        customButton.component.componentId = new Text("component4");
64
-    }
65
-
66
-    @Test
67
-    public void setButton_setsTextButton() {
68
-        uut.setRightButtons(rightButtons(textButton));
69
-        uut.setLeftButtons(leftButton(leftButton));
70
-        assertThat(uut.getMenu().getItem(0).getTitle()).isEqualTo(textButton.text.get());
71
-    }
72
-
73
-    @Test
74
-    public void setButton_setsCustomButton() {
75
-        uut.setLeftButtons(leftButton(leftButton));
76
-        uut.setRightButtons(rightButtons(customButton));
77
-        ReactView btnView = (ReactView) uut.getMenu().getItem(0).getActionView();
78
-        assertThat(btnView.getComponentName()).isEqualTo(customButton.component.name.get());
79
-    }
80
-
81
-    @Test
82
-    public void setRightButtons_emptyButtonsListClearsRightButtons() {
83
-        uut.setLeftButtons(new ArrayList<>());
84
-        uut.setRightButtons(rightButtons(customButton, textButton));
85
-        uut.setLeftButtons(new ArrayList<>());
86
-        uut.setRightButtons(new ArrayList<>());
87
-        assertThat(uut.getMenu().size()).isEqualTo(0);
88
-    }
89
-
90
-    @Test
91
-    public void setLeftButtons_emptyButtonsListClearsLeftButton() {
92
-        uut.setLeftButtons(leftButton(leftButton));
93
-        uut.setRightButtons(rightButtons(customButton));
94
-        assertThat(uut.getNavigationIcon()).isNotNull();
95
-
96
-        uut.setLeftButtons(new ArrayList<>());
97
-        uut.setRightButtons(rightButtons(textButton));
98
-        assertThat(uut.getNavigationIcon()).isNull();
99
-    }
100
-
101
     @Test
39
     @Test
102
     public void setLeftButton_titleIsAligned() {
40
     public void setLeftButton_titleIsAligned() {
103
         uut.setTitle("Title");
41
         uut.setTitle("Title");
110
         verify(uut).alignTextView(any(), eq(title));
48
         verify(uut).alignTextView(any(), eq(title));
111
     }
49
     }
112
 
50
 
113
-    @Test
114
-    public void setRightButtons_buttonsAreAddedInReverseOrderToMatchOrderOnIOs() {
115
-        uut.setLeftButtons(new ArrayList<>());
116
-        uut.setRightButtons(rightButtons(textButton, customButton));
117
-        assertThat(uut.getMenu().getItem(1).getTitle()).isEqualTo(textButton.text.get());
118
-    }
119
-
120
     @Test
51
     @Test
121
     public void setComponent_addsComponentToTitleBar() {
52
     public void setComponent_addsComponentToTitleBar() {
122
         View component = new View(activity);
53
         View component = new View(activity);
161
         uut.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
92
         uut.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
162
         verify(spy).setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
93
         verify(spy).setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
163
     }
94
     }
164
-
165
-    private List<TitleBarButtonController> leftButton(Button leftButton) {
166
-        return Collections.singletonList(createButtonController(activity, uut, leftButton));
167
-    }
168
-
169
-    private List<TitleBarButtonController> rightButtons(Button... buttons) {
170
-        return CollectionUtils.map(Arrays.asList(buttons), button -> createButtonController(activity, uut, button));
171
-    }
172
 }
95
 }

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

2
 
2
 
3
 import android.app.Activity;
3
 import android.app.Activity;
4
 import android.graphics.Color;
4
 import android.graphics.Color;
5
-import android.graphics.Typeface;
6
 import android.view.MenuItem;
5
 import android.view.MenuItem;
7
 
6
 
8
 import com.reactnativenavigation.BaseTest;
7
 import com.reactnativenavigation.BaseTest;
9
 import com.reactnativenavigation.TestUtils;
8
 import com.reactnativenavigation.TestUtils;
10
 import com.reactnativenavigation.mocks.ImageLoaderMock;
9
 import com.reactnativenavigation.mocks.ImageLoaderMock;
11
-import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
10
+import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock;
12
 import com.reactnativenavigation.parse.params.Bool;
11
 import com.reactnativenavigation.parse.params.Bool;
13
 import com.reactnativenavigation.parse.params.Button;
12
 import com.reactnativenavigation.parse.params.Button;
14
 import com.reactnativenavigation.parse.params.Colour;
13
 import com.reactnativenavigation.parse.params.Colour;
18
 import com.reactnativenavigation.utils.ButtonPresenter;
17
 import com.reactnativenavigation.utils.ButtonPresenter;
19
 import com.reactnativenavigation.viewcontrollers.button.IconResolver;
18
 import com.reactnativenavigation.viewcontrollers.button.IconResolver;
20
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
19
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
20
+import com.reactnativenavigation.views.titlebar.TitleBar;
21
 
21
 
22
 import org.junit.Test;
22
 import org.junit.Test;
23
 
23
 
24
-import androidx.appcompat.widget.Toolbar;
25
-
26
 import static org.assertj.core.api.Java6Assertions.assertThat;
24
 import static org.assertj.core.api.Java6Assertions.assertThat;
27
 import static org.mockito.ArgumentMatchers.any;
25
 import static org.mockito.ArgumentMatchers.any;
28
 import static org.mockito.ArgumentMatchers.anyInt;
26
 import static org.mockito.ArgumentMatchers.anyInt;
44
         button = new Button();
42
         button = new Button();
45
         final Activity activity = newActivity();
43
         final Activity activity = newActivity();
46
 
44
 
47
-        TopBarButtonCreatorMock buttonCreatorMock = new TopBarButtonCreatorMock();
45
+        TitleBarButtonCreatorMock buttonCreatorMock = new TitleBarButtonCreatorMock();
48
         stackController = spy(TestUtils.newStackController(activity).build());
46
         stackController = spy(TestUtils.newStackController(activity).build());
49
         stackController.getView().layout(0, 0, 1080, 1920);
47
         stackController.getView().layout(0, 0, 1080, 1920);
50
         stackController.getTopBar().layout(0, 0, 1080, 200);
48
         stackController.getTopBar().layout(0, 0, 1080, 200);
51
         getTitleBar().layout(0, 0, 1080, 200);
49
         getTitleBar().layout(0, 0, 1080, 200);
52
 
50
 
53
-        optionsPresenter = spy(new ButtonPresenter(getTitleBar(), button));
51
+        optionsPresenter = spy(new ButtonPresenter(button));
54
         uut = new TitleBarButtonController(activity, new IconResolver(activity, ImageLoaderMock.mock()), optionsPresenter, button, buttonCreatorMock, (buttonId) -> {});
52
         uut = new TitleBarButtonController(activity, new IconResolver(activity, ImageLoaderMock.mock()), optionsPresenter, button, buttonCreatorMock, (buttonId) -> {});
55
 
53
 
56
         stackController.ensureViewIsCreated();
54
         stackController.ensureViewIsCreated();
98
         verify(optionsPresenter, times(0)).tint(any(), anyInt());
96
         verify(optionsPresenter, times(0)).tint(any(), anyInt());
99
     }
97
     }
100
 
98
 
101
-    @Test
102
-    public void fontFamily() {
103
-        setTextButton();
104
-        uut.addToMenu(getTitleBar(), 0);
105
-        verify(optionsPresenter, times(1)).setTypeFace(Typeface.MONOSPACE);
106
-    }
107
-
108
-    @Test
109
-    public void fontSize() {
110
-        setTextButton();
111
-        uut.addToMenu(getTitleBar(), 0);
112
-        verify(optionsPresenter, times(0)).setFontSize(getTitleBar().getMenu().getItem(0));
113
-
114
-        clearMenu();
115
-        button.fontSize = new Number(10);
116
-        uut.addToMenu(getTitleBar(), 0);
117
-        verify(optionsPresenter, times(1)).setFontSize(getTitleBar().getMenu().getItem(0));
118
-    }
119
-
120
-    @Test
121
-    public void textColor_enabled() {
122
-        setTextButton();
123
-        button.enabled = new Bool(false);
124
-        uut.addToMenu(getTitleBar(), 0);
125
-        dispatchPreDraw(getTitleBar());
126
-        verify(optionsPresenter, times(0)).setEnabledColor(any());
127
-
128
-        clearMenu();
129
-        button.enabled = new Bool(true);
130
-        button.color = new Colour(android.graphics.Color.RED);
131
-        uut.addToMenu(getTitleBar(), 0);
132
-        dispatchPreDraw(getTitleBar());
133
-        verify(optionsPresenter, times(1)).setEnabledColor(any());
134
-    }
135
-
136
-    private void clearMenu() {
137
-        getTitleBar().getMenu().clear();
138
-    }
139
-
140
-    @Test
141
-    public void textColor_disabled() {
142
-        setTextButton();
143
-        button.enabled = new Bool(false);
144
-        uut.addToMenu(getTitleBar(), 0);
145
-        dispatchPreDraw(getTitleBar());
146
-        verify(optionsPresenter, times(1)).setDisabledColor(any(), eq(Color.LTGRAY));
147
-
148
-        clearMenu();
149
-        button.disabledColor = new Colour(android.graphics.Color.BLACK);
150
-        uut.addToMenu(getTitleBar(), 0);
151
-        dispatchPreDraw(getTitleBar());
152
-        verify(optionsPresenter, times(1)).setDisabledColor(any(), eq(Color.BLACK));
153
-    }
154
-
155
-    private Toolbar getTitleBar() {
99
+    private TitleBar getTitleBar() {
156
         return stackController.getTopBar().getTitleBar();
100
         return stackController.getTopBar().getTitleBar();
157
     }
101
     }
158
 
102
 
159
-    private void setTextButton() {
160
-        button.id = "btn1";
161
-        button.text = new Text("Button");
162
-        button.fontFamily = Typeface.MONOSPACE;
163
-        button.showAsAction = new Number(MenuItem.SHOW_AS_ACTION_ALWAYS);
164
-    }
165
-
166
     private void setIconButton(boolean enabled) {
103
     private void setIconButton(boolean enabled) {
167
         button.id = "btn1";
104
         button.id = "btn1";
168
         button.icon = new Text("someIcon");
105
         button.icon = new Text("someIcon");

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

1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+
5
+import com.reactnativenavigation.BaseTest;
6
+import com.reactnativenavigation.parse.params.Button;
7
+import com.reactnativenavigation.parse.params.Text;
8
+import com.reactnativenavigation.react.Constants;
9
+import com.reactnativenavigation.react.ReactView;
10
+import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
11
+import com.reactnativenavigation.views.StackLayout;
12
+
13
+import org.junit.Test;
14
+import org.mockito.Mockito;
15
+
16
+import java.util.ArrayList;
17
+import java.util.Arrays;
18
+import java.util.Collections;
19
+import java.util.List;
20
+
21
+import static com.reactnativenavigation.utils.CollectionUtils.*;
22
+import static com.reactnativenavigation.utils.TitleBarHelper.createButtonController;
23
+import static org.assertj.core.api.Java6Assertions.assertThat;
24
+import static org.mockito.Mockito.spy;
25
+
26
+public class TopBarControllerTest extends BaseTest {
27
+    private TopBarController uut;
28
+    private Activity activity;
29
+    private Button leftButton;
30
+    private Button textButton1;
31
+    private Button textButton2;
32
+    private Button componentButton;
33
+
34
+    @Override
35
+    public void beforeEach() {
36
+        activity = newActivity();
37
+        uut = spy(new TopBarController());
38
+        StackLayout stack = Mockito.mock(StackLayout.class);
39
+        uut.createView(activity, stack);
40
+
41
+        createButtons();
42
+    }
43
+
44
+    @Test
45
+    public void setButton_setsTextButton() {
46
+        uut.applyRightButtons(rightButtons(textButton1));
47
+        uut.setLeftButtons(leftButton(leftButton));
48
+        assertThat(uut.getRightButton(0).getTitle().toString()).isEqualTo(textButton1.text.get());
49
+    }
50
+
51
+    @Test
52
+    public void setButton_setsCustomButton() {
53
+        uut.setLeftButtons(leftButton(leftButton));
54
+        uut.applyRightButtons(rightButtons(componentButton));
55
+        ReactView btnView = (ReactView) uut.getRightButton(0).getActionView();
56
+        assertThat(btnView.getComponentName()).isEqualTo(componentButton.component.name.get());
57
+    }
58
+
59
+    @Test
60
+    public void applyRightButtons_emptyButtonsListClearsRightButtons() {
61
+        uut.setLeftButtons(new ArrayList<>());
62
+        uut.applyRightButtons(rightButtons(componentButton, textButton1));
63
+        uut.setLeftButtons(new ArrayList<>());
64
+        uut.applyRightButtons(new ArrayList<>());
65
+        assertThat(uut.getRightButtonsCount()).isEqualTo(0);
66
+    }
67
+
68
+    @Test
69
+    public void applyRightButtons_previousButtonsAreCleared() {
70
+        uut.applyRightButtons(rightButtons(textButton1, componentButton));
71
+        assertThat(uut.getRightButtonsCount()).isEqualTo(2);
72
+
73
+        uut.applyRightButtons(rightButtons(textButton2));
74
+        assertThat(uut.getRightButtonsCount()).isEqualTo(1);
75
+    }
76
+
77
+    @Test
78
+    public void applyRightButtons_buttonsAreAddedInReverseOrderToMatchOrderOnIOs() {
79
+        uut.setLeftButtons(new ArrayList<>());
80
+        uut.applyRightButtons(rightButtons(textButton1, componentButton));
81
+        assertThat(uut.getRightButton(1).getTitle().toString()).isEqualTo(textButton1.text.get());
82
+    }
83
+
84
+    @Test
85
+    public void applyRightButtons_componentButtonIsReapplied() {
86
+        List<TitleBarButtonController> initialButtons = rightButtons(componentButton);
87
+        uut.applyRightButtons(initialButtons);
88
+        assertThat(uut.getRightButton(0).getItemId()).isEqualTo(componentButton.getIntId());
89
+
90
+        uut.applyRightButtons(rightButtons(textButton1));
91
+        assertThat(uut.getRightButton(0).getItemId()).isEqualTo(textButton1.getIntId());
92
+
93
+        uut.applyRightButtons(initialButtons);
94
+        assertThat(uut.getRightButton(0).getItemId()).isEqualTo(componentButton.getIntId());
95
+    }
96
+
97
+    @Test
98
+    public void mergeRightButtons_componentButtonIsNotAddedIfAlreadyAddedToMenu() {
99
+        List<TitleBarButtonController> initialButtons = rightButtons(componentButton);
100
+        uut.applyRightButtons(initialButtons);
101
+
102
+        uut.mergeRightButtons(initialButtons, Collections.EMPTY_LIST);
103
+
104
+    }
105
+
106
+    @Test
107
+    public void setLeftButtons_emptyButtonsListClearsLeftButton() {
108
+        uut.setLeftButtons(leftButton(leftButton));
109
+        uut.applyRightButtons(rightButtons(componentButton));
110
+        assertThat(uut.getLeftButton()).isNotNull();
111
+
112
+        uut.setLeftButtons(new ArrayList<>());
113
+        uut.applyRightButtons(rightButtons(textButton1));
114
+        assertThat(uut.getLeftButton()).isNull();
115
+    }
116
+
117
+    private void createButtons() {
118
+        leftButton = new Button();
119
+        leftButton.id = Constants.BACK_BUTTON_ID;
120
+
121
+        textButton1 = createTextButton("1");
122
+        textButton2 = createTextButton("2");
123
+
124
+        componentButton = new Button();
125
+        componentButton.id = "customBtn";
126
+        componentButton.component.name = new Text("com.rnn.customBtn");
127
+        componentButton.component.componentId = new Text("component4");
128
+    }
129
+
130
+    private Button createTextButton(String id) {
131
+        Button button = new Button();
132
+        button.id = id;
133
+        button.text = new Text("txt" + id);
134
+        return button;
135
+    }
136
+
137
+    private List<TitleBarButtonController> leftButton(Button leftButton) {
138
+        return Collections.singletonList(createButtonController(activity, leftButton));
139
+    }
140
+
141
+    private List<TitleBarButtonController> rightButtons(Button... buttons) {
142
+        return map(Arrays.asList(buttons), button -> createButtonController(activity, button));
143
+    }
144
+}

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

14
 import com.reactnativenavigation.mocks.SimpleViewController;
14
 import com.reactnativenavigation.mocks.SimpleViewController;
15
 import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
15
 import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
16
 import com.reactnativenavigation.mocks.TopBarBackgroundViewCreatorMock;
16
 import com.reactnativenavigation.mocks.TopBarBackgroundViewCreatorMock;
17
-import com.reactnativenavigation.mocks.TopBarButtonCreatorMock;
17
+import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock;
18
 import com.reactnativenavigation.parse.AnimationOptions;
18
 import com.reactnativenavigation.parse.AnimationOptions;
19
 import com.reactnativenavigation.parse.NestedAnimationsOptions;
19
 import com.reactnativenavigation.parse.NestedAnimationsOptions;
20
 import com.reactnativenavigation.parse.Options;
20
 import com.reactnativenavigation.parse.Options;
102
                     activity,
102
                     activity,
103
                     new TitleBarReactViewCreatorMock(),
103
                     new TitleBarReactViewCreatorMock(),
104
                     new TopBarBackgroundViewCreatorMock(),
104
                     new TopBarBackgroundViewCreatorMock(),
105
-                    new TopBarButtonCreatorMock(),
105
+                    new TitleBarButtonCreatorMock(),
106
                     new IconResolver(activity, ImageLoaderMock.mock()),
106
                     new IconResolver(activity, ImageLoaderMock.mock()),
107
                     new RenderChecker(),
107
                     new RenderChecker(),
108
                     new Options()
108
                     new Options()

+ 8
- 0
lib/src/interfaces/Options.ts View File

339
      * Properties to pass down to the component
339
      * Properties to pass down to the component
340
      */
340
      */
341
     passProps?: object;
341
     passProps?: object;
342
+    /**
343
+     * (Android only) component width
344
+     */
345
+    width?: number;
346
+    /**
347
+     * (Android only) component height
348
+     */
349
+    height?: number;
342
   };
350
   };
343
   /**
351
   /**
344
    * (iOS only) Set the button as an iOS system icon
352
    * (iOS only) Set the button as an iOS system icon

+ 8
- 2
playground/src/screens/OptionsScreen.js View File

40
         <Button label='Set React Title View' testID={SET_REACT_TITLE_VIEW} onPress={this.setReactTitleView} />
40
         <Button label='Set React Title View' testID={SET_REACT_TITLE_VIEW} onPress={this.setReactTitleView} />
41
         <Button label='Show Yellow Box' testID={SHOW_YELLOW_BOX_BTN} onPress={() => console.warn('Yellow Box')} />
41
         <Button label='Show Yellow Box' testID={SHOW_YELLOW_BOX_BTN} onPress={() => console.warn('Yellow Box')} />
42
         <Button label='StatusBar' onPress={this.statusBarScreen} />
42
         <Button label='StatusBar' onPress={this.statusBarScreen} />
43
-        <Button label='Buttons Screen' testID={GOTO_BUTTONS_SCREEN} onPress={this.goToButtonsScreen} />
43
+        <Button label='Buttons Screen' testID={GOTO_BUTTONS_SCREEN} onPress={this.pushButtonsScreen} />
44
       </Root>
44
       </Root>
45
     );
45
     );
46
   }
46
   }
101
 
101
 
102
   statusBarScreen = () => Navigation.showModal(Screens.StatusBar);
102
   statusBarScreen = () => Navigation.showModal(Screens.StatusBar);
103
 
103
 
104
-  goToButtonsScreen = () => Navigation.push(this, Screens.Buttons);
104
+  pushButtonsScreen = () => Navigation.push(this, Screens.Buttons, {
105
+    animations: {
106
+      push: {
107
+        waitForRender: true
108
+      }
109
+    }
110
+  });
105
 }
111
 }
106
 
112
 
107
 module.exports = Options;
113
 module.exports = Options;

+ 2
- 1
playground/src/screens/RoundedButton.js View File

39
     backgroundColor: 'transparent',
39
     backgroundColor: 'transparent',
40
     flexDirection: 'column',
40
     flexDirection: 'column',
41
     justifyContent: 'center',
41
     justifyContent: 'center',
42
-    alignItems: 'center'
42
+    alignItems: 'center',
43
+    padding: 4
43
   },
44
   },
44
   button: {
45
   button: {
45
     width: 40,
46
     width: 40,

+ 2
- 0
playground/src/testIDs.js View File

50
   SHOW_TABS_BTN: 'SHOW_TABS_BTN',
50
   SHOW_TABS_BTN: 'SHOW_TABS_BTN',
51
   HIDE_TABS_PUSH_BTN: 'HIDE_TABS_PUSH_BTN',
51
   HIDE_TABS_PUSH_BTN: 'HIDE_TABS_PUSH_BTN',
52
   ROUND_BUTTON: 'ROUND_BUTTON',
52
   ROUND_BUTTON: 'ROUND_BUTTON',
53
+  ROUND_BUTTON_2: 'ROUND_BUTTON_2',
53
   BUTTON_ONE: 'BUTTON_ONE',
54
   BUTTON_ONE: 'BUTTON_ONE',
54
   LEFT_BUTTON: 'LEFT_BUTTON',
55
   LEFT_BUTTON: 'LEFT_BUTTON',
55
   HIDE_TOPBAR_DEFAULT_OPTIONS: 'HIDE_TOPBAR_DEFAULT_OPTIONS',
56
   HIDE_TOPBAR_DEFAULT_OPTIONS: 'HIDE_TOPBAR_DEFAULT_OPTIONS',
92
   NAVIGATION_SCREEN: `NAVIGATION_SCREEN`,
93
   NAVIGATION_SCREEN: `NAVIGATION_SCREEN`,
93
 
94
 
94
   // Buttons
95
   // Buttons
96
+  ADD_BUTTON: 'ADD_BUTTON',
95
   TAB_BASED_APP_BUTTON: `TAB_BASED_APP_BUTTON`,
97
   TAB_BASED_APP_BUTTON: `TAB_BASED_APP_BUTTON`,
96
   TAB_BASED_APP_SIDE_BUTTON: `TAB_BASED_APP_SIDE_BUTTON`,
98
   TAB_BASED_APP_SIDE_BUTTON: `TAB_BASED_APP_SIDE_BUTTON`,
97
   PUSH_STATIC_LIFECYCLE_BUTTON: `PUSH_STATIC_LIFECYCLE_BUTTON`,
99
   PUSH_STATIC_LIFECYCLE_BUTTON: `PUSH_STATIC_LIFECYCLE_BUTTON`,