Skip to content

Commit

Permalink
BottomTab dot indicator (#5283)
Browse files Browse the repository at this point in the history
Add support for dot indicator to BottomTabs.
  • Loading branch information
guyca authored Jul 18, 2019
1 parent 76d832b commit f425155
Show file tree
Hide file tree
Showing 57 changed files with 1,208 additions and 490 deletions.
2 changes: 1 addition & 1 deletion e2e/BottomTabs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('BottomTabs', () => {
await expect(element(by.text('NEW'))).toBeVisible();
});

it(':ios: set Tab Bar badge null on a current Tab should reset badge', async () => {
it('set empty string badge on a current Tab should clear badge', async () => {
await elementById(TestIDs.SET_BADGE_BTN).tap();
await expect(element(by.text('NEW'))).toBeVisible();
await elementById(TestIDs.CLEAR_BADGE_BTN).tap();
Expand Down
3 changes: 1 addition & 2 deletions lib/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,11 @@ allprojects { p ->
def supportLibVersion = safeExtGet('supportLibVersion', DEFAULT_SUPPORT_LIB_VERSION)

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "com.android.support:design:${supportLibVersion}"
implementation "com.android.support:appcompat-v7:${supportLibVersion}"
implementation "com.android.support:support-v4:${supportLibVersion}"

implementation 'com.github.wix-playground:ahbottomnavigation:2.4.9'
implementation 'com.github.wix-playground:ahbottomnavigation:2.4.15'
implementation 'com.github.wix-playground:reflow-animator:1.0.4'
implementation 'com.github.clans:fab:1.6.4'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public static BottomTabOptions parse(TypefaceLoader typefaceManager, JSONObject
options.fontFamily = typefaceManager.getTypeFace(json.optString("fontFamily", ""));
options.fontSize = NumberParser.parse(json, "fontSize");
options.selectedFontSize = NumberParser.parse(json, "selectedFontSize");
options.dotIndicator = DotIndicatorOptions.parse(json.optJSONObject("dotIndicator"));
return options;
}

Expand All @@ -46,6 +47,7 @@ public static BottomTabOptions parse(TypefaceLoader typefaceManager, JSONObject
public Text testId = new NullText();
public Text badge = new NullText();
public Colour badgeColor = new NullColor();
public DotIndicatorOptions dotIndicator = new DotIndicatorOptions();
public Number fontSize = new NullNumber();
public Number selectedFontSize = new NullNumber();
@Nullable public Typeface fontFamily;
Expand All @@ -64,6 +66,7 @@ void mergeWith(final BottomTabOptions other) {
if (other.fontSize.hasValue()) fontSize = other.fontSize;
if (other.selectedFontSize.hasValue()) selectedFontSize = other.selectedFontSize;
if (other.fontFamily != null) fontFamily = other.fontFamily;
if (other.dotIndicator.hasValue()) dotIndicator = other.dotIndicator;
}

void mergeWithDefault(final BottomTabOptions defaultOptions) {
Expand All @@ -79,5 +82,6 @@ void mergeWithDefault(final BottomTabOptions defaultOptions) {
if (!selectedFontSize.hasValue()) selectedFontSize = defaultOptions.selectedFontSize;
if (fontFamily == null) fontFamily = defaultOptions.fontFamily;
if (!testId.hasValue()) testId = defaultOptions.testId;
if (!dotIndicator.hasValue()) dotIndicator = defaultOptions.dotIndicator;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.reactnativenavigation.parse;

import android.support.annotation.Nullable;

import com.reactnativenavigation.parse.params.Bool;
import com.reactnativenavigation.parse.params.Colour;
import com.reactnativenavigation.parse.params.NullBool;
import com.reactnativenavigation.parse.params.NullColor;
import com.reactnativenavigation.parse.params.NullNumber;
import com.reactnativenavigation.parse.params.Number;
import com.reactnativenavigation.parse.parsers.BoolParser;
import com.reactnativenavigation.parse.parsers.ColorParser;
import com.reactnativenavigation.parse.parsers.NumberParser;

import org.json.JSONObject;

public class DotIndicatorOptions {
public static DotIndicatorOptions parse(@Nullable JSONObject json) {
DotIndicatorOptions options = new DotIndicatorOptions();
if (json == null) return options;

options.color = ColorParser.parse(json, "color");
options.size = NumberParser.parse(json, "size");
options.visible = BoolParser.parse(json, "visible");

return options;
}

public Colour color = new NullColor();
public Number size = new NullNumber();
public Bool visible = new NullBool();

public boolean hasValue() {
return visible.hasValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;

import com.reactnativenavigation.parse.*;
import com.reactnativenavigation.utils.*;
import com.reactnativenavigation.viewcontrollers.*;
import com.reactnativenavigation.viewcontrollers.bottomtabs.*;
import com.reactnativenavigation.views.*;
import com.aurelhubert.ahbottomnavigation.notification.AHNotification;
import com.reactnativenavigation.parse.BottomTabOptions;
import com.reactnativenavigation.parse.DotIndicatorOptions;
import com.reactnativenavigation.parse.Options;
import com.reactnativenavigation.utils.ImageLoader;
import com.reactnativenavigation.utils.ImageLoadingListenerAdapter;
import com.reactnativenavigation.viewcontrollers.ViewController;
import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabFinder;
import com.reactnativenavigation.views.BottomTabs;
import com.reactnativenavigation.views.Component;

import java.util.*;
import java.util.List;

import static com.reactnativenavigation.utils.UiUtils.dpToPx;

public class BottomTabPresenter {
private final Context context;
Expand All @@ -23,6 +29,7 @@ public class BottomTabPresenter {
private final int defaultSelectedTextColor;
private final int defaultTextColor;
private final List<ViewController> tabs;
private final int defaultDotIndicatorSize;

public BottomTabPresenter(Context context, List<ViewController> tabs, ImageLoader imageLoader, Options defaultOptions) {
this.tabs = tabs;
Expand All @@ -32,6 +39,7 @@ public BottomTabPresenter(Context context, List<ViewController> tabs, ImageLoade
this.defaultOptions = defaultOptions;
defaultSelectedTextColor = defaultOptions.bottomTabOptions.selectedIconColor.get(ContextCompat.getColor(context, com.aurelhubert.ahbottomnavigation.R.color.colorBottomNavigationAccent));
defaultTextColor = defaultOptions.bottomTabOptions.iconColor.get(ContextCompat.getColor(context, com.aurelhubert.ahbottomnavigation.R.color.colorBottomNavigationInactive));
defaultDotIndicatorSize = dpToPx(context, 6);
}

public void setDefaultOptions(Options defaultOptions) {
Expand All @@ -45,8 +53,6 @@ public void bindView(BottomTabs bottomTabs) {
public void applyOptions() {
for (int i = 0; i < tabs.size(); i++) {
BottomTabOptions tab = tabs.get(i).resolveCurrentOptions(defaultOptions).bottomTabOptions;
bottomTabs.setBadge(i, tab.badge.get(""));
bottomTabs.setBadgeColor(tab.badgeColor.get(null));
bottomTabs.setTitleTypeface(i, tab.fontFamily);
bottomTabs.setIconActiveColor(i, tab.selectedIconColor.get(null));
bottomTabs.setIconInactiveColor(i, tab.iconColor.get(null));
Expand All @@ -55,28 +61,63 @@ public void applyOptions() {
bottomTabs.setTitleInactiveTextSizeInSp(i, tab.fontSize.hasValue() ? Float.valueOf(tab.fontSize.get()) : null);
bottomTabs.setTitleActiveTextSizeInSp(i, tab.selectedFontSize.hasValue() ? Float.valueOf(tab.selectedFontSize.get()) : null);
if (tab.testId.hasValue()) bottomTabs.setTag(i, tab.testId.get());
if (shouldApplyDot(tab)) applyDotIndicator(i, tab.dotIndicator); else applyBadge(i, tab);
}
}

public void mergeChildOptions(Options options, Component child) {
int index = bottomTabFinder.findByComponent(child);
if (index >= 0) {
BottomTabOptions bto = options.bottomTabOptions;
if (bto.badge.hasValue()) bottomTabs.setBadge(index, bto.badge.get());
if (bto.badgeColor.hasValue()) bottomTabs.setBadgeColor(bto.badgeColor.get());
if (bto.fontFamily != null) bottomTabs.setTitleTypeface(index, bto.fontFamily);
if (bto.selectedIconColor.hasValue()) bottomTabs.setIconActiveColor(index, bto.selectedIconColor.get());
if (bto.iconColor.hasValue()) bottomTabs.setIconInactiveColor(index, bto.iconColor.get());
if (bto.selectedTextColor.hasValue()) bottomTabs.setTitleActiveColor(index, bto.selectedTextColor.get());
if (bto.textColor.hasValue()) bottomTabs.setTitleInactiveColor(index, bto.textColor.get());
if (bto.text.hasValue()) bottomTabs.setText(index, bto.text.get());
if (bto.icon.hasValue()) imageLoader.loadIcon(context, bto.icon.get(), new ImageLoadingListenerAdapter() {
BottomTabOptions tab = options.bottomTabOptions;
if (tab.fontFamily != null) bottomTabs.setTitleTypeface(index, tab.fontFamily);
if (tab.selectedIconColor.hasValue()) bottomTabs.setIconActiveColor(index, tab.selectedIconColor.get());
if (tab.iconColor.hasValue()) bottomTabs.setIconInactiveColor(index, tab.iconColor.get());
if (tab.selectedTextColor.hasValue()) bottomTabs.setTitleActiveColor(index, tab.selectedTextColor.get());
if (tab.textColor.hasValue()) bottomTabs.setTitleInactiveColor(index, tab.textColor.get());
if (tab.text.hasValue()) bottomTabs.setText(index, tab.text.get());
if (tab.icon.hasValue()) imageLoader.loadIcon(context, tab.icon.get(), new ImageLoadingListenerAdapter() {
@Override
public void onComplete(@NonNull Drawable drawable) {
bottomTabs.setIcon(index, drawable);
}
});
if (bto.testId.hasValue()) bottomTabs.setTag(index, bto.testId.get());
if (tab.testId.hasValue()) bottomTabs.setTag(index, tab.testId.get());
if (shouldApplyDot(tab)) mergeDotIndicator(index, tab.dotIndicator); else mergeBadge(index, tab);
}
}

private void applyDotIndicator(int tabIndex, DotIndicatorOptions dotIndicator) {
AHNotification.Builder builder = new AHNotification.Builder()
.setText("")
.setBackgroundColor(dotIndicator.color.get(null))
.setSize(dotIndicator.size.get(defaultDotIndicatorSize));
bottomTabs.setNotification(builder.build(), tabIndex);
}

private void applyBadge(int tabIndex, BottomTabOptions tab) {
AHNotification.Builder builder = new AHNotification.Builder()
.setText(tab.badge.get(""))
.setBackgroundColor(tab.badgeColor.get(null));
bottomTabs.setNotification(builder.build(), tabIndex);
}

private void mergeBadge(int index, BottomTabOptions tab) {
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());
bottomTabs.setNotification(builder.build(), index);
}

private void mergeDotIndicator(int index, DotIndicatorOptions dotIndicator) {
AHNotification.Builder builder = new AHNotification.Builder();
if (dotIndicator.color.hasValue()) builder.setBackgroundColor(dotIndicator.color.get());
builder.setSize(dotIndicator.visible.isFalse() ? 0 : dotIndicator.size.get(defaultDotIndicatorSize));
AHNotification notification = builder.build();
if (notification.hasValue()) bottomTabs.setNotification(notification, index);
}

private boolean shouldApplyDot(BottomTabOptions tab) {
return tab.dotIndicator.visible.hasValue() && !tab.badge.hasValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public static float dpToPx(Context context, float dp) {
}

public static int dpToPx(Context context, int dp) {
if (dp <= 0) return dp;
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return (int) (dp * ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.IntRange;

import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
Expand Down Expand Up @@ -51,11 +50,6 @@ public void setBadge(int bottomTabIndex, String badge) {
setNotification(badge, bottomTabIndex);
}

public void setBadgeColor(@ColorInt Integer color) {
if (color == null) return;
setNotificationBackgroundColor(color);
}

@Override
public void setCurrentItem(@IntRange(from = 0) int position) {
super.setCurrentItem(position);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.app.Activity;
import android.graphics.Color;

import com.aurelhubert.ahbottomnavigation.notification.AHNotification;
import com.reactnativenavigation.BaseTest;
import com.reactnativenavigation.mocks.ImageLoaderMock;
import com.reactnativenavigation.mocks.SimpleViewController;
Expand All @@ -19,6 +20,7 @@
import java.util.Arrays;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
Expand Down Expand Up @@ -53,7 +55,7 @@ public void beforeEach() {
public void present() {
uut.applyOptions();
for (int i = 0; i < tabs.size(); i++) {
verify(bottomTabs, times(1)).setBadge(i, tabs.get(i).options.bottomTabOptions.badge.get(""));
verify(bottomTabs, times(1)).setNotification(any(AHNotification.class), eq(i));
verify(bottomTabs, times(1)).setTitleInactiveColor(i, tabs.get(i).options.bottomTabOptions.textColor.get(null));
verify(bottomTabs, times(1)).setTitleActiveColor(i, tabs.get(i).options.bottomTabOptions.selectedTextColor.get(null));
}
Expand All @@ -64,7 +66,7 @@ public void mergeChildOptions() {
for (int i = 0; i < 2; i++) {
Options options = tabs.get(i).options;
uut.mergeChildOptions(options, (Component) tabs.get(i).getView());
verify(bottomTabs, times(1)).setBadge(i, options.bottomTabOptions.badge.get());
verify(bottomTabs, times(1)).setNotification(any(AHNotification.class), eq(i));
verify(bottomTabs, times(1)).setIconActiveColor(eq(i), anyInt());
verify(bottomTabs, times(1)).setIconInactiveColor(eq(i), anyInt());
}
Expand Down
2 changes: 2 additions & 0 deletions lib/ios/Bool.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@

- (BOOL)getWithDefaultValue:(BOOL)value;

- (bool) isFalse;

@end
5 changes: 5 additions & 0 deletions lib/ios/Bool.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ - (BOOL)getWithDefaultValue:(BOOL)defaultValue {
}
}

- (bool)isFalse {
return self.value != nil && ![self.value boolValue];
}


@end
11 changes: 11 additions & 0 deletions lib/ios/DotIndicatorOptions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#import "RNNOptions.h"

@interface DotIndicatorOptions : RNNOptions

@property(nonatomic, strong) Color *color;
@property(nonatomic, strong) Number *size;
@property(nonatomic, strong) Bool *visible;

- (bool)hasValue;

@end
28 changes: 28 additions & 0 deletions lib/ios/DotIndicatorOptions.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#import "DotIndicatorOptions.h"
#import "NullColor.h"
#import "NullNumber.h"
#import "NullBool.h"


@implementation DotIndicatorOptions
- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super init];

self.color = [ColorParser parse:dict key:@"color"];
self.size = [NumberParser parse:dict key:@"size"];
self.visible = [BoolParser parse:dict key:@"visible"];
return self;
}

- (instancetype)init {
_color = [NullColor new];
_size = [NullNumber new];
_visible = [NullBool new];
return self;
}

- (bool)hasValue {
return [self.visible hasValue];
}

@end
7 changes: 7 additions & 0 deletions lib/ios/DotIndicatorParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>

@class DotIndicatorOptions;

@interface DotIndicatorParser : NSObject
+ (DotIndicatorOptions *)parse:(NSDictionary *)dict;
@end
10 changes: 10 additions & 0 deletions lib/ios/DotIndicatorParser.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import "DotIndicatorParser.h"
#import "DotIndicatorOptions.h"


@implementation DotIndicatorParser
+ (DotIndicatorOptions *)parse:(NSDictionary *)dict {
return [[DotIndicatorOptions alloc] initWithDict:dict[@"dotIndicator"]];
}

@end
9 changes: 6 additions & 3 deletions lib/ios/RNNBasePresenter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ typedef void (^RNNReactViewReadyCompletionBlock)(void);

@interface RNNBasePresenter : NSObject

@property (nonatomic, weak) id bindedViewController;
@property(nonatomic, weak) id boundViewController;

@property (nonatomic, strong) NSString* bindedComponentId;
@property(nonatomic, strong) NSString *boundComponentId;

- (void)bindViewController:(UIViewController *)bindedViewController;
- (void)bindViewController:(UIViewController *)boundViewController;

- (void)applyOptionsOnInit:(RNNNavigationOptions *)initialOptions;

Expand All @@ -18,8 +18,11 @@ typedef void (^RNNReactViewReadyCompletionBlock)(void);

- (void)applyOptionsOnWillMoveToParentViewController:(RNNNavigationOptions *)options;

- (void)applyDotIndicator:(UIViewController *)child;

- (void)mergeOptions:(RNNNavigationOptions *)newOptions currentOptions:(RNNNavigationOptions *)currentOptions defaultOptions:(RNNNavigationOptions *)defaultOptions;

- (void)renderComponents:(RNNNavigationOptions *)options perform:(RNNReactViewReadyCompletionBlock)readyBlock;

- (void)viewDidLayoutSubviews;
@end
Loading

0 comments on commit f425155

Please sign in to comment.