Browse Source

Merge remote-tracking branch 'origin/v2' into v2-rn51

Leo Natan 6 years ago
parent
commit
fd45a3ad60
No account linked to committer's email address
100 changed files with 2581 additions and 802 deletions
  1. 18
    3
      AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/OverlayTest.java
  2. 17
    0
      AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/StaticLifecycleEvents.java
  3. 2
    1
      Jenkinsfile
  4. 1
    1
      docs/_sidebar.md
  5. 4
    3
      docs/docs/CONTRIBUTING.md
  6. 3
    3
      docs/docs/Component.md
  7. 32
    33
      docs/docs/Navigation.md
  8. 2
    2
      docs/docs/Root.md
  9. 3
    2
      docs/docs/SideMenu.md
  10. 76
    12
      docs/docs/installation-android.md
  11. 1
    1
      docs/docs/options/BottomTabs.md
  12. 1
    1
      docs/docs/options/TopBar.md
  13. 76
    37
      docs/docs/usage.md
  14. 18
    18
      docs/docs/v1tov2diff.md
  15. 4
    3
      e2e/CustomTransition.js
  16. 12
    17
      e2e/Orientations.test.js
  17. 7
    1
      e2e/ScreenStack.test.js
  18. 21
    17
      e2e/ScreenStyle.test.js
  19. 3
    4
      e2e/TopLevelApi.test.js
  20. 4
    1
      lib/android/app/build.gradle
  21. 6
    0
      lib/android/app/src/main/java/com/reactnativenavigation/anim/AnimationListener.java
  22. 8
    0
      lib/android/app/src/main/java/com/reactnativenavigation/anim/FabAnimator.java
  23. 47
    0
      lib/android/app/src/main/java/com/reactnativenavigation/anim/FabCollapseBehaviour.java
  24. 42
    15
      lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java
  25. 16
    15
      lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarAnimator.java
  26. 49
    49
      lib/android/app/src/main/java/com/reactnativenavigation/anim/ViewAnimationSetBuilder.java
  27. 1
    0
      lib/android/app/src/main/java/com/reactnativenavigation/interfaces/ScrollEventListener.java
  28. 84
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/AnimationOptions.java
  29. 43
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/AnimationsOptions.java
  30. 55
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/BottomTabOptions.java
  31. 55
    27
      lib/android/app/src/main/java/com/reactnativenavigation/parse/BottomTabsOptions.java
  32. 130
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/FabOptions.java
  33. 9
    5
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutFactory.java
  34. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutNode.java
  35. 80
    27
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java
  36. 6
    2
      lib/android/app/src/main/java/com/reactnativenavigation/parse/OverlayOptions.java
  37. 25
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/SideMenuOptions.java
  38. 27
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/SideMenuRootOptions.java
  39. 0
    7
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Text.java
  40. 37
    21
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java
  41. 9
    4
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopTabOptions.java
  42. 15
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/TopTabsOptions.java
  43. 69
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/ValueAnimationOptions.java
  44. 23
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Bool.java
  45. 14
    9
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Button.java
  46. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Color.java
  47. 9
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/FloatParam.java
  48. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Fraction.java
  49. 26
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Interpolation.java
  50. 7
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullBool.java
  51. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullColor.java
  52. 14
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullFloatParam.java
  53. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullFraction.java
  54. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullNumber.java
  55. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullText.java
  56. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Number.java
  57. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Param.java
  58. 12
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Text.java
  59. 12
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/BoolParser.java
  60. 4
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/ColorParser.java
  61. 14
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/FloatParser.java
  62. 4
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/FractionParser.java
  63. 23
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/InterpolationParser.java
  64. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/JSONParser.java
  65. 3
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/LayoutNodeParser.java
  66. 4
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/NumberParser.java
  67. 4
    1
      lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/TextParser.java
  68. 59
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/BottomTabsOptionsPresenter.java
  69. 231
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/FabOptionsPresenter.java
  70. 21
    12
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java
  71. 24
    0
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/SideMenuOptionsPresenter.java
  72. 17
    7
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationEvent.java
  73. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java
  74. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java
  75. 15
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ArrayUtils.java
  76. 1
    1
      lib/android/app/src/main/java/com/reactnativenavigation/utils/CompatUtils.java
  77. 4
    4
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ImageLoader.java
  78. 26
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/UiUtils.java
  79. 28
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java
  80. 0
    131
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/BottomTabsController.java
  81. 13
    9
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java
  82. 12
    56
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ModalStack.java
  83. 111
    105
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java
  84. 26
    3
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java
  85. 31
    7
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/SideMenuController.java
  86. 53
    32
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java
  87. 25
    2
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  88. 36
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabFinder.java
  89. 152
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java
  90. 55
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/Modal.java
  91. 9
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalCreator.java
  92. 18
    4
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java
  93. 33
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/BottomTabs.java
  94. 13
    8
      lib/android/app/src/main/java/com/reactnativenavigation/views/ComponentLayout.java
  95. 75
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/Fab.java
  96. 53
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/FabMenu.java
  97. 25
    15
      lib/android/app/src/main/java/com/reactnativenavigation/views/StackLayout.java
  98. 54
    22
      lib/android/app/src/main/java/com/reactnativenavigation/views/TitleBarButton.java
  99. 53
    20
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java
  100. 0
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabs.java

+ 18
- 3
AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/OverlayTest.java View File

@@ -1,6 +1,7 @@
1 1
 package com.reactnativenavigation.e2e.androide2e;
2 2
 
3 3
 import android.support.test.uiautomator.By;
4
+import android.support.test.uiautomator.UiObjectNotFoundException;
4 5
 
5 6
 import org.junit.Test;
6 7
 
@@ -11,8 +12,22 @@ public class OverlayTest extends BaseTest {
11 12
 		elementByText("PUSH OPTIONS SCREEN").click();
12 13
         elementByText("SHOW OVERLAY").click();
13 14
 		assertExists(By.text("Test view"));
14
-		elementByText("OK").click();
15
-		assertExists(By.text("Overlay disappeared"));
16
-        elementByText("OK").click();
15
+        assetDismissed();
16
+	}
17
+
18
+    @Test
19
+	public void testOverlayNotInterceptingTouchEvents() throws Exception {
20
+		elementByText("PUSH OPTIONS SCREEN").click();
21
+        elementByText("SHOW TOUCH THROUGH OVERLAY").click();
22
+		assertExists(By.text("Test view"));
23
+        elementByText("DYNAMIC OPTIONS").click();
24
+        assertExists(By.text("Dynamic Title"));
25
+        assetDismissed();
17 26
 	}
27
+
28
+    private void assetDismissed() throws UiObjectNotFoundException {
29
+        elementByText("OK").click();
30
+        assertExists(By.text("Overlay disappeared"));
31
+        elementByText("OK").click();
32
+    }
18 33
 }

+ 17
- 0
AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/StaticLifecycleEvents.java View File

@@ -0,0 +1,17 @@
1
+package com.reactnativenavigation.e2e.androide2e;
2
+
3
+import android.support.test.uiautomator.By;
4
+
5
+import org.junit.Test;
6
+
7
+public class StaticLifecycleEvents extends BaseTest {
8
+    @Test
9
+    public void didAppearDidDisappear() throws Exception {
10
+        elementByText("STATIC LIFECYCLE EVENTS").click();
11
+        assertExists(By.text("Static Lifecycle Events"));
12
+        assertExists(By.text("didAppear | navigation.playground.StaticLifecycleOverlay"));
13
+        elementByText("PUSH").click();
14
+        assertExists(By.text("didAppear | navigation.playground.PushedScreen"));
15
+        assertExists(By.text("didDisappear | navigation.playground.WelcomeScreen"));
16
+    }
17
+}

+ 2
- 1
Jenkinsfile View File

