Browse Source

Smarter set root (#3251)

* setRoot({root:{}})

* support setRoot with root, modals, overlays

* Update setRoot calls in playground app to new api

* Update setRoot on Android to new api

currently only supporting root node

* Update setRoot on iOS to new api

* fix unit tests
Yogev B 6 years ago
parent
commit
f3efebe8ca
No account linked to committer's email address

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java View File

@@ -55,7 +55,7 @@ public class NavigationModule extends ReactContextBaseJavaModule {
55 55
 
56 56
 	@ReactMethod
57 57
 	public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise) {
58
-		final LayoutNode layoutTree = LayoutNodeParser.parse(new JSONObject(rawLayoutTree.toHashMap()));
58
+		final LayoutNode layoutTree = LayoutNodeParser.parse(new JSONObject(rawLayoutTree.getMap("root").toHashMap()));
59 59
 		handle(() -> {
60 60
             final ViewController viewController = newLayoutFactory().create(layoutTree);
61 61
             navigator().setRoot(viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));

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

@@ -48,7 +48,7 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
48 48
 	[_modalManager dismissAllModals];
49 49
 	[_eventEmitter sendOnNavigationCommand:setRoot params:@{@"layout": layout}];
50 50
 	
51
-	UIViewController *vc = [_controllerFactory createLayoutAndSaveToStore:layout];
51
+	UIViewController *vc = [_controllerFactory createLayoutAndSaveToStore:layout[@"root"]];
52 52
 	
53 53
 	UIApplication.sharedApplication.delegate.window.rootViewController = vc;
54 54
 	[UIApplication.sharedApplication.delegate.window makeKeyAndVisible];

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

@@ -100,6 +100,8 @@
100 100
 		507F44201FFA8A8800D9425B /* RNNRootViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 507F441F1FFA8A8800D9425B /* RNNRootViewProtocol.h */; };
101 101
 		50A00C37200F84D6000F01A6 /* RNNOverlayOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 50A00C35200F84D6000F01A6 /* RNNOverlayOptions.h */; };
102 102
 		50A00C38200F84D6000F01A6 /* RNNOverlayOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A00C36200F84D6000F01A6 /* RNNOverlayOptions.m */; };
103
+		50BE951220B5A787004F5DF5 /* RNNStatusBarOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 50BE951020B5A787004F5DF5 /* RNNStatusBarOptions.m */; };
104
+		50BE951320B5A787004F5DF5 /* RNNStatusBarOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 50BE951120B5A787004F5DF5 /* RNNStatusBarOptions.h */; };
103 105
 		50C4A496206BDDBB00DB292E /* RNNSubtitleOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 50C4A494206BDDBB00DB292E /* RNNSubtitleOptions.h */; };
104 106
 		50C4A497206BDDBB00DB292E /* RNNSubtitleOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 50C4A495206BDDBB00DB292E /* RNNSubtitleOptions.m */; };
105 107
 		50CB3B691FDE911400AA153B /* RNNSideMenuOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 50CB3B671FDE911400AA153B /* RNNSideMenuOptions.h */; };
@@ -295,6 +297,8 @@
295 297
 		507F441F1FFA8A8800D9425B /* RNNRootViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNRootViewProtocol.h; sourceTree = "<group>"; };
296 298
 		50A00C35200F84D6000F01A6 /* RNNOverlayOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNOverlayOptions.h; sourceTree = "<group>"; };
297 299
 		50A00C36200F84D6000F01A6 /* RNNOverlayOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNOverlayOptions.m; sourceTree = "<group>"; };
300
+		50BE951020B5A787004F5DF5 /* RNNStatusBarOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNStatusBarOptions.m; sourceTree = "<group>"; };
301
+		50BE951120B5A787004F5DF5 /* RNNStatusBarOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNStatusBarOptions.h; sourceTree = "<group>"; };
298 302
 		50C4A494206BDDBB00DB292E /* RNNSubtitleOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNSubtitleOptions.h; sourceTree = "<group>"; };
299 303
 		50C4A495206BDDBB00DB292E /* RNNSubtitleOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNSubtitleOptions.m; sourceTree = "<group>"; };
300 304
 		50CB3B671FDE911400AA153B /* RNNSideMenuOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNSideMenuOptions.h; sourceTree = "<group>"; };
@@ -509,6 +513,8 @@
509 513
 				50570B252061473D006A1B5C /* RNNTitleOptions.m */,
