Browse Source

setButtons for iOS (#1746)

Johan 7 years ago
parent
commit
5d1783cbcf

+ 15
- 1
e2e/ScreenStyle.test.js View File

@@ -1,6 +1,6 @@
1 1
 const Utils = require('./Utils');
2 2
 
3
-const elementByLabel = Utils.elementByLabel;
3
+const { elementByLabel, elementById } = Utils;
4 4
 
5 5
 describe('screen style', () => {
6 6
   beforeEach(async () => {
@@ -50,4 +50,18 @@ describe('screen style', () => {
50 50
     await elementByLabel('Set Tab Badge').tap();
51 51
     await expect(element(by.text('EnCyClOpEdIa'))).toBeVisible();
52 52
   });
53
+
54
+  it('set right buttons', async () => {
55
+    await elementByLabel('Push Options Screen').tap();
56
+    await expect(elementById('buttonOne')).toBeVisible();
57
+    await elementById('buttonOne').tap();
58
+    await expect(elementById('buttonTwo')).toBeVisible();
59
+    await elementById('buttonTwo').tap();
60
+    await expect(elementById('buttonOne')).toBeVisible();
61
+  });
62
+
63
+  it('set left buttons', async () => {
64
+    await elementByLabel('Push Options Screen').tap();
65
+    await expect(elementById('buttonLeft')).toBeVisible();
66
+  });
53 67
 });

+ 3
- 0
e2e/Utils.js View File

@@ -1,5 +1,8 @@
1 1
 module.exports = {
2 2
   elementByLabel: (label) => {
3 3
     return element(by.label(label));
4
+  },
5
+  elementById: (id) => {
6
+    return element(by.id(id));
4 7
   }
5 8
 };

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

@@ -11,6 +11,7 @@ public class NavigationEvent {
11 11
 	private static final String onAppLaunched = "RNN.appLaunched";
12 12
 	private static final String containerDidAppear = "RNN.containerDidAppear";
13 13
 	private static final String containerDidDisappear = "RNN.containerDidDisappear";
14
+	private static final String onNavigationButtonPressed = "RNN.navigationButtonPressed";
14 15
 
15 16
 	private final RCTDeviceEventEmitter emitter;
16 17
 
@@ -30,6 +31,11 @@ public class NavigationEvent {
30 31
 		emit(containerDidAppear, id);
31 32
 	}
32 33
 
34
+	public void sendOnNavigationButtonPressed(String id, String buttonId) {
35
+		//TODO!
36
+		//emit(onNavigationButtonPressed, id);
37
+	}
38
+
33 39
 	private void emit(String eventName) {
34 40
 		emit(eventName, Arguments.createMap());
35 41
 	}

+ 8
- 0
lib/ios/RCTHelpers.h View File

@@ -0,0 +1,8 @@
1
+#import <Foundation/Foundation.h>
2
+#import <React/RCTRootView.h>
3
+
4
+@interface RCTHelpers : NSObject
5
++ (NSMutableDictionary *)textAttributesFromDictionary:(NSDictionary *)dictionary withPrefix:(NSString *)prefix;
6
++ (NSMutableDictionary *)textAttributesFromDictionary:(NSDictionary *)dictionary withPrefix:(NSString *)prefix baseFont:(UIFont *)font;
7
++ (NSString*)getTimestampString;
8
+@end

+ 138
- 0
lib/ios/RCTHelpers.m View File

@@ -0,0 +1,138 @@
1
+#import "RCTHelpers.h"
2
+#import <React/RCTView.h>
3
+#import <React/RCTScrollView.h>
4
+#import <React/RCTFont.h>
5
+
6
+@implementation RCTHelpers
7
+
8
++ (NSMutableDictionary *)textAttributesFromDictionary:(NSDictionary *)dictionary withPrefix:(NSString *)prefix baseFont:(UIFont *)baseFont
9
+{
10
+	NSMutableDictionary *textAttributes = [NSMutableDictionary new];
11
+	
12
+	NSString *colorKey = @"color";
13
+	NSString *familyKey = @"fontFamily";
14
+	NSString *weightKey = @"fontWeight";
15
+	NSString *sizeKey = @"fontSize";
16
+	NSString *styleKey = @"fontStyle";
17
+	NSString *shadowColourKey = @"shadowColor";
18
+	NSString *shadowOffsetKey = @"shadowOffset";
19
+	NSString *shadowBlurRadiusKey = @"shadowBlurRadius";
20
+	NSString *showShadowKey = @"showShadow";
21
+	
22
+	if (prefix) {
23
+		
24
+		colorKey = [colorKey stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[colorKey substringToIndex:1].capitalizedString];
25
+		colorKey = [NSString stringWithFormat:@"%@%@", prefix, colorKey];
26
+		
27
+		familyKey = [familyKey stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[familyKey substringToIndex:1].capitalizedString];
28
+		familyKey = [NSString stringWithFormat:@"%@%@", prefix, familyKey];
29
+		
30
+		weightKey = [weightKey stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[weightKey substringToIndex:1].capitalizedString];
31
+		weightKey = [NSString stringWithFormat:@"%@%@", prefix, weightKey];
32
+		
33
+		sizeKey = [sizeKey stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[sizeKey substringToIndex:1].capitalizedString];
34
+		sizeKey = [NSString stringWithFormat:@"%@%@", prefix, sizeKey];
35
+		
36
+		styleKey = [styleKey stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[styleKey substringToIndex:1].capitalizedString];
37
+		styleKey = [NSString stringWithFormat:@"%@%@", prefix, styleKey];
38
+		
39
+		shadowColourKey = [shadowColourKey stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[shadowColourKey substringToIndex:1].capitalizedString];
40
+		shadowColourKey = [NSString stringWithFormat:@"%@%@", prefix, shadowColourKey];
41
+		
42
+		shadowOffsetKey = [shadowOffsetKey stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[shadowOffsetKey substringToIndex:1].capitalizedString];
43
+		shadowOffsetKey = [NSString stringWithFormat:@"%@%@", prefix, shadowOffsetKey];
44
+		
45
+		shadowBlurRadiusKey = [shadowBlurRadiusKey stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[shadowBlurRadiusKey substringToIndex:1].capitalizedString];
46
+		shadowBlurRadiusKey = [NSString stringWithFormat:@"%@%@", prefix, shadowBlurRadiusKey];
47
+		
48
+		showShadowKey = [showShadowKey stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[showShadowKey substringToIndex:1].capitalizedString];
49
+		showShadowKey = [NSString stringWithFormat:@"%@%@", prefix, showShadowKey];
50
+	}
51
+	
52
+	NSShadow *shadow;
53
+	
54
+	NSNumber *shadowColor = dictionary[shadowColourKey];
55
+	if (shadowColor && [shadowColor isKindOfClass:[NSNumber class]]) {
56
+		if (!shadow) {
57
+			shadow = [NSShadow new];
58
+		}
59
+		shadow.shadowColor = [RCTConvert UIColor:shadowColor];
60
+	}
61
+	
62
+	NSDictionary *shadowOffsetDict = dictionary[shadowOffsetKey];
63
+	if (shadowOffsetDict && [shadowOffsetDict isKindOfClass:[NSDictionary class]]) {
64
+		CGSize shadowOffset = [RCTConvert CGSize:shadowOffsetDict];
65
+		if (!shadow) {
66
+			shadow = [NSShadow new];
67
+		}
68
+		shadow.shadowOffset = shadowOffset;
69
+	}
70
+	
71
+	NSNumber *shadowRadius = dictionary[shadowBlurRadiusKey];
72
+	if (shadowRadius) {
73
+		CGFloat radius = [RCTConvert CGFloat:shadowRadius];
74
+		if (!shadow) {
75
+			shadow = [NSShadow new];
76
+		}
77
+		shadow.shadowBlurRadius = radius;
78
+	}
79
+	
80
+	NSNumber *showShadow = dictionary[showShadowKey];
81
+	if (showShadow) {
82
+		BOOL show = [RCTConvert BOOL:showShadow];
83
+		if (!show) {
84
+			shadow = nil;
85
+		}
86
+	}
87
+	
88
+	if (shadow) {
89
+		[textAttributes setObject:shadow forKey:NSShadowAttributeName];
90
+	}
91
+	
92
+	NSNumber *textColor = dictionary[colorKey];
93
+	if (textColor && [textColor isKindOfClass:[NSNumber class]])
94
+	{
95
+		UIColor *color = [RCTConvert UIColor:textColor];
96
+		[textAttributes setObject:color forKey:NSForegroundColorAttributeName];
97
+	}
98
+	
99
+	NSString *fontFamily = dictionary[familyKey];
100
+	if (![fontFamily isKindOfClass:[NSString class]]) {
101
+		fontFamily = nil;
102
+	}
103
+	
104
+	NSString *fontWeight = dictionary[weightKey];
105
+	if (![fontWeight isKindOfClass:[NSString class]]) {
106
+		fontWeight = nil;
107
+	}
108
+	
109
+	NSNumber *fontSize = dictionary[sizeKey];
110
+	if (![fontSize isKindOfClass:[NSNumber class]]) {
111
+		fontSize = nil;
112
+	}
113
+	
114
+	NSString *fontStyle = dictionary[styleKey];
115
+	if (![fontStyle isKindOfClass:[NSString class]]) {
116
+		fontStyle = nil;
117
+	}
118
+	
119
+	UIFont *font = [RCTFont updateFont:baseFont withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle variant:nil scaleMultiplier:1];
120
+	
121
+	if (font && (fontStyle || fontWeight || fontSize || fontFamily)) {
122
+		[textAttributes setObject:font forKey:NSFontAttributeName];
123
+	}
124
+	
125
+	return textAttributes;
126
+}
127
+
128
++ (NSMutableDictionary *)textAttributesFromDictionary:(NSDictionary *)dictionary withPrefix:(NSString *)prefix
129
+{
130
+	return [self textAttributesFromDictionary:dictionary withPrefix:prefix baseFont:[UIFont systemFontOfSize:[UIFont systemFontSize]]];
131
+}
132
+
133
++ (NSString *)getTimestampString {
134
+	long long milliseconds = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
135
+	return [NSString stringWithFormat:@"%lld", milliseconds];
136
+}
137
+
138
+@end

+ 1
- 0
lib/ios/RNNCommandsHandler.m View File

@@ -43,6 +43,7 @@
43 43
 		RNNRootViewController* rootVc = (RNNRootViewController*)vc;
44 44
 		[rootVc.navigationOptions mergeWith:options];
45 45
 		[rootVc.navigationOptions applyOn:vc];
46
+		[rootVc applyNavigationButtons];
46 47
 	}
47 48
 }
48 49
 

+ 2
- 0
lib/ios/RNNEventEmitter.h View File

@@ -12,4 +12,6 @@
12 12
 
13 13
 -(void)sendContainerDidDisappear:(NSString*)containerId;
14 14
 
15
+-(void)sendOnNavigationButtonPressed:(NSString*)containerId buttonId:(NSString*)buttonId;
16
+
15 17
 @end

+ 6
- 2
lib/ios/RNNEventEmitter.m View File

@@ -7,10 +7,10 @@ RCT_EXPORT_MODULE();
7 7
 static NSString* const onAppLaunched	= @"RNN.appLaunched";
8 8
 static NSString* const containerDidAppear	= @"RNN.containerDidAppear";
9 9
 static NSString* const containerDidDisappear	= @"RNN.containerDidDisappear";
10
-
10
+static NSString* const onNavigationButtonPressed	= @"RNN.navigationButtonPressed";
11 11
 
12 12
 -(NSArray<NSString *> *)supportedEvents {
13
-	return @[onAppLaunched, containerDidAppear, containerDidDisappear];
13
+	return @[onAppLaunched, containerDidAppear, containerDidDisappear, onNavigationButtonPressed];
14 14
 }
15 15
 
16 16
 # pragma mark public
@@ -27,6 +27,10 @@ static NSString* const containerDidDisappear	= @"RNN.containerDidDisappear";
27 27
 	[self send:containerDidDisappear body:containerId];
28 28
 }
29 29
 
30
+-(void)sendOnNavigationButtonPressed:(NSString *)containerId buttonId:(NSString*)buttonId {
31
+	[self send:onNavigationButtonPressed body:@{@"containerId":containerId , @"buttonId": buttonId }];
32
+}
33
+
30 34
 # pragma mark private
31 35
 
32 36
 -(void)send:(NSString *)eventName body:(id)body {

+ 14
- 0
lib/ios/RNNNavigationButtons.h View File

@@ -0,0 +1,14 @@
1
+#import <Foundation/Foundation.h>
2
+#import <UIKit/UIKit.h>
3
+#import "RNNRootViewController.h"
4
+
5
+@interface RNNNavigationButtons : NSObject
6
+
7
+-(instancetype)init;
8
+-(instancetype)initWithViewController:(RNNRootViewController*)viewController;
9
+
10
+-(void)applyLeftButtons:(NSArray*)leftButtons rightButtons:(NSArray*)rightButtons;
11
+
12
+@end
13
+
14
+

+ 111
- 0
lib/ios/RNNNavigationButtons.m View File

@@ -0,0 +1,111 @@
1
+#import "RNNNavigationButtons.h"
2
+#import "RNNUIBarButtonItem.h"
3
+#import <React/RCTConvert.h>
4
+#import "RCTHelpers.h"
5
+
6
+@interface RNNNavigationButtons()
7
+@property (weak, nonatomic) RNNRootViewController* viewController;
8
+
9
+@end
10
+
11
+@implementation RNNNavigationButtons
12
+
13
+-(instancetype)init {
14
+	return [super init];
15
+}
16
+
17
+-(instancetype)initWithViewController:(RNNRootViewController*)viewController {
18
+	self = [super init];
19
+	
20
+	self.viewController = viewController;
21
+	
22
+	return self;
23
+}
24
+
25
+-(void)applyLeftButtons:(NSArray*)leftButtons rightButtons:(NSArray*)rightButtons {
26
+	if(leftButtons) {
27
+		[self setButtons:leftButtons side:@"left" animated:NO];
28
+	}
29
+	
30
+	if(rightButtons) {
31
+		[self setButtons:rightButtons side:@"right" animated:NO];
32
+	}
33
+}
34
+
35
+-(void)setButtons:(NSArray*)buttons side:(NSString*)side animated:(BOOL)animated {
36
+	NSMutableArray *barButtonItems = [NSMutableArray new];
37
+	for (NSDictionary *button in buttons) {
38
+		RNNUIBarButtonItem* barButtonItem = [self buildButton:button];
39
+		if(barButtonItem) {
40
+			[barButtonItems addObject:barButtonItem];
41
+		}
42
+	}
43
+	
44
+	if ([side isEqualToString:@"left"])
45
+	{
46
+		[self.viewController.navigationItem setLeftBarButtonItems:barButtonItems animated:animated];
47
+	}
48
+	
49
+	if ([side isEqualToString:@"right"])
50
+	{
51
+		[self.viewController.navigationItem setRightBarButtonItems:barButtonItems animated:animated];
52
+	}
53
+}
54
+
55
+-(RNNUIBarButtonItem*)buildButton: (NSDictionary*)dictionary {
56
+	NSString* buttonId = dictionary[@"id"];
57
+	NSString* title = dictionary[@"title"];
58
+	
59
+	if(!buttonId){
60
+		@throw [NSException exceptionWithName:@"NSInvalidArgumentException" reason:[@"button id is not specified " stringByAppendingString:title] userInfo:nil];
61
+	}
62
+	
63
+	UIImage* iconImage = nil;
64
+	id icon = dictionary[@"icon"];
65
+	if (icon) {
66
+		iconImage = [RCTConvert UIImage:icon];
67
+	}
68
+	
69
+	RNNUIBarButtonItem *barButtonItem;
70
+	if (iconImage) {
71
+		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withIcon:iconImage];
72
+	} else if (title) {
73
+		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withTitle:title];
74
+		
75
+		NSMutableDictionary *buttonTextAttributes = [RCTHelpers textAttributesFromDictionary:dictionary withPrefix:@"button"];
76
+		if (buttonTextAttributes.allKeys.count > 0) {
77
+			[barButtonItem setTitleTextAttributes:buttonTextAttributes forState:UIControlStateNormal];
78
+		}
79
+	} else {
80
+		return nil;
81
+	}
82
+	
83
+	barButtonItem.target = self;
84
+	barButtonItem.action = @selector(onButtonPress:);
85
+	
86
+	NSNumber *disabled = dictionary[@"disabled"];
87
+	BOOL disabledBool = disabled ? [disabled boolValue] : NO;
88
+	if (disabledBool) {
89
+		[barButtonItem setEnabled:NO];
90
+	}
91
+	
92
+	NSNumber *disableIconTintString = dictionary[@"disableIconTint"];
93
+	BOOL disableIconTint = disableIconTintString ? [disableIconTintString boolValue] : NO;
94
+	if (disableIconTint) {
95
+		[barButtonItem setImage:[barButtonItem.image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
96
+	}
97
+	
98
+	NSString *testID = dictionary[@"testID"];
99
+	if (testID)
100
+	{
101
+		barButtonItem.accessibilityIdentifier = testID;
102
+	}
103
+	
104
+	return barButtonItem;
105
+}
106
+
107
+-(void)onButtonPress:(RNNUIBarButtonItem*)barButtonItem
108
+{
109
+	[self.viewController.eventEmitter sendOnNavigationButtonPressed:self.viewController.containerId buttonId:barButtonItem.buttonId];
110
+}
111
+@end

+ 3
- 3
lib/ios/RNNNavigationOptions.h View File

@@ -17,16 +17,16 @@ extern const NSInteger BLUR_STATUS_TAG;
17 17
 @property (nonatomic, strong) NSNumber* topBarTranslucent;
18 18
 @property (nonatomic, strong) NSString* tabBadge;
19 19
 @property (nonatomic, strong) NSNumber* topBarTextFontSize;
20
+@property (nonatomic, strong) NSArray* leftButtons;
21
+@property (nonatomic, strong) NSArray* rightButtons;
20 22
 @property (nonatomic, strong) NSNumber* topBarNoBorder;
21 23
 @property (nonatomic, strong) NSNumber* statusBarBlur;
22 24
 @property (nonatomic, strong) NSNumber* statusBarHideWithTopBar;
23 25
 
24
-
25 26
 -(instancetype)init;
26 27
 -(instancetype)initWithDict:(NSDictionary *)navigationOptions;
27 28
 
28 29
 -(void)applyOn:(UIViewController*)viewController;
29 30
 -(void)mergeWith:(NSDictionary*)otherOptions;
30 31
 
31
-@end
32
-
32
+@end

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

@@ -8,7 +8,7 @@ const NSInteger BLUR_STATUS_TAG = 78264801;
8 8
 -(instancetype)init {
9 9
 	return [self initWithDict:@{}];
10 10
 }
11
-
11
+	
12 12
 -(instancetype)initWithDict:(NSDictionary *)navigationOptions {
13 13
 	self = [super init];
14 14
 	self.topBarBackgroundColor = [navigationOptions objectForKey:@"topBarBackgroundColor"];
@@ -23,6 +23,8 @@ const NSInteger BLUR_STATUS_TAG = 78264801;
23 23
 	self.topBarTranslucent = [navigationOptions objectForKey:@"topBarTranslucent"];
24 24
 	self.tabBadge = [navigationOptions objectForKey:@"tabBadge"];
25 25
 	self.topBarTextFontSize = [navigationOptions objectForKey:@"topBarTextFontSize"];
26
+	self.leftButtons = [navigationOptions objectForKey:@"leftButtons"];
27
+	self.rightButtons = [navigationOptions objectForKey:@"rightButtons"];
26 28
 	self.topBarNoBorder = [navigationOptions objectForKey:@"topBarNoBorder"];
27 29
 
28 30
 	return self;
@@ -136,4 +138,5 @@ const NSInteger BLUR_STATUS_TAG = 78264801;
136 138
 	}
137 139
 	
138 140
 }
141
+
139 142
 @end

+ 4
- 0
lib/ios/RNNRootViewController.h View File

@@ -9,6 +9,8 @@
9 9
 @interface RNNRootViewController : UIViewController
10 10
 
11 11
 @property (nonatomic, strong) RNNNavigationOptions* navigationOptions;
12
+@property (nonatomic, strong) RNNEventEmitter *eventEmitter;
13
+@property (nonatomic, strong) NSString* containerId;
12 14
 
13 15
 -(instancetype)initWithName:(NSString*)name
14 16
 				withOptions:(RNNNavigationOptions*)options
@@ -17,4 +19,6 @@
17 19
 			   eventEmitter:(RNNEventEmitter*)eventEmitter;
18 20
 
19 21
 
22
+-(void) applyNavigationButtons;
23
+
20 24
 @end

+ 8
- 3
lib/ios/RNNRootViewController.m View File

@@ -1,13 +1,12 @@
1 1
 
2 2
 #import "RNNRootViewController.h"
3 3
 #import <React/RCTConvert.h>
4
-
4
+#import "RNNNavigationButtons.h"
5 5
 
6 6
 @interface RNNRootViewController()
7
-@property (nonatomic, strong) NSString* containerId;
8 7
 @property (nonatomic, strong) NSString* containerName;
9
-@property (nonatomic, strong) RNNEventEmitter *eventEmitter;
10 8
 @property (nonatomic) BOOL _statusBarHidden;
9
+@property (nonatomic, strong) RNNNavigationButtons* navigationButtons;
11 10
 
12 11
 @end
13 12
 
@@ -30,6 +29,7 @@
30 29
 												 name:RCTJavaScriptWillStartLoadingNotification
31 30
 											   object:nil];
