Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bottom tab Badge style #6973

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Draft
13 changes: 13 additions & 0 deletions RNNBottomTabBadgeOptions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import "RNNOptions.h"

@interface RNNBottomTabBadgeOptions : RNNOptions

@property(nonatomic, strong) Text *text;
@property(nonatomic, strong) Color *textColor;
@property(nonatomic, strong) Color *backgroundColor;

- (BOOL)hasValue;

+ (RNNBottomTabBadgeOptions *)parse:(NSDictionary *)dict;

@end
30 changes: 30 additions & 0 deletions RNNBottomTabBadgeOptions.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#import "RNNBottomTabBadgeOptions.h"

@implementation RNNBottomTabBadgeOptions

- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super initWithDict:dict];
self.text = [TextParser parse:dict key:@"text"];
self.textColor = [ColorParser parse:dict key:@"textColor"];
self.backgroundColor = [ColorParser parse:dict key:@"backgroundColor"];
return self;
}

- (void)mergeOptions:(RNNBottomTabBadgeOptions *)options {
if (options.text.hasValue)
self.text = options.text;
if (options.textColor.hasValue)
self.textColor = options.textColor;
if (options.backgroundColor.hasValue)
self.backgroundColor = options.backgroundColor;
}

- (BOOL)hasValue {
return self.text.hasValue || self.textColor.hasValue || self.backgroundColor.hasValue;
}