@@ -36,4 +36,5 @@ npm run test-e2e-android -- --release'''
36 36
       }
37 37
     }
38 38
   }
39
-}
39
+}
40
+

+ 1
- 1
docs/_sidebar.md View File

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

+ 4
- 3
docs/docs/CONTRIBUTING.md View File

@@ -92,8 +92,9 @@ No PR will be accepted without adequate test coverage.
92 92
 | `lib` | The project itself composed of: |
93 93
 | `lib/android` | android sources and unit tests |
94 94
 | `lib/ios` | iOS sources and unit tests |
95
-| `lib/src` | javascript sources and unit tests |
96
-| `lib/src/index.js` | the entry point for `import Navigation from 'react-native-navigation'` |
95
+| `lib/src` | TypeScript sources and unit tests |
96
+| `lib/dist` | compiled javascript sources and unit tests |
97
+| `lib/dist/index.js` | the entry point for `import Navigation from 'react-native-navigation'` |
97 98
 | `e2e` | [detox](https://github.com/wix/detox) iOS e2e tests (in the future, once detox supports it, we will have android e2e here as well) |
98 99
 | `AndroidE2E` | Android e2e tests using native uiautomator (until detox for android is ready) |
99 100
 | `playground` | The end-user project all e2e tests run against. Contains its own `src`, `android` and `ios`. Does not have its own package.json, depends on the local `<root>/lib` in order not to go through npm. |
@@ -105,13 +106,13 @@ No PR will be accepted without adequate test coverage.
105 106
 | Command | Description |
106 107
 | ------- | ----------- |
107 108
 | `npm install` | installs dependencies |
109
+| `npm run build` | compiles TypeScript sources `./lib/src` into javascript `./lib/dist` |
108 110
 | `npm run clean` | cleans all build directories, stops packager, fixes flakiness by removing watchman cache, etc. |
109 111
 | `npm run start` | starts the react-native packager for local debugging |
110 112
 | `npm run xcode` | for convenience, opens xcode in this project |
111 113
 | `npm run install-android`  |  builds playground debug/release version and installs on running android devices/emulators. <br> **Options:** `-- --release` |
112 114
 | `npm run uninstall-android` | uninstalls playground from running android devices/simulators |
113 115
 | `npm run test-js` | runs javascript tests and coverage report |
114
-| `npm run test-watch` | runs javascript tests in watch mode (can also use the provided wallaby config) |
115 116
 | `npm run test-unit-ios` | runs ios unit tests in debug/release <br> **Options:** `-- --release` |
116 117
 | `npm run test-unit-android` | runs android unit tests in debug/release <br> **Options:** `-- --release` |
117 118
 | `npm run test-e2e-ios` | runs the ios e2e suite (with detox) in debug/release <br> **Options:** `-- --release`|

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

@@ -1,11 +1,11 @@
1
-<h1>Container</h1>
1
+<h1>Component</h1>
2 2
 
3 3
 **Properties**
4 4
 
5 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 9
 | passProps | <code>object</code> | props |
10 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,32 +3,32 @@
3 3
 # Navigation
4 4
 
5 5
 * [Navigation](#Navigation)
6
-    * [.registerContainer(containerName, getContainerFunc)](#Navigation+registerContainer)
6
+    * [.registerComponent(componentName, getComponentFunc)](#Navigation+registerComponent)
7 7
     * [.setRoot(root)](#Navigation+setRoot)
8 8
     * [.setDefaultOptions(options)](#Navigation+setDefaultOptions)
9
-    * [.setOptions(containerId, options)](#Navigation+setOptions)
9
+    * [.setOptions(componentId, options)](#Navigation+setOptions)
10 10
     * [.showModal(params)](#Navigation+showModal)
11
-    * [.dismissModal(containerId)](#Navigation+dismissModal)
11
+    * [.dismissModal(componentId)](#Navigation+dismissModal)
12 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 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 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 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,7 +41,7 @@ Reset the navigation stack to a new screen (the stack root is changed).
41 41
 
42 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,20 +54,20 @@ Set default options to all screens. Useful for declaring a consistent style acro
54 54
 
55 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 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 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 71
 | options | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/options/NavigationOptions">NavigationOptions</a> |  |
72 72
 
73 73
 
@@ -81,20 +81,20 @@ Show a screen as a modal.
81 81
 
82 82
 | Param | Type |
83 83
 | --- | --- |
84
-| params | <code>object</code> | 
84
+| params | <code>object</code> |
85 85
 
86 86
 
87 87
 * * *
88 88
 
89 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 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,27 +109,27 @@ Dismiss all Modals
109 109
 
110 110
 <a name="Navigation+push"></a>
111 111
 
112
-## navigation.push(containerId, container)
112
+## navigation.push(componentId, component)
113 113
 Push a new screen into this screen's navigation stack.
114 114
 
115 115
 
116 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 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 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 133
 | params | <code>*</code> |  |
134 134
 
135 135
 
@@ -137,26 +137,26 @@ Pop a container from the stack, regardless of it's position.
137 137
 
138 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 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 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 157
 | Param | Type |
158 158
 | --- | --- |
159
-| containerId | <code>*</code> | 
159
+| componentId | <code>*</code> |
160 160
 
161 161
 
162 162
 * * *
@@ -165,4 +165,3 @@ Pop the container's stack to root.
165 165
 
166 166
 ## navigation.events()
167 167
 Obtain the events registery instance
168
-

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

@@ -4,7 +4,7 @@
4 4
 
5 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 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
 

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

@@ -4,6 +4,7 @@
4 4
 
5 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
+| center | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Component">Component</a> | 
9
+| right | <a href="https://wix.github.io/react-native-navigation/v2/#/docs/Component">Component</a> | 
9 10
 

+ 76
- 12
docs/docs/installation-android.md View File

@@ -1,5 +1,8 @@
1 1
 # Android Installation
2 2
 
3
+!> Make sure your Android Studio installation is updated. We recommend editing `gralde` and `java` files in Android Studio as the ide will suggest fixes and point out errors, this way you avoid most common pitfalls.
4
+
5
+
3 6
 1. Install `react-native-navigation` latest stable version.
4 7
 
5 8
 	```sh
@@ -12,24 +15,84 @@
12 15
 	include ':react-native-navigation'
13 16
 	project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/lib/android/app/')
14 17
 	```
18
+	
19
+3. Update `android/build.gradle`
20
+
21
+	```diff
22
+	buildscript {
23
+	    repositories {
24
+	+        mavenLocal()
25
+	+        mavenCentral()
26
+	+        google()
27
+	+        jcenter()
28
+	    }
29
+	    dependencies {
30
+	+        classpath 'com.android.tools.build:gradle:3.0.1'
31
+	-        classpath 'com.android.tools.build:gradle:2.2.3'
32
+	    }
33
+	}
34
+	
35
+	allprojects {
36
+	    repositories {
37
+	        mavenLocal()
38
+	+        mavenCentral()
39
+	+        google()
40
+	        jcenter()
41
+	        maven {
42
+	            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
43
+	            url "$rootDir/../../node_modules/react-native/android"
44
+	        }
45
+	    }
46
+	}
47
+	```
48
+
49
+4. Update project dependencies in `android/app/build.gradle`.
15 50
 
16
-3. Update project dependencies in `android/app/build.gradle`.
17 51
 	```groovy
18 52
 	android {
19
-		compileSdkVersion 25
20
-		buildToolsVersion "25.0.1"
53
+	    compileSdkVersion 25
54
+	    buildToolsVersion "27.0.3"
55
+	    
56
+	    defaultConfig {
57
+	        minSdkVersion 19
58
+	        targetSdkVersion 25
21 59
 		...
60
+	    }
61
+	
62
+	    compileOptions {
63
+	        sourceCompatibility JavaVersion.VERSION_1_8
64
+	        targetCompatibility JavaVersion.VERSION_1_8
65
+	    }
66
+	    ...
22 67
 	}
23
-
68
+	
24 69
 	dependencies {
25
-		compile fileTree(dir: "libs", include: ["*.jar"])
26
-		compile "com.android.support:appcompat-v7:23.0.1"
27
-		compile "com.facebook.react:react-native:+"
28
-		compile project(':react-native-navigation')
70
+	    implementation fileTree(dir: "libs", include: ["*.jar"])
71
+	    implementation "com.android.support:appcompat-v7:25.4.0"
72
+	    implementation "com.facebook.react:react-native:+"
73
+	    implementation project(':react-native-navigation')
29 74
 	}
30 75
 	```
76
+	
77
+5. Make sure you're using the new gradle plugin, edit `android/gradle/wrapper/gradle-wrapper.properties`
78
+
79
+	```diff
80
+	distributionBase=GRADLE_USER_HOME
81
+	distributionPath=wrapper/dists
82
+	zipStoreBase=GRADLE_USER_HOME
83
+	zipStorePath=wrapper/dists
84
+	+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
85
+	-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
86
+	```
87
+
88
+6. Update `gradle.properties` and disable incremental resource processing
89
+
90
+	```diff
91
+	+# Disable incremental resource processing as it broke relase build
92
+	+android.enableAapt2=false
93
+	```
31 94
 
32
-4. In `MainActivity.java` it should extend `com.reactnativenavigation.NavigationActivity` instead of `ReactActivity`.
95
+7. In `MainActivity.java` it should extend `com.reactnativenavigation.NavigationActivity` instead of `ReactActivity`.
33 96
 
34 97
 	This file can be located in `android/app/src/main/java/com/yourproject/`.
35 98
 
@@ -43,7 +106,8 @@
43 106
 
44 107
 	If you have any **react-native** related methods, you can safely delete them.
45 108
 
46
-5. In `MainApplication.java`, add the following
109
+8. In `MainApplication.java`, add the following
110
+	
47 111
 	```java
48 112
 	import com.reactnativenavigation.NavigationApplication;
49 113
 
@@ -53,7 +117,6 @@
53 117
 		return BuildConfig.DEBUG;
54 118
 	}
55 119
 
56
-	@Nullable
57 120
 	@Override
58 121
 	public List<ReactPackage> createAdditionalReactPackages() {
59 122
 		return Arrays.<ReactPackage>asList(
@@ -65,7 +128,8 @@
65 128
 
66 129
 	Make sure that `isDebug` methods is implemented.
67 130
 
68
-6. Update `AndroidManifest.xml` and set **android:name** value to `.MainApplication`
131
+9. Update `AndroidManifest.xml` and set **android:name** value to `.MainApplication`
132
+	
69 133
 	```xml
70 134
 	<application
71 135
 		android:name=".MainApplication"

+ 1
- 1
docs/docs/options/BottomTabs.md View File

@@ -7,7 +7,7 @@
7 7
 | currentTabId | <code>string</code> | 
8 8
 | currentTabIndex | <code>number</code> | 
9 9
 | hidden | <code>boolean</code> | 
10
-| animateHide | <code>boolean</code> | 
10
+| animate | <code>boolean</code> | 
11 11
 | testID | <code>string</code> | 
12 12
 | drawUnder | <code>boolean</code> | 
13 13
 

+ 1
- 1
docs/docs/options/TopBar.md View File

@@ -12,7 +12,7 @@
12 12
 | textFontFamily | <code>string</code> | 
13 13
 | testID | <code>string</code> | 
14 14
 | hidden | <code>boolean</code> | 
15
-| animateHide | <code>boolean</code> | 
15
+| animate | <code>boolean</code> | 
16 16
 | hideOnScroll | <code>boolean</code> | 
17 17
 | transparent | <code>boolean</code> | 
18 18
 | translucent | <code>boolean</code> | 

+ 76
- 37
docs/docs/usage.md View File

@@ -4,7 +4,7 @@
4 4
 
5 5
 ### Navigation
6 6
 ```js
7
-import Navigation from 'react-native-navigation';
7
+import { Navigation } from 'react-native-navigation';
8 8
 ```
9 9
 ### Events - On App Launched
10 10
 How to initiate your app.
@@ -12,18 +12,18 @@ How to initiate your app.
12 12
 ```js
13 13
 Navigation.events().onAppLaunched(() => {
14 14
   Navigation.setRoot({
15
-    container: {
15
+    component: {
16 16
       name: 'navigation.playground.WelcomeScreen'
17 17
     }
18 18
   });
19 19
 });
20 20
 ```
21 21
 
22
-### registerContainer(screenID, generator)
22
+### registerComponent(screenID, generator)
23 23
 Every screen component in your app must be registered with a unique name. The component itself is a traditional React component extending React.Component.
24 24
 
25 25
 ```js
26
-Navigation.registerContainer(`navigation.playground.WelcomeScreen`, () => WelcomeScreen);
26
+Navigation.registerComponent(`navigation.playground.WelcomeScreen`, () => WelcomeScreen);
27 27
 ```
28 28
 
29 29
 ### setRoot({params})
@@ -31,20 +31,22 @@ Start a Single page app with two side menus:
31 31
 
32 32
 ```js
33 33
 Navigation.setRoot({
34
-  container: {
35
-    name: 'navigation.playground.WelcomeScreen'
36
-  },
37 34
   sideMenu: {
38 35
     left: {
39
-      container: {
36
+      component: {
40 37
         name: 'navigation.playground.TextScreen',
41 38
         passProps: {
42 39
           text: 'This is a left side menu screen'
43 40
         }
44 41
       }
45 42
     },
43
+    center: {
44
+      component: {
45
+        name: 'navigation.playground.WelcomeScreen'
46
+      },
47
+    },
46 48
     right: {
47
-      container: {
49
+      component: {
48 50
         name: 'navigation.playground.TextScreen',
49 51
         passProps: {
50 52
           text: 'This is a right side menu screen'
@@ -58,25 +60,60 @@ Start a tab based app:
58 60
 
59 61
 ```js
60 62
 Navigation.setRoot({
61
-  bottomTabs: [
62
-    {
63
-      container: {
64
-        name: 'navigation.playground.TextScreen',
65
-        passProps: {
66
-          text: 'This is tab 1',
67
-          myFunction: () => 'Hello from a function!'
68
-        }
69
-      }
63
+  bottomTabs: {
64
+    children: [
65
+      {
66
+        component: {
67
+          name: 'navigation.playground.TextScreen',
68
+          passProps: {
69
+            text: 'This is tab 1',
70
+            myFunction: () => 'Hello from a function!',
71
+          },
72
+        },
73
+      },
74
+      {
75
+        component: {
76
+          name: 'navigation.playground.TextScreen',
77
+          passProps: {
78
+            text: 'This is tab 2',
79
+          },
80
+        },
81
+      },
82
+    ],
83
+  },
84
+});
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
+      },
70 96
     },
71
-    {
72
-      container: {
73
-        name: 'navigation.playground.TextScreen',
74
-        passProps: {
75
-          text: 'This is tab 2'
76
-        }
77
-      }
78
-    }
79
-  ]
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
+  },
80 117
 });
81 118
 ```
82 119
 ## Screen API
@@ -85,18 +122,20 @@ Navigation.setRoot({
85 122
 Push a new screen into this screen's navigation stack.
86 123
 
87 124
 ```js
88
-Navigation.push(this.props.containerId, {
89
-  name: 'navigation.playground.PushedScreen',
90
-  passProps: {}
125
+Navigation.push(this.props.componentId, {
126
+  component: {
127
+    name: 'navigation.playground.PushedScreen',
128
+    passProps: {}
129
+  }
91 130
 });
92 131
 ```
93
-### pop(containerId)
132
+### pop(componentId)
94 133
 Pop the top screen from this screen's navigation stack.
95 134
 
96 135
 ```js
97
-Navigation.pop(this.props.containerId);
136
+Navigation.pop(this.props.componentId);
98 137
 ```
99
-### popTo(containerId)
138
+### popTo(componentId)
100 139
 ```js
101 140
 Navigation.popTo(previousScreenId);
102 141
 ```
@@ -104,14 +143,14 @@ Navigation.popTo(previousScreenId);
104 143
 Pop all the screens until the root from this screen's navigation stack
105 144
 
106 145
 ```js
107
-Navigation.popToRoot(this.props.containerId);
146
+Navigation.popToRoot(this.props.componentId);
108 147
 ```
109 148
 ### showModal(params = {})
110 149
 Show a screen as a modal.
111 150
 
112 151
 ```js
113 152
 Navigation.showModal({
114
-  container: {
153
+  component: {
115 154
     name: 'navigation.playground.ModalScreen',
116 155
     passProps: {
117 156
         key: 'value'
@@ -119,11 +158,11 @@ Navigation.showModal({
119 158
   }
120 159
 });
121 160
 ```
122
-### dismissModal(containerId)
161
+### dismissModal(componentId)
123 162
 Dismiss modal.
124 163
 
125 164
 ```js
126
-Navigation.dismissModal(this.props.containerId);
165
+Navigation.dismissModal(this.props.componentId);
127 166
 ```
128 167
 ### dismissAllModals()
129 168
 Dismiss all the current modals at the same time.

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

@@ -17,9 +17,9 @@ These issues originate from the same problem: you cannot specify on which screen
17 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 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 21
 ```js
22
-Navigator.pop(this.props.containerId)
22
+Navigator.pop(this.props.componentId)
23 23
 ```
24 24
 ### Built for Contributors
25 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,18 +179,18 @@ How to initiate your app.
179 179
 ```js
180 180
 Navigation.events().onAppLaunched(() => {
181 181
     Navigation.setRoot({
182
-      container: {
182
+      component: {
183 183
         name: 'navigation.playground.WelcomeScreen'
184 184
       }
185 185
     });
186 186
   });
187 187
 ```
188 188
 
189
-#### registerContainer(screenID, generator)
189
+#### registerComponent(screenID, generator)
190 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 192
 ```js
193
-Navigation.registerContainer(`navigation.playground.WelcomeScreen`, () => WelcomeScreen);
193
+Navigation.registerComponent(`navigation.playground.WelcomeScreen`, () => WelcomeScreen);
194 194
 ```
195 195
 
196 196
 #### setRoot({params})
@@ -198,12 +198,12 @@ Start a Single page app with two side menus:
198 198
 
199 199
 ```js
200 200
 Navigation.setRoot({
201
-      container: {
201
+      component: {
202 202
         name: 'navigation.playground.WelcomeScreen'
203 203
       },
204 204
       sideMenu: {
205 205
         left: {
206
-          container: {
206
+          component: {
207 207
             name: 'navigation.playground.TextScreen',
208 208
             passProps: {
209 209
               text: 'This is a left side menu screen'
@@ -211,7 +211,7 @@ Navigation.setRoot({
211 211
           }
212 212
         },
213 213
         right: {
214
-          container: {
214
+          component: {
215 215
             name: 'navigation.playground.TextScreen',
216 216
             passProps: {
217 217
               text: 'This is a right side menu screen'
@@ -227,7 +227,7 @@ Start a tab based app:
227 227
 Navigation.setRoot({
228 228
       tabs: [
229 229
         {
230
-          container: {
230
+          component: {
231 231
             name: 'navigation.playground.TextScreen',
232 232
             passProps: {
233 233
               text: 'This is tab 1',
@@ -236,7 +236,7 @@ Navigation.setRoot({
236 236
           }
237 237
         },
238 238
         {
239
-          container: {
239
+          component: {
240 240
             name: 'navigation.playground.TextScreen',
241 241
             passProps: {
242 242
               text: 'This is tab 2'
@@ -252,34 +252,34 @@ Navigation.setRoot({
252 252
 Push a new screen into this screen's navigation stack.
253 253
 
254 254
 ```js
255
-Navigation.push(this.props.containerId, {
255
+Navigation.push(this.props.componentId, {
256 256
       name: 'navigation.playground.PushedScreen',
257 257
       passProps: {}
258 258
     });
259 259
 ```
260
-#### pop(containerId)
260
+#### pop(componentId)
261 261
 Pop the top screen from this screen's navigation stack.
262 262
 
263 263
 ```js
264
-Navigation.pop(this.props.containerId);
264
+Navigation.pop(this.props.componentId);
265 265
 ```
266 266
 #### popTo(params)
267 267
 
268 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 271
 #### popToRoot()
272 272
 Pop all the screens until the root from this screen's navigation stack
273 273
 
274 274
 ```js
275
-Navigation.popToRoot(this.props.containerId);
275
+Navigation.popToRoot(this.props.componentId);
276 276
 ```
277 277
 #### showModal(params = {})
278 278
 Show a screen as a modal.
279 279
 
280 280
 ```js
281 281
 Navigation.showModal({
282
-      container: {
282
+      component: {
283 283
         name: 'navigation.playground.ModalScreen',
284 284
         passProps: {
285 285
             key: 'value'
@@ -287,11 +287,11 @@ Navigation.showModal({
287 287
       }
288 288
     });
289 289
 ```
290
-#### dismissModal(containerId)
290
+#### dismissModal(componentId)
291 291
 Dismiss modal.
292 292
 
293 293
 ```js
294
-Navigation.dismissModal(this.props.containerId);
294
+Navigation.dismissModal(this.props.componentId);
295 295
 ```
296 296
 #### dismissAllModals()
297 297
 Dismiss all the current modals at the same time.

+ 4
- 3
e2e/CustomTransition.js View File

@@ -1,7 +1,8 @@
1 1
 
2 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 7
 describe('custom transition', () => {
7 8
   beforeEach(async () => {
@@ -9,8 +10,8 @@ describe('custom transition', () => {
9 10
   });
10 11
 
11 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 15
     await expect(element(by.id('shared_image1'))).toExist();
15 16
     await element(by.id('shared_image1')).tap();
16 17
     await expect(element(by.id('shared_image2'))).toExist();

+ 12
- 17
e2e/Orientations.test.js View File

@@ -4,46 +4,41 @@ const testIDs = require('../playground/src/testIDs');
4 4
 
5 5
 const { elementById } = Utils;
6 6
 
7
-describe('orientation', () => {
7
+describe(':ios: orientation', () => {
8 8
   beforeEach(async () => {
9 9
     await device.relaunchApp();
10 10
   });
11 11
 
12
-  afterEach(async () => {
13
-    await device.setOrientation('landscape');
14
-    await device.setOrientation('portrait');
15
-  });
16
-
17 12
   it('default allows all', async () => {
18 13
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
19 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 16
     await device.setOrientation('landscape');
22
-    await expect(element(by.id('currentOrientation'))).toHaveText('Landscape');
17
+    await expect(elementById(testIDs.LANDSCAPE_ELEMENT)).toBeVisible();
23 18
     await device.setOrientation('portrait');
24
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
19
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
25 20
     await elementById(testIDs.DISMISS_BUTTON).tap();
26 21
   });
27 22
 
28 23
   it('landscape and portrait array', async () => {
29 24
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
30 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 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 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 31
     await elementById(testIDs.DISMISS_BUTTON).tap();
37 32
   });
38 33
 
39 34
   it('portrait only', async () => {
40 35
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
41 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 38
     await device.setOrientation('landscape');
44
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
39
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
45 40
     await device.setOrientation('portrait');
46
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
41
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
47 42
     await elementById(testIDs.DISMISS_BUTTON).tap();
48 43
   });
49 44
 
@@ -51,9 +46,9 @@ describe('orientation', () => {
51 46
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
52 47
     await elementById(testIDs.LANDSCAPE_ORIENTATION_BUTTON).tap();
53 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 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 52
     await elementById(testIDs.DISMISS_BUTTON).tap();
58 53
   });
59 54
 });

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

@@ -90,7 +90,13 @@ describe('screen stack', () => {
90 90
     await elementById(testIDs.SHOW_MODAL_BUTTON).tap();
91 91
     await elementById(testIDs.MODAL_WITH_STACK_BUTTON).tap();
92 92
     await expect(elementByLabel('Screen 2')).toBeVisible();
93
-    await Utils.tapBackIos();
93
+    await elementById(testIDs.POP_BUTTON).tap();
94 94
     await expect(elementByLabel('Screen 1')).toBeVisible();
95 95
   });
96
+
97
+  it(':ios: push native component with options', async () => {
98
+    await elementById(testIDs.PUSH_NATIVE_COMPONENT_BUTTON).tap();
99
+    await expect(elementById('TestLabel')).toBeVisible();
100
+    await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
101
+  });
96 102
 });

+ 21
- 17
e2e/ScreenStyle.test.js View File

@@ -10,14 +10,14 @@ describe('screen style', () => {
10 10
 
11 11
   it('declare a options on component component', async () => {
12 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 16
   it('change title on component component', async () => {
17 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 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 23
   it('set dynamic options with valid options will do something and not crash', async () => {
@@ -35,39 +35,31 @@ describe('screen style', () => {
35 35
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
36 36
   });
37 37
 
38
-  it('hides topBar onScroll down and shows it on scroll up', async () => {
38
+  it(':ios: hides topBar onScroll down and shows it on scroll up', async () => {
39 39
     await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
40 40
     await elementById(testIDs.SCROLLVIEW_SCREEN_BUTTON).tap();
41 41
     await elementById(testIDs.TOGGLE_TOP_BAR_HIDE_ON_SCROLL).tap();
42 42
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
43
-    await element(by.id(testIDs.SCROLLVIEW_ELEMENT)).swipe('up', 'fast');
43
+    await element(by.id(testIDs.SCROLLVIEW_ELEMENT)).swipe('up', 'slow');
44 44
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeNotVisible();
45
-    await element(by.id(testIDs.SCROLLVIEW_ELEMENT)).swipe('down', 'fast');
45
+    await element(by.id(testIDs.SCROLLVIEW_ELEMENT)).swipe('down', 'slow');
46 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 49
   it('set Tab Bar badge on a current Tab', async () => {
58 50
     await elementById(testIDs.TAB_BASED_APP_BUTTON).tap();
59 51
     await elementById(testIDs.SET_TAB_BADGE_BUTTON).tap();
60 52
     await expect(element(by.text('TeSt'))).toBeVisible();
61 53
   });
62 54
 
63
-  it('hide Tab Bar', async () => {
55
+  it(':ios: hide Tab Bar', async () => {
64 56
     await elementById(testIDs.TAB_BASED_APP_BUTTON).tap();
65 57
     await expect(elementById(testIDs.BOTTOM_TABS_ELEMENT)).toBeVisible();
66 58
     await elementById(testIDs.HIDE_BOTTOM_TABS_BUTTON).tap();
67 59
     await expect(elementById(testIDs.BOTTOM_TABS_ELEMENT)).toBeNotVisible();
68 60
   });
69 61
 
70
-  it('show Tab Bar', async () => {
62
+  it(':ios: show Tab Bar', async () => {
71 63
     await elementById(testIDs.TAB_BASED_APP_BUTTON).tap();
72 64
     await elementById(testIDs.HIDE_BOTTOM_TABS_BUTTON).tap();
73 65
     await expect(elementById(testIDs.BOTTOM_TABS_ELEMENT)).toBeNotVisible();
@@ -91,7 +83,7 @@ describe('screen style', () => {
91 83
     await expect(elementById(testIDs.CENTERED_TEXT_HEADER)).toBeVisible();
92 84
   });
93 85
 
94
-  it('set right buttons', async () => {
86
+  it(':ios: set right buttons', async () => {
95 87
     await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
96 88
     await expect(elementById('buttonOne')).toBeVisible();
97 89
     await elementById('buttonOne').tap();
@@ -126,4 +118,16 @@ describe('screen style', () => {
126 118
     await elementById(testIDs.POP_BUTTON).tap();
127 119
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
128 120
   });
121
+
122
+  it('supports user-provided id', async () => {
123
+    await elementById(testIDs.PROVIDED_ID).tap();
124
+    await expect(elementByLabel('User provided id')).toBeVisible();
125
+  });
126
+
127
+  it('stack options should not override component options', async () => {
128
+    await elementById(testIDs.TAB_BASED_APP_BUTTON).tap();
129
+    await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeNotVisible();
130
+    await elementById(testIDs.SECOND_TAB_BAR_BUTTON).tap();
131
+    await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
132
+  });
129 133
 });

+ 3
- 4
e2e/TopLevelApi.test.js View File

@@ -18,7 +18,7 @@ describe('top level api', () => {
18 18
     await expect(elementByLabel('Hello from a function!')).toBeVisible();
19 19
   });
20 20
 
21
-  it('switch to tabs with side menus', async () => {
21
+  it(':ios: switch to tabs with side menus', async () => {
22 22
     await elementById(testIDs.TAB_BASED_APP_SIDE_BUTTON).tap();
23 23
     await elementById(testIDs.CENTERED_TEXT_HEADER).swipe('right');
24 24
     await expect(elementById(testIDs.HIDE_LEFT_SIDE_MENU_BUTTON)).toBeVisible();
@@ -28,16 +28,15 @@ describe('top level api', () => {
28 28
     await elementById(testIDs.PUSH_LIFECYCLE_BUTTON).tap();
29 29
     await expect(elementByLabel('didAppear')).toBeVisible();
30 30
     await elementById(testIDs.PUSH_TO_TEST_DID_DISAPPEAR_BUTTON).tap();
31
-    await expect(elementByLabel('Alert')).toBeVisible();
32 31
     await expect(elementByLabel('didDisappear')).toBeVisible();
33 32
   });
34 33
 
35 34
   it('unmount is called on pop', async () => {
36 35
     await elementById(testIDs.PUSH_LIFECYCLE_BUTTON).tap();
37 36
     await expect(elementByLabel('didAppear')).toBeVisible();
38
-    await Utils.tapBackIos();
37
+    await elementById(testIDs.POP_BUTTON).tap();
39 38
     await expect(elementByLabel('componentWillUnmount')).toBeVisible();
40
-    await element(by.traits(['button']).and(by.label('OK'))).atIndex(0).tap();
39
+    await elementByLabel('OK').atIndex(0).tap();
41 40
     await expect(elementByLabel('didDisappear')).toBeVisible();
42 41
   });
43 42
 });

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

@@ -59,7 +59,10 @@ dependencies {
59 59
     implementation fileTree(include: ['*.jar'], dir: 'libs')
60 60
     implementation 'com.android.support:design:25.4.0'
61 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'
64
+    implementation 'com.github.clans:fab:1.6.4'
65
+
63 66
 
64 67
     // node_modules
65 68
     //noinspection GradleDynamicVersion

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

@@ -0,0 +1,6 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+
4
+public interface AnimationListener {
5
+    void onAnimationEnd();
6
+}

+ 8
- 0
lib/android/app/src/main/java/com/reactnativenavigation/anim/FabAnimator.java View File

@@ -0,0 +1,8 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+
4
+public interface FabAnimator {
5
+    void show();
6
+
7
+    void hide();
8
+}

+ 47
- 0
lib/android/app/src/main/java/com/reactnativenavigation/anim/FabCollapseBehaviour.java View File

@@ -0,0 +1,47 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+
4
+import android.support.annotation.NonNull;
5
+
6
+import com.reactnativenavigation.interfaces.ScrollEventListener;
7
+
8
+public class FabCollapseBehaviour implements ScrollEventListener.OnScrollListener, ScrollEventListener.OnDragListener {
9
+
10
+    private FabAnimator fabAnimator;
11
+    private ScrollEventListener scrollEventListener;
12
+
13
+    public FabCollapseBehaviour(FabAnimator fabAnimator) {
14
+        this.fabAnimator = fabAnimator;
15
+    }
16
+
17
+    public void enableCollapse(@NonNull ScrollEventListener scrollEventListener) {
18
+        this.scrollEventListener = scrollEventListener;
19
+        this.scrollEventListener.register(null, this, this);
20
+    }
21
+
22
+    public void disableCollapse() {
23
+        if (scrollEventListener != null) {
24
+            scrollEventListener.unregister();
25
+        }
26
+    }
27
+
28
+    @Override
29
+    public void onScrollUp(float nextTranslation) {
30
+        //empty
31
+    }
32
+
33
+    @Override
34
+    public void onScrollDown(float nextTranslation) {
35
+        //empty
36
+    }
37
+
38
+    @Override
39
+    public void onShow() {
40
+        fabAnimator.show();
41
+    }
42
+
43
+    @Override
44
+    public void onHide() {
45
+        fabAnimator.hide();
46
+    }
47
+}

+ 42
- 15
lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java View File

@@ -5,19 +5,19 @@ import android.animation.AnimatorListenerAdapter;
5 5
 import android.animation.AnimatorSet;
6 6
 import android.animation.ObjectAnimator;
7 7
 import android.content.Context;
8
+import android.support.annotation.NonNull;
8 9
 import android.support.annotation.Nullable;
9 10
 import android.view.View;
10 11
 import android.view.animation.AccelerateInterpolator;
11 12
 import android.view.animation.DecelerateInterpolator;
12 13
 
14
+import com.reactnativenavigation.parse.AnimationsOptions;
13 15
 import com.reactnativenavigation.utils.UiUtils;
14 16
 
15 17
 @SuppressWarnings("ResourceType")
16 18
 public class NavigationAnimator {
17 19
 
18
-    public interface NavigationAnimationListener {
19
-        void onAnimationEnd();
20
-    }
20
+    private AnimationsOptions options = new AnimationsOptions();
21 21
 
22 22
     private static final int DURATION = 300;
23 23
     private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
@@ -28,12 +28,14 @@ public class NavigationAnimator {
28 28
         translationY = UiUtils.getWindowHeight(context);
29 29
     }
30 30
 
31
-    public void animatePush(final View view, @Nullable final NavigationAnimationListener animationListener) {
31
+    public void animatePush(final View view, @Nullable final AnimationListener animationListener) {
32 32
         view.setVisibility(View.INVISIBLE);
33
-        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1);
34
-        alpha.setInterpolator(DECELERATE_INTERPOLATOR);
35
-
36
-        AnimatorSet set = new AnimatorSet();
33
+        AnimatorSet set;
34
+        if (options.push.hasValue()) {
35
+            set = options.push.getAnimation(view);
36
+        } else {
37
+            set = getDefaultPushAnimation(view);
38
+        }
37 39
         set.addListener(new AnimatorListenerAdapter() {
38 40
             @Override
39 41
             public void onAnimationStart(Animator animation) {
@@ -47,19 +49,30 @@ public class NavigationAnimator {
47 49
                 }
48 50
             }
49 51
         });
52
+        set.start();
53
+    }
54
+
55
+    @NonNull
56
+    private AnimatorSet getDefaultPushAnimation(View view) {
57
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1);
58
+        alpha.setInterpolator(DECELERATE_INTERPOLATOR);
59
+
60
+        AnimatorSet set = new AnimatorSet();
50 61
         ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, this.translationY, 0);
51 62
         translationY.setInterpolator(DECELERATE_INTERPOLATOR);
52 63
         translationY.setDuration(DURATION);
53 64
         alpha.setDuration(DURATION);
54 65
         set.playTogether(translationY, alpha);
55
-        set.start();
66
+        return set;
56 67
     }
57 68
 
58
-    public void animatePop(View view, @Nullable final NavigationAnimationListener animationListener) {
59
-        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
60
-        alpha.setInterpolator(ACCELERATE_INTERPOLATOR);
61
-
62
-        AnimatorSet set = new AnimatorSet();
69
+    public void animatePop(View view, @Nullable final AnimationListener animationListener) {
70
+        AnimatorSet set;
71
+        if (options.pop.hasValue()) {
72
+            set = options.pop.getAnimation(view);
73
+        } else {
74
+            set = getDefaultPopAnimation(view);
75
+        }
63 76
         set.addListener(new AnimatorListenerAdapter() {
64 77
             @Override
65 78
             public void onAnimationEnd(Animator animation) {
@@ -68,11 +81,25 @@ public class NavigationAnimator {
68 81
                 }
69 82
             }
70 83
         });
84
+        set.start();
85
+    }
86
+
87
+    @NonNull
88
+    private AnimatorSet getDefaultPopAnimation(View view) {
89
+        AnimatorSet set = new AnimatorSet();
90
+
91
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
92
+        alpha.setInterpolator(ACCELERATE_INTERPOLATOR);
93
+
71 94
         ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0, this.translationY);
72 95
         translationY.setInterpolator(ACCELERATE_INTERPOLATOR);
73 96
         translationY.setDuration(DURATION);
74 97
         alpha.setDuration(DURATION);
75 98
         set.playTogether(translationY, alpha);
76
-        set.start();
99
+        return set;
100
+    }
101
+
102
+    public void setOptions(AnimationsOptions options) {
103
+        this.options = options;
77 104
     }
78 105
 }

+ 16
- 15
lib/android/app/src/main/java/com/reactnativenavigation/anim/TopBarAnimator.java View File

@@ -20,26 +20,23 @@ public class TopBarAnimator {
20 20
 
21 21
     private TopBar topBar;
22 22
     private View contentView;
23
+    private ObjectAnimator hideAnimator;
24
+    private ObjectAnimator showAnimator;
23 25
 
24 26
     public TopBarAnimator(TopBar topBar) {
25 27
         this.topBar = topBar;
26 28
     }
27 29
 
28
-    public TopBarAnimator(TopBar topBar, View contentView) {
29
-        this.topBar = topBar;
30
-        this.contentView = contentView;
31
-    }
32
-
33 30
     public void show() {
34 31
         show(-1 * topBar.getMeasuredHeight(), decelerateInterpolator, DURATION_TOPBAR);
35 32
     }
36 33
 
37 34
     public void show(float startTranslation, TimeInterpolator interpolator, int duration) {
38
-        ObjectAnimator topbarAnim = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, 0);
39
-        topbarAnim.setInterpolator(interpolator);
40
-        topbarAnim.setDuration(duration);
35
+        showAnimator = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, 0);
36
+        showAnimator.setInterpolator(interpolator);
37
+        showAnimator.setDuration(duration);
41 38
 
42
-        topbarAnim.addListener(new AnimatorListenerAdapter() {
39
+        showAnimator.addListener(new AnimatorListenerAdapter() {
43 40
 
44 41
             @Override
45 42
             public void onAnimationStart(Animator animation) {
@@ -55,7 +52,7 @@ public class TopBarAnimator {
55 52
                 }
56 53
             }
57 54
         });
58
-        topbarAnim.start();
55
+        showAnimator.start();
59 56
     }
60 57
 
61 58
     public void hide() {
@@ -63,11 +60,11 @@ public class TopBarAnimator {
63 60
     }
64 61
 
65 62
     void hide(float startTranslation, TimeInterpolator interpolator, int duration) {
66
-        ObjectAnimator animator = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, -1 * topBar.getMeasuredHeight());
67
-        animator.setInterpolator(interpolator);
68
-        animator.setDuration(duration);
63
+        hideAnimator = ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Y, startTranslation, -1 * topBar.getMeasuredHeight());
64
+        hideAnimator.setInterpolator(interpolator);
65
+        hideAnimator.setDuration(duration);
69 66
 
70
-        animator.addListener(new AnimatorListenerAdapter() {
67
+        hideAnimator.addListener(new AnimatorListenerAdapter() {
71 68
             @Override
72 69
             public void onAnimationEnd(Animator animation) {
73 70
                 if (contentView != null) {
@@ -79,6 +76,10 @@ public class TopBarAnimator {
79 76
                 topBar.setVisibility(View.GONE);
80 77
             }
81 78
         });
82
-        animator.start();
79
+        hideAnimator.start();
80
+    }
81
+
82
+    public boolean isRunning() {
83
+        return (hideAnimator != null && hideAnimator.isRunning()) || (showAnimator != null && showAnimator.isRunning());
83 84
     }
84 85
 }

+ 49
- 49
lib/android/app/src/main/java/com/reactnativenavigation/anim/ViewAnimationSetBuilder.java View File

@@ -8,53 +8,53 @@ import java.util.List;
8 8
 
9 9
 public class ViewAnimationSetBuilder implements Animation.AnimationListener {
10 10
 
11
-	private Runnable onComplete;
12
-	private List<View> views = new ArrayList<>();
13
-	private List<Animation> pendingAnimations = new ArrayList<>();
14
-	private boolean started = false;
15
-
16
-	public ViewAnimationSetBuilder withEndListener(Runnable onComplete) {
17
-		this.onComplete = onComplete;
18
-		return this;
19
-	}
20
-
21
-	public ViewAnimationSetBuilder add(View view, Animation animation) {
22
-		views.add(view);
23
-		pendingAnimations.add(animation);
24
-		animation.setAnimationListener(this);
25
-		view.setAnimation(animation);
26
-		return this;
27
-	}
28
-
29
-	public void start() {
30
-		for (Animation animation : pendingAnimations) {
31
-			animation.start();
32
-		}
33
-		started = true;
34
-		if (pendingAnimations.isEmpty()) finish();
35
-	}
36
-
37
-	@Override
38
-	public void onAnimationStart(final Animation animation) {
39
-		// nothing
40
-	}
41
-
42
-	@Override
43
-	public void onAnimationEnd(final Animation animation) {
44
-		pendingAnimations.remove(animation);
45
-		if (started && pendingAnimations.isEmpty()) finish();
46
-	}
47
-
48
-	@Override
49
-	public void onAnimationRepeat(final Animation animation) {
50
-		// nothing
51
-	}
52
-
53
-	private void finish() {
54
-		for (View view : views) {
55
-			view.clearAnimation();
56
-		}
57
-		views.clear();
58
-		if (onComplete != null) onComplete.run();
59
-	}
11
+    private Runnable onComplete;
12
+    private List<View> views = new ArrayList<>();
13
+    private List<Animation> pendingAnimations = new ArrayList<>();
14
+    private boolean started = false;
15
+
16
+    public ViewAnimationSetBuilder withEndListener(Runnable onComplete) {
17
+        this.onComplete = onComplete;
18
+        return this;
19
+    }
20
+
21
+    public ViewAnimationSetBuilder add(View view, Animation animation) {
22
+        views.add(view);
23
+        pendingAnimations.add(animation);
24
+        animation.setAnimationListener(this);
25
+        view.setAnimation(animation);
26
+        return this;
27
+    }
28
+
29
+    public void start() {
30
+        for (Animation animation : pendingAnimations) {
31
+            animation.start();
32
+        }
33
+        started = true;
34
+        if (pendingAnimations.isEmpty()) finish();
35
+    }
36
+
37
+    @Override
38
+    public void onAnimationStart(final Animation animation) {
39
+        // nothing
40
+    }
41
+
42
+    @Override
43
+    public void onAnimationEnd(final Animation animation) {
44
+        pendingAnimations.remove(animation);
45
+        if (started && pendingAnimations.isEmpty()) finish();
46
+    }
47
+
48
+    @Override
49
+    public void onAnimationRepeat(final Animation animation) {
50
+        // nothing
51
+    }
52
+
53
+    private void finish() {
54
+        for (View view : views) {
55
+            view.clearAnimation();
56
+        }
57
+        views.clear();
58
+        if (onComplete != null) onComplete.run();
59
+    }
60 60
 }

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

@@ -79,6 +79,7 @@ public class ScrollEventListener implements EventDispatcherListener {
79 79
     private void onVerticalScroll(int scrollY, int oldScrollY) {
80 80
         if (scrollY < 0) return;
81 81
         if (!dragStarted) return;
82
+        if (view == null) return;
82 83
 
83 84
         final int scrollDiff = calcScrollDiff(scrollY, oldScrollY, view.getMeasuredHeight());
84 85
         final float translationY = view.getTranslationY() - scrollDiff;

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

@@ -0,0 +1,84 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+
4
+import android.animation.Animator;
5
+import android.animation.AnimatorSet;
6
+import android.util.Property;
7
+import android.view.View;
8
+
9
+import org.json.JSONObject;
10
+
11
+import java.util.ArrayList;
12
+import java.util.HashSet;
13
+import java.util.Iterator;
14
+import java.util.List;
15
+
16
+public class AnimationOptions {
17
+
18
+    public static AnimationOptions parse(JSONObject json) {
19
+        AnimationOptions options = new AnimationOptions();
20
+        if (json == null) return options;
21
+
22
+        options.hasValue = true;
23
+        for (Iterator<String> it = json.keys(); it.hasNext(); ) {
24
+            String key = it.next();
25
+            options.valueOptions.add(ValueAnimationOptions.parse(json.optJSONObject(key), getAnimProp(key)));
26
+        }
27
+
28
+        return options;
29
+    }
30
+
31
+    private boolean hasValue = false;
32
+
33
+    private HashSet<ValueAnimationOptions> valueOptions = new HashSet<>();
34
+
35
+    void mergeWith(AnimationOptions other) {
36
+        if (other.hasValue()) {
37
+            hasValue = true;
38
+            valueOptions = other.valueOptions;
39
+        }
40
+    }
41
+
42
+    void mergeWithDefault(AnimationOptions defaultOptions) {
43
+        if (defaultOptions.hasValue()) {
44
+            hasValue = true;
45
+            valueOptions = defaultOptions.valueOptions;
46
+        }
47
+    }
48
+
49
+    public boolean hasValue() {
50
+        return hasValue;
51
+    }
52
+
53
+    public AnimatorSet getAnimation(View view) {
54
+        AnimatorSet animationSet = new AnimatorSet();
55
+        List<Animator> animators = new ArrayList<>();
56
+        for (ValueAnimationOptions options: valueOptions) {
57
+            animators.add(options.getAnimation(view));
58
+        }
59
+        animationSet.playTogether(animators);
60
+        return animationSet;
61
+    }
62
+
63
+    private static Property<View, Float> getAnimProp(String key) {
64
+        switch (key) {
65
+            case "y":
66
+                return View.TRANSLATION_Y;
67
+            case "x":
68
+                return View.TRANSLATION_X;
69
+            case "alpha":
70
+                return View.ALPHA;
71
+            case "scaleY":
72
+                return View.SCALE_Y;
73
+            case "scaleX":
74
+                return View.SCALE_X;
75
+            case "rotationX":
76
+                return View.ROTATION_X;
77
+            case "rotationY":
78
+                return View.ROTATION_Y;
79
+            case "rotation":
80
+                return View.ROTATION;
81
+        }
82
+        throw new IllegalArgumentException("This animation is not supported: " + key);
83
+    }
84
+}

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

@@ -0,0 +1,43 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+
4
+import org.json.JSONObject;
5
+
6
+public class AnimationsOptions {
7
+
8
+    public static AnimationsOptions parse(JSONObject json) {
9
+        AnimationsOptions options = new AnimationsOptions();
10
+        if (json == null) return options;
11
+
12
+        options.push = AnimationOptions.parse(json.optJSONObject("push"));
13
+        options.pop = AnimationOptions.parse(json.optJSONObject("pop"));
14
+        options.startApp = AnimationOptions.parse(json.optJSONObject("startApp"));
15
+        options.showModal = AnimationOptions.parse(json.optJSONObject("showModal"));
16
+        options.dismissModal = AnimationOptions.parse(json.optJSONObject("dismissModal"));
17
+
18
+        return options;
19
+    }
20
+
21
+    public AnimationOptions push = new AnimationOptions();
22
+    public AnimationOptions pop = new AnimationOptions();
23
+    public AnimationOptions startApp = new AnimationOptions();
24
+    public AnimationOptions showModal = new AnimationOptions();
25
+    public AnimationOptions dismissModal = new AnimationOptions();
26
+
27
+    void mergeWith(AnimationsOptions other) {
28
+        push.mergeWith(other.push);
29
+        pop.mergeWith(other.pop);
30
+        startApp.mergeWith(other.startApp);
31
+        showModal.mergeWith(other.showModal);
32
+        dismissModal.mergeWith(other.dismissModal);
33
+
34
+    }
35
+
36
+    void mergeWithDefault(AnimationsOptions defaultOptions) {
37
+        push.mergeWithDefault(defaultOptions.push);
38
+        pop.mergeWithDefault(defaultOptions.pop);
39
+        startApp.mergeWithDefault(defaultOptions.startApp);
40
+        showModal.mergeWithDefault(defaultOptions.showModal);
41
+        dismissModal.mergeWithDefault(defaultOptions.dismissModal);
42
+    }
43
+}

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

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

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

@@ -1,7 +1,17 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3
-
4
-import com.reactnativenavigation.parse.Options.BooleanOptions;
3
+import com.reactnativenavigation.parse.params.Bool;
4
+import com.reactnativenavigation.parse.params.Color;
5
+import com.reactnativenavigation.parse.params.NullBool;
6
+import com.reactnativenavigation.parse.params.NullColor;
7
+import com.reactnativenavigation.parse.params.NullNumber;
8
+import com.reactnativenavigation.parse.params.NullText;
9
+import com.reactnativenavigation.parse.params.Number;
10
+import com.reactnativenavigation.parse.params.Text;
11
+import com.reactnativenavigation.parse.parsers.BoolParser;
12
+import com.reactnativenavigation.parse.parsers.ColorParser;
13
+import com.reactnativenavigation.parse.parsers.NumberParser;
14
+import com.reactnativenavigation.parse.parsers.TextParser;
5 15
 
6 16
 import org.json.JSONObject;
7 17
 
@@ -11,54 +21,72 @@ public class BottomTabsOptions implements DEFAULT_VALUES {
11 21
 		BottomTabsOptions options = new BottomTabsOptions();
12 22
 		if (json == null) return options;
13 23
 
14
-		options.currentTabId = TextParser.parse(json, "currentTabId");
15
-		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.animateHide = BooleanOptions.parse(json.optString("animateHide"));
24
+        options.backgroundColor = ColorParser.parse(json, "backgroundColor");
25
+        options.tabColor = ColorParser.parse(json, "tabColor");
26
+        options.selectedTabColor = ColorParser.parse(json, "selectedTabColor");
27
+        options.currentTabId = TextParser.parse(json, "currentTabId");
28
+		options.currentTabIndex = NumberParser.parse(json,"currentTabIndex");
29
+		options.visible = BoolParser.parse(json,"visible");
30
+		options.animate = BoolParser.parse(json,"animate");
31
+        options.testId = TextParser.parse(json, "testID");
19 32
 
20 33
 		return options;
21 34
 	}
22 35
 
23
-	int tabBadge = NO_INT_VALUE;
24
-	BooleanOptions hidden = BooleanOptions.False;
25
-	BooleanOptions animateHide = BooleanOptions.False;
26
-	public int currentTabIndex = NO_INT_VALUE;
36
+    public Color backgroundColor = new NullColor();
37
+    public Color tabColor = new NullColor();
38
+    public Color selectedTabColor = new NullColor();
39
+	public Bool visible = new NullBool();
40
+	public Bool animate = new NullBool();
41
+	public Number currentTabIndex = new NullNumber();
27 42
 	public Text currentTabId = new NullText();
43
+    public Text testId = new NullText();
28 44
 
29 45
 	void mergeWith(final BottomTabsOptions other) {
30 46
 		if (other.currentTabId.hasValue()) {
31 47
 			currentTabId = other.currentTabId;
32 48
 		}
33
-		if (NO_INT_VALUE != other.currentTabIndex) {
49
+		if (other.currentTabIndex.hasValue()) {
34 50
             currentTabIndex = other.currentTabIndex;
35 51
 		}
36
-		if (NO_INT_VALUE != other.tabBadge) {
37
-			tabBadge = other.tabBadge;
38
-		}
39
-		if (other.hidden != BooleanOptions.NoValue) {
40
-			hidden = other.hidden;
52
+		if (other.visible.hasValue()) {
53
+			visible = other.visible;
41 54
 		}
42
-		if (other.animateHide != BooleanOptions.NoValue) {
43
-			animateHide = other.animateHide;
55
+		if (other.animate.hasValue()) {
56
+			animate = other.animate;
44 57
 		}
45
-	}
58
+        if (other.tabColor.hasValue()) {
59
+            tabColor = other.tabColor;
60
+        }
61
+        if (other.selectedTabColor.hasValue()) {
62
+            selectedTabColor = other.selectedTabColor;
63
+        }
64
+        if (other.backgroundColor.hasValue()) {
65
+		    backgroundColor = other.backgroundColor;
66
+        }
67
+    }
46 68
 
47 69
     void mergeWithDefault(final BottomTabsOptions defaultOptions) {
48 70
         if (!currentTabId.hasValue()) {
49 71
             currentTabId = defaultOptions.currentTabId;
50 72
         }
51
-        if (NO_INT_VALUE == currentTabIndex) {
73
+        if (!currentTabIndex.hasValue()) {
52 74
             currentTabIndex = defaultOptions.currentTabIndex;
53 75
         }
54
-        if (NO_INT_VALUE == tabBadge) {
55
-            tabBadge = defaultOptions.tabBadge;
76
+        if (!visible.hasValue()) {
77
+            visible = defaultOptions.visible;
78
+        }
79
+        if (!animate.hasValue()) {
80
+            animate = defaultOptions.animate;
81
+        }
82
+        if (!tabColor.hasValue()) {
83
+            tabColor = defaultOptions.tabColor;
56 84
         }
57
-        if (hidden == BooleanOptions.NoValue) {
58
-            hidden = defaultOptions.hidden;
85
+        if (!selectedTabColor.hasValue()) {
86
+            selectedTabColor = defaultOptions.selectedTabColor;
59 87
         }
60
-        if (animateHide == BooleanOptions.NoValue) {
61
-            animateHide = defaultOptions.animateHide;
88
+        if (!backgroundColor.hasValue()) {
89
+            backgroundColor = defaultOptions.backgroundColor;
62 90
         }
63 91
     }
64 92
 }

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

@@ -0,0 +1,130 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+
4
+import com.reactnativenavigation.parse.params.Bool;
5
+import com.reactnativenavigation.parse.params.Color;
6
+import com.reactnativenavigation.parse.params.NullBool;
7
+import com.reactnativenavigation.parse.params.NullColor;
8
+import com.reactnativenavigation.parse.params.NullText;
9
+import com.reactnativenavigation.parse.params.Text;
10
+import com.reactnativenavigation.parse.parsers.BoolParser;
11
+import com.reactnativenavigation.parse.parsers.ColorParser;
12
+import com.reactnativenavigation.parse.parsers.TextParser;
13
+
14
+import org.json.JSONArray;
15
+import org.json.JSONObject;
16
+
17
+import java.util.ArrayList;
18
+
19
+public class FabOptions implements DEFAULT_VALUES {
20
+
21
+    public static FabOptions parse(JSONObject json) {
22
+        FabOptions options = new FabOptions();
23
+        if (json == null) return options;
24
+
25
+        options.id = TextParser.parse(json, "id");
26
+        options.backgroundColor = ColorParser.parse(json, "backgroundColor");
27
+        options.clickColor = ColorParser.parse(json, "clickColor");
28
+        options.rippleColor = ColorParser.parse(json, "rippleColor");
29
+        options.visible = BoolParser.parse(json, "visible");
30
+        if (json.has("icon")) {
31
+            options.icon = TextParser.parse(json.optJSONObject("icon"), "uri");
32
+        }
33
+        if (json.has("actions")) {
34
+            JSONArray fabsArray = json.optJSONArray("actions");
35
+            for (int i = 0; i < fabsArray.length(); i++) {
36
+                options.actionsArray.add(FabOptions.parse(fabsArray.optJSONObject(i)));
37
+            }
38
+        }
39
+        options.alignHorizontally = TextParser.parse(json, "alignHorizontally");
40
+        options.alignVertically = TextParser.parse(json, "alignVertically");
41
+        options.hideOnScroll = BoolParser.parse(json, "hideOnScroll");
42
+        options.size = TextParser.parse(json, "size");
43
+
44
+        return options;
45
+    }
46
+
47
+    public Text id = new NullText();
48
+    public Color backgroundColor = new NullColor();
49
+    public Color clickColor = new NullColor();
50
+    public Color rippleColor = new NullColor();
51
+    public Text icon = new NullText();
52
+    public Bool visible = new NullBool();
53
+    public ArrayList<FabOptions> actionsArray = new ArrayList<>();
54
+    public Text alignHorizontally = new NullText();
55
+    public Text alignVertically = new NullText();
56
+    public Bool hideOnScroll = new NullBool();
57
+    public Text size = new NullText();
58
+
59
+    void mergeWith(final FabOptions other) {
60
+        if (other.id.hasValue()) {
61
+            id = other.id;
62
+        }
63
+        if (other.backgroundColor.hasValue()) {
64
+            backgroundColor = other.backgroundColor;
65
+        }
66
+        if (other.clickColor.hasValue()) {
67
+            clickColor = other.clickColor;
68
+        }
69
+        if (other.rippleColor.hasValue()) {
70
+            rippleColor = other.rippleColor;
71
+        }
72
+        if (other.visible.hasValue()) {
73
+            visible = other.visible;
74
+        }
75
+        if (other.icon.hasValue()) {
76
+            icon = other.icon;
77
+        }
78
+        if (other.actionsArray.size() > 0) {
79
+            actionsArray = other.actionsArray;
80
+        }
81
+        if (other.alignVertically.hasValue()) {
82
+            alignVertically = other.alignVertically;
83
+        }
84
+        if (other.alignHorizontally.hasValue()) {
85
+            alignHorizontally = other.alignHorizontally;
86
+        }
87
+        if (other.hideOnScroll.hasValue()) {
88
+            hideOnScroll = other.hideOnScroll;
89
+        }
90
+        if (other.size.hasValue()) {
91
+            size = other.size;
92
+        }
93
+    }
94
+
95
+    void mergeWithDefault(FabOptions defaultOptions) {
96
+        if (!id.hasValue()) {
97
+            id = defaultOptions.id;
98
+        }
99
+        if (!backgroundColor.hasValue()) {
100
+            backgroundColor = defaultOptions.backgroundColor;
101
+        }
102
+        if (!clickColor.hasValue()) {
103
+            clickColor = defaultOptions.clickColor;
104
+        }
105
+        if (!rippleColor.hasValue()) {
106
+            rippleColor = defaultOptions.rippleColor;
107
+        }
108
+        if (!visible.hasValue()) {
109
+            visible = defaultOptions.visible;
110
+        }
111
+        if (!icon.hasValue()) {
112
+            icon = defaultOptions.icon;
113
+        }
114
+        if (actionsArray.size() == 0) {
115
+            actionsArray = defaultOptions.actionsArray;
116
+        }
117
+        if (!alignHorizontally.hasValue()) {
118
+            alignHorizontally = defaultOptions.alignHorizontally;
119
+        }
120
+        if (!alignVertically.hasValue()) {
121
+            alignVertically = defaultOptions.alignVertically;
122
+        }
123
+        if (!hideOnScroll.hasValue()) {
124
+            hideOnScroll = defaultOptions.hideOnScroll;
125
+        }
126
+        if (!size.hasValue()) {
127
+            size = defaultOptions.size;
128
+        }
129
+    }
130
+}

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

@@ -3,9 +3,10 @@ package com.reactnativenavigation.parse;
3 3
 import android.app.Activity;
4 4
 
5 5
 import com.facebook.react.ReactInstanceManager;
6
+import com.reactnativenavigation.utils.ImageLoader;
6 7
 import com.reactnativenavigation.utils.NoOpPromise;
7 8
 import com.reactnativenavigation.utils.TypefaceLoader;
8
-import com.reactnativenavigation.viewcontrollers.BottomTabsController;
9
+import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController;
9 10
 import com.reactnativenavigation.viewcontrollers.ComponentViewController;
10 11
 import com.reactnativenavigation.viewcontrollers.SideMenuController;
11 12
 import com.reactnativenavigation.viewcontrollers.StackController;
@@ -55,7 +56,8 @@ public class LayoutFactory {
55 56
 	}
56 57
 
57 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 61
 		for (LayoutNode child : node.children) {
60 62
 			ViewController childLayout = create(child);
61 63
 			switch (child.type) {
@@ -100,7 +102,8 @@ public class LayoutFactory {
100 102
 	}
101 103
 
102 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 107
         for (int i = 0; i < node.children.size(); i++) {
105 108
             if (i < node.children.size() - 1) {
106 109
                 stackController.push(create(node.children.get(i)), new NoOpPromise());
@@ -112,10 +115,11 @@ public class LayoutFactory {
112 115
 	}
113 116
 
114 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 120
 		List<ViewController> tabs = new ArrayList<>();
117 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 124
 		tabsComponent.setTabs(tabs);
121 125
 		return tabsComponent;

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

@@ -27,7 +27,7 @@ public class LayoutNode {
27 27
 		this(id, type, new JSONObject(), new ArrayList<>());
28 28
 	}
29 29
 
30
-	LayoutNode(String id, Type type, JSONObject data, List<LayoutNode> children) {
30
+	public LayoutNode(String id, Type type, JSONObject data, List<LayoutNode> children) {
31 31
 		this.id = id;
32 32
 		this.type = type;
33 33
 		this.data = data;

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

@@ -1,7 +1,7 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3
+import android.support.annotation.CheckResult;
3 4
 import android.support.annotation.NonNull;
4
-import android.text.TextUtils;
5 5
 
6 6
 import com.reactnativenavigation.utils.TypefaceLoader;
7 7
 
@@ -9,58 +9,111 @@ import org.json.JSONObject;
9 9
 
10 10
 public class Options implements DEFAULT_VALUES {
11 11
 
12
-    public enum BooleanOptions {
13
-		True,
14
-		False,
15
-		NoValue;
16
-
17
-		static BooleanOptions parse(String value) {
18
-			if (!TextUtils.isEmpty(value)) {
19
-				return Boolean.valueOf(value) ? True : False;
20
-			}
21
-			return NoValue;
22
-		}
23
-	}
24
-
25 12
     @NonNull
26 13
     public static Options parse(TypefaceLoader typefaceManager, JSONObject json) {
27 14
         return parse(typefaceManager, json, new Options());
28 15
     }
29 16
 
30
-	@NonNull
31
-	public static Options parse(TypefaceLoader typefaceManager, JSONObject json, @NonNull Options defaultOptions) {
32
-		Options result = new Options();
33
-		if (json == null) return result;
17
+    @NonNull
18
+    public static Options parse(TypefaceLoader typefaceManager, JSONObject json, @NonNull Options defaultOptions) {
19
+        Options result = new Options();
20
+        if (json == null) return result;
34 21
 
35
-		result.topBarOptions = TopBarOptions.parse(typefaceManager, json.optJSONObject("topBar"));
36
-		result.topTabsOptions = TopTabsOptions.parse(json.optJSONObject("topTabs"));
22
+        result.topBarOptions = TopBarOptions.parse(typefaceManager, json.optJSONObject("topBar"));
23
+        result.topTabsOptions = TopTabsOptions.parse(json.optJSONObject("topTabs"));
37 24
         result.topTabOptions = TopTabOptions.parse(typefaceManager, json.optJSONObject("topTab"));
38
-		result.bottomTabsOptions = BottomTabsOptions.parse(json.optJSONObject("bottomTabs"));
25
+        result.bottomTabOptions = BottomTabOptions.parse(json.optJSONObject("bottomTab"));
26
+        result.bottomTabsOptions = BottomTabsOptions.parse(json.optJSONObject("bottomTabs"));
39 27
         result.overlayOptions = OverlayOptions.parse(json.optJSONObject("overlay"));
28
+        result.fabOptions = FabOptions.parse(json.optJSONObject("fab"));
29
+        result.animationsOptions = AnimationsOptions.parse(json.optJSONObject("animations"));
30
+        result.sideMenuRootOptions = SideMenuRootOptions.parse(json.optJSONObject("sideMenu"));
40 31
 
41
-		return result.withDefaultOptions(defaultOptions);
42
-	}
32
+        return result.withDefaultOptions(defaultOptions);
33
+    }
43 34
 
44 35
     @NonNull public TopBarOptions topBarOptions = new TopBarOptions();
45 36
     @NonNull public TopTabsOptions topTabsOptions = new TopTabsOptions();
46 37
     @NonNull public TopTabOptions topTabOptions = new TopTabOptions();
38
+    @NonNull public BottomTabOptions bottomTabOptions = new BottomTabOptions();
47 39
     @NonNull public BottomTabsOptions bottomTabsOptions = new BottomTabsOptions();
48 40
     @NonNull public OverlayOptions overlayOptions = new OverlayOptions();
41
+    @NonNull public FabOptions fabOptions = new FabOptions();
42
+    @NonNull public AnimationsOptions animationsOptions = new AnimationsOptions();
43
+    @NonNull public SideMenuRootOptions sideMenuRootOptions = new SideMenuRootOptions();
49 44
 
50 45
     void setTopTabIndex(int i) {
51 46
         topTabOptions.tabIndex = i;
52 47
     }
53 48
 
54
-	public void mergeWith(final Options other) {
55
-        topBarOptions.mergeWith(other.topBarOptions);
56
-        topTabsOptions.mergeWith(other.topTabsOptions);
57
-        bottomTabsOptions.mergeWith(other.bottomTabsOptions);
49
+    @CheckResult
50
+    public Options copy() {
51
+        Options result = new Options();
52
+        result.topBarOptions.mergeWith(topBarOptions);
53
+        result.topTabsOptions.mergeWith(topTabsOptions);
54
+        result.topTabOptions.mergeWith(topTabOptions);
55
+        result.bottomTabOptions.mergeWith(bottomTabOptions);
56
+        result.bottomTabsOptions.mergeWith(bottomTabsOptions);
57
+        result.overlayOptions = overlayOptions;
58
+        result.fabOptions.mergeWith(fabOptions);
59
+        result.animationsOptions.mergeWith(animationsOptions);
60
+        result.sideMenuRootOptions.mergeWith(sideMenuRootOptions);
61
+        return result;
62
+    }
63
+
64
+    @CheckResult
65
+	public Options mergeWith(final Options other) {
66
+        Options result = copy();
67
+        result.topBarOptions.mergeWith(other.topBarOptions);
68
+        result.topTabsOptions.mergeWith(other.topTabsOptions);
69
+        result.topTabOptions.mergeWith(other.topTabOptions);
70
+        result.bottomTabOptions.mergeWith(other.bottomTabOptions);
71
+        result.bottomTabsOptions.mergeWith(other.bottomTabsOptions);
72
+        result.fabOptions.mergeWith(other.fabOptions);
73
+        result.animationsOptions.mergeWith(other.animationsOptions);
74
+        result.sideMenuRootOptions.mergeWith(other.sideMenuRootOptions);
75
+        return result;
58 76
     }
59 77
 
60 78
     Options withDefaultOptions(final Options other) {
61 79
         topBarOptions.mergeWithDefault(other.topBarOptions);
80
+        topTabOptions.mergeWithDefault(other.topTabOptions);
62 81
         topTabsOptions.mergeWithDefault(other.topTabsOptions);
82
+        bottomTabOptions.mergeWithDefault(other.bottomTabOptions);
63 83
         bottomTabsOptions.mergeWithDefault(other.bottomTabsOptions);
84
+        fabOptions.mergeWithDefault(other.fabOptions);
85
+        animationsOptions.mergeWithDefault(other.animationsOptions);
86
+        sideMenuRootOptions.mergeWithDefault(other.sideMenuRootOptions);
87
+        return this;
88
+    }
89
+
90
+    public Options clearTopBarOptions() {
91
+        topBarOptions = new TopBarOptions();
92
+        return this;
93
+    }
94
+
95
+    public Options clearBottomTabsOptions() {
96
+        bottomTabsOptions = new BottomTabsOptions();
97
+        return this;
98
+    }
99
+
100
+    public Options clearTopTabOptions() {
101
+        topTabOptions = new TopTabOptions();
102
+        return this;
103
+    }
104
+
105
+    public Options clearTopTabsOptions() {
106
+        topTabsOptions = new TopTabsOptions();
107
+        return this;
108
+    }
109
+
110
+    public Options clearBottomTabOptions() {
111
+        bottomTabOptions = new BottomTabOptions();
112
+        return this;
113
+    }
114
+
115
+    public Options clearSideMenuOptions() {
116
+        sideMenuRootOptions = new SideMenuRootOptions();
64 117
         return this;
65 118
     }
66 119
 }

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

@@ -1,15 +1,19 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3
+import com.reactnativenavigation.parse.params.Bool;
4
+import com.reactnativenavigation.parse.params.NullBool;
5
+import com.reactnativenavigation.parse.parsers.BoolParser;
6
+
3 7
 import org.json.JSONObject;
4 8
 
5 9
 public class OverlayOptions {
6
-    public Options.BooleanOptions interceptTouchOutside = Options.BooleanOptions.False;
10
+    public Bool interceptTouchOutside = new NullBool();
7 11
 
8 12
     public static OverlayOptions parse(JSONObject json) {
9 13
         OverlayOptions options = new OverlayOptions();
10 14
         if (json == null) return options;
11 15
 
12
-        options.interceptTouchOutside = Options.BooleanOptions.parse(json.optString("interceptTouchOutside", ""));
16
+        options.interceptTouchOutside = BoolParser.parse(json,"interceptTouchOutside");
13 17
         return options;
14 18
     }
15 19
 }

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

@@ -0,0 +1,25 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+import com.reactnativenavigation.parse.params.Bool;
4
+import com.reactnativenavigation.parse.params.NullBool;
5
+import com.reactnativenavigation.parse.parsers.BoolParser;
6
+
7
+import org.json.JSONObject;
8
+
9
+public class SideMenuOptions {
10
+    public Bool visible = new NullBool();
11
+
12
+    public static SideMenuOptions parse(JSONObject json) {
13
+        SideMenuOptions options = new SideMenuOptions();
14
+        if (json == null) return options;
15
+
16
+        options.visible = BoolParser.parse(json, "visible");
17
+        return options;
18
+    }
19
+
20
+    public void mergeWith(SideMenuOptions other) {
21
+        if (other.visible.hasValue()) {
22
+            visible = other.visible;
23
+        }
24
+    }
25
+}

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

@@ -0,0 +1,27 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+import org.json.JSONObject;
4
+
5
+public class SideMenuRootOptions {
6
+    public SideMenuOptions left = new SideMenuOptions();
7
+    public SideMenuOptions right = new SideMenuOptions();
8
+
9
+    public static SideMenuRootOptions parse(JSONObject json) {
10
+        SideMenuRootOptions options = new SideMenuRootOptions();
11
+        if (json == null) return options;
12
+
13
+        options.left = SideMenuOptions.parse(json.optJSONObject("left"));
14
+        options.right = SideMenuOptions.parse(json.optJSONObject("right"));
15
+
16
+        return options;
17
+    }
18
+
19
+    public void mergeWith(SideMenuRootOptions other) {
20
+        left.mergeWith(other.left);
21
+        right.mergeWith(other.right);
22
+    }
23
+
24
+    public void mergeWithDefault(SideMenuRootOptions sideMenuRootOptions) {
25
+
26
+    }
27
+}

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

@@ -1,7 +0,0 @@
1
-package com.reactnativenavigation.parse;
2
-
3
-public class Text extends Param<String> {
4
-    public Text(String value) {
5
-        super(value);
6
-    }
7
-}

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

@@ -4,6 +4,19 @@ package com.reactnativenavigation.parse;
4 4
 import android.graphics.Typeface;
5 5
 import android.support.annotation.Nullable;
6 6
 
7
+import com.reactnativenavigation.parse.params.Bool;
8
+import com.reactnativenavigation.parse.params.Button;
9
+import com.reactnativenavigation.parse.params.Color;
10
+import com.reactnativenavigation.parse.params.Fraction;
11
+import com.reactnativenavigation.parse.params.NullBool;
12
+import com.reactnativenavigation.parse.params.NullColor;
13
+import com.reactnativenavigation.parse.params.NullFraction;
14
+import com.reactnativenavigation.parse.params.NullText;
15
+import com.reactnativenavigation.parse.params.Text;
16
+import com.reactnativenavigation.parse.parsers.BoolParser;
17
+import com.reactnativenavigation.parse.parsers.ColorParser;
18
+import com.reactnativenavigation.parse.parsers.FractionParser;
19
+import com.reactnativenavigation.parse.parsers.TextParser;
7 20
 import com.reactnativenavigation.utils.TypefaceLoader;
8 21
 
9 22
 import org.json.JSONObject;
@@ -21,30 +34,33 @@ public class TopBarOptions implements DEFAULT_VALUES {
21 34
         options.textColor = ColorParser.parse(json, "textColor");
22 35
         options.textFontSize = FractionParser.parse(json, "textFontSize");
23 36
         options.textFontFamily = typefaceManager.getTypeFace(json.optString("textFontFamily", ""));
24
-        options.hidden = Options.BooleanOptions.parse(json.optString("hidden"));
25
-        options.animateHide = Options.BooleanOptions.parse(json.optString("animateHide"));
26
-        options.hideOnScroll = Options.BooleanOptions.parse(json.optString("hideOnScroll"));
27
-        options.drawBehind = Options.BooleanOptions.parse(json.optString("drawBehind"));
37
+        options.visible = BoolParser.parse(json, "visible");
38
+        options.animate = BoolParser.parse(json,"animate");
39
+        options.hideOnScroll = BoolParser.parse(json,"hideOnScroll");
40
+        options.drawBehind = BoolParser.parse(json,"drawBehind");
28 41
         options.rightButtons = Button.parseJsonArray(json.optJSONArray("rightButtons"));
29 42
         options.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons"));
43
+        options.testId = TextParser.parse(json, "testID");
30 44
 
31 45
         return options;
32 46
     }
33 47
 
34 48
     public Text title = new NullText();
49
+    public Text testId = new NullText();
35 50
     public Color backgroundColor = new NullColor();
36 51
     public Color textColor = new NullColor();
37 52
     public Fraction textFontSize = new NullFraction();
38 53
     @Nullable public Typeface textFontFamily;
39
-    public Options.BooleanOptions hidden = Options.BooleanOptions.NoValue;
40
-    public Options.BooleanOptions animateHide = Options.BooleanOptions.NoValue;
41
-    public Options.BooleanOptions hideOnScroll = Options.BooleanOptions.NoValue;
42
-    public Options.BooleanOptions drawBehind = Options.BooleanOptions.NoValue;
54
+    public Bool visible = new NullBool();
55
+    public Bool animate = new NullBool();
56
+    public Bool hideOnScroll = new NullBool();
57
+    public Bool drawBehind = new NullBool();
43 58
     public ArrayList<Button> leftButtons;
44 59
     public ArrayList<Button> rightButtons;
45 60
 
46 61
     void mergeWith(final TopBarOptions other) {
47
-        if (other.title != null) title = other.title;
62
+        if (other.title.hasValue())
63
+            title = other.title;
48 64
         if (other.backgroundColor.hasValue())
49 65
             backgroundColor = other.backgroundColor;
50 66
         if (other.textColor.hasValue())
@@ -53,16 +69,16 @@ public class TopBarOptions implements DEFAULT_VALUES {
53 69
             textFontSize = other.textFontSize;
54 70
         if (other.textFontFamily != null)
55 71
             textFontFamily = other.textFontFamily;
56
-        if (other.hidden != Options.BooleanOptions.NoValue) {
57
-            hidden = other.hidden;
72
+        if (other.visible.hasValue()) {
73
+            visible = other.visible;
58 74
         }
59
-        if (other.animateHide != Options.BooleanOptions.NoValue) {
60
-            animateHide = other.animateHide;
75
+        if (other.animate.hasValue()) {
76
+            animate = other.animate;
61 77
         }
62
-        if (other.hideOnScroll != Options.BooleanOptions.NoValue) {
78
+        if (other.hideOnScroll.hasValue()) {
63 79
             hideOnScroll = other.hideOnScroll;
64 80
         }
65
-        if (other.drawBehind != Options.BooleanOptions.NoValue) {
81
+        if (other.drawBehind.hasValue()) {
66 82
             drawBehind = other.drawBehind;
67 83
         }
68 84
         if (other.leftButtons != null)
@@ -82,13 +98,13 @@ public class TopBarOptions implements DEFAULT_VALUES {
82 98
             textFontSize = defaultOptions.textFontSize;
83 99
         if (textFontFamily == null)
84 100
             textFontFamily = defaultOptions.textFontFamily;
85
-        if (hidden == Options.BooleanOptions.NoValue)
86
-            hidden = defaultOptions.hidden;
87
-        if (animateHide == Options.BooleanOptions.NoValue)
88
-            animateHide = defaultOptions.animateHide;
89
-        if (hideOnScroll == Options.BooleanOptions.NoValue)
101
+        if (!visible.hasValue())
102
+            visible = defaultOptions.visible;
103
+        if (!animate.hasValue())
104
+            animate = defaultOptions.animate;
105
+        if (!hideOnScroll.hasValue())
90 106
             hideOnScroll = defaultOptions.hideOnScroll;
91
-        if (drawBehind == Options.BooleanOptions.NoValue)
107
+        if (!drawBehind.hasValue())
92 108
             drawBehind = defaultOptions.drawBehind;
93 109
         if (leftButtons == null)
94 110
             leftButtons = defaultOptions.leftButtons;

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

@@ -3,6 +3,9 @@ package com.reactnativenavigation.parse;
3 3
 import android.graphics.Typeface;
4 4
 import android.support.annotation.Nullable;
5 5
 
6
+import com.reactnativenavigation.parse.params.NullText;
7
+import com.reactnativenavigation.parse.params.Text;
8
+import com.reactnativenavigation.parse.parsers.TextParser;
6 9
 import com.reactnativenavigation.utils.TypefaceLoader;
7 10
 
8 11
 import org.json.JSONObject;
@@ -21,11 +24,13 @@ public class TopTabOptions implements DEFAULT_VALUES {
21 24
         return result;
22 25
     }
23 26
 
24
-    void mergeWith(TopTabOptions topTabsOptions) {
25
-
27
+    void mergeWith(TopTabOptions other) {
28
+        if (other.title.hasValue()) title = other.title;
29
+        if (other.fontFamily != null) fontFamily = other.fontFamily;
30
+        if (other.tabIndex >= 0) tabIndex = other.tabIndex;
26 31
     }
27 32
 
28
-    void mergeWithDefault(TopTabOptions topTabsOptions) {
29
-
33
+    void mergeWithDefault(TopTabOptions other) {
34
+        if (fontFamily == null) fontFamily = other.fontFamily;
30 35
     }
31 36
 }

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

@@ -3,6 +3,16 @@ package com.reactnativenavigation.parse;
3 3
 import android.support.annotation.NonNull;
4 4
 import android.support.annotation.Nullable;
5 5
 
6
+import com.reactnativenavigation.parse.params.Bool;
7
+import com.reactnativenavigation.parse.params.Color;
8
+import com.reactnativenavigation.parse.params.NullBool;
9
+import com.reactnativenavigation.parse.params.NullColor;
10
+import com.reactnativenavigation.parse.params.NullNumber;
11
+import com.reactnativenavigation.parse.params.Number;
12
+import com.reactnativenavigation.parse.parsers.BoolParser;
13
+import com.reactnativenavigation.parse.parsers.ColorParser;
14
+import com.reactnativenavigation.parse.parsers.NumberParser;
15
+
6 16
 import org.json.JSONObject;
7 17
 
8 18
 public class TopTabsOptions implements DEFAULT_VALUES {
@@ -10,13 +20,15 @@ public class TopTabsOptions implements DEFAULT_VALUES {
10 20
     @NonNull public Color selectedTabColor = new NullColor();
11 21
     @NonNull public Color unselectedTabColor = new NullColor();
12 22
     @NonNull public Number fontSize = new NullNumber();
23
+    @NonNull public Bool visible = new NullBool();
13 24
 
14 25
     public static TopTabsOptions parse(@Nullable JSONObject json) {
15 26
         TopTabsOptions result = new TopTabsOptions();
16 27
         if (json == null) return result;
17
-        result.selectedTabColor = ColorParser.parse(json, "selectedTabColor");
28
+        result.selectedTabColor = ColorParser.parse(json, "selectedColor");
18 29
         result.unselectedTabColor = ColorParser.parse(json, "unselectedTabColor");
19 30
         result.fontSize = NumberParser.parse(json, "fontSize");
31
+        result.visible = BoolParser.parse(json, "visible");
20 32
         return result;
21 33
     }
22 34
 
@@ -24,11 +36,13 @@ public class TopTabsOptions implements DEFAULT_VALUES {
24 36
         if (other.selectedTabColor.hasValue()) selectedTabColor = other.selectedTabColor;
25 37
         if (other.unselectedTabColor.hasValue()) unselectedTabColor = other.unselectedTabColor;
26 38
         if (other.fontSize.hasValue()) fontSize = other.fontSize;
39
+        if (other.visible.hasValue()) visible = other.visible;
27 40
     }
28 41
 
29 42
     void mergeWithDefault(TopTabsOptions defaultOptions) {
30 43
         if (!selectedTabColor.hasValue()) selectedTabColor = defaultOptions.selectedTabColor;
31 44
         if (!unselectedTabColor.hasValue()) unselectedTabColor = defaultOptions.unselectedTabColor;
32 45
         if (!fontSize.hasValue()) fontSize = defaultOptions.fontSize;
46
+        if (!visible.hasValue()) visible = defaultOptions.visible;
33 47
     }
34 48
 }

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

@@ -0,0 +1,69 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+
4
+import android.animation.Animator;
5
+import android.animation.ObjectAnimator;
6
+import android.util.Property;
7
+import android.view.View;
8
+
9
+import com.reactnativenavigation.parse.params.FloatParam;
10
+import com.reactnativenavigation.parse.params.Interpolation;
11
+import com.reactnativenavigation.parse.params.NullFloatParam;
12
+import com.reactnativenavigation.parse.params.NullNumber;
13
+import com.reactnativenavigation.parse.params.Number;
14
+import com.reactnativenavigation.parse.parsers.FloatParser;
15
+import com.reactnativenavigation.parse.parsers.InterpolationParser;
16
+import com.reactnativenavigation.parse.parsers.NumberParser;
17
+
18
+import org.json.JSONObject;
19
+
20
+public class ValueAnimationOptions implements DEFAULT_VALUES {
21
+
22
+    public static ValueAnimationOptions parse(JSONObject json, Property<View, Float> property) {
23
+        ValueAnimationOptions options = new ValueAnimationOptions();
24
+
25
+        options.animProp = property;
26
+        options.from = FloatParser.parse(json, "from");
27
+        options.to = FloatParser.parse(json, "to");
28
+        options.duration = NumberParser.parse(json, "duration");
29
+        options.startDelay = NumberParser.parse(json, "startDelay");
30
+        options.interpolation = InterpolationParser.parse(json, "interpolation");
31
+
32
+        return options;
33
+    }
34
+
35
+    private Property<View, Float> animProp;
36
+
37
+    private FloatParam from = new NullFloatParam();
38
+    private FloatParam to = new NullFloatParam();
39
+    private Number duration = new NullNumber();
40
+    private Number startDelay = new NullNumber();
41
+    private Interpolation interpolation = Interpolation.NO_VALUE;
42
+
43
+    Animator getAnimation(View view) {
44
+        if (!this.from.hasValue() || !this.to.hasValue())
45
+            throw new IllegalArgumentException("Params 'from' and 'to' are mandatory");
46
+        ObjectAnimator animator = ObjectAnimator.ofFloat(view, animProp, this.from.get(), this.to.get());
47
+        animator.setInterpolator(this.interpolation.getInterpolator());
48
+        if (this.duration.hasValue())
49
+            animator.setDuration(this.duration.get());
50
+        if (this.startDelay.hasValue())
51
+            animator.setStartDelay(this.startDelay.get());
52
+        return animator;
53
+    }
54
+
55
+    @Override
56
+    public boolean equals(Object o) {
57
+        if (this == o) return true;
58
+        if (o == null || getClass() != o.getClass()) return false;
59
+
60
+        ValueAnimationOptions options = (ValueAnimationOptions) o;
61
+
62
+        return animProp.equals(options.animProp);
63
+    }
64
+
65
+    @Override
66
+    public int hashCode() {
67
+        return animProp.hashCode();
68
+    }
69
+}

+ 23
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Bool.java View File

@@ -0,0 +1,23 @@
1
+package com.reactnativenavigation.parse.params;
2
+
3
+public class Bool extends Param<Boolean> {
4
+    public Bool(Boolean value) {
5
+        super(value);
6
+    }
7
+
8
+    public boolean isFalseOrUndefined() {
9
+        return !hasValue() || !get();
10
+    }
11
+
12
+    public boolean isTrueOrUndefined() {
13
+        return !hasValue() || get();
14
+    }
15
+
16
+    public boolean isTrue() {
17
+        return hasValue() && get();
18
+    }
19
+
20
+    public boolean isFalse() {
21
+        return hasValue() && !get();
22
+    }
23
+}

lib/android/app/src/main/java/com/reactnativenavigation/parse/Button.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Button.java View File

@@ -1,8 +1,11 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.params;
2 2
 
3 3
 import android.support.annotation.ColorInt;
4 4
 import android.view.MenuItem;
5 5
 
6
+import com.reactnativenavigation.parse.parsers.BoolParser;
7
+import com.reactnativenavigation.parse.parsers.TextParser;
8
+
6 9
 import org.json.JSONArray;
7 10
 import org.json.JSONObject;
8 11
 
@@ -12,25 +15,27 @@ import static com.reactnativenavigation.parse.Options.NO_INT_VALUE;
12 15
 
13 16
 public class Button {
14 17
 	public String id;
15
-	public Text title;
16
-	public Options.BooleanOptions disabled;
17
-	public Options.BooleanOptions disableIconTint;
18
+	public Text title = new NullText();
19
+	public Bool enabled = new NullBool();
20
+	public Bool disableIconTint = new NullBool();
18 21
 	public int showAsAction;
19 22
 	@ColorInt public int buttonColor;
20 23
 	public int buttonFontSize;
21
-	public Text buttonFontWeight;
22
-	public Text icon;
24
+	private Text buttonFontWeight = new NullText();
25
+	public Text icon = new NullText();
26
+	public Text testId = new NullText();
23 27
 
24 28
 	private static Button parseJson(JSONObject json)  {
25 29
 		Button button = new Button();
26 30
 		button.id = json.optString("id");
27 31
 		button.title = TextParser.parse(json, "title");
28
-		button.disabled = Options.BooleanOptions.parse(json.optString("disabled", ""));
29
-		button.disableIconTint = Options.BooleanOptions.parse(json.optString("disableIconTint", ""));
32
+		button.enabled = BoolParser.parse(json,"enabled");
33
+		button.disableIconTint = BoolParser.parse(json,"disableIconTint");
30 34
 		button.showAsAction = parseShowAsAction(json);
31 35
 		button.buttonColor = json.optInt("buttonColor", NO_INT_VALUE);
32 36
 		button.buttonFontSize = json.optInt("buttonFontSize", NO_INT_VALUE);
33 37
 		button.buttonFontWeight = TextParser.parse(json, "buttonFontWeight");
38
+        button.testId = TextParser.parse(json, "testID");
34 39
 
35 40
 		if (json.has("icon")) {
36 41
 			button.icon = TextParser.parse(json.optJSONObject("icon"), "uri");
@@ -39,7 +44,7 @@ public class Button {
39 44
 		return button;
40 45
 	}
41 46
 
42
-	static ArrayList<Button> parseJsonArray(JSONArray jsonArray) {
47
+	public static ArrayList<Button> parseJsonArray(JSONArray jsonArray) {
43 48
 		ArrayList<Button> buttons = new ArrayList<>();
44 49
 
45 50
 		if (jsonArray == null) {

lib/android/app/src/main/java/com/reactnativenavigation/parse/Color.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Color.java View File

@@ -1,4 +1,4 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.params;
2 2
 
3 3
 import android.support.annotation.ColorInt;
4 4
 

+ 9
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/params/FloatParam.java View File

@@ -0,0 +1,9 @@
1
+package com.reactnativenavigation.parse.params;
2
+
3
+
4
+public class FloatParam extends Param<Float> {
5
+
6
+    public FloatParam(Float value) {
7
+        super(value);
8
+    }
9
+}

lib/android/app/src/main/java/com/reactnativenavigation/parse/Fraction.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Fraction.java View File

@@ -1,4 +1,4 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.params;
2 2
 
3 3
 public class Fraction extends Param<Float> {
4 4
     public Fraction(float value) {

+ 26
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Interpolation.java View File

@@ -0,0 +1,26 @@
1
+package com.reactnativenavigation.parse.params;
2
+
3
+
4
+import android.animation.TimeInterpolator;
5
+import android.view.animation.AccelerateDecelerateInterpolator;
6
+import android.view.animation.AccelerateInterpolator;
7
+import android.view.animation.DecelerateInterpolator;
8
+
9
+public enum Interpolation {
10
+    ACCELERATING,
11
+    DECELERATING,
12
+    DEFAULT,
13
+    NO_VALUE;
14
+
15
+    public TimeInterpolator getInterpolator() {
16
+        switch (this) {
17
+            case ACCELERATING:
18
+                return new AccelerateInterpolator();
19
+            case DECELERATING:
20
+                return new DecelerateInterpolator();
21
+            case DEFAULT:
22
+                return new AccelerateDecelerateInterpolator();
23
+        }
24
+        return null;
25
+    }
26
+}

+ 7
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullBool.java View File

@@ -0,0 +1,7 @@
1
+package com.reactnativenavigation.parse.params;
2
+
3
+public class NullBool extends Bool {
4
+    public NullBool() {
5
+        super(null);
6
+    }
7
+}

lib/android/app/src/main/java/com/reactnativenavigation/parse/NullColor.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullColor.java View File

@@ -1,8 +1,8 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.params;
2 2
 
3 3
 public class NullColor extends Color {
4 4
 
5
-    NullColor() {
5
+    public NullColor() {
6 6
         super(0);
7 7
     }
8 8
 

+ 14
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullFloatParam.java View File

@@ -0,0 +1,14 @@
1
+package com.reactnativenavigation.parse.params;
2
+
3
+
4
+public class NullFloatParam extends FloatParam {
5
+
6
+    public NullFloatParam() {
7
+        super(0f);
8
+    }
9
+
10
+    @Override
11
+    public boolean hasValue() {
12
+        return false;
13
+    }
14
+}

lib/android/app/src/main/java/com/reactnativenavigation/parse/NullFraction.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullFraction.java View File

@@ -1,7 +1,7 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.params;
2 2
 
3 3
 public class NullFraction extends Fraction {
4
-    NullFraction() {
4
+    public NullFraction() {
5 5
         super(0);
6 6
     }
7 7
 

lib/android/app/src/main/java/com/reactnativenavigation/parse/NullNumber.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullNumber.java View File

@@ -1,7 +1,7 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.params;
2 2
 
3 3
 public class NullNumber extends Number {
4
-    NullNumber() {
4
+    public NullNumber() {
5 5
         super(0);
6 6
     }
7 7
 

lib/android/app/src/main/java/com/reactnativenavigation/parse/NullText.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/params/NullText.java View File

@@ -1,4 +1,4 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.params;
2 2
 
3 3
 public class NullText extends Text {
4 4
     public NullText() {

lib/android/app/src/main/java/com/reactnativenavigation/parse/Number.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Number.java View File

@@ -1,4 +1,4 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.params;
2 2
 
3 3
 public class Number extends Param<Integer> {
4 4
 

lib/android/app/src/main/java/com/reactnativenavigation/parse/Param.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Param.java View File

@@ -1,4 +1,4 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.params;
2 2
 
3 3
 public abstract class Param<T> {
4 4
     protected T value;

+ 12
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Text.java View File

@@ -0,0 +1,12 @@
1
+package com.reactnativenavigation.parse.params;
2
+
3
+public class Text extends Param<String> {
4
+    public Text(String value) {
5
+        super(value);
6
+    }
7
+
8
+    @Override
9
+    public String toString() {
10
+        return hasValue() ? value : "No Value";
11
+    }
12
+}

+ 12
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/BoolParser.java View File

@@ -0,0 +1,12 @@
1
+package com.reactnativenavigation.parse.parsers;
2
+
3
+import com.reactnativenavigation.parse.params.Bool;
4
+import com.reactnativenavigation.parse.params.NullBool;
5
+
6
+import org.json.JSONObject;
7
+
8
+public class BoolParser {
9
+    public static Bool parse(JSONObject json, String bool) {
10
+        return json.has(bool) ? new Bool(json.optBoolean(bool)) : new NullBool();
11
+    }
12
+}

lib/android/app/src/main/java/com/reactnativenavigation/parse/ColorParser.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/ColorParser.java View File

@@ -1,4 +1,7 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.parsers;
2
+
3
+import com.reactnativenavigation.parse.params.Color;
4
+import com.reactnativenavigation.parse.params.NullColor;
2 5
 
3 6
 import org.json.JSONObject;
4 7
 

+ 14
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/FloatParser.java View File

@@ -0,0 +1,14 @@
1
+package com.reactnativenavigation.parse.parsers;
2
+
3
+import com.reactnativenavigation.parse.params.FloatParam;
4
+import com.reactnativenavigation.parse.params.NullFloatParam;
5
+import com.reactnativenavigation.parse.params.NullNumber;
6
+import com.reactnativenavigation.parse.params.Number;
7
+
8
+import org.json.JSONObject;
9
+
10
+public class FloatParser {
11
+    public static FloatParam parse(JSONObject json, String number) {
12
+        return json.has(number) ? new FloatParam((float) json.optDouble(number)) : new NullFloatParam();
13
+    }
14
+}

lib/android/app/src/main/java/com/reactnativenavigation/parse/FractionParser.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/FractionParser.java View File

@@ -1,4 +1,7 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.parsers;
2
+
3
+import com.reactnativenavigation.parse.params.Fraction;
4
+import com.reactnativenavigation.parse.params.NullFraction;
2 5
 
3 6
 import org.json.JSONObject;
4 7
 

+ 23
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/InterpolationParser.java View File

@@ -0,0 +1,23 @@
1
+package com.reactnativenavigation.parse.parsers;
2
+
3
+
4
+import com.reactnativenavigation.parse.params.Interpolation;
5
+
6
+import org.json.JSONObject;
7
+
8
+public class InterpolationParser {
9
+    public static Interpolation parse(JSONObject json, String intepolation) {
10
+        Interpolation result = Interpolation.DEFAULT;
11
+        if (json.has(intepolation)) {
12
+            switch (json.optString(intepolation)) {
13
+                case "decelerate":
14
+                    result = Interpolation.DECELERATING;
15
+                    break;
16
+                case "accelerate":
17
+                    result = Interpolation.ACCELERATING;
18
+                    break;
19
+            }
20
+        }
21
+        return result;
22
+    }
23
+}

lib/android/app/src/main/java/com/reactnativenavigation/parse/JSONParser.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/JSONParser.java View File

@@ -1,4 +1,4 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.parsers;
2 2
 
3 3
 import com.facebook.react.bridge.ReadableArray;
4 4
 import com.facebook.react.bridge.ReadableMap;

lib/android/app/src/main/java/com/reactnativenavigation/parse/LayoutNodeParser.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/LayoutNodeParser.java View File

@@ -1,7 +1,9 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.parsers;
2 2
 
3 3
 import android.support.annotation.NonNull;
4 4
 
5
+import com.reactnativenavigation.parse.LayoutNode;
6
+
5 7
 import org.json.JSONArray;
6 8
 import org.json.JSONObject;
7 9
 

lib/android/app/src/main/java/com/reactnativenavigation/parse/NumberParser.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/NumberParser.java View File

@@ -1,4 +1,7 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.parsers;
2
+
3
+import com.reactnativenavigation.parse.params.NullNumber;
4
+import com.reactnativenavigation.parse.params.Number;
2 5
 
3 6
 import org.json.JSONObject;
4 7
 

lib/android/app/src/main/java/com/reactnativenavigation/parse/TextParser.java → lib/android/app/src/main/java/com/reactnativenavigation/parse/parsers/TextParser.java View File

@@ -1,4 +1,7 @@
1
-package com.reactnativenavigation.parse;
1
+package com.reactnativenavigation.parse.parsers;
2
+
3
+import com.reactnativenavigation.parse.params.NullText;
4
+import com.reactnativenavigation.parse.params.Text;
2 5
 
3 6
 import org.json.JSONObject;
4 7
 

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

@@ -0,0 +1,59 @@
1
+package com.reactnativenavigation.presentation;
2
+
3
+import com.reactnativenavigation.parse.BottomTabOptions;
4
+import com.reactnativenavigation.parse.BottomTabsOptions;
5
+import com.reactnativenavigation.parse.Options;
6
+import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabFinder;
7
+import com.reactnativenavigation.views.BottomTabs;
8
+
9
+public class BottomTabsOptionsPresenter {
10
+    private BottomTabs bottomTabs;
11
+    private BottomTabFinder bottomTabFinder;
12
+
13
+    public BottomTabsOptionsPresenter(BottomTabs bottomTabs, BottomTabFinder bottomTabFinder) {
14
+        this.bottomTabs = bottomTabs;
15
+        this.bottomTabFinder = bottomTabFinder;
16
+    }
17
+
18
+    public void present(Options options) {
19
+        applyBottomTabsOptions(options.bottomTabsOptions);
20
+    }
21
+
22
+    public void present(Options options, int tabIndex) {
23
+        applyBottomTabOptions(options.bottomTabOptions, tabIndex);
24
+    }
25
+
26
+    private void applyBottomTabOptions(BottomTabOptions options, int tabIndex) {
27
+        if (options.badge.hasValue()) {
28
+            bottomTabs.setBadge(tabIndex, options.badge);
29
+        }
30
+    }
31
+
32
+    private void applyBottomTabsOptions(BottomTabsOptions options) {
33
+        if (options.backgroundColor.hasValue()) {
34
+            bottomTabs.setBackgroundColor(options.backgroundColor.get());
35
+        }
36
+        if (options.currentTabIndex.hasValue()) {
37
+            bottomTabs.setCurrentItem(options.currentTabIndex.get());
38
+        }
39
+        if (options.testId.hasValue()) {
40
+            bottomTabs.setTag(options.testId.get());
41
+        }
42
+        if (options.selectedTabColor.hasValue()) {
43
+            bottomTabs.setAccentColor(options.selectedTabColor.get());
44
+        }
45
+        if (options.tabColor.hasValue()) {
46
+            bottomTabs.setInactiveColor(options.tabColor.get());
47
+        }
48
+        if (options.currentTabId.hasValue()) {
49
+            int tabIndex = bottomTabFinder.findByControllerId(options.currentTabId.get());
50
+            if (tabIndex >= 0) bottomTabs.setCurrentItem(tabIndex);
51
+        }
52
+        if (options.visible.isTrueOrUndefined()) {
53
+            bottomTabs.restoreBottomNavigation(options.animate.isTrueOrUndefined());
54
+        }
55
+        if (options.visible.isFalse()) {
56
+            bottomTabs.hideBottomNavigation(options.animate.isTrueOrUndefined());
57
+        }
58
+    }
59
+}

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

@@ -0,0 +1,231 @@
1
+package com.reactnativenavigation.presentation;
2
+
3
+
4
+import android.support.annotation.NonNull;
5
+import android.view.Gravity;
6
+import android.view.View;
7
+import android.view.ViewGroup;
8
+import android.widget.FrameLayout;
9
+import android.widget.RelativeLayout;
10
+
11
+import com.reactnativenavigation.R;
12
+import com.reactnativenavigation.parse.FabOptions;
13
+import com.reactnativenavigation.parse.Options;
14
+import com.reactnativenavigation.views.Fab;
15
+import com.reactnativenavigation.views.FabMenu;
16
+import com.reactnativenavigation.views.ReactComponent;
17
+
18
+import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
19
+import static android.widget.RelativeLayout.ALIGN_PARENT_LEFT;
20
+import static android.widget.RelativeLayout.ALIGN_PARENT_RIGHT;
21
+import static android.widget.RelativeLayout.ALIGN_PARENT_TOP;
22
+import static com.github.clans.fab.FloatingActionButton.SIZE_MINI;
23
+import static com.github.clans.fab.FloatingActionButton.SIZE_NORMAL;
24
+
25
+public class FabOptionsPresenter {
26
+    private ViewGroup viewGroup;
27
+    private ReactComponent component;
28
+
29
+    private Fab fab;
30
+    private FabMenu fabMenu;
31
+
32
+    public void applyOptions(FabOptions options, @NonNull ReactComponent component, @NonNull ViewGroup viewGroup) {
33
+        this.viewGroup = viewGroup;
34
+        this.component = component;
35
+
36
+        if (options.id.hasValue()) {
37
+            if (fabMenu != null && fabMenu.getFabId().equals(options.id.get())) {
38
+                setParams(fabMenu, options);
39
+                fabMenu.bringToFront();
40
+                applyFabMenuOptions(fabMenu, options);
41
+            } else if (fab != null && fab.getFabId().equals(options.id.get())) {
42
+                setParams(fab, options);
43
+                fab.bringToFront();
44
+                applyFabOptions(fab, options);
45
+                fab.setOnClickListener(v -> component.sendOnNavigationButtonPressed(options.id.get()));
46
+            } else {
47
+                createFab(options);
48
+            }
49
+        } else {
50
+            removeFab();
51
+            removeFabMenu();
52
+        }
53
+    }
54
+
55
+    private void createFab(FabOptions options) {
56
+        if (options.actionsArray.size() > 0) {
57
+            fabMenu = new FabMenu(viewGroup.getContext(), options.id.get());
58
+            setParams(fabMenu, options);
59
+            applyFabMenuOptions(fabMenu, options);
60
+            viewGroup.addView(fabMenu);
61
+        } else {
62
+            fab = new Fab(viewGroup.getContext(), options.id.get());
63
+            setParams(fab, options);
64
+            applyFabOptions(fab, options);
65
+            fab.setOnClickListener(v -> component.sendOnNavigationButtonPressed(options.id.get()));
66
+            viewGroup.addView(fab);
67
+        }
68
+    }
69
+
70
+    private void removeFabMenu() {
71
+        if (fabMenu != null) {
72
+            fabMenu.hideMenuButton(true);
73
+            viewGroup.removeView(fabMenu);
74
+            fabMenu = null;
75
+        }
76
+    }
77
+
78
+    private void removeFab() {
79
+        if (fab != null) {
80
+            fab.hide(true);
81
+            viewGroup.removeView(fab);
82
+            fab = null;
83
+        }
84
+    }
85
+
86
+    private void setParams(View fab, FabOptions options) {
87
+        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
88
+        if (viewGroup instanceof RelativeLayout) {
89
+            RelativeLayout.LayoutParams layoutParamsRelative = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
90
+            layoutParamsRelative.bottomMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
91
+            layoutParamsRelative.rightMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
92
+            layoutParamsRelative.leftMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
93
+            layoutParamsRelative.topMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
94
+            if (options.alignHorizontally.hasValue()) {
95
+                if ("right".equals(options.alignHorizontally.get())) {
96
+                    layoutParamsRelative.removeRule(ALIGN_PARENT_LEFT);
97
+                    layoutParamsRelative.addRule(ALIGN_PARENT_RIGHT);
98
+                }
99
+                if ("left".equals(options.alignHorizontally.get())) {
100
+                    layoutParamsRelative.removeRule(ALIGN_PARENT_RIGHT);
101
+                    layoutParamsRelative.addRule(ALIGN_PARENT_LEFT);
102
+                }
103
+            } else {
104
+                layoutParamsRelative.addRule(ALIGN_PARENT_RIGHT);
105
+            }
106
+            if (options.alignVertically.hasValue()) {
107
+                if ("top".equals(options.alignVertically.get())) {
108
+                    layoutParamsRelative.removeRule(ALIGN_PARENT_BOTTOM);
109
+                    layoutParamsRelative.addRule(ALIGN_PARENT_TOP);
110
+                }
111
+                if ("bottom".equals(options.alignVertically.get())) {
112
+                    layoutParamsRelative.removeRule(ALIGN_PARENT_TOP);
113
+                    layoutParamsRelative.addRule(ALIGN_PARENT_BOTTOM);
114
+                }
115
+            } else {
116
+                layoutParamsRelative.addRule(ALIGN_PARENT_BOTTOM);
117
+            }
118
+            layoutParams = layoutParamsRelative;
119
+        }
120
+        if (viewGroup instanceof FrameLayout) {
121
+            FrameLayout.LayoutParams layoutParamsFrame = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
122
+            layoutParamsFrame.bottomMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
123
+            layoutParamsFrame.rightMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
124
+            layoutParamsFrame.leftMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
125
+            layoutParamsFrame.topMargin = (int) viewGroup.getContext().getResources().getDimension(R.dimen.margin);
126
+            if (options.alignHorizontally.hasValue()) {
127
+                if ("right".equals(options.alignHorizontally.get())) {
128
+                    removeGravityParam(layoutParamsFrame, Gravity.LEFT);
129
+                    setGravityParam(layoutParamsFrame, Gravity.RIGHT);
130
+                }
131
+                if ("left".equals(options.alignHorizontally.get())) {
132
+                    removeGravityParam(layoutParamsFrame, Gravity.RIGHT);
133
+                    setGravityParam(layoutParamsFrame, Gravity.LEFT);
134
+                }
135
+            } else {
136
+                setGravityParam(layoutParamsFrame, Gravity.RIGHT);
137
+            }
138
+            if (options.alignVertically.hasValue()) {
139
+                if ("top".equals(options.alignVertically.get())) {
140
+                    removeGravityParam(layoutParamsFrame, Gravity.BOTTOM);
141
+                    setGravityParam(layoutParamsFrame, Gravity.TOP);
142
+                }
143
+                if ("bottom".equals(options.alignVertically.get())) {
144
+                    removeGravityParam(layoutParamsFrame, Gravity.TOP);
145
+                    setGravityParam(layoutParamsFrame, Gravity.BOTTOM);
146
+                }
147
+            } else {
148
+                setGravityParam(layoutParamsFrame, Gravity.BOTTOM);
149
+            }
150
+            layoutParams = layoutParamsFrame;
151
+        }
152
+        fab.setLayoutParams(layoutParams);
153
+    }
154
+
155
+    private void applyFabOptions(Fab fab, FabOptions options) {
156
+        if (options.visible.isTrueOrUndefined()) {
157
+            fab.show(true);
158
+        }
159
+        if (options.visible.isFalse()) {
160
+            fab.hide(true);
161
+        }
162
+        if (options.backgroundColor.hasValue()) {
163
+            fab.setColorNormal(options.backgroundColor.get());
164
+        }
165
+        if (options.clickColor.hasValue()) {
166
+            fab.setColorPressed(options.clickColor.get());
167
+        }
168
+        if (options.rippleColor.hasValue()) {
169
+            fab.setColorRipple(options.rippleColor.get());
170
+        }
171
+        if (options.icon.hasValue()) {
172
+            fab.applyIcon(options.icon.get());
173
+        }
174
+        if (options.size.hasValue()) {
175
+            fab.setButtonSize("mini".equals(options.size.get()) ? SIZE_MINI : SIZE_NORMAL);
176
+        }
177
+        if (options.hideOnScroll.isTrue()) {
178
+            fab.enableCollapse(component.getScrollEventListener());
179
+        }
180
+        if (options.hideOnScroll.isFalseOrUndefined()) {
181
+            fab.disableCollapse();
182
+        }
183
+    }
184
+
185
+    private void applyFabMenuOptions(FabMenu fabMenu, FabOptions options) {
186
+        if (options.visible.isTrueOrUndefined()) {
187
+            fabMenu.showMenuButton(true);
188
+        }
189
+        if (options.visible.isFalse()) {
190
+            fabMenu.hideMenuButton(true);
191
+        }
192
+
193
+        if (options.backgroundColor.hasValue()) {
194
+            fabMenu.setMenuButtonColorNormal(options.backgroundColor.get());
195
+        }
196
+        if (options.clickColor.hasValue()) {
197
+            fabMenu.setMenuButtonColorPressed(options.clickColor.get());
198
+        }
199
+        if (options.rippleColor.hasValue()) {
200
+            fabMenu.setMenuButtonColorRipple(options.rippleColor.get());
201
+        }
202
+        for (Fab fabStored : fabMenu.getActions()) {
203
+            fabMenu.removeMenuButton(fabStored);
204
+        }
205
+        fabMenu.getActions().clear();
206
+        for (FabOptions fabOption : options.actionsArray) {
207
+            Fab fab = new Fab(viewGroup.getContext(), fabOption.id.get());
208
+            applyFabOptions(fab, fabOption);
209
+            fab.setOnClickListener(v -> component.sendOnNavigationButtonPressed(options.id.get()));
210
+
211
+            fabMenu.getActions().add(fab);
212
+            fabMenu.addMenuButton(fab);
213
+        }
214
+        if (options.hideOnScroll.isTrue()) {
215
+            fabMenu.enableCollapse(component.getScrollEventListener());
216
+        }
217
+        if (options.hideOnScroll.isFalseOrUndefined()) {
218
+            fabMenu.disableCollapse();
219
+        }
220
+    }
221
+
222
+    private void setGravityParam(FrameLayout.LayoutParams params, int gravityParam) {
223
+        params.gravity = params.gravity | gravityParam;
224
+    }
225
+
226
+    private void removeGravityParam(FrameLayout.LayoutParams params, int gravityParam) {
227
+        if ((params.gravity & gravityParam) == gravityParam) {
228
+            params.gravity = params.gravity & ~gravityParam;
229
+        }
230
+    }
231
+}

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

@@ -1,6 +1,6 @@
1 1
 package com.reactnativenavigation.presentation;
2 2
 
3
-import com.reactnativenavigation.parse.Button;
3
+import com.reactnativenavigation.parse.params.Button;
4 4
 import com.reactnativenavigation.parse.Options;
5 5
 import com.reactnativenavigation.parse.TopBarOptions;
6 6
 import com.reactnativenavigation.parse.TopTabOptions;
@@ -10,9 +10,6 @@ import com.reactnativenavigation.views.TopBar;
10 10
 
11 11
 import java.util.ArrayList;
12 12
 
13
-import static com.reactnativenavigation.parse.Options.BooleanOptions.False;
14
-import static com.reactnativenavigation.parse.Options.BooleanOptions.True;
15
-
16 13
 public class OptionsPresenter {
17 14
     private TopBar topBar;
18 15
     private ReactComponent component;
@@ -22,6 +19,10 @@ public class OptionsPresenter {
22 19
         this.component = component;
23 20
     }
24 21
 
22
+    public OptionsPresenter(TopBar topBar) {
23
+        this.topBar = topBar;
24
+    }
25
+
25 26
     public void applyOptions(Options options) {
26 27
         applyButtons(options.topBarOptions.leftButtons, options.topBarOptions.rightButtons);
27 28
         applyTopBarOptions(options.topBarOptions);
@@ -34,23 +35,24 @@ public class OptionsPresenter {
34 35
         topBar.setBackgroundColor(options.backgroundColor);
35 36
         topBar.setTitleTextColor(options.textColor);
36 37
         topBar.setTitleFontSize(options.textFontSize);
38
+        if (options.testId.hasValue()) topBar.setTestId(options.testId.get());
37 39
 
38 40
         topBar.setTitleTypeface(options.textFontFamily);
39
-        if (options.hidden == True) {
40
-            topBar.hide(options.animateHide);
41
+        if (options.visible.isFalse()) {
42
+            topBar.hide(options.animate);
41 43
         }
42
-        if (options.hidden == False) {
43
-            topBar.show(options.animateHide);
44
+        if (options.visible.isTrueOrUndefined()) {
45
+            topBar.show(options.animate);
44 46
         }
45
-        if (options.drawBehind == True) {
47
+        if (options.drawBehind.isTrue()) {
46 48
             component.drawBehindTopBar();
47
-        } else if (options.drawBehind == False) {
49
+        } else if (options.drawBehind.isFalseOrUndefined()) {
48 50
             component.drawBelowTopBar(topBar);
49 51
         }
50 52
 
51
-        if (options.hideOnScroll == True) {
53
+        if (options.hideOnScroll.isTrue()) {
52 54
             topBar.enableCollapse(component.getScrollEventListener());
53
-        } else if (options.hideOnScroll == False) {
55
+        } else if (options.hideOnScroll.isTrue()) {
54 56
             topBar.disableCollapse();
55 57
         }
56 58
     }
@@ -62,6 +64,7 @@ public class OptionsPresenter {
62 64
     private void applyTopTabsOptions(TopTabsOptions options) {
63 65
         topBar.applyTopTabsColors(options.selectedTabColor, options.unselectedTabColor);
64 66
         topBar.applyTopTabsFontSize(options.fontSize);
67
+        topBar.setTopTabsVisible(options.visible.isTrueOrUndefined());
65 68
     }
66 69
 
67 70
     private void applyTopTabOptions(TopTabOptions topTabOptions) {
@@ -69,4 +72,10 @@ public class OptionsPresenter {
69 72
             topBar.setTopTabFontFamily(topTabOptions.tabIndex, topTabOptions.fontFamily);
70 73
         }
71 74
     }
75
+
76
+    public void onChildWillDisappear(Options disappearing, Options appearing) {
77
+        if (disappearing.topBarOptions.visible.isTrueOrUndefined() && appearing.topBarOptions.visible.isFalse()) {
78
+            topBar.hide(disappearing.topBarOptions.animate);
79
+        }
80
+    }
72 81
 }

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

@@ -0,0 +1,24 @@
1
+package com.reactnativenavigation.presentation;
2
+
3
+import android.support.v4.widget.DrawerLayout;
4
+import android.view.Gravity;
5
+
6
+import com.reactnativenavigation.parse.SideMenuRootOptions;
7
+
8
+public class SideMenuOptionsPresenter {
9
+
10
+    private DrawerLayout sideMenu;
11
+
12
+    public SideMenuOptionsPresenter(DrawerLayout sideMenu) {
13
+        this.sideMenu = sideMenu;
14
+    }
15
+
16
+    public void present(SideMenuRootOptions options) {
17
+        if (options.left.visible.isTrue()) {
18
+            sideMenu.openDrawer(Gravity.LEFT);
19
+        }
20
+        if (options.right.visible.isTrue()) {
21
+            sideMenu.openDrawer(Gravity.RIGHT);
22
+        }
23
+    }
24
+}

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

@@ -1,14 +1,12 @@
1 1
 package com.reactnativenavigation.react;
2 2
 
3
+import android.support.annotation.NonNull;
4
+
3 5
 import com.facebook.react.bridge.Arguments;
4 6
 import com.facebook.react.bridge.ReactContext;
5 7
 import com.facebook.react.bridge.WritableMap;
6
-import com.facebook.react.bridge.WritableNativeArray;
7 8
 import com.facebook.react.modules.core.DeviceEventManagerModule;
8 9
 
9
-import org.json.JSONException;
10
-import org.json.JSONObject;
11
-
12 10
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
13 11
 
14 12
 public class NavigationEvent {
@@ -16,6 +14,7 @@ public class NavigationEvent {
16 14
 	private static final String componentDidAppear = "RNN.componentDidAppear";
17 15
 	private static final String componentDidDisappear = "RNN.componentDidDisappear";
18 16
 	private static final String onNavigationButtonPressed = "RNN.navigationButtonPressed";
17
+    private static final String componentLifecycle = "RNN.componentLifecycle";
19 18
 
20 19
 	private final RCTDeviceEventEmitter emitter;
21 20
 
@@ -27,15 +26,26 @@ public class NavigationEvent {
27 26
 		emit(onAppLaunched);
28 27
 	}
29 28
 
30
-	public void componentDidDisappear(String id) {
29
+	public void componentDidDisappear(String id, String componentName) {
31 30
 		emit(componentDidDisappear, id);
31
+		emit(componentLifecycle, getLifecycleEventData(id, componentName, "didDisappear"));
32 32
 	}
33 33
 
34
-	public void componentDidAppear(String id) {
34
+	public void componentDidAppear(String id, String componentName) {
35 35
 		emit(componentDidAppear, id);
36
+        emit(componentLifecycle, getLifecycleEventData(id, componentName, "didAppear"));
36 37
 	}
37 38
 
38
-	public void sendOnNavigationButtonPressed(String id, String buttonId) {
39
+    @NonNull
40
+    private WritableMap getLifecycleEventData(String id, String componentName, String didAppear) {
41
+        WritableMap map = Arguments.createMap();
42
+        map.putString("componentId", id);
43
+        map.putString("componentName", componentName);
44
+        map.putString("event", didAppear);
45
+        return map;
46
+    }
47
+
48
+    public void sendOnNavigationButtonPressed(String id, String buttonId) {
39 49
 		WritableMap map = Arguments.createMap();
40 50
 		map.putString("componentId", id);
41 51
 		map.putString("buttonId", buttonId);

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

@@ -9,10 +9,10 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
9 9
 import com.facebook.react.bridge.ReactMethod;
10 10
 import com.facebook.react.bridge.ReadableMap;
11 11
 import com.reactnativenavigation.NavigationActivity;
12
-import com.reactnativenavigation.parse.JSONParser;
12
+import com.reactnativenavigation.parse.parsers.JSONParser;
13 13
 import com.reactnativenavigation.parse.LayoutFactory;
14 14
 import com.reactnativenavigation.parse.LayoutNode;
15
-import com.reactnativenavigation.parse.LayoutNodeParser;
15
+import com.reactnativenavigation.parse.parsers.LayoutNodeParser;
16 16
 import com.reactnativenavigation.parse.Options;
17 17
 import com.reactnativenavigation.utils.TypefaceLoader;
18 18
 import com.reactnativenavigation.utils.UiThread;

+ 2
- 2
lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java View File

@@ -60,12 +60,12 @@ public class ReactView extends ReactRootView implements IReactView {
60 60
 
61 61
 	@Override
62 62
 	public void sendComponentStart() {
63
-		new NavigationEvent(reactInstanceManager.getCurrentReactContext()).componentDidAppear(componentId);
63
+		new NavigationEvent(reactInstanceManager.getCurrentReactContext()).componentDidAppear(componentId, componentName);
64 64
 	}
65 65
 
66 66
 	@Override
67 67
 	public void sendComponentStop() {
68
-		new NavigationEvent(reactInstanceManager.getCurrentReactContext()).componentDidDisappear(componentId);
68
+		new NavigationEvent(reactInstanceManager.getCurrentReactContext()).componentDidDisappear(componentId, componentName);
69 69
 	}
70 70
 
71 71
     @Override

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

@@ -0,0 +1,15 @@
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,7 +9,7 @@ public class CompatUtils {
9 9
 	private static final AtomicInteger viewId = new AtomicInteger(1);
10 10
 
11 11
 	public static int generateViewId() {
12
-		if (Build.VERSION.SDK_INT >= 17) {
12
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
13 13
 			return View.generateViewId();
14 14
 		} else {
15 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,7 +17,7 @@ import java.io.IOException;
17 17
 import java.io.InputStream;
18 18
 import java.net.URL;
19 19
 
20
-public class ImageUtils {
20
+public class ImageLoader {
21 21
 
22 22
 	public interface ImageLoadingListener {
23 23
 		void onComplete(@NonNull Drawable drawable);
@@ -25,7 +25,7 @@ public class ImageUtils {
25 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 29
         try {
30 30
             StrictMode.ThreadPolicy threadPolicy = adjustThreadPolicyDebug();
31 31
             
@@ -40,7 +40,7 @@ public class ImageUtils {
40 40
         }
41 41
     }
42 42
 
43
-    private static StrictMode.ThreadPolicy adjustThreadPolicyDebug() {
43
+    private StrictMode.ThreadPolicy adjustThreadPolicyDebug() {
44 44
         StrictMode.ThreadPolicy threadPolicy = null;
45 45
         if (NavigationApplication.instance.isDebug()) {
46 46
             threadPolicy = StrictMode.getThreadPolicy();
@@ -49,7 +49,7 @@ public class ImageUtils {
49 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 53
         if (NavigationApplication.instance.isDebug() && threadPolicy != null) {
54 54
             StrictMode.setThreadPolicy(threadPolicy);
55 55
         }

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

@@ -1,9 +1,11 @@
1 1
 package com.reactnativenavigation.utils;
2 2
 
3 3
 import android.content.Context;
4
+import android.content.res.Resources;
4 5
 import android.graphics.PorterDuff;
5 6
 import android.graphics.PorterDuffColorFilter;
6 7
 import android.graphics.drawable.Drawable;
8
+import android.os.Build;
7 9
 import android.os.Handler;
8 10
 import android.os.Looper;
9 11
 import android.util.DisplayMetrics;
@@ -11,7 +13,13 @@ import android.view.View;
11 13
 import android.view.ViewTreeObserver;
12 14
 import android.view.WindowManager;
13 15
 
16
+import com.reactnativenavigation.NavigationApplication;
17
+
14 18
 public class UiUtils {
19
+    public static final int STATUS_BAR_HEIGHT_M = 24;
20
+    public static final int STATUS_BAR_HEIGHT_L = 25;
21
+    private static int statusBarHeight = -1;
22
+
15 23
 	public static void runOnPreDrawOnce(final View view, final Runnable task) {
16 24
         view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
17 25
             @Override
@@ -39,4 +47,22 @@ public class UiUtils {
39 47
 		}
40 48
 		return metrics.heightPixels;
41 49
 	}
50
+
51
+    public static int getStatusBarHeight(Context context) {
52
+        if (statusBarHeight > 0) {
53
+            return statusBarHeight;
54
+        }
55
+        final Resources resources = context.getResources();
56
+        final int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
57
+        statusBarHeight = resourceId > 0 ?
58
+                resources.getDimensionPixelSize(resourceId) :
59
+                (int) dpToPx(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? STATUS_BAR_HEIGHT_M : STATUS_BAR_HEIGHT_L);
60
+        return statusBarHeight;
61
+    }
62
+
63
+    public static float dpToPx(float dp) {
64
+        float scale = NavigationApplication.instance.getResources().getDisplayMetrics().density;
65
+        return dp * scale + 0.5f;
66
+    }
67
+
42 68
 }

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

@@ -4,6 +4,9 @@ import android.support.annotation.Nullable;
4 4
 import android.view.View;
5 5
 import android.view.ViewGroup;
6 6
 
7
+import java.util.ArrayList;
8
+import java.util.List;
9
+
7 10
 public class ViewUtils {
8 11
     @Nullable
9 12
     public static <T> T findChildByClass(ViewGroup root, Class clazz) {
@@ -22,4 +25,29 @@ public class ViewUtils {
22 25
         }
23 26
         return null;
24 27
     }
28
+
29
+    public static <T> List<T> findChildrenByClassRecursive(ViewGroup root, Class clazz) {
30
+        ArrayList<T> ret = new ArrayList<>();
31
+        for (int i = 0; i < root.getChildCount(); i++) {
32
+            View view = root.getChildAt(i);
33
+            if (view instanceof ViewGroup) {
34
+                ret.addAll(findChildrenByClassRecursive((ViewGroup) view, clazz));
35
+            }
36
+            if (clazz.isAssignableFrom(view.getClass())) {
37
+                ret.add((T) view);
38
+            }
39
+        }
40
+        return ret;
41
+    }
42
+
43
+    public static <T> List<T> findChildrenByClass(ViewGroup root, Class clazz) {
44
+        List<T> ret = new ArrayList<>();
45
+        for (int i = 0; i < root.getChildCount(); i++) {
46
+            View view = root.getChildAt(i);
47
+            if (clazz.isAssignableFrom(view.getClass())) {
48
+                ret.add((T) view);
49
+            }
50
+        }
51
+        return ret;
52
+    }
25 53
 }

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

@@ -1,131 +0,0 @@
1
-package com.reactnativenavigation.viewcontrollers;
2
-
3
-import android.app.Activity;
4
-import android.graphics.Color;
5
-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;
11
-import android.widget.RelativeLayout;
12
-
13
-import com.reactnativenavigation.parse.Options;
14
-import com.reactnativenavigation.parse.Text;
15
-import com.reactnativenavigation.presentation.NavigationOptionsListener;
16
-import com.reactnativenavigation.utils.CompatUtils;
17
-
18
-import java.util.ArrayList;
19
-import java.util.Collection;
20
-import java.util.List;
21
-
22
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
23
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
24
-import static android.widget.RelativeLayout.ABOVE;
25
-import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
26
-import static com.reactnativenavigation.parse.DEFAULT_VALUES.NO_INT_VALUE;
27
-
28
-public class BottomTabsController extends ParentController
29
-		implements BottomNavigationView.OnNavigationItemSelectedListener, NavigationOptionsListener {
30
-	private BottomNavigationView bottomNavigationView;
31
-	private List<ViewController> tabs = new ArrayList<>();
32
-	private int selectedIndex = 0;
33
-
34
-	public BottomTabsController(final Activity activity, final String id) {
35
-		super(activity, id);
36
-	}
37
-
38
-	@NonNull
39
-	@Override
40
-	protected ViewGroup createView() {
41
-		RelativeLayout root = new RelativeLayout(getActivity());
42
-		bottomNavigationView = new BottomNavigationView(getActivity());
43
-		bottomNavigationView.setId(CompatUtils.generateViewId());
44
-		bottomNavigationView.setBackgroundColor(Color.DKGRAY);
45
-		bottomNavigationView.setOnNavigationItemSelectedListener(this);
46
-		RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
47
-		lp.addRule(ALIGN_PARENT_BOTTOM);
48
-		root.addView(bottomNavigationView, lp);
49
-		return root;
50
-	}
51
-
52
-	@Override
53
-	public boolean handleBack() {
54
-		return !tabs.isEmpty() && tabs.get(selectedIndex).handleBack();
55
-	}
56
-
57
-	@Override
58
-	public boolean onNavigationItemSelected(@NonNull final MenuItem item) {
59
-		selectTabAtIndex(item.getItemId());
60
-		return true;
61
-	}
62
-
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
-	}
68
-
69
-	public void setTabs(final List<ViewController> tabs) {
70
-		if (tabs.size() > 5) {
71
-			throw new RuntimeException("Too many tabs!");
72
-		}
73
-		this.tabs = tabs;
74
-		getView();
75
-		for (int i = 0; i < tabs.size(); i++) {
76
-			String title = String.valueOf(i);
77
-			createTab(tabs.get(i), i, title);
78
-		}
79
-		selectTabAtIndex(0);
80
-	}
81
-
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);
88
-	}
89
-
90
-	int getSelectedIndex() {
91
-		return selectedIndex;
92
-	}
93
-
94
-	@NonNull
95
-	@Override
96
-	public Collection<ViewController> getChildControllers() {
97
-		return tabs;
98
-	}
99
-
100
-	@Override
101
-	public void mergeOptions(Options options) {
102
-        if (options.bottomTabsOptions.currentTabIndex != NO_INT_VALUE) {
103
-            selectTabAtIndex(options.bottomTabsOptions.currentTabIndex);
104
-        }
105
-        if (options.bottomTabsOptions.currentTabId.hasValue()) {
106
-            Text id = options.bottomTabsOptions.currentTabId;
107
-            for (ViewController controller : tabs) {
108
-                if (controller.getId().equals(id.get())) {
109
-                    selectTabAtIndex(tabs.indexOf(controller));
110
-                }
111
-                if (controller instanceof StackController) {
112
-                    if (hasControlWithId((StackController) controller, id.get())) {
113
-                        selectTabAtIndex(tabs.indexOf(controller));
114
-                    }
115
-                }
116
-            }
117
-        }
118
-    }
119
-
120
-	private boolean hasControlWithId(StackController controller, String id) {
121
-		for (ViewController child : controller.getChildControllers()) {
122
-			if (id.equals(child.getId())) {
123
-				return true;
124
-			}
125
-			if (child instanceof StackController) {
126
-				return hasControlWithId((StackController) child, id);
127
-			}
128
-		}
129
-		return false;
130
-	}
131
-}

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

@@ -18,17 +18,15 @@ public class ComponentViewController extends ViewController<ComponentLayout> imp
18 18
                                    final String id,
19 19
                                    final String componentName,
20 20
                                    final ReactViewCreator viewCreator,
21
-                                   final Options initialNavigationOptions) {
22
-        super(activity, id);
21
+                                   final Options initialOptions) {
22
+        super(activity, id, initialOptions);
23 23
         this.componentName = componentName;
24 24
         this.viewCreator = viewCreator;
25
-        options = initialNavigationOptions;
26 25
     }
27 26
 
28 27
     @Override
29 28
     public void onViewAppeared() {
30 29
         super.onViewAppeared();
31
-        view.applyOptions(options);
32 30
         view.sendComponentStart();
33 31
     }
34 32
 
@@ -38,6 +36,16 @@ public class ComponentViewController extends ViewController<ComponentLayout> imp
38 36
         super.onViewDisappear();
39 37
     }
40 38
 
39
+    @Override
40
+    public void sendOnNavigationButtonPressed(String buttonId) {
41
+        getView().sendOnNavigationButtonPressed(buttonId);
42
+    }
43
+
44
+    @Override
45
+    public void applyOptions(Options options) {
46
+        view.applyOptions(options);
47
+    }
48
+
41 49
     @Override
42 50
     protected boolean isViewShown() {
43 51
         return super.isViewShown() && view.isReady();
@@ -52,15 +60,11 @@ public class ComponentViewController extends ViewController<ComponentLayout> imp
52 60
 
53 61
     @Override
54 62
     public void mergeOptions(Options options) {
55
-        this.options.mergeWith(options);
63
+        this.options = this.options.mergeWith(options);
56 64
         view.applyOptions(this.options);
57 65
         applyOnParentController(parentController -> parentController.applyOptions(this.options, view));
58 66
     }
59 67
 
60
-    Options getOptions() {
61
-        return options;
62
-    }
63
-
64 68
     ReactComponent getComponent() {
65 69
         return view;
66 70
     }

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

@@ -1,27 +1,26 @@
1 1
 package com.reactnativenavigation.viewcontrollers;
2 2
 
3
-import android.app.Dialog;
4
-import android.content.DialogInterface;
5 3
 import android.support.annotation.Nullable;
6
-import android.view.KeyEvent;
7
-import android.view.View;
8 4
 
9 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 9
 import java.util.ArrayList;
13 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 12
 public class ModalStack {
19 13
 
20 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 24
 		modal.show();
26 25
 		if (promise != null) {
27 26
 			promise.resolve(viewController.getId());
@@ -52,7 +51,7 @@ public class ModalStack {
52 51
 	}
53 52
 
54 53
 	@Nullable
55
-	private Modal findModalByComponentId(String componentId) {
54
+	public Modal findModalByComponentId(String componentId) {
56 55
 		for (Modal modal : modals) {
57 56
 			if (modal.containsDeepComponentId(componentId)) {
58 57
 				return modal;
@@ -64,49 +63,6 @@ public class ModalStack {
64 63
 	@Nullable
65 64
     ViewController findControllerById(String id) {
66 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
 }

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

@@ -12,6 +12,7 @@ import com.reactnativenavigation.presentation.NavigationOptionsListener;
12 12
 import com.reactnativenavigation.presentation.OverlayManager;
13 13
 import com.reactnativenavigation.utils.CompatUtils;
14 14
 import com.reactnativenavigation.utils.NoOpPromise;
15
+import com.reactnativenavigation.viewcontrollers.modal.ModalCreator;
15 16
 
16 17
 import java.util.Collection;
17 18
 import java.util.Collections;
@@ -19,47 +20,52 @@ import java.util.Collections;
19 20
 public class Navigator extends ParentController {
20 21
 
21 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 25
     private OverlayManager overlayManager = new OverlayManager();
25 26
     private Options defaultOptions = new Options();
26 27
 
27 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 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
+    @Override
56
+    public void sendOnNavigationButtonPressed(String buttonId) {
57
+
58
+    }
59
+
60
+    public void setRoot(final ViewController viewController, Promise promise) {
61
+        if (root != null) {
62
+            root.destroy();
63
+        }
64
+
65
+        root = viewController;
66
+        getView().addView(viewController.getView());
61 67
         promise.resolve(viewController.getId());
62
-	}
68
+    }
63 69
 
64 70
     public void setDefaultOptions(Options defaultOptions) {
65 71
         this.defaultOptions = defaultOptions;
@@ -69,78 +75,78 @@ public class Navigator extends ParentController {
69 75
         return defaultOptions;
70 76
     }
71 77
 
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) {
134
-        overlayManager.show(root.getView(), overlay);
135
-	}
136
-
137
-	public void dismissOverlay(final String componentId) {
138
-		overlayManager.dismiss(root.getView(), componentId);
139
-	}
140
-
141
-	static void rejectPromise(Promise promise) {
78
+    public void setOptions(final String componentId, Options options) {
79
+        ViewController target = findControllerById(componentId);
80
+        if (target instanceof NavigationOptionsListener) {
81
+            ((NavigationOptionsListener) target).mergeOptions(options);
82
+        }
83
+        if (root instanceof NavigationOptionsListener) {
84
+            ((NavigationOptionsListener) root).mergeOptions(options);
85
+        }
86
+    }
87
+
88
+    public void push(final String fromId, final ViewController viewController, Promise promise) {
89
+        ViewController from = findControllerById(fromId);
90
+        if (from != null) {
91
+            from.performOnParentStack(stack -> ((StackController) stack).animatePush(viewController, promise));
92
+        }
93
+    }
94
+
95
+    void pop(final String fromId, Promise promise) {
96
+        ViewController from = findControllerById(fromId);
97
+        if (from != null) {
98
+            from.performOnParentStack(stack -> ((StackController) stack).pop(promise));
99
+        }
100
+    }
101
+
102
+    public void popSpecific(final String id, Promise promise) {
103
+        ViewController from = findControllerById(id);
104
+        if (from != null) {
105
+            from.performOnParentStack(stack -> ((StackController) stack).popSpecific(from, promise), () -> rejectPromise(promise));
106
+        } else {
107
+            rejectPromise(promise);
108
+        }
109
+    }
110
+
111
+    public void popToRoot(final String id, Promise promise) {
112
+        ViewController from = findControllerById(id);
113
+        if (from != null) {
114
+            from.performOnParentStack(stack -> ((StackController) stack).popToRoot(promise));
115
+        }
116
+    }
117
+
118
+    public void popTo(final String componentId, Promise promise) {
119
+        ViewController target = findControllerById(componentId);
120
+        if (target != null) {
121
+            target.performOnParentStack(stack -> ((StackController) stack).popTo(target, promise), () -> rejectPromise(promise));
122
+        } else {
123
+            rejectPromise(promise);
124
+        }
125
+    }
126
+
127
+    public void showModal(final ViewController viewController, Promise promise) {
128
+        modalStack.showModal(viewController, promise);
129
+    }
130
+
131
+    public void dismissModal(final String componentId, Promise promise) {
132
+        modalStack.dismissModal(componentId, promise);
133
+    }
134
+
135
+    public void dismissAllModals(Promise promise) {
136
+        modalStack.dismissAll(promise);
137
+    }
138
+
139
+    public void showOverlay(ViewController overlay) {
140
+        overlayManager.show(getView(), overlay);
141
+    }
142
+
143
+    public void dismissOverlay(final String componentId) {
144
+        overlayManager.dismiss(getView(), componentId);
145
+    }
146
+
147
+    static void rejectPromise(Promise promise) {
142 148
         promise.reject(new Throwable("Nothing to pop"));
143
-	}
149
+    }
144 150
 
145 151
     @Nullable
146 152
     @Override

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

@@ -1,6 +1,7 @@
1 1
 package com.reactnativenavigation.viewcontrollers;
2 2
 
3 3
 import android.app.Activity;
4
+import android.support.annotation.CallSuper;
4 5
 import android.support.annotation.NonNull;
5 6
 import android.support.annotation.Nullable;
6 7
 import android.support.v4.view.ViewPager;
@@ -13,8 +14,8 @@ import java.util.Collection;
13 14
 
14 15
 public abstract class ParentController<T extends ViewGroup> extends ViewController {
15 16
 
16
-	public ParentController(final Activity activity, final String id) {
17
-		super(activity, id);
17
+	public ParentController(final Activity activity, final String id, Options initialOptions) {
18
+		super(activity, id, initialOptions);
18 19
 	}
19 20
 
20 21
 	@NonNull
@@ -44,8 +45,24 @@ public abstract class ParentController<T extends ViewGroup> extends ViewControll
44 45
 		return null;
45 46
 	}
46 47
 
48
+	@Override
49
+    public boolean containsComponent(ReactComponent component) {
50
+        if (super.containsComponent(component)) {
51
+            return true;
52
+        }
53
+        for (ViewController child : getChildControllers()) {
54
+            if (child.containsComponent(component)) return true;
55
+        }
56
+        return false;
57
+    }
58
+
59
+    @CallSuper
47 60
     public void applyOptions(Options options, ReactComponent childComponent) {
61
+        mergeChildOptions(options);
62
+    }
48 63
 
64
+    private void mergeChildOptions(Options options) {
65
+        this.options = this.options.mergeWith(options);
49 66
     }
50 67
 
51 68
 	@Override
@@ -56,11 +73,17 @@ public abstract class ParentController<T extends ViewGroup> extends ViewControll
56 73
 		}
57 74
 	}
58 75
 
76
+	@CallSuper
59 77
     void clearOptions() {
60
-
78
+	    applyOnParentController(parent -> ((ParentController) parent).clearOptions());
79
+        options = initialOptions.copy();
61 80
     }
62 81
 
63 82
     public void setupTopTabsWithViewPager(ViewPager viewPager) {
64 83
 
65 84
     }
85
+
86
+    public void clearTopTabs() {
87
+
88
+    }
66 89
 }

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

@@ -5,7 +5,11 @@ import android.support.annotation.NonNull;
5 5
 import android.support.v4.widget.DrawerLayout;
6 6
 import android.view.Gravity;
7 7
 import android.view.View;
8
-import android.view.ViewGroup;
8
+
9
+import com.reactnativenavigation.parse.Options;
10
+import com.reactnativenavigation.presentation.NavigationOptionsListener;
11
+import com.reactnativenavigation.presentation.SideMenuOptionsPresenter;
12
+import com.reactnativenavigation.views.ReactComponent;
9 13
 
10 14
 import java.util.ArrayList;
11 15
 import java.util.Collection;
@@ -13,23 +17,28 @@ import java.util.Collection;
13 17
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
14 18
 import static android.widget.ListPopupWindow.WRAP_CONTENT;
15 19
 
16
-public class SideMenuController extends ParentController {
20
+public class SideMenuController extends ParentController<DrawerLayout> implements NavigationOptionsListener {
17 21
 
18 22
 	private ViewController centerController;
19 23
 	private ViewController leftController;
20 24
 	private ViewController rightController;
21 25
 
22
-	public SideMenuController(final Activity activity, final String id) {
23
-		super(activity, id);
26
+	public SideMenuController(final Activity activity, final String id, Options initialOptions) {
27
+		super(activity, id, initialOptions);
24 28
 	}
25 29
 
26 30
 	@NonNull
27 31
 	@Override
28
-	protected ViewGroup createView() {
32
+	protected DrawerLayout createView() {
29 33
         return new DrawerLayout(getActivity());
30 34
 	}
31 35
 
32
-	@NonNull
36
+    @Override
37
+    public void sendOnNavigationButtonPressed(String buttonId) {
38
+        centerController.sendOnNavigationButtonPressed(buttonId);
39
+    }
40
+
41
+    @NonNull
33 42
 	@Override
34 43
 	public Collection<ViewController> getChildControllers() {
35 44
 		ArrayList<ViewController> children = new ArrayList<>();
@@ -39,7 +48,22 @@ public class SideMenuController extends ParentController {
39 48
 		return children;
40 49
 	}
41 50
 
42
-	public void setCenterController(ViewController centerController) {
51
+    @Override
52
+    public void applyOptions(Options options, ReactComponent childComponent) {
53
+        super.applyOptions(options, childComponent);
54
+        applyOnParentController(parentController ->
55
+                ((ParentController) parentController).applyOptions(this.options, childComponent)
56
+        );
57
+    }
58
+
59
+    @Override
60
+    public void mergeOptions(Options options) {
61
+        this.options = this.options.mergeWith(options);
62
+        new SideMenuOptionsPresenter(getView()).present(this.options.sideMenuRootOptions);
63
+        this.options = this.options.copy().clearSideMenuOptions();
64
+    }
65
+
66
+    public void setCenterController(ViewController centerController) {
43 67
 		this.centerController = centerController;
44 68
 		View childView = centerController.getView();
45 69
 		getView().addView(childView);

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

@@ -17,31 +17,42 @@ import com.reactnativenavigation.views.TopBar;
17 17
 import java.util.Collection;
18 18
 import java.util.Iterator;
19 19
 
20
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
21
+
20 22
 public class StackController extends ParentController <StackLayout> {
21 23
 
22 24
     private static final NoOpPromise NO_OP = new NoOpPromise();
23 25
     private final IdStack<ViewController> stack = new IdStack<>();
24 26
     private final NavigationAnimator animator;
25
-    private StackLayout stackLayout;
26 27
 
27
-    public StackController(final Activity activity, String id) {
28
-		super(activity, id);
28
+    public StackController(final Activity activity, String id, Options initialOptions) {
29
+        super(activity, id, initialOptions);
29 30
         animator = new NavigationAnimator(activity);
30 31
     }
31 32
 
32 33
     @RestrictTo(RestrictTo.Scope.TESTS)
33 34
     TopBar getTopBar() {
34
-        return stackLayout.getTopBar();
35
+        return getView().getTopBar();
35 36
     }
36 37
 
38
+    @RestrictTo(RestrictTo.Scope.TESTS)
39
+    StackLayout getStackLayout() {return getView();}
40
+
37 41
     @Override
38 42
     public void applyOptions(Options options, ReactComponent component) {
39
-        stackLayout.applyOptions(options, component);
43
+        super.applyOptions(options, component);
44
+        getView().applyOptions(this.options, component);
45
+        applyOnParentController(parentController ->
46
+                ((ParentController) parentController).applyOptions(this.options.copy().clearTopBarOptions(), component)
47
+        );
48
+        fabOptionsPresenter.applyOptions(options.fabOptions, component, getView());
49
+        animator.setOptions(options.animationsOptions);
40 50
     }
41 51
 
42 52
     @Override
43 53
     void clearOptions() {
44
-        stackLayout.clearOptions();
54
+        super.clearOptions();
55
+        getView().clearOptions();
45 56
     }
46 57
 
47 58
     public void push(ViewController child, final Promise promise) {
@@ -50,7 +61,7 @@ public class StackController extends ParentController <StackLayout> {
50 61
         child.setParentController(this);
51 62
         stack.push(child.getId(), child);
52 63
         View enteringView = child.getView();
53
-        getView().addView(enteringView);
64
+        getView().addView(enteringView, MATCH_PARENT, MATCH_PARENT);
54 65
 
55 66
         if (toRemove != null) {
56 67
             getView().removeView(toRemove.getView());
@@ -59,22 +70,22 @@ public class StackController extends ParentController <StackLayout> {
59 70
     }
60 71
 
61 72
     public void animatePush(final ViewController child, final Promise promise) {
62
-		final ViewController toRemove = stack.peek();
73
+        final ViewController toRemove = stack.peek();
63 74
 
64 75
 		child.setParentController(this);
65 76
 		stack.push(child.getId(), child);
66 77
 		View enteringView = child.getView();
67
-		getView().addView(enteringView);
78
+		getView().addView(enteringView, MATCH_PARENT, MATCH_PARENT);
68 79
 
69
-		if (toRemove != null) {
80
+        if (toRemove != null) {
70 81
             animator.animatePush(enteringView, () -> {
71 82
                 getView().removeView(toRemove.getView());
72 83
                 promise.resolve(child.getId());
73 84
             });
74
-		} else {
75
-			promise.resolve(child.getId());
76
-		}
77
-	}
85
+        } else {
86
+            promise.resolve(child.getId());
87
+        }
88
+    }
78 89
 
79 90
     void pop(final Promise promise) {
80 91
         if (!canPop()) {
@@ -82,32 +93,33 @@ public class StackController extends ParentController <StackLayout> {
82 93
             return;
83 94
         }
84 95
 
85
-        final ViewController poppedTop = stack.pop();
86
-        ViewController newTop = stack.peek();
96
+        final ViewController exitingController = stack.pop();
97
+        final ViewController enteringController = stack.peek();
98
+        popInternal(exitingController, enteringController);
87 99
 
88
-        View enteringView = newTop.getView();
89
-        final View exitingView = poppedTop.getView();
90
-        getView().addView(enteringView, getView().getChildCount() - 1);
91
-
92
-        finishPopping(exitingView, poppedTop, promise);
100
+        finishPopping(exitingController.getView(), exitingController, promise);
93 101
     }
94 102
 
95
-	private void animatePop(final Promise promise) {
103
+	void animatePop(final Promise promise) {
96 104
 		if (!canPop()) {
97 105
 			Navigator.rejectPromise(promise);
98 106
 			return;
99 107
 		}
100 108
 
101
-		final ViewController poppedTop = stack.pop();
102
-		ViewController newTop = stack.peek();
103
-
104
-		View enteringView = newTop.getView();
105
-		final View exitingView = poppedTop.getView();
106
-		getView().addView(enteringView, getView().getChildCount() - 1);
109
+		final ViewController exitingController = stack.pop();
110
+        final ViewController enteringController = stack.peek();
111
+        popInternal(exitingController, enteringController);
107 112
 
108
-        animator.animatePop(exitingView, () -> finishPopping(exitingView, poppedTop, promise));
113
+        animator.animatePop(exitingController.getView(), () -> finishPopping(exitingController.getView(), exitingController, promise));
109 114
 	}
110 115
 
116
+    private void popInternal(ViewController disappearing, ViewController appearing) {
117
+        disappearing.onViewWillDisappear();
118
+        appearing.onViewWillAppear();
119
+        getView().onChildWillDisappear(disappearing.options, appearing.options);
120
+        getView().addView(appearing.getView(), getView().indexOfChild(disappearing.getView()));
121
+    }
122
+
111 123
     boolean canPop() {
112 124
         return stack.size() > 1;
113 125
     }
@@ -180,11 +192,15 @@ public class StackController extends ParentController <StackLayout> {
180 192
         return false;
181 193
 	}
182 194
 
195
+    @Override
196
+    public void sendOnNavigationButtonPressed(String buttonId) {
197
+        peek().sendOnNavigationButtonPressed(buttonId);
198
+    }
199
+
183 200
     @NonNull
184 201
     @Override
185 202
     protected StackLayout createView() {
186
-        stackLayout = new StackLayout(getActivity());
187
-        return stackLayout;
203
+        return new StackLayout(getActivity(), this::sendOnNavigationButtonPressed);
188 204
     }
189 205
 
190 206
 	@NonNull
@@ -195,6 +211,11 @@ public class StackController extends ParentController <StackLayout> {
195 211
 
196 212
     @Override
197 213
     public void setupTopTabsWithViewPager(ViewPager viewPager) {
198
-        stackLayout.setupTopTabsWithViewPager(viewPager);
214
+        getView().initTopTabs(viewPager);
215
+    }
216
+
217
+    @Override
218
+    public void clearTopTabs() {
219
+        getView().clearTopTabs();
199 220
     }
200 221
 }

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

@@ -1,6 +1,7 @@
1 1
 package com.reactnativenavigation.viewcontrollers;
2 2
 
3 3
 import android.app.Activity;
4
+import android.support.annotation.CallSuper;
4 5
 import android.support.annotation.NonNull;
5 6
 import android.support.annotation.Nullable;
6 7
 import android.support.annotation.VisibleForTesting;
@@ -10,6 +11,7 @@ import android.view.ViewManager;
10 11
 import android.view.ViewTreeObserver;
11 12
 
12 13
 import com.reactnativenavigation.parse.Options;
14
+import com.reactnativenavigation.presentation.FabOptionsPresenter;
13 15
 import com.reactnativenavigation.utils.CompatUtils;
14 16
 import com.reactnativenavigation.utils.StringUtils;
15 17
 import com.reactnativenavigation.utils.Task;
@@ -29,6 +31,7 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
29 31
         boolean onViewDisappear(View view);
30 32
     }
31 33
 
34
+    Options initialOptions;
32 35
     public Options options;
33 36
 
34 37
     private final Activity activity;
@@ -38,10 +41,14 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
38 41
     private boolean isShown;
39 42
     private boolean isDestroyed;
40 43
     private ViewVisibilityListener viewVisibilityListener = new ViewVisibilityListenerAdapter();
44
+    FabOptionsPresenter fabOptionsPresenter;
41 45
 
42
-    public ViewController(Activity activity, String id) {
46
+    public ViewController(Activity activity, String id, Options initialOptions) {
43 47
         this.activity = activity;
44 48
         this.id = id;
49
+        fabOptionsPresenter = new FabOptionsPresenter();
50
+        this.initialOptions = initialOptions;
51
+        options = initialOptions.copy();
45 52
     }
46 53
 
47 54
     protected abstract T createView();
@@ -125,18 +132,32 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
125 132
         return isSameId(id) ? this : null;
126 133
     }
127 134
 
135
+    public boolean containsComponent(ReactComponent component) {
136
+        return getView().equals(component);
137
+    }
138
+
139
+    public void onViewWillAppear() {
140
+
141
+    }
142
+
128 143
     public void onViewAppeared() {
129 144
         isShown = true;
145
+        applyOptions(options);
130 146
         applyOnParentController(parentController -> {
131 147
             parentController.clearOptions();
132
-            parentController.applyOptions(options, (ReactComponent) getView());
148
+            if (getView() instanceof ReactComponent) parentController.applyOptions(options, (ReactComponent) getView());
133 149
         });
134 150
     }
135 151
 
152
+    public void onViewWillDisappear() {
153
+
154
+    }
155
+
136 156
     public void onViewDisappear() {
137 157
         isShown = false;
138 158
     }
139 159
 
160
+    @CallSuper
140 161
     public void destroy() {
141 162
         if (isShown) {
142 163
             isShown = false;
@@ -170,6 +191,8 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
170 191
         }
171 192
     }
172 193
 
194
+    public abstract void sendOnNavigationButtonPressed(String buttonId);
195
+
173 196
     protected boolean isViewShown() {
174 197
         return !isDestroyed && getView().isShown();
175 198
     }

+ 36
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabFinder.java View File

@@ -0,0 +1,36 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs;
2
+
3
+import android.support.annotation.IntRange;
4
+
5
+import com.reactnativenavigation.viewcontrollers.ViewController;
6
+import com.reactnativenavigation.views.ReactComponent;
7
+
8
+import java.util.List;
9
+
10
+public class BottomTabFinder {
11
+    private List<ViewController> tabs;
12
+
13
+    @IntRange(from = -1)
14
+    public int findByComponent(ReactComponent component) {
15
+        for (int i = 0; i < tabs.size(); i++) {
16
+            if (tabs.get(i).containsComponent(component)) {
17
+                return i;
18
+            }
19
+        }
20
+        return -1;
21
+    }
22
+
23
+    @IntRange(from = -1)
24
+    public int findByControllerId(String id) {
25
+        for (int i = 0; i < tabs.size(); i++) {
26
+            if (tabs.get(i).findControllerById(id) != null) {
27
+                return i;
28
+            }
29
+        }
30
+        return -1;
31
+    }
32
+
33
+    void setTabs(List<ViewController> tabs) {
34
+        this.tabs = tabs;
35
+    }
36
+}

+ 152
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java View File

@@ -0,0 +1,152 @@
1
+package com.reactnativenavigation.viewcontrollers.bottomtabs;
2
+
3
+import android.app.Activity;
4
+import android.graphics.drawable.Drawable;
5
+import android.support.annotation.NonNull;
6
+import android.view.ViewGroup;
7
+import android.widget.RelativeLayout;
8
+
9
+import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
10
+import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem;
11
+import com.reactnativenavigation.parse.BottomTabOptions;
12
+import com.reactnativenavigation.parse.Options;
13
+import com.reactnativenavigation.presentation.BottomTabsOptionsPresenter;
14
+import com.reactnativenavigation.presentation.NavigationOptionsListener;
15
+import com.reactnativenavigation.utils.ImageLoader;
16
+import com.reactnativenavigation.viewcontrollers.ParentController;
17
+import com.reactnativenavigation.viewcontrollers.ViewController;
18
+import com.reactnativenavigation.views.BottomTabs;
19
+import com.reactnativenavigation.views.ReactComponent;
20
+
21
+import java.util.ArrayList;
22
+import java.util.Collection;
23
+import java.util.List;
24
+
25
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
26
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
27
+import static android.widget.RelativeLayout.ABOVE;
28
+import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
29
+
30
+public class BottomTabsController extends ParentController implements AHBottomNavigation.OnTabSelectedListener, NavigationOptionsListener {
31
+	private BottomTabs bottomTabs;
32
+	private List<ViewController> tabs = new ArrayList<>();
33
+    private ImageLoader imageLoader;
34
+    private BottomTabFinder bottomTabFinder = new BottomTabFinder();
35
+
36
+    public BottomTabsController(final Activity activity, ImageLoader imageLoader, final String id, Options initialOptions) {
37
+		super(activity, id, initialOptions);
38
+        this.imageLoader = imageLoader;
39
+    }
40
+
41
+	@NonNull
42
+	@Override
43
+	protected ViewGroup createView() {
44
+		RelativeLayout root = new RelativeLayout(getActivity());
45
+		bottomTabs = new BottomTabs(getActivity());
46
+        bottomTabs.setOnTabSelectedListener(this);
47
+		RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
48
+		lp.addRule(ALIGN_PARENT_BOTTOM);
49
+		root.addView(bottomTabs, lp);
50
+		return root;
51
+	}
52
+
53
+    @Override
54
+    public void applyOptions(Options options) {
55
+        super.applyOptions(options);
56
+        new BottomTabsOptionsPresenter(bottomTabs, bottomTabFinder).present(options);
57
+    }
58
+
59
+    @Override
60
+    public void applyOptions(Options options, ReactComponent childComponent) {
61
+        super.applyOptions(options, childComponent);
62
+        int tabIndex = bottomTabFinder.findByComponent(childComponent);
63
+        if (tabIndex >= 0) new BottomTabsOptionsPresenter(bottomTabs, bottomTabFinder).present(this.options, tabIndex);
64
+        applyOnParentController(parentController ->
65
+                ((ParentController) parentController).applyOptions(this.options.copy().clearBottomTabsOptions().clearBottomTabOptions(), childComponent)
66
+        );
67
+    }
68
+
69
+    @Override
70
+	public boolean handleBack() {
71
+		return !tabs.isEmpty() && tabs.get(bottomTabs.getCurrentItem()).handleBack();
72
+	}
73
+
74
+    @Override
75
+    public void sendOnNavigationButtonPressed(String buttonId) {
76
+        getCurrentTab().sendOnNavigationButtonPressed(buttonId);
77
+    }
78
+
79
+    private ViewController getCurrentTab() {
80
+        return tabs.get(bottomTabs.getCurrentItem());
81
+    }
82
+
83
+    @Override
84
+    public boolean onTabSelected(int index, boolean wasSelected) {
85
+        if (wasSelected) return false;
86
+        selectTabAtIndex(index);
87
+        return true;
88
+	}
89
+	
90
+	public void setTabs(final List<ViewController> tabs) {
91
+		if (tabs.size() > 5) {
92
+			throw new RuntimeException("Too many tabs!");
93
+		}
94
+		this.tabs = tabs;
95
+        bottomTabFinder.setTabs(tabs);
96
+        getView();
97
+		for (int i = 0; i < tabs.size(); i++) {
98
+		    tabs.get(i).setParentController(this);
99
+			createTab(i, tabs.get(i).options.bottomTabOptions);
100
+		}
101
+		selectTabAtIndex(0);
102
+	}
103
+
104
+	private void createTab(int index, final BottomTabOptions tabOptions) {
105
+	    if (!tabOptions.icon.hasValue()) {
106
+            throw new RuntimeException("BottomTab must have an icon");
107
+        }
108
+        imageLoader.loadIcon(getActivity(), tabOptions.icon.get(), new ImageLoader.ImageLoadingListener() {
109
+            @Override
110
+            public void onComplete(@NonNull Drawable drawable) {
111
+                AHBottomNavigationItem item = new AHBottomNavigationItem(tabOptions.title.get(""), drawable);
112
+                bottomTabs.addItem(item);
113
+                bottomTabs.post(() -> bottomTabs.setTabTag(index, tabOptions.testId));
114
+            }
115
+
116
+            @Override
117
+            public void onError(Throwable error) {
118
+                error.printStackTrace();
119
+            }
120
+        });
121
+
122
+        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
123
+        params.addRule(ABOVE, bottomTabs.getId());
124
+	}
125
+
126
+    public int getSelectedIndex() {
127
+		return bottomTabs.getCurrentItem();
128
+	}
129
+
130
+	@NonNull
131
+	@Override
132
+	public Collection<ViewController> getChildControllers() {
133
+		return tabs;
134
+	}
135
+
136
+	@Override
137
+	public void mergeOptions(Options options) {
138
+        this.options = this.options.mergeWith(options);
139
+        new BottomTabsOptionsPresenter(bottomTabs, bottomTabFinder).present(this.options);
140
+    }
141
+
142
+    public void selectTabAtIndex(final int newIndex) {
143
+        getView().removeView(getCurrentView());
144
+        bottomTabs.setCurrentItem(newIndex, false);
145
+        getView().addView(getCurrentView());
146
+    }
147
+
148
+    @NonNull
149
+    private ViewGroup getCurrentView() {
150
+        return tabs.get(bottomTabs.getCurrentItem()).getView();
151
+    }
152
+}

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

@@ -0,0 +1,55 @@
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

@@ -0,0 +1,9 @@
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
+}

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

@@ -21,12 +21,10 @@ public class TopTabsController extends ParentController<TopTabsViewPager> implem
21 21
 
22 22
     private List<ViewController> tabs;
23 23
     private TopTabsLayoutCreator viewCreator;
24
-    private Options options;
25 24
 
26 25
     public TopTabsController(Activity activity, String id, List<ViewController> tabs, TopTabsLayoutCreator viewCreator, Options options) {
27
-        super(activity, id);
26
+        super(activity, id, options);
28 27
         this.viewCreator = viewCreator;
29
-        this.options = options;
30 28
         this.tabs = tabs;
31 29
         for (ViewController tab : tabs) {
32 30
             tab.setParentController(this);
@@ -59,9 +57,20 @@ public class TopTabsController extends ParentController<TopTabsViewPager> implem
59 57
         performOnCurrentTab(ViewController::onViewAppeared);
60 58
     }
61 59
 
60
+    @Override
61
+    public void onViewWillDisappear() {
62
+        super.onViewWillDisappear();
63
+    }
64
+
62 65
     @Override
63 66
     public void onViewDisappear() {
64 67
         performOnCurrentTab(ViewController::onViewDisappear);
68
+        applyOnParentController(parentController -> ((ParentController) parentController).clearTopTabs());
69
+    }
70
+
71
+    @Override
72
+    public void sendOnNavigationButtonPressed(String buttonId) {
73
+        performOnCurrentTab(tab -> tab.sendOnNavigationButtonPressed(buttonId));
65 74
     }
66 75
 
67 76
     @Override
@@ -71,7 +80,12 @@ public class TopTabsController extends ParentController<TopTabsViewPager> implem
71 80
 
72 81
     @Override
73 82
     public void applyOptions(Options options, ReactComponent childComponent) {
74
-        applyOnParentController(parentController -> ((ParentController) parentController).applyOptions(options, childComponent));
83
+        super.applyOptions(options, childComponent);
84
+        applyOnParentController(parentController -> {
85
+                Options opt = this.options.copy();
86
+                ((ParentController) parentController).applyOptions(opt.clearTopTabOptions().clearTopTabsOptions(), childComponent);
87
+            }
88
+        );
75 89
     }
76 90
 
77 91
     @Override

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

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

+ 13
- 8
lib/android/app/src/main/java/com/reactnativenavigation/views/ComponentLayout.java View File

@@ -25,6 +25,7 @@ public class ComponentLayout extends FrameLayout implements ReactComponent, Titl
25 25
 		super(context);
26 26
 		this.reactView = reactView;
27 27
         addView(reactView.asView(), MATCH_PARENT, MATCH_PARENT);
28
+        setContentDescription("ComponentLayout");
28 29
         touchDelegate = new OverlayTouchDelegate(reactView);
29 30
     }
30 31
 
@@ -55,7 +56,7 @@ public class ComponentLayout extends FrameLayout implements ReactComponent, Titl
55 56
 
56 57
     @Override
57 58
     public void applyOptions(Options options) {
58
-        touchDelegate.setInterceptTouchOutside(options.overlayOptions.interceptTouchOutside == Options.BooleanOptions.True);
59
+        touchDelegate.setInterceptTouchOutside(options.overlayOptions.interceptTouchOutside.isTrue());
59 60
     }
60 61
 
61 62
     @Override
@@ -75,16 +76,20 @@ public class ComponentLayout extends FrameLayout implements ReactComponent, Titl
75 76
 
76 77
     @Override
77 78
     public void drawBehindTopBar() {
78
-        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
79
-        layoutParams.removeRule(BELOW);
80
-        reactView.asView().setLayoutParams(layoutParams);
79
+        if (getParent() instanceof RelativeLayout) {
80
+            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
81
+            layoutParams.removeRule(BELOW);
82
+            setLayoutParams(layoutParams);
83
+        }
81 84
     }
82 85
 
83 86
     @Override
84 87
     public void drawBelowTopBar(TopBar topBar) {
85
-        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
86
-        layoutParams.addRule(BELOW, topBar.getId());
87
-        reactView.asView().setLayoutParams(layoutParams);
88
+        if (getParent() instanceof RelativeLayout) {
89
+            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
90
+            layoutParams.addRule(BELOW, topBar.getId());
91
+            setLayoutParams(layoutParams);
92
+        }
88 93
     }
89 94
 
90 95
     @Override
@@ -94,6 +99,6 @@ public class ComponentLayout extends FrameLayout implements ReactComponent, Titl
94 99
 
95 100
     @Override
96 101
     public boolean onInterceptTouchEvent(MotionEvent ev) {
97
-        return touchDelegate.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
102
+        return touchDelegate.onInterceptTouchEvent(ev);
98 103
     }
99 104
 }

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

@@ -0,0 +1,75 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.content.Context;
4
+import android.graphics.drawable.Drawable;
5
+import android.support.annotation.NonNull;
6
+
7
+import com.github.clans.fab.FloatingActionButton;
8
+import com.reactnativenavigation.anim.FabAnimator;
9
+import com.reactnativenavigation.anim.FabCollapseBehaviour;
10
+import com.reactnativenavigation.interfaces.ScrollEventListener;
11
+import com.reactnativenavigation.utils.ImageLoader;
12
+
13
+
14
+public class Fab extends FloatingActionButton implements FabAnimator {
15
+
16
+    private String id = "";
17
+    private FabCollapseBehaviour collapseBehaviour;
18
+
19
+    public Fab(Context context, String id) {
20
+        super(context);
21
+        collapseBehaviour = new FabCollapseBehaviour(this);
22
+        this.id = id;
23
+    }
24
+
25
+    public void applyIcon(String icon) {
26
+        new ImageLoader().loadIcon(getContext(), icon, new ImageLoader.ImageLoadingListener() {
27
+            @Override
28
+            public void onComplete(@NonNull Drawable drawable) {
29
+                setImageDrawable(drawable);
30
+            }
31
+
32
+            @Override
33
+            public void onError(Throwable error) {
34
+                error.printStackTrace();
35
+            }
36
+        });
37
+    }
38
+
39
+    @Override
40
+    public boolean equals(Object o) {
41
+        if (this == o) return true;
42
+        if (o == null || getClass() != o.getClass()) return false;
43
+
44
+        Fab fab = (Fab) o;
45
+
46
+        return id.equals(fab.id);
47
+    }
48
+
49
+    @Override
50
+    public int hashCode() {
51
+        return id.hashCode();
52
+    }
53
+
54
+    @Override
55
+    public void show() {
56
+        show(true);
57
+    }
58
+
59
+    @Override
60
+    public void hide() {
61
+        hide(true);
62
+    }
63
+
64
+    public void enableCollapse(@NonNull ScrollEventListener scrollEventListener) {
65
+        collapseBehaviour.enableCollapse(scrollEventListener);
66
+    }
67
+
68
+    public void disableCollapse() {
69
+        collapseBehaviour.disableCollapse();
70
+    }
71
+
72
+    public String getFabId() {
73
+        return id;
74
+    }
75
+}

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

@@ -0,0 +1,53 @@
1
+package com.reactnativenavigation.views;
2
+
3
+import android.content.Context;
4
+
5
+import com.github.clans.fab.FloatingActionMenu;
6
+import com.reactnativenavigation.anim.FabAnimator;
7
+import com.reactnativenavigation.anim.FabCollapseBehaviour;
8
+import com.reactnativenavigation.interfaces.ScrollEventListener;
9
+
10
+import java.util.HashSet;
11
+
12
+
13
+public class FabMenu extends FloatingActionMenu implements FabAnimator {
14
+
15
+    private String id = "";
16
+    private HashSet<Fab> actions = new HashSet<>();
17
+
18
+    private FabCollapseBehaviour collapseBehaviour;
19
+
20
+    public FabMenu(Context context, String id) {
21
+        super(context);
22
+        this.id = id;
23
+        collapseBehaviour = new FabCollapseBehaviour(this);
24
+        onFinishInflate();
25
+        setOnMenuButtonClickListener(v -> toggle(true));
26
+    }
27
+
28
+    @Override
29
+    public void show() {
30
+        showMenu(true);
31
+    }
32
+
33
+    @Override
34
+    public void hide() {
35
+        hideMenu(true);
36
+    }
37
+
38
+    public void enableCollapse(ScrollEventListener scrollEventListener) {
39
+        collapseBehaviour.enableCollapse(scrollEventListener);
40
+    }
41
+
42
+    public void disableCollapse() {
43
+        collapseBehaviour.disableCollapse();
44
+    }
45
+
46
+    public HashSet<Fab> getActions() {
47
+        return actions;
48
+    }
49
+
50
+    public String getFabId() {
51
+        return id;
52
+    }
53
+}

+ 25
- 15
lib/android/app/src/main/java/com/reactnativenavigation/views/StackLayout.java View File

@@ -1,5 +1,6 @@
1 1
 package com.reactnativenavigation.views;
2 2
 
3
+import android.annotation.SuppressLint;
3 4
 import android.content.Context;
4 5
 import android.support.annotation.RestrictTo;
5 6
 import android.support.v4.view.ViewPager;
@@ -12,40 +13,49 @@ import com.reactnativenavigation.utils.CompatUtils;
12 13
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
13 14
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
14 15
 
15
-public class StackLayout extends RelativeLayout implements TitleBarButton.OnClickListener {
16
+@SuppressLint("ViewConstructor")
17
+public class StackLayout extends RelativeLayout {
18
+    private TopBar topBar;
16 19
 
17
-    private final TopBar topBar;
18
-
19
-    public StackLayout(Context context) {
20
+    public StackLayout(Context context, TitleBarButton.OnClickListener topBarButtonClickListener) {
20 21
         super(context);
21
-        topBar = new TopBar(context, this);
22
+        topBar = new TopBar(context, topBarButtonClickListener, this);
22 23
         topBar.setId(CompatUtils.generateViewId());
23 24
         createLayout();
25
+        setContentDescription("StackLayout");
24 26
     }
25 27
 
26 28
     void createLayout() {
27 29
         addView(topBar, MATCH_PARENT, WRAP_CONTENT);
28 30
     }
29 31
 
30
-    @Override
31
-    public void onPress(String buttonId) {
32
-
33
-    }
34
-
35 32
     public void applyOptions(Options options, ReactComponent component) {
36 33
         new OptionsPresenter(topBar, component).applyOptions(options);
37 34
     }
38 35
 
39
-    @RestrictTo(RestrictTo.Scope.TESTS)
40
-    public TopBar getTopBar() {
41
-        return topBar;
36
+    public void onChildWillDisappear(Options disappearing, Options appearing) {
37
+        new OptionsPresenter(topBar).onChildWillDisappear(disappearing, appearing);
42 38
     }
43 39
 
44 40
     public void clearOptions() {
45 41
         topBar.clear();
46 42
     }
47 43
 
48
-    public void setupTopTabsWithViewPager(ViewPager viewPager) {
49
-        topBar.setupTopTabsWithViewPager(viewPager);
44
+    public void initTopTabs(ViewPager viewPager) {
45
+        topBar.initTopTabs(viewPager);
46
+    }
47
+
48
+    public void clearTopTabs() {
49
+        topBar.clearTopTabs();
50
+    }
51
+
52
+    @RestrictTo(RestrictTo.Scope.TESTS)
53
+    public TopBar getTopBar() {
54
+        return topBar;
55
+    }
56
+
57
+    @RestrictTo(RestrictTo.Scope.TESTS)
58
+    public void setTopBar(TopBar topBar) {
59
+        this.topBar = topBar;
50 60
     }
51 61
 }

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

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

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

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
4 4
 import android.content.Context;
5 5
 import android.graphics.Typeface;
6 6
 import android.support.annotation.Nullable;
7
+import android.support.annotation.VisibleForTesting;
7 8
 import android.support.design.widget.AppBarLayout;
8 9
 import android.support.v4.view.ViewPager;
9 10
 import android.support.v7.widget.Toolbar;
@@ -16,23 +17,27 @@ import android.widget.TextView;
16 17
 import com.reactnativenavigation.anim.TopBarAnimator;
17 18
 import com.reactnativenavigation.anim.TopBarCollapseBehavior;
18 19
 import com.reactnativenavigation.interfaces.ScrollEventListener;
19
-import com.reactnativenavigation.parse.Button;
20
-import com.reactnativenavigation.parse.Color;
21
-import com.reactnativenavigation.parse.Fraction;
22
-import com.reactnativenavigation.parse.Number;
23
-import com.reactnativenavigation.parse.Options;
20
+import com.reactnativenavigation.parse.params.Button;
21
+import com.reactnativenavigation.parse.params.Color;
22
+import com.reactnativenavigation.parse.params.Fraction;
23
+import com.reactnativenavigation.parse.params.Number;
24
+import com.reactnativenavigation.parse.params.Bool;
24 25
 
25 26
 import java.util.ArrayList;
26 27
 
28
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
29
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
30
+
27 31
 @SuppressLint("ViewConstructor")
28 32
 public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAwareView {
29 33
     private final Toolbar titleBar;
30 34
     private TitleBarButton.OnClickListener onClickListener;
31 35
     private final TopBarCollapseBehavior collapsingBehavior;
32
-    private final TopBarAnimator animator;
36
+    private TopBarAnimator animator;
33 37
     private TopTabs topTabs;
38
+    private StackLayout parentView;
34 39
 
35
-    public TopBar(final Context context, TitleBarButton.OnClickListener onClickListener) {
40
+    public TopBar(final Context context, TitleBarButton.OnClickListener onClickListener, StackLayout parentView) {
36 41
         super(context);
37 42
         this.onClickListener = onClickListener;
38 43
         collapsingBehavior = new TopBarCollapseBehavior(this);
@@ -40,6 +45,7 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
40 45
         titleBar.getMenu();
41 46
         topTabs = new TopTabs(getContext());
42 47
         this.animator = new TopBarAnimator(this);
48
+        this.parentView = parentView;
43 49
         addView(titleBar);
44 50
     }
45 51
 
@@ -51,6 +57,10 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
51 57
         return titleBar.getTitle() != null ? titleBar.getTitle().toString() : "";
52 58
     }
53 59
 
60
+    public void setTestId(String testId) {
61
+        setTag(testId);
62
+    }
63
+
54 64
     public void setTitleTextColor(Color color) {
55 65
         if (color.hasValue()) titleBar.setTitleTextColor(color.get());
56 66
     }
@@ -81,6 +91,10 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
81 91
         topTabs.applyTopTabsFontSize(fontSize);
82 92
     }
83 93
 
94
+    public void setTopTabsVisible(boolean visible) {
95
+        topTabs.setVisibility(this, visible);
96
+    }
97
+
84 98
     public void setButtons(ArrayList<Button> leftButtons, ArrayList<Button> rightButtons) {
85 99
         setLeftButtons(leftButtons);
86 100
         setRightButtons(rightButtons);
@@ -147,13 +161,9 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
147 161
         return titleBar;
148 162
     }
149 163
 
150
-    public void setupTopTabsWithViewPager(ViewPager viewPager) {
151
-        initTopTabs();
152
-        topTabs.setupWithViewPager(viewPager);
153
-    }
154
-
155
-    private void initTopTabs() {
164
+    public void initTopTabs(ViewPager viewPager) {
156 165
         topTabs = new TopTabs(getContext());
166
+        topTabs.init(viewPager);
157 167
         addView(topTabs);
158 168
     }
159 169
 
@@ -165,32 +175,55 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
165 175
         collapsingBehavior.disableCollapse();
166 176
     }
167 177
 
168
-    public void show(Options.BooleanOptions animated) {
178
+    public void show(Bool animated) {
169 179
         if (getVisibility() == View.VISIBLE) {
170 180
             return;
171 181
         }
172
-        if (animated == Options.BooleanOptions.True) {
182
+        if (animated.isTrueOrUndefined()) {
173 183
             animator.show();
174
-        } else {
184
+        } else if (!animator.isRunning()) {
175 185
             setVisibility(View.VISIBLE);
176 186
         }
177 187
     }
178 188
 
179
-    public void hide(Options.BooleanOptions animated) {
189
+    public void hide(Bool animated) {
180 190
         if (getVisibility() == View.GONE) {
181 191
             return;
182 192
         }
183
-        if (animated == Options.BooleanOptions.True) {
193
+        if (animated.isTrueOrUndefined()) {
184 194
             animator.hide();
185
-        } else {
195
+        } else if (!animator.isRunning()){
186 196
             setVisibility(View.GONE);
187 197
         }
188 198
     }
189 199
 
200
+    @Override
201
+    public void setVisibility(int visibility) {
202
+        super.setVisibility(visibility);
203
+        if (visibility == View.GONE) {
204
+            this.parentView.removeView(this);
205
+        } else if (visibility == View.VISIBLE && this.getParent() == null) {
206
+            this.parentView.addView(this, MATCH_PARENT, WRAP_CONTENT);
207
+        }
208
+    }
209
+
190 210
     public void clear() {
191 211
         titleBar.setTitle(null);
192 212
         titleBar.setNavigationIcon(null);
193 213
         titleBar.getMenu().clear();
194
-        removeView(topTabs);
214
+    }
215
+
216
+    public void clearTopTabs() {
217
+        topTabs.clear(this);
218
+    }
219
+
220
+    @VisibleForTesting()
221
+    public TopTabs getTopTabs() {
222
+        return topTabs;
223
+    }
224
+
225
+    @VisibleForTesting
226
+    public void setAnimator(TopBarAnimator animator) {
227
+        this.animator = animator;
195 228
     }
196 229
 }

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


Some files were not shown because too many files changed in this diff