32 31
 	
32
+	self.navigationButtons = [[RNNNavigationButtons alloc] initWithViewController:self];
33 33
 	
34 34
 	return self;
35 35
 }
@@ -37,6 +37,7 @@
37 37
 -(void)viewWillAppear:(BOOL)animated{
38 38
 	[super viewWillAppear:animated];
39 39
 	[self.navigationOptions applyOn:self];
40
+	[self applyNavigationButtons];
40 41
 }
41 42
 
42 43
 - (BOOL)prefersStatusBarHidden {
@@ -58,6 +59,10 @@
58 59
 	[self.eventEmitter sendContainerDidDisappear:self.containerId];
59 60
 }
60 61
 
62
+-(void) applyNavigationButtons{
63
+	[self.navigationButtons applyLeftButtons:self.navigationOptions.leftButtons rightButtons:self.navigationOptions.rightButtons];
64
+}
65
+
61 66
 /**
62 67
  *	fix for #877, #878
63 68
  */

+ 11
- 0
lib/ios/RNNUIBarButtonItem.h View File

@@ -0,0 +1,11 @@
1
+#import <Foundation/Foundation.h>
2
+
3
+@interface RNNUIBarButtonItem : UIBarButtonItem
4
+
5
+@property (nonatomic, strong) NSString* buttonId;
6
+
7
+-(instancetype)init:(NSString*)buttonId withIcon:(UIImage*)iconImage;
8
+-(instancetype)init:(NSString*)buttonId withTitle:(NSString*)title;
9
+
10
+@end
11
+