+ (RNNBottomTabBadgeOptions *)parse:(NSDictionary *)dict {
return [[RNNBottomTabBadgeOptions alloc] initWithDict:dict[@"badge"]];
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.reactnativenavigation.options

import android.content.Context
import com.reactnativenavigation.options.params.Colour
import com.reactnativenavigation.options.params.NullColor
import com.reactnativenavigation.options.params.NullText
import com.reactnativenavigation.options.params.Text
import com.reactnativenavigation.options.parsers.ColorParser
import com.reactnativenavigation.options.parsers.TextParser
import org.json.JSONObject

fun parseBadgeOptions(context: Context, json: JSONObject?): BadgeOptions {
return json?.let {
BadgeOptions(TextParser.parse(json, "text"), ColorParser.parse(context, json, "textColor"),
ColorParser.parse(context, json, "backgroundColor")
)
} ?: BadgeOptions()
}

class BadgeOptions(var text: Text = NullText(), var textColor: Colour = NullColor(), var backgroundColor: Colour = NullColor()) {
fun hasValue() = text.hasValue() || textColor.hasValue() || backgroundColor.hasValue()
fun mergeWith(other: BadgeOptions) {
if (other.text.hasValue())
this.text = other.text
if (other.textColor.hasValue())
this.textColor = other.textColor
if (other.backgroundColor.hasValue())
this.backgroundColor = other.backgroundColor
}

fun mergeWithDefaults(other: BadgeOptions) {
if (!this.text.hasValue())
this.text = other.text
if (!this.textColor.hasValue())
this.textColor = other.textColor
if (!this.backgroundColor.hasValue())
this.backgroundColor = other.backgroundColor
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ public static BottomTabOptions parse(Context context, TypefaceLoader typefaceMan
options.selectedIcon = IconParser.parse(json, "selectedIcon");
options.iconColor = ColorParser.parse(context, json, "iconColor");
options.selectedIconColor = ColorParser.parse(context, json, "selectedIconColor");
options.badge = TextParser.parse(json, "badge");
options.badgeColor = ColorParser.parse(context, json, "badgeColor");
options.badge = BadgeOptionsKt.parseBadgeOptions(context, json.optJSONObject("badge"));
options.animateBadge = BoolParser.parse(json, "animateBadge");
options.testId = TextParser.parse(json, "testID");
options.font = FontParser.parse(json);
Expand All @@ -58,8 +57,7 @@ public static BottomTabOptions parse(Context context, TypefaceLoader typefaceMan
public Colour iconColor = new NullColor();
public Colour selectedIconColor = new NullColor();
public Text testId = new NullText();
public Text badge = new NullText();
public Colour badgeColor = new NullColor();
public BadgeOptions badge = new BadgeOptions();
public Bool animateBadge = new NullBool();
public DotIndicatorOptions dotIndicator = new DotIndicatorOptions();
public Number fontSize = new NullNumber();
Expand All @@ -78,8 +76,7 @@ void mergeWith(final BottomTabOptions other) {
if (other.selectedIcon.hasValue()) selectedIcon = other.selectedIcon;
if (other.iconColor.hasValue()) iconColor = other.iconColor;
if (other.selectedIconColor.hasValue()) selectedIconColor = other.selectedIconColor;
if (other.badge.hasValue()) badge = other.badge;
if (other.badgeColor.hasValue()) badgeColor = other.badgeColor;
badge.mergeWith(other.badge);
if (other.animateBadge.hasValue()) animateBadge = other.animateBadge;
if (other.testId.hasValue()) testId = other.testId;
if (other.fontSize.hasValue()) fontSize = other.fontSize;
Expand All @@ -99,8 +96,7 @@ void mergeWithDefault(final BottomTabOptions defaultOptions) {
if (!selectedIcon.hasValue()) selectedIcon = defaultOptions.selectedIcon;
if (!iconColor.hasValue()) iconColor = defaultOptions.iconColor;
if (!selectedIconColor.hasValue()) selectedIconColor = defaultOptions.selectedIconColor;
if (!badge.hasValue()) badge = defaultOptions.badge;
if (!badgeColor.hasValue()) badgeColor = defaultOptions.badgeColor;
badge.mergeWithDefaults(defaultOptions.badge);
if (!animateBadge.hasValue()) animateBadge = defaultOptions.animateBadge;
if (!fontSize.hasValue()) fontSize = defaultOptions.fontSize;
if (!selectedFontSize.hasValue()) selectedFontSize = defaultOptions.selectedFontSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class BottomTabPresenter {
private final List<ViewController<?>> tabs;
private final int defaultDotIndicatorSize;

public BottomTabPresenter(Context context, List<ViewController<?>> tabs, ImageLoader imageLoader, TypefaceLoader typefaceLoader, Options defaultOptions) {
public BottomTabPresenter(Context context, List<ViewController<?>> tabs, ImageLoader imageLoader, TypefaceLoader typefaceLoader, Options defaultOptions) {
this.tabs = tabs;
this.context = context;
this.bottomTabFinder = new BottomTabFinder(tabs);
Expand Down Expand Up @@ -65,7 +65,8 @@ public void applyOptions() {
if (tab.fontSize.hasValue()) bottomTabs.setTitleInactiveTextSizeInSp(i, Float.valueOf(tab.fontSize.get()));
if (tab.selectedFontSize.hasValue()) bottomTabs.setTitleActiveTextSizeInSp(i, Float.valueOf(tab.selectedFontSize.get()));
if (tab.testId.hasValue()) bottomTabs.setTag(i, tab.testId.get());
if (shouldApplyDot(tab)) applyDotIndicator(i, tab.dotIndicator); else applyBadge(i, tab);
if (shouldApplyDot(tab)) applyDotIndicator(i, tab.dotIndicator);
else applyBadge(i, tab);
}
});
}
Expand Down Expand Up @@ -104,16 +105,19 @@ public void onComplete(@NonNull Drawable drawable) {
}
});
if (tab.testId.hasValue()) bottomTabs.setTag(index, tab.testId.get());
if (shouldApplyDot(tab)) mergeDotIndicator(index, tab.dotIndicator); else mergeBadge(index, tab);
if (shouldApplyDot(tab))
mergeDotIndicator(index, tab.dotIndicator);
else
mergeBadge(index, tab);
}
});
}

private void applyDotIndicator(int tabIndex, DotIndicatorOptions dotIndicator) {
if(dotIndicator.visible.isFalse()) return;
if (dotIndicator.visible.isFalse()) return;
AHNotification.Builder builder = new AHNotification.Builder()
.setText("")
.setBackgroundColor(dotIndicator.color.get(null))
.setBackgroundColor(dotIndicator.color.get(0))
.setSize(dotIndicator.size.get(defaultDotIndicatorSize))
.animate(dotIndicator.animate.get(false));
bottomTabs.perform(bottomTabs -> bottomTabs.setNotification(builder.build(), tabIndex));
Expand All @@ -122,21 +126,38 @@ private void applyDotIndicator(int tabIndex, DotIndicatorOptions dotIndicator) {
private void applyBadge(int tabIndex, BottomTabOptions tab) {
if (bottomTabs == null) return;
AHNotification.Builder builder = new AHNotification.Builder()
.setText(tab.badge.get(""))
.setBackgroundColor(tab.badgeColor.get(null))
.setText(tab.badge.getText().get(""))
.setBackgroundColor(tab.badge.getBackgroundColor().get(0))
.setTextColor(tab.badge.getTextColor().get(0))
.animate(tab.animateBadge.get(false));
bottomTabs.perform(bottomTabs -> bottomTabs.setNotification(builder.build(), tabIndex));
}

private void mergeBadge(int index, BottomTabOptions tab) {
if (bottomTabs == null) return;
if (!tab.badge.hasValue()) return;

AHNotification.Builder builder = new AHNotification.Builder();
if (tab.badge.hasValue()) builder.setText(tab.badge.get());
if (tab.badgeColor.hasValue()) builder.setBackgroundColor(tab.badgeColor.get());
if (tab.badgeColor.hasValue()) builder.setBackgroundColor(tab.badgeColor.get());
if (tab.animateBadge.hasValue()) builder.animate(tab.animateBadge.get());
bottomTabs.perform(bottomTabs -> bottomTabs.setNotification(builder.build(), index));
if (tab.badge.hasValue()) {
BottomTabOptions oldOptions = tabs.get(index).resolveCurrentOptions(defaultOptions).bottomTabOptions;

if (tab.badge.getText().hasValue())
builder.setText(tab.badge.getText().get());
else
builder.setText(oldOptions.badge.getText().get(""));
if (tab.badge.getTextColor().hasValue())
builder.setTextColor(tab.badge.getTextColor().get());
else
builder.setTextColor(oldOptions.badge.getTextColor().get(0));


if (tab.badge.getBackgroundColor().hasValue())
builder.setBackgroundColor(tab.badge.getBackgroundColor().get());
else
builder.setBackgroundColor(oldOptions.badge.getBackgroundColor().get(0));
if (tab.animateBadge.hasValue())
builder.animate(tab.animateBadge.get());
bottomTabs.perform(bottomTabs -> bottomTabs.setNotification(builder.build(), index));
}
}

private void mergeDotIndicator(int index, DotIndicatorOptions dotIndicator) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.reactnativenavigation.options

import android.graphics.Color
import com.reactnativenavigation.BaseTest
import org.assertj.core.api.Assertions.assertThat
import org.json.JSONObject
import org.junit.Test

class BadgeOptionsTest : BaseTest() {
lateinit var uut: BadgeOptions

@Test
fun `parse - should parse json options`() {
uut = parseBadgeOptions(newActivity(), JSONObject().apply {
put("text", "hello")
put("textColor", Color.RED)
put("backgroundColor", Color.RED)
})

assertThat(uut.text.get()).isEqualTo("hello")
assertThat(uut.textColor.get()).isEqualTo(Color.RED)
assertThat(uut.backgroundColor.get()).isEqualTo(Color.RED)
}

@Test
fun `parse - should parse null for missing json options`() {
uut = parseBadgeOptions(newActivity(), JSONObject().apply {
put("text", "hello")
put("backgroundColor", Color.RED)
})

assertThat(uut.text.get()).isEqualTo("hello")
assertThat(uut.textColor.hasValue()).isFalse()
assertThat(uut.backgroundColor.get()).isEqualTo(Color.RED)

uut = parseBadgeOptions(newActivity(), JSONObject().apply {
})

assertThat(uut.text.hasValue()).isFalse()
assertThat(uut.textColor.hasValue()).isFalse()
assertThat(uut.backgroundColor.hasValue()).isFalse()
}

@Test
fun `hasValue - true when one of the props has value false otherwhise`() {
uut = parseBadgeOptions(newActivity(), JSONObject().apply {
put("text", "hello")
put("backgroundColor", Color.RED)
})
assertThat(uut.hasValue()).isTrue()

uut = parseBadgeOptions(newActivity(), JSONObject().apply {
put("backgroundColor", Color.RED)
})
assertThat(uut.hasValue()).isTrue()

uut = parseBadgeOptions(newActivity(), JSONObject().apply {
})
assertThat(uut.hasValue()).isFalse()
}

@Test
fun `mergeWith - should merge changes only`() {
uut = parseBadgeOptions(newActivity(), JSONObject().apply {
put("text", "hello")
put("backgroundColor", Color.RED)
})

assertThat(uut.textColor.hasValue()).isFalse()
val uut2 = parseBadgeOptions(newActivity(), JSONObject().apply {
put("backgroundColor", Color.YELLOW)
put("textColor", Color.BLUE)
})
uut.mergeWith(uut2)

assertThat(uut.textColor.hasValue()).isTrue()
assertThat(uut.backgroundColor.get()).isEqualTo(Color.YELLOW)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,27 @@
import com.reactnativenavigation.mocks.ImageLoaderMock;
import com.reactnativenavigation.mocks.SimpleViewController;
import com.reactnativenavigation.mocks.TypefaceLoaderMock;
import com.reactnativenavigation.options.BadgeOptions;
import com.reactnativenavigation.options.Options;
import com.reactnativenavigation.options.params.Colour;
import com.reactnativenavigation.options.params.DontApplyColour;
import com.reactnativenavigation.options.params.NullColor;
import com.reactnativenavigation.options.params.NullText;
import com.reactnativenavigation.options.params.Text;
import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry;
import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController;
import com.reactnativenavigation.views.bottomtabs.BottomTabs;

import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;

import java.util.Arrays;
import java.util.List;

import static com.reactnativenavigation.utils.CollectionUtils.*;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
Expand Down Expand Up @@ -113,17 +118,48 @@ public void mergeChildOptions_nullColorsAreNotMerged() {
verify(bottomTabs, times(0)).setIconInactiveColor(anyInt(), anyInt());
}

@Test
public void mergeChildOptions_shouldMergeBadgeOptions() {
Options options = new Options();
options.bottomTabOptions.badge = new BadgeOptions(new Text("tab1badge"), new Colour(Color.RED), new Colour(Color.BLUE));
uut.mergeChildOptions(options, child2);
ArgumentCaptor<AHNotification> notificationArgumentCaptor = ArgumentCaptor.forClass(AHNotification.class);
verify(bottomTabs, times(1)).setNotification(notificationArgumentCaptor.capture(), anyInt());
AHNotification value = notificationArgumentCaptor.getValue();
assertThat(value).isNotNull();
assertThat(value.getReadableText()).isEqualTo("tab1badge");
assertThat(value.getBackgroundColor()).isEqualTo(Color.BLUE);
assertThat(value.getTextColor()).isEqualTo(Color.RED);
}

@Test
public void mergeChildOptions_shouldMergeChangedBadgeOptionsOnly() {
Options options = new Options();
options.bottomTabOptions.badge = new BadgeOptions(new Text("tab1badge"), new Colour(Color.RED), new Colour(Color.BLUE));
uut.mergeChildOptions(options, child2);

Options options2 = new Options();
options2.bottomTabOptions.badge = new BadgeOptions(new NullText(), new Colour(Color.YELLOW), new NullColor());
uut.mergeChildOptions(options2, child2);

//now second time
ArgumentCaptor<AHNotification> notificationArgumentCaptor2 = ArgumentCaptor.forClass(AHNotification.class);
verify(bottomTabs, times(2)).setNotification(notificationArgumentCaptor2.capture(), anyInt());
AHNotification value2 = notificationArgumentCaptor2.getValue();
assertThat(value2.getTextColor()).isEqualTo(Color.YELLOW);
}

private Options createTab1Options() {
Options options = new Options();
options.bottomTabOptions.badge = new Text("tab1badge");
options.bottomTabOptions.badge = new BadgeOptions(new Text("tab1badge"), new NullColor(), new NullColor());
options.bottomTabOptions.iconColor = new Colour(Color.RED);
options.bottomTabOptions.selectedIconColor = new Colour(Color.RED);
return options;
}

private Options createTab2Options() {
Options options = new Options();
options.bottomTabOptions.badge = new Text("tab2badge");
options.bottomTabOptions.badge = new BadgeOptions(new Text("tab2badge"), new NullColor(), new NullColor());
options.bottomTabOptions.iconColor = new Colour(Color.RED);
options.bottomTabOptions.selectedIconColor = new Colour(Color.RED);
return options;
Expand Down
Loading