mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-03-29 22:42:59 +08:00
Compare commits
45 Commits
2.0.0-alph
...
2.0.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f9c504627 | ||
|
|
953763f7d9 | ||
|
|
7c351df14d | ||
|
|
da9426b4b9 | ||
|
|
4169fad8b3 | ||
|
|
4f8efd2873 | ||
|
|
cbc86bb6d8 | ||
|
|
0927e03687 | ||
|
|
efaf0cd125 | ||
|
|
21e6a9732a | ||
|
|
31192250e1 | ||
|
|
124e8acb2d | ||
|
|
2c5f95cea6 | ||
|
|
0a2336d005 | ||
|
|
58d1791d4a | ||
|
|
3d56c5d4e2 | ||
|
|
47658d4d7d | ||
|
|
258ae419de | ||
|
|
62123f16f9 | ||
|
|
a94424192b | ||
|
|
752d6c0f04 | ||
|
|
a017713efc | ||
|
|
26384b625e | ||
|
|
5d5e8bfca6 | ||
|
|
2536837795 | ||
|
|
20650a8ede | ||
|
|
ee0dbfe8ae | ||
|
|
23cbc009d4 | ||
|
|
f21a093918 | ||
|
|
1d4712acbd | ||
|
|
09c71a45a2 | ||
|
|
79e664f11d | ||
|
|
b622abc935 | ||
|
|
7d4bbb8f88 | ||
|
|
7c304a007f | ||
|
|
adf3333462 | ||
|
|
d4636d3130 | ||
|
|
4749405d64 | ||
|
|
faff1138f7 | ||
|
|
b9473ccb04 | ||
|
|
5cfe3f2814 | ||
|
|
c7f5fe6554 | ||
|
|
535902014d | ||
|
|
4a9a3a877a | ||
|
|
c590283359 |
@@ -11,7 +11,7 @@ import {
|
||||
createSwitchNavigator,
|
||||
createAppContainer,
|
||||
} from 'react-navigation';
|
||||
import { useScreens } from 'react-native-screens';
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
|
||||
import Stack from './stack';
|
||||
import NativeStack from './nativeStack';
|
||||
@@ -20,7 +20,7 @@ import Navigation from './navigation';
|
||||
import NativeNavigation from './nativeNavigation';
|
||||
import NavigationTabsAndStack from './navigationTabsAndStack';
|
||||
|
||||
useScreens();
|
||||
enableScreens();
|
||||
|
||||
const SCREENS = {
|
||||
Stack: { screen: Stack, title: 'Screen container based stack' },
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { Text, View, StyleSheet, Button } from 'react-native';
|
||||
import { useScreens } from 'react-native-screens';
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
import {
|
||||
createAppContainer,
|
||||
createStackNavigator,
|
||||
createBottomTabNavigator,
|
||||
} from 'react-navigation';
|
||||
|
||||
useScreens();
|
||||
enableScreens();
|
||||
|
||||
class DetailsScreen extends React.Component {
|
||||
static navigationOptions = ({ navigation }) => {
|
||||
|
||||
45
README.md
45
README.md
@@ -17,23 +17,26 @@ Screens support is built into [react-navigation](https://github.com/react-naviga
|
||||
|
||||
To configure react-navigation to use screens instead of plain RN Views for rendering screen views, follow the steps below:
|
||||
|
||||
1. Add this library as a depedency to your project:
|
||||
```
|
||||
1. Add this library as a dependency to your project:
|
||||
|
||||
```bash
|
||||
yarn add react-native-screens
|
||||
```
|
||||
|
||||
2. Link native modules this library ships with into your app:
|
||||
```
|
||||
2.Link native modules this library ships with into your app:
|
||||
|
||||
```bash
|
||||
react-native link react-native-screens
|
||||
```
|
||||
|
||||
> If you are not familiar with the concept of linking libraries [read on here](https://facebook.github.io/react-native/docs/linking-libraries-ios).
|
||||
> If you are not familiar with the concept of linking libraries [read on here](https://facebook.github.io/react-native/docs/linking-libraries-ios).
|
||||
|
||||
3.Enable screens support before any of your navigation screen renders. Add the following code to your main application file (e.g. App.js):
|
||||
|
||||
3. Enable screens support before any of your navigation screen renders. Add the following code to your main application file (e.g. App.js):
|
||||
```js
|
||||
import { useScreens } from 'react-native-screens';
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
|
||||
useScreens();
|
||||
enableScreens();
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -56,12 +59,14 @@ implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
|
||||
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.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn add react-native-screens
|
||||
```
|
||||
|
||||
2. Open your App.js file and add the following snippet somewhere near the top of the file (e.g. right after import statements):
|
||||
```
|
||||
|
||||
```js
|
||||
import { useScreens } from 'react-native-screens';
|
||||
|
||||
useScreens();
|
||||
@@ -85,8 +90,7 @@ Then replace places when you use `createStackNavigator` with `createNativeStackN
|
||||
|
||||
## Interop with other libraries
|
||||
|
||||
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).
|
||||
|
||||
This library should work out of the box with all existing react-native libraries. If you experience problems with interoperability please [report an issue](https://github.com/kmagiera/react-native-screens/issues).
|
||||
|
||||
## Guide for navigation library authors
|
||||
|
||||
@@ -136,6 +140,7 @@ A callback that gets called when the current screen is dismissed by hardware bac
|
||||
Allows for the customization of how the given screen should appear/dissapear when pushed or popped at the top of the stack. The followin values are currently supported:
|
||||
- `"default"` – uses a platform default animation
|
||||
- `"fade"` – fades screen in or out
|
||||
- `"flip"` – flips the screen, requires `stackPresentation: "modal"` (iOS only)
|
||||
- `"none"` – the screen appears/dissapears without an animation
|
||||
|
||||
#### `stackPresentation`
|
||||
@@ -214,6 +219,18 @@ Allows for customizing font family to be used for back button title on iOS.
|
||||
|
||||
Allows for customizing font size to be used for back button title on iOS.
|
||||
|
||||
#### `largeTitle` (iOS only)
|
||||
|
||||
When set to `true` it makes the title display using the large title effect.
|
||||
|
||||
#### `largeTitleFontFamily` (iOS only)
|
||||
|
||||
Customize font family to be used for the large title.
|
||||
|
||||
#### `largeTitleFontSize` (iOS only)
|
||||
|
||||
Customize the size of the font to be used for the large title.
|
||||
|
||||
## Guide for native component authors
|
||||
|
||||
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.
|
||||
@@ -258,5 +275,5 @@ React native screens library is licensed under [The MIT License](LICENSE).
|
||||
|
||||
This project is supported by amazing people from [Expo.io](https://expo.io) and [Software Mansion](https://swmansion.com)
|
||||
|
||||
[](https://expo.io)
|
||||
[](https://swmansion.com)
|
||||
[](https://expo.io)
|
||||
[](https://swmansion.com)
|
||||
|
||||
@@ -48,7 +48,9 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
}
|
||||
|
||||
def configureReactNativePom(def pom) {
|
||||
@@ -89,12 +91,12 @@ afterEvaluate { project ->
|
||||
}
|
||||
|
||||
task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) {
|
||||
archiveClassifier.set('javadoc')
|
||||
classifier = 'javadoc'
|
||||
from androidJavadoc.destinationDir
|
||||
}
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
archiveClassifier.set('sources')
|
||||
classifier = 'sources'
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
include '**/*.java'
|
||||
}
|
||||
|
||||
@@ -17,5 +17,6 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
android.useDeprecatedNdk=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
@@ -13,11 +10,11 @@ import android.widget.TextView;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.bridge.GuardedRunnable;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.PointerEvents;
|
||||
import com.facebook.react.uimanager.ReactPointerEventsView;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
|
||||
public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
|
||||
@@ -33,34 +30,6 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
FADE
|
||||
}
|
||||
|
||||
public static class ScreenFragment extends Fragment {
|
||||
|
||||
private Screen mScreenView;
|
||||
|
||||
public ScreenFragment() {
|
||||
throw new IllegalStateException("Screen fragments should never be restored");
|
||||
}
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
public ScreenFragment(Screen screenView) {
|
||||
super();
|
||||
mScreenView = screenView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return mScreenView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mScreenView.mEventDispatcher.dispatchEvent(new ScreenDismissedEvent(mScreenView.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
private static OnAttachStateChangeListener sShowSoftKeyboardOnAttach = new OnAttachStateChangeListener() {
|
||||
|
||||
@Override
|
||||
@@ -77,8 +46,7 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
}
|
||||
};
|
||||
|
||||
private final Fragment mFragment;
|
||||
private final EventDispatcher mEventDispatcher;
|
||||
private @Nullable Fragment mFragment;
|
||||
private @Nullable ScreenContainer mContainer;
|
||||
private boolean mActive;
|
||||
private boolean mTransitioning;
|
||||
@@ -87,13 +55,23 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
|
||||
public Screen(ReactContext context) {
|
||||
super(context);
|
||||
mFragment = new ScreenFragment(this);
|
||||
mEventDispatcher = context.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int b, int r) {
|
||||
// no-op
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
if (changed) {
|
||||
final int width = r - l;
|
||||
final int height = b - t;
|
||||
final ReactContext reactContext = (ReactContext) getContext();
|
||||
reactContext.runOnNativeModulesQueueThread(
|
||||
new GuardedRunnable(reactContext) {
|
||||
@Override
|
||||
public void runGuarded() {
|
||||
reactContext.getNativeModule(UIManagerModule.class)
|
||||
.updateNodeSize(getId(), width, height);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,14 +144,18 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
|
||||
mContainer = container;
|
||||
}
|
||||
|
||||
protected @Nullable ScreenContainer getContainer() {
|
||||
return mContainer;
|
||||
protected void setFragment(Fragment fragment) {
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
protected Fragment getFragment() {
|
||||
protected @Nullable Fragment getFragment() {
|
||||
return mFragment;
|
||||
}
|
||||
|
||||
protected @Nullable ScreenContainer getContainer() {
|
||||
return mContainer;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
if (active == mActive) {
|
||||
return;
|
||||
|
||||
@@ -6,8 +6,8 @@ import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
@@ -16,32 +16,56 @@ import com.facebook.react.modules.core.ReactChoreographer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ScreenContainer extends ViewGroup {
|
||||
public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
|
||||
protected final ArrayList<Screen> mScreens = new ArrayList<>();
|
||||
private final Set<Screen> mActiveScreens = new HashSet<>();
|
||||
protected final ArrayList<T> mScreenFragments = new ArrayList<>();
|
||||
private final Set<ScreenFragment> mActiveScreenFragments = new HashSet<>();
|
||||
|
||||
private @Nullable FragmentManager mFragmentManager;
|
||||
private @Nullable FragmentTransaction mCurrentTransaction;
|
||||
private boolean mNeedUpdate;
|
||||
private boolean mIsAttached;
|
||||
private boolean mLayoutEnqueued = false;
|
||||
|
||||
private ChoreographerCompat.FrameCallback mFrameCallback = new ChoreographerCompat.FrameCallback() {
|
||||
private final ChoreographerCompat.FrameCallback mFrameCallback = new ChoreographerCompat.FrameCallback() {
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
updateIfNeeded();
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mLayoutRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLayoutEnqueued = false;
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
|
||||
layout(getLeft(), getTop(), getRight(), getBottom());
|
||||
}
|
||||
};
|
||||
|
||||
public ScreenContainer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
|
||||
// no-op
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
for (int i = 0, size = getChildCount(); i < size; i++) {
|
||||
getChildAt(i).layout(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
super.requestLayout();
|
||||
|
||||
if (!mLayoutEnqueued) {
|
||||
mLayoutEnqueued = true;
|
||||
post(mLayoutRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
protected void markUpdated() {
|
||||
@@ -59,31 +83,44 @@ public class ScreenContainer extends ViewGroup {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected T adapt(Screen screen) {
|
||||
return (T) new ScreenFragment(screen);
|
||||
}
|
||||
|
||||
protected void addScreen(Screen screen, int index) {
|
||||
mScreens.add(index, screen);
|
||||
T fragment = adapt(screen);
|
||||
screen.setFragment(fragment);
|
||||
mScreenFragments.add(index, fragment);
|
||||
screen.setContainer(this);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected void removeScreenAt(int index) {
|
||||
mScreens.get(index).setContainer(null);
|
||||
mScreens.remove(index);
|
||||
mScreenFragments.get(index).getScreen().setContainer(null);
|
||||
mScreenFragments.remove(index);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected int getScreenCount() {
|
||||
return mScreens.size();
|
||||
return mScreenFragments.size();
|
||||
}
|
||||
|
||||
protected Screen getScreenAt(int index) {
|
||||
return mScreens.get(index);
|
||||
return mScreenFragments.get(index).getScreen();
|
||||
}
|
||||
|
||||
protected final FragmentActivity findRootFragmentActivity() {
|
||||
private FragmentManager findFragmentManager() {
|
||||
ViewParent parent = this;
|
||||
while (!(parent instanceof ReactRootView) && parent.getParent() != null) {
|
||||
// We traverse view hierarchy up until we find screen parent or a root view
|
||||
while (!(parent instanceof ReactRootView || parent instanceof Screen) && parent.getParent() != null) {
|
||||
parent = parent.getParent();
|
||||
}
|
||||
// If parent is of type Screen it means we are inside a nested fragment structure.
|
||||
// Otherwise we expect to connect directly with root view and get root fragment manager
|
||||
if (parent instanceof Screen) {
|
||||
return ((Screen) parent).getFragment().getChildFragmentManager();
|
||||
}
|
||||
|
||||
// we expect top level view to be of type ReactRootView, this isn't really necessary but in order
|
||||
// to find root view we test if parent is null. This could potentially happen also when the view
|
||||
// is detached from the hierarchy and that test would not correctly indicate the root view. So
|
||||
@@ -102,12 +139,19 @@ public class ScreenContainer extends ViewGroup {
|
||||
throw new IllegalStateException(
|
||||
"In order to use RNScreens components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity");
|
||||
}
|
||||
return (FragmentActivity) context;
|
||||
return ((FragmentActivity) context).getSupportFragmentManager();
|
||||
}
|
||||
|
||||
protected final FragmentManager getFragmentManager() {
|
||||
if (mFragmentManager == null) {
|
||||
mFragmentManager = findFragmentManager();
|
||||
}
|
||||
return mFragmentManager;
|
||||
}
|
||||
|
||||
protected FragmentTransaction getOrCreateTransaction() {
|
||||
if (mCurrentTransaction == null) {
|
||||
mCurrentTransaction = findRootFragmentActivity().getSupportFragmentManager().beginTransaction();
|
||||
mCurrentTransaction = getFragmentManager().beginTransaction();
|
||||
mCurrentTransaction.setReorderingAllowed(true);
|
||||
}
|
||||
return mCurrentTransaction;
|
||||
@@ -120,25 +164,24 @@ public class ScreenContainer extends ViewGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private void attachScreen(Screen screen) {
|
||||
getOrCreateTransaction().add(getId(), screen.getFragment());
|
||||
mActiveScreens.add(screen);
|
||||
private void attachScreen(ScreenFragment screenFragment) {
|
||||
getOrCreateTransaction().add(getId(), screenFragment);
|
||||
mActiveScreenFragments.add(screenFragment);
|
||||
}
|
||||
|
||||
private void moveToFront(Screen screen) {
|
||||
private void moveToFront(ScreenFragment screenFragment) {
|
||||
FragmentTransaction transaction = getOrCreateTransaction();
|
||||
Fragment fragment = screen.getFragment();
|
||||
transaction.remove(fragment);
|
||||
transaction.add(getId(), fragment);
|
||||
transaction.remove(screenFragment);
|
||||
transaction.add(getId(), screenFragment);
|
||||
}
|
||||
|
||||
private void detachScreen(Screen screen) {
|
||||
getOrCreateTransaction().remove(screen.getFragment());
|
||||
mActiveScreens.remove(screen);
|
||||
private void detachScreen(ScreenFragment screenFragment) {
|
||||
getOrCreateTransaction().remove(screenFragment);
|
||||
mActiveScreenFragments.remove(screenFragment);
|
||||
}
|
||||
|
||||
protected boolean isScreenActive(Screen screen, List<Screen> allScreens) {
|
||||
return screen.isActive();
|
||||
protected boolean isScreenActive(ScreenFragment screenFragment) {
|
||||
return screenFragment.getScreen().isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,6 +197,14 @@ public class ScreenContainer extends ViewGroup {
|
||||
mIsAttached = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
for (int i = 0, size = getChildCount(); i < size; i++) {
|
||||
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIfNeeded() {
|
||||
if (!mNeedUpdate || !mIsAttached) {
|
||||
return;
|
||||
@@ -164,26 +215,26 @@ public class ScreenContainer extends ViewGroup {
|
||||
|
||||
protected void onUpdate() {
|
||||
// detach screens that are no longer active
|
||||
Set<Screen> orphaned = new HashSet<>(mActiveScreens);
|
||||
for (int i = 0, size = mScreens.size(); i < size; i++) {
|
||||
Screen screen = mScreens.get(i);
|
||||
boolean isActive = isScreenActive(screen, mScreens);
|
||||
if (!isActive && mActiveScreens.contains(screen)) {
|
||||
detachScreen(screen);
|
||||
Set<ScreenFragment> orphaned = new HashSet<>(mActiveScreenFragments);
|
||||
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
|
||||
ScreenFragment screenFragment = mScreenFragments.get(i);
|
||||
boolean isActive = isScreenActive(screenFragment);
|
||||
if (!isActive && mActiveScreenFragments.contains(screenFragment)) {
|
||||
detachScreen(screenFragment);
|
||||
}
|
||||
orphaned.remove(screen);
|
||||
orphaned.remove(screenFragment);
|
||||
}
|
||||
if (!orphaned.isEmpty()) {
|
||||
Object[] orphanedAry = orphaned.toArray();
|
||||
for (int i = 0; i < orphanedAry.length; i++) {
|
||||
detachScreen((Screen) orphanedAry[i]);
|
||||
detachScreen((ScreenFragment) orphanedAry[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// detect if we are "transitioning" based on the number of active screens
|
||||
int activeScreens = 0;
|
||||
for (int i = 0, size = mScreens.size(); i < size; i++) {
|
||||
if (isScreenActive(mScreens.get(i), mScreens)) {
|
||||
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
|
||||
if (isScreenActive(mScreenFragments.get(i))) {
|
||||
activeScreens += 1;
|
||||
}
|
||||
}
|
||||
@@ -191,16 +242,16 @@ public class ScreenContainer extends ViewGroup {
|
||||
|
||||
// attach newly activated screens
|
||||
boolean addedBefore = false;
|
||||
for (int i = 0, size = mScreens.size(); i < size; i++) {
|
||||
Screen screen = mScreens.get(i);
|
||||
boolean isActive = isScreenActive(screen, mScreens);
|
||||
if (isActive && !mActiveScreens.contains(screen)) {
|
||||
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
|
||||
ScreenFragment screenFragment = mScreenFragments.get(i);
|
||||
boolean isActive = isScreenActive(screenFragment);
|
||||
if (isActive && !mActiveScreenFragments.contains(screenFragment)) {
|
||||
addedBefore = true;
|
||||
attachScreen(screen);
|
||||
attachScreen(screenFragment);
|
||||
} else if (isActive && addedBefore) {
|
||||
moveToFront(screen);
|
||||
moveToFront(screenFragment);
|
||||
}
|
||||
screen.setTransitioning(transitioning);
|
||||
screenFragment.getScreen().setTransitioning(transitioning);
|
||||
}
|
||||
tryCommitTransaction();
|
||||
}
|
||||
|
||||
@@ -43,4 +43,9 @@ public class ScreenContainerViewManager extends ViewGroupManager<ScreenContainer
|
||||
public View getChildAt(ScreenContainer parent, int index) {
|
||||
return parent.getScreenAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsCustomLayoutForChildren() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
|
||||
public class ScreenFragment extends Fragment {
|
||||
|
||||
protected Screen mScreenView;
|
||||
|
||||
public ScreenFragment() {
|
||||
throw new IllegalStateException("Screen fragments should never be restored");
|
||||
}
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
public ScreenFragment(Screen screenView) {
|
||||
super();
|
||||
mScreenView = screenView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return mScreenView;
|
||||
}
|
||||
|
||||
public Screen getScreen() {
|
||||
return mScreenView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
((ReactContext) mScreenView.getContext())
|
||||
.getNativeModule(UIManagerModule.class)
|
||||
.getEventDispatcher()
|
||||
.dispatchEvent(new ScreenDismissedEvent(mScreenView.getId()));
|
||||
}
|
||||
}
|
||||
@@ -2,48 +2,94 @@ package com.swmansion.rnscreens;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ScreenStack extends ScreenContainer {
|
||||
public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
|
||||
private final ArrayList<Screen> mStack = new ArrayList<>();
|
||||
private final Set<Screen> mDismissed = new HashSet<>();
|
||||
private static final String BACK_STACK_TAG = "RN_SCREEN_LAST";
|
||||
|
||||
private Screen mTopScreen = null;
|
||||
private final ArrayList<ScreenStackFragment> mStack = new ArrayList<>();
|
||||
private final Set<ScreenStackFragment> mDismissed = new HashSet<>();
|
||||
|
||||
private ScreenStackFragment mTopScreen = null;
|
||||
|
||||
private final FragmentManager.OnBackStackChangedListener mBackStackListener = new FragmentManager.OnBackStackChangedListener() {
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
if (getFragmentManager().getBackStackEntryCount() == 0) {
|
||||
// when back stack entry count hits 0 it means the user's navigated back using hw back
|
||||
// button. As the "fake" transaction we installed on the back stack does nothing we need
|
||||
// to handle back navigation on our own.
|
||||
dismiss(mTopScreen);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final FragmentManager.FragmentLifecycleCallbacks mLifecycleCallbacks = new FragmentManager.FragmentLifecycleCallbacks() {
|
||||
@Override
|
||||
public void onFragmentResumed(FragmentManager fm, Fragment f) {
|
||||
if (mTopScreen == f) {
|
||||
setupBackHandlerIfNeeded(mTopScreen);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ScreenStack(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void dismiss(Screen screen) {
|
||||
mDismissed.add(screen);
|
||||
public void dismiss(ScreenStackFragment screenFragment) {
|
||||
mDismissed.add(screenFragment);
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
public Screen getTopScreen() {
|
||||
for (int i = getScreenCount() - 1; i >= 0; i--) {
|
||||
Screen screen = getScreenAt(i);
|
||||
if (!mDismissed.contains(screen)) {
|
||||
return screen;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Stack is empty");
|
||||
return mTopScreen.getScreen();
|
||||
}
|
||||
|
||||
public Screen getRootScreen() {
|
||||
for (int i = 0, size = getScreenCount(); i < size; i++) {
|
||||
Screen screen = getScreenAt(i);
|
||||
if (!mDismissed.contains(screen)) {
|
||||
if (!mDismissed.contains(screen.getFragment())) {
|
||||
return screen;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Stack has no root screen set");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScreenStackFragment adapt(Screen screen) {
|
||||
return new ScreenStackFragment(screen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
FragmentManager fm = getFragmentManager();
|
||||
fm.removeOnBackStackChangedListener(mBackStackListener);
|
||||
getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks);
|
||||
if (!fm.isStateSaved()) {
|
||||
// state save means that the container where fragment manager was installed has been unmounted.
|
||||
// This could happen as a result of dismissing nested stack. In such a case we don't need to
|
||||
// reset back stack as it'd result in a crash caused by the fact the fragment manager is no
|
||||
// longer attached.
|
||||
fm.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeScreenAt(int index) {
|
||||
Screen toBeRemoved = getScreenAt(index);
|
||||
@@ -54,20 +100,24 @@ public class ScreenStack extends ScreenContainer {
|
||||
@Override
|
||||
protected void onUpdate() {
|
||||
// remove all screens previously on stack
|
||||
for (Screen screen : mStack) {
|
||||
if (!mScreens.contains(screen) || mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().remove(screen.getFragment());
|
||||
for (ScreenStackFragment screen : mStack) {
|
||||
if (!mScreenFragments.contains(screen) || mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().remove(screen);
|
||||
}
|
||||
}
|
||||
Screen newTop = null;
|
||||
Screen belowTop = null; // this is only set if newTop has TRANSPARENT_MODAL presentation mode
|
||||
|
||||
for (int i = mScreens.size() - 1; i >= 0; i--) {
|
||||
Screen screen = mScreens.get(i);
|
||||
// When going back from a nested stack with a single screen on it, we may hit an edge case
|
||||
// when all screens are dismissed and no screen is to be displayed on top. We need to gracefully
|
||||
// handle the case of newTop being NULL, which happens in several places below
|
||||
ScreenStackFragment newTop = null; // newTop is nullable, see the above comment ^
|
||||
ScreenStackFragment belowTop = null; // this is only set if newTop has TRANSPARENT_MODAL presentation mode
|
||||
|
||||
for (int i = mScreenFragments.size() - 1; i >= 0; i--) {
|
||||
ScreenStackFragment screen = mScreenFragments.get(i);
|
||||
if (!mDismissed.contains(screen)) {
|
||||
if (newTop == null) {
|
||||
newTop = screen;
|
||||
if (newTop.getStackPresentation() != Screen.StackPresentation.TRANSPARENT_MODAL) {
|
||||
if (newTop.getScreen().getStackPresentation() != Screen.StackPresentation.TRANSPARENT_MODAL) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -77,35 +127,37 @@ public class ScreenStack extends ScreenContainer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (Screen screen : mScreens) {
|
||||
for (ScreenStackFragment screen : mScreenFragments) {
|
||||
// add all new views that weren't on stack before
|
||||
if (!mStack.contains(screen) && !mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().add(getId(), screen.getFragment());
|
||||
getOrCreateTransaction().add(getId(), screen);
|
||||
}
|
||||
// detach all screens that should not be visible
|
||||
if (screen != newTop && screen != belowTop && !mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().hide(screen.getFragment());
|
||||
getOrCreateTransaction().hide(screen);
|
||||
}
|
||||
}
|
||||
// attach "below top" screen if set
|
||||
if (belowTop != null) {
|
||||
final Screen top = newTop;
|
||||
getOrCreateTransaction().show(belowTop.getFragment()).runOnCommit(new Runnable() {
|
||||
final ScreenStackFragment top = newTop;
|
||||
getOrCreateTransaction().show(belowTop).runOnCommit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
top.bringToFront();
|
||||
top.getScreen().bringToFront();
|
||||
}
|
||||
});
|
||||
}
|
||||
getOrCreateTransaction().show(newTop.getFragment());
|
||||
|
||||
if (newTop != null) {
|
||||
getOrCreateTransaction().show(newTop);
|
||||
}
|
||||
|
||||
if (!mStack.contains(newTop)) {
|
||||
// if new top screen wasn't on stack we do "open animation" so long it is not the very first screen on stack
|
||||
if (mTopScreen != null) {
|
||||
// there was some other screen attached before
|
||||
int transition = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
|
||||
switch (mTopScreen.getStackAnimation()) {
|
||||
switch (mTopScreen.getScreen().getStackAnimation()) {
|
||||
case NONE:
|
||||
transition = FragmentTransaction.TRANSIT_NONE;
|
||||
break;
|
||||
@@ -118,7 +170,7 @@ public class ScreenStack extends ScreenContainer {
|
||||
} else if (mTopScreen != null && !mTopScreen.equals(newTop)) {
|
||||
// otherwise if we are performing top screen change we do "back animation"
|
||||
int transition = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;
|
||||
switch (mTopScreen.getStackAnimation()) {
|
||||
switch (mTopScreen.getScreen().getStackAnimation()) {
|
||||
case NONE:
|
||||
transition = FragmentTransaction.TRANSIT_NONE;
|
||||
break;
|
||||
@@ -132,8 +184,64 @@ public class ScreenStack extends ScreenContainer {
|
||||
mTopScreen = newTop;
|
||||
|
||||
mStack.clear();
|
||||
mStack.addAll(mScreens);
|
||||
mStack.addAll(mScreenFragments);
|
||||
|
||||
tryCommitTransaction();
|
||||
|
||||
if (mTopScreen != null) {
|
||||
setupBackHandlerIfNeeded(mTopScreen);
|
||||
}
|
||||
|
||||
for (ScreenStackFragment screen : mStack) {
|
||||
screen.onStackUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The below method sets up fragment manager's back stack in a way that it'd trigger our back
|
||||
* stack change listener when hw back button is clicked.
|
||||
*
|
||||
* Because back stack by default rolls back the transaction the stack entry is associated with we
|
||||
* generate a "fake" transaction that hides and shows the top fragment. As a result when back
|
||||
* stack entry is rolled back nothing happens and we are free to handle back navigation on our
|
||||
* own in `mBackStackListener`.
|
||||
*
|
||||
* We pop that "fake" transaction each time we update stack and we add a new one in case the top
|
||||
* screen is allowed to be dismised using hw back button. This way in the listener we can tell
|
||||
* if back button was pressed based on the count of the items on back stack. We expect 0 items
|
||||
* in case hw back is pressed becakse we try to keep the number of items at 1 by always resetting
|
||||
* and adding new items. In case we don't add a new item to back stack we remove listener so that
|
||||
* it does not get triggered.
|
||||
*
|
||||
* It is important that we don't install back handler when stack contains a single screen as in
|
||||
* that case we want the parent navigator or activity handler to take over.
|
||||
*/
|
||||
private void setupBackHandlerIfNeeded(ScreenStackFragment topScreen) {
|
||||
if (!mTopScreen.isResumed()) {
|
||||
// if the top fragment is not in a resumed state, adding back stack transaction would throw.
|
||||
// In such a case we skip installing back handler and use FragmentLifecycleCallbacks to get
|
||||
// notified when it gets resumed so that we can install the handler.
|
||||
return;
|
||||
}
|
||||
getFragmentManager().removeOnBackStackChangedListener(mBackStackListener);
|
||||
getFragmentManager().popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
ScreenStackFragment firstScreen = null;
|
||||
for (int i = 0, size = mStack.size(); i < size; i++) {
|
||||
ScreenStackFragment screen = mStack.get(i);
|
||||
if (!mDismissed.contains(screen)) {
|
||||
firstScreen = screen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (topScreen != firstScreen && topScreen.isDismissable()) {
|
||||
getFragmentManager()
|
||||
.beginTransaction()
|
||||
.hide(topScreen)
|
||||
.show(topScreen)
|
||||
.addToBackStack(BACK_STACK_TAG)
|
||||
.setPrimaryNavigationFragment(topScreen)
|
||||
.commitAllowingStateLoss();
|
||||
getFragmentManager().addOnBackStackChangedListener(mBackStackListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
|
||||
public class ScreenStackFragment extends ScreenFragment {
|
||||
|
||||
private static final float TOOLBAR_ELEVATION = PixelUtil.toPixelFromDIP(4);
|
||||
|
||||
private AppBarLayout mAppBarLayout;
|
||||
private Toolbar mToolbar;
|
||||
private boolean mShadowHidden;
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
public ScreenStackFragment(Screen screenView) {
|
||||
super(screenView);
|
||||
}
|
||||
|
||||
public void removeToolbar() {
|
||||
if (mAppBarLayout != null) {
|
||||
((CoordinatorLayout) getView()).removeView(mAppBarLayout);
|
||||
}
|
||||
}
|
||||
|
||||
public void setToolbar(Toolbar toolbar) {
|
||||
if (mAppBarLayout != null) {
|
||||
mAppBarLayout.addView(toolbar);
|
||||
}
|
||||
mToolbar = toolbar;
|
||||
AppBarLayout.LayoutParams params = new AppBarLayout.LayoutParams(
|
||||
AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT);
|
||||
params.setScrollFlags(0);
|
||||
mToolbar.setLayoutParams(params);
|
||||
}
|
||||
|
||||
public void setToolbarShadowHidden(boolean hidden) {
|
||||
if (mShadowHidden != hidden) {
|
||||
mAppBarLayout.setTargetElevation(hidden ? 0 : TOOLBAR_ELEVATION);
|
||||
mShadowHidden = hidden;
|
||||
}
|
||||
}
|
||||
|
||||
public void onStackUpdate() {
|
||||
View child = mScreenView.getChildAt(0);
|
||||
if (child instanceof ScreenStackHeaderConfig) {
|
||||
((ScreenStackHeaderConfig) child).onUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
CoordinatorLayout view = new CoordinatorLayout(getContext());
|
||||
CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
|
||||
params.setBehavior(new AppBarLayout.ScrollingViewBehavior());
|
||||
mScreenView.setLayoutParams(params);
|
||||
view.addView(mScreenView);
|
||||
|
||||
mAppBarLayout = new AppBarLayout(getContext());
|
||||
// By default AppBarLayout will have a background color set but since we cover the whole layout
|
||||
// with toolbar (that can be semi-transparent) the bar layout background color does not pay a
|
||||
// role. On top of that it breaks screens animations when alfa offscreen compositing is off
|
||||
// (which is the default)
|
||||
mAppBarLayout.setBackgroundColor(Color.TRANSPARENT);
|
||||
mAppBarLayout.setLayoutParams(new AppBarLayout.LayoutParams(
|
||||
AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT));
|
||||
view.addView(mAppBarLayout);
|
||||
|
||||
if (mToolbar != null) {
|
||||
mAppBarLayout.addView(mToolbar);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public boolean isDismissable() {
|
||||
View child = mScreenView.getChildAt(0);
|
||||
if (child instanceof ScreenStackHeaderConfig) {
|
||||
return ((ScreenStackHeaderConfig) child).isDismissable();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -10,73 +10,35 @@ import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.views.text.ReactFontManager;
|
||||
|
||||
public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
|
||||
private static final float TOOLBAR_ELEVATION = PixelUtil.toPixelFromDIP(4);
|
||||
|
||||
private static final class ToolbarWithLayoutLoop extends Toolbar {
|
||||
|
||||
private final Runnable mLayoutRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLayoutEnqueued = false;
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
|
||||
layout(getLeft(), getTop(), getRight(), getBottom());
|
||||
}
|
||||
};
|
||||
private boolean mLayoutEnqueued = false;
|
||||
|
||||
public ToolbarWithLayoutLoop(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
super.requestLayout();
|
||||
|
||||
if (!mLayoutEnqueued) {
|
||||
mLayoutEnqueued = true;
|
||||
post(mLayoutRunnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final ScreenStackHeaderSubview mConfigSubviews[] = new ScreenStackHeaderSubview[3];
|
||||
private int mSubviewsCount = 0;
|
||||
private String mTitle;
|
||||
private int mTitleColor;
|
||||
private String mTitleFontFamily;
|
||||
private int mTitleFontSize;
|
||||
private float mTitleFontSize;
|
||||
private int mBackgroundColor;
|
||||
private boolean mIsHidden;
|
||||
private boolean mGestureEnabled = true;
|
||||
private boolean mIsBackButtonHidden;
|
||||
private boolean mIsShadowHidden;
|
||||
private int mTintColor;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private final Toolbar mToolbar;
|
||||
|
||||
private OnBackPressedCallback mBackCallback = new OnBackPressedCallback(false) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
getScreenStack().dismiss(getScreen());
|
||||
}
|
||||
};
|
||||
private boolean mIsAttachedToWindow = false;
|
||||
|
||||
private OnClickListener mBackClickListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
getScreenStack().dismiss(getScreen());
|
||||
getScreenStack().dismiss(getScreenFragment());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,42 +46,31 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
super(context);
|
||||
setVisibility(View.GONE);
|
||||
|
||||
mToolbar = new ToolbarWithLayoutLoop(context);
|
||||
mToolbar = new Toolbar(context);
|
||||
|
||||
// set primary color as background by default
|
||||
TypedValue tv = new TypedValue();
|
||||
if (context.getTheme().resolveAttribute(android.R.attr.colorPrimary, tv, true)) {
|
||||
mToolbar.setBackgroundColor(tv.data);
|
||||
}
|
||||
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
|
||||
if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
|
||||
mHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateToolbarLayout() {
|
||||
mToolbar.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(mWidth, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(mHeight, View.MeasureSpec.EXACTLY));
|
||||
mToolbar.layout(0, 0, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
if (mWidth != (r - l)) {
|
||||
mWidth = (r - l);
|
||||
updateToolbarLayout();
|
||||
}
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
update();
|
||||
mIsAttachedToWindow = true;
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mIsAttachedToWindow = false;
|
||||
}
|
||||
|
||||
private Screen getScreen() {
|
||||
@@ -141,45 +92,48 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Fragment getScreenFragment() {
|
||||
private ScreenStackFragment getScreenFragment() {
|
||||
ViewParent screen = getParent();
|
||||
if (screen instanceof Screen) {
|
||||
return ((Screen) screen).getFragment();
|
||||
Fragment fragment = ((Screen) screen).getFragment();
|
||||
if (fragment instanceof ScreenStackFragment) {
|
||||
return (ScreenStackFragment) fragment;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void installBackCallback() {
|
||||
mBackCallback.remove();
|
||||
Fragment fragment = getScreenFragment();
|
||||
fragment.requireActivity().getOnBackPressedDispatcher().addCallback(fragment, mBackCallback);
|
||||
public boolean isDismissable() {
|
||||
return mGestureEnabled;
|
||||
}
|
||||
|
||||
private void update() {
|
||||
public void onUpdate() {
|
||||
Screen parent = (Screen) getParent();
|
||||
final ScreenStack stack = getScreenStack();
|
||||
boolean isRoot = stack == null ? true : stack.getRootScreen() == parent;
|
||||
boolean isTop = stack == null ? true : stack.getTopScreen() == parent;
|
||||
|
||||
if (!mIsAttachedToWindow || !isTop) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsHidden) {
|
||||
if (mToolbar.getParent() != null) {
|
||||
parent.removeView(mToolbar);
|
||||
getScreenFragment().removeToolbar();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mToolbar.getParent() == null) {
|
||||
parent.addView(mToolbar);
|
||||
getScreenFragment().setToolbar(mToolbar);
|
||||
}
|
||||
|
||||
AppCompatActivity activity = (AppCompatActivity) parent.getFragment().getActivity();
|
||||
AppCompatActivity activity = (AppCompatActivity) getScreenFragment().getActivity();
|
||||
activity.setSupportActionBar(mToolbar);
|
||||
ActionBar actionBar = activity.getSupportActionBar();
|
||||
|
||||
// hide back button
|
||||
final ScreenStack stack = getScreenStack();
|
||||
boolean isRoot = stack == null ? true : stack.getRootScreen() == parent;
|
||||
actionBar.setDisplayHomeAsUpEnabled(isRoot ? false : !mIsBackButtonHidden);
|
||||
if (!isRoot) {
|
||||
installBackCallback();
|
||||
}
|
||||
mBackCallback.setEnabled(!isRoot);
|
||||
|
||||
// when setSupportActionBar is called a toolbar wrapper gets initialized that overwrites
|
||||
// navigation click listener. The default behavior set in the wrapper is to call into
|
||||
@@ -188,7 +142,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
|
||||
|
||||
// shadow
|
||||
actionBar.setElevation(mIsShadowHidden ? 0 : TOOLBAR_ELEVATION);
|
||||
getScreenFragment().setToolbarShadowHidden(mIsShadowHidden);
|
||||
|
||||
// title
|
||||
actionBar.setTitle(mTitle);
|
||||
@@ -253,6 +207,12 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdate() {
|
||||
if (getParent() != null) {
|
||||
onUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public ScreenStackHeaderSubview getConfigSubview(int index) {
|
||||
return mConfigSubviews[index];
|
||||
}
|
||||
@@ -266,6 +226,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
mSubviewsCount--;
|
||||
}
|
||||
mConfigSubviews[index] = null;
|
||||
maybeUpdate();
|
||||
}
|
||||
|
||||
public void addConfigSubview(ScreenStackHeaderSubview child, int index) {
|
||||
@@ -273,6 +234,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
mSubviewsCount++;
|
||||
}
|
||||
mConfigSubviews[index] = child;
|
||||
maybeUpdate();
|
||||
}
|
||||
|
||||
private TextView getTitleTextView() {
|
||||
@@ -296,7 +258,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
mTitleFontFamily = titleFontFamily;
|
||||
}
|
||||
|
||||
public void setTitleFontSize(int titleFontSize) {
|
||||
public void setTitleFontSize(float titleFontSize) {
|
||||
mTitleFontSize = titleFontSize;
|
||||
}
|
||||
|
||||
@@ -316,6 +278,10 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
mIsShadowHidden = hideShadow;
|
||||
}
|
||||
|
||||
public void setGestureEnabled(boolean gestureEnabled) {
|
||||
mGestureEnabled = gestureEnabled;
|
||||
}
|
||||
|
||||
public void setHideBackButton(boolean hideBackButton) {
|
||||
mIsBackButtonHidden = hideBackButton;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenS
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAfterUpdateTransaction(ScreenStackHeaderConfig parent) {
|
||||
super.onAfterUpdateTransaction(parent);
|
||||
parent.onUpdate();
|
||||
}
|
||||
|
||||
@ReactProp(name = "title")
|
||||
public void setTitle(ScreenStackHeaderConfig config, String title) {
|
||||
config.setTitle(title);
|
||||
@@ -63,8 +69,8 @@ public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenS
|
||||
}
|
||||
|
||||
@ReactProp(name = "titleFontSize")
|
||||
public void setTitleFontSize(ScreenStackHeaderConfig config, double titleFontSizeSP) {
|
||||
config.setTitleFontSize((int) PixelUtil.toPixelFromSP(titleFontSizeSP));
|
||||
public void setTitleFontSize(ScreenStackHeaderConfig config, float titleFontSize) {
|
||||
config.setTitleFontSize(titleFontSize);
|
||||
}
|
||||
|
||||
@ReactProp(name = "titleColor", customType = "Color")
|
||||
@@ -82,6 +88,11 @@ public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenS
|
||||
config.setHideShadow(hideShadow);
|
||||
}
|
||||
|
||||
@ReactProp(name = "gestureEnabled", defaultBoolean = true)
|
||||
public void setGestureEnabled(ScreenStackHeaderConfig config, boolean gestureEnabled) {
|
||||
config.setGestureEnabled(gestureEnabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "hideBackButton")
|
||||
public void setHideBackButton(ScreenStackHeaderConfig config, boolean hideBackButton) {
|
||||
config.setHideBackButton(hideBackButton);
|
||||
|
||||
@@ -59,4 +59,9 @@ public class ScreenStackViewManager extends ViewGroupManager<ScreenStack> {
|
||||
public View getChildAt(ScreenStack parent, int index) {
|
||||
return parent.getScreenAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsCustomLayoutForChildren() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,11 @@ public class ScreenViewManager extends ViewGroupManager<Screen> {
|
||||
public void setStackPresentation(Screen view, String presentation) {
|
||||
if ("push".equals(presentation)) {
|
||||
view.setStackPresentation(Screen.StackPresentation.PUSH);
|
||||
} else if ("modal".equals(presentation)) {
|
||||
} else if ("modal".equals(presentation) || "containedModal".equals(presentation)) {
|
||||
// at the moment Android implementation does not handle contained vs regular modals
|
||||
view.setStackPresentation(Screen.StackPresentation.MODAL);
|
||||
} else if ("transparentModal".equals(presentation)) {
|
||||
} else if ("transparentModal".equals(presentation) || "containedTransparentModal".equals((presentation))) {
|
||||
// at the moment Android implementation does not handle contained vs regular modals
|
||||
view.setStackPresentation(Screen.StackPresentation.TRANSPARENT_MODAL);
|
||||
} else {
|
||||
throw new JSApplicationIllegalArgumentException("Unknown presentation type " + presentation);
|
||||
|
||||
@@ -18,6 +18,13 @@ import {
|
||||
ScreenStackHeaderTitleView,
|
||||
} from 'react-native-screens';
|
||||
|
||||
function renderComponentOrThunk(componentOrThunk, props) {
|
||||
if (typeof componentOrThunk === 'function') {
|
||||
return componentOrThunk(props);
|
||||
}
|
||||
return componentOrThunk;
|
||||
}
|
||||
|
||||
class StackView extends React.Component {
|
||||
_removeScene = route => {
|
||||
const { navigation } = this.props;
|
||||
@@ -41,10 +48,13 @@ class StackView extends React.Component {
|
||||
headerTitleStyle,
|
||||
headerBackTitleStyle,
|
||||
headerBackTitle,
|
||||
headerBackTitleVisible,
|
||||
headerTintColor,
|
||||
gestureEnabled,
|
||||
largeTitle,
|
||||
headerLargeTitleStyle,
|
||||
translucent,
|
||||
hideShadow,
|
||||
} = options;
|
||||
|
||||
const scene = {
|
||||
@@ -60,13 +70,18 @@ class StackView extends React.Component {
|
||||
titleFontFamily: headerTitleStyle && headerTitleStyle.fontFamily,
|
||||
titleColor: headerTintColor,
|
||||
titleFontSize: headerTitleStyle && headerTitleStyle.fontSize,
|
||||
backTitle: headerBackTitle,
|
||||
backTitle: headerBackTitleVisible === false ? '' : headerBackTitle,
|
||||
backTitleFontFamily:
|
||||
headerBackTitleStyle && headerBackTitleStyle.fontFamily,
|
||||
backTitleFontSize: headerBackTitleStyle && headerBackTitleStyle.fontSize,
|
||||
color: headerTintColor,
|
||||
gestureEnabled: gestureEnabled === undefined ? true : gestureEnabled,
|
||||
largeTitle,
|
||||
largeTitleFontFamily:
|
||||
headerLargeTitleStyle && headerLargeTitleStyle.fontFamily,
|
||||
largeTitleFontSize:
|
||||
headerLargeTitleStyle && headerLargeTitleStyle.fontSize,
|
||||
hideShadow,
|
||||
};
|
||||
|
||||
const hasHeader = headerMode !== 'none' && options.header !== null;
|
||||
@@ -83,7 +98,7 @@ class StackView extends React.Component {
|
||||
if (options.headerLeft !== undefined) {
|
||||
children.push(
|
||||
<ScreenStackHeaderLeftView key="left">
|
||||
{options.headerLeft({ scene })}
|
||||
{renderComponentOrThunk(options.headerLeft, { scene })}
|
||||
</ScreenStackHeaderLeftView>
|
||||
);
|
||||
} else if (options.headerBackImage !== undefined) {
|
||||
@@ -113,17 +128,21 @@ class StackView extends React.Component {
|
||||
}
|
||||
|
||||
if (options.headerTitle) {
|
||||
children.push(
|
||||
<ScreenStackHeaderTitleView key="title">
|
||||
{options.headerTitle({ scene })}
|
||||
</ScreenStackHeaderTitleView>
|
||||
);
|
||||
if (title === undefined && typeof options.headerTitle === 'string') {
|
||||
headerOptions.title = options.headerTitle;
|
||||
} else {
|
||||
children.push(
|
||||
<ScreenStackHeaderTitleView key="title">
|
||||
{renderComponentOrThunk(options.headerTitle, { scene })}
|
||||
</ScreenStackHeaderTitleView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.headerRight) {
|
||||
children.push(
|
||||
<ScreenStackHeaderRightView key="right">
|
||||
{options.headerRight({ scene })}
|
||||
{renderComponentOrThunk(options.headerRight, { scene })}
|
||||
</ScreenStackHeaderRightView>
|
||||
);
|
||||
}
|
||||
@@ -136,20 +155,21 @@ class StackView extends React.Component {
|
||||
};
|
||||
|
||||
_renderScene = (index, route, descriptor) => {
|
||||
const { navigation, getComponent } = descriptor;
|
||||
const { navigation, getComponent, options } = descriptor;
|
||||
const { mode, transparentCard } = this.props.navigationConfig;
|
||||
const SceneComponent = getComponent();
|
||||
|
||||
let stackPresentation = 'push';
|
||||
if (mode === 'modal') {
|
||||
stackPresentation = transparentCard ? 'transparentModal' : 'modal';
|
||||
if (mode === 'modal' || mode === 'containedModal') {
|
||||
stackPresentation =
|
||||
transparentCard || options.cardTransparent ? 'transparentModal' : mode;
|
||||
}
|
||||
|
||||
const { screenProps } = this.props;
|
||||
return (
|
||||
<Screen
|
||||
key={`screen_${route.key}`}
|
||||
style={StyleSheet.absoluteFill}
|
||||
style={options.cardStyle}
|
||||
stackPresentation={stackPresentation}
|
||||
onDismissed={() => this._removeScene(route)}>
|
||||
{this._renderHeaderConfig(index, route, descriptor)}
|
||||
|
||||
@@ -17,6 +17,7 @@ typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {
|
||||
RNSScreenStackAnimationDefault,
|
||||
RNSScreenStackAnimationNone,
|
||||
RNSScreenStackAnimationFade,
|
||||
RNSScreenStackAnimationFlip,
|
||||
};
|
||||
|
||||
@interface RCTConvert (RNSScreen)
|
||||
@@ -26,6 +27,13 @@ typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {
|
||||
|
||||
@end
|
||||
|
||||
@interface RNSScreen : UIViewController
|
||||
|
||||
- (instancetype)initWithView:(UIView *)view;
|
||||
- (void)notifyFinishTransitioning;
|
||||
|
||||
@end
|
||||
|
||||
@interface RNSScreenManager : RCTViewManager
|
||||
@end
|
||||
|
||||
|
||||
119
ios/RNSScreen.m
119
ios/RNSScreen.m
@@ -8,40 +8,6 @@
|
||||
#import <React/RCTShadowView.h>
|
||||
#import <React/RCTTouchHandler.h>
|
||||
|
||||
@interface RNSScreenFrameData : NSObject
|
||||
@property (nonatomic, readonly) CGFloat rightInset;
|
||||
@property (nonatomic, readonly) CGFloat topInset;
|
||||
@property (nonatomic, readonly) CGFloat bottomInset;
|
||||
@property (nonatomic, readonly) CGFloat leftInset;
|
||||
@property (nonatomic, readonly) CGFloat navbarOffset;
|
||||
|
||||
- (instancetype)initWithInsets:(UIEdgeInsets)insets;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RNSScreenFrameData
|
||||
|
||||
- (instancetype)initWithInsets:(UIEdgeInsets)insets andNavbarOffset:(CGFloat)navbarOffset
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_topInset = insets.top;
|
||||
_bottomInset = insets.bottom;
|
||||
_leftInset = insets.left;
|
||||
_rightInset = insets.right;
|
||||
_navbarOffset = navbarOffset;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RNSScreen : UIViewController
|
||||
|
||||
- (instancetype)initWithView:(UIView *)view;
|
||||
- (void)notifyFinishTransitioning;
|
||||
|
||||
@end
|
||||
|
||||
@interface RNSScreenView () <UIAdaptivePresentationControllerDelegate>
|
||||
@end
|
||||
|
||||
@@ -58,7 +24,6 @@
|
||||
if (self = [super init]) {
|
||||
_bridge = bridge;
|
||||
_controller = [[RNSScreen alloc] initWithView:self];
|
||||
_controller.presentationController.delegate = self;
|
||||
_stackPresentation = RNSScreenStackPresentationPush;
|
||||
_stackAnimation = RNSScreenStackAnimationDefault;
|
||||
}
|
||||
@@ -66,20 +31,15 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reactSetFrame:(CGRect)frame
|
||||
{
|
||||
// ignore setFrame call from react, the frame of this view
|
||||
// is controlled by the UIViewController it is contained in
|
||||
}
|
||||
|
||||
- (void)updateBounds
|
||||
{
|
||||
CGFloat navbarOffset = 0;
|
||||
UINavigationController *navctr = self.controller.navigationController;
|
||||
if (!navctr.isNavigationBarHidden && !navctr.navigationBar.isTranslucent) {
|
||||
CGRect navbarFrame = navctr.navigationBar.frame;
|
||||
navbarOffset = navbarFrame.origin.y + navbarFrame.size.height;
|
||||
}
|
||||
|
||||
[_bridge.uiManager
|
||||
setLocalData:[[RNSScreenFrameData alloc]
|
||||
initWithInsets:UIEdgeInsetsZero
|
||||
andNavbarOffset:navbarOffset]
|
||||
forView:self];
|
||||
[_bridge.uiManager setSize:self.bounds.size forView:self];
|
||||
}
|
||||
|
||||
- (void)setActive:(BOOL)active
|
||||
@@ -121,6 +81,28 @@
|
||||
_controller.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
break;
|
||||
}
|
||||
// `modalPresentationStyle` must be set before accessing `presentationController`
|
||||
// otherwise a default controller will be created and cannot be changed after.
|
||||
// Documented here: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621426-presentationcontroller?language=objc
|
||||
_controller.presentationController.delegate = self;
|
||||
}
|
||||
|
||||
- (void)setStackAnimation:(RNSScreenStackAnimation)stackAnimation
|
||||
{
|
||||
_stackAnimation = stackAnimation;
|
||||
|
||||
switch (stackAnimation) {
|
||||
case RNSScreenStackAnimationFade:
|
||||
_controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
break;
|
||||
case RNSScreenStackAnimationFlip:
|
||||
_controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
|
||||
break;
|
||||
case RNSScreenStackAnimationNone:
|
||||
case RNSScreenStackAnimationDefault:
|
||||
// Default
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIView *)reactSuperview
|
||||
@@ -132,6 +114,8 @@
|
||||
{
|
||||
if (![view isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
|
||||
[super addSubview:view];
|
||||
} else {
|
||||
((RNSScreenStackHeaderConfig*) view).screenView = self;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +181,7 @@
|
||||
@implementation RNSScreen {
|
||||
__weak UIView *_view;
|
||||
__weak id _previousFirstResponder;
|
||||
CGRect _lastViewFrame;
|
||||
}
|
||||
|
||||
- (instancetype)initWithView:(UIView *)view
|
||||
@@ -207,6 +192,16 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews
|
||||
{
|
||||
[super viewDidLayoutSubviews];
|
||||
|
||||
if (!CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
|
||||
_lastViewFrame = self.view.frame;
|
||||
[((RNSScreenView *)self.view) updateBounds];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)findFirstResponder:(UIView*)parent
|
||||
{
|
||||
if (parent.isFirstResponder) {
|
||||
@@ -240,10 +235,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void) viewDidAppear:(BOOL)animated {
|
||||
[((RNSScreenView *)self.view) updateBounds];
|
||||
}
|
||||
|
||||
- (void)notifyFinishTransitioning
|
||||
{
|
||||
[_previousFirstResponder becomeFirstResponder];
|
||||
@@ -260,23 +251,6 @@
|
||||
|
||||
@end
|
||||
|
||||
@interface RNSScreenShadowView : RCTShadowView
|
||||
@end
|
||||
|
||||
@implementation RNSScreenShadowView
|
||||
|
||||
- (void)setLocalData:(RNSScreenFrameData *)data
|
||||
{
|
||||
self.paddingTop = (YGValue){data.topInset, YGUnitPoint};
|
||||
self.paddingBottom = (YGValue){data.bottomInset, YGUnitPoint};
|
||||
self.paddingLeft = (YGValue){data.leftInset, YGUnitPoint};
|
||||
self.paddingRight = (YGValue){data.rightInset, YGUnitPoint};
|
||||
self.top = (YGValue){data.navbarOffset, YGUnitPoint};
|
||||
[self didSetProps:@[@"paddingTop", @"paddingBottom", @"paddingLeft", @"paddingRight", @"top"]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RNSScreenManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
@@ -291,11 +265,6 @@ RCT_EXPORT_VIEW_PROPERTY(onDismissed, RCTDirectEventBlock);
|
||||
return [[RNSScreenView alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return [RNSScreenShadowView new];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTConvert (RNSScreen)
|
||||
@@ -311,8 +280,10 @@ RCT_ENUM_CONVERTER(RNSScreenStackPresentation, (@{
|
||||
RCT_ENUM_CONVERTER(RNSScreenStackAnimation, (@{
|
||||
@"default": @(RNSScreenStackAnimationDefault),
|
||||
@"none": @(RNSScreenStackAnimationNone),
|
||||
@"fade": @(RNSScreenStackAnimationFade)
|
||||
@"fade": @(RNSScreenStackAnimationFade),
|
||||
@"flip": @(RNSScreenStackAnimationFlip),
|
||||
}), RNSScreenStackAnimationDefault, integerValue)
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
{
|
||||
subview.reactSuperview = self;
|
||||
[_reactSubviews insertObject:subview atIndex:atIndex];
|
||||
subview.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(RNSScreenView *)subview
|
||||
@@ -159,6 +160,10 @@
|
||||
[super layoutSubviews];
|
||||
[self reactAddControllerToClosestParent:_controller];
|
||||
_controller.view.frame = self.bounds;
|
||||
for (RNSScreenView *subview in _reactSubviews) {
|
||||
subview.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
|
||||
[subview setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTUIManagerUtils.h>
|
||||
#import <React/RCTShadowView.h>
|
||||
#import <React/RCTRootContentView.h>
|
||||
#import <React/RCTTouchHandler.h>
|
||||
|
||||
@interface RNSScreenStackView () <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
|
||||
@end
|
||||
@@ -35,6 +37,12 @@
|
||||
_needUpdate = NO;
|
||||
[self addSubview:_controller.view];
|
||||
_controller.interactivePopGestureRecognizer.delegate = self;
|
||||
|
||||
// we have to initialize viewControllers with a non empty array for
|
||||
// largeTitle header to render in the opened state. If it is empty
|
||||
// the header will render in collapsed state which is perhaps a bug
|
||||
// in UIKit but ¯\_(ツ)_/¯
|
||||
[_controller setViewControllers:@[[UIViewController new]]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -58,7 +66,6 @@
|
||||
if ([viewController isEqual:[_reactSubviews objectAtIndex:i - 1].controller]) {
|
||||
break;
|
||||
} else {
|
||||
// TODO: send dismiss event
|
||||
[_dismissedScreens addObject:[_reactSubviews objectAtIndex:i - 1]];
|
||||
}
|
||||
}
|
||||
@@ -72,7 +79,7 @@
|
||||
} else if (operation == UINavigationControllerOperationPop) {
|
||||
screen = (RNSScreenView *) fromVC.view;
|
||||
}
|
||||
if (screen != nil && screen.stackAnimation != RNSScreenStackAnimationDefault) {
|
||||
if (screen != nil && (screen.stackAnimation == RNSScreenStackAnimationFade || screen.stackAnimation == RNSScreenStackAnimationNone)) {
|
||||
return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
|
||||
}
|
||||
return nil;
|
||||
@@ -85,30 +92,13 @@
|
||||
// Without the below code the Touchable will remain active (highlighted) for the duration of back
|
||||
// gesture and onPress may fire when we release the finger.
|
||||
UIView *parent = _controller.view;
|
||||
while (parent != nil && ![parent isKindOfClass:[RCTRootView class]]) parent = parent.superview;
|
||||
RCTRootView *rootView = (RCTRootView *)parent;
|
||||
[rootView cancelTouches];
|
||||
while (parent != nil && ![parent isKindOfClass:[RCTRootContentView class]]) parent = parent.superview;
|
||||
RCTRootContentView *rootView = (RCTRootContentView *)parent;
|
||||
[rootView.touchHandler cancel];
|
||||
|
||||
return _controller.viewControllers.count > 1;
|
||||
}
|
||||
|
||||
- (void)markUpdated
|
||||
{
|
||||
// We want 'updateContainer' to be executed on main thread after all enqueued operations in
|
||||
// uimanager are complete. In order to achieve that we enqueue call on UIManagerQueue from which
|
||||
// we enqueue call on the main queue. This seems to be working ok in all the cases I've tried but
|
||||
// there is a chance it is not the correct way to do that.
|
||||
if (!_needUpdate) {
|
||||
_needUpdate = YES;
|
||||
RCTExecuteOnUIManagerQueue(^{
|
||||
RCTExecuteOnMainQueue(^{
|
||||
_needUpdate = NO;
|
||||
[self updateContainer];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)markChildUpdated
|
||||
{
|
||||
// do nothing
|
||||
@@ -126,14 +116,12 @@
|
||||
return;
|
||||
}
|
||||
[_reactSubviews insertObject:subview atIndex:atIndex];
|
||||
[self markUpdated];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(RNSScreenView *)subview
|
||||
{
|
||||
[_reactSubviews removeObject:subview];
|
||||
[_dismissedScreens removeObject:subview];
|
||||
[self markUpdated];
|
||||
}
|
||||
|
||||
- (NSArray<UIView *> *)reactSubviews
|
||||
@@ -144,6 +132,7 @@
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// do nothing
|
||||
[self updateContainer];
|
||||
}
|
||||
|
||||
- (void)setModalViewControllers:(NSArray<UIViewController *> *)controllers
|
||||
@@ -186,11 +175,17 @@
|
||||
UIViewController *top = controllers.lastObject;
|
||||
UIViewController *lastTop = _controller.viewControllers.lastObject;
|
||||
|
||||
BOOL shouldAnimate = ((RNSScreenView *) lastTop.view).stackAnimation != RNSScreenStackAnimationNone;
|
||||
// at the start we set viewControllers to contain a single UIVIewController
|
||||
// instance. This is a workaround for header height adjustment bug (see comment
|
||||
// in the init function). Here, we need to detect if the initial empty
|
||||
// controller is still there
|
||||
BOOL firstTimePush = ![lastTop isKindOfClass:[RNSScreen class]];
|
||||
|
||||
if (_controller.viewControllers.count == 0) {
|
||||
BOOL shouldAnimate = !firstTimePush && ((RNSScreenView *) lastTop.view).stackAnimation != RNSScreenStackAnimationNone;
|
||||
|
||||
if (firstTimePush) {
|
||||
// nothing pushed yet
|
||||
[_controller setViewControllers:@[top] animated:NO];
|
||||
[_controller setViewControllers:controllers animated:NO];
|
||||
} else if (top != lastTop) {
|
||||
if (![controllers containsObject:lastTop]) {
|
||||
// last top controller is no longer on stack
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#import <React/RCTViewManager.h>
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
#import "RNSScreen.h"
|
||||
|
||||
@interface RNSScreenStackHeaderConfig : UIView
|
||||
|
||||
@property (nonatomic, weak) RNSScreenView *screenView;
|
||||
|
||||
@property (nonatomic, retain) NSString *title;
|
||||
@property (nonatomic, retain) NSString *titleFontFamily;
|
||||
@property (nonatomic, retain) NSNumber *titleFontSize;
|
||||
@@ -14,6 +18,8 @@
|
||||
@property (nonatomic, retain) UIColor *color;
|
||||
@property (nonatomic) BOOL hide;
|
||||
@property (nonatomic) BOOL largeTitle;
|
||||
@property (nonatomic, retain) NSString *largeTitleFontFamily;
|
||||
@property (nonatomic, retain) NSNumber *largeTitleFontSize;
|
||||
@property (nonatomic) BOOL hideBackButton;
|
||||
@property (nonatomic) BOOL hideShadow;
|
||||
@property (nonatomic) BOOL translucent;
|
||||
|
||||
@@ -66,47 +66,91 @@
|
||||
return _reactSubviews;
|
||||
}
|
||||
|
||||
- (UIViewController*)screen
|
||||
- (UIView *)reactSuperview
|
||||
{
|
||||
UIView *superview = self.superview;
|
||||
if ([superview isKindOfClass:[RNSScreenView class]]) {
|
||||
return ((RNSScreenView *)superview).controller;
|
||||
return _screenView;
|
||||
}
|
||||
|
||||
- (void)removeFromSuperview
|
||||
{
|
||||
[super removeFromSuperview];
|
||||
_screenView = nil;
|
||||
}
|
||||
|
||||
- (void)updateViewControllerIfNeeded
|
||||
{
|
||||
UIViewController *vc = _screenView.controller;
|
||||
UINavigationController *nav = (UINavigationController*) vc.parentViewController;
|
||||
if (vc != nil && nav.visibleViewController == vc) {
|
||||
[RNSScreenStackHeaderConfig updateViewController:self.screenView.controller withConfig:self];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
||||
{
|
||||
[super didSetProps:changedProps];
|
||||
[self updateViewControllerIfNeeded];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
[super didUpdateReactSubviews];
|
||||
[self updateViewControllerIfNeeded];
|
||||
}
|
||||
|
||||
+ (void)setAnimatedConfig:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
|
||||
{
|
||||
UINavigationBar *navbar = ((UINavigationController *)vc.parentViewController).navigationBar;
|
||||
BOOL hideShadow = config.hideShadow;
|
||||
[navbar setTintColor:config.color];
|
||||
if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
|
||||
[navbar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
|
||||
[navbar setBarTintColor:[UIColor clearColor]];
|
||||
hideShadow = YES;
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// font customized on the navigation item level, so nothing to do here
|
||||
} else {
|
||||
[navbar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
|
||||
[navbar setBarTintColor:config.backgroundColor];
|
||||
}
|
||||
[navbar setTranslucent:config.translucent];
|
||||
[navbar setValue:@(hideShadow ? YES : NO) forKey:@"hidesShadow"];
|
||||
BOOL hideShadow = config.hideShadow;
|
||||
|
||||
if (config.titleFontFamily || config.titleFontSize || config.titleColor) {
|
||||
NSMutableDictionary *attrs = [NSMutableDictionary new];
|
||||
|
||||
if (config.titleColor) {
|
||||
attrs[NSForegroundColorAttributeName] = config.titleColor;
|
||||
}
|
||||
|
||||
CGFloat size = config.titleFontSize ? [config.titleFontSize floatValue] : 17;
|
||||
if (config.titleFontFamily) {
|
||||
attrs[NSFontAttributeName] = [UIFont fontWithName:config.titleFontFamily size:size];
|
||||
if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
|
||||
[navbar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
|
||||
[navbar setBarTintColor:[UIColor clearColor]];
|
||||
hideShadow = YES;
|
||||
} else {
|
||||
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:size];
|
||||
[navbar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
|
||||
[navbar setBarTintColor:config.backgroundColor];
|
||||
}
|
||||
[navbar setTitleTextAttributes:attrs];
|
||||
}
|
||||
[navbar setTranslucent:config.translucent];
|
||||
[navbar setValue:@(hideShadow ? YES : NO) forKey:@"hidesShadow"];
|
||||
|
||||
if (config.titleFontFamily || config.titleFontSize || config.titleColor) {
|
||||
NSMutableDictionary *attrs = [NSMutableDictionary new];
|
||||
|
||||
if (config.titleColor) {
|
||||
attrs[NSForegroundColorAttributeName] = config.titleColor;
|
||||
}
|
||||
|
||||
CGFloat size = config.titleFontSize ? [config.titleFontSize floatValue] : 17;
|
||||
if (config.titleFontFamily) {
|
||||
attrs[NSFontAttributeName] = [UIFont fontWithName:config.titleFontFamily size:size];
|
||||
} else {
|
||||
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:size];
|
||||
}
|
||||
[navbar setTitleTextAttributes:attrs];
|
||||
}
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
if (config.largeTitle && (config.largeTitleFontFamily || config.largeTitleFontSize || config.titleColor)) {
|
||||
NSMutableDictionary *largeAttrs = [NSMutableDictionary new];
|
||||
if (config.titleColor) {
|
||||
largeAttrs[NSForegroundColorAttributeName] = config.titleColor;
|
||||
}
|
||||
CGFloat largeSize = config.largeTitleFontSize ? [config.largeTitleFontSize floatValue] : 34;
|
||||
if (config.largeTitleFontFamily) {
|
||||
largeAttrs[NSFontAttributeName] = [UIFont fontWithName:config.largeTitleFontFamily size:largeSize];
|
||||
} else {
|
||||
largeAttrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:largeSize];
|
||||
}
|
||||
[navbar setLargeTitleTextAttributes:largeAttrs];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)setTitleAttibutes:(NSDictionary *)attrs forButton:(UIBarButtonItem *)button
|
||||
@@ -121,6 +165,11 @@
|
||||
}
|
||||
|
||||
+ (void)willShowViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
|
||||
{
|
||||
[self updateViewController:vc withConfig:config];
|
||||
}
|
||||
|
||||
+ (void)updateViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
|
||||
{
|
||||
UINavigationItem *navitem = vc.navigationItem;
|
||||
UINavigationController *navctr = (UINavigationController *)vc.parentViewController;
|
||||
@@ -131,10 +180,21 @@
|
||||
BOOL wasHidden = navctr.navigationBarHidden;
|
||||
BOOL shouldHide = config == nil || config.hide;
|
||||
|
||||
if (!shouldHide && !config.translucent) {
|
||||
// when nav bar is not translucent we chage edgesForExtendedLayout to avoid system laying out
|
||||
// the screen underneath navigation controllers
|
||||
vc.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
} else {
|
||||
// system default is UIRectEdgeAll
|
||||
vc.edgesForExtendedLayout = UIRectEdgeAll;
|
||||
}
|
||||
|
||||
[navctr setNavigationBarHidden:shouldHide animated:YES];
|
||||
navctr.interactivePopGestureRecognizer.enabled = config.gestureEnabled;
|
||||
#ifdef __IPHONE_13_0
|
||||
vc.modalInPresentation = !config.gestureEnabled;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
vc.modalInPresentation = !config.gestureEnabled;
|
||||
}
|
||||
#endif
|
||||
if (shouldHide) {
|
||||
return;
|
||||
@@ -168,7 +228,76 @@
|
||||
}
|
||||
navitem.largeTitleDisplayMode = config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;
|
||||
}
|
||||
#ifdef __IPHONE_13_0
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
|
||||
|
||||
if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
|
||||
// transparent background color
|
||||
[appearance configureWithTransparentBackground];
|
||||
} else {
|
||||
// non-transparent background or default background
|
||||
if (config.translucent) {
|
||||
[appearance configureWithDefaultBackground];
|
||||
} else {
|
||||
[appearance configureWithOpaqueBackground];
|
||||
}
|
||||
|
||||
// set background color if specified
|
||||
if (config.backgroundColor) {
|
||||
appearance.backgroundColor = config.backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
|
||||
appearance.backgroundColor = config.backgroundColor;
|
||||
}
|
||||
|
||||
if (config.hideShadow) {
|
||||
appearance.shadowColor = nil;
|
||||
}
|
||||
|
||||
if (config.titleFontFamily || config.titleFontSize || config.titleColor) {
|
||||
NSMutableDictionary *attrs = [NSMutableDictionary new];
|
||||
|
||||
if (config.titleColor) {
|
||||
attrs[NSForegroundColorAttributeName] = config.titleColor;
|
||||
}
|
||||
|
||||
CGFloat size = config.titleFontSize ? [config.titleFontSize floatValue] : 17;
|
||||
if (config.titleFontFamily) {
|
||||
attrs[NSFontAttributeName] = [UIFont fontWithName:config.titleFontFamily size:size];
|
||||
} else {
|
||||
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:size];
|
||||
}
|
||||
appearance.titleTextAttributes = attrs;
|
||||
}
|
||||
|
||||
if (config.largeTitleFontFamily || config.largeTitleFontSize || config.titleColor) {
|
||||
NSMutableDictionary *largeAttrs = [NSMutableDictionary new];
|
||||
|
||||
if (config.titleColor) {
|
||||
largeAttrs[NSForegroundColorAttributeName] = config.titleColor;
|
||||
}
|
||||
|
||||
CGFloat largeSize = config.largeTitleFontSize ? [config.largeTitleFontSize floatValue] : 34;
|
||||
if (config.largeTitleFontFamily) {
|
||||
largeAttrs[NSFontAttributeName] = [UIFont fontWithName:config.largeTitleFontFamily size:largeSize];
|
||||
} else {
|
||||
largeAttrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:largeSize];
|
||||
}
|
||||
|
||||
appearance.largeTitleTextAttributes = largeAttrs;
|
||||
}
|
||||
|
||||
navitem.standardAppearance = appearance;
|
||||
navitem.compactAppearance = appearance;
|
||||
navitem.scrollEdgeAppearance = appearance;
|
||||
}
|
||||
#endif
|
||||
navitem.leftBarButtonItem = nil;
|
||||
navitem.rightBarButtonItem = nil;
|
||||
navitem.titleView = nil;
|
||||
for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
|
||||
switch (subview.type) {
|
||||
case RNSScreenStackHeaderSubviewTypeLeft: {
|
||||
@@ -235,6 +364,8 @@ RCT_EXPORT_VIEW_PROPERTY(backTitleFontSize, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(largeTitle, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(largeTitleFontFamily, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(largeTitleFontSize, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(hideBackButton, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(hideShadow, BOOL)
|
||||
// `hidden` is an UIView property, we need to use different name internally
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-screens",
|
||||
"version": "2.0.0-alpha.4",
|
||||
"version": "2.0.0-alpha.15",
|
||||
"description": "First incomplete navigation solution for your react-native app.",
|
||||
"scripts": {
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
|
||||
1
src/screens.d.ts
vendored
1
src/screens.d.ts
vendored
@@ -6,6 +6,7 @@ declare module 'react-native-screens' {
|
||||
import { ViewProps, Animated } from 'react-native';
|
||||
|
||||
export function useScreens(shouldUseScreens?: boolean): void;
|
||||
export function enableScreens(shouldEnableScreens?: boolean): void;
|
||||
export function screensEnabled(): boolean;
|
||||
|
||||
export interface ScreenProps extends ViewProps {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from 'react-native';
|
||||
import { version } from 'react-native/Libraries/Core/ReactNativeVersion';
|
||||
|
||||
let USE_SCREENS = false;
|
||||
let ENABLE_SCREENS = false;
|
||||
|
||||
// UIManager[`${moduleName}`] is deprecated in RN 0.58 and `getViewManagerConfig` is added.
|
||||
// We can remove this when we drop support for RN < 0.58.
|
||||
@@ -17,17 +17,23 @@ const getViewManagerConfigCompat = name =>
|
||||
? UIManager.getViewManagerConfig(name)
|
||||
: UIManager[name];
|
||||
|
||||
function useScreens(shouldUseScreens = true) {
|
||||
USE_SCREENS = shouldUseScreens;
|
||||
if (USE_SCREENS && !getViewManagerConfigCompat('RNSScreen')) {
|
||||
function enableScreens(shouldEnableScreens = true) {
|
||||
ENABLE_SCREENS = shouldEnableScreens;
|
||||
if (ENABLE_SCREENS && !getViewManagerConfigCompat('RNSScreen')) {
|
||||
console.error(
|
||||
`Screen native module hasn't been linked. Please check the react-native-screens README for more details`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// we should remove this at some point
|
||||
function useScreens(shouldUseScreens = true) {
|
||||
console.warn('Method `useScreens` is deprecated, please use `enableScreens`');
|
||||
enableScreens(shouldUseScreens);
|
||||
}
|
||||
|
||||
function screensEnabled() {
|
||||
return USE_SCREENS;
|
||||
return ENABLE_SCREENS;
|
||||
}
|
||||
|
||||
// We initialize these lazily so that importing the module doesn't throw error when not linked
|
||||
@@ -83,7 +89,7 @@ class Screen extends React.Component {
|
||||
this.props.onComponentRef && this.props.onComponentRef(ref);
|
||||
};
|
||||
render() {
|
||||
if (!USE_SCREENS) {
|
||||
if (!ENABLE_SCREENS) {
|
||||
// Filter out active prop in this case because it is unused and
|
||||
// can cause problems depending on react-native version:
|
||||
// https://github.com/react-navigation/react-navigation/issues/4886
|
||||
@@ -97,7 +103,8 @@ class Screen extends React.Component {
|
||||
AnimatedNativeScreen ||
|
||||
Animated.createAnimatedComponent(ScreensNativeModules.NativeScreen);
|
||||
|
||||
if (version.minor >= 57) {
|
||||
// When using RN from master version is 0.0.0
|
||||
if (version.minor >= 57 || version.minor === 0) {
|
||||
return <AnimatedNativeScreen {...this.props} ref={this.setRef} />;
|
||||
} else {
|
||||
// On RN version below 0.57 we need to wrap screen's children with an
|
||||
@@ -120,7 +127,7 @@ class Screen extends React.Component {
|
||||
|
||||
class ScreenContainer extends React.Component {
|
||||
render() {
|
||||
if (!USE_SCREENS) {
|
||||
if (!ENABLE_SCREENS) {
|
||||
return <View {...this.props} />;
|
||||
} else {
|
||||
return <ScreensNativeModules.NativeScreenContainer {...this.props} />;
|
||||
@@ -196,6 +203,7 @@ module.exports = {
|
||||
ScreenStackHeaderTitleView,
|
||||
ScreenStackHeaderCenterView,
|
||||
|
||||
enableScreens,
|
||||
useScreens,
|
||||
screensEnabled,
|
||||
};
|
||||
|
||||
@@ -2,19 +2,19 @@ import debounce from 'debounce';
|
||||
import React from 'react';
|
||||
import { Animated, View } from 'react-native';
|
||||
|
||||
let _shouldUseScreens = true;
|
||||
let _shouldEnableScreens = true;
|
||||
|
||||
export function useScreens(shouldUseScreens = true) {
|
||||
if (shouldUseScreens) {
|
||||
export function enableScreens(shouldEnableScreens = true) {
|
||||
if (shouldEnableScreens) {
|
||||
console.warn(
|
||||
'react-native-screens is not fully supported on this platform yet.'
|
||||
);
|
||||
}
|
||||
_shouldUseScreens = shouldUseScreens;
|
||||
_shouldEnableScreens = shouldEnableScreens;
|
||||
}
|
||||
|
||||
export function screensEnabled() {
|
||||
return _shouldUseScreens;
|
||||
return _shouldEnableScreens;
|
||||
}
|
||||
|
||||
function isAnimatedValue(value) {
|
||||
|
||||
Reference in New Issue
Block a user