Daniel Zlotin 6 years ago
parent
commit
c7d7f89525
93 changed files with 1287 additions and 522 deletions
  1. 2
    1
      Jenkinsfile
  2. 1
    1
      docs/_sidebar.md
  3. 3
    3
      docs/docs/Component.md
  4. 32
    33
      docs/docs/Navigation.md
  5. 2
    2
      docs/docs/Root.md
  6. 2
    2
      docs/docs/SideMenu.md
  7. 42
    9
      docs/docs/usage.md
  8. 18
    18
      docs/docs/v1tov2diff.md
  9. 4
    3
      e2e/CustomTransition.js
  10. 11
    16
      e2e/Orientations.test.js
  11. 1
    1
      e2e/ScreenStack.test.js
  12. 3
    11
      e2e/ScreenStyle.test.js
  13. 2
    2
      e2e/TopLevelApi.test.js
  14. 2
    1
      lib/android/app/build.gradle
  15. 48
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/BottomTabOptions.java
  16. 26
    16
      lib/android/app/src/main/java/com/reactnativenavigation/parse/BottomTabsOptions.java
  17. 3
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Button.java
  18. 8
    4
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java
  19. 5
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java
  20. 2
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java
  21. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopTabsOptions.java
  22. 25
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/BottomTabOptionsPresenter.java
  23. 1
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java
  24. 15
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ArrayUtils.java
  25. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/utils/CompatUtils.java
  26. 4
    4
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ImageLoader.java
  27. 14
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java
  28. 87
    41
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/BottomTabsController.java
  29. 2
    3
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java
  30. 12
    56
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ModalStack.java
  31. 103
    102
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java
  32. 13
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java
  33. 4
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/SideMenuController.java
  34. 3
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java
  35. 7
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  36. 55
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/Modal.java
  37. 9
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalCreator.java
  38. 1
    3
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java
  39. 32
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/BottomTabs.java
  40. 50
    16
      lib/android/app/src/main/java/com/reactnativenavigation/views/TitleBarButton.java
  41. 4
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java
  42. 49
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/ImageLoaderMock.java
  43. 14
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/ModalCreatorMock.java
  44. 2
    3
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java
  45. 10
    12
      lib/android/app/src/test/java/com/reactnativenavigation/parse/NavigationOptionsTest.java
  46. 13
    0
      lib/android/app/src/test/java/com/reactnativenavigation/utils/OptionHelper.java
  47. 48
    29
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/BottomTabsControllerTest.java
  48. 1
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ComponentViewControllerTest.java
  49. 57
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ModalStackTest.java
  50. 55
    21
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java
  51. 2
    2
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java
  52. 10
    9
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java
  53. 26
    13
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java
  54. 0
    25
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsControllerMock.java
  55. 1
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java
  56. 5
    4
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java
  57. 2
    0
      lib/ios/RNNBottomTabsOptions.h
  58. 4
    1
      lib/ios/RNNControllerFactory.m
  59. 7
    0
      lib/ios/RNNCustomTitleView.h
  60. 47
    0
      lib/ios/RNNCustomTitleView.m
  61. 5
    1
      lib/ios/RNNNavigationButtons.m
  62. 4
    0
      lib/ios/RNNNavigationController.m
  63. 1
    0
      lib/ios/RNNRootViewController.h
  64. 22
    1
      lib/ios/RNNRootViewController.m
  65. 3
    0
      lib/ios/RNNRootViewProtocol.h
  66. 4
    0
      lib/ios/RNNTabBarController.m
  67. 3
    0
      lib/ios/RNNTopBarOptions.h
  68. 1
    0
      lib/ios/RNNTopBarOptions.m
  69. 4
    1
      lib/ios/RNNUIBarButtonItem.h
  70. 16
    0
      lib/ios/RNNUIBarButtonItem.m
  71. 8
    0
      lib/ios/ReactNativeNavigation.xcodeproj/project.pbxproj
  72. 7
    2
      lib/src/commands/LayoutTreeCrawler.ts
  73. 13
    0
      package.json
  74. 9
    0
      playground/android/app/build.gradle
  75. 24
    0
      playground/android/app/src/androidTest/java/com/reactnativenavigation/playground/DetoxTest.java
  76. 3
    0
      playground/android/settings.gradle
  77. BIN
      playground/src/images/one@2x.png
  78. BIN
      playground/src/images/one_selected@2x.png
  79. BIN
      playground/src/images/three@2x.png
  80. BIN
      playground/src/images/three_selected@2x.png
  81. BIN
      playground/src/images/two@2x.png
  82. BIN
      playground/src/images/two_selected@2x.png
  83. 47
    0
      playground/src/screens/CustomTopBar.js
  84. 5
    0
      playground/src/screens/LifecycleScreen.js
  85. 1
    1
      playground/src/screens/OptionsScreen.js
  86. 3
    1
      playground/src/screens/OrientationDetectScreen.js
  87. 4
    8
      playground/src/screens/OrientationSelectScreen.js
  88. 1
    1
      playground/src/screens/StaticLifecycleOverlay.js
  89. 10
    0
      playground/src/screens/TextScreen.js
  90. 59
    25
      playground/src/screens/WelcomeScreen.js
  91. 2
    0
      playground/src/screens/index.js
  92. 3
    0
      playground/src/testIDs.js
  93. 12
    0
      scripts/test.e2e.android.detox.js

+ 2
- 1
Jenkinsfile View File

36
       }
36
       }
37
     }
37
     }
38
   }
38
   }
39
-}
39
+}
40
+

+ 1
- 1
docs/_sidebar.md View File

6
  - [Top Level](/docs/Navigation)
6
  - [Top Level](/docs/Navigation)
7
 - Params
7
 - Params
8
  - [Root](/docs/Root)
8
  - [Root](/docs/Root)
9
- - [Container](/docs/Container)
9
+ - [Component](/docs/Component)
10
  - [SideMenu](/docs/SideMenu)
10
  - [SideMenu](/docs/SideMenu)
11
 - Options
11
 - Options
12
  - [NavigationOptions](/docs/options/NavigationOptions)
12
  - [NavigationOptions](/docs/options/NavigationOptions)

docs/docs/Container.md → docs/docs/Component.md View File

1
-<h1>Container</h1>
1
+<h1>Component</h1>
2
 
2
 
3
 **Properties**
3
 **Properties**
4
 
4
 
5
 | Name | Type | Description |
5
 | Name | Type | Description |
6
 | --- | --- | --- |
6
 | --- | --- | --- |