+ 19
- 0
lib/ios/RNNUIBarButtonItem.m View File

@@ -0,0 +1,19 @@
1
+#import <Foundation/Foundation.h>
2
+#import <UIKit/UIKit.h>
3
+#import "RNNUIBarButtonItem.h"
4
+
5
+@implementation RNNUIBarButtonItem
6
+
7
+-(instancetype)init:(NSString*)buttonId withIcon:(UIImage*)iconImage {
8
+	self = [super initWithImage:iconImage style:UIBarButtonItemStylePlain target:nil action:nil];
9
+	self.buttonId = buttonId;
10
+	return self;
11
+}
12
+
13
+-(instancetype)init:(NSString*)buttonId withTitle:(NSString*)title {
14
+	self = [super initWithTitle:title style:UIBarButtonItemStylePlain target:nil action:nil];
15
+	self.buttonId = buttonId;
16
+	return self;
17
+}
18
+
19
+@end

+ 28
- 0
lib/ios/ReactNativeNavigation.xcodeproj/project.pbxproj View File

@@ -7,6 +7,10 @@
7 7
 	objects = {
8 8
 
9 9
 /* Begin PBXBuildFile section */
10
+		214545251F4DC125006E8DA1 /* RNNUIBarButtonItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 214545241F4DC125006E8DA1 /* RNNUIBarButtonItem.m */; };
11
+		2145452A1F4DC85F006E8DA1 /* RCTHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 214545291F4DC85F006E8DA1 /* RCTHelpers.m */; };
12
+		21B85E5D1F44480200B314B5 /* RNNNavigationButtons.m in Sources */ = {isa = PBXBuildFile; fileRef = 21B85E5C1F44480200B314B5 /* RNNNavigationButtons.m */; };
13
+		21B85E5F1F44482A00B314B5 /* RNNNavigationButtons.h in Headers */ = {isa = PBXBuildFile; fileRef = 21B85E5E1F44482A00B314B5 /* RNNNavigationButtons.h */; };
10 14
 		261F0E641E6EC94900989DE2 /* RNNModalManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 261F0E621E6EC94900989DE2 /* RNNModalManager.h */; };