510 514
 				50C4A494206BDDBB00DB292E /* RNNSubtitleOptions.h */,
511 515
 				50C4A495206BDDBB00DB292E /* RNNSubtitleOptions.m */,
516
+				50BE951120B5A787004F5DF5 /* RNNStatusBarOptions.h */,
517
+				50BE951020B5A787004F5DF5 /* RNNStatusBarOptions.m */,
512 518
 				50EB4ED52068EBE000D6ED34 /* RNNBackgroundOptions.h */,
513 519
 				50EB4ED62068EBE000D6ED34 /* RNNBackgroundOptions.m */,
514 520
 				A7626BFF1FC578AB00492FB8 /* RNNBottomTabsOptions.h */,
@@ -783,6 +789,7 @@
783 789
 				268692821E5054F800E2C612 /* RNNStore.h in Headers */,
784 790
 				E8AEDB3C1F55A1C2000F5A6A /* RNNElementView.h in Headers */,
785 791
 				E8E518321F83B3E0000467AC /* RNNUtils.h in Headers */,
792
+				50BE951320B5A787004F5DF5 /* RNNStatusBarOptions.h in Headers */,
786 793
 				507F44201FFA8A8800D9425B /* RNNRootViewProtocol.h in Headers */,
787 794
 				50570BEA2063E09B006A1B5C /* RNNTitleViewHelper.h in Headers */,
788 795
 				263905CA1E4C6F440023D7D3 /* SidebarLuvocracyAnimation.h in Headers */,
@@ -933,6 +940,7 @@
933 940
 				E8DA24411F97459B00CD552B /* RNNElementFinder.m in Sources */,
934 941
 				50570B272061473D006A1B5C /* RNNTitleOptions.m in Sources */,
935 942
 				263905BF1E4C6F440023D7D3 /* RCCTheSideBarManagerViewController.m in Sources */,
943
+				50BE951220B5A787004F5DF5 /* RNNStatusBarOptions.m in Sources */,
936 944
 				7B1126A01E2D263F00F9B03B /* RNNEventEmitter.m in Sources */,
937 945
 				A7626BFD1FC2FB2C00492FB8 /* RNNTopBarOptions.m in Sources */,
938 946
 				263905CB1E4C6F440023D7D3 /* SidebarLuvocracyAnimation.m in Sources */,

+ 6
- 6
lib/ios/ReactNativeNavigationTests/RNNRootViewControllerTest.m View File

@@ -70,7 +70,7 @@
70 70
 }
71 71
 
72 72
 - (void)testStatusBarHidden_true {
73
-	self.options.statusBarHidden = @(1);
73
+	self.options.statusBar.hidden = @(1);
74 74
 	__unused RNNNavigationController* nav = [[RNNNavigationController alloc] initWithRootViewController:self.uut];
75 75
 	[self.uut viewWillAppear:false];
76 76
 	
@@ -78,7 +78,7 @@
78 78
 }
79 79
 
80 80
 - (void)testStatusBarHideWithTopBar_false {
81
-	self.options.statusBarHideWithTopBar = @(0);
81
+	self.options.statusBar.hideWithTopBar = @(0);
82 82
 	self.options.topBar.visible = @(0);
83 83
 	__unused UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:self.uut];
84 84
 	[self.uut viewWillAppear:false];
@@ -87,7 +87,7 @@
87 87
 }
88 88
 
89 89
 - (void)testStatusBarHideWithTopBar_true {
90
-	self.options.statusBarHideWithTopBar = @(1);
90
+	self.options.statusBar.hideWithTopBar = @(1);
91 91
 	self.options.topBar.visible = @(0);
92 92
 	__unused UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:self.uut];