7
-| name | <code>string</code> | The container's registered name |
8
-| topTabs | [<code>Array.&lt;Container&gt;</code>](#Container) |  |
7
+| name | <code>string</code> | The components registered name |
8
+| topTabs | [<code>Array.&lt;Component&gt;</code>](#Component) |  |
9
 | passProps | <code>object</code> | props |
9
 | passProps | <code>object</code> | props |
10
 | options | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Options">Options</a> |  |
10
 | options | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Options">Options</a> |  |
11
 
11
 

+ 32
- 33
docs/docs/Navigation.md View File

3
 # Navigation
3
 # Navigation
4
 
4
 
5
 * [Navigation](#Navigation)
5
 * [Navigation](#Navigation)
6
-    * [.registerContainer(containerName, getContainerFunc)](#Navigation+registerContainer)
6
+    * [.registerComponent(componentName, getComponentFunc)](#Navigation+registerComponent)
7
     * [.setRoot(root)](#Navigation+setRoot)
7
     * [.setRoot(root)](#Navigation+setRoot)
8
     * [.setDefaultOptions(options)](#Navigation+setDefaultOptions)
8
     * [.setDefaultOptions(options)](#Navigation+setDefaultOptions)
9
-    * [.setOptions(containerId, options)](#Navigation+setOptions)
9
+    * [.setOptions(componentId, options)](#Navigation+setOptions)
10
     * [.showModal(params)](#Navigation+showModal)
10
     * [.showModal(params)](#Navigation+showModal)
11
-    * [.dismissModal(containerId)](#Navigation+dismissModal)
11
+    * [.dismissModal(componentId)](#Navigation+dismissModal)
12
     * [.dismissAllModals()](#Navigation+dismissAllModals)
12
     * [.dismissAllModals()](#Navigation+dismissAllModals)
13
-    * [.push(containerId, container)](#Navigation+push)
14
-    * [.pop(containerId, params)](#Navigation+pop)
15
-    * [.popTo(containerId)](#Navigation+popTo)
16
-    * [.popToRoot(containerId)](#Navigation+popToRoot)
13
+    * [.push(componentId, component)](#Navigation+push)
14
+    * [.pop(componentId, params)](#Navigation+pop)
15
+    * [.popTo(componentId)](#Navigation+popTo)
16
+    * [.popToRoot(componentId)](#Navigation+popToRoot)
17
     * [.events()](#Navigation+events)
17
     * [.events()](#Navigation+events)
18
 
18
 
19
 
19
 
20
 * * *
20
 * * *
21
 
21
 
22
-<a name="Navigation+registerContainer"></a>
22
+<a name="Navigation+registerComponent"></a>
23
 
23
 
24
-## navigation.registerContainer(containerName, getContainerFunc)
24
+## navigation.registerComponent(componentName, getComponentFunc)
25
 Every screen component in your app must be registered with a unique name. The component itself is a traditional React component extending React.Component.
25
 Every screen component in your app must be registered with a unique name. The component itself is a traditional React component extending React.Component.
26
 
26
 
27
 
27
 
28
 | Param | Type | Description |
28
 | Param | Type | Description |
29
 | --- | --- | --- |
29
 | --- | --- | --- |
30
-| containerName | <code>string</code> | Unique container name |
31
-| getContainerFunc | <code>function</code> | generator function, typically `() => require('./myContainer')` |
30
+| componentName | <code>string</code> | Unique component name |
31
+| getComponentFunc | <code>function</code> | generator function, typically `() => require('./myComponent')` |
32
 
32
 
33
 
33
 
34
 * * *
34
 * * *
41
 
41
 
42
 | Param | Type |
42
 | Param | Type |
43
 | --- | --- |
43
 | --- | --- |
44
-| root | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Root">Root</a> | 
44
+| root | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Root">Root</a> |
45
 
45
 
46
 
46
 
47
 * * *
47
 * * *
54
 
54
 
55
 | Param | Type |
55
 | Param | Type |
56
 | --- | --- |
56
 | --- | --- |
57
-| options | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/options/NavigationOptions">NavigationOptions</a> | 
57
+| options | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/options/NavigationOptions">NavigationOptions</a> |
58
 
58
 
59
 
59
 
60
 * * *
60
 * * *
61
 
61
 
62
 <a name="Navigation+setOptions"></a>
62
 <a name="Navigation+setOptions"></a>
63
 
63
 
64
-## navigation.setOptions(containerId, options)
65
-Change a containers navigation options
64
+## navigation.setOptions(componentId, options)
65
+Change a components navigation options
66
 
66
 
67
 
67
 
68
 | Param | Type | Description |
68
 | Param | Type | Description |
69
 | --- | --- | --- |
69
 | --- | --- | --- |
70
-| containerId | <code>string</code> | The container's id. |
70
+| componentId | <code>string</code> | The component's id. |
71
 | options | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/options/NavigationOptions">NavigationOptions</a> |  |
71
 | options | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/options/NavigationOptions">NavigationOptions</a> |  |
72
 
72
 
73
 
73
 
81
 
81
 
82
 | Param | Type |
82
 | Param | Type |
83
 | --- | --- |
83
 | --- | --- |
84
-| params | <code>object</code> | 
84
+| params | <code>object</code> |
85
 
85
 
86
 
86
 
87
 * * *
87
 * * *
88
 
88
 
89
 <a name="Navigation+dismissModal"></a>
89
 <a name="Navigation+dismissModal"></a>
90
 
90
 
91
-## navigation.dismissModal(containerId)
92
-Dismiss a modal by containerId. The dismissed modal can be anywhere in the stack.
91
+## navigation.dismissModal(componentId)
92
+Dismiss a modal by componentId. The dismissed modal can be anywhere in the stack.
93
 
93
 
94
 
94
 
95
 | Param | Type | Description |
95
 | Param | Type | Description |
96
 | --- | --- | --- |
96
 | --- | --- | --- |
97
-| containerId | <code>string</code> | The container's id. |
97
+| componentId | <code>string</code> | The component's id. |
98
 
98
 
99
 
99
 
100
 * * *
100
 * * *
109
 
109
 
110
 <a name="Navigation+push"></a>
110
 <a name="Navigation+push"></a>
111
 
111
 
112
-## navigation.push(containerId, container)
112
+## navigation.push(componentId, component)
113
 Push a new screen into this screen's navigation stack.
113
 Push a new screen into this screen's navigation stack.
114
 
114
 
115
 
115
 
116
 | Param | Type | Description |
116
 | Param | Type | Description |
117
 | --- | --- | --- |
117
 | --- | --- | --- |
118
-| containerId | <code>string</code> | The container's id. |
119
-| container | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Container">Container</a> |  |
118
+| componentId | <code>string</code> | The component's id. |
119
+| component | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Component">Component</a> |  |
120
 
120
 
121
 
121
 
122
 * * *
122
 * * *
123
 
123
 
124
 <a name="Navigation+pop"></a>
124
 <a name="Navigation+pop"></a>
125
 
125
 
126
-## navigation.pop(containerId, params)
127
-Pop a container from the stack, regardless of it's position.
126
+## navigation.pop(componentId, params)
127
+Pop a component from the stack, regardless of it's position.
128
 
128
 
129
 
129
 
130
 | Param | Type | Description |
130
 | Param | Type | Description |
131
 | --- | --- | --- |
131
 | --- | --- | --- |
132
-| containerId | <code>string</code> | The container's id. |
132
+| componentId | <code>string</code> | The component's id. |
133
 | params | <code>*</code> |  |
133
 | params | <code>*</code> |  |
134
 
134
 
135
 
135
 
137
 
137
 
138
 <a name="Navigation+popTo"></a>
138
 <a name="Navigation+popTo"></a>
139
 
139
 
140
-## navigation.popTo(containerId)
141
-Pop the stack to a given container
140
+## navigation.popTo(componentId)
141
+Pop the stack to a given component
142
 
142
 
143
 
143
 
144
 | Param | Type | Description |
144
 | Param | Type | Description |
145
 | --- | --- | --- |
145
 | --- | --- | --- |
146
-| containerId | <code>string</code> | The container's id. |
146
+| componentId | <code>string</code> | The component's id. |
147
 
147
 
148
 
148
 
149
 * * *
149
 * * *
150
 
150
 
151
 <a name="Navigation+popToRoot"></a>
151
 <a name="Navigation+popToRoot"></a>
152
 
152
 
153
-## navigation.popToRoot(containerId)
154
-Pop the container's stack to root.
153
+## navigation.popToRoot(componentId)
154
+Pop the component's stack to root.
155
 
155
 
156
 
156
 
157
 | Param | Type |
157
 | Param | Type |
158
 | --- | --- |
158
 | --- | --- |
159
-| containerId | <code>*</code> | 
159
+| componentId | <code>*</code> |
160
 
160
 
161
 
161
 
162
 * * *
162
 * * *
165
 
165
 
166
 ## navigation.events()
166
 ## navigation.events()
167
 Obtain the events registery instance
167
 Obtain the events registery instance
168
-

+ 2
- 2
docs/docs/Root.md View File

4
 
4
 
5
 | Name | Type |
5
 | Name | Type |
6
 | --- | --- |
6
 | --- | --- |
7
-| container | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Container">Container</a> | 
7
+| component | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Component">Component</a> | 
8
 | sideMenu | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/SideMenu">SideMenu</a> | 
8
 | sideMenu | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/SideMenu">SideMenu</a> | 
9
-| bottomTabs | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Container">Container[]</a> | 
9
+| bottomTabs | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Component">Component[]</a> | 
10
 
10
 

+ 2
- 2
docs/docs/SideMenu.md View File

4
 
4
 
5
 | Name | Type |
5
 | Name | Type |
6
 | --- | --- |
6
 | --- | --- |
7
-| left | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Container">Container</a> | 
8
-| right | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Container">Container</a> | 
7
+| left | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Component">Component</a> | 
8
+| right | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Component">Component</a> | 
9
 
9
 

+ 42
- 9
docs/docs/usage.md View File

4
 
4
 
5
 ### Navigation
5
 ### Navigation
6
 ```js
6
 ```js
7
-import Navigation from 'react-native-navigation';
7
+import { Navigation } from 'react-native-navigation';
8
 ```
8
 ```
9
 ### Events - On App Launched
9
 ### Events - On App Launched
10
 How to initiate your app.
10
 How to initiate your app.
83
   },
83
   },
84
 });
84
 });
85
 ```
85
 ```
86
+
87
+Start a stack based app (with options):
88
+
89
+```js
90
+Navigation.setRoot({
91
+  stack: {
92
+    options: {
93
+      topBar: {
94
+        hidden: true,
95
+      },
96
+    },
97
+    children: [
98
+      {
99
+        component: {
100
+          name: 'navigation.playground.TextScreen',
101
+          passProps: {
102
+            text: 'This is tab 1',
103
+            myFunction: () => 'Hello from a function!',
104
+          },
105
+        },
106
+      },
107
+      {
108
+        component: {
109
+          name: 'navigation.playground.TextScreen',
110
+          passProps: {
111
+            text: 'This is tab 2',
112
+          },
113
+        },
114
+      },
115
+    ],
116
+  },
117
+});
118
+```
86
 ## Screen API
119
 ## Screen API
87
 
120
 
88
 ### push(params)
121
 ### push(params)
89
 Push a new screen into this screen's navigation stack.
122
 Push a new screen into this screen's navigation stack.
90
 
123
 
91
 ```js
124
 ```js
92
-Navigation.push(this.props.containerId, {
125
+Navigation.push(this.props.componentId, {
93
   name: 'navigation.playground.PushedScreen',
126
   name: 'navigation.playground.PushedScreen',
94
   passProps: {}
127
   passProps: {}
95
 });
128
 });
96
 ```
129
 ```
97
-### pop(containerId)
130
+### pop(componentId)
98
 Pop the top screen from this screen's navigation stack.
131
 Pop the top screen from this screen's navigation stack.
99
 
132
 
100
 ```js
133
 ```js
101
-Navigation.pop(this.props.containerId);
134
+Navigation.pop(this.props.componentId);
102
 ```
135
 ```
103
-### popTo(containerId)
136
+### popTo(componentId)
104
 ```js
137
 ```js
105
 Navigation.popTo(previousScreenId);
138
 Navigation.popTo(previousScreenId);
106
 ```
139
 ```
108
 Pop all the screens until the root from this screen's navigation stack
141
 Pop all the screens until the root from this screen's navigation stack
109
 
142
 
110
 ```js
143
 ```js
111
-Navigation.popToRoot(this.props.containerId);
144
+Navigation.popToRoot(this.props.componentId);
112
 ```
145
 ```
113
 ### showModal(params = {})
146
 ### showModal(params = {})
114
 Show a screen as a modal.
147
 Show a screen as a modal.
115
 
148
 
116
 ```js
149
 ```js
117
 Navigation.showModal({
150
 Navigation.showModal({
118
-  container: {
151
+  component: {
119
     name: 'navigation.playground.ModalScreen',
152
     name: 'navigation.playground.ModalScreen',
120
     passProps: {
153
     passProps: {
121
         key: 'value'
154
         key: 'value'
123
   }
156
   }
124
 });
157
 });
125
 ```
158
 ```
126
-### dismissModal(containerId)
159
+### dismissModal(componentId)
127
 Dismiss modal.
160
 Dismiss modal.
128
 
161
 
129
 ```js
162
 ```js
130
-Navigation.dismissModal(this.props.containerId);
163
+Navigation.dismissModal(this.props.componentId);
131
 ```
164
 ```
132
 ### dismissAllModals()
165
 ### dismissAllModals()
133
 Dismiss all the current modals at the same time.
166
 Dismiss all the current modals at the same time.

+ 18
- 18
docs/docs/v1tov2diff.md View File

17
 There are ways to solve some of these problems in v1 but they are not straightforward. We want to change that.
17
 There are ways to solve some of these problems in v1 but they are not straightforward. We want to change that.
18
 
18
 
19
 #### New API
19
 #### New API
20
-To solve this problem in v2, every screen receives as a prop it’s containerId. Whenever you want to perform an action from that screen you need to pass the containerId to the function:
20
+To solve this problem in v2, every screen receives as a prop it’s componentId. Whenever you want to perform an action from that screen you need to pass the componentId to the function:
21
 ```js
21
 ```js
22
-Navigator.pop(this.props.containerId)
22
+Navigator.pop(this.props.componentId)
23
 ```
23
 ```
24
 ### Built for Contributors
24
 ### Built for Contributors
25
 Currently, it requires a lot of work to accept pull requests. We need to manually make sure that everything works before we approve them because v1 is not thoroughly tested. <br>
25
 Currently, it requires a lot of work to accept pull requests. We need to manually make sure that everything works before we approve them because v1 is not thoroughly tested. <br>
179
 ```js
179
 ```js
180
 Navigation.events().onAppLaunched(() => {
180
 Navigation.events().onAppLaunched(() => {
181
     Navigation.setRoot({
181
     Navigation.setRoot({
182
-      container: {
182
+      component: {
183
         name: 'navigation.playground.WelcomeScreen'
183
         name: 'navigation.playground.WelcomeScreen'
184
       }
184
       }
185
     });
185
     });
186
   });
186
   });
187
 ```
187
 ```
188
 
188
 
189
-#### registerContainer(screenID, generator)
189
+#### registerComponent(screenID, generator)
190
 Every screen component in your app must be registered with a unique name. The component itself is a traditional React component extending React.Component.
190
 Every screen component in your app must be registered with a unique name. The component itself is a traditional React component extending React.Component.
191
 
191
 
192
 ```js
192
 ```js
193
-Navigation.registerContainer(`navigation.playground.WelcomeScreen`, () => WelcomeScreen);
193
+Navigation.registerComponent(`navigation.playground.WelcomeScreen`, () => WelcomeScreen);
194
 ```
194
 ```
195
 
195
 
196
 #### setRoot({params})
196
 #### setRoot({params})
198
 
198
 
199
 ```js
199
 ```js
200
 Navigation.setRoot({
200
 Navigation.setRoot({
201
-      container: {
201
+      component: {
202
         name: 'navigation.playground.WelcomeScreen'
202
         name: 'navigation.playground.WelcomeScreen'
203
       },
203
       },
204
       sideMenu: {
204
       sideMenu: {
205
         left: {
205
         left: {
206
-          container: {
206
+          component: {
207
             name: 'navigation.playground.TextScreen',
207
             name: 'navigation.playground.TextScreen',
208
             passProps: {
208
             passProps: {
209
               text: 'This is a left side menu screen'
209
               text: 'This is a left side menu screen'
211
           }
211
           }
212
         },
212
         },
213
         right: {
213
         right: {
214
-          container: {
214
+          component: {
215
             name: 'navigation.playground.TextScreen',
215
             name: 'navigation.playground.TextScreen',
216
             passProps: {
216
             passProps: {
217
               text: 'This is a right side menu screen'
217
               text: 'This is a right side menu screen'
227
 Navigation.setRoot({
227
 Navigation.setRoot({
228
       tabs: [
228
       tabs: [
229
         {
229
         {
230
-          container: {
230
+          component: {
231
             name: 'navigation.playground.TextScreen',
231
             name: 'navigation.playground.TextScreen',
232
             passProps: {
232
             passProps: {
233
               text: 'This is tab 1',
233
               text: 'This is tab 1',
236
           }
236
           }
237
         },
237
         },
238
         {
238
         {
239
-          container: {
239
+          component: {
240
             name: 'navigation.playground.TextScreen',
240
             name: 'navigation.playground.TextScreen',
241
             passProps: {
241
             passProps: {
242
               text: 'This is tab 2'
242
               text: 'This is tab 2'
252
 Push a new screen into this screen's navigation stack.
252
 Push a new screen into this screen's navigation stack.
253
 
253
 
254
 ```js
254
 ```js
255
-Navigation.push(this.props.containerId, {
255
+Navigation.push(this.props.componentId, {
256
       name: 'navigation.playground.PushedScreen',
256
       name: 'navigation.playground.PushedScreen',
257
       passProps: {}
257
       passProps: {}
258
     });
258
     });
259
 ```
259
 ```
260
-#### pop(containerId)
260
+#### pop(componentId)
261
 Pop the top screen from this screen's navigation stack.
261
 Pop the top screen from this screen's navigation stack.
262
 
262
 
263
 ```js
263
 ```js
264
-Navigation.pop(this.props.containerId);
264
+Navigation.pop(this.props.componentId);
265
 ```
265
 ```
266
 #### popTo(params)
266
 #### popTo(params)
267
 
267
 
268
 ```js
268
 ```js
269
-Navigation.popTo(this.props.containerId, this.props.previousScreenIds[0]);
269
+Navigation.popTo(this.props.componentId, this.props.previousScreenIds[0]);
270
 ```
270
 ```
271
 #### popToRoot()
271
 #### popToRoot()
272
 Pop all the screens until the root from this screen's navigation stack
272
 Pop all the screens until the root from this screen's navigation stack
273
 
273
 
274
 ```js
274
 ```js
275
-Navigation.popToRoot(this.props.containerId);
275
+Navigation.popToRoot(this.props.componentId);
276
 ```
276
 ```
277
 #### showModal(params = {})
277
 #### showModal(params = {})
278
 Show a screen as a modal.
278
 Show a screen as a modal.
279
 
279
 
280
 ```js
280
 ```js
281
 Navigation.showModal({
281
 Navigation.showModal({
282
-      container: {
282
+      component: {
283
         name: 'navigation.playground.ModalScreen',
283
         name: 'navigation.playground.ModalScreen',
284
         passProps: {
284
         passProps: {
285
             key: 'value'
285
             key: 'value'
287
       }
287
       }
288
     });
288
     });
289
 ```
289
 ```
290
-#### dismissModal(containerId)
290
+#### dismissModal(componentId)
291
 Dismiss modal.
291
 Dismiss modal.
292
 
292
 
293
 ```js
293
 ```js
294
-Navigation.dismissModal(this.props.containerId);
294
+Navigation.dismissModal(this.props.componentId);
295
 ```
295
 ```
296
 #### dismissAllModals()
296
 #### dismissAllModals()
297
 Dismiss all the current modals at the same time.
297
 Dismiss all the current modals at the same time.

+ 4
- 3
e2e/CustomTransition.js View File

1
 
1
 
2
 const Utils = require('./Utils');
2
 const Utils = require('./Utils');
3
+const testIDs = require('../playground/src/testIDs');
3
 
4
 
4
-const elementByLabel = Utils.elementByLabel;
5
+const elementById = Utils.elementById;
5
 
6
 
6
 describe('custom transition', () => {
7
 describe('custom transition', () => {
7
   beforeEach(async () => {
8
   beforeEach(async () => {
9
   });
10
   });
10
 
11
 
11
   it('sanity', async () => {
12
   it('sanity', async () => {
12
-    await elementByLabel('Push Options Screen').tap();
13
-    await elementByLabel('Custom Transition').tap();
13
+    await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
14
+    await elementById(testIDs.CUSTOM_TRANSITION_BUTTON).tap();
14
     await expect(element(by.id('shared_image1'))).toExist();
15
     await expect(element(by.id('shared_image1'))).toExist();
15
     await element(by.id('shared_image1')).tap();
16
     await element(by.id('shared_image1')).tap();
16
     await expect(element(by.id('shared_image2'))).toExist();
17
     await expect(element(by.id('shared_image2'))).toExist();

+ 11
- 16
e2e/Orientations.test.js View File

9
     await device.relaunchApp();
9
     await device.relaunchApp();
10
   });
10
   });
11
 
11
 
12
-  afterEach(async () => {
13
-    await device.setOrientation('landscape');
14
-    await device.setOrientation('portrait');
15
-  });
16
-
17
   it('default allows all', async () => {
12
   it('default allows all', async () => {
18
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
13
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
19
     await elementById(testIDs.DEFAULT_ORIENTATION_BUTTON).tap();
14
     await elementById(testIDs.DEFAULT_ORIENTATION_BUTTON).tap();
20
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
15
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
21
     await device.setOrientation('landscape');
16
     await device.setOrientation('landscape');
22
-    await expect(element(by.id('currentOrientation'))).toHaveText('Landscape');
17
+    await expect(elementById(testIDs.LANDSCAPE_ELEMENT)).toBeVisible();
23
     await device.setOrientation('portrait');
18
     await device.setOrientation('portrait');
24
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
19
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
25
     await elementById(testIDs.DISMISS_BUTTON).tap();
20
     await elementById(testIDs.DISMISS_BUTTON).tap();
26
   });
21
   });
27
 
22
 
28
   it('landscape and portrait array', async () => {
23
   it('landscape and portrait array', async () => {
29
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
24
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
30
     await elementById(testIDs.LANDSCAPE_PORTRAIT_ORIENTATION_BUTTON).tap();
25
     await elementById(testIDs.LANDSCAPE_PORTRAIT_ORIENTATION_BUTTON).tap();
31
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
26
+    await expect(element(by.id(testIDs.PORTRAIT_ELEMENT))).toBeVisible();
32
     await device.setOrientation('landscape');
27
     await device.setOrientation('landscape');
33
-    await expect(element(by.id('currentOrientation'))).toHaveText('Landscape');
28
+    await expect(element(by.id(testIDs.LANDSCAPE_ELEMENT))).toBeVisible();
34
     await device.setOrientation('portrait');
29
     await device.setOrientation('portrait');
35
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
30
+    await expect(element(by.id(testIDs.PORTRAIT_ELEMENT))).toBeVisible();
36
     await elementById(testIDs.DISMISS_BUTTON).tap();
31
     await elementById(testIDs.DISMISS_BUTTON).tap();
37
   });
32
   });
38
 
33
 
39
   it('portrait only', async () => {
34
   it('portrait only', async () => {
40
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
35
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
41
     await elementById(testIDs.PORTRAIT_ORIENTATION_BUTTON).tap();
36
     await elementById(testIDs.PORTRAIT_ORIENTATION_BUTTON).tap();
42
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
37
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
43
     await device.setOrientation('landscape');
38
     await device.setOrientation('landscape');
44
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
39
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
45
     await device.setOrientation('portrait');
40
     await device.setOrientation('portrait');
46
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
41
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
47
     await elementById(testIDs.DISMISS_BUTTON).tap();
42
     await elementById(testIDs.DISMISS_BUTTON).tap();
48
   });
43
   });
49
 
44
 
51
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
46
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
52
     await elementById(testIDs.LANDSCAPE_ORIENTATION_BUTTON).tap();
47
     await elementById(testIDs.LANDSCAPE_ORIENTATION_BUTTON).tap();
53
     await device.setOrientation('landscape');
48
     await device.setOrientation('landscape');
54
-    await expect(element(by.id('currentOrientation'))).toHaveText('Landscape');
49
+    await expect(element(by.id(testIDs.LANDSCAPE_ELEMENT))).toBeVisible();
55
     await device.setOrientation('portrait');
50
     await device.setOrientation('portrait');
56
-    await expect(element(by.id('currentOrientation'))).toHaveText('Landscape');
51
+    await expect(element(by.id(testIDs.LANDSCAPE_ELEMENT))).toBeVisible();
57
     await elementById(testIDs.DISMISS_BUTTON).tap();
52
     await elementById(testIDs.DISMISS_BUTTON).tap();
58
   });
53
   });
59
 });
54
 });

+ 1
- 1
e2e/ScreenStack.test.js View File

90
     await elementById(testIDs.SHOW_MODAL_BUTTON).tap();
90
     await elementById(testIDs.SHOW_MODAL_BUTTON).tap();
91
     await elementById(testIDs.MODAL_WITH_STACK_BUTTON).tap();
91
     await elementById(testIDs.MODAL_WITH_STACK_BUTTON).tap();
92
     await expect(elementByLabel('Screen 2')).toBeVisible();
92
     await expect(elementByLabel('Screen 2')).toBeVisible();
93
-    await Utils.tapBackIos();
93
+    await elementById(testIDs.POP_BUTTON).tap();
94
     await expect(elementByLabel('Screen 1')).toBeVisible();
94
     await expect(elementByLabel('Screen 1')).toBeVisible();
95
   });
95
   });
96
 });
96
 });

+ 3
- 11
e2e/ScreenStyle.test.js View File

10
 
10
 
11
   it('declare a options on component component', async () => {
11
   it('declare a options on component component', async () => {
12
     await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
12
     await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
13
-    await expect(element(by.label('Static Title'))).toBeVisible();
13
+    await expect(elementByLabel('Static Title')).toBeVisible();
14
   });
14
   });
15
 
15
 
16
   it('change title on component component', async () => {
16
   it('change title on component component', async () => {
17
     await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
17
     await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
18
-    await expect(element(by.label('Static Title'))).toBeVisible();
18
+    await expect(elementByLabel('Static Title')).toBeVisible();
19
     await elementById(testIDs.DYNAMIC_OPTIONS_BUTTON).tap();
19
     await elementById(testIDs.DYNAMIC_OPTIONS_BUTTON).tap();
20
-    await expect(element(by.label('Dynamic Title'))).toBeVisible();
20
+    await expect(elementByLabel('Dynamic Title')).toBeVisible();
21
   });
21
   });
22
 
22
 
23
   it('set dynamic options with valid options will do something and not crash', async () => {
23
   it('set dynamic options with valid options will do something and not crash', async () => {
46
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
46
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
47
   });
47
   });
48
 
48
 
49
-  it('makes topBar transparent and opaque', async () => {
50
-    await elementByLabel('Push Options Screen').tap();
51
-    await elementByLabel('Top Bar Transparent').tap();
52
-    await expect(element(by.type('_UIVisualEffectBackdropView'))).toBeNotVisible();
53
-    await elementByLabel('Top Bar Opaque').tap();
54
-    await expect(element(by.type('_UIVisualEffectBackdropView')).atIndex(1)).toBeVisible();
55
-  });
56
-
57
   it('set Tab Bar badge on a current Tab', async () => {
49
   it('set Tab Bar badge on a current Tab', async () => {
58
     await elementById(testIDs.TAB_BASED_APP_BUTTON).tap();
50
     await elementById(testIDs.TAB_BASED_APP_BUTTON).tap();
59
     await elementById(testIDs.SET_TAB_BADGE_BUTTON).tap();
51
     await elementById(testIDs.SET_TAB_BADGE_BUTTON).tap();

+ 2
- 2
e2e/TopLevelApi.test.js View File

35
   it('unmount is called on pop', async () => {
35
   it('unmount is called on pop', async () => {
36
     await elementById(testIDs.PUSH_LIFECYCLE_BUTTON).tap();
36
     await elementById(testIDs.PUSH_LIFECYCLE_BUTTON).tap();
37
     await expect(elementByLabel('didAppear')).toBeVisible();
37
     await expect(elementByLabel('didAppear')).toBeVisible();
38
-    await Utils.tapBackIos();
38
+    await elementById(testIDs.POP_BUTTON).tap();
39
     await expect(elementByLabel('componentWillUnmount')).toBeVisible();
39
     await expect(elementByLabel('componentWillUnmount')).toBeVisible();
40
-    await element(by.traits(['button']).and(by.label('OK'))).atIndex(0).tap();
40
+    await elementByLabel('OK').atIndex(0).tap();
41
     await expect(elementByLabel('didDisappear')).toBeVisible();
41
     await expect(elementByLabel('didDisappear')).toBeVisible();
42
   });
42
   });
43
 });
43
 });

+ 2
- 1
lib/android/app/build.gradle View File

59
     implementation fileTree(include: ['*.jar'], dir: 'libs')
59
     implementation fileTree(include: ['*.jar'], dir: 'libs')
60
     implementation 'com.android.support:design:25.4.0'
60
     implementation 'com.android.support:design:25.4.0'
61
     implementation 'com.android.support:appcompat-v7:25.4.0'
61
     implementation 'com.android.support:appcompat-v7:25.4.0'
62
-    implementation "com.android.support:support-v4:25.4.0"
62
+    implementation 'com.android.support:support-v4:25.4.0'
63
+    implementation 'com.aurelhubert:ahbottomnavigation:2.1.0'
63
 
64
 
64
     // node_modules
65
     // node_modules
65
     //noinspection GradleDynamicVersion
66
     //noinspection GradleDynamicVersion

+ 48
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/BottomTabOptions.java View File

1
+package com.reactnativenavigation.parse;
2
+
3
+import org.json.JSONObject;
4
+
5
+public class BottomTabOptions implements DEFAULT_VALUES {
6
+
7
+    public static BottomTabOptions parse(JSONObject json) {
8
+        BottomTabOptions options = new BottomTabOptions();
9
+        if (json == null) return options;
10
+
11
+        options.title = TextParser.parse(json, "title");
12
+        if (json.has("icon")) {
13
+            options.icon = TextParser.parse(json.optJSONObject("icon"), "uri");
14
+        }
15
+        options.badge = TextParser.parse(json, "badge");
16
+        options.testId = TextParser.parse(json, "testID");
17
+        return options;
18
+    }
19
+
20
+    public Text title = new NullText();
21
+    public Text icon = new NullText();
22
+    public Text testId = new NullText();
23
+    public Text badge = new NullText();
24
+
25
+    void mergeWith(final BottomTabOptions other) {
26
+        if (other.title.hasValue()) {
27
+            title = other.title;
28
+        }
29
+        if (other.icon.hasValue()) {
30
+            icon = other.icon;
31
+        }
32
+        if (other.badge.hasValue()) {
33
+            badge = other.badge;
34
+        }
35
+    }
36
+
37
+    void mergeWithDefault(final BottomTabOptions defaultOptions) {
38
+        if (!title.hasValue()) {
39
+            title = defaultOptions.title;
40
+        }
41
+        if (!icon.hasValue()) {
42
+            icon = defaultOptions.icon;
43
+        }
44
+        if (!badge.hasValue()) {
45
+            badge = defaultOptions.badge;
46
+        }
47
+    }
48
+}

+ 26
- 16
lib/android/app/src/main/java/com/reactnativenavigation/parse/BottomTabsOptions.java View File

11
 		BottomTabsOptions options = new BottomTabsOptions();
11
 		BottomTabsOptions options = new BottomTabsOptions();
12
 		if (json == null) return options;
12
 		if (json == null) return options;
13
 
13
 
14
-		options.currentTabId = TextParser.parse(json, "currentTabId");
14
+        options.color = ColorParser.parse(json, "tabColor");
15
+        options.selectedColor = ColorParser.parse(json, "selectedTabColor");
16
+        options.currentTabId = TextParser.parse(json, "currentTabId");
15
 		options.currentTabIndex = json.optInt("currentTabIndex", NO_INT_VALUE);
17
 		options.currentTabIndex = json.optInt("currentTabIndex", NO_INT_VALUE);
16
-		options.tabBadge = json.optInt("tabBadge", NO_INT_VALUE);
17
-		options.hidden = BooleanOptions.parse(json.optString("hidden"));
18
+		options.visible = BooleanOptions.parse(json.optString("visible"));
18
 		options.animateHide = BooleanOptions.parse(json.optString("animateHide"));
19
 		options.animateHide = BooleanOptions.parse(json.optString("animateHide"));
20
+        options.testId = TextParser.parse(json, "testID");
19
 
21
 
20
 		return options;
22
 		return options;
21
 	}
23
 	}
22
 
24
 
23
-	int tabBadge = NO_INT_VALUE;
24
-	BooleanOptions hidden = BooleanOptions.False;
25
+    public Color color = new NullColor();
26
+    public Color selectedColor = new NullColor();
27
+	BooleanOptions visible = BooleanOptions.False;
25
 	BooleanOptions animateHide = BooleanOptions.False;
28
 	BooleanOptions animateHide = BooleanOptions.False;
26
 	public int currentTabIndex = NO_INT_VALUE;
29
 	public int currentTabIndex = NO_INT_VALUE;
27
 	public Text currentTabId = new NullText();
30
 	public Text currentTabId = new NullText();
31
+    public Text testId = new NullText();
28
 
32
 
29
 	void mergeWith(final BottomTabsOptions other) {
33
 	void mergeWith(final BottomTabsOptions other) {
30
 		if (other.currentTabId.hasValue()) {
34
 		if (other.currentTabId.hasValue()) {
33
 		if (NO_INT_VALUE != other.currentTabIndex) {
37
 		if (NO_INT_VALUE != other.currentTabIndex) {
34
             currentTabIndex = other.currentTabIndex;
38
             currentTabIndex = other.currentTabIndex;
35
 		}
39
 		}
36
-		if (NO_INT_VALUE != other.tabBadge) {
37
-			tabBadge = other.tabBadge;
38
-		}
39
-		if (other.hidden != BooleanOptions.NoValue) {
40
-			hidden = other.hidden;
40
+		if (other.visible != BooleanOptions.NoValue) {
41
+			visible = other.visible;
41
 		}
42
 		}
42
 		if (other.animateHide != BooleanOptions.NoValue) {
43
 		if (other.animateHide != BooleanOptions.NoValue) {
43
 			animateHide = other.animateHide;
44
 			animateHide = other.animateHide;
44
 		}
45
 		}
45
-	}
46
+        if (other.color.hasValue()) {
47
+            color = other.color;
48
+        }
49
+        if (other.selectedColor.hasValue()) {
50
+            selectedColor = other.selectedColor;
51
+        }
52
+    }
46
 
53
 
47
     void mergeWithDefault(final BottomTabsOptions defaultOptions) {
54
     void mergeWithDefault(final BottomTabsOptions defaultOptions) {
48
         if (!currentTabId.hasValue()) {
55
         if (!currentTabId.hasValue()) {
51
         if (NO_INT_VALUE == currentTabIndex) {
58
         if (NO_INT_VALUE == currentTabIndex) {
52
             currentTabIndex = defaultOptions.currentTabIndex;
59
             currentTabIndex = defaultOptions.currentTabIndex;
53
         }
60
         }
54
-        if (NO_INT_VALUE == tabBadge) {
55
-            tabBadge = defaultOptions.tabBadge;
56
-        }
57
-        if (hidden == BooleanOptions.NoValue) {
58
-            hidden = defaultOptions.hidden;
61
+        if (visible == BooleanOptions.NoValue) {
62
+            visible = defaultOptions.visible;
59
         }
63
         }
60
         if (animateHide == BooleanOptions.NoValue) {
64
         if (animateHide == BooleanOptions.NoValue) {
61
             animateHide = defaultOptions.animateHide;
65
             animateHide = defaultOptions.animateHide;
62
         }
66
         }
67
+        if (!color.hasValue()) {
68
+            color = defaultOptions.color;
69
+        }
70
+        if (!selectedColor.hasValue()) {
71
+            selectedColor = defaultOptions.selectedColor;
72
+        }
63
     }
73
     }
64
 }
74
 }

+ 3
- 1
lib/android/app/src/main/java/com/reactnativenavigation/parse/Button.java View File

19
 	@ColorInt public int buttonColor;
19
 	@ColorInt public int buttonColor;
20
 	public int buttonFontSize;
20
 	public int buttonFontSize;
21
 	public Text buttonFontWeight;
21
 	public Text buttonFontWeight;
22
-	public Text icon;
22
+	public Text icon = new NullText();
23
+	public Text testId;
23
 
24
 
24
 	private static Button parseJson(JSONObject json)  {
25
 	private static Button parseJson(JSONObject json)  {
25
 		Button button = new Button();
26
 		Button button = new Button();
31
 		button.buttonColor = json.optInt("buttonColor", NO_INT_VALUE);
32
 		button.buttonColor = json.optInt("buttonColor", NO_INT_VALUE);
32
 		button.buttonFontSize = json.optInt("buttonFontSize", NO_INT_VALUE);
33
 		button.buttonFontSize = json.optInt("buttonFontSize", NO_INT_VALUE);
33
 		button.buttonFontWeight = TextParser.parse(json, "buttonFontWeight");
34
 		button.buttonFontWeight = TextParser.parse(json, "buttonFontWeight");
35
+        button.testId = TextParser.parse(json, "testID");
34
 
36
 
35
 		if (json.has("icon")) {
37
 		if (json.has("icon")) {
36
 			button.icon = TextParser.parse(json.optJSONObject("icon"), "uri");
38
 			button.icon = TextParser.parse(json.optJSONObject("icon"), "uri");

+ 8
- 4
lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java View File

3
 import android.app.Activity;
3
 import android.app.Activity;
4
 
4
 
5
 import com.facebook.react.ReactInstanceManager;
5
 import com.facebook.react.ReactInstanceManager;
6
+import com.reactnativenavigation.utils.ImageLoader;
6
 import com.reactnativenavigation.utils.NoOpPromise;
7
 import com.reactnativenavigation.utils.NoOpPromise;
7
 import com.reactnativenavigation.utils.TypefaceLoader;
8
 import com.reactnativenavigation.utils.TypefaceLoader;
8
 import com.reactnativenavigation.viewcontrollers.BottomTabsController;
9
 import com.reactnativenavigation.viewcontrollers.BottomTabsController;
55
 	}
56
 	}
56
 
57
 
57
     private ViewController createSideMenuRoot(LayoutNode node) {
58
     private ViewController createSideMenuRoot(LayoutNode node) {
58
-		SideMenuController sideMenuLayout = new SideMenuController(activity, node.id);
59
+        final Options options = Options.parse(typefaceManager, node.getNavigationOptions(), defaultOptions);
60
+		SideMenuController sideMenuLayout = new SideMenuController(activity, node.id, options);
59
 		for (LayoutNode child : node.children) {
61
 		for (LayoutNode child : node.children) {
60
 			ViewController childLayout = create(child);
62
 			ViewController childLayout = create(child);
61
 			switch (child.type) {
63
 			switch (child.type) {
100
 	}
102
 	}
101
 
103
 
102
 	private ViewController createStack(LayoutNode node) {
104
 	private ViewController createStack(LayoutNode node) {
103
-		StackController stackController = new StackController(activity, node.id);
105
+        final Options options = Options.parse(typefaceManager, node.getNavigationOptions(), defaultOptions);
106
+		StackController stackController = new StackController(activity, node.id, options);
104
         for (int i = 0; i < node.children.size(); i++) {
107
         for (int i = 0; i < node.children.size(); i++) {
105
             if (i < node.children.size() - 1) {
108
             if (i < node.children.size() - 1) {
106
                 stackController.push(create(node.children.get(i)), new NoOpPromise());
109
                 stackController.push(create(node.children.get(i)), new NoOpPromise());
112
 	}
115
 	}
113
 
116
 
114
 	private ViewController createBottomTabs(LayoutNode node) {
117
 	private ViewController createBottomTabs(LayoutNode node) {
115
-		final BottomTabsController tabsComponent = new BottomTabsController(activity, node.id);
118
+        final Options options = Options.parse(typefaceManager, node.getNavigationOptions(), defaultOptions);
119
+		final BottomTabsController tabsComponent = new BottomTabsController(activity, new ImageLoader(), node.id, options);
116
 		List<ViewController> tabs = new ArrayList<>();
120
 		List<ViewController> tabs = new ArrayList<>();
117
 		for (int i = 0; i < node.children.size(); i++) {
121
 		for (int i = 0; i < node.children.size(); i++) {
118
-			tabs.add(create(node.children.get(i)));
122
+            tabs.add(create(node.children.get(i)));
119
 		}
123
 		}
120
 		tabsComponent.setTabs(tabs);
124
 		tabsComponent.setTabs(tabs);
121
 		return tabsComponent;
125
 		return tabsComponent;

+ 5
- 1
lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java View File

35
 		result.topBarOptions = TopBarOptions.parse(typefaceManager, json.optJSONObject("topBar"));
35
 		result.topBarOptions = TopBarOptions.parse(typefaceManager, json.optJSONObject("topBar"));
36
 		result.topTabsOptions = TopTabsOptions.parse(json.optJSONObject("topTabs"));
36
 		result.topTabsOptions = TopTabsOptions.parse(json.optJSONObject("topTabs"));
37
         result.topTabOptions = TopTabOptions.parse(typefaceManager, json.optJSONObject("topTab"));
37
         result.topTabOptions = TopTabOptions.parse(typefaceManager, json.optJSONObject("topTab"));
38
-		result.bottomTabsOptions = BottomTabsOptions.parse(json.optJSONObject("bottomTabs"));
38
+        result.bottomTabOptions = BottomTabOptions.parse(json.optJSONObject("bottomTab"));
39
+        result.bottomTabsOptions = BottomTabsOptions.parse(json.optJSONObject("bottomTabs"));
39
         result.overlayOptions = OverlayOptions.parse(json.optJSONObject("overlay"));
40
         result.overlayOptions = OverlayOptions.parse(json.optJSONObject("overlay"));
40
 
41
 
41
 		return result.withDefaultOptions(defaultOptions);
42
 		return result.withDefaultOptions(defaultOptions);
44
     @NonNull public TopBarOptions topBarOptions = new TopBarOptions();
45
     @NonNull public TopBarOptions topBarOptions = new TopBarOptions();
45
     @NonNull public TopTabsOptions topTabsOptions = new TopTabsOptions();
46
     @NonNull public TopTabsOptions topTabsOptions = new TopTabsOptions();
46
     @NonNull public TopTabOptions topTabOptions = new TopTabOptions();
47
     @NonNull public TopTabOptions topTabOptions = new TopTabOptions();
48
+    @NonNull public BottomTabOptions bottomTabOptions = new BottomTabOptions();
47
     @NonNull public BottomTabsOptions bottomTabsOptions = new BottomTabsOptions();
49
     @NonNull public BottomTabsOptions bottomTabsOptions = new BottomTabsOptions();
48
     @NonNull public OverlayOptions overlayOptions = new OverlayOptions();
50
     @NonNull public OverlayOptions overlayOptions = new OverlayOptions();
49
 
51
 
54
 	public void mergeWith(final Options other) {
56
 	public void mergeWith(final Options other) {
55
         topBarOptions.mergeWith(other.topBarOptions);
57
         topBarOptions.mergeWith(other.topBarOptions);
56
         topTabsOptions.mergeWith(other.topTabsOptions);
58
         topTabsOptions.mergeWith(other.topTabsOptions);
59
+        bottomTabOptions.mergeWith(other.bottomTabOptions);
57
         bottomTabsOptions.mergeWith(other.bottomTabsOptions);
60
         bottomTabsOptions.mergeWith(other.bottomTabsOptions);
58
     }
61
     }
59
 
62
 
60
     Options withDefaultOptions(final Options other) {
63
     Options withDefaultOptions(final Options other) {
61
         topBarOptions.mergeWithDefault(other.topBarOptions);
64
         topBarOptions.mergeWithDefault(other.topBarOptions);
62
         topTabsOptions.mergeWithDefault(other.topTabsOptions);
65
         topTabsOptions.mergeWithDefault(other.topTabsOptions);
66
+        bottomTabOptions.mergeWithDefault(other.bottomTabOptions);
63
         bottomTabsOptions.mergeWithDefault(other.bottomTabsOptions);
67
         bottomTabsOptions.mergeWithDefault(other.bottomTabsOptions);
64
         return this;
68
         return this;
65
     }
69
     }

+ 2
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java View File

27
         options.drawBehind = Options.BooleanOptions.parse(json.optString("drawBehind"));
27
         options.drawBehind = Options.BooleanOptions.parse(json.optString("drawBehind"));
28
         options.rightButtons = Button.parseJsonArray(json.optJSONArray("rightButtons"));
28
         options.rightButtons = Button.parseJsonArray(json.optJSONArray("rightButtons"));
29
         options.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons"));
29
         options.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons"));
30
+        options.testId = TextParser.parse(json, "testID");
30
 
31
 
31
         return options;
32
         return options;
32
     }
33
     }
33
 
34
 
34
     public Text title = new NullText();
35
     public Text title = new NullText();
36
+    public Text testId = new NullText();
35
     public Color backgroundColor = new NullColor();
37
     public Color backgroundColor = new NullColor();
36
     public Color textColor = new NullColor();
38
     public Color textColor = new NullColor();
37
     public Fraction textFontSize = new NullFraction();
39
     public Fraction textFontSize = new NullFraction();

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/parse/TopTabsOptions.java View File

14
     public static TopTabsOptions parse(@Nullable JSONObject json) {
14
     public static TopTabsOptions parse(@Nullable JSONObject json) {
15
         TopTabsOptions result = new TopTabsOptions();
15
         TopTabsOptions result = new TopTabsOptions();
16
         if (json == null) return result;
16
         if (json == null) return result;
17
-        result.selectedTabColor = ColorParser.parse(json, "selectedTabColor");
17
+        result.selectedTabColor = ColorParser.parse(json, "selectedColor");
18
         result.unselectedTabColor = ColorParser.parse(json, "unselectedTabColor");
18
         result.unselectedTabColor = ColorParser.parse(json, "unselectedTabColor");
19
         result.fontSize = NumberParser.parse(json, "fontSize");
19
         result.fontSize = NumberParser.parse(json, "fontSize");
20
         return result;
20
         return result;

+ 25
- 0
lib/android/app/src/main/java/com/reactnativenavigation/presentation/BottomTabOptionsPresenter.java View File

1
+package com.reactnativenavigation.presentation;
2
+
3
+import android.support.annotation.IntRange;
4
+
5
+import com.reactnativenavigation.parse.BottomTabOptions;
6
+import com.reactnativenavigation.parse.Options;
7
+import com.reactnativenavigation.views.BottomTabs;
8
+
9
+public class BottomTabOptionsPresenter {
10
+    private BottomTabs bottomTabs;
11
+
12
+    public BottomTabOptionsPresenter(BottomTabs bottomTabs) {
13
+        this.bottomTabs = bottomTabs;
14
+    }
15
+
16
+    public void present(Options options, @IntRange(from = 0) int bottomTabIndex) {
17
+        applyBottomTabOptions(options.bottomTabOptions, bottomTabIndex);
18
+    }
19
+
20
+    private void applyBottomTabOptions(BottomTabOptions options, int bottomTabIndex) {
21
+        if (options.badge.hasValue()) {
22
+            bottomTabs.setBadge(bottomTabIndex, options.badge);
23
+        }
24
+    }
25
+}

+ 1
- 0
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java View File

34
         topBar.setBackgroundColor(options.backgroundColor);
34
         topBar.setBackgroundColor(options.backgroundColor);
35
         topBar.setTitleTextColor(options.textColor);
35
         topBar.setTitleTextColor(options.textColor);
36
         topBar.setTitleFontSize(options.textFontSize);
36
         topBar.setTitleFontSize(options.textFontSize);
37
+        if (options.testId.hasValue()) topBar.setTestId(options.testId.get());
37
 
38
 
38
         topBar.setTitleTypeface(options.textFontFamily);
39
         topBar.setTitleTypeface(options.textFontFamily);
39
         if (options.hidden == True) {
40
         if (options.hidden == True) {

+ 15
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/ArrayUtils.java View File

1
+package com.reactnativenavigation.utils;
2
+
3
+public class ArrayUtils {
4
+    public static boolean contains(Object[] array, Object item) {
5
+        if (isNullOrEmpty(array)) return false;
6
+        for (Object o : array) {
7
+            if (o == item) return true;
8
+        }
9
+        return false;
10
+    }
11
+
12
+    private static boolean isNullOrEmpty(Object[] array) {
13
+        return array == null || array.length == 0;
14
+    }
15
+}

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/utils/CompatUtils.java View File

9
 	private static final AtomicInteger viewId = new AtomicInteger(1);
9
 	private static final AtomicInteger viewId = new AtomicInteger(1);
10
 
10
 
11
 	public static int generateViewId() {
11
 	public static int generateViewId() {
12
-		if (Build.VERSION.SDK_INT >= 17) {
12
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
13
 			return View.generateViewId();
13
 			return View.generateViewId();
14
 		} else {
14
 		} else {
15
 			while (true) {
15
 			while (true) {

lib/android/app/src/main/java/com/reactnativenavigation/utils/ImageUtils.java → lib/android/app/src/main/java/com/reactnativenavigation/utils/ImageLoader.java View File

17
 import java.io.InputStream;
17
 import java.io.InputStream;
18
 import java.net.URL;
18
 import java.net.URL;
19
 
19
 
20
-public class ImageUtils {
20
+public class ImageLoader {
21
 
21
 
22
 	public interface ImageLoadingListener {
22
 	public interface ImageLoadingListener {
23
 		void onComplete(@NonNull Drawable drawable);
23
 		void onComplete(@NonNull Drawable drawable);
25
 		void onError(Throwable error);
25
 		void onError(Throwable error);
26
 	}
26
 	}
27
 
27
 
28
-	public static void loadIcon(final Context context, final String uri, final ImageLoadingListener listener) {
28
+	public void loadIcon(final Context context, final String uri, final ImageLoadingListener listener) {
29
         try {
29
         try {
30
             StrictMode.ThreadPolicy threadPolicy = adjustThreadPolicyDebug();
30
             StrictMode.ThreadPolicy threadPolicy = adjustThreadPolicyDebug();
31
             
31
             
40
         }
40
         }
41
     }
41
     }
42
 
42
 
43
-    private static StrictMode.ThreadPolicy adjustThreadPolicyDebug() {
43
+    private StrictMode.ThreadPolicy adjustThreadPolicyDebug() {
44
         StrictMode.ThreadPolicy threadPolicy = null;
44
         StrictMode.ThreadPolicy threadPolicy = null;
45
         if (NavigationApplication.instance.isDebug()) {
45
         if (NavigationApplication.instance.isDebug()) {
46
             threadPolicy = StrictMode.getThreadPolicy();
46
             threadPolicy = StrictMode.getThreadPolicy();
49
         return threadPolicy;
49
         return threadPolicy;
50
     }
50
     }
51
 
51
 
52
-    private static void restoreThreadPolicyDebug(@Nullable StrictMode.ThreadPolicy threadPolicy) {
52
+    private void restoreThreadPolicyDebug(@Nullable StrictMode.ThreadPolicy threadPolicy) {
53
         if (NavigationApplication.instance.isDebug() && threadPolicy != null) {
53
         if (NavigationApplication.instance.isDebug() && threadPolicy != null) {
54
             StrictMode.setThreadPolicy(threadPolicy);
54
             StrictMode.setThreadPolicy(threadPolicy);
55
         }
55
         }

+ 14
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java View File

4
 import android.view.View;
4
 import android.view.View;
5
 import android.view.ViewGroup;
5
 import android.view.ViewGroup;
6
 
6
 
7
+import java.util.ArrayList;
8
+import java.util.List;
9
+
7
 public class ViewUtils {
10
 public class ViewUtils {
8
     @Nullable
11
     @Nullable
9
     public static <T> T findChildByClass(ViewGroup root, Class clazz) {
12
     public static <T> T findChildByClass(ViewGroup root, Class clazz) {
22
         }
25
         }
23
         return null;
26
         return null;
24
     }
27
     }
28
+
29
+    public static <T> List<T> findChildrenByClass(ViewGroup root, Class clazz) {
30
+        List<T> ret = new ArrayList<>();
31
+        for (int i = 0; i < root.getChildCount(); i++) {
32
+            View view = root.getChildAt(i);
33
+            if (clazz.isAssignableFrom(view.getClass())) {
34
+                ret.add((T) view);
35
+            }
36
+        }
37
+        return ret;
38
+    }
25
 }
39
 }

+ 87
- 41
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/BottomTabsController.java View File

2
 
2
 
3
 import android.app.Activity;
3
 import android.app.Activity;
4
 import android.graphics.Color;
4
 import android.graphics.Color;
5
+import android.graphics.drawable.Drawable;
6
+import android.support.annotation.IntRange;
5
 import android.support.annotation.NonNull;
7
 import android.support.annotation.NonNull;
6
-import android.support.design.widget.BottomNavigationView;
7
-import android.view.Menu;
8
-import android.view.MenuItem;
9
-import android.view.View;
10
 import android.view.ViewGroup;
8
 import android.view.ViewGroup;
11
 import android.widget.RelativeLayout;
9
 import android.widget.RelativeLayout;
12
 
10
 
11
+import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
12
+import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem;
13
+import com.reactnativenavigation.parse.BottomTabOptions;
14
+import com.reactnativenavigation.parse.BottomTabsOptions;
13
 import com.reactnativenavigation.parse.Options;
15
 import com.reactnativenavigation.parse.Options;
14
 import com.reactnativenavigation.parse.Text;
16
 import com.reactnativenavigation.parse.Text;
17
+import com.reactnativenavigation.presentation.BottomTabOptionsPresenter;
15
 import com.reactnativenavigation.presentation.NavigationOptionsListener;
18
 import com.reactnativenavigation.presentation.NavigationOptionsListener;
16
-import com.reactnativenavigation.utils.CompatUtils;
19
+import com.reactnativenavigation.utils.ImageLoader;
20
+import com.reactnativenavigation.utils.UiUtils;
21
+import com.reactnativenavigation.views.BottomTabs;
22
+import com.reactnativenavigation.views.ReactComponent;
17
 
23
 
18
 import java.util.ArrayList;
24
 import java.util.ArrayList;
19
 import java.util.Collection;
25
 import java.util.Collection;
25
 import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
31
 import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
26
 import static com.reactnativenavigation.parse.DEFAULT_VALUES.NO_INT_VALUE;
32
 import static com.reactnativenavigation.parse.DEFAULT_VALUES.NO_INT_VALUE;
27
 
33
 
28
-public class BottomTabsController extends ParentController
29
-		implements BottomNavigationView.OnNavigationItemSelectedListener, NavigationOptionsListener {
30
-	private BottomNavigationView bottomNavigationView;
34
+public class BottomTabsController extends ParentController implements AHBottomNavigation.OnTabSelectedListener, NavigationOptionsListener {
35
+	private BottomTabs bottomTabs;
31
 	private List<ViewController> tabs = new ArrayList<>();
36
 	private List<ViewController> tabs = new ArrayList<>();
32
-	private int selectedIndex = 0;
37
+    private ImageLoader imageLoader;
33
 
38
 
34
-	public BottomTabsController(final Activity activity, final String id) {
35
-		super(activity, id);
36
-	}
39
+    public BottomTabsController(final Activity activity, ImageLoader imageLoader, final String id, Options initialOptions) {
40
+		super(activity, id, initialOptions);
41
+        this.imageLoader = imageLoader;
42
+    }
37
 
43
 
38
 	@NonNull
44
 	@NonNull
39
 	@Override
45
 	@Override
40
 	protected ViewGroup createView() {
46
 	protected ViewGroup createView() {
41
 		RelativeLayout root = new RelativeLayout(getActivity());
47
 		RelativeLayout root = new RelativeLayout(getActivity());
42
-		bottomNavigationView = new BottomNavigationView(getActivity());
43
-		bottomNavigationView.setId(CompatUtils.generateViewId());
44
-		bottomNavigationView.setBackgroundColor(Color.DKGRAY);
45
-		bottomNavigationView.setOnNavigationItemSelectedListener(this);
48
+		bottomTabs = new BottomTabs(getActivity(), options.bottomTabsOptions);
49
+        bottomTabs.setOnTabSelectedListener(this);
46
 		RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
50
 		RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
47
 		lp.addRule(ALIGN_PARENT_BOTTOM);
51
 		lp.addRule(ALIGN_PARENT_BOTTOM);
48
-		root.addView(bottomNavigationView, lp);
52
+		root.addView(bottomTabs, lp);
49
 		return root;
53
 		return root;
50
 	}
54
 	}
51
 
55
 
52
-	@Override
53
-	public boolean handleBack() {
54
-		return !tabs.isEmpty() && tabs.get(selectedIndex).handleBack();
55
-	}
56
+    @Override
57
+    public void applyOptions(Options options, ReactComponent childComponent) {
58
+        int tabIndex = findTabContainingComponent(childComponent);
59
+        if (tabIndex >= 0) new BottomTabOptionsPresenter(bottomTabs).present(options, tabIndex);
60
+    }
56
 
61
 
57
-	@Override
58
-	public boolean onNavigationItemSelected(@NonNull final MenuItem item) {
59
-		selectTabAtIndex(item.getItemId());
60
-		return true;
62
+    @Override
63
+	public boolean handleBack() {
64
+		return !tabs.isEmpty() && tabs.get(bottomTabs.getCurrentItem()).handleBack();
61
 	}
65
 	}
62
 
66
 
63
-	void selectTabAtIndex(final int newIndex) {
64
-		tabs.get(selectedIndex).getView().setVisibility(View.GONE);
65
-		selectedIndex = newIndex;
66
-		tabs.get(selectedIndex).getView().setVisibility(View.VISIBLE);
67
+    @Override
68
+    public boolean onTabSelected(int index, boolean wasSelected) {
69
+        if (wasSelected) return false;
70
+        selectTabAtIndex(index);
71
+        return true;
67
 	}
72
 	}
68
-
73
+	
69
 	public void setTabs(final List<ViewController> tabs) {
74
 	public void setTabs(final List<ViewController> tabs) {
70
 		if (tabs.size() > 5) {
75
 		if (tabs.size() > 5) {
71
 			throw new RuntimeException("Too many tabs!");
76
 			throw new RuntimeException("Too many tabs!");
73
 		this.tabs = tabs;
78
 		this.tabs = tabs;
74
 		getView();
79
 		getView();
75
 		for (int i = 0; i < tabs.size(); i++) {
80
 		for (int i = 0; i < tabs.size(); i++) {
76
-			String title = String.valueOf(i);
77
-			createTab(tabs.get(i), i, title);
81
+		    tabs.get(i).setParentController(this);
82
+			createTab(i, tabs.get(i).options.bottomTabOptions, tabs.get(i).options.bottomTabsOptions);
78
 		}
83
 		}
79
 		selectTabAtIndex(0);
84
 		selectTabAtIndex(0);
80
 	}
85
 	}
81
 
86
 
82
-	private void createTab(ViewController tab, final int index, final String title) {
83
-		bottomNavigationView.getMenu().add(0, index, Menu.NONE, title);
84
-		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
85
-		params.addRule(ABOVE, bottomNavigationView.getId());
86
-		tab.getView().setVisibility(View.GONE);
87
-		getView().addView(tab.getView(), params);
87
+	private void createTab(int index, final BottomTabOptions tabOptions, final BottomTabsOptions bottomTabsOptions) {
88
+	    if (!tabOptions.icon.hasValue()) {
89
+            throw new RuntimeException("BottomTab must have an icon");
90
+        }
91
+        imageLoader.loadIcon(getActivity(), tabOptions.icon.get(), new ImageLoader.ImageLoadingListener() {
92
+            @Override
93
+            public void onComplete(@NonNull Drawable drawable) {
94
+                setIconColor(drawable, bottomTabsOptions);
95
+                AHBottomNavigationItem item = new AHBottomNavigationItem(tabOptions.title.get(""), drawable);
96
+                bottomTabs.addItem(item);
97
+                bottomTabs.post(() -> bottomTabs.setTabTag(index, tabOptions.testId));
98
+            }
99
+
100
+            @Override
101
+            public void onError(Throwable error) {
102
+                error.printStackTrace();
103
+            }
104
+        });
105
+
106
+        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
107
+        params.addRule(ABOVE, bottomTabs.getId());
88
 	}
108
 	}
89
 
109
 
90
-	int getSelectedIndex() {
91
-		return selectedIndex;
110
+    private void setIconColor(Drawable drawable, BottomTabsOptions options) {
111
+        UiUtils.tintDrawable(drawable, Color.RED);
112
+    }
113
+
114
+    int getSelectedIndex() {
115
+		return bottomTabs.getCurrentItem();
92
 	}
116
 	}
93
 
117
 
94
 	@NonNull
118
 	@NonNull
99
 
123
 
100
 	@Override
124
 	@Override
101
 	public void mergeOptions(Options options) {
125
 	public void mergeOptions(Options options) {
126
+        this.options.mergeWith(options);
102
         if (options.bottomTabsOptions.currentTabIndex != NO_INT_VALUE) {
127
         if (options.bottomTabsOptions.currentTabIndex != NO_INT_VALUE) {
103
             selectTabAtIndex(options.bottomTabsOptions.currentTabIndex);
128
             selectTabAtIndex(options.bottomTabsOptions.currentTabIndex);
104
         }
129
         }
117
         }
142
         }
118
     }
143
     }
119
 
144
 
120
-	private boolean hasControlWithId(StackController controller, String id) {
145
+    void selectTabAtIndex(final int newIndex) {
146
+        getView().removeView(getCurrentView());
147
+        bottomTabs.setCurrentItem(newIndex, false);
148
+        getView().addView(getCurrentView());
149
+    }
150
+
151
+    @NonNull
152
+    private ViewGroup getCurrentView() {
153
+        return tabs.get(bottomTabs.getCurrentItem()).getView();
154
+    }
155
+
156
+    private boolean hasControlWithId(StackController controller, String id) {
121
 		for (ViewController child : controller.getChildControllers()) {
157
 		for (ViewController child : controller.getChildControllers()) {
122
 			if (id.equals(child.getId())) {
158
 			if (id.equals(child.getId())) {
123
 				return true;
159
 				return true;
128
 		}
164
 		}
129
 		return false;
165
 		return false;
130
 	}
166
 	}
167
+
168
+	@IntRange(from = -1)
169
+    private int findTabContainingComponent(ReactComponent component) {
170
+        for (int i = 0; i < tabs.size(); i++) {
171
+            if (tabs.get(i).containsComponent(component)) {
172
+                return i;
173
+            }
174
+        }
175
+        return -1;
176
+    }
131
 }
177
 }

+ 2
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java View File

18
                                    final String id,
18
                                    final String id,
19
                                    final String componentName,
19
                                    final String componentName,
20
                                    final ReactViewCreator viewCreator,
20
                                    final ReactViewCreator viewCreator,
21
-                                   final Options initialNavigationOptions) {
22
-        super(activity, id);
21
+                                   final Options initialOptions) {
22
+        super(activity, id, initialOptions);
23
         this.componentName = componentName;
23
         this.componentName = componentName;
24
         this.viewCreator = viewCreator;
24
         this.viewCreator = viewCreator;
25
-        options = initialNavigationOptions;
26
     }
25
     }
27
 
26
 
28
     @Override
27
     @Override

+ 12
- 56
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ModalStack.java View File

1
 package com.reactnativenavigation.viewcontrollers;
1
 package com.reactnativenavigation.viewcontrollers;
2
 
2
 
3
-import android.app.Dialog;
4
-import android.content.DialogInterface;
5
 import android.support.annotation.Nullable;
3
 import android.support.annotation.Nullable;
6
-import android.view.KeyEvent;
7
-import android.view.View;
8
 
4
 
9
 import com.facebook.react.bridge.Promise;
5
 import com.facebook.react.bridge.Promise;
10
-import com.reactnativenavigation.R;
6
+import com.reactnativenavigation.viewcontrollers.modal.Modal;
7
+import com.reactnativenavigation.viewcontrollers.modal.ModalCreator;
11
 
8
 
12
 import java.util.ArrayList;
9
 import java.util.ArrayList;
13
 import java.util.List;
10
 import java.util.List;
14
 
11
 
15
-import static android.view.View.MeasureSpec.EXACTLY;
16
-import static android.view.View.MeasureSpec.makeMeasureSpec;
17
-
18
 public class ModalStack {
12
 public class ModalStack {
19
 
13
 
20
 	private List<Modal> modals = new ArrayList<>();
14
 	private List<Modal> modals = new ArrayList<>();
15
+    private ModalCreator creator;
16
+
17
+    public ModalStack(ModalCreator creator) {
18
+        this.creator = creator;
19
+    }
21
 
20
 
22
-	public void showModal(final ViewController viewController, Promise promise) {
23
-		Modal modal = new Modal(viewController);
24
-		modals.add(modal);
21
+    public void showModal(final ViewController viewController, Promise promise) {
22
+        Modal modal = creator.create(viewController);
23
+        modals.add(modal);
25
 		modal.show();
24
 		modal.show();
26
 		if (promise != null) {
25
 		if (promise != null) {
27
 			promise.resolve(viewController.getId());
26
 			promise.resolve(viewController.getId());
52
 	}
51
 	}
53
 
52
 
54
 	@Nullable
53
 	@Nullable
55
-	private Modal findModalByComponentId(String componentId) {
54
+	public Modal findModalByComponentId(String componentId) {
56
 		for (Modal modal : modals) {
55
 		for (Modal modal : modals) {
57
 			if (modal.containsDeepComponentId(componentId)) {
56
 			if (modal.containsDeepComponentId(componentId)) {
58
 				return modal;
57
 				return modal;
64
 	@Nullable
63
 	@Nullable
65
     ViewController findControllerById(String id) {
64
     ViewController findControllerById(String id) {
66
         Modal modal = findModalByComponentId(id);
65
         Modal modal = findModalByComponentId(id);
67
-        return modal != null ? modal.viewController : null;
68
-    }
69
-
70
-    private static class Modal implements DialogInterface.OnKeyListener {
71
-		public final ViewController viewController;
72
-		private final Dialog dialog;
73
-
74
-		Modal(final ViewController viewController) {
75
-			this.viewController = viewController;
76
-			dialog = new Dialog(viewController.getActivity(), R.style.Modal);
77
-			dialog.setOnKeyListener(this);
78
-		}
79
-
80
-		void show() {
81
-			preMeasureView();
82
-			dialog.setContentView(viewController.getView());
83
-			dialog.show();
84
-		}
85
-
86
-		void dismiss() {
87
-			dialog.dismiss();
88
-		}
89
-
90
-		boolean containsDeepComponentId(String componentId) {
91
-			return viewController.findControllerById(componentId) != null;
92
-		}
93
-
94
-		private void preMeasureView() {
95
-			View decorView = viewController.getActivity().getWindow().getDecorView();
96
-			viewController.getView().measure(makeMeasureSpec(decorView.getMeasuredWidth(), EXACTLY), makeMeasureSpec(decorView.getMeasuredHeight(), EXACTLY));
97
-		}
98
-
99
-        @Override
100
-        public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
101
-            if (keyCode == KeyEvent.KEYCODE_BACK) {
102
-                if (event.getAction() == KeyEvent.ACTION_UP) {
103
-                    if (viewController.handleBack()) {
104
-                        return true;
105
-                    }
106
-                    dialog.dismiss();
107
-                }
108
-            }
109
-            return false;
110
-        }
66
+        return modal != null ? modal.viewController.findControllerById(id) : null;
111
     }
67
     }
112
 }
68
 }

+ 103
- 102
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java View File

12
 import com.reactnativenavigation.presentation.OverlayManager;
12
 import com.reactnativenavigation.presentation.OverlayManager;
13
 import com.reactnativenavigation.utils.CompatUtils;
13
 import com.reactnativenavigation.utils.CompatUtils;
14
 import com.reactnativenavigation.utils.NoOpPromise;
14
 import com.reactnativenavigation.utils.NoOpPromise;
15
+import com.reactnativenavigation.viewcontrollers.modal.ModalCreator;
15
 
16
 
16
 import java.util.Collection;
17
 import java.util.Collection;
17
 import java.util.Collections;
18
 import java.util.Collections;
19
 public class Navigator extends ParentController {
20
 public class Navigator extends ParentController {
20
 
21
 
21
     private static final NoOpPromise NO_OP = new NoOpPromise();
22
     private static final NoOpPromise NO_OP = new NoOpPromise();
22
-    private final ModalStack modalStack = new ModalStack();
23
-	private ViewController root;
23
+    private final ModalStack modalStack = new ModalStack(new ModalCreator());
24
+    private ViewController root;
24
     private OverlayManager overlayManager = new OverlayManager();
25
     private OverlayManager overlayManager = new OverlayManager();
25
     private Options defaultOptions = new Options();
26
     private Options defaultOptions = new Options();
26
 
27
 
27
     public Navigator(final Activity activity) {
28
     public Navigator(final Activity activity) {
28
-		super(activity, "navigator" + CompatUtils.generateViewId());
29
-	}
29
+        super(activity, "navigator" + CompatUtils.generateViewId(), new Options());
30
+    }
31
+
32
+    @NonNull
33
+    @Override
34
+    protected ViewGroup createView() {
35
+        return new FrameLayout(getActivity());
36
+    }
30
 
37
 
31
     @NonNull
38
     @NonNull
32
-	@Override
33
-	protected ViewGroup createView() {
34
-		return new FrameLayout(getActivity());
35
-	}
36
-
37
-	@NonNull
38
-	@Override
39
-	public Collection<ViewController> getChildControllers() {
40
-		return root == null ? Collections.emptyList() : Collections.singletonList(root);
41
-	}
42
-
43
-	@Override
44
-	public boolean handleBack() {
45
-		return root != null && root.handleBack();
46
-	}
47
-
48
-	@Override
49
-	public void destroy() {
50
-		modalStack.dismissAll(NO_OP);
51
-		super.destroy();
52
-	}
53
-
54
-	public void setRoot(final ViewController viewController, Promise promise) {
55
-		if (root != null) {
56
-			root.destroy();
57
-		}
58
-
59
-		root = viewController;
60
-		getView().addView(viewController.getView());
39
+    @Override
40
+    public Collection<ViewController> getChildControllers() {
41
+        return root == null ? Collections.emptyList() : Collections.singletonList(root);
42
+    }
43
+
44
+    @Override
45
+    public boolean handleBack() {
46
+        return root != null && root.handleBack();
47
+    }
48
+
49
+    @Override
50
+    public void destroy() {
51
+        modalStack.dismissAll(NO_OP);
52
+        super.destroy();
53
+    }
54
+
55
+    public void setRoot(final ViewController viewController, Promise promise) {
56
+        if (root != null) {
57
+            root.destroy();
58
+        }
59
+
60
+        root = viewController;
61
+        getView().addView(viewController.getView());
61
         promise.resolve(viewController.getId());
62
         promise.resolve(viewController.getId());
62
-	}
63
+    }
63
 
64
 
64
     public void setDefaultOptions(Options defaultOptions) {
65
     public void setDefaultOptions(Options defaultOptions) {
65
         this.defaultOptions = defaultOptions;
66
         this.defaultOptions = defaultOptions;
69
         return defaultOptions;
70
         return defaultOptions;
70
     }
71
     }
71
 
72
 
72
-	public void setOptions(final String componentId, Options options) {
73
-		ViewController target = findControllerById(componentId);
74
-		if (target instanceof NavigationOptionsListener) {
75
-			((NavigationOptionsListener) target).mergeOptions(options);
76
-		}
77
-		if (root instanceof NavigationOptionsListener) {
78
-			((NavigationOptionsListener) root).mergeOptions(options);
79
-		}
80
-	}
81
-
82
-	public void push(final String fromId, final ViewController viewController, Promise promise) {
83
-		ViewController from = findControllerById(fromId);
84
-		if (from != null) {
85
-		    from.performOnParentStack(stack -> ((StackController) stack).animatePush(viewController, promise));
86
-		}
87
-	}
88
-
89
-	void pop(final String fromId, Promise promise) {
90
-		ViewController from = findControllerById(fromId);
91
-		if (from != null) {
92
-		    from.performOnParentStack(stack -> ((StackController) stack).pop(promise));
93
-		}
94
-	}
95
-
96
-	public void popSpecific(final String id, Promise promise) {
97
-		ViewController from = findControllerById(id);
98
-		if (from != null) {
99
-		    from.performOnParentStack(stack -> ((StackController) stack).popSpecific(from, promise), () -> rejectPromise(promise));
100
-		} else {
101
-			rejectPromise(promise);
102
-		}
103
-	}
104
-
105
-	public void popToRoot(final String id, Promise promise) {
106
-		ViewController from = findControllerById(id);
107
-		if (from != null) {
108
-		    from.performOnParentStack(stack -> ((StackController) stack).popToRoot(promise));
109
-		}
110
-	}
111
-
112
-	public void popTo(final String componentId, Promise promise) {
113
-		ViewController target = findControllerById(componentId);
114
-		if (target != null) {
115
-		    target.performOnParentStack(stack -> ((StackController) stack).popTo(target, promise), () -> rejectPromise(promise));
116
-		} else {
117
-			rejectPromise(promise);
118
-		}
119
-	}
120
-
121
-	public void showModal(final ViewController viewController, Promise promise) {
122
-		modalStack.showModal(viewController, promise);
123
-	}
124
-
125
-	public void dismissModal(final String componentId, Promise promise) {
126
-		modalStack.dismissModal(componentId, promise);
127
-	}
128
-
129
-	public void dismissAllModals(Promise promise) {
130
-		modalStack.dismissAll(promise);
131
-	}
132
-
133
-	public void showOverlay(ViewController overlay) {
73
+    public void setOptions(final String componentId, Options options) {
74
+        ViewController target = findControllerById(componentId);
75
+        if (target instanceof NavigationOptionsListener) {
76
+            ((NavigationOptionsListener) target).mergeOptions(options);
77
+        }
78
+        if (root instanceof NavigationOptionsListener) {
79
+            ((NavigationOptionsListener) root).mergeOptions(options);
80
+        }
81
+    }
82
+
83
+    public void push(final String fromId, final ViewController viewController, Promise promise) {
84
+        ViewController from = findControllerById(fromId);
85
+        if (from != null) {
86
+            from.performOnParentStack(stack -> ((StackController) stack).animatePush(viewController, promise));
87
+        }
88
+    }
89
+
90
+    void pop(final String fromId, Promise promise) {
91
+        ViewController from = findControllerById(fromId);
92
+        if (from != null) {
93
+            from.performOnParentStack(stack -> ((StackController) stack).pop(promise));
94
+        }
95
+    }
96
+
97
+    public void popSpecific(final String id, Promise promise) {
98
+        ViewController from = findControllerById(id);
99
+        if (from != null) {
100
+            from.performOnParentStack(stack -> ((StackController) stack).popSpecific(from, promise), () -> rejectPromise(promise));
101
+        } else {
102
+            rejectPromise(promise);
103
+        }
104
+    }
105
+
106
+    public void popToRoot(final String id, Promise promise) {
107
+        ViewController from = findControllerById(id);
108
+        if (from != null) {
109
+            from.performOnParentStack(stack -> ((StackController) stack).popToRoot(promise));
110
+        }
111
+    }
112
+
113
+    public void popTo(final String componentId, Promise promise) {
114
+        ViewController target = findControllerById(componentId);
115
+        if (target != null) {
116
+            target.performOnParentStack(stack -> ((StackController) stack).popTo(target, promise), () -> rejectPromise(promise));
117
+        } else {
118
+            rejectPromise(promise);
119
+        }
120
+    }
121
+
122
+    public void showModal(final ViewController viewController, Promise promise) {
123
+        modalStack.showModal(viewController, promise);
124
+    }
125
+
126
+    public void dismissModal(final String componentId, Promise promise) {
127
+        modalStack.dismissModal(componentId, promise);
128
+    }
129
+
130
+    public void dismissAllModals(Promise promise) {
131
+        modalStack.dismissAll(promise);
132
+    }
133
+
134
+    public void showOverlay(ViewController overlay) {
134
         overlayManager.show(getView(), overlay);
135
         overlayManager.show(getView(), overlay);
135
-	}
136
+    }
136
 
137
 
137
-	public void dismissOverlay(final String componentId) {
138
-		overlayManager.dismiss(getView(), componentId);
139
-	}
138
+    public void dismissOverlay(final String componentId) {
139
+        overlayManager.dismiss(getView(), componentId);
140
+    }
140
 
141
 
141
-	static void rejectPromise(Promise promise) {
142
+    static void rejectPromise(Promise promise) {
142
         promise.reject(new Throwable("Nothing to pop"));
143
         promise.reject(new Throwable("Nothing to pop"));
143
-	}
144
+    }
144
 
145
 
145
     @Nullable
146
     @Nullable
146
     @Override
147
     @Override

+ 13
- 2
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java View File

13
 
13
 
14
 public abstract class ParentController<T extends ViewGroup> extends ViewController {
14
 public abstract class ParentController<T extends ViewGroup> extends ViewController {
15
 
15
 
16
-	public ParentController(final Activity activity, final String id) {
17
-		super(activity, id);
16
+	public ParentController(final Activity activity, final String id, Options initialOptions) {
17
+		super(activity, id, initialOptions);
18
 	}
18
 	}
19
 
19
 
20
 	@NonNull
20
 	@NonNull
44
 		return null;
44
 		return null;
45
 	}
45
 	}
46
 
46
 
47
+	@Override
48
+    public boolean containsComponent(ReactComponent component) {
49
+        if (super.containsComponent(component)) {
50
+            return true;
51
+        }
52
+        for (ViewController child : getChildControllers()) {
53
+            if (child.containsComponent(component)) return true;
54
+        }
55
+        return false;
56
+    }
57
+
47
     public void applyOptions(Options options, ReactComponent childComponent) {
58
     public void applyOptions(Options options, ReactComponent childComponent) {
48
 
59
 
49
     }
60
     }

+ 4
- 2
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/SideMenuController.java View File

7
 import android.view.View;
7
 import android.view.View;
8
 import android.view.ViewGroup;
8
 import android.view.ViewGroup;
9
 
9
 
10
+import com.reactnativenavigation.parse.Options;
11
+
10
 import java.util.ArrayList;
12
 import java.util.ArrayList;
11
 import java.util.Collection;
13
 import java.util.Collection;
12
 
14
 
19
 	private ViewController leftController;
21
 	private ViewController leftController;
20
 	private ViewController rightController;
22
 	private ViewController rightController;
21
 
23
 
22
-	public SideMenuController(final Activity activity, final String id) {
23
-		super(activity, id);
24
+	public SideMenuController(final Activity activity, final String id, Options initialOptions) {
25
+		super(activity, id, initialOptions);
24
 	}
26
 	}
25
 
27
 
26
 	@NonNull
28
 	@NonNull

+ 3
- 2
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java View File

24
     private final NavigationAnimator animator;
24
     private final NavigationAnimator animator;
25
     private StackLayout stackLayout;
25
     private StackLayout stackLayout;
26
 
26
 
27
-    public StackController(final Activity activity, String id) {
28
-		super(activity, id);
27
+    public StackController(final Activity activity, String id, Options initialOptions) {
28
+		super(activity, id, initialOptions);
29
         animator = new NavigationAnimator(activity);
29
         animator = new NavigationAnimator(activity);
30
     }
30
     }
31
 
31
 
37
     @Override
37
     @Override
38
     public void applyOptions(Options options, ReactComponent component) {
38
     public void applyOptions(Options options, ReactComponent component) {
39
         stackLayout.applyOptions(options, component);
39
         stackLayout.applyOptions(options, component);
40
+        applyOnParentController(parentController -> ((ParentController) parentController).applyOptions(options, component));
40
     }
41
     }
41
 
42
 
42
     @Override
43
     @Override

+ 7
- 2
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java View File

39
     private boolean isDestroyed;
39
     private boolean isDestroyed;
40
     private ViewVisibilityListener viewVisibilityListener = new ViewVisibilityListenerAdapter();
40
     private ViewVisibilityListener viewVisibilityListener = new ViewVisibilityListenerAdapter();
41
 
41
 
42
-    public ViewController(Activity activity, String id) {
42
+    public ViewController(Activity activity, String id, Options initialOptions) {
43
         this.activity = activity;
43
         this.activity = activity;
44
         this.id = id;
44
         this.id = id;
45
+        options = initialOptions;
45
     }
46
     }
46
 
47
 
47
     protected abstract T createView();
48
     protected abstract T createView();
125
         return isSameId(id) ? this : null;
126
         return isSameId(id) ? this : null;
126
     }
127
     }
127
 
128
 
129
+    public boolean containsComponent(ReactComponent component) {
130
+        return getView().equals(component);
131
+    }
132
+
128
     public void onViewAppeared() {
133
     public void onViewAppeared() {
129
         isShown = true;
134
         isShown = true;
130
         applyOnParentController(parentController -> {
135
         applyOnParentController(parentController -> {
131
             parentController.clearOptions();
136
             parentController.clearOptions();
132
-            parentController.applyOptions(options, (ReactComponent) getView());
137
+            if (getView() instanceof ReactComponent) parentController.applyOptions(options, (ReactComponent) getView());
133
         });
138
         });
134
     }
139
     }
135
 
140
 

+ 55
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/Modal.java View File

1
+package com.reactnativenavigation.viewcontrollers.modal;
2
+
3
+import android.app.Dialog;
4
+import android.content.DialogInterface;
5
+import android.view.KeyEvent;
6
+import android.view.View;
7
+
8
+import com.reactnativenavigation.R;
9
+import com.reactnativenavigation.viewcontrollers.ViewController;
10
+
11
+import static android.view.View.MeasureSpec.EXACTLY;
12
+import static android.view.View.MeasureSpec.makeMeasureSpec;
13
+
14
+public class Modal implements DialogInterface.OnKeyListener {
15
+    public final ViewController viewController;
16
+    private final Dialog dialog;
17
+
18
+    public Modal(final ViewController viewController) {
19
+        this.viewController = viewController;
20
+        dialog = new Dialog(viewController.getActivity(), R.style.Modal);
21
+        dialog.setOnKeyListener(this);
22
+    }
23
+
24
+    public void show() {
25
+        preMeasureView();
26
+        dialog.setContentView(viewController.getView());
27
+        dialog.show();
28
+    }
29
+
30
+    public void dismiss() {
31
+        dialog.dismiss();
32
+    }
33
+
34
+    public boolean containsDeepComponentId(String componentId) {
35
+        return viewController.findControllerById(componentId) != null;
36
+    }
37
+
38
+    private void preMeasureView() {
39
+        View decorView = viewController.getActivity().getWindow().getDecorView();
40
+        viewController.getView().measure(makeMeasureSpec(decorView.getMeasuredWidth(), EXACTLY), makeMeasureSpec(decorView.getMeasuredHeight(), EXACTLY));
41
+    }
42
+
43
+    @Override
44
+    public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
45
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
46
+            if (event.getAction() == KeyEvent.ACTION_UP) {
47
+                if (viewController.handleBack()) {
48
+                    return true;
49
+                }
50
+                dialog.dismiss();
51
+            }
52
+        }
53
+        return false;
54
+    }
55
+}

+ 9
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalCreator.java View File

1
+package com.reactnativenavigation.viewcontrollers.modal;
2
+
3
+import com.reactnativenavigation.viewcontrollers.ViewController;
4
+
5
+public class ModalCreator {
6
+    public Modal create(ViewController viewController) {
7
+        return new Modal(viewController);
8
+    }
9
+}

+ 1
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java View File

21
 
21
 
22
     private List<ViewController> tabs;
22
     private List<ViewController> tabs;
23
     private TopTabsLayoutCreator viewCreator;
23
     private TopTabsLayoutCreator viewCreator;
24
-    private Options options;
25
 
24
 
26
     public TopTabsController(Activity activity, String id, List<ViewController> tabs, TopTabsLayoutCreator viewCreator, Options options) {
25
     public TopTabsController(Activity activity, String id, List<ViewController> tabs, TopTabsLayoutCreator viewCreator, Options options) {
27
-        super(activity, id);
26
+        super(activity, id, options);
28
         this.viewCreator = viewCreator;
27
         this.viewCreator = viewCreator;
29
-        this.options = options;
30
         this.tabs = tabs;
28
         this.tabs = tabs;
31
         for (ViewController tab : tabs) {
29
         for (ViewController tab : tabs) {
32
             tab.setParentController(this);
30
             tab.setParentController(this);

+ 32
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/BottomTabs.java View File

1
+package com.reactnativenavigation.views;
2
+
3
+import android.annotation.SuppressLint;
4
+import android.content.Context;
5
+
6
+import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
7
+import com.reactnativenavigation.parse.BottomTabsOptions;
8
+import com.reactnativenavigation.parse.Text;
9
+import com.reactnativenavigation.utils.CompatUtils;
10
+
11
+@SuppressLint("ViewConstructor")
12
+public class BottomTabs extends AHBottomNavigation {
13
+    public BottomTabs(Context context, BottomTabsOptions bottomTabsOptions) {
14
+        super(context);
15
+        setId(CompatUtils.generateViewId());
16
+        setTestId(bottomTabsOptions.testId);
17
+    }
18
+
19
+    private void setTestId(Text testId) {
20
+        if (testId.hasValue()) setTag(testId.get());
21
+    }
22
+
23
+    public void setTabTag(int index, Text testId) {
24
+        if (!testId.hasValue()) return;
25
+        if (testId.hasValue()) getViewAtPosition(index).setTag(testId.get());
26
+        if (testId.hasValue()) getViewAtPosition(index).setContentDescription(testId.get());
27
+    }
28
+
29
+    public void setBadge(int bottomTabIndex, Text badge) {
30
+        setNotification(badge.get(), bottomTabIndex);
31
+    }
32
+}

+ 50
- 16
lib/android/app/src/main/java/com/reactnativenavigation/views/TitleBarButton.java View File

4
 import android.graphics.Color;
4
 import android.graphics.Color;
5
 import android.graphics.drawable.Drawable;
5
 import android.graphics.drawable.Drawable;
6
 import android.support.annotation.NonNull;
6
 import android.support.annotation.NonNull;
7
+import android.support.v7.widget.ActionMenuView;
7
 import android.support.v7.widget.Toolbar;
8
 import android.support.v7.widget.Toolbar;
8
 import android.text.Spannable;
9
 import android.text.Spannable;
9
 import android.text.SpannableString;
10
 import android.text.SpannableString;
12
 import android.view.Menu;
13
 import android.view.Menu;
13
 import android.view.MenuItem;
14
 import android.view.MenuItem;
14
 import android.view.View;
15
 import android.view.View;
16
+import android.widget.ImageButton;
15
 import android.widget.TextView;
17
 import android.widget.TextView;
16
 
18
 
17
 import com.reactnativenavigation.parse.Button;
19
 import com.reactnativenavigation.parse.Button;
18
 import com.reactnativenavigation.parse.Options;
20
 import com.reactnativenavigation.parse.Options;
19
-import com.reactnativenavigation.utils.ImageUtils;
21
+import com.reactnativenavigation.parse.Text;
22
+import com.reactnativenavigation.utils.ArrayUtils;
23
+import com.reactnativenavigation.utils.ImageLoader;
20
 import com.reactnativenavigation.utils.UiUtils;
24
 import com.reactnativenavigation.utils.UiUtils;
25
+import com.reactnativenavigation.utils.ViewUtils;
21
 
26
 
22
 import java.util.ArrayList;
27
 import java.util.ArrayList;
28
+import java.util.List;
23
 
29
 
24
 public class TitleBarButton implements MenuItem.OnMenuItemClickListener {
30
 public class TitleBarButton implements MenuItem.OnMenuItemClickListener {
25
     public interface OnClickListener {
31
     public interface OnClickListener {
49
 			setTextColor();
55
 			setTextColor();
50
 			setFontSize(menuItem);
56
 			setFontSize(menuItem);
51
 		}
57
 		}
52
-	}
53
 
58
 
54
-	void applyNavigationIcon(Context context) {
59
+        setTestId(button.testId);
60
+    }
61
+
62
+    void applyNavigationIcon(Context context) {
55
 		if (!hasIcon()) {
63
 		if (!hasIcon()) {
56
 			Log.w("RNN", "Left button needs to have an icon");
64
 			Log.w("RNN", "Left button needs to have an icon");
57
 			return;
65
 			return;
58
 		}
66
 		}
59
 
67
 
60
-		ImageUtils.loadIcon(context, button.icon.get(), new ImageUtils.ImageLoadingListener() {
68
+		new ImageLoader().loadIcon(context, button.icon.get(), new ImageLoader.ImageLoadingListener() {
61
 			@Override
69
 			@Override
62
 			public void onComplete(@NonNull Drawable drawable) {
70
 			public void onComplete(@NonNull Drawable drawable) {
63
 				icon = drawable;
71
 				icon = drawable;
64
                 setIconColor();
72
                 setIconColor();
65
                 setNavigationClickListener();
73
                 setNavigationClickListener();
66
                 toolbar.setNavigationIcon(icon);
74
                 toolbar.setNavigationIcon(icon);
67
-			}
75
+                setLeftButtonTestId();
76
+            }
68
 
77
 
69
 			@Override
78
 			@Override
70
 			public void onError(Throwable error) {
79
 			public void onError(Throwable error) {
73
 		});
82
 		});
74
 	}
83
 	}
75
 
84
 
76
-	private void applyIcon(Context context, final MenuItem menuItem) {
77
-		ImageUtils.loadIcon(context, button.icon.get(), new ImageUtils.ImageLoadingListener() {
85
+    private void setLeftButtonTestId() {
86
+        if (!button.testId.hasValue()) return;
87
+        toolbar.post(() -> {
88
+            ImageButton leftButton = ViewUtils.findChildByClass(toolbar, ImageButton.class);
89
+            if (leftButton != null) {
90
+                leftButton.setTag(button.testId.get());
91
+            }
92
+        });
93
+    }
94
+
95
+    private void applyIcon(Context context, final MenuItem menuItem) {
96
+        new ImageLoader().loadIcon(context, button.icon.get(), new ImageLoader.ImageLoadingListener() {
78
 			@Override
97
 			@Override
79
 			public void onComplete(@NonNull Drawable drawable) {
98
 			public void onComplete(@NonNull Drawable drawable) {
80
 				icon = drawable;
99
 				icon = drawable;
104
 
123
 
105
 	private void setTextColor() {
124
 	private void setTextColor() {
106
 		UiUtils.runOnPreDrawOnce(this.toolbar, () -> {
125
 		UiUtils.runOnPreDrawOnce(this.toolbar, () -> {
107
-            ArrayList<View> outViews = findActualTextViewInMenuByLabel();
126
+            ArrayList<View> outViews = findActualTextViewInMenuByText();
108
             setTextColorForFoundButtonViews(outViews);
127
             setTextColorForFoundButtonViews(outViews);
109
         });
128
         });
110
 	}
129
 	}
125
 		return true;
144
 		return true;
126
 	}
145
 	}
127
 
146
 
128
-	@NonNull
129
-	private ArrayList<View> findActualTextViewInMenuByLabel() {
130
-		ArrayList<View> outViews = new ArrayList<>();
131
-		this.toolbar.findViewsWithText(outViews, button.title.get(), View.FIND_VIEWS_WITH_TEXT);
132
-		return outViews;
133
-	}
134
-
135
 	private void setTextColorForFoundButtonViews(ArrayList<View> buttons) {
147
 	private void setTextColorForFoundButtonViews(ArrayList<View> buttons) {
136
 		for (View button : buttons) {
148
 		for (View button : buttons) {
137
 			((TextView) button).setTextColor(this.button.buttonColor);
149
 			((TextView) button).setTextColor(this.button.buttonColor);
139
 	}
151
 	}
140
 
152
 
141
 	private boolean hasIcon() {
153
 	private boolean hasIcon() {
142
-		return button.icon != null;
154
+		return button.icon.hasValue();
143
 	}
155
 	}
156
+
157
+    private void setTestId(Text testId) {
158
+        if (!testId.hasValue()) return;
159
+        UiUtils.runOnPreDrawOnce(this.toolbar, () -> {
160
+            ActionMenuView buttonsLayout = ViewUtils.findChildByClass(toolbar, ActionMenuView.class);
161
+            List<TextView> buttons = ViewUtils.findChildrenByClass(buttonsLayout, TextView.class);
162
+            for (TextView view : buttons) {
163
+                if (button.title.hasValue() && button.title.get().equals(view.getText())) {
164
+                    view.setTag(testId.get());
165
+                } else if (button.icon.hasValue() && ArrayUtils.contains(view.getCompoundDrawables(), icon)) {
166
+                    view.setTag(testId.get());
167
+                }
168
+            }
169
+        });
170
+    }
171
+
172
+    @NonNull
173
+    private ArrayList<View> findActualTextViewInMenuByText() {
174
+        ArrayList<View> outViews = new ArrayList<>();
175
+        this.toolbar.findViewsWithText(outViews, button.title.get(), View.FIND_VIEWS_WITH_TEXT);
176
+        return outViews;
177
+    }
144
 }
178
 }

+ 4
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java View File

51
         return titleBar.getTitle() != null ? titleBar.getTitle().toString() : "";
51
         return titleBar.getTitle() != null ? titleBar.getTitle().toString() : "";
52
     }
52
     }
53
 
53
 
54
+    public void setTestId(String testId) {
55
+        setTag(testId);
56
+    }
57
+
54
     public void setTitleTextColor(Color color) {
58
     public void setTitleTextColor(Color color) {
55
         if (color.hasValue()) titleBar.setTitleTextColor(color.get());
59
         if (color.hasValue()) titleBar.setTitleTextColor(color.get());
56
     }
60
     }

+ 49
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/ImageLoaderMock.java View File

1
+package com.reactnativenavigation.mocks;
2
+
3
+import android.graphics.Canvas;
4
+import android.graphics.ColorFilter;
5
+import android.graphics.drawable.Drawable;
6
+import android.support.annotation.NonNull;
7
+
8
+import com.reactnativenavigation.utils.ImageLoader;
9
+
10
+import org.mockito.Mockito;
11
+
12
+import static org.mockito.ArgumentMatchers.any;
13
+import static org.mockito.ArgumentMatchers.anyString;
14
+import static org.mockito.Mockito.doAnswer;
15
+
16
+public class ImageLoaderMock {
17
+    private static Drawable mockDrawable = new Drawable() {
18
+        @Override
19
+        public void draw(@NonNull Canvas canvas) {
20
+
21
+        }
22
+
23
+        @Override
24
+        public void setAlpha(int alpha) {
25
+
26
+        }
27
+
28
+        @Override
29
+        public void setColorFilter(@android.support.annotation.Nullable ColorFilter colorFilter) {
30
+
31
+        }
32
+
33
+        @Override
34
+        public int getOpacity() {
35
+            return 0;
36
+        }
37
+    };
38
+
39
+    public static ImageLoader mock() {
40
+        ImageLoader imageLoader = Mockito.mock(ImageLoader.class);
41
+        doAnswer(
42
+                invocation -> {
43
+                    ((ImageLoader.ImageLoadingListener) invocation.getArguments()[2]).onComplete(mockDrawable);
44
+                    return null;
45
+                }
46
+        ).when(imageLoader).loadIcon(any(), anyString(), any());
47
+        return imageLoader;
48
+    }
49
+}

+ 14
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/ModalCreatorMock.java View File

1
+package com.reactnativenavigation.mocks;
2
+
3
+import com.reactnativenavigation.viewcontrollers.ViewController;
4
+import com.reactnativenavigation.viewcontrollers.modal.Modal;
5
+import com.reactnativenavigation.viewcontrollers.modal.ModalCreator;
6
+
7
+import static org.mockito.Mockito.spy;
8
+
9
+public class ModalCreatorMock extends ModalCreator {
10
+    @Override
11
+    public Modal create(ViewController viewController) {
12
+        return spy(new Modal(viewController));
13
+    }
14
+}

+ 2
- 3
lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java View File

15
 
15
 
16
 public class SimpleViewController extends ViewController<FrameLayout> {
16
 public class SimpleViewController extends ViewController<FrameLayout> {
17
 
17
 
18
-    public SimpleViewController(final Activity activity, String id) {
19
-        super(activity, id);
20
-        options = new Options();
18
+    public SimpleViewController(final Activity activity, String id, Options options) {
19
+        super(activity, id, options);
21
     }
20
     }
22
 
21
 
23
     @Override
22
     @Override

+ 10
- 12
lib/android/app/src/test/java/com/reactnativenavigation/parse/NavigationOptionsTest.java View File

27
     private static final Options.BooleanOptions TOP_BAR_HIDE_ON_SCROLL = True;
27
     private static final Options.BooleanOptions TOP_BAR_HIDE_ON_SCROLL = True;
28
     private static final Options.BooleanOptions BOTTOM_TABS_ANIMATE_HIDE = True;
28
     private static final Options.BooleanOptions BOTTOM_TABS_ANIMATE_HIDE = True;
29
     private static final Options.BooleanOptions BOTTOM_TABS_HIDDEN = True;
29
     private static final Options.BooleanOptions BOTTOM_TABS_HIDDEN = True;
30
-    private static final int BOTTOM_TABS_BADGE = 3;
30
+    private static final String BOTTOM_TABS_BADGE = "3";
31
     private static final String BOTTOM_TABS_CURRENT_TAB_ID = "ComponentId";
31
     private static final String BOTTOM_TABS_CURRENT_TAB_ID = "ComponentId";
32
     private static final int BOTTOM_TABS_CURRENT_TAB_INDEX = 1;
32
     private static final int BOTTOM_TABS_CURRENT_TAB_INDEX = 1;
33
     private TypefaceLoader mockLoader;
33
     private TypefaceLoader mockLoader;
47
     public void parsesJson() throws Exception {
47
     public void parsesJson() throws Exception {
48
         JSONObject json = new JSONObject()
48
         JSONObject json = new JSONObject()
49
                 .put("topBar", createTopBar())
49
                 .put("topBar", createTopBar())
50
-                .put("bottomTabs", createTabBar());
50
+                .put("bottomTabs", createBottomTabs());
51
         Options result = Options.parse(mockLoader, json);
51
         Options result = Options.parse(mockLoader, json);
52
         assertResult(result);
52
         assertResult(result);
53
     }
53
     }
62
         assertThat(result.topBarOptions.drawBehind).isEqualTo(TOP_BAR_DRAW_BEHIND);
62
         assertThat(result.topBarOptions.drawBehind).isEqualTo(TOP_BAR_DRAW_BEHIND);
63
         assertThat(result.topBarOptions.hideOnScroll).isEqualTo(TOP_BAR_HIDE_ON_SCROLL);
63
         assertThat(result.topBarOptions.hideOnScroll).isEqualTo(TOP_BAR_HIDE_ON_SCROLL);
64
         assertThat(result.bottomTabsOptions.animateHide).isEqualTo(BOTTOM_TABS_ANIMATE_HIDE);
64
         assertThat(result.bottomTabsOptions.animateHide).isEqualTo(BOTTOM_TABS_ANIMATE_HIDE);
65
-        assertThat(result.bottomTabsOptions.hidden).isEqualTo(BOTTOM_TABS_HIDDEN);
66
-        assertThat(result.bottomTabsOptions.tabBadge).isEqualTo(BOTTOM_TABS_BADGE);
65
+        assertThat(result.bottomTabsOptions.visible).isEqualTo(BOTTOM_TABS_HIDDEN);
67
         assertThat(result.bottomTabsOptions.currentTabId.get()).isEqualTo(BOTTOM_TABS_CURRENT_TAB_ID);
66
         assertThat(result.bottomTabsOptions.currentTabId.get()).isEqualTo(BOTTOM_TABS_CURRENT_TAB_ID);
68
         assertThat(result.bottomTabsOptions.currentTabIndex).isEqualTo(BOTTOM_TABS_CURRENT_TAB_INDEX);
67
         assertThat(result.bottomTabsOptions.currentTabIndex).isEqualTo(BOTTOM_TABS_CURRENT_TAB_INDEX);
69
     }
68
     }
70
 
69
 
71
     @NonNull
70
     @NonNull
72
-    private JSONObject createTabBar() throws JSONException {
71
+    private JSONObject createBottomTabs() throws JSONException {
73
         return new JSONObject()
72
         return new JSONObject()
74
                 .put("currentTabId", BOTTOM_TABS_CURRENT_TAB_ID)
73
                 .put("currentTabId", BOTTOM_TABS_CURRENT_TAB_ID)
75
                 .put("currentTabIndex", BOTTOM_TABS_CURRENT_TAB_INDEX)
74
                 .put("currentTabIndex", BOTTOM_TABS_CURRENT_TAB_INDEX)
76
-                .put("hidden", BOTTOM_TABS_HIDDEN)
77
-                .put("animateHide", BOTTOM_TABS_ANIMATE_HIDE)
78
-                .put("tabBadge", BOTTOM_TABS_BADGE);
75
+                .put("visible", BOTTOM_TABS_HIDDEN)
76
+                .put("animateHide", BOTTOM_TABS_ANIMATE_HIDE);
79
     }
77
     }
80
 
78
 
81
     @NonNull
79
     @NonNull
99
                 .put("textColor", TOP_BAR_TEXT_COLOR)
97
                 .put("textColor", TOP_BAR_TEXT_COLOR)
100
                 .put("textFontSize", TOP_BAR_FONT_SIZE)
98
                 .put("textFontSize", TOP_BAR_FONT_SIZE)
101
                 .put("textFontFamily", TOP_BAR_FONT_FAMILY)
99
                 .put("textFontFamily", TOP_BAR_FONT_FAMILY)
102
-                .put("hidden", TOP_BAR_HIDDEN);
100
+                .put("visible", TOP_BAR_HIDDEN);
103
     }
101
     }
104
 
102
 
105
     @NonNull
103
     @NonNull
107
         return new JSONObject()
105
         return new JSONObject()
108
                 .put("currentTabId", BOTTOM_TABS_CURRENT_TAB_ID)
106
                 .put("currentTabId", BOTTOM_TABS_CURRENT_TAB_ID)
109
                 .put("currentTabIndex", BOTTOM_TABS_CURRENT_TAB_INDEX)
107
                 .put("currentTabIndex", BOTTOM_TABS_CURRENT_TAB_INDEX)
110
-                .put("hidden", BOTTOM_TABS_HIDDEN)
108
+                .put("visible", BOTTOM_TABS_HIDDEN)
111
                 .put("animateHide", BOTTOM_TABS_ANIMATE_HIDE)
109
                 .put("animateHide", BOTTOM_TABS_ANIMATE_HIDE)
112
                 .put("tabBadge", BOTTOM_TABS_BADGE);
110
                 .put("tabBadge", BOTTOM_TABS_BADGE);
113
     }
111
     }
116
     public void mergeDefaultOptions() throws Exception {
114
     public void mergeDefaultOptions() throws Exception {
117
         JSONObject json = new JSONObject();
115
         JSONObject json = new JSONObject();
118
         json.put("topBar", createTopBar());
116
         json.put("topBar", createTopBar());
119
-        json.put("bottomTabs", createTabBar());
117
+        json.put("bottomTabs", createBottomTabs());
120
         Options defaultOptions = Options.parse(mockLoader, json);
118
         Options defaultOptions = Options.parse(mockLoader, json);
121
         Options options = new Options();
119
         Options options = new Options();
122
 
120
 
133
 
131
 
134
         JSONObject json = new JSONObject()
132
         JSONObject json = new JSONObject()
135
                 .put("topBar", createTopBar())
133
                 .put("topBar", createTopBar())
136
-                .put("bottomTabs", createTabBar());
134
+                .put("bottomTabs", createBottomTabs());
137
         Options options = Options.parse(mockLoader, json);
135
         Options options = Options.parse(mockLoader, json);
138
         options.withDefaultOptions(defaultOptions);
136
         options.withDefaultOptions(defaultOptions);
139
         assertResult(options);
137
         assertResult(options);

+ 13
- 0
lib/android/app/src/test/java/com/reactnativenavigation/utils/OptionHelper.java View File

1
+package com.reactnativenavigation.utils;
2
+
3
+import com.reactnativenavigation.parse.Options;
4
+import com.reactnativenavigation.parse.Text;
5
+
6
+public class OptionHelper {
7
+    public static Options createBottomTabOptions() {
8
+        Options options = new Options();
9
+        options.bottomTabOptions.title = new Text("Tab");
10
+        options.bottomTabOptions.icon = new Text("http://127.0.0.1/icon.png");
11
+        return options;
12
+    }
13
+}

+ 48
- 29
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/BottomTabsControllerTest.java View File

1
 package com.reactnativenavigation.viewcontrollers;
1
 package com.reactnativenavigation.viewcontrollers;
2
 
2
 
3
-import android.app.*;
4
-import android.support.annotation.*;
5
-import android.support.design.widget.*;
6
-import android.view.*;
7
-import android.widget.*;
8
-
9
-import com.reactnativenavigation.*;
10
-import com.reactnativenavigation.mocks.*;
11
-
12
-import org.assertj.core.api.iterable.*;
13
-import org.junit.*;
14
-
15
-import java.util.*;
16
-
17
-import static org.assertj.core.api.Java6Assertions.*;
18
-import static org.mockito.Mockito.*;
3
+import android.app.Activity;
4
+import android.support.annotation.NonNull;
5
+import android.widget.RelativeLayout;
6
+
7
+import com.reactnativenavigation.BaseTest;
8
+import com.reactnativenavigation.mocks.ImageLoaderMock;
9
+import com.reactnativenavigation.mocks.MockPromise;
10
+import com.reactnativenavigation.mocks.SimpleViewController;
11
+import com.reactnativenavigation.parse.Options;
12
+import com.reactnativenavigation.utils.ImageLoader;
13
+import com.reactnativenavigation.utils.OptionHelper;
14
+import com.reactnativenavigation.views.BottomTabs;
15
+
16
+import org.junit.Test;
17
+
18
+import java.util.Arrays;
19
+import java.util.Collections;
20
+import java.util.List;
21
+
22
+import static org.assertj.core.api.Java6Assertions.assertThat;
23
+import static org.mockito.Mockito.spy;
24
+import static org.mockito.Mockito.times;
25
+import static org.mockito.Mockito.verify;
26
+import static org.mockito.Mockito.when;
19
 
27
 
20
 public class BottomTabsControllerTest extends BaseTest {
28
 public class BottomTabsControllerTest extends BaseTest {
21
 
29
 
26
     private ViewController child3;
34
     private ViewController child3;
27
     private ViewController child4;
35
     private ViewController child4;
28
     private ViewController child5;
36
     private ViewController child5;
37
+    private Options tabOptions = OptionHelper.createBottomTabOptions();
38
+    private ImageLoader imageLoaderMock = ImageLoaderMock.mock();
29
 
39
 
30
     @Override
40
     @Override
31
     public void beforeEach() {
41
     public void beforeEach() {
32
         super.beforeEach();
42
         super.beforeEach();
33
         activity = newActivity();
43
         activity = newActivity();
34
-        uut = new BottomTabsController(activity, "uut");
35
-        child1 = new SimpleViewController(activity, "child1");
36
-        child2 = new SimpleViewController(activity, "child2");
37
-        child3 = new SimpleViewController(activity, "child3");
38
-        child4 = new SimpleViewController(activity, "child4");
39
-        child5 = new SimpleViewController(activity, "child5");
44
+        uut = new BottomTabsController(activity, imageLoaderMock, "uut", new Options());
45
+        child1 = new SimpleViewController(activity, "child1", tabOptions);
46
+        child2 = new SimpleViewController(activity, "child2", tabOptions);
47
+        child3 = new SimpleViewController(activity, "child3", tabOptions);
48
+        child4 = new SimpleViewController(activity, "child4", tabOptions);
49
+        child5 = new SimpleViewController(activity, "child5", tabOptions);
40
     }
50
     }
41
 
51
 
42
     @Test
52
     @Test
43
     public void containsRelativeLayoutView() throws Exception {
53
     public void containsRelativeLayoutView() throws Exception {
44
         assertThat(uut.getView()).isInstanceOf(RelativeLayout.class);
54
         assertThat(uut.getView()).isInstanceOf(RelativeLayout.class);
45
-        assertThat(uut.getView().getChildAt(0)).isInstanceOf(BottomNavigationView.class);
55
+        assertThat(uut.getView().getChildAt(0)).isInstanceOf(BottomTabs.class);
46
     }
56
     }
47
 
57
 
48
     @Test(expected = RuntimeException.class)
58
     @Test(expected = RuntimeException.class)
49
     public void setTabs_ThrowWhenMoreThan5() throws Exception {
59
     public void setTabs_ThrowWhenMoreThan5() throws Exception {
50
         List<ViewController> tabs = createTabs();
60
         List<ViewController> tabs = createTabs();
51
-        tabs.add(new SimpleViewController(activity, "6"));
61
+        tabs.add(new SimpleViewController(activity, "6", tabOptions));
62
+        uut.setTabs(tabs);
63
+    }
64
+
65
+    @Test
66
+    public void setTab_controllerIsSetAsParent() throws Exception {
67
+        List<ViewController> tabs = createTabs();
52
         uut.setTabs(tabs);
68
         uut.setTabs(tabs);
69
+        for (ViewController tab : tabs) {
70
+            assertThat(tab.getParentController()).isEqualTo(uut);
71
+        }
53
     }
72
     }
54
 
73
 
55
     @Test
74
     @Test
56
-    public void setTabs_AddAllViewsAsGoneExceptFirst() throws Exception {
75
+    public void setTabs_AddAllViews() throws Exception {
57
         List<ViewController> tabs = createTabs();
76
         List<ViewController> tabs = createTabs();
58
         uut.setTabs(tabs);
77
         uut.setTabs(tabs);
59
-        assertThat(uut.getView().getChildCount()).isEqualTo(6);
60
-        assertThat(uut.getChildControllers()).extracting((Extractor<ViewController, Integer>) input -> input.getView().getVisibility()).containsExactly(View.VISIBLE, View.GONE, View.GONE, View.GONE, View.GONE);
78
+        assertThat(uut.getView().getChildCount()).isEqualTo(2);
79
+        assertThat(((ViewController) ((List) uut.getChildControllers()).get(0)).getView().getParent()).isNotNull();
61
     }
80
     }
62
 
81
 
63
     @Test
82
     @Test
68
         uut.selectTabAtIndex(3);
87
         uut.selectTabAtIndex(3);
69
 
88
 
70
         assertThat(uut.getSelectedIndex()).isEqualTo(3);
89
         assertThat(uut.getSelectedIndex()).isEqualTo(3);
71
-        assertThat(uut.getChildControllers()).extracting((Extractor<ViewController, Integer>) input -> input.getView().getVisibility()).containsExactly(View.GONE, View.GONE, View.GONE, View.VISIBLE, View.GONE);
90
+        assertThat(((ViewController) ((List) uut.getChildControllers()).get(0)).getView().getParent()).isNull();
72
     }
91
     }
73
 
92
 
74
     @Test
93
     @Test
75
     public void findControllerById_ReturnsSelfOrChildren() throws Exception {
94
     public void findControllerById_ReturnsSelfOrChildren() throws Exception {
76
         assertThat(uut.findControllerById("123")).isNull();
95
         assertThat(uut.findControllerById("123")).isNull();
77
         assertThat(uut.findControllerById(uut.getId())).isEqualTo(uut);
96
         assertThat(uut.findControllerById(uut.getId())).isEqualTo(uut);
78
-        StackController inner = new StackController(activity, "inner");
97
+        StackController inner = new StackController(activity, "inner", tabOptions);
79
         inner.animatePush(child1, new MockPromise());
98
         inner.animatePush(child1, new MockPromise());
80
         assertThat(uut.findControllerById(child1.getId())).isNull();
99
         assertThat(uut.findControllerById(child1.getId())).isNull();
81
         uut.setTabs(Collections.singletonList(inner));
100
         uut.setTabs(Collections.singletonList(inner));

+ 1
- 1
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ComponentViewControllerTest.java View File

25
         super.beforeEach();
25
         super.beforeEach();
26
         Activity activity = newActivity();
26
         Activity activity = newActivity();
27
         view = spy(new TestComponentLayout(activity, new TestReactView(activity)));
27
         view = spy(new TestComponentLayout(activity, new TestReactView(activity)));
28
-        ParentController<StackLayout> parentController = new StackController(activity, "stack");
28
+        ParentController<StackLayout> parentController = new StackController(activity, "stack", new Options());
29
         uut = new ComponentViewController(activity, "componentId1", "componentName", (activity1, componentId, componentName) -> view, new Options());
29
         uut = new ComponentViewController(activity, "componentId1", "componentName", (activity1, componentId, componentName) -> view, new Options());
30
         uut.setParentController(parentController);
30
         uut.setParentController(parentController);
31
         parentController.ensureViewIsCreated();
31
         parentController.ensureViewIsCreated();

+ 57
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ModalStackTest.java View File

1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import com.reactnativenavigation.BaseTest;
4
+import com.reactnativenavigation.mocks.MockPromise;
5
+import com.reactnativenavigation.mocks.ModalCreatorMock;
6
+import com.reactnativenavigation.mocks.SimpleViewController;
7
+import com.reactnativenavigation.parse.Options;
8
+import com.reactnativenavigation.viewcontrollers.modal.Modal;
9
+
10
+import org.junit.Test;
11
+
12
+import javax.annotation.Nullable;
13
+
14
+import static org.assertj.core.api.Assertions.assertThat;
15
+import static org.mockito.Mockito.spy;
16
+import static org.mockito.Mockito.times;
17
+import static org.mockito.Mockito.verify;
18
+
19
+public class ModalStackTest extends BaseTest {
20
+    private static final String CONTROLLER_ID = "simpleController";
21
+    private ModalStack uut;
22
+    private SimpleViewController viewController;
23
+
24
+    @Override
25
+    public void beforeEach() {
26
+        uut = spy(new ModalStack(new ModalCreatorMock()));
27
+        viewController = new SimpleViewController(newActivity(), CONTROLLER_ID, new Options());
28
+    }
29
+
30
+    @Test
31
+    public void modalRefIsSaved() throws Exception {
32
+        uut.showModal(viewController, new MockPromise());
33
+        assertThat(findModal()).isNotNull();
34
+    }
35
+
36
+    @Test
37
+    public void modalIsShown() throws Exception {
38
+        uut.showModal(viewController, new MockPromise() {
39
+            @Override
40
+            public void resolve(@Nullable Object value) {
41
+                verify(findModal(), times(1)).show();
42
+            }
43
+        });
44
+    }
45
+
46
+    @Test
47
+    public void modalIsDismissed() throws Exception {
48
+        uut.showModal(viewController, new MockPromise());
49
+        assertThat(findModal()).isNotNull();
50
+        uut.dismissModal(CONTROLLER_ID, new MockPromise());
51
+        assertThat(findModal()).isNull();
52
+    }
53
+
54
+    private Modal findModal() {
55
+        return uut.findModalByComponentId("simpleController");
56
+    }
57
+}

+ 55
- 21
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java View File

1
 package com.reactnativenavigation.viewcontrollers;
1
 package com.reactnativenavigation.viewcontrollers;
2
 
2
 
3
-import android.app.*;
4
-import android.support.annotation.*;
3
+import android.app.Activity;
4
+import android.support.annotation.NonNull;
5
 
5
 
6
-import com.reactnativenavigation.*;
7
-import com.reactnativenavigation.mocks.*;
8
-import com.reactnativenavigation.parse.*;
9
-import com.reactnativenavigation.utils.*;
6
+import com.reactnativenavigation.BaseTest;
7
+import com.reactnativenavigation.mocks.ImageLoaderMock;
8
+import com.reactnativenavigation.mocks.MockPromise;
9
+import com.reactnativenavigation.mocks.SimpleComponentViewController;
10
+import com.reactnativenavigation.mocks.SimpleViewController;
11
+import com.reactnativenavigation.parse.Options;
12
+import com.reactnativenavigation.parse.Text;
13
+import com.reactnativenavigation.utils.CompatUtils;
14
+import com.reactnativenavigation.utils.ImageLoader;
15
+import com.reactnativenavigation.utils.OptionHelper;
10
 
16
 
11
-import org.junit.*;
17
+import org.junit.Test;
12
 
18
 
13
-import java.util.*;
19
+import java.util.Arrays;
14
 
20
 
15
 import javax.annotation.Nullable;
21
 import javax.annotation.Nullable;
16
 
22
 
17
-import static org.assertj.core.api.Java6Assertions.*;
18
-import static org.mockito.Mockito.*;
23
+import static org.assertj.core.api.Java6Assertions.assertThat;
24
+import static org.mockito.Mockito.spy;
25
+import static org.mockito.Mockito.times;
26
+import static org.mockito.Mockito.verify;
27
+import static org.mockito.Mockito.when;
19
 
28
 
20
 public class NavigatorTest extends BaseTest {
29
 public class NavigatorTest extends BaseTest {
21
     private Activity activity;
30
     private Activity activity;
22
     private Navigator uut;
31
     private Navigator uut;
23
-    private ParentController parentController;
32
+    private StackController parentController;
24
     private SimpleViewController child1;
33
     private SimpleViewController child1;
25
     private ViewController child2;
34
     private ViewController child2;
26
     private ViewController child3;
35
     private ViewController child3;
27
     private ViewController child4;
36
     private ViewController child4;
28
     private ViewController child5;
37
     private ViewController child5;
29
-
38
+    private Options tabOptions = OptionHelper.createBottomTabOptions();
39
+    private ImageLoader imageLoaderMock;
30
 
40
 
31
     @Override
41
     @Override
32
     public void beforeEach() {
42
     public void beforeEach() {
33
         super.beforeEach();
43
         super.beforeEach();
44
+        imageLoaderMock = ImageLoaderMock.mock();
34
         activity = newActivity();
45
         activity = newActivity();
35
         uut = new Navigator(activity);
46
         uut = new Navigator(activity);
36
-        parentController = new StackController(activity, "stack");
47
+        parentController = spy(new StackController(activity, "stack", new Options()));
37
         parentController.ensureViewIsCreated();
48
         parentController.ensureViewIsCreated();
38
-        child1 = new SimpleViewController(activity, "child1");
39
-        child2 = new SimpleViewController(activity, "child2");
40
-        child3 = new SimpleViewController(activity, "child3");
41
-        child4 = new SimpleViewController(activity, "child4");
42
-        child5 = new SimpleViewController(activity, "child5");
49
+        child1 = new SimpleViewController(activity, "child1", tabOptions);
50
+        child2 = new SimpleViewController(activity, "child2", tabOptions);
51
+        child3 = new SimpleViewController(activity, "child3", tabOptions);
52
+        child4 = new SimpleViewController(activity, "child4", tabOptions);
53
+        child5 = new SimpleViewController(activity, "child5", tabOptions);
43
     }
54
     }
44
 
55
 
45
     @Test
56
     @Test
94
         bottomTabsController.setTabs(Arrays.asList(stack1, stack2));
105
         bottomTabsController.setTabs(Arrays.asList(stack1, stack2));
95
         uut.setRoot(bottomTabsController, new MockPromise());
106
         uut.setRoot(bottomTabsController, new MockPromise());
96
 
107
 
97
-        SimpleViewController newChild = new SimpleViewController(activity, "new child");
108
+        SimpleViewController newChild = new SimpleViewController(activity, "new child", tabOptions);
98
         uut.push(child2.getId(), newChild, new MockPromise());
109
         uut.push(child2.getId(), newChild, new MockPromise());
99
 
110
 
100
         assertThat(stack1.getChildControllers()).doesNotContain(newChild);
111
         assertThat(stack1.getChildControllers()).doesNotContain(newChild);
223
 
234
 
224
     @NonNull
235
     @NonNull
225
     private BottomTabsController newTabs() {
236
     private BottomTabsController newTabs() {
226
-        return new BottomTabsController(activity, "tabsController");
237
+        return new BottomTabsController(activity, imageLoaderMock, "tabsController", new Options());
227
     }
238
     }
228
 
239
 
229
     @NonNull
240
     @NonNull
230
     private StackController newStack() {
241
     private StackController newStack() {
231
-        return new StackController(activity, "stack" + CompatUtils.generateViewId());
242
+        return new StackController(activity, "stack" + CompatUtils.generateViewId(), tabOptions);
232
     }
243
     }
233
 
244
 
234
     @Test
245
     @Test
301
         uut.push(stackController.getId(), child2, new MockPromise());
312
         uut.push(stackController.getId(), child2, new MockPromise());
302
         assertIsChildById(stackController.getView(), child2.getView());
313
         assertIsChildById(stackController.getView(), child2.getView());
303
     }
314
     }
315
+
316
+    @Test
317
+    public void pushedStackCanBePopped() throws Exception {
318
+        StackController parent = new StackController(activity, "someStack", new Options());
319
+        parent.ensureViewIsCreated();
320
+        uut.setRoot(parent, new MockPromise());
321
+        parent.push(parentController, new MockPromise());
322
+
323
+        parentController.push(child1, new MockPromise());
324
+        parentController.push(child2, new MockPromise());
325
+        assertThat(parentController.getChildControllers().size()).isEqualTo(2);
326
+        child1.ensureViewIsCreated();
327
+        child2.ensureViewIsCreated();
328
+
329
+        MockPromise promise = new MockPromise() {
330
+            @Override
331
+            public void resolve(@Nullable Object value) {
332
+                assertThat(parentController.getChildControllers().size()).isEqualTo(1);
333
+            }
334
+        };
335
+        uut.popSpecific("child2", promise);
336
+        verify(parentController, times(1)).popSpecific(child2, promise);
337
+    }
304
 }
338
 }

+ 2
- 2
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java View File

44
                 (activity1, componentId, componentName) -> view,
44
                 (activity1, componentId, componentName) -> view,
45
                 initialNavigationOptions
45
                 initialNavigationOptions
46
         );
46
         );
47
-        stackController = new StackController(activity, "stack");
47
+        stackController = new StackController(activity, "stack", new Options());
48
         stackController.ensureViewIsCreated();
48
         stackController.ensureViewIsCreated();
49
         uut.setParentController(stackController);
49
         uut.setParentController(stackController);
50
     }
50
     }
62
     public void initialOptionsAppliedOnAppear() throws Exception {
62
     public void initialOptionsAppliedOnAppear() throws Exception {
63
         assertThat(uut.getOptions()).isSameAs(initialNavigationOptions);
63
         assertThat(uut.getOptions()).isSameAs(initialNavigationOptions);
64
         initialNavigationOptions.topBarOptions.title = new Text("the title");
64
         initialNavigationOptions.topBarOptions.title = new Text("the title");
65
-        StackController stackController = new StackController(activity, "stackId");
65
+        StackController stackController = new StackController(activity, "stackId", new Options());
66
         stackController.animatePush(uut, new MockPromise() {});
66
         stackController.animatePush(uut, new MockPromise() {});
67
         assertThat(stackController.getTopBar().getTitle()).isEmpty();
67
         assertThat(stackController.getTopBar().getTitle()).isEmpty();
68
 
68
 

+ 10
- 9
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java View File

7
 
7
 
8
 import com.reactnativenavigation.*;
8
 import com.reactnativenavigation.*;
9
 import com.reactnativenavigation.mocks.*;
9
 import com.reactnativenavigation.mocks.*;
10
+import com.reactnativenavigation.parse.Options;
10
 
11
 
11
 import org.junit.*;
12
 import org.junit.*;
12
 
13
 
26
         super.beforeEach();
27
         super.beforeEach();
27
         activity = newActivity();
28
         activity = newActivity();
28
         children = new ArrayList<>();
29
         children = new ArrayList<>();
29
-        uut = new ParentController(activity, "uut") {
30
+        uut = new ParentController(activity, "uut", new Options()) {
30
 
31
 
31
             @NonNull
32
             @NonNull
32
             @Override
33
             @Override
54
 
55
 
55
     @Test
56
     @Test
56
     public void findControllerById_ChildById() throws Exception {
57
     public void findControllerById_ChildById() throws Exception {
57
-        SimpleViewController child1 = new SimpleViewController(activity, "child1");
58
-        SimpleViewController child2 = new SimpleViewController(activity, "child2");
58
+        SimpleViewController child1 = new SimpleViewController(activity, "child1", new Options());
59
+        SimpleViewController child2 = new SimpleViewController(activity, "child2", new Options());
59
         children.add(child1);
60
         children.add(child1);
60
         children.add(child2);
61
         children.add(child2);
61
 
62
 
65
 
66
 
66
     @Test
67
     @Test
67
     public void findControllerById_Recursive() throws Exception {
68
     public void findControllerById_Recursive() throws Exception {
68
-        StackController stackController = new StackController(activity, "stack");
69
-        SimpleViewController child1 = new SimpleViewController(activity, "child1");
70
-        SimpleViewController child2 = new SimpleViewController(activity, "child2");
69
+        StackController stackController = new StackController(activity, "stack", new Options());
70
+        SimpleViewController child1 = new SimpleViewController(activity, "child1", new Options());
71
+        SimpleViewController child2 = new SimpleViewController(activity, "child2", new Options());
71
         stackController.animatePush(child1, new MockPromise());
72
         stackController.animatePush(child1, new MockPromise());
72
         stackController.animatePush(child2, new MockPromise());
73
         stackController.animatePush(child2, new MockPromise());
73
         children.add(stackController);
74
         children.add(stackController);
77
 
78
 
78
     @Test
79
     @Test
79
     public void destroy_DestroysChildren() throws Exception {
80
     public void destroy_DestroysChildren() throws Exception {
80
-        ViewController child1 = spy(new SimpleViewController(activity, "child1"));
81
+        ViewController child1 = spy(new SimpleViewController(activity, "child1", new Options()));
81
         children.add(child1);
82
         children.add(child1);
82
 
83
 
83
         verify(child1, times(0)).destroy();
84
         verify(child1, times(0)).destroy();
87
 
88
 
88
     @Test
89
     @Test
89
     public void optionsAreClearedWhenChildIsAppeared() throws Exception {
90
     public void optionsAreClearedWhenChildIsAppeared() throws Exception {
90
-        StackController stackController = spy(new StackController(activity, "stack"));
91
-        SimpleViewController child1 = new SimpleViewController(activity, "child1");
91
+        StackController stackController = spy(new StackController(activity, "stack", new Options()));
92
+        SimpleViewController child1 = new SimpleViewController(activity, "child1", new Options());
92
         stackController.animatePush(child1, new MockPromise());
93
         stackController.animatePush(child1, new MockPromise());
93
 
94
 
94
         child1.onViewAppeared();
95
         child1.onViewAppeared();

+ 26
- 13
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java View File

1
 package com.reactnativenavigation.viewcontrollers;
1
 package com.reactnativenavigation.viewcontrollers;
2
 
2
 
3
-import android.app.*;
3
+import android.app.Activity;
4
 import android.view.View;
4
 import android.view.View;
5
 
5
 
6
-import com.reactnativenavigation.*;
7
-import com.reactnativenavigation.mocks.*;
6
+import com.reactnativenavigation.BaseTest;
7
+import com.reactnativenavigation.mocks.MockPromise;
8
+import com.reactnativenavigation.mocks.SimpleViewController;
9
+import com.reactnativenavigation.parse.Options;
8
 
10
 
9
-import org.assertj.core.api.iterable.*;
10
-import org.junit.*;
11
+import org.assertj.core.api.iterable.Extractor;
12
+import org.junit.Test;
11
 
13
 
12
 import javax.annotation.Nullable;
14
 import javax.annotation.Nullable;
13
 
15
 
14
-import static org.assertj.core.api.Java6Assertions.*;
15
-import static org.mockito.Mockito.*;
16
+import static org.assertj.core.api.Java6Assertions.assertThat;
17
+import static org.mockito.Mockito.spy;
18
+import static org.mockito.Mockito.times;
19
+import static org.mockito.Mockito.verify;
16
 
20
 
17
 public class StackControllerTest extends BaseTest {
21
 public class StackControllerTest extends BaseTest {
18
 
22
 
26
     public void beforeEach() {
30
     public void beforeEach() {
27
         super.beforeEach();
31
         super.beforeEach();
28
         activity = newActivity();
32
         activity = newActivity();
29
-        uut = new StackController(activity, "uut");
30
-        child1 = new SimpleViewController(activity, "child1");
31
-        child2 = new SimpleViewController(activity, "child2");
32
-        child3 = new SimpleViewController(activity, "child3");
33
+        uut = new StackController(activity, "uut", new Options());
34
+        child1 = new SimpleViewController(activity, "child1", new Options());
35
+        child2 = new SimpleViewController(activity, "child2", new Options());
36
+        child3 = new SimpleViewController(activity, "child3", new Options());
33
     }
37
     }
34
 
38
 
35
     @Test
39
     @Test
84
         uut.animatePush(child1, new MockPromise());
88
         uut.animatePush(child1, new MockPromise());
85
         assertThat(child1.getParentController()).isEqualTo(uut);
89
         assertThat(child1.getParentController()).isEqualTo(uut);
86
 
90
 
87
-        StackController anotherNavController = new StackController(activity, "another");
91
+        StackController anotherNavController = new StackController(activity, "another", new Options());
88
         anotherNavController.animatePush(child2, new MockPromise());
92
         anotherNavController.animatePush(child2, new MockPromise());
89
         assertThat(child2.getParentController()).isEqualTo(anotherNavController);
93
         assertThat(child2.getParentController()).isEqualTo(anotherNavController);
90
     }
94
     }
264
 
268
 
265
     @Test
269
     @Test
266
     public void findControllerById_Deeply() throws Exception {
270
     public void findControllerById_Deeply() throws Exception {
267
-        StackController stack = new StackController(activity, "stack2");
271
+        StackController stack = new StackController(activity, "stack2", new Options());
268
         stack.animatePush(child2, new MockPromise());
272
         stack.animatePush(child2, new MockPromise());
269
         uut.animatePush(stack, new MockPromise());
273
         uut.animatePush(stack, new MockPromise());
270
         assertThat(uut.findControllerById(child2.getId())).isEqualTo(child2);
274
         assertThat(uut.findControllerById(child2.getId())).isEqualTo(child2);
325
         });
329
         });
326
     }
330
     }
327
 
331
 
332
+    @Test
333
+    public void stackCanBePushed() throws Exception {
334
+        StackController parent = new StackController(activity, "someStack", new Options());
335
+        parent.ensureViewIsCreated();
336
+        parent.push(uut, new MockPromise());
337
+        uut.onViewAppeared();
338
+        assertThat(parent.getView().getChildAt(1)).isEqualTo(uut.getView());
339
+    }
340
+
328
     private void assertContainsOnlyId(String... ids) {
341
     private void assertContainsOnlyId(String... ids) {
329
         assertThat(uut.size()).isEqualTo(ids.length);
342
         assertThat(uut.size()).isEqualTo(ids.length);
330
         assertThat(uut.getChildControllers()).extracting((Extractor<ViewController, String>) ViewController::getId).containsOnly(ids);
343
         assertThat(uut.getChildControllers()).extracting((Extractor<ViewController, String>) ViewController::getId).containsOnly(ids);

+ 0
- 25
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsControllerMock.java View File

1
-package com.reactnativenavigation.viewcontrollers;
2
-
3
-import android.app.*;
4
-import android.support.annotation.*;
5
-import android.view.*;
6
-
7
-import java.util.*;
8
-
9
-public class TopTabsControllerMock extends ParentController {
10
-    TopTabsControllerMock(Activity activity, String id) {
11
-        super(activity, id);
12
-    }
13
-
14
-    @NonNull
15
-    @Override
16
-    protected ViewGroup createView() {
17
-        return null;
18
-    }
19
-
20
-    @NonNull
21
-    @Override
22
-    public Collection<? extends ViewController> getChildControllers() {
23
-        return null;
24
-    }
25
-}

+ 1
- 1
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java View File

50
         uut = spy(new TopTabsController(activity, "componentId", tabControllers, layoutCreator, options));
50
         uut = spy(new TopTabsController(activity, "componentId", tabControllers, layoutCreator, options));
51
         tabControllers.forEach(viewController -> viewController.setParentController(uut));
51
         tabControllers.forEach(viewController -> viewController.setParentController(uut));
52
 
52
 
53
-        parentController = spy(new StackController(activity, "stackId"));
53
+        parentController = spy(new StackController(activity, "stackId", new Options()));
54
         uut.setParentController(parentController);
54
         uut.setParentController(parentController);
55
     }
55
     }
56
 
56
 

+ 5
- 4
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java View File

9
 import com.reactnativenavigation.BaseTest;
9
 import com.reactnativenavigation.BaseTest;
10
 import com.reactnativenavigation.mocks.MockPromise;
10
 import com.reactnativenavigation.mocks.MockPromise;
11
 import com.reactnativenavigation.mocks.SimpleViewController;
11
 import com.reactnativenavigation.mocks.SimpleViewController;
12
+import com.reactnativenavigation.parse.Options;
12
 
13
 
13
 import org.assertj.android.api.Assertions;
14
 import org.assertj.android.api.Assertions;
14
 import org.junit.Test;
15
 import org.junit.Test;
31
     public void beforeEach() {
32
     public void beforeEach() {
32
         super.beforeEach();
33
         super.beforeEach();
33
         activity = newActivity();
34
         activity = newActivity();
34
-        uut = new SimpleViewController(activity, "uut");
35
+        uut = new SimpleViewController(activity, "uut", new Options());
35
     }
36
     }
36
 
37
 
37
     @Test
38
     @Test
47
     @Test
48
     @Test
48
     public void canOverrideViewCreation() throws Exception {
49
     public void canOverrideViewCreation() throws Exception {
49
         final FrameLayout otherView = new FrameLayout(activity);
50
         final FrameLayout otherView = new FrameLayout(activity);
50
-        ViewController myController = new ViewController(activity, "vc") {
51
+        ViewController myController = new ViewController(activity, "vc", new Options()) {
51
             @Override
52
             @Override
52
             protected FrameLayout createView() {
53
             protected FrameLayout createView() {
53
                 return otherView;
54
                 return otherView;
59
     @Test
60
     @Test
60
     public void holdsAReferenceToStackControllerOrNull() throws Exception {
61
     public void holdsAReferenceToStackControllerOrNull() throws Exception {
61
         assertThat(uut.getParentController()).isNull();
62
         assertThat(uut.getParentController()).isNull();
62
-        StackController nav = new StackController(activity, "stack");
63
+        StackController nav = new StackController(activity, "stack", new Options());
63
         nav.animatePush(uut, new MockPromise());
64
         nav.animatePush(uut, new MockPromise());
64
         assertThat(uut.getParentController()).isEqualTo(nav);
65
         assertThat(uut.getParentController()).isEqualTo(nav);
65
     }
66
     }
143
 
144
 
144
     @Test
145
     @Test
145
     public void onDestroy_RemovesGlobalLayoutListener() throws Exception {
146
     public void onDestroy_RemovesGlobalLayoutListener() throws Exception {
146
-        new SimpleViewController(activity, "ensureNotNull").destroy();
147
+        new SimpleViewController(activity, "ensureNotNull", new Options()).destroy();
147
 
148
 
148
         ViewController spy = spy(uut);
149
         ViewController spy = spy(uut);
149
         View view = spy.getView();
150
         View view = spy.getView();

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

13
 @property (nonatomic, strong) NSNumber* hideShadow;
13
 @property (nonatomic, strong) NSNumber* hideShadow;
14
 @property (nonatomic, strong) NSNumber* backgroundColor;
14
 @property (nonatomic, strong) NSNumber* backgroundColor;
15
 @property (nonatomic, strong) NSNumber* textColor;
15
 @property (nonatomic, strong) NSNumber* textColor;
16
+@property (nonatomic, strong) NSNumber* tabColor;
17
+@property (nonatomic, strong) NSNumber* selectedTabColor;
16
 @property (nonatomic, strong) NSNumber* selectedTextColor;
18
 @property (nonatomic, strong) NSNumber* selectedTextColor;
17
 @property (nonatomic, strong) NSString* fontFamily;
19
 @property (nonatomic, strong) NSString* fontFamily;
18
 @property (nonatomic, strong) NSNumber* fontSize;
20
 @property (nonatomic, strong) NSNumber* fontSize;

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

97
 
97
 
98
 - (UIViewController<RNNRootViewProtocol> *)createStack:(RNNLayoutNode*)node {
98
 - (UIViewController<RNNRootViewProtocol> *)createStack:(RNNLayoutNode*)node {
99
 	RNNNavigationController* vc = [[RNNNavigationController alloc] init];
99
 	RNNNavigationController* vc = [[RNNNavigationController alloc] init];
100
-	
100
+	RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initWithDict:node.data[@"options"]];
101
 	NSMutableArray* controllers = [NSMutableArray new];
101
 	NSMutableArray* controllers = [NSMutableArray new];
102
 	for (NSDictionary* child in node.children) {
102
 	for (NSDictionary* child in node.children) {
103
 		[controllers addObject:[self fromTree:child]];
103
 		[controllers addObject:[self fromTree:child]];
104
 	}
104
 	}
105
 	[vc setViewControllers:controllers];
105
 	[vc setViewControllers:controllers];
106
+	[vc setOptions:options];
106
 	
107
 	
107
 	return vc;
108
 	return vc;
108
 }
109
 }
109
 
110
 
110
 -(UIViewController<RNNRootViewProtocol> *)createTabs:(RNNLayoutNode*)node {
111
 -(UIViewController<RNNRootViewProtocol> *)createTabs:(RNNLayoutNode*)node {
111
 	RNNTabBarController* vc = [[RNNTabBarController alloc] init];
112
 	RNNTabBarController* vc = [[RNNTabBarController alloc] init];
113
+	RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initWithDict:node.data[@"options"]];
112
 	
114
 	
113
 	NSMutableArray* controllers = [NSMutableArray new];
115
 	NSMutableArray* controllers = [NSMutableArray new];
114
 	for (NSDictionary *child in node.children) {
116
 	for (NSDictionary *child in node.children) {
119
 		[controllers addObject:childVc];
121
 		[controllers addObject:childVc];
120
 	}
122
 	}
121
 	[vc setViewControllers:controllers];
123
 	[vc setViewControllers:controllers];
124
+	[vc setOptions:options];
122
 	
125
 	
123
 	return vc;
126
 	return vc;
124
 }
127
 }

+ 7
- 0
lib/ios/RNNCustomTitleView.h View File

1
+#import <UIKit/UIKit.h>
2
+
3
+@interface RNNCustomTitleView : UIView
4
+
5
+-(instancetype)initWithFrame:(CGRect)frame subView:(UIView*)subView alignment:(NSString*)alignment;
6
+
7
+@end

+ 47
- 0
lib/ios/RNNCustomTitleView.m View File

1
+#import "RNNCustomTitleView.h"
2
+
3
+@interface RNNCustomTitleView ()
4
+@property (nonatomic, strong) UIView *subView;
5
+@property (nonatomic, strong) NSString *subViewAlign;
6
+@end
7
+
8
+@implementation RNNCustomTitleView
9
+
10
+
11
+-(instancetype)initWithFrame:(CGRect)frame subView:(UIView*)subView alignment:(NSString*)alignment {
12
+    self = [super initWithFrame:frame];
13
+    
14
+    if (self) {
15
+        self.backgroundColor = [UIColor clearColor];
16
+        self.subView = subView;
17
+        self.subViewAlign = alignment;
18
+        
19
+        subView.frame = self.bounds;
20
+        [self addSubview:subView];
21
+    }
22
+    
23
+    return self;
24
+}
25
+
26
+
27
+-(void)layoutSubviews {
28
+    [super layoutSubviews];
29
+    
30
+    if ([self.subViewAlign isEqualToString:@"fill"]) {
31
+        self.subView.frame = self.bounds;
32
+    }
33
+    else {
34
+        
35
+        CGFloat superViewWidth = self.superview.frame.size.width;
36
+        CGFloat paddingLeftFromCenter = (superViewWidth/2) - self.frame.origin.x;
37
+        CGFloat paddingRightFromCenter = self.frame.size.width - paddingLeftFromCenter;;
38
+        CGRect reactViewFrame = self.subView.bounds;
39
+        CGFloat minPadding = MIN(paddingLeftFromCenter, paddingRightFromCenter);
40
+        
41
+        reactViewFrame.size.width = minPadding*2;
42
+        reactViewFrame.origin.x = paddingLeftFromCenter - minPadding;
43
+        self.subView.frame = reactViewFrame;
44
+    }
45
+}
46
+
47
+@end

+ 5
- 1
lib/ios/RNNNavigationButtons.m View File

54
 -(RNNUIBarButtonItem*)buildButton: (NSDictionary*)dictionary {
54
 -(RNNUIBarButtonItem*)buildButton: (NSDictionary*)dictionary {
55
 	NSString* buttonId = dictionary[@"id"];
55
 	NSString* buttonId = dictionary[@"id"];
56
 	NSString* title = dictionary[@"title"];
56
 	NSString* title = dictionary[@"title"];
57
+	NSString* component = dictionary[@"component"];
57
 	
58
 	
58
 	if (!buttonId) {
59
 	if (!buttonId) {
59
 		@throw [NSException exceptionWithName:@"NSInvalidArgumentException" reason:[@"button id is not specified " stringByAppendingString:title] userInfo:nil];
60
 		@throw [NSException exceptionWithName:@"NSInvalidArgumentException" reason:[@"button id is not specified " stringByAppendingString:title] userInfo:nil];
66
 	}
67
 	}
67
 	
68
 	
68
 	RNNUIBarButtonItem *barButtonItem;
69
 	RNNUIBarButtonItem *barButtonItem;
69
-	if (iconImage) {
70
+	if (component) {
71
+		RCTRootView *view = (RCTRootView*)[self.viewController.creator createRootView:component rootViewId:buttonId];
72
+		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withCustomView:view];
73
+	} else if (iconImage) {
70
 		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withIcon:iconImage];
74
 		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withIcon:iconImage];
71
 	} else if (title) {
75
 	} else if (title) {
72
 		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withTitle:title];
76
 		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withTitle:title];

+ 4
- 0
lib/ios/RNNNavigationController.m View File

16
 	return rootVC.isAnimated;
16
 	return rootVC.isAnimated;
17
 }
17
 }
18
 
18
 
19
+- (void)setOptions:(RNNNavigationOptions *)options {
20
+	((UIViewController<RNNRootViewProtocol>*)self.topViewController).options = options;
21
+}
22
+
19
 - (NSString *)componentId {
23
 - (NSString *)componentId {
20
 	return ((UIViewController<RNNRootViewProtocol>*)self.topViewController).componentId;
24
 	return ((UIViewController<RNNRootViewProtocol>*)self.topViewController).componentId;
21
 }
25
 }

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

15
 @property (nonatomic, strong) RNNEventEmitter *eventEmitter;
15
 @property (nonatomic, strong) RNNEventEmitter *eventEmitter;
16
 @property (nonatomic, strong) NSString* componentId;
16
 @property (nonatomic, strong) NSString* componentId;
17
 @property (nonatomic, strong) RNNTopTabsViewController* topTabsViewController;
17
 @property (nonatomic, strong) RNNTopTabsViewController* topTabsViewController;
18
+@property (nonatomic) id<RNNRootViewCreator> creator;
18
 @property (nonatomic, strong) RNNAnimator* animator;
19
 @property (nonatomic, strong) RNNAnimator* animator;
19
 
20
 
20
 -(instancetype)initWithName:(NSString*)name
21
 -(instancetype)initWithName:(NSString*)name

+ 22
- 1
lib/ios/RNNRootViewController.m View File

2
 #import "RNNRootViewController.h"
2
 #import "RNNRootViewController.h"
3
 #import <React/RCTConvert.h>
3
 #import <React/RCTConvert.h>
4
 #import "RNNAnimator.h"
4
 #import "RNNAnimator.h"
5
+#import "RNNCustomTitleView.h"
5
 
6
 
6
 @interface RNNRootViewController()
7
 @interface RNNRootViewController()
7
 @property (nonatomic, strong) NSString* componentName;
8
 @property (nonatomic, strong) NSString* componentName;
8
 @property (nonatomic) BOOL _statusBarHidden;
9
 @property (nonatomic) BOOL _statusBarHidden;
9
-
10
 @end
10
 @end
11
 
11
 
12
 @implementation RNNRootViewController
12
 @implementation RNNRootViewController
22
 	self.options = options;
22
 	self.options = options;
23
 	self.eventEmitter = eventEmitter;
23
 	self.eventEmitter = eventEmitter;
24
 	self.animator = [[RNNAnimator alloc] initWithTransitionOptions:self.options.customTransition];
24
 	self.animator = [[RNNAnimator alloc] initWithTransitionOptions:self.options.customTransition];
25
+	self.creator = creator;
25
 	self.view = [creator createRootView:self.componentName rootViewId:self.componentId];
26
 	self.view = [creator createRootView:self.componentName rootViewId:self.componentId];
26
 	
27
 	
27
 	[[NSNotificationCenter defaultCenter] addObserver:self
28
 	[[NSNotificationCenter defaultCenter] addObserver:self
37
 -(void)viewWillAppear:(BOOL)animated{
38
 -(void)viewWillAppear:(BOOL)animated{
38
 	[super viewWillAppear:animated];
39
 	[super viewWillAppear:animated];
39
 	[self.options applyOn:self];
40
 	[self.options applyOn:self];
41
+	[self setCustomNavigationTitleView];
42
+	[self setCustomNavigationBarView];
40
 }
43
 }
41
 
44
 
42
 -(void)viewDidAppear:(BOOL)animated {
45
 -(void)viewDidAppear:(BOOL)animated {
57
 	[super viewDidLoad];
60
 	[super viewDidLoad];
58
 }
61
 }
59
 
62
 
63
+- (void)setCustomNavigationTitleView {
64
+	if (self.options.topBar.customTitleViewName) {
65
+		UIView *reactView = [_creator createRootView:self.options.topBar.customTitleViewName rootViewId:self.options.topBar.customTitleViewName];
66
+		
67
+		RNNCustomTitleView *titleView = [[RNNCustomTitleView alloc] initWithFrame:self.navigationController.navigationBar.bounds subView:reactView alignment:nil];
68
+		self.navigationItem.titleView = titleView;
69
+	}
70
+}
71
+
72
+- (void)setCustomNavigationBarView {
73
+	if (self.options.topBar.customViewName) {
74
+		UIView *reactView = [_creator createRootView:self.options.topBar.customViewName rootViewId:@"navBar"];
75
+		
76
+		RNNCustomTitleView *titleView = [[RNNCustomTitleView alloc] initWithFrame:self.navigationController.navigationBar.bounds subView:reactView alignment:nil];
77
+		[self.navigationController.navigationBar addSubview:titleView];
78
+	}
79
+}
80
+
60
 -(BOOL)isCustomTransitioned {
81
 -(BOOL)isCustomTransitioned {
61
 	return self.options.customTransition != nil;
82
 	return self.options.customTransition != nil;
62
 }
83
 }

+ 3
- 0
lib/ios/RNNRootViewProtocol.h View File

2
 
2
 
3
 @protocol RNNRootViewProtocol <NSObject, UINavigationControllerDelegate>
3
 @protocol RNNRootViewProtocol <NSObject, UINavigationControllerDelegate>
4
 
4
 
5
+@optional
6
+- (void)setOptions:(RNNNavigationOptions*)options;
7
+
5
 @required
8
 @required
6
 
9
 
7
 - (BOOL)isCustomTransitioned;
10
 - (BOOL)isCustomTransitioned;

+ 4
- 0
lib/ios/RNNTabBarController.m View File

41
 	return YES;
41
 	return YES;
42
 }
42
 }
43
 
43
 
44
+- (void)setOptions:(RNNNavigationOptions *)options {
45
+	[((UIViewController<RNNRootViewProtocol>*)self.selectedViewController) setOptions:options];
46
+}
47
+
44
 - (NSString *)componentId {
48
 - (NSString *)componentId {
45
 	return ((UIViewController<RNNRootViewProtocol>*)self.selectedViewController).componentId;
49
 	return ((UIViewController<RNNRootViewProtocol>*)self.selectedViewController).componentId;
46
 }
50
 }

+ 3
- 0
lib/ios/RNNTopBarOptions.h View File

21
 @property (nonatomic, strong) NSNumber* largeTitle;
21
 @property (nonatomic, strong) NSNumber* largeTitle;
22
 @property (nonatomic, strong) NSString* testID;
22
 @property (nonatomic, strong) NSString* testID;
23
 
23
 
24
+@property (nonatomic, strong) NSString* customTitleViewName;
25
+@property (nonatomic, strong) NSString* customViewName;
26
+
24
 @end
27
 @end

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

1
 #import "RNNTopBarOptions.h"
1
 #import "RNNTopBarOptions.h"
2
 #import "RNNNavigationButtons.h"
2
 #import "RNNNavigationButtons.h"
3
+#import "RNNCustomTitleView.h"
3
 
4
 
4
 extern const NSInteger BLUR_TOPBAR_TAG;
5
 extern const NSInteger BLUR_TOPBAR_TAG;
5
 
6
 

+ 4
- 1
lib/ios/RNNUIBarButtonItem.h View File

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

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

16
 	return self;
16
 	return self;
17
 }
17
 }
18
 
18
 
19
+-(instancetype)init:(NSString*)buttonId withCustomView:(RCTRootView *)reactView {
20
+	self = [super initWithCustomView:reactView];
21
+	
22
+	reactView.sizeFlexibility = RCTRootViewSizeFlexibilityWidthAndHeight;
23
+	reactView.delegate = self;
24
+	reactView.backgroundColor = [UIColor clearColor];
25
+	self.buttonId = buttonId;
26
+	return self;
27
+}
28
+
29
+- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView {
30
+	CGSize size = rootView.intrinsicContentSize;
31
+	rootView.frame = CGRectMake(0, 0, size.width, size.height);
32
+	self.width = size.width;
33
+}
34
+
19
 @end
35
 @end

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

61
 		2DCD9196200014A900EDC75D /* RNNBridgeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DCD9194200014A900EDC75D /* RNNBridgeManager.m */; };
61
 		2DCD9196200014A900EDC75D /* RNNBridgeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DCD9194200014A900EDC75D /* RNNBridgeManager.m */; };
62
 		390AD477200F499D00A8250D /* RNNSwizzles.h in Headers */ = {isa = PBXBuildFile; fileRef = 390AD475200F499D00A8250D /* RNNSwizzles.h */; };
62
 		390AD477200F499D00A8250D /* RNNSwizzles.h in Headers */ = {isa = PBXBuildFile; fileRef = 390AD475200F499D00A8250D /* RNNSwizzles.h */; };
63
 		390AD478200F499D00A8250D /* RNNSwizzles.m in Sources */ = {isa = PBXBuildFile; fileRef = 390AD476200F499D00A8250D /* RNNSwizzles.m */; };
63
 		390AD478200F499D00A8250D /* RNNSwizzles.m in Sources */ = {isa = PBXBuildFile; fileRef = 390AD476200F499D00A8250D /* RNNSwizzles.m */; };
64
+		5016E8EF20209690009D4F7C /* RNNCustomTitleView.h in Headers */ = {isa = PBXBuildFile; fileRef = 5016E8ED2020968F009D4F7C /* RNNCustomTitleView.h */; };
65
+		5016E8F020209690009D4F7C /* RNNCustomTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5016E8EE2020968F009D4F7C /* RNNCustomTitleView.m */; };
64
 		5032774E2015E86D00ECD75D /* RNNNavigationEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 5032774C2015E86D00ECD75D /* RNNNavigationEvent.h */; };
66
 		5032774E2015E86D00ECD75D /* RNNNavigationEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 5032774C2015E86D00ECD75D /* RNNNavigationEvent.h */; };
65
 		5032774F2015E86D00ECD75D /* RNNNavigationEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 5032774D2015E86D00ECD75D /* RNNNavigationEvent.m */; };
67
 		5032774F2015E86D00ECD75D /* RNNNavigationEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 5032774D2015E86D00ECD75D /* RNNNavigationEvent.m */; };
66
 		503277602016302900ECD75D /* RNNComponentLifecycleEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 5032775E2016302900ECD75D /* RNNComponentLifecycleEvent.h */; };
68
 		503277602016302900ECD75D /* RNNComponentLifecycleEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 5032775E2016302900ECD75D /* RNNComponentLifecycleEvent.h */; };
238
 		2DCD9194200014A900EDC75D /* RNNBridgeManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNBridgeManager.m; sourceTree = "<group>"; };
240
 		2DCD9194200014A900EDC75D /* RNNBridgeManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNBridgeManager.m; sourceTree = "<group>"; };
239
 		390AD475200F499D00A8250D /* RNNSwizzles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNSwizzles.h; sourceTree = "<group>"; };
241
 		390AD475200F499D00A8250D /* RNNSwizzles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNSwizzles.h; sourceTree = "<group>"; };
240
 		390AD476200F499D00A8250D /* RNNSwizzles.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNSwizzles.m; sourceTree = "<group>"; };
242
 		390AD476200F499D00A8250D /* RNNSwizzles.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNSwizzles.m; sourceTree = "<group>"; };
243
+		5016E8ED2020968F009D4F7C /* RNNCustomTitleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNCustomTitleView.h; sourceTree = "<group>"; };
244
+		5016E8EE2020968F009D4F7C /* RNNCustomTitleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNCustomTitleView.m; sourceTree = "<group>"; };
241
 		5032774C2015E86D00ECD75D /* RNNNavigationEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNNavigationEvent.h; sourceTree = "<group>"; };
245
 		5032774C2015E86D00ECD75D /* RNNNavigationEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNNavigationEvent.h; sourceTree = "<group>"; };
242
 		5032774D2015E86D00ECD75D /* RNNNavigationEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNNavigationEvent.m; sourceTree = "<group>"; };
246
 		5032774D2015E86D00ECD75D /* RNNNavigationEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNNavigationEvent.m; sourceTree = "<group>"; };
243
 		5032775E2016302900ECD75D /* RNNComponentLifecycleEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNComponentLifecycleEvent.h; sourceTree = "<group>"; };
247
 		5032775E2016302900ECD75D /* RNNComponentLifecycleEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNComponentLifecycleEvent.h; sourceTree = "<group>"; };
657
 		E8AEDB471F584175000F5A6A /* Components */ = {
661
 		E8AEDB471F584175000F5A6A /* Components */ = {
658
 			isa = PBXGroup;
662
 			isa = PBXGroup;
659
 			children = (
663
 			children = (
664
+				5016E8ED2020968F009D4F7C /* RNNCustomTitleView.h */,
665
+				5016E8EE2020968F009D4F7C /* RNNCustomTitleView.m */,
660
 				E8A5CD601F49114F00E89D0D /* RNNElement.h */,
666
 				E8A5CD601F49114F00E89D0D /* RNNElement.h */,
661
 				E8A5CD611F49114F00E89D0D /* RNNElement.m */,
667
 				E8A5CD611F49114F00E89D0D /* RNNElement.m */,
662
 				E8AEDB3A1F55A1C2000F5A6A /* RNNElementView.h */,
668
 				E8AEDB3A1F55A1C2000F5A6A /* RNNElementView.h */,
694
 				50F5DFC11F407A8C001A00BC /* RNNTabBarController.h in Headers */,
700
 				50F5DFC11F407A8C001A00BC /* RNNTabBarController.h in Headers */,
695
 				50CB3B691FDE911400AA153B /* RNNSideMenuOptions.h in Headers */,
701
 				50CB3B691FDE911400AA153B /* RNNSideMenuOptions.h in Headers */,
696
 				263905BD1E4C6F440023D7D3 /* RCCDrawerProtocol.h in Headers */,
702
 				263905BD1E4C6F440023D7D3 /* RCCDrawerProtocol.h in Headers */,
703
+				5016E8EF20209690009D4F7C /* RNNCustomTitleView.h in Headers */,
697
 				263905C21E4C6F440023D7D3 /* SidebarAnimation.h in Headers */,
704
 				263905C21E4C6F440023D7D3 /* SidebarAnimation.h in Headers */,
698
 				E8E518361F83B94A000467AC /* RNNViewLocation.h in Headers */,
705
 				E8E518361F83B94A000467AC /* RNNViewLocation.h in Headers */,
699
 				263905B51E4C6F440023D7D3 /* MMExampleDrawerVisualStateManager.h in Headers */,
706
 				263905B51E4C6F440023D7D3 /* MMExampleDrawerVisualStateManager.h in Headers */,
856
 				E8A5CD531F464F0400E89D0D /* RNNAnimator.m in Sources */,
863
 				E8A5CD531F464F0400E89D0D /* RNNAnimator.m in Sources */,
857
 				50CB3B6A1FDE911400AA153B /* RNNSideMenuOptions.m in Sources */,
864
 				50CB3B6A1FDE911400AA153B /* RNNSideMenuOptions.m in Sources */,
858
 				261F0E6B1E6F028A00989DE2 /* RNNNavigationStackManager.m in Sources */,
865
 				261F0E6B1E6F028A00989DE2 /* RNNNavigationStackManager.m in Sources */,
866
+				5016E8F020209690009D4F7C /* RNNCustomTitleView.m in Sources */,
859
 				E8DA24411F97459B00CD552B /* RNNElementFinder.m in Sources */,
867
 				E8DA24411F97459B00CD552B /* RNNElementFinder.m in Sources */,
860
 				263905BF1E4C6F440023D7D3 /* RCCTheSideBarManagerViewController.m in Sources */,
868
 				263905BF1E4C6F440023D7D3 /* RCCTheSideBarManagerViewController.m in Sources */,
861
 				7B1126A01E2D263F00F9B03B /* RNNEventEmitter.m in Sources */,
869
 				7B1126A01E2D263F00F9B03B /* RNNEventEmitter.m in Sources */,

+ 7
- 2
lib/src/commands/LayoutTreeCrawler.ts View File

2
 import { OptionsProcessor } from './OptionsProcessor';
2
 import { OptionsProcessor } from './OptionsProcessor';
3
 import { LayoutType, isLayoutType } from './LayoutType';
3
 import { LayoutType, isLayoutType } from './LayoutType';
4
 
4
 
5
+export interface Data {
6
+  name?: string;
7
+  options?: any;
8
+  passProps?: any;
9
+}
5
 export interface LayoutNode {
10
 export interface LayoutNode {
6
   id?: string;
11
   id?: string;
7
   type: LayoutType;
12
   type: LayoutType;
8
-  data: object;
13
+  data: Data;
9
   children: LayoutNode[];
14
   children: LayoutNode[];
10
 }
15
 }
11
 
16
 
24
     if (node.type === LayoutType.Component) {
29
     if (node.type === LayoutType.Component) {
25
       this._handleComponent(node);
30
       this._handleComponent(node);
26
     }
31
     }
32
+    OptionsProcessor.processOptions(node.data.options);
27
     _.forEach(node.children, this.crawl);
33
     _.forEach(node.children, this.crawl);
28
   }
34
   }
29
 
35
 
31
     this._assertComponentDataName(node);
37
     this._assertComponentDataName(node);
32
     this._savePropsToStore(node);
38
     this._savePropsToStore(node);
33
     this._applyStaticOptions(node);
39
     this._applyStaticOptions(node);
34
-    OptionsProcessor.processOptions(node.data.options);
35
   }
40
   }
36
 
41
 
37
   _savePropsToStore(node) {
42
   _savePropsToStore(node) {

+ 13
- 0
package.json View File

38
     "test-unit-ios": "node ./scripts/test.unit.ios.js",
38
     "test-unit-ios": "node ./scripts/test.unit.ios.js",
39
     "pretest-e2e-android": "npm run build",
39
     "pretest-e2e-android": "npm run build",
40
     "test-e2e-android": "node ./scripts/test.e2e.android.js",
40
     "test-e2e-android": "node ./scripts/test.e2e.android.js",
41
+    "test-e2e-android-detox": "node ./scripts/test.e2e.android.detox.js",
41
     "pretest-e2e-ios": "npm run build",
42
     "pretest-e2e-ios": "npm run build",
42
     "test-e2e-ios": "node ./scripts/test.e2e.ios.js",
43
     "test-e2e-ios": "node ./scripts/test.e2e.ios.js",
43
     "test-all": "node ./scripts/test.all.js",
44
     "test-all": "node ./scripts/test.all.js",
123
         "build": "RCT_NO_LAUNCH_PACKAGER=true xcodebuild build -scheme playground_release -project playground/ios/playground.xcodeproj -sdk iphonesimulator -configuration Release -derivedDataPath playground/ios/DerivedData/playground ONLY_ACTIVE_ARCH=YES -quiet",
124
         "build": "RCT_NO_LAUNCH_PACKAGER=true xcodebuild build -scheme playground_release -project playground/ios/playground.xcodeproj -sdk iphonesimulator -configuration Release -derivedDataPath playground/ios/DerivedData/playground ONLY_ACTIVE_ARCH=YES -quiet",
124
         "type": "ios.simulator",
125
         "type": "ios.simulator",
125
         "name": "iPhone SE"
126
         "name": "iPhone SE"
127
+      },
128
+      "android.emu.debug": {
129
+        "binaryPath": "playground/android/app/build/outputs/apk/debug/app-debug.apk",
130
+        "build": "cd playground/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ../..",
131
+        "type": "android.emulator",
132
+        "name": "Nexus_5X_API_26"
133
+      },
134
+      "android.emu.release": {
135
+        "binaryPath": "playground/android/app/build/outputs/apk/release/app-release.apk",
136
+        "build": "cd playground/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ../..",
137
+        "type": "android.emulator",
138
+        "name": "Nexus_5X_API_26"
126
       }
139
       }
127
     }
140
     }
128
   }
141
   }

+ 9
- 0
playground/android/app/build.gradle View File

25
         ndk {
25
         ndk {
26
             abiFilters "armeabi-v7a", "x86"
26
             abiFilters "armeabi-v7a", "x86"
27
         }
27
         }
28
+
29
+        testBuildType System.getProperty('testBuildType', 'debug')  //this will later be used to control the test apk build type
30
+        missingDimensionStrategy "minReactNative", "minReactNative46" //read note
31
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
28
     }
32
     }
29
     signingConfigs {
33
     signingConfigs {
30
         release {
34
         release {
47
     implementation 'com.android.support:design:25.4.0'
51
     implementation 'com.android.support:design:25.4.0'
48
     implementation "com.android.support:appcompat-v7:25.4.0"
52
     implementation "com.android.support:appcompat-v7:25.4.0"
49
     implementation project(':react-native-navigation')
53
     implementation project(':react-native-navigation')
54
+
55
+    androidTestImplementation(project(path: ":detox"))
56
+    androidTestImplementation 'junit:junit:4.12'
57
+    androidTestImplementation 'com.android.support.test:runner:1.0.1'
58
+    androidTestImplementation 'com.android.support.test:rules:1.0.1'
50
 }
59
 }

+ 24
- 0
playground/android/app/src/androidTest/java/com/reactnativenavigation/playground/DetoxTest.java View File

1
+package com.reactnativenavigation.playground;
2
+
3
+import android.support.test.filters.LargeTest;
4
+import android.support.test.rule.ActivityTestRule;
5
+import android.support.test.runner.AndroidJUnit4;
6
+
7
+import com.wix.detox.Detox;
8
+
9
+import org.junit.Rule;
10
+import org.junit.Test;
11
+import org.junit.runner.RunWith;
12
+
13
+@RunWith(AndroidJUnit4.class)
14
+@LargeTest
15
+public class DetoxTest {
16
+
17
+    @Rule
18
+    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
19
+
20
+    @Test
21
+    public void runDetoxTests() throws InterruptedException {
22
+        Detox.runTests(mActivityRule);
23
+    }
24
+}

+ 3
- 0
playground/android/settings.gradle View File

3
 include ':app'
3
 include ':app'
4
 include ':react-native-navigation'
4
 include ':react-native-navigation'
5
 project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../../lib/android/app/')
5
 project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../../lib/android/app/')
6
+
7
+include ':detox'
8
+project(':detox').projectDir = new File(rootProject.projectDir, '../../node_modules/detox/android/detox')

BIN
playground/src/images/one@2x.png View File


BIN
playground/src/images/one_selected@2x.png View File


BIN
playground/src/images/three@2x.png View File


BIN
playground/src/images/three_selected@2x.png View File


BIN
playground/src/images/two@2x.png View File


BIN
playground/src/images/two_selected@2x.png View File


+ 47
- 0
playground/src/screens/CustomTopBar.js View File

1
+const React = require('react');
2
+const { Component } = require('react');
3
+const {
4
+  StyleSheet,
5
+  View,
6
+  TouchableOpacity,
7
+  Text,
8
+  Alert,
9
+  Platform
10
+} = require('react-native');
11
+
12
+class CustomTopBar extends Component {
13
+
14
+  constructor(props) {
15
+    super(props);
16
+    this.state = {};
17
+  }
18
+
19
+  render() {
20
+    return (
21
+      <View style={styles.container}>
22
+        <TouchableOpacity stye={styles.button} onPress={() => Alert.alert(this.props.title, 'Thanks for that :)')}>
23
+          <Text style={styles.text}>Press Me</Text>
24
+        </TouchableOpacity>
25
+      </View>
26
+    );
27
+  }
28
+}
29
+
30
+module.exports = CustomTopBar;
31
+
32
+const styles = StyleSheet.create({
33
+  container: {
34
+    flex: 1,
35
+    justifyContent: 'center',
36
+    alignItems: 'center',
37
+    backgroundColor: 'white'
38
+  },
39
+  button: {
40
+    alignSelf: 'center',
41
+    backgroundColor: 'green'
42
+  },
43
+  text: {
44
+    alignSelf: 'center',
45
+    color: Platform.OS === 'ios' ? 'black' : 'white'
46
+  }
47
+});

+ 5
- 0
playground/src/screens/LifecycleScreen.js View File

37
         <Text style={styles.h1}>{`Lifecycle Screen`}</Text>
37
         <Text style={styles.h1}>{`Lifecycle Screen`}</Text>
38
         <Text style={styles.h1}>{this.state.text}</Text>
38
         <Text style={styles.h1}>{this.state.text}</Text>
39
         <Button title='Push to test didDisappear' testID={testIDs.PUSH_TO_TEST_DID_DISAPPEAR_BUTTON} onPress={this.onClickPush} />
39
         <Button title='Push to test didDisappear' testID={testIDs.PUSH_TO_TEST_DID_DISAPPEAR_BUTTON} onPress={this.onClickPush} />
40
+        <Button title='Pop' testID={testIDs.POP_BUTTON} onPress={() => this.onClickPop()} />
40
         <Text style={styles.footer}>{`this.props.componentId = ${this.props.componentId}`}</Text>
41
         <Text style={styles.footer}>{`this.props.componentId = ${this.props.componentId}`}</Text>
41
       </View>
42
       </View>
42
     );
43
     );
45
   onClickPush() {
46
   onClickPush() {
46
     Navigation.push(this.props.componentId, { component: { name: 'navigation.playground.TextScreen' } });
47
     Navigation.push(this.props.componentId, { component: { name: 'navigation.playground.TextScreen' } });
47
   }
48
   }
49
+
50
+  onClickPop() {
51
+    Navigation.pop(this.props.componentId);
52
+  }
48
 }
53
 }
49
 module.exports = LifecycleScreen;
54
 module.exports = LifecycleScreen;
50
 
55
 

+ 1
- 1
playground/src/screens/OptionsScreen.js View File

63
         <Button title='Top Bar Transparent' onPress={this.onClickTopBarTransparent} />
63
         <Button title='Top Bar Transparent' onPress={this.onClickTopBarTransparent} />
64
         <Button title='Top Bar Opaque' onPress={this.onClickTopBarOpaque} />
64
         <Button title='Top Bar Opaque' onPress={this.onClickTopBarOpaque} />
65
         <Button title='scrollView Screen' testID={testIDs.SCROLLVIEW_SCREEN_BUTTON} onPress={this.onClickScrollViewScreen} />
65
         <Button title='scrollView Screen' testID={testIDs.SCROLLVIEW_SCREEN_BUTTON} onPress={this.onClickScrollViewScreen} />
66
-        <Button title='Custom Transition' onPress={this.onClickCustomTranstition} />
66
+        <Button title='Custom Transition' testID={testIDs.CUSTOM_TRANSITION_BUTTON} onPress={this.onClickCustomTranstition} />
67
         <Button title='Show custom alert' testID={testIDs.SHOW_CUSTOM_ALERT_BUTTON} onPress={this.onClickAlert} />
67
         <Button title='Show custom alert' testID={testIDs.SHOW_CUSTOM_ALERT_BUTTON} onPress={this.onClickAlert} />
68
         <Button title='Show snackbar' testID={testIDs.SHOW_SNACKBAR_BUTTON} onPress={this.onClickSnackbar} />
68
         <Button title='Show snackbar' testID={testIDs.SHOW_SNACKBAR_BUTTON} onPress={this.onClickSnackbar} />
69
         <Button title='Show overlay' testID={testIDs.SHOW_OVERLAY_BUTTON} onPress={() => this.onClickShowOverlay(true)} />
69
         <Button title='Show overlay' testID={testIDs.SHOW_OVERLAY_BUTTON} onPress={() => this.onClickShowOverlay(true)} />

+ 3
- 1
playground/src/screens/OrientationDetectScreen.js View File

23
         <Text style={styles.h1}>{`Orientation Screen`}</Text>
23
         <Text style={styles.h1}>{`Orientation Screen`}</Text>
24
         <Button title='Dismiss' testID={testIDs.DISMISS_BUTTON} onPress={() => Navigation.dismissModal(this.props.componentId)} />
24
         <Button title='Dismiss' testID={testIDs.DISMISS_BUTTON} onPress={() => Navigation.dismissModal(this.props.componentId)} />
25
         <Text style={styles.footer}>{`this.props.componentId = ${this.props.componentId}`}</Text>
25
         <Text style={styles.footer}>{`this.props.componentId = ${this.props.componentId}`}</Text>
26
-        <Text style={styles.footer} testID='currentOrientation'>{this.state.horizontal ? 'Landscape' : 'Portrait'}</Text>
26
+        {this.state.horizontal ?
27
+        <Text style={styles.footer} testID={testIDs.LANDSCAPE_ELEMENT}>Landscape</Text> :
28
+        <Text style={styles.footer} testID={testIDs.PORTRAIT_ELEMENT}>Portrait</Text>}
27
       </View>
29
       </View>
28
     );
30
     );
29
   }
31
   }

+ 4
- 8
playground/src/screens/OrientationSelectScreen.js View File

1
 const React = require('react');
1
 const React = require('react');
2
 const { Component } = require('react');
2
 const { Component } = require('react');
3
+
3
 const { View, Text, Button } = require('react-native');
4
 const { View, Text, Button } = require('react-native');
4
 
5
 
5
 const { Navigation } = require('react-native-navigation');
6
 const { Navigation } = require('react-native-navigation');
6
 const testIDs = require('../testIDs');
7
 const testIDs = require('../testIDs');
7
 
8
 
8
 class OrientationSelectScreen extends Component {
9
 class OrientationSelectScreen extends Component {
9
-
10
   render() {
10
   render() {
11
     return (
11
     return (
12
       <View style={styles.root}>
12
       <View style={styles.root}>
37
   root: {
37
   root: {
38
     flexGrow: 1,
38
     flexGrow: 1,
39
     justifyContent: 'center',
39
     justifyContent: 'center',
40
-    alignItems: 'center'
40
+    alignItems: 'center',
41
+    backgroundColor: '#f5fcff'
41
   },
42
   },
42
   h1: {
43
   h1: {
43
     fontSize: 24,
44
     fontSize: 24,
44
     textAlign: 'center',
45
     textAlign: 'center',
45
-    margin: 30
46
-  },
47
-  footer: {
48
-    fontSize: 10,
49
-    color: '#888',
50
-    marginTop: 10
46
+    margin: 10
51
   }
47
   }
52
 };
48
 };

+ 1
- 1
playground/src/screens/StaticLifecycleOverlay.js View File

1
 const React = require('react');
1
 const React = require('react');
2
 const { Component } = require('react');
2
 const { Component } = require('react');
3
 const { View, Text } = require('react-native');
3
 const { View, Text } = require('react-native');
4
-const Navigation = require('react-native-navigation');
4
+const { Navigation } = require('react-native-navigation');
5
 
5
 
6
 class StaticLifecycleOverlay extends Component {
6
 class StaticLifecycleOverlay extends Component {
7
   constructor(props) {
7
   constructor(props) {

+ 10
- 0
playground/src/screens/TextScreen.js View File

20
   constructor(props) {
20
   constructor(props) {
21
     super(props);
21
     super(props);
22
     globalFirstComponentID = (props.text === 'This is tab 1') ? props.componentId : globalFirstComponentID;
22
     globalFirstComponentID = (props.text === 'This is tab 1') ? props.componentId : globalFirstComponentID;
23
+    this.onClickPop = this.onClickPop.bind(this);
23
   }
24
   }
24
 
25
 
25
   render() {
26
   render() {
35
         <Button title='Show Tab Bar' testID={testIDs.SHOW_BOTTOM_TABS_BUTTON} onPress={() => this.hideTabBar(false)} />
36
         <Button title='Show Tab Bar' testID={testIDs.SHOW_BOTTOM_TABS_BUTTON} onPress={() => this.hideTabBar(false)} />
36
         <Button title='Show Left Side Menu' testID={testIDs.SHOW_LEFT_SIDE_MENU_BUTTON} onPress={() => this.showSideMenu('left')} />
37
         <Button title='Show Left Side Menu' testID={testIDs.SHOW_LEFT_SIDE_MENU_BUTTON} onPress={() => this.showSideMenu('left')} />
37
         <Button title='Show Right Side Menu' testID={testIDs.SHOW_RIGHT_SIDE_MENU_BUTTON} onPress={() => this.showSideMenu('right')} />
38
         <Button title='Show Right Side Menu' testID={testIDs.SHOW_RIGHT_SIDE_MENU_BUTTON} onPress={() => this.showSideMenu('right')} />
39
+        <Button title='Pop' testID={testIDs.POP_BUTTON} onPress={this.onClickPop} />
38
       </View>
40
       </View>
39
     );
41
     );
40
   }
42
   }
41
 
43
 
44
+  async onClickPop() {
45
+    await Navigation.pop(this.props.componentId);
46
+  }
47
+
42
   renderTextFromFunctionInProps() {
48
   renderTextFromFunctionInProps() {
43
     if (!this.props.myFunction) {
49
     if (!this.props.myFunction) {
44
       return undefined;
50
       return undefined;
91
       }
97
       }
92
     });
98
     });
93
   }
99
   }
100
+
101
+  onClickPop() {
102
+    Navigation.pop(this.props.componentId);
103
+  }
94
 }
104
 }
95
 
105
 
96
 module.exports = TextScreen;
106
 module.exports = TextScreen;

+ 59
- 25
playground/src/screens/WelcomeScreen.js View File

59
                     passProps: {
59
                     passProps: {
60
                       text: 'This is tab 1',
60
                       text: 'This is tab 1',
61
                       myFunction: () => 'Hello from a function!'
61
                       myFunction: () => 'Hello from a function!'
62
-                    },
63
-                    options: {
64
-                      bottomTab: {
65
-                        title: 'Tab 1',
66
-                        testID: testIDs.FIRST_TAB_BAR_BUTTON
67
-                      },
68
-                      bottomTabs: {
69
-                        textColor: '#12766b',
70
-                        selectedTextColor: 'red',
71
-                        fontFamily: 'HelveticaNeue-Italic',
72
-                        fontSize: 13
73
-                      }
74
                     }
62
                     }
75
                   }
63
                   }
76
                 }
64
                 }
77
-              ]
65
+              ],
66
+              options: {
67
+                bottomTab: {
68
+                  title: 'Tab 1',
69
+                  icon: require('../images/one.png'),
70
+                  testID: testIDs.FIRST_TAB_BAR_BUTTON
71
+                }
72
+              }
78
             }
73
             }
79
           },
74
           },
80
           {
75
           {
85
                     name: 'navigation.playground.TextScreen',
80
                     name: 'navigation.playground.TextScreen',
86
                     passProps: {
81
                     passProps: {
87
                       text: 'This is tab 2'
82
                       text: 'This is tab 2'
88
-                    },
89
-                    options: {
90
-                      bottomTab: {
91
-                        title: 'Tab 2',
92
-                        testID: testIDs.SECOND_TAB_BAR_BUTTON
93
-                      }
94
                     }
83
                     }
95
                   }
84
                   }
96
                 }
85
                 }
97
-              ]
86
+              ],
87
+              options: {
88
+                bottomTab: {
89
+                  title: 'Tab 2',
90
+                  icon: require('../images/two.png'),
91
+                  testID: testIDs.SECOND_TAB_BAR_BUTTON
92
+                }
93
+              }
98
             }
94
             }
99
           }
95
           }
100
-        ]
96
+        ],
97
+        options: {
98
+          bottomTabs: {
99
+            tabColor: 'red',
100
+            selectedTabColor: 'blue',
101
+            fontFamily: 'HelveticaNeue-Italic',
102
+            fontSize: 13,
103
+            testID: testIDs.BOTTOM_TABS_ELEMENT
104
+          }
105
+        }
101
       }
106
       }
102
     });
107
     });
103
   }
108
   }
127
                         }
132
                         }
128
                       }
133
                       }
129
                     }
134
                     }
130
-                  ]
135
+                  ],
136
+                  options: {
137
+                    bottomTab: {
138
+                      title: 'Tab 1',
139
+                      icon: require('../images/one.png'),
140
+                      testID: testIDs.FIRST_TAB_BAR_BUTTON
141
+                    }
142
+                  }
131
                 }
143
                 }
132
               },
144
               },
133
               {
145
               {
141
                         }
153
                         }
142
                       }
154
                       }
143
                     }
155
                     }
144
-                  ]
156
+                  ],
157
+                  options: {
158
+                    bottomTab: {
159
+                      title: 'Tab 2',
160
+                      icon: require('../images/two.png'),
161
+                      testID: testIDs.SECOND_TAB_BAR_BUTTON
162
+                    }
163
+                  }
145
                 }
164
                 }
146
               },
165
               },
147
               {
166
               {
155
                         }
174
                         }
156
                       }
175
                       }
157
                     }
176
                     }
158
-                  ]
177
+                  ],
178
+                  options: {
179
+                    bottomTab: {
180
+                      title: 'Tab 3',
181
+                      icon: require('../images/three.png'),
182
+                      testID: testIDs.SECOND_TAB_BAR_BUTTON
183
+                    }
184
+                  }
159
                 }
185
                 }
160
               }
186
               }
161
-            ]
187
+            ],
188
+            options: {
189
+              bottomTabs: {
190
+                tabColor: 'red',
191
+                selectedTabColor: 'blue',
192
+                fontFamily: 'HelveticaNeue-Italic',
193
+                fontSize: 13
194
+              }
195
+            }
162
           }