11 15
 		261F0E651E6EC94900989DE2 /* RNNModalManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 261F0E631E6EC94900989DE2 /* RNNModalManager.m */; };
12 16
 		261F0E6A1E6F028A00989DE2 /* RNNNavigationStackManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 261F0E681E6F028A00989DE2 /* RNNNavigationStackManager.h */; };
@@ -118,6 +122,12 @@
118 122
 /* End PBXCopyFilesBuildPhase section */
119 123
 
120 124
 /* Begin PBXFileReference section */
125
+		214545241F4DC125006E8DA1 /* RNNUIBarButtonItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNUIBarButtonItem.m; sourceTree = "<group>"; };
126
+		214545261F4DC164006E8DA1 /* RNNUIBarButtonItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNUIBarButtonItem.h; sourceTree = "<group>"; };
127
+		214545281F4DC81F006E8DA1 /* RCTHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTHelpers.h; sourceTree = "<group>"; };
128
+		214545291F4DC85F006E8DA1 /* RCTHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTHelpers.m; sourceTree = "<group>"; };
129
+		21B85E5C1F44480200B314B5 /* RNNNavigationButtons.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNNavigationButtons.m; sourceTree = "<group>"; };
130
+		21B85E5E1F44482A00B314B5 /* RNNNavigationButtons.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNNavigationButtons.h; sourceTree = "<group>"; };
121 131
 		261F0E621E6EC94900989DE2 /* RNNModalManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNModalManager.h; sourceTree = "<group>"; };
122 132
 		261F0E631E6EC94900989DE2 /* RNNModalManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNModalManager.m; sourceTree = "<group>"; };
123 133
 		261F0E681E6F028A00989DE2 /* RNNNavigationStackManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNNavigationStackManager.h; sourceTree = "<group>"; };
@@ -244,6 +254,15 @@
244 254
 /* End PBXFrameworksBuildPhase section */
