Browse Source

setButtons for iOS (#1746)

Johan 7 years ago
parent
commit
5d1783cbcf

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

1
 const Utils = require('./Utils');
1
 const Utils = require('./Utils');
2
 
2
 
3
-const elementByLabel = Utils.elementByLabel;
3
+const { elementByLabel, elementById } = Utils;
4
 
4
 
5
 describe('screen style', () => {
5
 describe('screen style', () => {
6
   beforeEach(async () => {
6
   beforeEach(async () => {
50
     await elementByLabel('Set Tab Badge').tap();
50
     await elementByLabel('Set Tab Badge').tap();
51
     await expect(element(by.text('EnCyClOpEdIa'))).toBeVisible();
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
 module.exports = {
1
 module.exports = {
2
   elementByLabel: (label) => {
2
   elementByLabel: (label) => {
3
     return element(by.label(label));
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
 	private static final String onAppLaunched = "RNN.appLaunched";
11
 	private static final String onAppLaunched = "RNN.appLaunched";
12
 	private static final String containerDidAppear = "RNN.containerDidAppear";
12
 	private static final String containerDidAppear = "RNN.containerDidAppear";
13
 	private static final String containerDidDisappear = "RNN.containerDidDisappear";
13
 	private static final String containerDidDisappear = "RNN.containerDidDisappear";
14
+	private static final String onNavigationButtonPressed = "RNN.navigationButtonPressed";
14
 
15
 
15
 	private final RCTDeviceEventEmitter emitter;
16
 	private final RCTDeviceEventEmitter emitter;
16
 
17
 
30
 		emit(containerDidAppear, id);
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
 	private void emit(String eventName) {
39
 	private void emit(String eventName) {
34
 		emit(eventName, Arguments.createMap());
40
 		emit(eventName, Arguments.createMap());
35
 	}
41
 	}

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

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

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
 		RNNRootViewController* rootVc = (RNNRootViewController*)vc;
43
 		RNNRootViewController* rootVc = (RNNRootViewController*)vc;
44
 		[rootVc.navigationOptions mergeWith:options];
44
 		[rootVc.navigationOptions mergeWith:options];
45
 		[rootVc.navigationOptions applyOn:vc];
45
 		[rootVc.navigationOptions applyOn:vc];
46
+		[rootVc applyNavigationButtons];
46
 	}
47
 	}
47
 }
48
 }
48
 
49
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4
 #import "RNNTestRootViewCreator.h"
4
 #import "RNNTestRootViewCreator.h"
5
 #import <React/RCTConvert.h>
5
 #import <React/RCTConvert.h>
6
 #import "RNNNavigationOptions.h"
6
 #import "RNNNavigationOptions.h"
7
+#import "RNNUIBarButtonItem.h"
7
 
8
 
8
 @interface RNNRootViewControllerTest : XCTestCase
9
 @interface RNNRootViewControllerTest : XCTestCase
9
 
10
 
224
 	//	XCTAssertThrows([self.uut viewWillAppear:false]);
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
 -(void)testTopBarNoBorderOn {
289
 -(void)testTopBarNoBorderOn {
228
 	NSNumber* topBarNoBorderInput = @(1);
290
 	NSNumber* topBarNoBorderInput = @(1);
229
 	self.options.topBarNoBorder = topBarNoBorderInput;
291
 	self.options.topBarNoBorder = topBarNoBorderInput;

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

16
   appLaunched(callback) {
16
   appLaunched(callback) {
17
     this.emitter.addListener('RNN.appLaunched', callback);
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
 module.exports = NativeEventsReceiver;
25
 module.exports = NativeEventsReceiver;

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

1
 const _ = require('lodash');
1
 const _ = require('lodash');
2
 const { processColor } = require('react-native');
2
 const { processColor } = require('react-native');
3
+const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource');
3
 
4
 
4
 class OptionsProcessor {
5
 class OptionsProcessor {
5
   static processOptions(navigationOptions) {
6
   static processOptions(navigationOptions) {
7
       if (_.endsWith(key, 'Color')) {
8
       if (_.endsWith(key, 'Color')) {
8
         navigationOptions[key] = processColor(value);
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
         OptionsProcessor.processOptions(value);
15
         OptionsProcessor.processOptions(value);
12
       }
16
       }
13
     });
17
     });

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

75
     expect(navigationOptions.innerObj.theKeyColor).toEqual(0xffff0000);
75
     expect(navigationOptions.innerObj.theKeyColor).toEqual(0xffff0000);
76
     expect(navigationOptions.innerObj.innerMostObj.anotherColor).toEqual(0xffffff00);
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
         }
46
         }
47
       }
47
       }
48
 
48
 
49
+      onNavigationButtonPressed(buttonId) {
50
+        if (this.originalContainerRef.onNavigationButtonPressed) {
51
+          this.originalContainerRef.onNavigationButtonPressed(buttonId);
52
+        }
53
+      }
54
+
49
       componentWillReceiveProps(nextProps) {
55
       componentWillReceiveProps(nextProps) {
50
         this.setState({
56
         this.setState({
51
           allProps: _.merge({}, nextProps, store.getPropsForContainerId(this.state.containerId))
57
           allProps: _.merge({}, nextProps, store.getPropsForContainerId(this.state.containerId))

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

140
   describe('container lifecycle', () => {
140
   describe('container lifecycle', () => {
141
     const didAppearCallback = jest.fn();
141
     const didAppearCallback = jest.fn();
142
     const didDisappearCallback = jest.fn();
142
     const didDisappearCallback = jest.fn();
143
+    const onNavigationButtonPressedCallback = jest.fn();
143
 
144
 
144
     class MyLifecycleContainer extends MyContainer {
145
     class MyLifecycleContainer extends MyContainer {
145
       didAppear() {
146
       didAppear() {
149
       didDisappear() {
150
       didDisappear() {
150
         didDisappearCallback();
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
       const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
160
       const NavigationContainer = ContainerWrapper.wrap(containerName, MyContainer, store);
156
       const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
161
       const tree = renderer.create(<NavigationContainer containerId={'container1'} />);
157
       expect(() => tree.getInstance().didAppear()).not.toThrow();
162
       expect(() => tree.getInstance().didAppear()).not.toThrow();
158
       expect(() => tree.getInstance().didDisappear()).not.toThrow();
163
       expect(() => tree.getInstance().didDisappear()).not.toThrow();
164
+      expect(() => tree.getInstance().onNavigationButtonPressed()).not.toThrow();
159
     });
165
     });
160
 
166
 
161
     it('calls didAppear on OriginalContainer', () => {
167
     it('calls didAppear on OriginalContainer', () => {
173
       tree.getInstance().didDisappear();
179
       tree.getInstance().didDisappear();
174
       expect(didDisappearCallback).toHaveBeenCalledTimes(1);
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
     this.store = store;
3
     this.store = store;
4
     this.containerDidAppear = this.containerDidAppear.bind(this);
4
     this.containerDidAppear = this.containerDidAppear.bind(this);
5
     this.containerDidDisappear = this.containerDidDisappear.bind(this);
5
     this.containerDidDisappear = this.containerDidDisappear.bind(this);
6
+    this.onNavigationButtonPressed = this.onNavigationButtonPressed.bind(this);
6
   }
7
   }
7
 
8
 
8
   containerDidAppear(id) {
9
   containerDidAppear(id) {
18
       ref.didDisappear();
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
 module.exports = Lifecycle;
31
 module.exports = Lifecycle;

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

9
   beforeEach(() => {
9
   beforeEach(() => {
10
     mockRef = {
10
     mockRef = {
11
       didAppear: jest.fn(),
11
       didAppear: jest.fn(),
12
-      didDisappear: jest.fn()
12
+      didDisappear: jest.fn(),
13
+      onNavigationButtonPressed: jest.fn()
13
     };
14
     };
14
     store = new Store();
15
     store = new Store();
15
     store.setRefForContainerId('myUniqueId', mockRef);
16
     store.setRefForContainerId('myUniqueId', mockRef);
54
       uut.containerDidDisappear('myUniqueId');
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
   listenAndHandlePrivateEvents() {
9
   listenAndHandlePrivateEvents() {
10
     this.nativeEventsReceiver.containerDidAppear(this.lifecycle.containerDidAppear);
10
     this.nativeEventsReceiver.containerDidAppear(this.lifecycle.containerDidAppear);
11
     this.nativeEventsReceiver.containerDidDisappear(this.lifecycle.containerDidDisappear);
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
     uut.listenAndHandlePrivateEvents();
33
     uut.listenAndHandlePrivateEvents();
34
     expect(nativeEventsReceiver.containerDidDisappear).toHaveBeenCalledTimes(1);
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
     alert('componentWillUnmount'); // eslint-disable-line no-alert
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
   render() {
33
   render() {
30
     return (
34
     return (
31
       <View style={styles.root}>
35
       <View style={styles.root}>

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

5
 
5
 
6
 const Navigation = require('react-native-navigation');
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
 class OptionsScreen extends Component {
12
 class OptionsScreen extends Component {
13
+
9
   static get navigationOptions() {
14
   static get navigationOptions() {
10
     return {
15
     return {
11
       title: 'Static Title',
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
     );
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
   onClickDynamicOptions() {
83
   onClickDynamicOptions() {
38
     Navigation.setOptions(this.props.containerId, {
84
     Navigation.setOptions(this.props.containerId, {
39
       title: 'Dynamic Title',
85
       title: 'Dynamic Title',