Ver código fonte

[v2] Preview peek and pop (iOS) (#3273)

* V2: Peek and pop

* Add event when preview is dismissed in non commit mode

* tslint

* Refactor playground. Document preview api

* Added width prop

* Implement hybrid push/preview button

* Fixing small linting errors
Birkir Rafn Guðjónsson 6 anos atrás
pai
commit
7c422f7fa1

+ 3
- 2
docs/README.md Ver arquivo

@@ -61,7 +61,7 @@ v2 is written in Test Driven Development. We have a test for every feature inclu
61 61
 | sideMenu             |    ✅  |    ✅ |
62 62
 | tabs            |    ✅  |    ✅ |
63 63
 | External Component       |   ✅  |   ✅ |
64
-| splitView           |   [Contribute](/docs/WorkingLocally.md)   |   [Contribute](/docs/WorkingLocally.md) |
64
+| splitView           |      |   [Contribute](/docs/WorkingLocally.md) |
65 65
 
66 66
 ### Screen API
67 67
 
@@ -79,6 +79,7 @@ v2 is written in Test Driven Development. We have a test for every feature inclu
79 79
 | customTransition            |✅|✅|
80 80
 | Screen Visibility        | ✅     |✅|
81 81
 | async commands (await push)     |  ✅        |✅   |
82
+| preview              |   ✅       |	:x:		|
82 83
 
83 84
 ### Navigation Options
84 85
 
@@ -212,4 +213,4 @@ Note:  v1 properties with names beginning with 'navBar' are replaced in v2 with
212 213
 | switchToTab         |    ✅    |       ✅   |✅|
213 214
 | topBar react component        |   ✅     |✅|✅|
214 215
 |Shared Element Transition|     :x:  |✅| [Contribute](/docs/WorkingLocally.md)|
215
-| splitViewScreen       |     :x:  |    [Contribute](/docs/WorkingLocally.md)      | [Contribute](/docs/WorkingLocally.md)|
216
+| splitViewScreen       |     :x:  |          | [Contribute](/docs/WorkingLocally.md)|

+ 12
- 0
docs/docs/styling.md Ver arquivo

@@ -132,6 +132,18 @@ Navigation.mergeOptions(this.props.componentId, {
132 132
   },
133 133
   overlay: {
134 134
     interceptTouchOutside: true
135
+  },
136
+  preview: {
137
+    elementId: 'PreviewId',
138
+    width: 100,
139
+    height: 100,
140
+    commit: false,
141
+    actions: [{
142
+      id: 'ActionId1',
143
+      title: 'Action title',
144
+      style: 'selected', // default, selected, destructive,
145
+      actions: [/* ... */]
146
+    }]
135 147
   }  
136 148
 }
137 149
 ```

+ 38
- 5
lib/ios/RNNCommandsHandler.m Ver arquivo

@@ -5,11 +5,14 @@
5 5
 #import "RNNNavigationOptions.h"
6 6
 #import "RNNRootViewController.h"
7 7
 #import "RNNSplitViewController.h"
8
+#import "RNNElementFinder.h"
8 9
 #import "React/RCTUIManager.h"
9 10
 
11
+
10 12
 static NSString* const setRoot	= @"setRoot";
11 13
 static NSString* const setStackRoot	= @"setStackRoot";
12 14
 static NSString* const push	= @"push";
15
+static NSString* const preview	= @"preview";
13 16
 static NSString* const pop	= @"pop";
14 17
 static NSString* const popTo	= @"popTo";
15 18
 static NSString* const popToRoot	= @"popToRoot";
@@ -93,11 +96,41 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
93 96
 
94 97
 -(void)push:(NSString*)componentId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
95 98
 	[self assertReady];
96
-	[_eventEmitter sendOnNavigationCommand:push params:@{@"componentId": componentId}];
97
-	UIViewController<RNNRootViewProtocol> *newVc = [_controllerFactory createLayoutAndSaveToStore:layout];
98
-	[_navigationStackManager push:newVc onTop:componentId completion:^{
99
-		completion();
100
-	} rejection:rejection];
99
+
100
+	UIViewController<RNNRootViewProtocol, UIViewControllerPreviewingDelegate> *newVc = [_controllerFactory createLayoutAndSaveToStore:layout];
101
+
102
+	if (newVc.options.preview.elementId) {
103
+		UIViewController* vc = [_store findComponentForId:componentId];
104
+
105
+		if([vc isKindOfClass:[RNNRootViewController class]]) {
106
+			RNNRootViewController* rootVc = (RNNRootViewController*)vc;
107
+			rootVc.previewController = newVc;
108
+
109
+			RNNElementFinder* elementFinder = [[RNNElementFinder alloc] initWithFromVC:vc];
110
+			RNNElementView* elementView = [elementFinder findElementForId:newVc.options.preview.elementId];
111
+
112
+			CGSize size = CGSizeMake(rootVc.view.frame.size.width, rootVc.view.frame.size.height);
113
+			
114
+			if (newVc.options.preview.width) {
115
+				size.width = [newVc.options.preview.width floatValue];
116
+			}
117
+
118
+			if (newVc.options.preview.height) {
119
+				size.height = [newVc.options.preview.height floatValue];
120
+			}
121
+
122
+			if (newVc.options.preview.width || newVc.options.preview.height) {
123
+				newVc.preferredContentSize = size;
124
+			}
125
+
126
+			[rootVc registerForPreviewingWithDelegate:(id)rootVc sourceView:elementView];
127
+		}
128
+	} else {
129
+		[_eventEmitter sendOnNavigationCommand:push params:@{@"componentId": componentId}];
130
+		[_navigationStackManager push:newVc onTop:componentId completion:^{
131
+			completion();
132
+		} rejection:rejection];
133
+	}
101 134
 }
102 135
 
103 136
 -(void)setStackRoot:(NSString*)componentId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {

+ 1
- 1
lib/ios/RNNControllerFactory.h Ver arquivo

@@ -13,7 +13,7 @@
13 13
 						  eventEmitter:(RNNEventEmitter*)eventEmitter
14 14
 							 andBridge:(RCTBridge*)bridge;
15 15
 
16
--(UIViewController<RNNRootViewProtocol> *)createLayoutAndSaveToStore:(NSDictionary*)layout;
16
+-(UIViewController<RNNRootViewProtocol, UIViewControllerPreviewingDelegate> *)createLayoutAndSaveToStore:(NSDictionary*)layout;
17 17
 
18 18
 - (UIViewController<RNNRootViewProtocol> *)createOverlay:(NSDictionary*)layout;
19 19
 

+ 1
- 0
lib/ios/RNNElementFinder.h Ver arquivo

@@ -4,6 +4,7 @@
4 4
 @interface RNNElementFinder : NSObject
5 5
 
6 6
 - (instancetype)initWithToVC:(UIViewController *)toVC andfromVC:(UIViewController *)fromVC;
7
+- (instancetype)initWithFromVC:(UIViewController *)fromVC;
7 8
 
8 9
 - (RNNElementView *)findElementForId:(NSString *)elementId;
9 10
 

+ 8
- 0
lib/ios/RNNElementFinder.m Ver arquivo

@@ -18,6 +18,14 @@
18 18
 	return self;
19 19
 }
20 20
 
21
+- (instancetype)initWithFromVC:(UIViewController *)fromVC {
22
+	self = [super init];
23
+
24
+	self.fromVCTransitionElements = [self findRNNElementViews:fromVC.view];
25
+	
26
+	return self;
27
+}
28
+
21 29
 - (RNNElementView *)findViewToAnimate:(NSArray *)RNNTransitionElementViews withId:(NSString *)elementId{
22 30
 	for (RNNElementView* view in RNNTransitionElementViews) {
23 31
 		if ([view.elementId isEqualToString:elementId]){

+ 2
- 0
lib/ios/RNNNavigationOptions.h Ver arquivo

@@ -9,6 +9,7 @@
9 9
 #import "RNNAnimationOptions.h"
10 10
 #import "RNNTransitionsOptions.h"
11 11
 #import "RNNStatusBarOptions.h"
12
+#import "RNNPreviewOptions.h"
12 13
 #import "RNNLayoutOptions.h"
13 14
 
14 15
 extern const NSInteger BLUR_TOPBAR_TAG;
@@ -26,6 +27,7 @@ extern const NSInteger TOP_BAR_TRANSPARENT_TAG;
26 27
 @property (nonatomic, strong) RNNAnimationOptions* customTransition;
27 28
 @property (nonatomic, strong) RNNTransitionsOptions* animations;
28 29
 @property (nonatomic, strong) RNNStatusBarOptions* statusBar;
30
+@property (nonatomic, strong) RNNPreviewOptions* preview;
29 31
 @property (nonatomic, strong) RNNLayoutOptions* layout;
30 32
 
31 33
 @property (nonatomic, strong) NSMutableDictionary* originalTopBarImages;

+ 11
- 0
lib/ios/RNNPreviewOptions.h Ver arquivo

@@ -0,0 +1,11 @@
1
+#import "RNNOptions.h"
2
+
3
+@interface RNNPreviewOptions : RNNOptions
4
+
5
+@property (nonatomic, strong) NSString* elementId;
6
+@property (nonatomic, strong) NSNumber* width;
7
+@property (nonatomic, strong) NSNumber* height;
8
+@property (nonatomic, strong) NSNumber* commit;
9
+@property (nonatomic, strong) NSArray* actions;
10
+
11
+@end

+ 12
- 0
lib/ios/RNNPreviewOptions.m Ver arquivo

@@ -0,0 +1,12 @@
1
+#import "RNNPreviewOptions.h"
2
+
3
+@implementation RNNPreviewOptions
4
+
5
+- (instancetype)initWithDict:(NSDictionary *)dict {
6
+
7
+	self = [super initWithDict:dict];
8
+
9
+	return self;
10
+}
11
+
12
+@end

+ 3
- 1
lib/ios/RNNRootViewController.h Ver arquivo

@@ -9,7 +9,7 @@
9 9
 #import "RNNTopTabsViewController.h"
10 10
 #import "RNNRootViewProtocol.h"
11 11
 
12
-@interface RNNRootViewController : UIViewController	<RNNRootViewProtocol>
12
+@interface RNNRootViewController : UIViewController	<RNNRootViewProtocol, UIViewControllerPreviewingDelegate>
13 13
 
14 14
 @property (nonatomic, strong) RNNNavigationOptions* options;
15 15
 @property (nonatomic, strong) RNNEventEmitter *eventEmitter;
@@ -17,6 +17,8 @@
17 17
 @property (nonatomic, strong) RNNTopTabsViewController* topTabsViewController;
18 18
 @property (nonatomic) id<RNNRootViewCreator> creator;
19 19
 @property (nonatomic, strong) RNNAnimator* animator;
20
+@property (nonatomic, strong) UIViewController* previewController;
21
+
20 22
 
21 23
 -(instancetype)initWithName:(NSString*)name
22 24
 				withOptions:(RNNNavigationOptions*)options

+ 64
- 0
lib/ios/RNNRootViewController.m Ver arquivo

@@ -238,6 +238,70 @@
238 238
 	}
239 239
 }
240 240
 
241
+- (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location{
242
+	if (self.previewController) {
243
+		RNNRootViewController * vc = (RNNRootViewController*) self.previewController;
244
+		[_eventEmitter sendOnNavigationEvent:@"previewContext" params:@{
245
+																   @"previewComponentId": vc.componentId,
246
+																   @"componentId": self.componentId
247
+																   }];
248
+	}
249
+	return self.previewController;
250
+}
251
+
252
+
253
+- (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
254
+	RNNRootViewController * vc = (RNNRootViewController*) self.previewController;
255
+	NSDictionary * params = @{
256
+						  @"previewComponentId": vc.componentId,
257
+						  @"componentId": self.componentId
258
+						  };
259
+	if (vc.options.preview.commit) {
260
+		[_eventEmitter sendOnNavigationEvent:@"previewCommit" params:params];
261
+		[self.navigationController pushViewController:vc animated:false];
262
+	} else {
263
+		[_eventEmitter sendOnNavigationEvent:@"previewDismissed" params:params];
264
+	}
265
+}
266
+
267
+- (void)onActionPress:(NSString *)id {
268
+	[_eventEmitter sendOnNavigationButtonPressed:self.componentId buttonId:id];
269
+}
270
+
271
+- (UIPreviewAction *) convertAction:(NSDictionary *)action {
272
+	NSString *actionId = action[@"id"];
273
+	NSString *actionTitle = action[@"title"];
274
+	UIPreviewActionStyle actionStyle = UIPreviewActionStyleDefault;
275
+	if ([action[@"style"] isEqualToString:@"selected"]) {
276
+	   	actionStyle = UIPreviewActionStyleSelected;
277
+	} else if ([action[@"style"] isEqualToString:@"destructive"]) {
278
+		actionStyle = UIPreviewActionStyleDestructive;
279
+	}
280
+	
281
+	return [UIPreviewAction actionWithTitle:actionTitle style:actionStyle handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
282
+		[self onActionPress:actionId];
283
+	}];
284
+}
285
+
286
+- (NSArray<id<UIPreviewActionItem>> *)previewActionItems {
287
+	NSMutableArray *actions = [[NSMutableArray alloc] init];
288
+	for (NSDictionary *previewAction in self.options.preview.actions) {
289
+		UIPreviewAction *action = [self convertAction:previewAction];
290
+		NSDictionary *actionActions = previewAction[@"actions"];
291
+	    if (actionActions.count > 0) {
292
+			NSMutableArray *group = [[NSMutableArray alloc] init];
293
+	     	for (NSDictionary *previewGroupAction in actionActions) {
294
+	        	[group addObject:[self convertAction:previewGroupAction]];
295
+	      	}
296
+	      	UIPreviewActionGroup *actionGroup = [UIPreviewActionGroup actionGroupWithTitle:action.title style:UIPreviewActionStyleDefault actions:group];
297
+	      	[actions addObject:actionGroup];
298
+		} else {
299
+	    	[actions addObject:action];
300
+	    }
301
+	}
302
+	return actions;
303
+}
304
+
241 305
 /**
242 306
  *	fix for #877, #878
243 307
  */

+ 6
- 1
lib/ios/ReactNativeNavigation.xcodeproj/project.pbxproj Ver arquivo

@@ -156,9 +156,9 @@
156 156
 		A7626BFD1FC2FB2C00492FB8 /* RNNTopBarOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = A7626BFC1FC2FB2C00492FB8 /* RNNTopBarOptions.m */; };
157 157
 		A7626C011FC5796200492FB8 /* RNNBottomTabsOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = A7626C001FC5796200492FB8 /* RNNBottomTabsOptions.m */; };
158 158
 		E33AC20020B5BA0B0090DB8A /* RNNSplitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E33AC1FF20B5BA0B0090DB8A /* RNNSplitViewController.m */; };
159
-		E33AC20420B5C3890090DB8A /* RNNStatusBarOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = E33AC20220B5C3880090DB8A /* RNNStatusBarOptions.h */; };
160 159
 		E33AC20520B5C3890090DB8A /* RNNStatusBarOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = E33AC20320B5C3890090DB8A /* RNNStatusBarOptions.m */; };
161 160
 		E33AC20820B5C4F90090DB8A /* RNNSplitViewOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = E33AC20720B5C4F90090DB8A /* RNNSplitViewOptions.m */; };
161
+		E3458D3E20BD9CE40023149B /* RNNPreviewOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = E3458D3D20BD9CE40023149B /* RNNPreviewOptions.m */; };
162 162
 		E8367B801F7A8A4700675C05 /* VICMAImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = E8367B7E1F7A8A4700675C05 /* VICMAImageView.h */; };
163 163
 		E8367B811F7A8A4700675C05 /* VICMAImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = E8367B7F1F7A8A4700675C05 /* VICMAImageView.m */; };
164 164
 		E83BAD681F2734B500A9F3DD /* RNNNavigationOptionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E83BAD671F2734B500A9F3DD /* RNNNavigationOptionsTest.m */; };
@@ -369,6 +369,8 @@
369 369
 		E33AC20320B5C3890090DB8A /* RNNStatusBarOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNStatusBarOptions.m; sourceTree = "<group>"; };
370 370
 		E33AC20620B5C49E0090DB8A /* RNNSplitViewOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNSplitViewOptions.h; sourceTree = "<group>"; };
371 371
 		E33AC20720B5C4F90090DB8A /* RNNSplitViewOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNSplitViewOptions.m; sourceTree = "<group>"; };
372
+		E3458D3C20BD9CA10023149B /* RNNPreviewOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNPreviewOptions.h; sourceTree = "<group>"; };
373
+		E3458D3D20BD9CE40023149B /* RNNPreviewOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNPreviewOptions.m; sourceTree = "<group>"; };
372 374
 		E8367B7E1F7A8A4700675C05 /* VICMAImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VICMAImageView.h; sourceTree = "<group>"; };
373 375
 		E8367B7F1F7A8A4700675C05 /* VICMAImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VICMAImageView.m; sourceTree = "<group>"; };
374 376
 		E83BAD671F2734B500A9F3DD /* RNNNavigationOptionsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNNavigationOptionsTest.m; sourceTree = "<group>"; };
@@ -557,6 +559,8 @@
557 559
 				50415CB920553B8E00BB682E /* RNNScreenTransition.m */,
558 560
 				E33AC20620B5C49E0090DB8A /* RNNSplitViewOptions.h */,
559 561
 				E33AC20720B5C4F90090DB8A /* RNNSplitViewOptions.m */,
562
+				E3458D3C20BD9CA10023149B /* RNNPreviewOptions.h */,
563
+				E3458D3D20BD9CE40023149B /* RNNPreviewOptions.m */,
560 564
 			);
561 565
 			name = Options;
562 566
 			sourceTree = "<group>";
@@ -1025,6 +1029,7 @@
1025 1029
 				50F5DFC61F407AA0001A00BC /* RNNNavigationController.m in Sources */,
1026 1030
 				21B85E5D1F44480200B314B5 /* RNNNavigationButtons.m in Sources */,
1027 1031
 				E8E518371F83B94A000467AC /* RNNViewLocation.m in Sources */,
1032
+				E3458D3E20BD9CE40023149B /* RNNPreviewOptions.m in Sources */,
1028 1033
 				263905C91E4C6F440023D7D3 /* SidebarFlipboardAnimation.m in Sources */,
1029 1034
 			);
1030 1035
 			runOnlyForDeploymentPostprocessing = 0;

+ 112
- 0
playground/src/screens/Button.js Ver arquivo

@@ -0,0 +1,112 @@
1
+const React = require('react');
2
+const { Component } = require('react');
3
+const PropTypes = require('prop-types');
4
+const { Platform, ColorPropType, StyleSheet, TouchableNativeFeedback, TouchableOpacity, View, Text } = require('react-native');
5
+
6
+class Button extends Component {
7
+
8
+    static propTypes = {
9
+    title: PropTypes.string.isRequired,
10
+    accessibilityLabel: PropTypes.string,
11
+    color: ColorPropType,
12
+    disabled: PropTypes.bool,
13
+    hasTVPreferredFocus: PropTypes.bool,
14
+    onPress: PropTypes.func,
15
+    onPressIn: PropTypes.func,
16
+    testID: PropTypes.string,
17
+  };
18
+
19
+  render() {
20
+    const {
21
+      accessibilityLabel,
22
+      color,
23
+      onPress,
24
+      onPressIn,
25
+      title,
26
+      hasTVPreferredFocus,
27
+      disabled,
28
+      testID,
29
+    } = this.props;
30
+    const buttonStyles = [styles.button];
31
+    const textStyles = [styles.text];
32
+
33
+    if (color) {
34
+      if (Platform.OS === 'ios') {
35
+        textStyles.push({color: color});
36
+      } else {
37
+        buttonStyles.push({backgroundColor: color});
38
+      }
39
+    }
40
+
41
+    const accessibilityTraits = ['button'];
42
+    if (disabled) {
43
+      buttonStyles.push(styles.buttonDisabled);
44
+      textStyles.push(styles.textDisabled);
45
+      accessibilityTraits.push('disabled');
46
+    }
47
+
48
+    const formattedTitle =
49
+      Platform.OS === 'android' ? title.toUpperCase() : title;
50
+    const Touchable =
51
+      Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity;
52
+    return (
53
+      <Touchable
54
+        accessibilityComponentType='button'
55
+        accessibilityLabel={accessibilityLabel}
56
+        accessibilityTraits={accessibilityTraits}
57
+        hasTVPreferredFocus={hasTVPreferredFocus}
58
+        testID={testID}
59
+        disabled={disabled}
60
+        onPress={onPress}
61
+        onPressIn={onPressIn}>
62
+        <View style={buttonStyles}>
63
+          <Text style={textStyles} disabled={disabled}>
64
+            {formattedTitle}
65
+          </Text>
66
+        </View>
67
+      </Touchable>
68
+    );
69
+  }
70
+}
71
+
72
+const styles = StyleSheet.create({
73
+  button: Platform.select({
74
+    ios: {},
75
+    android: {
76
+      elevation: 4,
77
+      backgroundColor: '#2196F3',
78
+      borderRadius: 2,
79
+    },
80
+  }),
81
+  text: Platform.select({
82
+    ios: {
83
+      color: '#007AFF',
84
+      textAlign: 'center',
85
+      padding: 8,
86
+      fontSize: 18,
87
+    },
88
+    android: {
89
+      color: 'white',
90
+      textAlign: 'center',
91
+      padding: 8,
92
+      fontWeight: '500',
93
+    },
94
+  }),
95
+  buttonDisabled: Platform.select({
96
+    ios: {},
97
+    android: {
98
+      elevation: 0,
99
+      backgroundColor: '#dfdfdf',
100
+    },
101
+  }),
102
+  textDisabled: Platform.select({
103
+    ios: {
104
+      color: '#cdcdcd',
105
+    },
106
+    android: {
107
+      color: '#a1a1a1',
108
+    },
109
+  }),
110
+});
111
+
112
+module.exports = Button;

+ 72
- 1
playground/src/screens/PushedScreen.js Ver arquivo

@@ -3,9 +3,10 @@ const _ = require('lodash');
3 3
 const React = require('react');
4 4
 const { Component } = require('react');
5 5
 
6
-const { View, Text, Button } = require('react-native');
6
+const { View, Text, Platform } = require('react-native');
7 7
 
8 8
 const { Navigation } = require('react-native-navigation');
9
+const Button = require('./Button');
9 10
 const testIDs = require('../testIDs');
10 11
 
11 12
 class PushedScreen extends Component {
@@ -29,6 +30,34 @@ class PushedScreen extends Component {
29 30
     this.onClickPopToFirstPosition = this.onClickPopToFirstPosition.bind(this);
30 31
     this.onClickPopToRoot = this.onClickPopToRoot.bind(this);
31 32
     this.onClickSetStackRoot = this.onClickSetStackRoot.bind(this);
33
+    this.state = { disabled: false };
34
+  }
35
+
36
+  listeners = [];
37
+
38
+  componentWillMount() {
39
+    this.listeners.push(
40
+      Navigation.events().registerNativeEventListener((name, params) => {
41
+        if (name === 'previewContext') {
42
+          const { previewComponentId } = params;
43
+          this.setState({ previewComponentId });
44
+        }
45
+      }),
46
+      Navigation.events().registerComponentDidAppearListener((componentId, componentName) => {
47
+        if (this.state.previewComponentId === componentId) {
48
+          this.setState({ disabled: true });
49
+        }
50
+      }),
51
+      Navigation.events().registerComponentDidDisappearListener((componentId, componentName) => {
52
+        if (this.state.previewComponentId === componentId) {
53
+          this.setState({ disabled: false });
54
+        }
55
+      })
56
+    );
57
+  }
58
+
59
+  componentWillUnmount() {
60
+    this.listeners.forEach(listener => listener.remove && listener.remove());
32 61
   }
33 62
 
34 63
   render() {
@@ -38,6 +67,11 @@ class PushedScreen extends Component {
38 67
         <Text testID={testIDs.PUSHED_SCREEN_HEADER} style={styles.h1}>{`Pushed Screen`}</Text>
39 68
         <Text style={styles.h2}>{`Stack Position: ${stackPosition}`}</Text>
40 69
         <Button title='Push' testID={testIDs.PUSH_BUTTON} onPress={this.onClickPush} />
70
+          {Platform.OS === 'ios' && (
71
+            <Navigation.Element elementId='PreviewElement'>
72
+              <Button testID={testIDs.SHOW_PREVIEW_BUTTON} onPress={this.onClickPush} onPressIn={this.onClickShowPreview} title='Push Preview' />
73
+            </Navigation.Element>
74
+          )}
41 75
         <Button title='Pop' testID={testIDs.POP_BUTTON} onPress={this.onClickPop} />
42 76
         <Button title='Pop Previous' testID={testIDs.POP_PREVIOUS_BUTTON} onPress={this.onClickPopPrevious} />
43 77
         <Button title='Pop To Root' testID={testIDs.POP_TO_ROOT} onPress={this.onClickPopToRoot} />
@@ -48,7 +82,44 @@ class PushedScreen extends Component {
48 82
     );
49 83
   }
50 84
 
85
+  onClickShowPreview = async () => {
86
+    await Navigation.push(this.props.componentId, {
87
+      component: {
88
+        name: 'navigation.playground.PushedScreen',
89
+        passProps: {
90
+          stackPosition: this.getStackPosition() + 1,
91
+          previousScreenIds: _.concat([], this.props.previousScreenIds || [], this.props.componentId)
92
+        },
93
+        options: {
94
+          topBar: {
95
+            title: {
96
+              text: `Pushed ${this.getStackPosition() + 1}`
97
+            }
98
+          },
99
+          animations: {
100
+            push: {
101
+              enable: false
102
+            }
103
+          },
104
+          preview: {
105
+            elementId: 'PreviewElement',
106
+            height: 400,
107
+            commit: true,
108
+            actions: [{
109
+              id: 'action-cancel',
110
+              title: 'Cancel'
111
+            }]
112
+          }
113
+        }
114
+      }
115
+    });
116
+  }
117
+
51 118
   async onClickPush() {
119
+    if (this.state.disabled) {
120
+      return;
121
+    }
122
+
52 123
     await Navigation.push(this.props.componentId, {
53 124
       component: {
54 125
         name: 'navigation.playground.PushedScreen',

+ 39
- 1
playground/src/screens/WelcomeScreen.js Ver arquivo

@@ -1,8 +1,9 @@
1 1
 const React = require('react');
2 2
 const { Component } = require('react');
3
-const { View, Text, Button, Platform } = require('react-native');
3
+const { View, Text, Platform, TouchableHighlight } = require('react-native');
4 4
 
5 5
 const testIDs = require('../testIDs');
6
+const Button = require('./Button');
6 7
 
7 8
 const { Navigation } = require('react-native-navigation');
8 9
 
@@ -38,6 +39,11 @@ class WelcomeScreen extends Component {
38 39
           <Button title='Push Lifecycle Screen' testID={testIDs.PUSH_LIFECYCLE_BUTTON} onPress={this.onClickLifecycleScreen} />
39 40
           <Button title='Static Lifecycle Events' testID={testIDs.PUSH_STATIC_LIFECYCLE_BUTTON} onPress={this.onClickShowStaticLifecycleOverlay} />
40 41
           <Button title='Push' testID={testIDs.PUSH_BUTTON} onPress={this.onClickPush} />
42
+          {Platform.OS === 'ios' && (
43
+            <Navigation.Element elementId='PreviewElement'>
44
+              <Button testID={testIDs.SHOW_PREVIEW_BUTTON} onPressIn={this.onClickShowPreview} title='Push Preview' />
45
+            </Navigation.Element>
46
+          )}
41 47
           <Button title='Push Options Screen' testID={testIDs.PUSH_OPTIONS_BUTTON} onPress={this.onClickPushOptionsScreen} />
42 48
           <Button title='Push External Component' testID={testIDs.PUSH_EXTERNAL_COMPONENT_BUTTON} onPress={this.onClickPushExternalComponent} />
43 49
           {Platform.OS === 'android' && <Button title='Push Top Tabs screen' testID={testIDs.PUSH_TOP_TABS_BUTTON} onPress={this.onClickPushTopTabsScreen} />}
@@ -312,6 +318,38 @@ class WelcomeScreen extends Component {
312 318
     undefined();
313 319
   }
314 320
 
321
+  onClickShowPreview = async () => {
322
+    await Navigation.push(this.props.componentId, {
323
+      component: {
324
+        name: 'navigation.playground.PushedScreen',
325
+        options: {
326
+          animations: {
327
+            push: {
328
+              enable: false
329
+            }
330
+          },
331
+          preview: {
332
+            elementId: 'PreviewElement',
333
+            height: 300,
334
+            commit: true,
335
+            actions: [{
336
+              id: 'action-cancel',
337
+              title: 'Cancel'
338
+            }, {
339
+              id: 'action-delete',
340
+              title: 'Delete',
341
+              actions: [{
342
+                id: 'action-delete-sure',
343
+                title: 'Are you sure?',
344
+                style: 'destructive'
345
+              }]
346
+            }]
347
+          }
348
+        }
349
+      }
350
+    });
351
+  }
352
+
315 353
   onClickPushOptionsScreen = () => {
316 354
     Navigation.push(this.props.componentId, {
317 355
       component: {

+ 1
- 0
playground/src/testIDs.js Ver arquivo

@@ -61,6 +61,7 @@ module.exports = {
61 61
   SET_STACK_ROOT_BUTTON: `SET_STACK_ROOT_BUTTON`,
62 62
   MODAL_LIFECYCLE_BUTTON: `MODAL_LIFECYCLE_BUTTON`,
63 63
   SPLIT_VIEW_BUTTON: `SPLIT_VIEW_BUTTON`,
64
+  SHOW_PREVIEW_BUTTON: `SHOW_PREVIEW_BUTTON`,
64 65
 
65 66
   // Elements
66 67
   SCROLLVIEW_ELEMENT: `SCROLLVIEW_ELEMENT`,