245 255
 
246 256
 /* Begin PBXGroup section */
257
+		214545271F4DC7ED006E8DA1 /* Helpers */ = {
258
+			isa = PBXGroup;
259
+			children = (
260
+				214545281F4DC81F006E8DA1 /* RCTHelpers.h */,
261
+				214545291F4DC85F006E8DA1 /* RCTHelpers.m */,
262
+			);
263
+			name = Helpers;
264
+			sourceTree = "<group>";
265
+		};
247 266
 		263905881E4C6F440023D7D3 /* RNNSideMenu */ = {
248 267
 			isa = PBXGroup;
249 268
 			children = (
@@ -333,6 +352,10 @@
333 352
 				263905E51E4CAC950023D7D3 /* RNNSideMenuChildVC.m */,
334 353
 				E83BAD691F27362500A9F3DD /* RNNNavigationOptions.h */,
335 354
 				E83BAD6A1F27363A00A9F3DD /* RNNNavigationOptions.m */,
355
+				21B85E5E1F44482A00B314B5 /* RNNNavigationButtons.h */,
356
+				21B85E5C1F44480200B314B5 /* RNNNavigationButtons.m */,
357
+				214545261F4DC164006E8DA1 /* RNNUIBarButtonItem.h */,
358
+				214545241F4DC125006E8DA1 /* RNNUIBarButtonItem.m */,
336 359
 			);
337 360
 			name = Controllers;
338 361
 			sourceTree = "<group>";
@@ -389,6 +412,7 @@
389 412
 		D8AFADB41BEE6F3F00A4592D = {
390 413
 			isa = PBXGroup;
391 414
 			children = (
415
+				214545271F4DC7ED006E8DA1 /* Helpers */,
392 416
 				7BA500731E2544B9001B9E1B /* ReactNativeNavigation.h */,
393 417
 				7BA500741E2544B9001B9E1B /* ReactNativeNavigation.m */,
394 418
 				268692801E5054F800E2C612 /* RNNStore.h */,
@@ -441,6 +465,7 @@
441 465
 				263905C01E4C6F440023D7D3 /* SidebarAirbnbAnimation.h in Headers */,
442 466
 				263905E61E4CAC950023D7D3 /* RNNSideMenuChildVC.h in Headers */,
443 467
 				263905C41E4C6F440023D7D3 /* SidebarFacebookAnimation.h in Headers */,
468
+				21B85E5F1F44482A00B314B5 /* RNNNavigationButtons.h in Headers */,
444 469
 				7BEF0D181E437684003E96B0 /* RNNRootViewController.h in Headers */,
445 470
 				7B1126A61E2D2B6C00F9B03B /* RNNBridgeModule.h in Headers */,
446 471
 				263905BE1E4C6F440023D7D3 /* RCCTheSideBarManagerViewController.h in Headers */,
@@ -582,9 +607,11 @@
582 607
 				7BA500781E254908001B9E1B /* RNNSplashScreen.m in Sources */,
583 608
 				263905BA1E4C6F440023D7D3 /* RCCDrawerController.m in Sources */,
584 609
 				263905BC1E4C6F440023D7D3 /* RCCDrawerHelper.m in Sources */,
610
+				2145452A1F4DC85F006E8DA1 /* RCTHelpers.m in Sources */,
585 611
 				263905C11E4C6F440023D7D3 /* SidebarAirbnbAnimation.m in Sources */,
586 612
 				263905D71E4C94970023D7D3 /* RNNSideMenuController.m in Sources */,
587 613
 				26916C991E4B9E7700D13680 /* RNNReactRootViewCreator.m in Sources */,
614
+				214545251F4DC125006E8DA1 /* RNNUIBarButtonItem.m in Sources */,
588 615
 				263905B81E4C6F440023D7D3 /* UIViewController+MMDrawerController.m in Sources */,
589 616
 				263905CD1E4C6F440023D7D3 /* SidebarWunderlistAnimation.m in Sources */,
590 617
 				263905CF1E4C6F440023D7D3 /* TheSidebarController.m in Sources */,
@@ -593,6 +620,7 @@
593 620
 				268692831E5054F800E2C612 /* RNNStore.m in Sources */,
594 621
 				7BC9346E1E26886E00EFA125 /* RNNControllerFactory.m in Sources */,
595 622
 				263905B61E4C6F440023D7D3 /* MMExampleDrawerVisualStateManager.m in Sources */,
623
+				21B85E5D1F44480200B314B5 /* RNNNavigationButtons.m in Sources */,
596 624
 				263905C91E4C6F440023D7D3 /* SidebarFlipboardAnimation.m in Sources */,
597 625
 			);
598 626
 			runOnlyForDeploymentPostprocessing = 0;

+ 62
- 0
lib/ios/ReactNativeNavigationTests/RNNRootViewControllerTest.m View File

@@ -4,6 +4,7 @@
4 4
 #import "RNNTestRootViewCreator.h"
5 5
 #import <React/RCTConvert.h>
6 6
 #import "RNNNavigationOptions.h"
7
+#import "RNNUIBarButtonItem.h"
7 8
 
8 9
 @interface RNNRootViewControllerTest : XCTestCase
9 10
 
@@ -224,6 +225,67 @@
224 225
 	//	XCTAssertThrows([self.uut viewWillAppear:false]);
225 226
 }
226 227
 
228
+-(void)testRightButtonsWithTitle_withoutStyle {
229
+	self.options.rightButtons = @[@{@"id": @"testId", @"title": @"test"}];
230
+	__unused UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:self.uut];
231
+	[self.uut viewWillAppear:false];
232
+	
233
+	RNNUIBarButtonItem* button = (RNNUIBarButtonItem*)[nav.topViewController.navigationItem.rightBarButtonItems objectAtIndex:0];
234
+	NSString* expectedButtonId = @"testId";
235
+	NSString* expectedTitle = @"test";
236
+	XCTAssertTrue([button.buttonId isEqualToString:expectedButtonId]);
237
+	XCTAssertTrue([button.title isEqualToString:expectedTitle]);
238
+	XCTAssertTrue(button.enabled);
239
+}
240
+
241
+-(void)testRightButtonsWithTitle_withStyle {
242
+	NSNumber* inputColor = @(0xFFFF0000);
243
+	
244
+	self.options.rightButtons = @[@{@"id": @"testId", @"title": @"test", @"disabled": @true, @"buttonColor": inputColor, @"buttonFontSize": @22, @"buttonFontWeight": @"800"}];
245
+	__unused UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:self.uut];
246
+	[self.uut viewWillAppear:false];
247
+	
248
+	RNNUIBarButtonItem* button = (RNNUIBarButtonItem*)[nav.topViewController.navigationItem.rightBarButtonItems objectAtIndex:0];
249
+	NSString* expectedButtonId = @"testId";
250
+	NSString* expectedTitle = @"test";
251
+	XCTAssertTrue([button.buttonId isEqualToString:expectedButtonId]);
252
+	XCTAssertTrue([button.title isEqualToString:expectedTitle]);
253
+	XCTAssertFalse(button.enabled);
254
+	
255
+	//TODO: Determine how to tests buttonColor,buttonFontSize and buttonFontWeight?
256
+}
257
+
258
+
259
+-(void)testLeftButtonsWithTitle_withoutStyle {
260
+	self.options.leftButtons = @[@{@"id": @"testId", @"title": @"test"}];
261
+	__unused UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:self.uut];
262
+	[self.uut viewWillAppear:false];
263
+	
264
+	RNNUIBarButtonItem* button = (RNNUIBarButtonItem*)[nav.topViewController.navigationItem.leftBarButtonItems objectAtIndex:0];
265
+	NSString* expectedButtonId = @"testId";
266
+	NSString* expectedTitle = @"test";
267
+	XCTAssertTrue([button.buttonId isEqualToString:expectedButtonId]);
268
+	XCTAssertTrue([button.title isEqualToString:expectedTitle]);
269
+	XCTAssertTrue(button.enabled);
270
+}
271
+
272
+-(void)testLeftButtonsWithTitle_withStyle {
273
+	NSNumber* inputColor = @(0xFFFF0000);
274
+	
275
+	self.options.leftButtons = @[@{@"id": @"testId", @"title": @"test", @"disabled": @true, @"buttonColor": inputColor, @"buttonFontSize": @22, @"buttonFontWeight": @"800"}];
276
+	__unused UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:self.uut];
277
+	[self.uut viewWillAppear:false];
278
+	
279
+	RNNUIBarButtonItem* button = (RNNUIBarButtonItem*)[nav.topViewController.navigationItem.leftBarButtonItems objectAtIndex:0];
280
+	NSString* expectedButtonId = @"testId";
281
+	NSString* expectedTitle = @"test";
282
+	XCTAssertTrue([button.buttonId isEqualToString:expectedButtonId]);
283
+	XCTAssertTrue([button.title isEqualToString:expectedTitle]);
284
+	XCTAssertFalse(button.enabled);
285
+	
286
+	//TODO: Determine how to tests buttonColor,buttonFontSize and buttonFontWeight?
287
+}
288
+
227 289
 -(void)testTopBarNoBorderOn {
228 290
 	NSNumber* topBarNoBorderInput = @(1);
229 291
 	self.options.topBarNoBorder = topBarNoBorderInput;

+ 4
- 0
lib/src/adapters/NativeEventsReceiver.js View File

@@ -16,6 +16,10 @@ class NativeEventsReceiver {
16 16
   appLaunched(callback) {
17 17
     this.emitter.addListener('RNN.appLaunched', callback);
18 18
   }
19
+
20
+  navigationButtonPressed(callback) {
21
+    this.emitter.addListener('RNN.navigationButtonPressed', callback);
22
+  }
19 23
 }
20 24
 
21 25
 module.exports = NativeEventsReceiver;

+ 5
- 1
lib/src/commands/OptionsProcessor.js View File

@@ -1,5 +1,6 @@
1 1
 const _ = require('lodash');
2 2
 const { processColor } = require('react-native');
3
+const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource');
3 4
 
4 5
 class OptionsProcessor {
5 6
   static processOptions(navigationOptions) {
@@ -7,7 +8,10 @@ class OptionsProcessor {
7 8
       if (_.endsWith(key, 'Color')) {
8 9
         navigationOptions[key] = processColor(value);
9 10
       }
10
-      if (_.isPlainObject(value)) {
11
+      if (_.isEqual(key, 'icon') || _.endsWith(key, 'Icon') || _.endsWith(key, 'Image')) {
12
+        navigationOptions[key] = resolveAssetSource(navigationOptions[key]);
13
+      }
14
+      if (_.isPlainObject(value) || _.isArray(value)) {
11 15
         OptionsProcessor.processOptions(value);
12 16
       }
13 17
     });

+ 16
- 0
lib/src/commands/OptionsProcessor.test.js View File

@@ -75,4 +75,20 @@ describe('navigation options', () => {
75 75
     expect(navigationOptions.innerObj.theKeyColor).toEqual(0xffff0000);
76 76
     expect(navigationOptions.innerObj.innerMostObj.anotherColor).toEqual(0xffffff00);
77 77
   });
78
+
79
+  it('resolve image sources with name/ending with icon', () => {
80
+    navigationOptions.icon = 'require("https://wix.github.io/react-native-navigation/_images/logo.png");';
81
+    navigationOptions.innerObj = {
82
+      myIcon: 'require("https://wix.github.io/react-native-navigation/_images/logo.png");',
83
+      myOtherValue: 'value'
84
+    };
85
+    OptionsProcessor.processOptions(navigationOptions);
86
+
87
+    // As we can't import external images and we don't want to add an image here
88
+    // I assign the icons to strings (what the require would generally look like)
89
+    // and expect the value to be resovled, in this case it doesn't find anything and returns null
90
+    expect(navigationOptions.icon).toEqual(null);
91
+    expect(navigationOptions.innerObj.myIcon).toEqual(null);
92
+    expect(navigationOptions.innerObj.myOtherValue).toEqual('value');
93
+  });
78 94
 });

+ 6
- 0
lib/src/containers/ContainerWrapper.js View File

@@ -46,6 +46,12 @@ class ContainerWrapper {
46 46
         }
47 47
       }
