react-native-screens 1.0.0-alpha.2 → 1.0.0-alpha.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -1
- package/RNScreens.podspec +24 -0
- package/android/build.gradle +17 -10
- package/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.java +4 -8
- package/android/src/main/java/com/swmansion/rnscreens/{RNScreenPackage.java → RNScreensPackage.java} +1 -2
- package/android/src/main/java/com/swmansion/rnscreens/Screen.java +44 -3
- package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.java +61 -18
- package/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.java +1 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.java +3 -3
- package/ios/RNSScreen.h +2 -0
- package/ios/RNSScreen.m +51 -7
- package/ios/RNSScreenContainer.h +0 -8
- package/ios/RNSScreenContainer.m +93 -24
- package/ios/RNScreens.xcodeproj/project.pbxproj +170 -7
- package/package.json +11 -6
- package/src/screens.d.ts +22 -0
- package/src/screens.native.js +120 -0
- package/src/screens.web.js +113 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.java +0 -35
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.java +0 -24
- package/ios/RNSScreenStack.h +0 -14
- package/ios/RNSScreenStack.m +0 -245
- package/ios/RNScreens.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/RNScreens.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/RNScreens.xcodeproj/project.xcworkspace/xcuserdata/mdk.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/RNScreens.xcodeproj/xcuserdata/mdk.xcuserdatad/xcschemes/xcschememanagement.plist +0 -24
- package/src/screens.js +0 -14
package/README.md
CHANGED
|
@@ -1,3 +1,165 @@
|
|
|
1
1
|
# react-native-screens
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This project aims to expose native navigation container components to React Native. It is not designed to be used as a standalone library but rather as a dependency of a [full-featured navigation library](https://github.com/react-navigation/react-navigation).
|
|
4
|
+
|
|
5
|
+
## How can I take advantage of that?
|
|
6
|
+
|
|
7
|
+
Screens are already integrated with the React Native's most popular navigation library [react-navigation](https://github.com/react-navigation/react-navigation) and [Expo](https://expo.io).
|
|
8
|
+
Read usage guide depending on if you are [using Expo](#usage-in-expo-with-react-navigation) or [not](#usage-with-react-navigation-without-expo).
|
|
9
|
+
|
|
10
|
+
## Usage with [react-navigation](https://github.com/react-navigation/react-navigation) (without Expo)
|
|
11
|
+
|
|
12
|
+
Screens support is built into [react-navigation](https://github.com/react-navigation/react-navigation) starting from version [2.14.0](https://github.com/react-navigation/react-navigation/releases/tag/2.14.0) for all the different navigator types (stack, tab, drawer, etc). We plan on adding it to other navigators in near future.
|
|
13
|
+
|
|
14
|
+
To configure react-navigation to use screens instead of plain RN Views for rendering screen views, follow the steps below:
|
|
15
|
+
|
|
16
|
+
1. Add this library as a depedency to your project:
|
|
17
|
+
```
|
|
18
|
+
yarn add react-native-screens
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Link native modules this library ships with into your app:
|
|
22
|
+
```
|
|
23
|
+
react-native link react-native-screens
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
> If you are not familiar with the concept of linking libraries [read on here](https://facebook.github.io/react-native/docs/linking-libraries-ios).
|
|
27
|
+
|
|
28
|
+
3. Enable screens support before any of your navigation screen renders. Add the following code to your main application file (e.g. App.js):
|
|
29
|
+
```js
|
|
30
|
+
import { useScreens } from 'react-native-screens';
|
|
31
|
+
|
|
32
|
+
useScreens();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Note that the above code need to execute before first render of a navigation screen. You can check Example's app [App.js](https://github.com/kmagiera/react-native-screens/blob/master/Example/App.js#L16) file as a reference.
|
|
36
|
+
|
|
37
|
+
4. On Android change your main activity class to extend [`ReactFragmentActivity`](https://github.com/facebook/react-native/blob/0.57-stable/ReactAndroid/src/main/java/com/facebook/react/ReactFragmentActivity.java). The file you'll have to change is likely called `MainActivity.java` unless you customized it after creating your project:
|
|
38
|
+
```diff
|
|
39
|
+
-import com.facebook.react.ReactActivity;
|
|
40
|
+
+import android.os.Bundle;
|
|
41
|
+
+import com.facebook.react.ReactFragmentActivity;
|
|
42
|
+
import com.facebook.react.ReactActivityDelegate;
|
|
43
|
+
|
|
44
|
+
-public class MainActivity extends ReactActivity {
|
|
45
|
+
+public class MainActivity extends ReactFragmentActivity {
|
|
46
|
+
+
|
|
47
|
+
+ @Override
|
|
48
|
+
+ protected void onCreate(Bundle savedInstanceState) {
|
|
49
|
+
+ super.onCreate(null);
|
|
50
|
+
+ }
|
|
51
|
+
|
|
52
|
+
@Override
|
|
53
|
+
protected String getMainComponentName() {
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
5. Make sure that the version of [react-navigation](https://github.com/react-navigation/react-navigation) you are using is 2.14.0 or higher
|
|
57
|
+
|
|
58
|
+
5. You are all set 🎉 – when screens are enabled in your application code react-navigation will automatically use them instead of relying on plain React Native Views.
|
|
59
|
+
|
|
60
|
+
## Usage in Expo with [react-navigation](https://github.com/react-navigation/react-navigation)
|
|
61
|
+
|
|
62
|
+
Screens support is built into Expo [SDK 30](https://blog.expo.io/expo-sdk-30-0-0-is-now-available-e64d8b1db2a7) and react-navigation starting from [2.14.0](https://github.com/react-navigation/react-navigation/releases/tag/2.14.0). Make sure your app use these versions before you start.
|
|
63
|
+
|
|
64
|
+
1. Add screens library as dependency to your project – you can skip this step when using snack as the dependency will be imported when you import it in one of the JS files
|
|
65
|
+
```
|
|
66
|
+
yarn add react-native-screens
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. Open your App.js file and add the following snippet somewhere near the top of the file (e.g. right after import statements):
|
|
70
|
+
```
|
|
71
|
+
import { useScreens } from 'react-native-screens';
|
|
72
|
+
|
|
73
|
+
useScreens();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
3. That's all 🎉 – enjoy faster navigation in your Expo app. Keep in mind screens are in pretty early phase so please report if you discover some issues.
|
|
77
|
+
|
|
78
|
+
## Interop with [react-native-navigation](https://github.com/wix/react-native-navigation)
|
|
79
|
+
|
|
80
|
+
React-native-navigation library already uses native containers for rendering navigation scenes so wrapping these scenes with `<ScreenContainer>` or `<Screen>` component does not provide any benefits. Yet if you would like to build a component that uses screens primitives under the hood (for example a view pager component) it is safe to use `<ScreenContainer>` and `<Screen>` components for that as these work out of the box when rendered on react-native-navigation scenes.
|
|
81
|
+
|
|
82
|
+
## Interop with other libraries
|
|
83
|
+
|
|
84
|
+
This library should work out of the box with all existing react-native libraries. If you expirience problems with interoperability please [report an issue](https://github.com/kmagiera/react-native-screens/issues).
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## Guide for navigation library authors
|
|
88
|
+
|
|
89
|
+
If you are building navigation library you may want to use react-native-screens to have a control which parts of the react component tree are attached to the native view hierarchy.
|
|
90
|
+
To do that react-native-screens provides you with two components documented below:
|
|
91
|
+
|
|
92
|
+
### `<ScreenContainer/>`
|
|
93
|
+
|
|
94
|
+
This component is a container for one or more `Screen` components.
|
|
95
|
+
It does not accept other component types are direct children.
|
|
96
|
+
The role of container is to control which of its children screens should be attached to the view hierarchy.
|
|
97
|
+
It does that by monitoring `active` property of each of its children.
|
|
98
|
+
It it possible to have as many `active` children as you'd like but in order for the component to be the most effictent we should keep the number of active screens to the minimum.
|
|
99
|
+
In a case of stack navigator or tabs navigator we only want to have one active screen (the top most view on a stack or the selected tab).
|
|
100
|
+
Then for the time of transitioning between views we may want to activate a second screen for the duration of transition, and then go back to just one active screen.
|
|
101
|
+
|
|
102
|
+
### `<Screen/>`
|
|
103
|
+
|
|
104
|
+
This component is a container for views we want to display on a navigation screen.
|
|
105
|
+
It is designed to only be rendered as a direct child of `ScreenContainer`.
|
|
106
|
+
In addition to plain React Native [View props](http://facebook.github.io/react-native/docs/view#props) this component only accepts a single additional property called `active`.
|
|
107
|
+
When `active` is set to `0`, the parent container will detach its views from the native view hierarchy.
|
|
108
|
+
Otherwise the views will be attached as long as the parent container is attached too.
|
|
109
|
+
|
|
110
|
+
### Example
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
<ScreenContainer>
|
|
114
|
+
<Screen>{tab1}</Screen>
|
|
115
|
+
<Screen active={1}>{tab2}</Screen>
|
|
116
|
+
<Screen>{tab3}</Screen>
|
|
117
|
+
</ScreenContainer>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Guide for native component authors
|
|
121
|
+
|
|
122
|
+
If you are adding a new native component to be used from the React Native app, you may want it to respond to navigation lifecycle events.
|
|
123
|
+
|
|
124
|
+
Good example is a map component that shows current user location. When component is on the top-most screen it should register for location updates and display users location on the map. But if we navigate away from a screen that has a map, e.g. by pushing new screen on top of it or if it is in one of a tabs and user just switched to the previous app, we may want to stop listening to location updates.
|
|
125
|
+
|
|
126
|
+
In order to achieve that we need to know at the native component level when our native view goes out of sight. With react-native-screens you can do that in the following way:
|
|
127
|
+
|
|
128
|
+
### Navigation lifecycle on iOS
|
|
129
|
+
|
|
130
|
+
In order for your native view on iOS to be notified when its parent navigation container goes into background override `didMoveToWindow` method:
|
|
131
|
+
|
|
132
|
+
```objective-c
|
|
133
|
+
- (void)didMoveToWindow
|
|
134
|
+
{
|
|
135
|
+
[super didMoveToWindow];
|
|
136
|
+
BOOL isVisible = self.superview && self.window;
|
|
137
|
+
if (isVisible) {
|
|
138
|
+
// navigation container this view belongs to became visible
|
|
139
|
+
} else {
|
|
140
|
+
// we are in a background
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
You can check our example app for a fully functional demo see [RNSSampleLifecycleAwareView.m](https://github.com/kmagiera/react-native-screens/blob/master/Example/ios/ScreensExample/RNSSampleLifecycleAwareView.m) for more details.
|
|
146
|
+
|
|
147
|
+
### Navigation lifecycle on Android
|
|
148
|
+
|
|
149
|
+
On Android you can use [LifecycleObserver](https://developer.android.com/reference/android/arch/lifecycle/LifecycleObserver) interface which is a part of Android compat library to make your view handle lifecycle events.
|
|
150
|
+
Check [LifecycleAwareView.java](https://github.com/kmagiera/react-native-screens/blob/master/Example/android/app/src/main/java/com/swmansion/rnscreens/example/LifecycleAwareView.java) from our example app for more details on that.
|
|
151
|
+
|
|
152
|
+
In addition to that you will need to register for receiving these updates. This can be done using [`LifecycleHelper.register`](https://github.com/kmagiera/react-native-screens/blob/master/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.java#L50).
|
|
153
|
+
Remember to call [`LifecycleHelper.unregister`](https://github.com/kmagiera/react-native-screens/blob/master/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.java#L59) before the view is dropped.
|
|
154
|
+
Please refer to [SampleLifecycleAwareViewManager.java](https://github.com/kmagiera/react-native-screens/blob/master/Example/android/app/src/main/java/com/swmansion/rnscreens/example/SampleLifecycleAwareViewManager.java) from our example app to see what are the best ways of using the above methods.
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
React native screens library is licensed under [The MIT License](LICENSE).
|
|
159
|
+
|
|
160
|
+
## Credits
|
|
161
|
+
|
|
162
|
+
This project is supported by amazing people from [Expo.io](https://expo.io) and [Software Mansion](https://swmansion.com)
|
|
163
|
+
|
|
164
|
+
[](https://expo.io)
|
|
165
|
+
[](https://swmansion.com)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "RNScreens"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.description = <<-DESC
|
|
10
|
+
RNScreens - first incomplete navigation solution for your React Native app
|
|
11
|
+
DESC
|
|
12
|
+
s.homepage = "https://github.com/kmagiera/react-native-screens"
|
|
13
|
+
s.license = "MIT"
|
|
14
|
+
# s.license = { :type => "MIT", :file => "FILE_LICENSE" }
|
|
15
|
+
s.author = { "author" => "author@domain.cn" }
|
|
16
|
+
s.platform = :ios, "7.0"
|
|
17
|
+
s.source = { :git => "https://github.com/kmagiera/react-native-screens.git", :tag => "#{s.version}" }
|
|
18
|
+
|
|
19
|
+
s.source_files = "ios/**/*.{h,m}"
|
|
20
|
+
s.requires_arc = true
|
|
21
|
+
|
|
22
|
+
s.dependency "React"
|
|
23
|
+
end
|
|
24
|
+
|
package/android/build.gradle
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
|
|
2
2
|
buildscript {
|
|
3
3
|
repositories {
|
|
4
|
+
google()
|
|
4
5
|
jcenter()
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
dependencies {
|
|
8
|
-
|
|
9
|
-
// https://github.com/facebook/react-native/blob/1e8f3b11027fe0a7514b4fc97d0798d3c64bc895/local-cli/templates/HelloWorld/android/build.gradle#L8
|
|
10
|
-
classpath 'com.android.tools.build:gradle:2.2.3'
|
|
9
|
+
classpath 'com.android.tools.build:gradle:3.3.1'
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
apply plugin: 'com.android.library'
|
|
15
14
|
apply plugin: 'maven'
|
|
16
15
|
|
|
16
|
+
def safeExtGet(prop, fallback) {
|
|
17
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
18
|
+
}
|
|
19
|
+
|
|
17
20
|
android {
|
|
18
|
-
compileSdkVersion
|
|
19
|
-
buildToolsVersion
|
|
21
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 28)
|
|
22
|
+
buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
|
|
20
23
|
|
|
21
24
|
defaultConfig {
|
|
22
|
-
minSdkVersion 16
|
|
23
|
-
targetSdkVersion 22
|
|
25
|
+
minSdkVersion safeExtGet('minSdkVersion', 16)
|
|
26
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 22)
|
|
24
27
|
versionCode 1
|
|
25
28
|
versionName "1.0"
|
|
26
29
|
}
|
|
@@ -37,10 +40,14 @@ repositories {
|
|
|
37
40
|
url "$projectDir/../node_modules/react-native/android"
|
|
38
41
|
}
|
|
39
42
|
mavenCentral()
|
|
43
|
+
mavenLocal()
|
|
44
|
+
google()
|
|
45
|
+
jcenter()
|
|
46
|
+
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
dependencies {
|
|
43
|
-
|
|
50
|
+
implementation 'com.facebook.react:react-native:+'
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
def configureReactNativePom(def pom) {
|
|
@@ -93,8 +100,8 @@ afterEvaluate { project ->
|
|
|
93
100
|
|
|
94
101
|
android.libraryVariants.all { variant ->
|
|
95
102
|
def name = variant.name.capitalize()
|
|
96
|
-
task "jar${name}"(type: Jar, dependsOn: variant.
|
|
97
|
-
from variant.
|
|
103
|
+
task "jar${name}"(type: Jar, dependsOn: variant.javaCompileProvider.get()) {
|
|
104
|
+
from variant.javaCompileProvider.get().destinationDir
|
|
98
105
|
}
|
|
99
106
|
}
|
|
100
107
|
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
package com.swmansion.rnscreens;
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
3
|
+
import androidx.lifecycle.Lifecycle;
|
|
4
|
+
import androidx.lifecycle.LifecycleObserver;
|
|
5
|
+
import androidx.fragment.app.Fragment;
|
|
6
|
+
|
|
7
7
|
import android.view.View;
|
|
8
8
|
import android.view.ViewParent;
|
|
9
9
|
|
|
10
|
-
import com.facebook.react.modules.core.ChoreographerCompat;
|
|
11
|
-
import com.facebook.react.modules.core.ReactChoreographer;
|
|
12
|
-
|
|
13
|
-
import java.util.ArrayList;
|
|
14
10
|
import java.util.HashMap;
|
|
15
11
|
import java.util.Map;
|
|
16
12
|
|
package/android/src/main/java/com/swmansion/rnscreens/{RNScreenPackage.java → RNScreensPackage.java}
RENAMED
|
@@ -9,7 +9,7 @@ import java.util.Arrays;
|
|
|
9
9
|
import java.util.Collections;
|
|
10
10
|
import java.util.List;
|
|
11
11
|
|
|
12
|
-
public class
|
|
12
|
+
public class RNScreensPackage implements ReactPackage {
|
|
13
13
|
@Override
|
|
14
14
|
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
|
15
15
|
return Collections.emptyList();
|
|
@@ -19,7 +19,6 @@ public class RNScreenPackage implements ReactPackage {
|
|
|
19
19
|
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
|
20
20
|
return Arrays.<ViewManager>asList(
|
|
21
21
|
new ScreenContainerViewManager(),
|
|
22
|
-
new ScreenStackViewManager(),
|
|
23
22
|
new ScreenViewManager()
|
|
24
23
|
);
|
|
25
24
|
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
package com.swmansion.rnscreens;
|
|
2
2
|
|
|
3
|
+
import androidx.annotation.Nullable;
|
|
4
|
+
import androidx.fragment.app.Fragment;
|
|
5
|
+
|
|
3
6
|
import android.annotation.SuppressLint;
|
|
4
7
|
import android.content.Context;
|
|
8
|
+
import android.graphics.Paint;
|
|
5
9
|
import android.os.Bundle;
|
|
6
|
-
import android.support.annotation.Nullable;
|
|
7
|
-
import android.support.v4.app.Fragment;
|
|
8
10
|
import android.view.LayoutInflater;
|
|
9
11
|
import android.view.View;
|
|
10
12
|
import android.view.ViewGroup;
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
import com.facebook.react.uimanager.PointerEvents;
|
|
15
|
+
import com.facebook.react.uimanager.ReactPointerEventsView;
|
|
16
|
+
|
|
17
|
+
public class Screen extends ViewGroup implements ReactPointerEventsView {
|
|
13
18
|
|
|
14
19
|
public static class ScreenFragment extends Fragment {
|
|
15
20
|
|
|
@@ -36,6 +41,7 @@ public class Screen extends ViewGroup {
|
|
|
36
41
|
private final Fragment mFragment;
|
|
37
42
|
private @Nullable ScreenContainer mContainer;
|
|
38
43
|
private boolean mActive;
|
|
44
|
+
private boolean mTransitioning;
|
|
39
45
|
|
|
40
46
|
public Screen(Context context) {
|
|
41
47
|
super(context);
|
|
@@ -47,6 +53,38 @@ public class Screen extends ViewGroup {
|
|
|
47
53
|
// no-op
|
|
48
54
|
}
|
|
49
55
|
|
|
56
|
+
/**
|
|
57
|
+
* While transitioning this property allows to optimize rendering behavior on Android and provide
|
|
58
|
+
* a correct blending options for the animated screen. It is turned on automatically by the container
|
|
59
|
+
* when transitioning is detected and turned off immediately after
|
|
60
|
+
*/
|
|
61
|
+
public void setTransitioning(boolean transitioning) {
|
|
62
|
+
if (mTransitioning == transitioning) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
mTransitioning = transitioning;
|
|
66
|
+
super.setLayerType(transitioning ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, null);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Override
|
|
70
|
+
public boolean hasOverlappingRendering() {
|
|
71
|
+
return mTransitioning;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Override
|
|
75
|
+
public PointerEvents getPointerEvents() {
|
|
76
|
+
return mTransitioning ? PointerEvents.NONE : PointerEvents.AUTO;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Override
|
|
80
|
+
public void setLayerType(int layerType, @Nullable Paint paint) {
|
|
81
|
+
// ignore - layer type is controlled by `transitioning` prop
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public void setNeedsOffscreenAlphaCompositing(boolean needsOffscreenAlphaCompositing) {
|
|
85
|
+
// ignore - offscreen alpha is controlled by `transitioning` prop
|
|
86
|
+
}
|
|
87
|
+
|
|
50
88
|
protected void setContainer(@Nullable ScreenContainer mContainer) {
|
|
51
89
|
this.mContainer = mContainer;
|
|
52
90
|
}
|
|
@@ -60,6 +98,9 @@ public class Screen extends ViewGroup {
|
|
|
60
98
|
}
|
|
61
99
|
|
|
62
100
|
public void setActive(boolean active) {
|
|
101
|
+
if (active == mActive) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
63
104
|
mActive = active;
|
|
64
105
|
if (mContainer != null) {
|
|
65
106
|
mContainer.notifyChildUpdate();
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
package com.swmansion.rnscreens;
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import androidx.annotation.Nullable;
|
|
4
|
+
import androidx.fragment.app.Fragment;
|
|
5
|
+
import androidx.fragment.app.FragmentActivity;
|
|
6
|
+
import androidx.fragment.app.FragmentTransaction;
|
|
7
|
+
|
|
4
8
|
import android.content.Context;
|
|
5
|
-
import android.
|
|
6
|
-
import android.support.v4.app.Fragment;
|
|
7
|
-
import android.support.v4.app.FragmentActivity;
|
|
8
|
-
import android.support.v4.app.FragmentManager;
|
|
9
|
-
import android.support.v4.app.FragmentTransaction;
|
|
9
|
+
import android.content.ContextWrapper;
|
|
10
10
|
import android.view.ViewGroup;
|
|
11
|
+
import android.view.ViewParent;
|
|
11
12
|
|
|
12
|
-
import com.facebook.react.
|
|
13
|
+
import com.facebook.react.ReactRootView;
|
|
13
14
|
import com.facebook.react.modules.core.ChoreographerCompat;
|
|
14
15
|
import com.facebook.react.modules.core.ReactChoreographer;
|
|
15
16
|
|
|
@@ -22,10 +23,10 @@ public class ScreenContainer extends ViewGroup {
|
|
|
22
23
|
|
|
23
24
|
private final ArrayList<Screen> mScreens = new ArrayList<>();
|
|
24
25
|
private final Set<Screen> mActiveScreens = new HashSet<>();
|
|
25
|
-
private final FragmentManager mFragmentManager;
|
|
26
26
|
|
|
27
27
|
private @Nullable FragmentTransaction mCurrentTransaction;
|
|
28
28
|
private boolean mNeedUpdate;
|
|
29
|
+
private boolean mIsAttached;
|
|
29
30
|
|
|
30
31
|
private ChoreographerCompat.FrameCallback mFrameCallback = new ChoreographerCompat.FrameCallback() {
|
|
31
32
|
@Override
|
|
@@ -36,13 +37,6 @@ public class ScreenContainer extends ViewGroup {
|
|
|
36
37
|
|
|
37
38
|
public ScreenContainer(Context context) {
|
|
38
39
|
super(context);
|
|
39
|
-
Activity activity = ((ReactContext) context).getCurrentActivity();
|
|
40
|
-
if (activity instanceof FragmentActivity) {
|
|
41
|
-
mFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
|
|
42
|
-
} else {
|
|
43
|
-
throw new IllegalStateException(
|
|
44
|
-
"In order to use RNScreen components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity");
|
|
45
|
-
}
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
@Override
|
|
@@ -85,9 +79,35 @@ public class ScreenContainer extends ViewGroup {
|
|
|
85
79
|
return mScreens.get(index);
|
|
86
80
|
}
|
|
87
81
|
|
|
82
|
+
private FragmentActivity findRootFragmentActivity() {
|
|
83
|
+
ViewParent parent = this;
|
|
84
|
+
while (!(parent instanceof ReactRootView) && parent.getParent() != null) {
|
|
85
|
+
parent = parent.getParent();
|
|
86
|
+
}
|
|
87
|
+
// we expect top level view to be of type ReactRootView, this isn't really necessary but in order
|
|
88
|
+
// to find root view we test if parent is null. This could potentially happen also when the view
|
|
89
|
+
// is detached from the hierarchy and that test would not correctly indicate the root view. So
|
|
90
|
+
// in order to make sure we indeed reached the root we test if it is of a correct type. This
|
|
91
|
+
// allows us to provide a more descriptive error message for the aforementioned case.
|
|
92
|
+
if (!(parent instanceof ReactRootView)) {
|
|
93
|
+
throw new IllegalStateException("ScreenContainer is not attached under ReactRootView");
|
|
94
|
+
}
|
|
95
|
+
// ReactRootView is expected to be initialized with the main React Activity as a context but
|
|
96
|
+
// in case of Expo the activity is wrapped in ContextWrapper and we need to unwrap it
|
|
97
|
+
Context context = ((ReactRootView) parent).getContext();
|
|
98
|
+
while (!(context instanceof FragmentActivity) && context instanceof ContextWrapper) {
|
|
99
|
+
context = ((ContextWrapper) context).getBaseContext();
|
|
100
|
+
}
|
|
101
|
+
if (!(context instanceof FragmentActivity)) {
|
|
102
|
+
throw new IllegalStateException(
|
|
103
|
+
"In order to use RNScreens components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity");
|
|
104
|
+
}
|
|
105
|
+
return (FragmentActivity) context;
|
|
106
|
+
}
|
|
107
|
+
|
|
88
108
|
private FragmentTransaction getOrCreateTransaction() {
|
|
89
109
|
if (mCurrentTransaction == null) {
|
|
90
|
-
mCurrentTransaction =
|
|
110
|
+
mCurrentTransaction = findRootFragmentActivity().getSupportFragmentManager().beginTransaction();
|
|
91
111
|
mCurrentTransaction.setReorderingAllowed(true);
|
|
92
112
|
}
|
|
93
113
|
return mCurrentTransaction;
|
|
@@ -95,7 +115,7 @@ public class ScreenContainer extends ViewGroup {
|
|
|
95
115
|
|
|
96
116
|
private void tryCommitTransaction() {
|
|
97
117
|
if (mCurrentTransaction != null) {
|
|
98
|
-
mCurrentTransaction.
|
|
118
|
+
mCurrentTransaction.commitAllowingStateLoss();
|
|
99
119
|
mCurrentTransaction = null;
|
|
100
120
|
}
|
|
101
121
|
}
|
|
@@ -121,8 +141,21 @@ public class ScreenContainer extends ViewGroup {
|
|
|
121
141
|
return screen.isActive();
|
|
122
142
|
}
|
|
123
143
|
|
|
144
|
+
@Override
|
|
145
|
+
protected void onAttachedToWindow() {
|
|
146
|
+
super.onAttachedToWindow();
|
|
147
|
+
mIsAttached = true;
|
|
148
|
+
updateIfNeeded();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@Override
|
|
152
|
+
protected void onDetachedFromWindow() {
|
|
153
|
+
super.onDetachedFromWindow();
|
|
154
|
+
mIsAttached = false;
|
|
155
|
+
}
|
|
156
|
+
|
|
124
157
|
private void updateIfNeeded() {
|
|
125
|
-
if (!mNeedUpdate ||
|
|
158
|
+
if (!mNeedUpdate || !mIsAttached) {
|
|
126
159
|
return;
|
|
127
160
|
}
|
|
128
161
|
mNeedUpdate = false;
|
|
@@ -144,6 +177,15 @@ public class ScreenContainer extends ViewGroup {
|
|
|
144
177
|
}
|
|
145
178
|
}
|
|
146
179
|
|
|
180
|
+
// detect if we are "transitioning" based on the number of active screens
|
|
181
|
+
int activeScreens = 0;
|
|
182
|
+
for (int i = 0, size = mScreens.size(); i < size; i++) {
|
|
183
|
+
if (isScreenActive(mScreens.get(i), mScreens)) {
|
|
184
|
+
activeScreens += 1;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
boolean transitioning = activeScreens > 1;
|
|
188
|
+
|
|
147
189
|
// attach newly activated screens
|
|
148
190
|
boolean addedBefore = false;
|
|
149
191
|
for (int i = 0, size = mScreens.size(); i < size; i++) {
|
|
@@ -155,6 +197,7 @@ public class ScreenContainer extends ViewGroup {
|
|
|
155
197
|
} else if (isActive && addedBefore) {
|
|
156
198
|
moveToFront(screen);
|
|
157
199
|
}
|
|
200
|
+
screen.setTransitioning(transitioning);
|
|
158
201
|
}
|
|
159
202
|
tryCommitTransaction();
|
|
160
203
|
}
|
|
@@ -24,7 +24,7 @@ public class ScreenContainerViewManager extends ViewGroupManager<ScreenContainer
|
|
|
24
24
|
@Override
|
|
25
25
|
public void addView(ScreenContainer parent, View child, int index) {
|
|
26
26
|
if (!(child instanceof Screen)) {
|
|
27
|
-
throw new IllegalArgumentException("Attempt attach child that is not of type
|
|
27
|
+
throw new IllegalArgumentException("Attempt attach child that is not of type RNScreens");
|
|
28
28
|
}
|
|
29
29
|
parent.addScreen((Screen) child, index);
|
|
30
30
|
}
|
|
@@ -20,8 +20,8 @@ public class ScreenViewManager extends ViewGroupManager<Screen> {
|
|
|
20
20
|
return new Screen(reactContext);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
@ReactProp(name = "active",
|
|
24
|
-
public void setActive(Screen view,
|
|
25
|
-
view.setActive(active);
|
|
23
|
+
@ReactProp(name = "active", defaultFloat = 0)
|
|
24
|
+
public void setActive(Screen view, float active) {
|
|
25
|
+
view.setActive(active != 0);
|
|
26
26
|
}
|
|
27
27
|
}
|
package/ios/RNSScreen.h
CHANGED
package/ios/RNSScreen.m
CHANGED
|
@@ -4,10 +4,15 @@
|
|
|
4
4
|
@interface RNSScreen : UIViewController
|
|
5
5
|
|
|
6
6
|
- (instancetype)initWithView:(UIView *)view;
|
|
7
|
+
- (void)notifyFinishTransitioning;
|
|
7
8
|
|
|
8
9
|
@end
|
|
9
10
|
|
|
10
|
-
@implementation RNSScreenView
|
|
11
|
+
@implementation RNSScreenView {
|
|
12
|
+
RNSScreen *_controller;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@synthesize controller = _controller;
|
|
11
16
|
|
|
12
17
|
- (instancetype)init
|
|
13
18
|
{
|
|
@@ -20,18 +25,21 @@
|
|
|
20
25
|
|
|
21
26
|
- (void)setActive:(BOOL)active
|
|
22
27
|
{
|
|
23
|
-
_active
|
|
24
|
-
|
|
28
|
+
if (active != _active) {
|
|
29
|
+
_active = active;
|
|
30
|
+
[_reactSuperview markChildUpdated];
|
|
31
|
+
}
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
- (
|
|
34
|
+
- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
|
|
28
35
|
{
|
|
29
|
-
|
|
36
|
+
// pointer events settings are managed by the parent screen container, we ignore any attempt
|
|
37
|
+
// of setting that via React props
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
- (
|
|
40
|
+
- (UIView *)reactSuperview
|
|
33
41
|
{
|
|
34
|
-
|
|
42
|
+
return _reactSuperview;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
- (void)invalidate
|
|
@@ -40,10 +48,16 @@
|
|
|
40
48
|
_controller = nil;
|
|
41
49
|
}
|
|
42
50
|
|
|
51
|
+
- (void)notifyFinishTransitioning
|
|
52
|
+
{
|
|
53
|
+
[_controller notifyFinishTransitioning];
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
@end
|
|
44
57
|
|
|
45
58
|
@implementation RNSScreen {
|
|
46
59
|
__weak UIView *_view;
|
|
60
|
+
__weak id _previousFirstResponder;
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
- (instancetype)initWithView:(UIView *)view
|
|
@@ -54,6 +68,36 @@
|
|
|
54
68
|
return self;
|
|
55
69
|
}
|
|
56
70
|
|
|
71
|
+
- (id)findFirstResponder:(UIView*)parent
|
|
72
|
+
{
|
|
73
|
+
if (parent.isFirstResponder) {
|
|
74
|
+
return parent;
|
|
75
|
+
}
|
|
76
|
+
for (UIView *subView in parent.subviews) {
|
|
77
|
+
id responder = [self findFirstResponder:subView];
|
|
78
|
+
if (responder != nil) {
|
|
79
|
+
return responder;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return nil;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
- (void)willMoveToParentViewController:(UIViewController *)parent
|
|
86
|
+
{
|
|
87
|
+
if (parent == nil) {
|
|
88
|
+
id responder = [self findFirstResponder:self.view];
|
|
89
|
+
if (responder != nil) {
|
|
90
|
+
_previousFirstResponder = responder;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)notifyFinishTransitioning
|
|
96
|
+
{
|
|
97
|
+
[_previousFirstResponder becomeFirstResponder];
|
|
98
|
+
_previousFirstResponder = nil;
|
|
99
|
+
}
|
|
100
|
+
|
|
57
101
|
- (void)loadView
|
|
58
102
|
{
|
|
59
103
|
self.view = _view;
|
package/ios/RNSScreenContainer.h
CHANGED
|
@@ -3,16 +3,8 @@
|
|
|
3
3
|
@protocol RNSScreenContainerDelegate
|
|
4
4
|
|
|
5
5
|
- (void)markChildUpdated;
|
|
6
|
-
- (void)didUpdateChildren;
|
|
7
6
|
|
|
8
7
|
@end
|
|
9
8
|
|
|
10
9
|
@interface RNSScreenContainerView : UIView <RNSScreenContainerDelegate>
|
|
11
|
-
|
|
12
|
-
- (void)markChildUpdated;
|
|
13
|
-
- (void)didUpdateChildren;
|
|
14
|
-
|
|
15
|
-
@end
|
|
16
|
-
|
|
17
|
-
@interface RNSScreenContainerManager : RCTViewManager
|
|
18
10
|
@end
|