93 93
 	[self.uut viewWillAppear:false];
@@ -97,7 +97,7 @@
97 97
 
98 98
 
99 99
 - (void)testStatusBarHidden_false {
100
-	self.options.statusBarHidden = @(0);
100
+	self.options.statusBar.hidden = @(0);
101 101
 	__unused RNNNavigationController* nav = [[RNNNavigationController alloc] initWithRootViewController:self.uut];
102 102
 	[self.uut viewWillAppear:false];
103 103
 	
@@ -448,7 +448,7 @@
448 448
 
449 449
 -(void)testStatusBarBlurOn {
450 450
 	NSNumber* statusBarBlurInput = @(1);
451
-	self.options.statusBarBlur = statusBarBlurInput;
451
+	self.options.statusBar.blur = statusBarBlurInput;
452 452
 	__unused UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:self.uut];
453 453
 	[self.uut viewWillAppear:false];
454 454
 	XCTAssertNotNil([self.uut.view viewWithTag:BLUR_STATUS_TAG]);
@@ -456,7 +456,7 @@
456 456
 
457 457
 -(void)testStatusBarBlurOff {
458 458
 	NSNumber* statusBarBlurInput = @(0);
459
-	self.options.statusBarBlur = statusBarBlurInput;
459
+	self.options.statusBar.blur = statusBarBlurInput;
460 460
 	__unused UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:self.uut];
461 461
 	[self.uut viewWillAppear:false];
462 462
 	XCTAssertNil([self.uut.view viewWithTag:BLUR_STATUS_TAG]);

+ 2
- 2
lib/src/adapters/NativeCommandsSender.ts View File

@@ -6,8 +6,8 @@ export class NativeCommandsSender {
6 6
     this.nativeCommandsModule = NativeModules.RNNBridgeModule;
7 7
   }
8 8
 
9
-  setRoot(commandId: string, layoutTree: object) {
10
-    return this.nativeCommandsModule.setRoot(commandId, layoutTree);
9
+  setRoot(commandId: string, layout: { root: any, modals: any[], overlays: any[] }) {
10
+    return this.nativeCommandsModule.setRoot(commandId, layout);
11 11
   }
12 12
 
13 13
   setDefaultOptions(options: object) {

+ 81
- 16
lib/src/commands/Commands.test.ts View File

@@ -30,26 +30,32 @@ describe('Commands', () => {
30 30
   describe('setRoot', () => {
31 31
     it('sends setRoot to native after parsing into a correct layout tree', () => {
32 32
       uut.setRoot({
33
-        component: {
34
-          name: 'com.example.MyScreen'
33
+        root: {
34
+          component: {
35
+            name: 'com.example.MyScreen'
36
+          }
35 37
         }
36 38
       });
37 39
       expect(mockCommandsSender.setRoot).toHaveBeenCalledTimes(1);
38 40
       expect(mockCommandsSender.setRoot).toHaveBeenCalledWith('setRoot+UNIQUE_ID', {
39
-        type: 'Component',
40
-        id: 'Component+UNIQUE_ID',
41
-        children: [],
42
-        data: {
43
-          name: 'com.example.MyScreen',
44
-          options: {}
45
-        }
41
+        root: {
42
+          type: 'Component',
43
+          id: 'Component+UNIQUE_ID',
44
+          children: [],
45
+          data: {
46
+            name: 'com.example.MyScreen',
47
+            options: {}
48
+          }
49
+        },
50
+        modals: [],
51
+        overlays: []
46 52
       });
47 53
     });
48 54
 
49 55
     it('deep clones input to avoid mutation errors', () => {
50 56
       const obj = {};
51
-      uut.setRoot({ component: { name: 'bla', inner: obj } });
52
-      expect(mockCommandsSender.setRoot.mock.calls[0][1].data.inner).not.toBe(obj);
57
+      uut.setRoot({ root: { component: { name: 'bla', inner: obj } } });
58
+      expect(mockCommandsSender.setRoot.mock.calls[0][1].root.data.inner).not.toBe(obj);
53 59
     });
54 60
 
55 61
     it('passProps into components', () => {
@@ -57,16 +63,75 @@ describe('Commands', () => {
57 63
         fn: () => 'Hello'
58 64
       };
59 65
       expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual({});
60
-      uut.setRoot({ component: { name: 'asd', passProps } });
66
+      uut.setRoot({ root: { component: { name: 'asd', passProps } } });
61 67
       expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual(passProps);
62 68
       expect(store.getPropsForId('Component+UNIQUE_ID').fn()).toEqual('Hello');
63 69
     });
64 70
 
65 71
     it('returns a promise with the resolved layout', async () => {
66 72
       mockCommandsSender.setRoot.mockReturnValue(Promise.resolve('the resolved layout'));
67
-      const result = await uut.setRoot({ component: { name: 'com.example.MyScreen' } });
73
+      const result = await uut.setRoot({ root: { component: { name: 'com.example.MyScreen' } } });
68 74
       expect(result).toEqual('the resolved layout');
69 75
     });
76
+
77
+    it('inputs modals and overlays', () => {
78
+      uut.setRoot({
79
+        root: {
80
+          component: {
81
+            name: 'com.example.MyScreen'
82
+          }
83
+        },
84
+        modals: [
85
+          {
86
+            component: {
87
+              name: 'com.example.MyModal'
88
+            }
89
+          }
90
+        ],
91
+        overlays: [
92
+          {
93
+            component: {
94
+              name: 'com.example.MyOverlay'
95
+            }
96
+          }
97
+        ]
98
+      });
99
+      expect(mockCommandsSender.setRoot).toHaveBeenCalledTimes(1);
100
+      expect(mockCommandsSender.setRoot).toHaveBeenCalledWith('setRoot+UNIQUE_ID', {
101
+        root:
102
+          {
103
+            type: 'Component',
104
+            id: 'Component+UNIQUE_ID',
105
+            children: [],
106
+            data: {
107
+              name: 'com.example.MyScreen',
108
+              options: {}
109
+            }
110
+          },
111
+        modals: [
112
+          {
113
+            type: 'Component',
114
+            id: 'Component+UNIQUE_ID',
115
+            children: [],
116
+            data: {
117
+              name: 'com.example.MyModal',
118
+              options: {}
119
+            }
120
+          }
121
+        ],
122
+        overlays: [
123
+          {
124
+            type: 'Component',
125
+            id: 'Component+UNIQUE_ID',
126
+            children: [],
127
+            data: {
128
+              name: 'com.example.MyOverlay',
129
+              options: {}
130
+            }
131
+          }
132
+        ]
133
+      });
134
+    });
70 135
   });
71 136
 
72 137
   describe('mergeOptions', () => {
@@ -352,7 +417,7 @@ describe('Commands', () => {
352 417
 
353 418
     it('notify on all commands', () => {
354 419
       _.forEach(getAllMethodsOfUut(), (m) => {
355
-        uut[m]();
420
+        uut[m]({});
356 421
       });
357 422
       expect(cb).toHaveBeenCalledTimes(getAllMethodsOfUut().length);
358 423
     });
@@ -374,12 +439,12 @@ describe('Commands', () => {
374 439
         dismissOverlay: ['id'],
375 440
       };
376 441
       const paramsForMethodName = {
377
-        setRoot: { commandId: 'setRoot+UNIQUE_ID', layout: 'parsed' },
442
+        setRoot: { commandId: 'setRoot+UNIQUE_ID', layout: { root: 'parsed', modals: [], overlays: [] } },
378 443
         setDefaultOptions: { options: {} },
379 444
         mergeOptions: { componentId: 'id', options: {} },
380 445
         showModal: { commandId: 'showModal+UNIQUE_ID', layout: 'parsed' },
381 446
         dismissModal: { commandId: 'dismissModal+UNIQUE_ID', componentId: 'id' },
382
-        dismissAllModals: {commandId: 'dismissAllModals+UNIQUE_ID'},
447
+        dismissAllModals: { commandId: 'dismissAllModals+UNIQUE_ID' },
383 448
         push: { commandId: 'push+UNIQUE_ID', componentId: 'id', layout: 'parsed' },
384 449
         pop: { commandId: 'pop+UNIQUE_ID', componentId: 'id', options: {} },
385 450
         popTo: { commandId: 'popTo+UNIQUE_ID', componentId: 'id' },

+ 16
- 4
lib/src/commands/Commands.ts View File

@@ -14,12 +14,24 @@ export class Commands {
14 14
 
15 15
   public setRoot(simpleApi) {
16 16
     const input = _.cloneDeep(simpleApi);
17
-    const layout = this.layoutTreeParser.parse(input);
18
-    this.layoutTreeCrawler.crawl(layout);
17
+    const root = this.layoutTreeParser.parse(input.root);
18
+    this.layoutTreeCrawler.crawl(root);
19
+
20
+    const modals = _.map(input.modals, (modal) => {
21
+      const modalLayout = this.layoutTreeParser.parse(modal);
22
+      this.layoutTreeCrawler.crawl(modalLayout);
23
+      return modalLayout;
24
+    });
25
+
26
+    const overlays = _.map(input.overlays, (overlay) => {
27
+      const overlayLayout = this.layoutTreeParser.parse(overlay);
28
+      this.layoutTreeCrawler.crawl(overlayLayout);
29
+      return overlayLayout;
30
+    });
19 31
 
20 32
     const commandId = this.uniqueIdProvider.generate('setRoot');
21
-    const result = this.nativeCommandsSender.setRoot(commandId, layout);
22
-    this.commandsObserver.notify('setRoot', { commandId, layout });
33
+    const result = this.nativeCommandsSender.setRoot(commandId, { root, modals, overlays });
34
+    this.commandsObserver.notify('setRoot', { commandId, layout: { root, modals, overlays } });
23 35
     return result;
24 36
   }
25 37
 

+ 10
- 8
playground/src/app.js View File

@@ -149,15 +149,17 @@ function start() {
149 149
     });
150 150
 
151 151
     Navigation.setRoot({
152
-      stack: {
153
-        id: 'TEST',
154
-        children: [
155
-          {
156
-            component: {
157
-              name: 'navigation.playground.WelcomeScreen'
152
+      root: {
153
+        stack: {
154
+          id: 'TEST',
155
+          children: [
156
+            {
157
+              component: {
158
+                name: 'navigation.playground.WelcomeScreen'
159
+              }
158 160
             }
159
-          }
160
-        ]
161
+          ]
162
+        }
161 163
       }
162 164
     });
163 165
   });

+ 133
- 129
playground/src/screens/WelcomeScreen.js View File

@@ -46,71 +46,73 @@ class WelcomeScreen extends Component {
46 46
 
47 47
   onClickSwitchToTabs = () => {
48 48
     Navigation.setRoot({
49
-      bottomTabs: {
50
-        children: [
51
-          {
52
-            stack: {
53
-              id: 'TAB1_ID',
54
-              children: [
55
-                {
56
-                  component: {
57
-                    name: 'navigation.playground.TextScreen',
58
-                    passProps: {
59
-                      text: 'This is tab 1',
60
-                      myFunction: () => 'Hello from a function!'
61
-                    },
62
-                    options: {
63
-                      topBar: {
64
-                        visible: true,
65
-                        title: {
66
-                          text: 'React Native Navigation!'
49
+      root: {
50
+        bottomTabs: {
51
+          children: [
52
+            {
53
+              stack: {
54
+                id: 'TAB1_ID',
55
+                children: [
56
+                  {
57
+                    component: {
58
+                      name: 'navigation.playground.TextScreen',
59
+                      passProps: {
60
+                        text: 'This is tab 1',
61
+                        myFunction: () => 'Hello from a function!'
62
+                      },
63
+                      options: {
64
+                        topBar: {
65
+                          visible: true,
66
+                          title: {
67
+                            text: 'React Native Navigation!'
68
+                          }
67 69
                         }
68 70
                       }
69 71
                     }
70 72
                   }
71
-                }
72
-              ],
73
-              options: {
74
-                bottomTab: {
75
-                  title: 'Tab 1',
76
-                  icon: require('../images/one.png'),
77
-                  testID: testIDs.FIRST_TAB_BAR_BUTTON
78
-                },
79
-                topBar: {
80
-                  visible: false
73
+                ],
74
+                options: {
75
+                  bottomTab: {
76
+                    title: 'Tab 1',
77
+                    icon: require('../images/one.png'),
78
+                    testID: testIDs.FIRST_TAB_BAR_BUTTON
79
+                  },
80
+                  topBar: {
81
+                    visible: false
82
+                  }
81 83
                 }
82 84
               }
83
-            }
84
-          },
85
-          {
86
-            stack: {
87
-              children: [
88
-                {
89
-                  component: {
90
-                    name: 'navigation.playground.TextScreen',
91
-                    passProps: {
92
-                      text: 'This is tab 2'
85
+            },
86
+            {
87
+              stack: {
88
+                children: [
89
+                  {
90
+                    component: {
91
+                      name: 'navigation.playground.TextScreen',
92
+                      passProps: {
93
+                        text: 'This is tab 2'
94
+                      }
93 95
                     }
94 96
                   }
95
-                }
96
-              ],
97
-              options: {
98
-                bottomTab: {
99
-                  title: 'Tab 2',
100
-                  icon: require('../images/two.png'),
101
-                  testID: testIDs.SECOND_TAB_BAR_BUTTON
97
+                ],
98
+                options: {
99
+                  bottomTab: {
100
+                    title: 'Tab 2',
101
+                    icon: require('../images/two.png'),
102
+                    testID: testIDs.SECOND_TAB_BAR_BUTTON
103
+                  }
102 104
                 }
103 105
               }
104 106
             }
105
-          }
106
-        ],
107
-        options: {
108
-          bottomTabs: {
109
-            tabColor: 'red',
110
-            selectedTabColor: 'blue',
111
-            fontFamily: 'HelveticaNeue-Italic',
112
-            fontSize: 13,
113
-            testID: testIDs.BOTTOM_TABS_ELEMENT
107
+          ],
108
+          options: {
109
+            bottomTabs: {
110
+              tabColor: 'red',
111
+              selectedTabColor: 'blue',
112
+              fontFamily: 'HelveticaNeue-Italic',
113
+              fontSize: 13,
114
+              testID: testIDs.BOTTOM_TABS_ELEMENT
115
+            }
114 116
           }
115 117
         }
116 118
       }
@@ -119,97 +121,99 @@ class WelcomeScreen extends Component {
119 121
 
120 122
   onClickSwitchToSideMenus = () => {
121 123
     Navigation.setRoot({
122
-      sideMenu: {
123
-        left: {
124
-          component: {
125
-            name: 'navigation.playground.SideMenuScreen',
126
-            passProps: {
127
-              side: 'left'
124
+      root: {
125
+        sideMenu: {
126
+          left: {
127
+            component: {
128
+              name: 'navigation.playground.SideMenuScreen',
129
+              passProps: {
130
+                side: 'left'
131
+              }
128 132
             }
129
-          }
130
-        },
131
-        center: {
132
-          bottomTabs: {
133
-            children: [
134
-              {
135
-                stack: {
136
-                  children: [
137
-                    {
138
-                      component: {
139
-                        name: 'navigation.playground.TextScreen',
140
-                        passProps: {
141
-                          text: 'This is a side menu center screen tab 1'
133
+          },
134
+          center: {
135
+            bottomTabs: {
136
+              children: [
137
+                {
138
+                  stack: {
139
+                    children: [
140
+                      {
141
+                        component: {
142
+                          name: 'navigation.playground.TextScreen',
143
+                          passProps: {
144
+                            text: 'This is a side menu center screen tab 1'
145
+                          }
142 146
                         }
143 147
                       }
144
-                    }
145
-                  ],
146
-                  options: {
147
-                    bottomTab: {
148
-                      title: 'Tab 1',
149
-                      icon: require('../images/one.png'),
150
-                      testID: testIDs.FIRST_TAB_BAR_BUTTON
148
+                    ],
149
+                    options: {
150
+                      bottomTab: {
151
+                        title: 'Tab 1',
152
+                        icon: require('../images/one.png'),
153
+                        testID: testIDs.FIRST_TAB_BAR_BUTTON
154
+                      }
151 155
                     }
152 156
                   }
153
-                }
154
-              },
155
-              {
156
-                stack: {
157
-                  children: [
158
-                    {
159
-                      component: {
160
-                        name: 'navigation.playground.TextScreen',
161
-                        passProps: {
162
-                          text: 'This is a side menu center screen tab 2'
157
+                },
158
+                {
159
+                  stack: {
160
+                    children: [
161
+                      {
162
+                        component: {
163
+                          name: 'navigation.playground.TextScreen',
164
+                          passProps: {
165
+                            text: 'This is a side menu center screen tab 2'
166
+                          }
163 167
                         }
164 168
                       }
165
-                    }
166
-                  ],
167
-                  options: {
168
-                    bottomTab: {
169
-                      title: 'Tab 2',
170
-                      icon: require('../images/two.png'),
171
-                      testID: testIDs.SECOND_TAB_BAR_BUTTON
169
+                    ],
170
+                    options: {
171
+                      bottomTab: {
172
+                        title: 'Tab 2',
173
+                        icon: require('../images/two.png'),
174
+                        testID: testIDs.SECOND_TAB_BAR_BUTTON
175
+                      }
172 176
                     }
173 177
                   }
174
-                }
175
-              },
176
-              {
177
-                stack: {
178
-                  children: [
179
-                    {
180
-                      component: {
181
-                        name: 'navigation.playground.TextScreen',
182
-                        passProps: {
183
-                          text: 'This is a side menu center screen tab 3'
178
+                },
179
+                {
180
+                  stack: {
181
+                    children: [
182
+                      {
183
+                        component: {
184
+                          name: 'navigation.playground.TextScreen',
185
+                          passProps: {
186
+                            text: 'This is a side menu center screen tab 3'
187
+                          }
184 188
                         }
185 189
                       }
186
-                    }
187
-                  ],
188
-                  options: {
189
-                    bottomTab: {
190
-                      title: 'Tab 3',
191
-                      icon: require('../images/three.png'),
192
-                      testID: testIDs.SECOND_TAB_BAR_BUTTON
190
+                    ],
191
+                    options: {
192
+                      bottomTab: {
193
+                        title: 'Tab 3',
194
+                        icon: require('../images/three.png'),
195
+                        testID: testIDs.SECOND_TAB_BAR_BUTTON
196
+                      }
193 197
                     }
194 198
                   }
195 199
                 }
196
-              }
197
-            ],
198
-            options: {
199
-              bottomTabs: {
200
-                tabColor: 'red',
201
-                selectedTabColor: 'blue',
202
-                fontFamily: 'HelveticaNeue-Italic',
203
-                fontSize: 13
200
+              ],
201
+              options: {
202
+                bottomTabs: {
203
+                  tabColor: 'red',
204
+                  selectedTabColor: 'blue',
205
+                  fontFamily: 'HelveticaNeue-Italic',
206
+                  fontSize: 13
207
+                }
204 208
               }
205 209
             }
206
-          }
207
-        },
208
-        right: {
209
-          component: {
210
-            name: 'navigation.playground.SideMenuScreen',
211
-            passProps: {
212
-              side: 'right'
210
+          },
211
+          right: {
212
+            component: {
213
+              name: 'navigation.playground.SideMenuScreen',
214
+              passProps: {
215
+                side: 'right'
216
+              }
213 217
             }
214 218
           }
215 219
         }