48 48
 
49
+      onNavigationButtonPressed(buttonId) {
50
+        if (this.originalContainerRef.onNavigationButtonPressed) {
51
+          this.originalContainerRef.onNavigationButtonPressed(buttonId);
52
+        }
53
+      }
54
+
49 55
       componentWillReceiveProps(nextProps) {
50 56
         this.setState({
51 57
           allProps: _.merge({}, nextProps, store.getPropsForContainerId(this.state.containerId))

+ 15
- 1
lib/src/containers/ContainerWrapper.test.js View File

@@ -140,6 +140,7 @@ describe('ContainerWrapper', () => {
140 140
   describe('container lifecycle', () => {
141 141
     const didAppearCallback = jest.fn();
142 142
     const didDisappearCallback = jest.fn();
143
+    const onNavigationButtonPressedCallback = jest.fn();
143 144
 
144 145
     class MyLifecycleContainer extends MyContainer {
145 146
       didAppear() {
@@ -149,13 +150,18 @@ describe('ContainerWrapper', () => {
149 150
       didDisappear() {
150 151
         didDisappearCallback();
151 152
       }
153
+
154
+      onNavigationButtonPressed() {
155
+        onNavigationButtonPressedCallback();
156
+      }
152 157
     }
153 158
 
154
-    it('didAppear and didDisappear are optional', () => {
159
+    it('didAppear, didDisappear and onNavigationButtonPressed are optional', () => {
155 160
       const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
156 161
       const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
157 162
       expect(() => tree.getInstance().didAppear()).not.toThrow();
158 163
       expect(() => tree.getInstance().didDisappear()).not.toThrow();
164
+      expect(() => tree.getInstance().onNavigationButtonPressed()).not.toThrow();
159 165
     });
160 166
 
161 167
     it('calls didAppear on OriginalContainer', () => {
@@ -173,5 +179,13 @@ describe('ContainerWrapper', () => {
173 179
       tree.getInstance().didDisappear();
174 180
       expect(didDisappearCallback).toHaveBeenCalledTimes(1);
175 181
     });
182
+
183
+    it('calls onNavigationButtonPressed on OriginalContainer', () => {
184
+      const NavigationContainer = ContainerWrapper.wrap(containerName, MyLifecycleContainer, store);
185
+      const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
186
+      expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(0);
187
+      tree.getInstance().onNavigationButtonPressed();
188
+      expect(onNavigationButtonPressedCallback).toHaveBeenCalledTimes(1);
189
+    });
176 190
   });
177 191
 });

+ 8
- 0
lib/src/containers/Lifecycle.js View File

@@ -3,6 +3,7 @@ class Lifecycle {
3 3
     this.store = store;
4 4
     this.containerDidAppear = this.containerDidAppear.bind(this);
5 5
     this.containerDidDisappear = this.containerDidDisappear.bind(this);
6
+    this.onNavigationButtonPressed = this.onNavigationButtonPressed.bind(this);
6 7
   }
7 8
 
8 9
   containerDidAppear(id) {
@@ -18,6 +19,13 @@ class Lifecycle {
18 19
       ref.didDisappear();
19 20
     }
20 21
   }
22
+
23
+  onNavigationButtonPressed(params) {
24
+    const ref = this.store.getRefForContainerId(params.containerId);
25
+    if (ref && ref.onNavigationButtonPressed) {
26
+      ref.onNavigationButtonPressed(params.buttonId);
27
+    }
28
+  }
21 29
 }
22 30
 
23 31
 module.exports = Lifecycle;

+ 24
- 1
lib/src/containers/Lifecycle.test.js View File

@@ -9,7 +9,8 @@ describe('Lifecycle', () => {
9 9
   beforeEach(() => {
10 10
     mockRef = {
11 11
       didAppear: jest.fn(),
12
-      didDisappear: jest.fn()
12
+      didDisappear: jest.fn(),
13
+      onNavigationButtonPressed: jest.fn()
13 14
     };
14 15
     store = new Store();
15 16
     store.setRefForContainerId('myUniqueId', mockRef);
@@ -54,4 +55,26 @@ describe('Lifecycle', () => {
54 55
       uut.containerDidDisappear('myUniqueId');
55 56
     });
56 57
   });
58
+
59
+  describe('onNavigationButtonPressed', () => {
60
+    it('calls onNavigationButtonPressed on container ref from store', () => {
61
+      uut.onNavigationButtonPressed({
62
+        containerId: 'myUniqueId',
63
+        buttonId: 'myButtonId'
64
+      });
65
+      expect(mockRef.onNavigationButtonPressed).toHaveBeenCalledTimes(1);
66
+    });
67
+
68
+    it('skips undefined refs', () => {
69
+      uut.onNavigationButtonPressed('myButtonId');
70
+      expect(mockRef.didDisappear).not.toHaveBeenCalled();
71
+    });
72
+
73
+    it('skips unimplemented onNavigationButtonPressed', () => {
74
+      mockRef = {};
75
+      expect(mockRef.onNavigationButtonPressed).toBeUndefined();
76
+      store.setRefForContainerId('myUniqueId', mockRef);
77
+      uut.containerDidAppear('myUniqueId');
78
+    });
79
+  });
57 80
 });

+ 1
- 0
lib/src/events/PrivateEventsListener.js View File

@@ -9,6 +9,7 @@ class PrivateEventsListener {
9 9
   listenAndHandlePrivateEvents() {
10 10
     this.nativeEventsReceiver.containerDidAppear(this.lifecycle.containerDidAppear);
11 11
     this.nativeEventsReceiver.containerDidDisappear(this.lifecycle.containerDidDisappear);
12
+    this.nativeEventsReceiver.navigationButtonPressed(this.lifecycle.onNavigationButtonPressed);
12 13
   }
13 14
 }
14 15
 

+ 16
- 0
lib/src/events/PrivateEventsListener.test.js View File

@@ -33,4 +33,20 @@ describe('PrivateEventsListener', () => {
33 33
     uut.listenAndHandlePrivateEvents();
34 34
     expect(nativeEventsReceiver.containerDidDisappear).toHaveBeenCalledTimes(1);
35 35
   });
36
+
37
+  it('register and handle onNavigationButtonPressed', () => {
38
+    const mockRef = {
39
+      onNavigationButtonPressed: jest.fn()
40
+    };
41
+    store.setRefForContainerId('myContainerId', mockRef);
42
+    uut.listenAndHandlePrivateEvents();
43
+    expect(nativeEventsReceiver.navigationButtonPressed).toHaveBeenCalledTimes(1);
44
+    const callbackFunction = nativeEventsReceiver.navigationButtonPressed.mock.calls[0][0];
45
+    expect(callbackFunction).toBeInstanceOf(Function);
46
+
47
+    expect(mockRef.onNavigationButtonPressed).not.toHaveBeenCalled();
48
+    callbackFunction({ containerId: 'myContainerId', buttonId: 'myButtonId' });
49
+
50
+    expect(mockRef.onNavigationButtonPressed).toHaveBeenCalledTimes(1);
51
+  });
36 52
 });

+ 4
- 0
playground/src/containers/LifecycleScreen.js View File

@@ -26,6 +26,10 @@ class LifecycleScreen extends Component {
26 26
     alert('componentWillUnmount'); // eslint-disable-line no-alert
27 27
   }
28 28
 
29
+  onNavigationButtonPressed(id) {
30
+    alert(`onNavigationButtonPressed: ${id}`); // eslint-disable-line no-alert
31
+  }
32
+
29 33
   render() {
30 34
     return (
31 35
       <View style={styles.root}>

+ 47
- 1
playground/src/containers/OptionsScreen.js View File

@@ -5,11 +5,28 @@ const { View, Text, Button } = require('react-native');
5 5
 
6 6
 const Navigation = require('react-native-navigation');
7 7
 
8
+const BUTTON_ONE = 'buttonOne';
9
+const BUTTON_TWO = 'buttonTwo';
10
+const BUTTON_LEFT = 'buttonLeft';
11
+
8 12
 class OptionsScreen extends Component {
13
+
9 14
   static get navigationOptions() {
10 15
     return {
11 16
       title: 'Static Title',
12
-      topBarTextFontFamily: 'HelveticaNeue-Italic'
17
+      topBarTextFontFamily: 'HelveticaNeue-Italic',
18
+      rightButtons: [{
19
+        id: BUTTON_ONE,
20
+        testID: BUTTON_ONE,
21
+        title: 'One',
22
+        buttonColor: 'red'
23
+      }],
24
+      leftButtons: [{
25
+        id: BUTTON_LEFT,
26
+        testID: BUTTON_LEFT,
27
+        title: 'Left',
28
+        buttonColor: 'purple'
29
+      }]
13 30
     };
14 31
   }
15 32
 
@@ -34,6 +51,35 @@ class OptionsScreen extends Component {
34 51
     );
35 52
   }
36 53
 
54
+  onNavigationButtonPressed(id) {
55
+    if (id === BUTTON_ONE) {
56
+      Navigation.setOptions(this.props.containerId, {
57
+        rightButtons: [{
58
+          id: BUTTON_TWO,
59
+          testID: BUTTON_TWO,
60
+          title: 'Two',
61
+          // icon: require('../../img/navicon_add.png'),
62
+          // disableIconTint: true,
63
+          // disabled: true
64
+          buttonColor: 'green',
65
+          buttonFontSize: 28,
66
+          buttonFontWeight: '800'
67
+        }]
68
+      });
69
+    } else if (id === BUTTON_TWO) {
70
+      Navigation.setOptions(this.props.containerId, {
71
+        rightButtons: [{
72
+          id: BUTTON_ONE,
73
+          testID: BUTTON_ONE,
74
+          title: 'One',
75
+          buttonColor: 'red'
76
+        }]
77
+      });
78
+    } else if (id === BUTTON_LEFT) {
79
+      Navigation.pop(this.props.containerId);
80
+    }
81
+  }
82
+
37 83
   onClickDynamicOptions() {
38 84
     Navigation.setOptions(this.props.containerId, {
39 85
       title: 'Dynamic Title',