196
           }
163
         },
197
         },
164
         right: {
198
         right: {

+ 2
- 0
playground/src/screens/index.js View File

16
 const SideMenuScreen = require('./SideMenuScreen');
16
 const SideMenuScreen = require('./SideMenuScreen');
17
 const TopTabScreen = require('./TopTabScreen');
17
 const TopTabScreen = require('./TopTabScreen');
18
 const TopTabOptionsScreen = require('./TopTabOptionsScreen');
18
 const TopTabOptionsScreen = require('./TopTabOptionsScreen');
19
+const CustomTopBar = require('./CustomTopBar');
19
 
20
 
20
 function registerScreens() {
21
 function registerScreens() {
21
   Navigation.registerComponent(`navigation.playground.CustomTransitionDestination`, () => CustomTransitionDestination);
22
   Navigation.registerComponent(`navigation.playground.CustomTransitionDestination`, () => CustomTransitionDestination);
35
   Navigation.registerComponent('navigation.playground.SideMenuScreen', () => SideMenuScreen);
36
   Navigation.registerComponent('navigation.playground.SideMenuScreen', () => SideMenuScreen);
36
   Navigation.registerComponent('navigation.playground.TopTabScreen', () => TopTabScreen);
37
   Navigation.registerComponent('navigation.playground.TopTabScreen', () => TopTabScreen);
37
   Navigation.registerComponent('navigation.playground.TopTabOptionsScreen', () => TopTabOptionsScreen);
38
   Navigation.registerComponent('navigation.playground.TopTabOptionsScreen', () => TopTabOptionsScreen);
39
+  Navigation.registerComponent('navigation.playground.CustomTopBar', () => CustomTopBar);
38
 }
40
 }
39
 
41
 
40
 module.exports = {
42
 module.exports = {

+ 3
- 0
playground/src/testIDs.js View File

51
   SHOW_TOUCH_THROUGH_OVERLAY_BUTTON: `SHOW_TOUCH_THROUGH_OVERLAY_BUTTON`,
51
   SHOW_TOUCH_THROUGH_OVERLAY_BUTTON: `SHOW_TOUCH_THROUGH_OVERLAY_BUTTON`,
52
   OK_BUTTON: `OK_BUTTON`,
52
   OK_BUTTON: `OK_BUTTON`,
53
   MODAL_WITH_STACK_BUTTON: `MODAL_WITH_STACK_BUTTON`,
53
   MODAL_WITH_STACK_BUTTON: `MODAL_WITH_STACK_BUTTON`,
54
+  CUSTOM_TRANSITION_BUTTON: `CUSTOM_TRANSITION_BUTTON`,
54
 
55
 
55
   // Elements
56
   // Elements
56
   SCROLLVIEW_ELEMENT: `SCROLLVIEW_ELEMENT`,
57
   SCROLLVIEW_ELEMENT: `SCROLLVIEW_ELEMENT`,
57
   BOTTOM_TABS_ELEMENT: `BOTTOM_TABS_ELEMENT`,
58
   BOTTOM_TABS_ELEMENT: `BOTTOM_TABS_ELEMENT`,
58
   TOP_BAR_ELEMENT: `TOP_BAR_ELEMENT`,
59
   TOP_BAR_ELEMENT: `TOP_BAR_ELEMENT`,
60
+  LANDSCAPE_ELEMENT: `LANDSCAPE_ELEMENT`,
61
+  PORTRAIT_ELEMENT: `PORTRAIT_ELEMENT`,
59
 
62
 
60
   // Headers
63
   // Headers
61
   WELCOME_SCREEN_HEADER: `WELCOME_SCREEN_HEADER`,
64
   WELCOME_SCREEN_HEADER: `WELCOME_SCREEN_HEADER`,

+ 12
- 0
scripts/test.e2e.android.detox.js View File

1
+const _ = require('lodash');
2
+const exec = require('shell-utils').exec;
3
+
4
+const release = _.includes(process.argv, '--release');
5
+
6
+run();
7
+
8
+function run() {
9
+  const conf = release ? `release` : `debug`;
10
+  exec.execSync(`detox build --configuration android.emu.${conf}`);
11
+  exec.execSync(`detox test --configuration android.emu.${conf} ${process.env.CI ? '--cleanup' : ''}`);
12
+}