This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

21
node_modules/react-native-screens/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Krzysztof Magiera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

292
node_modules/react-native-screens/README.md generated vendored Normal file
View File

@ -0,0 +1,292 @@
# react-native-screens
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).
## Installation
### iOS
Installation on iOS should be completely handled with auto-linking, if you have insured pods are installed after adding this module, no other actions should be necessary
### Android
On Android the View state is not persisted consistently across Activity restarts, which can lead to crashes in those cases. It is recommended to override the native Android method called on Activity restarts in your main Activity, to avoid these crashes.
For most people using an app built from the react-native template, that means editing `MainActivity.java`, likely located in `android/app/src/main/java/<your package name>/MainActivity.java`
You should add this code, which specifically discards any Activity state persisted during the Activity restart process, to avoid inconsistencies that lead to crashes.
```java
import android.os.Bundle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
```
For people that must handle cases like this, there is [a more detailed discussion of the difficulties in a series of related comments](https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704633).
## How can I take advantage of that?
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).
Read usage guide depending on if you are [using Expo](#usage-in-expo-with-react-navigation) or [not](#usage-with-react-navigation-without-expo).
## Supported react-native version
Since version 2.0.0 react-native-screens requires RN v0.60+.
## Usage with [react-navigation](https://github.com/react-navigation/react-navigation)
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 shortly.
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 dependency to your project:
```bash
# bare React Native project
yarn add react-native-screens
# if you use Expo managed workflow
expo install react-native-screens
```
2. Enable screens support before any of your navigation screens renders. Add the following code to your main application file (e.g. App.js):
```js
import { enableScreens } from 'react-native-screens';
enableScreens();
```
Note that the above code needs to execute before the first render of a navigation screen. You can check the Example's app [App.js](https://github.com/kmagiera/react-native-screens/blob/master/Example/App.js#L16) file as a reference.
3. Make sure that the version of [react-navigation](https://github.com/react-navigation/react-navigation) you are using is 2.14.0 or higher
4. 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.
### Using createNativeStackNavigator with React Navigation
To take advantage of the native stack navigator primitive for React Navigation that leverages `UINavigationController` on iOS and `Fragment` on Android, please refer to the [README in react-native-screens/native-stack](https://github.com/software-mansion/react-native-screens/tree/master/native-stack) for React Navigation v5 and [README in react-native-screens/createNativeStackNavigator](https://github.com/software-mansion/react-native-screens/tree/master/createNativeStackNavigator) for older versions.
## Interop with [react-native-navigation](https://github.com/wix/react-native-navigation)
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.
## Interop with other libraries
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
If you are building a navigation library you may want to use react-native-screens to have control over which parts of the React component tree are attached to the native view hierarchy.
To do that react-native-screens provides you with two components documented below:
### `<ScreenContainer/>`
This component is a container for one or more `Screen` components.
It does not accept other component types as direct children.
The role of the container is to control which of its children's screens should be attached to the view hierarchy.
It does that by monitoring the `active` property of each of its children.
It is possible to have as many `active` children as you'd like but for the component to be the most efficient, we should keep the number of active screens to a minimum.
In the case of a stack navigator or tabs navigator, we only want to have one active screen (the topmost view on a stack or the selected tab).
While transitioning between views we may want to activate a second screen for the duration of the transition, and then go back to just one active screen.
### `<Screen/>`
This component is a container for views we want to display on a navigation screen.
It is designed to only be rendered as a direct child of `ScreenContainer`.
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`.
When `active` is set to `0`, the parent container will detach its views from the native view hierarchy.
Otherwise, the views will be attached as long as the parent container is attached too.
#### Example
```js
<ScreenContainer>
<Screen>{tab1}</Screen>
<Screen active={1}>{tab2}</Screen>
<Screen>{tab3}</Screen>
</ScreenContainer>
```
### `<ScreenStack>`
Screen stack component expects one or more `Screen` components as direct children and renders them in a platform-native stack container (for iOS it is `UINavigationController` and for Android inside `Fragment` container). For `Screen` components placed as children of `ScreenStack` the `active` property is ignored and instead the screen that corresponds to the last child is rendered as active. All types of updates done to the list of children are acceptable when the top element is exchanged the container will use platform default (unless customized) animation to transition between screens.
`StackScreen` extends the capabilities of the `Screen` component to allow additional customizations and to make it possible to handle events such as using hardware back or back gesture to dismiss the top screen. Below is the list of additional properties that can be used for `Screen` component:
#### `onDismissed`
A callback that gets called when the current screen is dismissed by hardware back (on Android) or dismiss gesture (swipe back or down). The callback takes no arguments.
#### `stackAnimation`
Allows for the customization of how the given screen should appear/disappear when pushed or popped at the top of the stack. The following 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/disappears without an animation
#### `stackPresentation`
Defines how the method that should be used to present the given screen. It is a separate property from `stackAnimation` as the presentation mode can carry additional semantic. The allowed values are:
- `push` the new screen will be pushed onto a stack which on iOS means that the default animation will be slide from the side, the animation on Android may vary depending on the OS version and theme.
- `modal` Explained below.
- `transparentModal` Explained below.
- `containedModal` Explained below.
- `containedTransparentModal` Explained below.
- `fullScreenModal` Explained below.
- `formSheet` Explained below.
For iOS:
- `modal` will use [`UIModalPresentationAutomatic`](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/uimodalpresentationautomatic?language=objc) on iOS 13 and later, and will use [`UIModalPresentationFullScreen`](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/uimodalpresentationfullscreen?language=objc) on iOS 12 and earlier.
- `fullScreenModal` will use [`UIModalPresentationFullScreen`](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/uimodalpresentationfullscreen?language=objc)
- `formSheet` will use [`UIModalPresentationFormSheet`](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/uimodalpresentationformsheet?language=objc)
- `transparentModal` will use [`UIModalPresentationOverFullScreen`](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/uimodalpresentationoverfullscreen?language=objc)
- `containedModal` will use [`UIModalPresentationCurrentContext`](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/uimodalpresentationcurrentcontext?language=objc)
- `containedTransparentModal` will use [`UIModalPresentationOverCurrentContext`](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/uimodalpresentationovercurrentcontext?language=objc)
For Android:
`modal`, `containedModal`, `fullScreenModal`, `formSheet` will use `Screen.StackPresentation.MODAL`.
`transparentModal`, `containedTransparentModal` will use `Screen.StackPresentation.TRANSPARENT_MODAL`.
### `<ScreenStackHeaderConfig>`
The config component is expected to be rendered as a direct child of `<Screen>`. It provides an ability to configure native navigation header that gets rendered as a part of the native screen stack. The component acts as a "virtual" element that is not directly rendered under `Screen`. You can use its properties to customize platform native header for the parent screen and also render react-native components that you'd like to be displayed inside the header (e.g. in the title are or on the side).
Along with this component's properties that can be used to customize header behavior, one can also use one of the below component containers to render custom react-native content in different areas of the native header:
- `ScreenStackHeaderCenterView` the children will render in the center of the native navigation bar.
- `ScreenStackHeaderRightView` the children will render on the right-hand side of the navigation bar (or on the left-hand side in case LTR locales are set on the user's device).
- `ScreenStackHeaderLeftView` the children will render on the left-hand side of the navigation bar (or on the right-hand side in case LTR locales are set on the user's device).
Below is a list of properties that can be set with `ScreenStackHeaderConfig` component:
#### `hidden`
When set to `true` the header will be hidden while the parent `Screen` is on the top of the stack. The default value is `false`.
#### `color`
Controls the color of items rendered on the header. This includes a back icon, back text (iOS only) and title text. If you want the title to have a different color use `titleColor` property.
#### `title`
A string representing screen title that will get rendered in the middle section of the header. On iOS, the title is centered on the header while on Android it is aligned to the left and placed next to the back button (if one is present).
#### `titleFontFamily`
Customize the font family to be used for the title.
#### `titleFontSize`
Customize the size of the font to be used for the title.
#### `titleColor`
Allows for setting text color of the title.
#### `backgroundColor`
Controls the color of the navigation header.
#### `hideShadow`
Boolean that allows for disabling drop shadow under navigation header. The default value is `true`.
#### `hideBackButton`
If set to `true` the back button will not be rendered as a part of the navigation header.
#### `backButtonInCustomView`
If set to `true` the back button will also be rendered while using `headerLeft` function.
#### `direction`
String that applies `rtl` or `ltr` form to the stack.
#### `gestureEnabled` (iOS only)
When set to `false` the back swipe gesture will be disabled when the parent `Screen` is on top of the stack. The default value is `true`.
#### `translucent`
When set to `true`, it allows the content to go under the navigation header, not bellow. If you want to create a transparent header, you should also set `backgroundColor` to `transparent`. The default value is `false`.
#### `backTitle` (iOS only)
Allows for controlling the string to be rendered next to the back button. By default, iOS uses the title of the previous screen.
#### `backTitleFontFamily` (iOS only)
Allows for customizing font family to be used for the back button title on iOS.
#### `backTitleFontSize` (iOS only)
Allows for customizing font size to be used for the 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 the 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.
A good example is a map component that shows the current user location. When the component is on the top-most screen, it should register for location updates and display the user's location on the map. But if we navigate away from a screen that has a map, e.g. by pushing a new screen on top of it or if it is in one of the tabs, and the user just switched to the previous app, we may want to stop listening to location updates.
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:
### Navigation lifecycle on iOS
In order for your native view on iOS to be notified when its parent navigation container goes into background override `didMoveToWindow` method:
```objective-c
- (void)didMoveToWindow
{
[super didMoveToWindow];
BOOL isVisible = self.superview && self.window;
if (isVisible) {
// navigation container this view belongs to became visible
} else {
// we are in a background
}
}
```
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.
### Navigation lifecycle on Android
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.
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.
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).
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.
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.
## License
React native screens library is licensed under [The MIT License](LICENSE).
## Credits
This project is supported by amazing people from [Expo.io](https://expo.io) and [Software Mansion](https://swmansion.com)
[![expo](https://avatars2.githubusercontent.com/u/12504344?v=3&s=100 'Expo.io')](https://expo.io)
[![swm](https://logo.swmansion.com/logo?color=white&variant=desktop&width=150&tag=react-native-screens-github 'Software Mansion')](https://swmansion.com)

24
node_modules/react-native-screens/RNScreens.podspec generated vendored Normal file
View File

@ -0,0 +1,24 @@
require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
Pod::Spec.new do |s|
s.name = "RNScreens"
s.version = package["version"]
s.summary = package["description"]
s.description = <<-DESC
RNScreens - first incomplete navigation solution for your React Native app
DESC
s.homepage = "https://github.com/kmagiera/react-native-screens"
s.license = "MIT"
# s.license = { :type => "MIT", :file => "FILE_LICENSE" }
s.author = { "author" => "author@domain.cn" }
s.platforms = { :ios => "9.0", :tvos => "11.0" }
s.source = { :git => "https://github.com/kmagiera/react-native-screens.git", :tag => "#{s.version}" }
s.source_files = "ios/**/*.{h,m}"
s.requires_arc = true
s.dependency "React-Core"
end

60
node_modules/react-native-screens/android/build.gradle generated vendored Normal file
View File

@ -0,0 +1,60 @@
buildscript {
// The Android Gradle plugin is only required when opening the android folder stand-alone.
// This avoids unnecessary downloads and potential conflicts when the library is included as a
// module dependency in an application project.
if (project == rootProject) {
repositories {
google()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:3.6.2")
}
}
}
apply plugin: 'com.android.library'
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
android {
compileSdkVersion safeExtGet('compileSdkVersion', 28)
buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 22)
versionCode 1
versionName "1.0"
}
lintOptions {
abortOnError false
}
}
repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
// Matches the RN Hello World template
// https://github.com/facebook/react-native/blob/1e8f3b11027fe0a7514b4fc97d0798d3c64bc895/local-cli/templates/HelloWorld/android/build.gradle#L21
url "$projectDir/../node_modules/react-native/android"
}
mavenCentral()
mavenLocal()
google()
jcenter()
}
dependencies {
implementation 'com.facebook.react:react-native:+'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.fragment:fragment:1.2.1'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'com.google.android.material:material:1.1.0'
}

View File

@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.swmansion.rnscreens">
</manifest>

View File

@ -0,0 +1,61 @@
package com.swmansion.rnscreens;
import android.view.View;
import android.view.ViewParent;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
public class LifecycleHelper {
public static @Nullable Fragment findNearestScreenFragmentAncestor(View view) {
ViewParent parent = view.getParent();
while (parent != null && !(parent instanceof Screen)) {
parent = parent.getParent();
}
if (parent != null) {
return ((Screen) parent).getFragment();
}
return null;
}
private Map<View, Lifecycle> mViewToLifecycleMap = new HashMap<>();
private View.OnLayoutChangeListener mRegisterOnLayoutChange = new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
registerViewWithLifecycleOwner(view);
view.removeOnLayoutChangeListener(this);
}
};
private void registerViewWithLifecycleOwner(View view) {
Fragment parent = findNearestScreenFragmentAncestor(view);
if (parent != null && view instanceof LifecycleObserver) {
Lifecycle lifecycle = parent.getLifecycle();
lifecycle.addObserver((LifecycleObserver) view);
mViewToLifecycleMap.put(view, lifecycle);
}
}
public <T extends View & LifecycleObserver> void register(T view) {
// we need to wait until view is mounted in the hierarchy as this method is called only at the
// moment of the view creation. In order to register lifecycle observer we need to find ancestor
// of type Screen and this can only happen when the view is properly attached. We rely on Android's
// onLayout callback being triggered when the view gets added to the hierarchy and only then we
// attempt to locate lifecycle owner ancestor.
view.addOnLayoutChangeListener(mRegisterOnLayoutChange);
}
public <T extends View & LifecycleObserver> void unregister(T view) {
Lifecycle lifecycle = mViewToLifecycleMap.get(view);
if (lifecycle != null) {
lifecycle.removeObserver(view);
}
}
}

View File

@ -0,0 +1,28 @@
package com.swmansion.rnscreens;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class RNScreensPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new ScreenContainerViewManager(),
new ScreenViewManager(),
new ScreenStackViewManager(),
new ScreenStackHeaderConfigViewManager(),
new ScreenStackHeaderSubviewManager()
);
}
}

View File

@ -0,0 +1,248 @@
package com.swmansion.rnscreens;
import android.content.Context;
import android.graphics.Paint;
import android.os.Parcelable;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.GuardedRunnable;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
public class Screen extends ViewGroup {
public enum StackPresentation {
PUSH,
MODAL,
TRANSPARENT_MODAL
}
public enum StackAnimation {
DEFAULT,
NONE,
FADE
}
public enum ReplaceAnimation {
PUSH,
POP
}
public enum ActivityState {
INACTIVE,
TRANSITIONING_OR_BELOW_TOP,
ON_TOP
}
private static OnAttachStateChangeListener sShowSoftKeyboardOnAttach = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
InputMethodManager inputMethodManager =
(InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(view, 0);
view.removeOnAttachStateChangeListener(sShowSoftKeyboardOnAttach);
}
@Override
public void onViewDetachedFromWindow(View view) {
}
};
private @Nullable ScreenFragment mFragment;
private @Nullable ScreenContainer mContainer;
private ActivityState mActivityState;
private boolean mTransitioning;
private StackPresentation mStackPresentation = StackPresentation.PUSH;
private ReplaceAnimation mReplaceAnimation = ReplaceAnimation.POP;
private StackAnimation mStackAnimation = StackAnimation.DEFAULT;
private boolean mGestureEnabled = true;
@Override
protected void onAnimationStart() {
super.onAnimationStart();
if (mFragment != null) {
mFragment.onViewAnimationStart();
}
}
@Override
protected void onAnimationEnd() {
super.onAnimationEnd();
if (mFragment != null) {
mFragment.onViewAnimationEnd();
}
}
public Screen(ReactContext context) {
super(context);
// we set layout params as WindowManager.LayoutParams to workaround the issue with TextInputs
// not displaying modal menus (e.g., copy/paste or selection). The missing menus are due to the
// fact that TextView implementation is expected to be attached to window when layout happens.
// Then, at the moment of layout it checks whether window type is in a reasonable range to tell
// whether it should enable selection controlls (see Editor.java#prepareCursorControllers).
// With screens, however, the text input component can be laid out before it is attached, in that
// case TextView tries to get window type property from the oldest existing parent, which in this
// case is a Screen class, as it is the root of the screen that is about to be attached. Setting
// params this way is not the most elegant way to solve this problem but workarounds it for the
// time being
setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION));
}
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
// do nothing, react native will keep the view hierarchy so no need to serialize/deserialize
// view's states. The side effect of restoring is that TextInput components would trigger set-text
// events which may confuse text input handling.
}
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
// ignore restoring instance state too as we are not saving anything anyways.
}
@Override
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
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// This method implements a workaround for RN's autoFocus functionality. Because of the way
// autoFocus is implemented it sometimes gets triggered before native text view is mounted. As
// a result Android ignores calls for opening soft keyboard and here we trigger it manually
// again after the screen is attached.
View view = getFocusedChild();
if (view != null) {
while (view instanceof ViewGroup) {
view = ((ViewGroup) view).getFocusedChild();
}
if (view instanceof TextView) {
TextView textView = (TextView) view;
if (textView.getShowSoftInputOnFocus()) {
textView.addOnAttachStateChangeListener(sShowSoftKeyboardOnAttach);
}
}
}
}
/**
* While transitioning this property allows to optimize rendering behavior on Android and provide
* a correct blending options for the animated screen. It is turned on automatically by the container
* when transitioning is detected and turned off immediately after
*/
public void setTransitioning(boolean transitioning) {
if (mTransitioning == transitioning) {
return;
}
mTransitioning = transitioning;
boolean isWebViewInScreen = hasWebView(this);
if (isWebViewInScreen && getLayerType() != View.LAYER_TYPE_HARDWARE) {
return;
}
super.setLayerType(transitioning && !isWebViewInScreen ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, null);
}
private boolean hasWebView(ViewGroup viewGroup) {
for(int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child instanceof WebView) {
return true;
} else if (child instanceof ViewGroup) {
if (hasWebView((ViewGroup) child)) {
return true;
}
}
}
return false;
}
public void setStackPresentation(StackPresentation stackPresentation) {
mStackPresentation = stackPresentation;
}
public void setStackAnimation(StackAnimation stackAnimation) {
mStackAnimation = stackAnimation;
}
public void setReplaceAnimation(ReplaceAnimation replaceAnimation) {
mReplaceAnimation = replaceAnimation;
}
public void setGestureEnabled(boolean gestureEnabled) {
mGestureEnabled = gestureEnabled;
}
public StackAnimation getStackAnimation() {
return mStackAnimation;
}
public ReplaceAnimation getReplaceAnimation() {
return mReplaceAnimation;
}
public StackPresentation getStackPresentation() {
return mStackPresentation;
}
@Override
public void setLayerType(int layerType, @Nullable Paint paint) {
// ignore - layer type is controlled by `transitioning` prop
}
protected void setContainer(@Nullable ScreenContainer container) {
mContainer = container;
}
protected void setFragment(ScreenFragment fragment) {
mFragment = fragment;
}
protected @Nullable ScreenFragment getFragment() {
return mFragment;
}
protected @Nullable ScreenContainer getContainer() {
return mContainer;
}
public void setActivityState(ActivityState activityState) {
if (activityState == mActivityState) {
return;
}
mActivityState = activityState;
if (mContainer != null) {
mContainer.notifyChildUpdate();
}
}
public ActivityState getActivityState() {
return mActivityState;
}
public boolean isGestureEnabled() {
return mGestureEnabled;
}
}

View File

@ -0,0 +1,30 @@
package com.swmansion.rnscreens;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
public class ScreenAppearEvent extends Event<ScreenAppearEvent> {
public static final String EVENT_NAME = "topAppear";
public ScreenAppearEvent(int viewId) {
super(viewId);
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
}
}

View File

@ -0,0 +1,380 @@
package com.swmansion.rnscreens;
import android.content.Context;
import android.content.ContextWrapper;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.inputmethod.InputMethodManager;
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;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.ReactChoreographer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
protected final ArrayList<T> mScreenFragments = new ArrayList<>();
protected @Nullable FragmentManager mFragmentManager;
private @Nullable FragmentTransaction mCurrentTransaction;
private @Nullable FragmentTransaction mProcessingTransaction;
private boolean mNeedUpdate;
private boolean mIsAttached;
private boolean mLayoutEnqueued = false;
private @Nullable ScreenFragment mParentScreenFragment = null;
private final ChoreographerCompat.FrameCallback mFrameCallback = new ChoreographerCompat.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
updateIfNeeded();
}
};
private final ChoreographerCompat.FrameCallback mLayoutCallback = new ChoreographerCompat.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
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 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 removeView(View view) {
// The below block is a workaround for an issue with keyboard handling within fragments. Despite
// Android handles input focus on the fragments that leave the screen, the keyboard stays open
// in a number of cases. The issue can be best reproduced on Android 5 devices, before some changes
// in Android's InputMethodManager have been introduced (specifically around dismissing the
// keyboard in onDetachedFromWindow). However, we also noticed the keyboard issue happen
// intermittently on recent versions of Android as well. The issue hasn't been previously noticed
// as in React Native <= 0.61 there was a logic that'd trigger keyboard dismiss upon a blur event
// (the blur even gets dispatched properly, the keyboard just stays open despite that) note
// the change in RN core here: https://github.com/facebook/react-native/commit/e9b4928311513d3cbbd9d875827694eab6cfa932
// The workaround is to force-hide keyboard when the screen that has focus is dismissed (we detect
// that in removeView as super.removeView causes the input view to un focus while keeping the
// keyboard open).
if (view == getFocusedChild()) {
((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
super.removeView(view);
}
@Override
public void requestLayout() {
super.requestLayout();
if (!mLayoutEnqueued && mLayoutCallback != null) {
mLayoutEnqueued = true;
// we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current
// looper loop instead of enqueueing the update in the next loop causing a one frame delay.
ReactChoreographer.getInstance().postFrameCallback(
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
mLayoutCallback);
}
}
public boolean isNested() {
return mParentScreenFragment != null;
}
protected void markUpdated() {
if (!mNeedUpdate) {
mNeedUpdate = true;
// enqueue callback of NATIVE_ANIMATED_MODULE type as all view operations are executed in
// DISPATCH_UI type and we want the callback to be called right after in the same frame.
ReactChoreographer.getInstance().postFrameCallback(
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
mFrameCallback);
}
}
protected void notifyChildUpdate() {
markUpdated();
}
protected T adapt(Screen screen) {
return (T) new ScreenFragment(screen);
}
protected void addScreen(Screen screen, int index) {
T fragment = adapt(screen);
screen.setFragment(fragment);
mScreenFragments.add(index, fragment);
screen.setContainer(this);
markUpdated();
}
protected void removeScreenAt(int index) {
mScreenFragments.get(index).getScreen().setContainer(null);
mScreenFragments.remove(index);
markUpdated();
}
protected void removeAllScreens() {
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
mScreenFragments.get(i).getScreen().setContainer(null);
}
mScreenFragments.clear();
markUpdated();
}
protected int getScreenCount() {
return mScreenFragments.size();
}
protected Screen getScreenAt(int index) {
return mScreenFragments.get(index).getScreen();
}
private void setFragmentManager(FragmentManager fm) {
mFragmentManager = fm;
updateIfNeeded();
}
private void setupFragmentManager() {
ViewParent parent = this;
// 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) {
ScreenFragment screenFragment = ((Screen) parent).getFragment();
setFragmentManager(screenFragment.getChildFragmentManager());
mParentScreenFragment = screenFragment;
mParentScreenFragment.registerChildScreenContainer(this);
return;
}
// 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
// in order to make sure we indeed reached the root we test if it is of a correct type. This
// allows us to provide a more descriptive error message for the aforementioned case.
if (!(parent instanceof ReactRootView)) {
throw new IllegalStateException("ScreenContainer is not attached under ReactRootView");
}
// ReactRootView is expected to be initialized with the main React Activity as a context but
// in case of Expo the activity is wrapped in ContextWrapper and we need to unwrap it
Context context = ((ReactRootView) parent).getContext();
while (!(context instanceof FragmentActivity) && context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
}
if (!(context instanceof FragmentActivity)) {
throw new IllegalStateException(
"In order to use RNScreens components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity");
}
setFragmentManager(((FragmentActivity) context).getSupportFragmentManager());
}
protected FragmentTransaction getOrCreateTransaction() {
if (mCurrentTransaction == null) {
mCurrentTransaction = mFragmentManager.beginTransaction();
mCurrentTransaction.setReorderingAllowed(true);
}
return mCurrentTransaction;
}
protected void tryCommitTransaction() {
if (mCurrentTransaction != null) {
final FragmentTransaction transaction = mCurrentTransaction;
mProcessingTransaction = transaction;
mProcessingTransaction.runOnCommit(new Runnable() {
@Override
public void run() {
if (mProcessingTransaction == transaction) {
// we need to take into account that commit is initiated with some other transaction while
// the previous one is still processing. In this case mProcessingTransaction gets overwritten
// and we don't want to set it to null until the second transaction is finished.
mProcessingTransaction = null;
}
}
});
mCurrentTransaction.commitAllowingStateLoss();
mCurrentTransaction = null;
}
}
private void attachScreen(ScreenFragment screenFragment) {
getOrCreateTransaction().add(getId(), screenFragment);
}
private void moveToFront(ScreenFragment screenFragment) {
FragmentTransaction transaction = getOrCreateTransaction();
transaction.remove(screenFragment);
transaction.add(getId(), screenFragment);
}
private void detachScreen(ScreenFragment screenFragment) {
getOrCreateTransaction().remove(screenFragment);
}
protected Screen.ActivityState getActivityState(ScreenFragment screenFragment) {
return screenFragment.getScreen().getActivityState();
}
protected boolean hasScreen(ScreenFragment screenFragment) {
return mScreenFragments.contains(screenFragment);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mIsAttached = true;
mNeedUpdate = true;
setupFragmentManager();
}
/**
* Removes fragments from fragment manager that are attached to this container
*/
private void removeMyFragments() {
FragmentTransaction transaction = mFragmentManager.beginTransaction();
boolean hasFragments = false;
for (Fragment fragment : mFragmentManager.getFragments()) {
if (fragment instanceof ScreenFragment && ((ScreenFragment) fragment).mScreenView.getContainer() == this) {
transaction.remove(fragment);
hasFragments = true;
}
}
if (hasFragments) {
transaction.commitNowAllowingStateLoss();
}
}
@Override
protected void onDetachedFromWindow() {
// if there are pending transactions and this view is about to get detached we need to perform
// them here as otherwise fragment manager will crash because it won't be able to find container
// view. We also need to make sure all the fragments attached to the given container are removed
// from fragment manager as in some cases fragment manager may be reused and in such case it'd
// attempt to reattach previously registered fragments that are not removed
if (mFragmentManager != null && !mFragmentManager.isDestroyed()) {
removeMyFragments();
mFragmentManager.executePendingTransactions();
}
if (mParentScreenFragment != null) {
mParentScreenFragment.unregisterChildScreenContainer(this);
mParentScreenFragment = null;
}
super.onDetachedFromWindow();
mIsAttached = false;
// When fragment container view is detached we force all its children to be removed.
// It is because children screens are controlled by their fragments, which can often have a
// delayed lifecycle (due to transitions). As a result due to ongoing transitions the fragment
// may choose not to remove the view despite the parent container being completely detached
// from the view hierarchy until the transition is over. In such a case when the container gets
// re-attached while tre transition is ongoing, the child view would still be there and we'd
// attept to re-attach it to with a misconfigured fragment. This would result in a crash. To
// avoid it we clear all the children here as we attach all the child fragments when the container
// is reattached anyways.
removeAllViews();
}
@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 || mFragmentManager == null) {
return;
}
mNeedUpdate = false;
onUpdate();
}
private final void onUpdate() {
// We double check if fragment manager have any pending transactions to run.
// In performUpdate we often check whether some fragments are added to
// manager to avoid adding them for the second time (which result in crash).
// By design performUpdate should be called at most once per frame, so this
// should never happen, but in case there are some pending transaction we
// need to flush them here such that Fragment#isAdded checks reflect the
// reality and that we don't have enqueued fragment add commands that will
// execute shortly and cause "Fragment already added" crash.
mFragmentManager.executePendingTransactions();
performUpdate();
}
protected void performUpdate() {
// detach screens that are no longer active
Set<Fragment> orphaned = new HashSet<>(mFragmentManager.getFragments());
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
ScreenFragment screenFragment = mScreenFragments.get(i);
if (getActivityState(screenFragment) == Screen.ActivityState.INACTIVE && screenFragment.isAdded()) {
detachScreen(screenFragment);
}
orphaned.remove(screenFragment);
}
if (!orphaned.isEmpty()) {
Object[] orphanedAry = orphaned.toArray();
for (int i = 0; i < orphanedAry.length; i++) {
if (orphanedAry[i] instanceof ScreenFragment) {
if (((ScreenFragment) orphanedAry[i]).getScreen().getContainer() == null) {
detachScreen((ScreenFragment) orphanedAry[i]);
}
}
}
}
boolean transitioning = true;
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
ScreenFragment screenFragment = mScreenFragments.get(i);
if (getActivityState(screenFragment) == Screen.ActivityState.ON_TOP) {
// if there is an "onTop" screen it means the transition has ended
transitioning = false;
}
}
// attach newly activated screens
boolean addedBefore = false;
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
ScreenFragment screenFragment = mScreenFragments.get(i);
Screen.ActivityState activityState = getActivityState(screenFragment);
if (activityState != Screen.ActivityState.INACTIVE && !screenFragment.isAdded()) {
addedBefore = true;
attachScreen(screenFragment);
} else if (activityState != Screen.ActivityState.INACTIVE && addedBefore) {
moveToFront(screenFragment);
}
screenFragment.getScreen().setTransitioning(transitioning);
}
tryCommitTransaction();
}
}

View File

@ -0,0 +1,56 @@
package com.swmansion.rnscreens;
import android.view.View;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
@ReactModule(name = ScreenContainerViewManager.REACT_CLASS)
public class ScreenContainerViewManager extends ViewGroupManager<ScreenContainer> {
protected static final String REACT_CLASS = "RNSScreenContainer";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ScreenContainer createViewInstance(ThemedReactContext reactContext) {
return new ScreenContainer(reactContext);
}
@Override
public void addView(ScreenContainer parent, View child, int index) {
if (!(child instanceof Screen)) {
throw new IllegalArgumentException("Attempt attach child that is not of type RNScreens");
}
parent.addScreen((Screen) child, index);
}
@Override
public void removeViewAt(ScreenContainer parent, int index) {
parent.removeScreenAt(index);
}
@Override
public void removeAllViews(ScreenContainer parent) {
parent.removeAllScreens();
}
@Override
public int getChildCount(ScreenContainer parent) {
return parent.getScreenCount();
}
@Override
public View getChildAt(ScreenContainer parent, int index) {
return parent.getScreenAt(index);
}
@Override
public boolean needsCustomLayoutForChildren() {
return true;
}
}

View File

@ -0,0 +1,30 @@
package com.swmansion.rnscreens;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
public class ScreenDisappearEvent extends Event<ScreenAppearEvent> {
public static final String EVENT_NAME = "topDisappear";
public ScreenDisappearEvent(int viewId) {
super(viewId);
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
}
}

View File

@ -0,0 +1,30 @@
package com.swmansion.rnscreens;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
public class ScreenDismissedEvent extends Event<ScreenDismissedEvent> {
public static final String EVENT_NAME = "topDismissed";
public ScreenDismissedEvent(int viewId) {
super(viewId);
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
}
}

View File

@ -0,0 +1,168 @@
package com.swmansion.rnscreens;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class ScreenFragment extends Fragment {
protected static View recycleView(View view) {
// screen fragments reuse view instances instead of creating new ones. In order to reuse a given
// view it needs to be detached from the view hierarchy to allow the fragment to attach it back.
ViewParent parent = view.getParent();
if (parent != null) {
((ViewGroup) parent).endViewTransition(view);
((ViewGroup) parent).removeView(view);
}
// view detached from fragment manager get their visibility changed to GONE after their state is
// dumped. Since we don't restore the state but want to reuse the view we need to change visibility
// back to VISIBLE in order for the fragment manager to animate in the view.
view.setVisibility(View.VISIBLE);
return view;
}
protected Screen mScreenView;
private List<ScreenContainer> mChildScreenContainers = new ArrayList<>();
public ScreenFragment() {
throw new IllegalStateException("Screen fragments should never be restored. Follow instructions from https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067 to properly configure your main activity.");
}
@SuppressLint("ValidFragment")
public ScreenFragment(Screen screenView) {
super();
mScreenView = screenView;
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
FrameLayout wrapper = new FrameLayout(getContext());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mScreenView.setLayoutParams(params);
wrapper.addView(recycleView(mScreenView));
return wrapper;
}
public Screen getScreen() {
return mScreenView;
}
protected void dispatchOnWillAppear() {
((ReactContext) mScreenView.getContext())
.getNativeModule(UIManagerModule.class)
.getEventDispatcher()
.dispatchEvent(new ScreenWillAppearEvent(mScreenView.getId()));
for (ScreenContainer sc : mChildScreenContainers) {
if (sc.getScreenCount() > 0) {
Screen topScreen = sc.getScreenAt(sc.getScreenCount() - 1);
topScreen.getFragment().dispatchOnWillAppear();
}
}
}
protected void dispatchOnAppear() {
((ReactContext) mScreenView.getContext())
.getNativeModule(UIManagerModule.class)
.getEventDispatcher()
.dispatchEvent(new ScreenAppearEvent(mScreenView.getId()));
for (ScreenContainer sc : mChildScreenContainers) {
if (sc.getScreenCount() > 0) {
Screen topScreen = sc.getScreenAt(sc.getScreenCount() - 1);
topScreen.getFragment().dispatchOnAppear();
}
}
}
protected void dispatchOnWillDisappear() {
((ReactContext) mScreenView.getContext())
.getNativeModule(UIManagerModule.class)
.getEventDispatcher()
.dispatchEvent(new ScreenWillDisappearEvent(mScreenView.getId()));
for (ScreenContainer sc : mChildScreenContainers) {
if (sc.getScreenCount() > 0) {
Screen topScreen = sc.getScreenAt(sc.getScreenCount() - 1);
topScreen.getFragment().dispatchOnWillDisappear();
}
}
}
protected void dispatchOnDisappear() {
((ReactContext) mScreenView.getContext())
.getNativeModule(UIManagerModule.class)
.getEventDispatcher()
.dispatchEvent(new ScreenDisappearEvent(mScreenView.getId()));
for (ScreenContainer sc : mChildScreenContainers) {
if (sc.getScreenCount() > 0) {
Screen topScreen = sc.getScreenAt(sc.getScreenCount() - 1);
topScreen.getFragment().dispatchOnDisappear();
}
}
}
public void registerChildScreenContainer(ScreenContainer screenContainer) {
mChildScreenContainers.add(screenContainer);
}
public void unregisterChildScreenContainer(ScreenContainer screenContainer) {
mChildScreenContainers.remove(screenContainer);
}
public void onViewAnimationStart() {
// onViewAnimationStart is triggered from View#onAnimationStart method of the fragment's root view.
// We override Screen#onAnimationStart and an appropriate method of the StackFragment's root view
// in order to achieve this.
if (isResumed()) {
dispatchOnWillAppear();
} else {
dispatchOnWillDisappear();
}
}
public void onViewAnimationEnd() {
// onViewAnimationEnd is triggered from View#onAnimationEnd method of the fragment's root view.
// We override Screen#onAnimationEnd and an appropriate method of the StackFragment's root view
// in order to achieve this.
if (isResumed()) {
dispatchOnAppear();
} else {
dispatchOnDisappear();
}
}
@Override
public void onDestroy() {
super.onDestroy();
ScreenContainer container = mScreenView.getContainer();
if (container == null || !container.hasScreen(this)) {
// we only send dismissed even when the screen has been removed from its container
((ReactContext) mScreenView.getContext())
.getNativeModule(UIManagerModule.class)
.getEventDispatcher()
.dispatchEvent(new ScreenDismissedEvent(mScreenView.getId()));
}
mChildScreenContainers.clear();
}
}

View File

@ -0,0 +1,291 @@
package com.swmansion.rnscreens;
import android.content.Context;
import android.view.View;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
private static final String BACK_STACK_TAG = "RN_SCREEN_LAST";
private final ArrayList<ScreenStackFragment> mStack = new ArrayList<>();
private final Set<ScreenStackFragment> mDismissed = new HashSet<>();
private ScreenStackFragment mTopScreen = null;
private boolean mRemovalTransitionStarted = false;
private final FragmentManager.OnBackStackChangedListener mBackStackListener = new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
if (mFragmentManager.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(ScreenStackFragment screenFragment) {
mDismissed.add(screenFragment);
markUpdated();
}
public Screen getTopScreen() {
return mTopScreen != null ? mTopScreen.getScreen() : null;
}
public Screen getRootScreen() {
for (int i = 0, size = getScreenCount(); i < size; i++) {
Screen screen = getScreenAt(i);
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() {
if (mFragmentManager != null) {
mFragmentManager.removeOnBackStackChangedListener(mBackStackListener);
mFragmentManager.unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks);
if (!mFragmentManager.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.
mFragmentManager.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
super.onDetachedFromWindow();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mFragmentManager.registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false);
}
@Override
public void startViewTransition(View view) {
super.startViewTransition(view);
mRemovalTransitionStarted = true;
}
@Override
public void endViewTransition(View view) {
super.endViewTransition(view);
if (mRemovalTransitionStarted) {
mRemovalTransitionStarted = false;
dispatchOnFinishTransitioning();
}
}
public void onViewAppearTransitionEnd() {
if (!mRemovalTransitionStarted) {
dispatchOnFinishTransitioning();
}
}
private void dispatchOnFinishTransitioning() {
((ReactContext) getContext())
.getNativeModule(UIManagerModule.class)
.getEventDispatcher()
.dispatchEvent(new StackFinishTransitioningEvent(getId()));
}
@Override
protected void removeScreenAt(int index) {
Screen toBeRemoved = getScreenAt(index);
mDismissed.remove(toBeRemoved.getFragment());
super.removeScreenAt(index);
}
@Override
protected void removeAllScreens() {
mDismissed.clear();
super.removeAllScreens();
}
@Override
protected boolean hasScreen(ScreenFragment screenFragment) {
return super.hasScreen(screenFragment) && !mDismissed.contains(screenFragment);
}
@Override
protected void performUpdate() {
// remove all screens previously on stack
for (ScreenStackFragment screen : mStack) {
if (!mScreenFragments.contains(screen) || mDismissed.contains(screen)) {
getOrCreateTransaction().remove(screen);
}
}
// 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.getScreen().getStackPresentation() != Screen.StackPresentation.TRANSPARENT_MODAL) {
break;
}
} else {
belowTop = screen;
break;
}
}
}
for (ScreenStackFragment screen : mScreenFragments) {
// detach all screens that should not be visible
if (screen != newTop && screen != belowTop && !mDismissed.contains(screen)) {
getOrCreateTransaction().remove(screen);
}
}
// attach "below top" screen if set
if (belowTop != null && !belowTop.isAdded()) {
final ScreenStackFragment top = newTop;
getOrCreateTransaction().add(getId(), belowTop).runOnCommit(new Runnable() {
@Override
public void run() {
top.getScreen().bringToFront();
}
});
}
if (newTop != null && !newTop.isAdded()) {
getOrCreateTransaction().add(getId(), 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 && newTop != null) {
// there was some other screen attached before
int transition = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
if (!mScreenFragments.contains(mTopScreen) && newTop.getScreen().getReplaceAnimation() == Screen.ReplaceAnimation.POP) {
// if the previous top screen does not exist anymore and the new top was not on the stack before,
// probably replace was called, so we check the animation
transition = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;
}
switch (newTop.getScreen().getStackAnimation()) {
case NONE:
transition = FragmentTransaction.TRANSIT_NONE;
break;
case FADE:
transition = FragmentTransaction.TRANSIT_FRAGMENT_FADE;
break;
}
getOrCreateTransaction().setTransition(transition);
}
} 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.getScreen().getStackAnimation()) {
case NONE:
transition = FragmentTransaction.TRANSIT_NONE;
break;
case FADE:
transition = FragmentTransaction.TRANSIT_FRAGMENT_FADE;
break;
}
getOrCreateTransaction().setTransition(transition);
}
mTopScreen = newTop;
mStack.clear();
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;
}
mFragmentManager.removeOnBackStackChangedListener(mBackStackListener);
mFragmentManager.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()) {
mFragmentManager
.beginTransaction()
.show(topScreen)
.addToBackStack(BACK_STACK_TAG)
.setPrimaryNavigationFragment(topScreen)
.commitAllowingStateLoss();
mFragmentManager.addOnBackStackChangedListener(mBackStackListener);
}
}
}

View File

@ -0,0 +1,227 @@
package com.swmansion.rnscreens;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.widget.LinearLayout;
import com.facebook.react.uimanager.PixelUtil;
import com.google.android.material.appbar.AppBarLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
public class ScreenStackFragment extends ScreenFragment {
private static class NotifyingCoordinatorLayout extends CoordinatorLayout {
private final ScreenFragment mFragment;
public NotifyingCoordinatorLayout(@NonNull Context context, ScreenFragment fragment) {
super(context);
mFragment = fragment;
}
private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mFragment.onViewAnimationStart();
}
@Override
public void onAnimationEnd(Animation animation) {
mFragment.onViewAnimationEnd();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};
@Override
public void startAnimation(Animation animation) {
// For some reason View##onAnimationEnd doesn't get called for
// exit transitions so we use this hack.
AnimationSet set = new AnimationSet(true);
set.addAnimation(animation);
set.setAnimationListener(mAnimationListener);
super.startAnimation(set);
}
}
private static final float TOOLBAR_ELEVATION = PixelUtil.toPixelFromDIP(4);
private AppBarLayout mAppBarLayout;
private Toolbar mToolbar;
private boolean mShadowHidden;
private boolean mIsTranslucent;
@SuppressLint("ValidFragment")
public ScreenStackFragment(Screen screenView) {
super(screenView);
}
public ScreenStackFragment() {
throw new IllegalStateException("ScreenStack fragments should never be restored. Follow instructions from https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067 to properly configure your main activity.");
}
public void removeToolbar() {
if (mAppBarLayout != null && mToolbar != null && mToolbar.getParent() == mAppBarLayout) {
mAppBarLayout.removeView(mToolbar);
}
mToolbar = null;
}
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 setToolbarTranslucent(boolean translucent) {
if (mIsTranslucent != translucent) {
ViewGroup.LayoutParams params = mScreenView.getLayoutParams();
((CoordinatorLayout.LayoutParams) params).setBehavior(translucent ? null : new AppBarLayout.ScrollingViewBehavior());
mIsTranslucent = translucent;
}
}
public void onStackUpdate() {
View child = mScreenView.getChildAt(0);
if (child instanceof ScreenStackHeaderConfig) {
((ScreenStackHeaderConfig) child).onUpdate();
}
}
@Override
public void onViewAnimationEnd() {
super.onViewAnimationEnd();
notifyViewAppearTransitionEnd();
}
@Nullable
@Override
public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) {
// this means that the fragment will appear without transition, in this case
// onViewAnimationStart and onViewAnimationEnd won't be called and we need to notify
// stack directly from here.
// When using the Toolbar back button this is called an extra time with transit = 0 but in
// this case we don't want to notify. The way I found to detect is case is check isHidden.
if (transit == 0 && !isHidden()) {
// If the container is nested then appear events will be dispatched by their parent screen so
// they must not be triggered here.
ScreenContainer container = getScreen().getContainer();
boolean isNested = container != null && container.isNested();
if (enter) {
if (!isNested) {
dispatchOnWillAppear();
dispatchOnAppear();
}
} else {
if (!isNested) {
dispatchOnWillDisappear();
dispatchOnDisappear();
}
notifyViewAppearTransitionEnd();
}
}
return null;
}
private void notifyViewAppearTransitionEnd() {
ViewParent screenStack = getView() != null ? getView().getParent() : null;
if (screenStack instanceof ScreenStack) {
((ScreenStack) screenStack).onViewAppearTransitionEnd();
}
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
CoordinatorLayout view = new NotifyingCoordinatorLayout(getContext(), this);
CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
params.setBehavior(mIsTranslucent ? null : new AppBarLayout.ScrollingViewBehavior());
mScreenView.setLayoutParams(params);
view.addView(recycleView(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 (mShadowHidden) {
mAppBarLayout.setTargetElevation(0);
}
if (mToolbar != null) {
mAppBarLayout.addView(recycleView(mToolbar));
}
return view;
}
public boolean isDismissable() {
return mScreenView.isGestureEnabled();
}
public boolean canNavigateBack() {
ScreenContainer container = mScreenView.getContainer();
if (container instanceof ScreenStack) {
if (((ScreenStack) container).getRootScreen() == getScreen()) {
// this screen is the root of the container, if it is nested we can check parent container
// if it is also a root or not
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof ScreenStackFragment) {
return ((ScreenStackFragment) parentFragment).canNavigateBack();
} else {
return false;
}
} else {
return true;
}
} else {
throw new IllegalStateException("ScreenStackFragment added into a non-stack container");
}
}
public void dismiss() {
ScreenContainer container = mScreenView.getContainer();
if (container instanceof ScreenStack) {
((ScreenStack) container).dismiss(this);
} else {
throw new IllegalStateException("ScreenStackFragment added into a non-stack container");
}
}
}

View File

@ -0,0 +1,395 @@
package com.swmansion.rnscreens;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import com.facebook.react.ReactApplication;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.views.text.ReactFontManager;
import java.util.ArrayList;
public class ScreenStackHeaderConfig extends ViewGroup {
private final ArrayList<ScreenStackHeaderSubview> mConfigSubviews = new ArrayList<>(3);
private String mTitle;
private int mTitleColor;
private String mTitleFontFamily;
private String mDirection;
private float mTitleFontSize;
private Integer mBackgroundColor;
private boolean mIsHidden;
private boolean mIsBackButtonHidden;
private boolean mIsShadowHidden;
private boolean mDestroyed;
private boolean mBackButtonInCustomView;
private boolean mIsTopInsetEnabled = true;
private boolean mIsTranslucent;
private int mTintColor;
private final Toolbar mToolbar;
private boolean mIsAttachedToWindow = false;
private int mDefaultStartInset;
private int mDefaultStartInsetWithNavigation;
private static class DebugMenuToolbar extends Toolbar {
public DebugMenuToolbar(Context context) {
super(context);
}
@Override
public boolean showOverflowMenu() {
((ReactApplication) getContext().getApplicationContext()).getReactNativeHost().getReactInstanceManager().showDevOptionsDialog();
return true;
}
}
private OnClickListener mBackClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
ScreenStackFragment fragment = getScreenFragment();
if (fragment != null) {
ScreenStack stack = getScreenStack();
if (stack != null && stack.getRootScreen() == fragment.getScreen()) {
Fragment parentFragment = fragment.getParentFragment();
if (parentFragment instanceof ScreenStackFragment) {
((ScreenStackFragment) parentFragment).dismiss();
}
} else {
fragment.dismiss();
}
}
}
};
public ScreenStackHeaderConfig(Context context) {
super(context);
setVisibility(View.GONE);
mToolbar = BuildConfig.DEBUG ? new DebugMenuToolbar(context) : new Toolbar(context);
mDefaultStartInset = mToolbar.getContentInsetStart();
mDefaultStartInsetWithNavigation = mToolbar.getContentInsetStartWithNavigation();
// 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);
}
mToolbar.setClipChildren(false);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// no-op
}
public void destroy() {
mDestroyed = true;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mIsAttachedToWindow = true;
onUpdate();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mIsAttachedToWindow = false;
}
private Screen getScreen() {
ViewParent screen = getParent();
if (screen instanceof Screen) {
return (Screen) screen;
}
return null;
}
private ScreenStack getScreenStack() {
Screen screen = getScreen();
if (screen != null) {
ScreenContainer container = screen.getContainer();
if (container instanceof ScreenStack) {
return (ScreenStack) container;
}
}
return null;
}
private ScreenStackFragment getScreenFragment() {
ViewParent screen = getParent();
if (screen instanceof Screen) {
Fragment fragment = ((Screen) screen).getFragment();
if (fragment instanceof ScreenStackFragment) {
return (ScreenStackFragment) fragment;
}
}
return null;
}
public void onUpdate() {
Screen parent = (Screen) getParent();
final ScreenStack stack = getScreenStack();
boolean isTop = stack == null ? true : stack.getTopScreen() == parent;
if (!mIsAttachedToWindow || !isTop || mDestroyed) {
return;
}
AppCompatActivity activity = (AppCompatActivity) getScreenFragment().getActivity();
if (activity == null) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && mDirection != null) {
if (mDirection.equals("rtl")) {
mToolbar.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
} else if (mDirection.equals("ltr")) {
mToolbar.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
}
}
if (mIsHidden) {
if (mToolbar.getParent() != null) {
getScreenFragment().removeToolbar();
}
return;
}
if (mToolbar.getParent() == null) {
getScreenFragment().setToolbar(mToolbar);
}
if (mIsTopInsetEnabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mToolbar.setPadding(0, getRootWindowInsets().getSystemWindowInsetTop(), 0, 0);
} else {
// Hacky fallback for old android. Before Marshmallow, the status bar height was always 25
mToolbar.setPadding(0, (int) (25 * getResources().getDisplayMetrics().density), 0, 0);
}
} else {
if (mToolbar.getPaddingTop() > 0) {
mToolbar.setPadding(0, 0, 0, 0);
}
}
activity.setSupportActionBar(mToolbar);
ActionBar actionBar = activity.getSupportActionBar();
// Reset toolbar insets. By default we set symmetric inset for start and end to match iOS
// implementation where both right and left icons are offset from the edge by default. We also
// reset startWithNavigation inset which corresponds to the distance between navigation icon and
// title. If title isn't set we clear that value few lines below to give more space to custom
// center-mounted views.
mToolbar.setContentInsetStartWithNavigation(mDefaultStartInsetWithNavigation);
mToolbar.setContentInsetsRelative(mDefaultStartInset, mDefaultStartInset);
// hide back button
actionBar.setDisplayHomeAsUpEnabled(getScreenFragment().canNavigateBack() ? !mIsBackButtonHidden : false);
// 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
// menu options handlers, but we prefer the back handling logic to stay here instead.
mToolbar.setNavigationOnClickListener(mBackClickListener);
// shadow
getScreenFragment().setToolbarShadowHidden(mIsShadowHidden);
// translucent
getScreenFragment().setToolbarTranslucent(mIsTranslucent);
// title
actionBar.setTitle(mTitle);
if (TextUtils.isEmpty(mTitle)) {
// if title is empty we set start navigation inset to 0 to give more space to custom rendered
// views. When it is set to default it'd take up additional distance from the back button which
// would impact the position of custom header views rendered at the center.
mToolbar.setContentInsetStartWithNavigation(0);
}
TextView titleTextView = getTitleTextView();
if (mTitleColor != 0) {
mToolbar.setTitleTextColor(mTitleColor);
}
if (titleTextView != null) {
if (mTitleFontFamily != null) {
titleTextView.setTypeface(ReactFontManager.getInstance().getTypeface(
mTitleFontFamily, 0, getContext().getAssets()));
}
if (mTitleFontSize > 0) {
titleTextView.setTextSize(mTitleFontSize);
}
}
// background
if (mBackgroundColor != null) {
mToolbar.setBackgroundColor(mBackgroundColor);
}
// color
if (mTintColor != 0) {
Drawable navigationIcon = mToolbar.getNavigationIcon();
if (navigationIcon != null) {
navigationIcon.setColorFilter(mTintColor, PorterDuff.Mode.SRC_ATOP);
}
}
// subviews
for (int i = mToolbar.getChildCount() - 1; i >= 0; i--) {
if (mToolbar.getChildAt(i) instanceof ScreenStackHeaderSubview) {
mToolbar.removeViewAt(i);
}
}
for (int i = 0, size = mConfigSubviews.size(); i < size; i++) {
ScreenStackHeaderSubview view = mConfigSubviews.get(i);
ScreenStackHeaderSubview.Type type = view.getType();
if (type == ScreenStackHeaderSubview.Type.BACK) {
// we special case BACK button header config type as we don't add it as a view into toolbar
// but instead just copy the drawable from imageview that's added as a first child to it.
View firstChild = view.getChildAt(0);
if (!(firstChild instanceof ImageView)) {
throw new JSApplicationIllegalArgumentException("Back button header config view should have Image as first child");
}
actionBar.setHomeAsUpIndicator(((ImageView) firstChild).getDrawable());
continue;
}
Toolbar.LayoutParams params =
new Toolbar.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
switch (type) {
case LEFT:
// when there is a left item we need to disable navigation icon by default
// we also hide title as there is no other way to display left side items
if (!mBackButtonInCustomView) {
mToolbar.setNavigationIcon(null);
}
mToolbar.setTitle(null);
params.gravity = Gravity.START;
break;
case RIGHT:
params.gravity = Gravity.END;
break;
case CENTER:
params.width = LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER_HORIZONTAL;
mToolbar.setTitle(null);
break;
}
view.setLayoutParams(params);
mToolbar.addView(view);
}
}
private void maybeUpdate() {
if (getParent() != null && !mDestroyed) {
onUpdate();
}
}
public ScreenStackHeaderSubview getConfigSubview(int index) {
return mConfigSubviews.get(index);
}
public int getConfigSubviewsCount() {
return mConfigSubviews.size();
}
public void removeConfigSubview(int index) {
mConfigSubviews.remove(index);
maybeUpdate();
}
public void removeAllConfigSubviews() {
mConfigSubviews.clear();
maybeUpdate();
}
public void addConfigSubview(ScreenStackHeaderSubview child, int index) {
mConfigSubviews.add(index, child);
maybeUpdate();
}
private TextView getTitleTextView() {
for (int i = 0, size = mToolbar.getChildCount(); i < size; i++) {
View view = mToolbar.getChildAt(i);
if (view instanceof TextView) {
TextView tv = (TextView) view;
if (tv.getText().equals(mToolbar.getTitle())) {
return tv;
}
}
}
return null;
}
public void setTitle(String title) {
mTitle = title;
}
public void setTitleFontFamily(String titleFontFamily) {
mTitleFontFamily = titleFontFamily;
}
public void setTitleFontSize(float titleFontSize) {
mTitleFontSize = titleFontSize;
}
public void setTitleColor(int color) {
mTitleColor = color;
}
public void setTintColor(int color) {
mTintColor = color;
}
public void setTopInsetEnabled(boolean topInsetEnabled) { mIsTopInsetEnabled = topInsetEnabled; }
public void setBackgroundColor(Integer color) {
mBackgroundColor = color;
}
public void setHideShadow(boolean hideShadow) {
mIsShadowHidden = hideShadow;
}
public void setHideBackButton(boolean hideBackButton) {
mIsBackButtonHidden = hideBackButton;
}
public void setHidden(boolean hidden) {
mIsHidden = hidden;
}
public void setTranslucent(boolean translucent) {
mIsTranslucent = translucent;
}
public void setBackButtonInCustomView(boolean backButtonInCustomView) { mBackButtonInCustomView = backButtonInCustomView; }
public void setDirection(String direction) {
mDirection = direction;
}
}

View File

@ -0,0 +1,142 @@
package com.swmansion.rnscreens;
import android.view.View;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import javax.annotation.Nonnull;
@ReactModule(name = ScreenStackHeaderConfigViewManager.REACT_CLASS)
public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenStackHeaderConfig> {
protected static final String REACT_CLASS = "RNSScreenStackHeaderConfig";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ScreenStackHeaderConfig createViewInstance(ThemedReactContext reactContext) {
return new ScreenStackHeaderConfig(reactContext);
}
@Override
public void addView(ScreenStackHeaderConfig parent, View child, int index) {
if (!(child instanceof ScreenStackHeaderSubview)) {
throw new JSApplicationCausedNativeException("Config children should be of type " + ScreenStackHeaderSubviewManager.REACT_CLASS);
}
parent.addConfigSubview((ScreenStackHeaderSubview) child, index);
}
@Override
public void onDropViewInstance(@Nonnull ScreenStackHeaderConfig view) {
view.destroy();
}
@Override
public void removeAllViews(ScreenStackHeaderConfig parent) {
parent.removeAllConfigSubviews();
}
@Override
public void removeViewAt(ScreenStackHeaderConfig parent, int index) {
parent.removeConfigSubview(index);
}
@Override
public int getChildCount(ScreenStackHeaderConfig parent) {
return parent.getConfigSubviewsCount();
}
@Override
public View getChildAt(ScreenStackHeaderConfig parent, int index) {
return parent.getConfigSubview(index);
}
@Override
public boolean needsCustomLayoutForChildren() {
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);
}
@ReactProp(name = "titleFontFamily")
public void setTitleFontFamily(ScreenStackHeaderConfig config, String titleFontFamily) {
config.setTitleFontFamily(titleFontFamily);
}
@ReactProp(name = "titleFontSize")
public void setTitleFontSize(ScreenStackHeaderConfig config, float titleFontSize) {
config.setTitleFontSize(titleFontSize);
}
@ReactProp(name = "titleColor", customType = "Color")
public void setTitleColor(ScreenStackHeaderConfig config, int titleColor) {
config.setTitleColor(titleColor);
}
@ReactProp(name = "backgroundColor", customType = "Color")
public void setBackgroundColor(ScreenStackHeaderConfig config, Integer backgroundColor) {
config.setBackgroundColor(backgroundColor);
}
@ReactProp(name = "hideShadow")
public void setHideShadow(ScreenStackHeaderConfig config, boolean hideShadow) {
config.setHideShadow(hideShadow);
}
@ReactProp(name = "hideBackButton")
public void setHideBackButton(ScreenStackHeaderConfig config, boolean hideBackButton) {
config.setHideBackButton(hideBackButton);
}
@ReactProp(name = "topInsetEnabled")
public void setTopInsetEnabled(ScreenStackHeaderConfig config, boolean topInsetEnabled) {
config.setTopInsetEnabled(topInsetEnabled);
}
@ReactProp(name = "color", customType = "Color")
public void setColor(ScreenStackHeaderConfig config, int color) {
config.setTintColor(color);
}
@ReactProp(name = "hidden")
public void setHidden(ScreenStackHeaderConfig config, boolean hidden) {
config.setHidden(hidden);
}
@ReactProp(name = "translucent")
public void setTranslucent(ScreenStackHeaderConfig config, boolean translucent) {
config.setTranslucent(translucent);
}
@ReactProp(name = "backButtonInCustomView")
public void setBackButtonInCustomView(ScreenStackHeaderConfig config, boolean backButtonInCustomView) {
config.setBackButtonInCustomView(backButtonInCustomView);
}
@ReactProp(name = "direction")
public void setDirection(ScreenStackHeaderConfig config, String direction) {
config.setDirection(direction);
}
// RCT_EXPORT_VIEW_PROPERTY(backTitle, NSString)
// RCT_EXPORT_VIEW_PROPERTY(backTitleFontFamily, NSString)
// RCT_EXPORT_VIEW_PROPERTY(backTitleFontSize, NSString)
// // `hidden` is an UIView property, we need to use different name internally
}

View File

@ -0,0 +1,55 @@
package com.swmansion.rnscreens;
import android.view.View;
import android.view.ViewParent;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.views.view.ReactViewGroup;
public class ScreenStackHeaderSubview extends ReactViewGroup {
public enum Type {
LEFT,
CENTER,
RIGHT,
BACK
}
private int mReactWidth, mReactHeight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
// dimensions provided by react
mReactWidth = MeasureSpec.getSize(widthMeasureSpec);
mReactHeight = MeasureSpec.getSize(heightMeasureSpec);
ViewParent parent = getParent();
if (parent != null) {
forceLayout();
((View) parent).requestLayout();
}
}
setMeasuredDimension(mReactWidth, mReactHeight);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// no-op
}
private Type mType = Type.RIGHT;
public ScreenStackHeaderSubview(ReactContext context) {
super(context);
}
public void setType(Type type) {
mType = type;
}
public Type getType() {
return mType;
}
}

View File

@ -0,0 +1,36 @@
package com.swmansion.rnscreens;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.view.ReactViewGroup;
import com.facebook.react.views.view.ReactViewManager;
@ReactModule(name = ScreenStackHeaderSubviewManager.REACT_CLASS)
public class ScreenStackHeaderSubviewManager extends ReactViewManager {
protected static final String REACT_CLASS = "RNSScreenStackHeaderSubview";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public ReactViewGroup createViewInstance(ThemedReactContext context) {
return new ScreenStackHeaderSubview(context);
}
@ReactProp(name = "type")
public void setType(ScreenStackHeaderSubview view, String type) {
if ("left".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.LEFT);
} else if ("center".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.CENTER);
} else if ("right".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.RIGHT);
} else if ("back".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.BACK);
}
}
}

View File

@ -0,0 +1,67 @@
package com.swmansion.rnscreens;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
@ReactModule(name = ScreenStackViewManager.REACT_CLASS)
public class ScreenStackViewManager extends ViewGroupManager<ScreenStack> {
protected static final String REACT_CLASS = "RNSScreenStack";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ScreenStack createViewInstance(ThemedReactContext reactContext) {
return new ScreenStack(reactContext);
}
@Override
public void addView(ScreenStack parent, View child, int index) {
if (!(child instanceof Screen)) {
throw new IllegalArgumentException("Attempt attach child that is not of type RNScreen");
}
parent.addScreen((Screen) child, index);
}
@Override
public void removeViewAt(ScreenStack parent, int index) {
prepareOutTransition(parent.getScreenAt(index));
parent.removeScreenAt(index);
}
private void prepareOutTransition(Screen screen) {
startTransitionRecursive(screen);
}
private void startTransitionRecursive(ViewGroup parent) {
for (int i = 0, size = parent.getChildCount(); i < size; i++) {
View child = parent.getChildAt(i);
parent.startViewTransition(child);
if (child instanceof ViewGroup) {
startTransitionRecursive((ViewGroup) child);
}
}
}
@Override
public int getChildCount(ScreenStack parent) {
return parent.getScreenCount();
}
@Override
public View getChildAt(ScreenStack parent, int index) {
return parent.getScreenAt(index);
}
@Override
public boolean needsCustomLayoutForChildren() {
return true;
}
}

View File

@ -0,0 +1,104 @@
package com.swmansion.rnscreens;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.Map;
import javax.annotation.Nullable;
@ReactModule(name = ScreenViewManager.REACT_CLASS)
public class ScreenViewManager extends ViewGroupManager<Screen> {
protected static final String REACT_CLASS = "RNSScreen";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected Screen createViewInstance(ThemedReactContext reactContext) {
return new Screen(reactContext);
}
@ReactProp(name = "activityState")
public void setActivityState(Screen view, Integer activityState) {
if (activityState == null) {
// Null will be provided when activityState is set as an animated value and we change
// it from JS to be a plain value (non animated).
// In case when null is received, we want to ignore such value and not make
// any updates as the actual non-null value will follow immediately.
return;
}
if (activityState == 0) {
view.setActivityState(Screen.ActivityState.INACTIVE);
} else if (activityState == 1) {
view.setActivityState(Screen.ActivityState.TRANSITIONING_OR_BELOW_TOP);
} else if (activityState == 2) {
view.setActivityState(Screen.ActivityState.ON_TOP);
}
}
@ReactProp(name = "stackPresentation")
public void setStackPresentation(Screen view, String presentation) {
if ("push".equals(presentation)) {
view.setStackPresentation(Screen.StackPresentation.PUSH);
} else if ("modal".equals(presentation) || "containedModal".equals(presentation) || "fullScreenModal".equals(presentation) || "formSheet".equals(presentation)) {
// at the moment Android implementation does not handle contained vs regular modals
view.setStackPresentation(Screen.StackPresentation.MODAL);
} 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);
}
}
@ReactProp(name = "stackAnimation")
public void setStackAnimation(Screen view, String animation) {
if (animation == null || "default".equals(animation)) {
view.setStackAnimation(Screen.StackAnimation.DEFAULT);
} else if ("none".equals(animation)) {
view.setStackAnimation(Screen.StackAnimation.NONE);
} else if ("fade".equals(animation)) {
view.setStackAnimation(Screen.StackAnimation.FADE);
}
}
@ReactProp(name = "gestureEnabled", defaultBoolean = true)
public void setGestureEnabled(Screen view, boolean gestureEnabled) {
view.setGestureEnabled(gestureEnabled);
}
@ReactProp(name = "replaceAnimation")
public void setReplaceAnimation(Screen view, String animation) {
if (animation == null || "pop".equals(animation)) {
view.setReplaceAnimation(Screen.ReplaceAnimation.POP);
} else if ("push".equals(animation)) {
view.setReplaceAnimation(Screen.ReplaceAnimation.PUSH);
}
}
@Nullable
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
ScreenDismissedEvent.EVENT_NAME,
MapBuilder.of("registrationName", "onDismissed"),
ScreenWillAppearEvent.EVENT_NAME,
MapBuilder.of("registrationName", "onWillAppear"),
ScreenAppearEvent.EVENT_NAME,
MapBuilder.of("registrationName", "onAppear"),
ScreenWillDisappearEvent.EVENT_NAME,
MapBuilder.of("registrationName", "onWillDisappear"),
ScreenDisappearEvent.EVENT_NAME,
MapBuilder.of("registrationName", "onDisappear"),
StackFinishTransitioningEvent.EVENT_NAME,
MapBuilder.of("registrationName", "onFinishTransitioning"));
}
}

View File

@ -0,0 +1,30 @@
package com.swmansion.rnscreens;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
public class ScreenWillAppearEvent extends Event<ScreenAppearEvent> {
public static final String EVENT_NAME = "topWillAppear";
public ScreenWillAppearEvent(int viewId) {
super(viewId);
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
}
}

View File

@ -0,0 +1,30 @@
package com.swmansion.rnscreens;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
public class ScreenWillDisappearEvent extends Event<ScreenAppearEvent> {
public static final String EVENT_NAME = "topWillDisappear";
public ScreenWillDisappearEvent(int viewId) {
super(viewId);
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
}
}

View File

@ -0,0 +1,29 @@
package com.swmansion.rnscreens;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
public class StackFinishTransitioningEvent extends Event<ScreenAppearEvent> {
public static final String EVENT_NAME = "topFinishTransitioning";
public StackFinishTransitioningEvent(int viewId) {
super(viewId);
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
}
}

View File

@ -0,0 +1,225 @@
# Native Stack Navigator
Provides a way for your app to transition between screens where each new screen is placed on top of a stack.
By default the stack navigator is configured to have the familiar iOS and Android look & feel: new screens slide in from the right on iOS, fade in from the bottom on Android. On iOS, the stack navigator can also be configured to a modal style where screens slide in from the bottom.
This navigator uses native navigation primitives (`UINavigationController` on iOS and `Fragment` on Android) for navigation under the hood. The main difference from React Navigation's JS-based [stack navigator](https://reactnavigation.org/docs/stack-navigator.html) is that the JS-based navigator re-implements animations and gestures while the native stack navigator relies on the platform primitives for animations and gestures. You should use this navigator if you want native feeling and performance for navigation and don't need much customization, as the customization options of this navigator are limited.
```sh
npm install react-native-screens @react-navigation/native
```
Make sure to enable `react-native-screens`. This needs to be done before our app renders. To do it, add the following code in your entry file (e.g. `App.js`):
```js
import { enableScreens } from 'react-native-screens';
enableScreens();
```
## API Definition
To use this navigator, import it from `react-native-screens/createNativeStackNavigator`:
```js
import createNativeStackNavigator from 'react-native-screens/createNativeStackNavigator';
const RootStack = createNativeStackNavigator(
{
Home: HomeScreen,
Details: DetailsScreen,
},
{
initialRouteName: 'Home',
}
);
```
### `navigationOptions` for screens inside of the navigator
#### `backButtonInCustomView`
Boolean indicating whether to hide the back button while using `headerLeft` function.
#### `direction`
String that applies `rtl` or `ltr` form to the stack. On Android, you have to add `android:supportsRtl="true"` in the manifest of your app to enable `rtl`. On Android, if you set the above flag in the manifest, the orientation changes without the need to do it programmatically if the phone has `rtl` direction enabled. On iOS, the direction defaults to `ltr`, and only way to change it is via this prop.
#### `gestureEnabled`
Whether you can use gestures to dismiss this screen. Defaults to `true`,
Gestures are only supported on iOS. They can be disabled only when `stackPresentation` is `push`.
#### `headerBackTitle`
Title string used by the back button on iOS. Defaults to the previous scene's `headerTitle`.
#### `headerBackTitleStyle`
Style object for header back title. Supported properties:
- `fontFamily`
- `fontSize`
#### `headerBackTitleVisible`
Whether the back button title should be visible or not. Defaults to `true`. Only supported on iOS.
#### `headerLeft`
Function which returns a React Element to display on the left side of the header. For now, on Android, using it will cause the title to also disappear.
#### `headerRight`
Function which returns a React Element to display on the right side of the header.
#### `headerHideBackButton`
Boolean indicating whether to hide the back button in the header. Only supported on Android.
#### `headerStyle`
Style object for the header. Supported properties:
- `backgroundColor`
- `blurEffect` (iOS only). Possible values can be checked in `index.d.ts` file.
#### `headerTintColor`
Tint color for the header. Changes the color of the back button and title.
#### `headerTitle`
String to be used by the header as title string. Defaults to scene `title`.
#### `headerTitleStyle`
Style object for header title. Supported properties:
- `fontFamily`
- `fontSize`
- `color`
#### `headerTopInsetEnabled`
A Boolean to that lets you opt out of insetting the header. You may want to * set this to `false` if you use an opaque status bar. Defaults to `true`. Insets are always applied on iOS because the header cannot be opaque. Only supported on Android.
#### `hideShadow`
Boolean indicating whether to hide the elevation shadow on the header.
#### `largeTitle`
Boolean used to set a native property to prefer a large title header (like in iOS setting).
For the large title to collapse on scroll, the content of the screen should be wrapped in a scrollable view such as `ScrollView` or `FlatList`. If the scrollable area doesn't fill the screen, the large title won't collapse on scroll.
Only supported on iOS.
#### `stackAnimation`
How the given screen should appear/disappear when pushed or popped at the top of the stack. Possible values:
- `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/disappears without an animation.
Defaults to `default`.
#### `stackPresentation`
How the screen should be presented. Possible values:
- `push` - The new screen will be pushed onto a stack. The default animation on iOS is to slide from the side. The animation on Android may vary depending on the OS version and theme.
- `modal` - The new screen will be presented modally. In addition, this allows for a nested stack to be rendered inside such screens.
- `transparentModal` - The new screen will be presented modally. In addition, the second to last screen will remain attached to the stack container such that if the top screen is translucent, the content below can still be seen. If `"modal"` is used instead, the below screen gets removed as soon as the transition ends.
- `containedModal` will use "UIModalPresentationCurrentContext" modal style on iOS and will fallback to `"modal"` on Android.
- `containedTransparentModal` will use "UIModalPresentationOverCurrentContext" modal style on iOS and will fallback to `"transparentModal"` on Android.
- `fullScreenModal` will use "UIModalPresentationFullScreen" modal style on iOS and will fallback to `"modal"` on Android.
- `formSheet` will use "UIModalPresentationFormSheet" modal style on iOS and will fallback to `"modal"` on Android.
Defaults to `push`.
#### `title`
A string that can be used as a fallback for `headerTitle`.
#### `translucent`
Boolean indicating whether the navigation bar is translucent.
### Status bar managment
With `native-stack`, the status bar can be managed by `UIViewController` on iOS. It requires:
1. Enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file (it disables the option to use React Native's `StatusBar` component).
2. Adding `#import <RNScreens/UIViewController+RNScreens.h>` in your project's `AppDelegate.m` (you can see this change applied in the `AppDelegate.m` of `Example` project).
#### `statusBarStyle`
Sets the status bar color (similar to the `StatusBar` component). Possible values: `auto` (based on [user interface style](https://developer.apple.com/documentation/uikit/uiuserinterfacestyle?language=objc), `inverted` (colors opposite to `auto`), `light`, `dark`.
Defaults to `auto`.
#### `statusBarAnimation`
Sets the status bar animation (similar to the `StatusBar` component). Possible values: `fade`, `none`, `slide`.
Defaults to `fade`.
#### `statusBarHidden`
Boolean saying if the status bar for this screen is hidden.
Defaults to `false`.
### Helpers
The stack navigator adds the following methods to the navigation prop:
#### `push`
Pushes a new screen to the top of the stack and navigate to it. The method accepts the following arguments:
- `name` - _string_ - Name of the route to push onto the stack.
- `params` - _object_ - Screen params to merge into the destination route (found in the pushed screen through `route.params`).
```js
navigation.push('Profile', { name: 'Wojtek' });
```
#### `pop`
Pops the current screen from the stack and navigates back to the previous screen. It takes one optional argument (`count`), which allows you to specify how many screens to pop back by.
```js
navigation.pop();
```
#### `popToTop`
Pops all of the screens in the stack except the first one and navigates to it.
```js
navigation.popToTop();
```
## Additional options
### Measuring header's height on iOS
Using translucent header on iOS can result in the need of measuring your header's height. In order to do it, you can use `react-native-safe-area-context`. It can be measured like this:
```js
import { useSafeAreaInsets } from 'react-native-safe-area-context';
...
const statusBarInset = useSafeAreaInsets().top; // inset of the status bar
const smallHeaderInset = statusBarInset + 44; // inset to use for a small header since it's frame is equal to 44 + the frame of status bar
const largeHeaderInset = statusBarInset + 96; // inset to use for a large header since it's frame is equal to 96 + the frame of status bar
```
You can also see an example of using these values with a `ScrollView` here: https://snack.expo.io/@wolewicki/ios-header-height.

View File

@ -0,0 +1,5 @@
{
"main": "../lib/commonjs/createNativeStackNavigator",
"module": "../lib/module/createNativeStackNavigator",
"react-native": "../src/createNativeStackNavigator"
}

77
node_modules/react-native-screens/ios/RNSScreen.h generated vendored Normal file
View File

@ -0,0 +1,77 @@
#import <React/RCTViewManager.h>
#import <React/RCTView.h>
#import <React/RCTComponent.h>
#import "RNSScreenContainer.h"
@class RNSScreenContainerView;
typedef NS_ENUM(NSInteger, RNSScreenStackPresentation) {
RNSScreenStackPresentationPush,
RNSScreenStackPresentationModal,
RNSScreenStackPresentationTransparentModal,
RNSScreenStackPresentationContainedModal,
RNSScreenStackPresentationContainedTransparentModal,
RNSScreenStackPresentationFullScreenModal,
RNSScreenStackPresentationFormSheet
};
typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {
RNSScreenStackAnimationDefault,
RNSScreenStackAnimationNone,
RNSScreenStackAnimationFade,
RNSScreenStackAnimationFlip,
};
typedef NS_ENUM(NSInteger, RNSScreenReplaceAnimation) {
RNSScreenReplaceAnimationPop,
RNSScreenReplaceAnimationPush,
};
typedef NS_ENUM(NSInteger, RNSActivityState) {
RNSActivityStateInactive = 0,
RNSActivityStateTransitioningOrBelowTop = 1,
RNSActivityStateOnTop = 2
};
@interface RCTConvert (RNSScreen)
+ (RNSScreenStackPresentation)RNSScreenStackPresentation:(id)json;
+ (RNSScreenStackAnimation)RNSScreenStackAnimation:(id)json;
@end
@interface RNSScreen : UIViewController <RNScreensViewControllerDelegate>
- (instancetype)initWithView:(UIView *)view;
- (void)notifyFinishTransitioning;
@end
@interface RNSScreenManager : RCTViewManager
@end
@interface RNSScreenView : RCTView
@property (nonatomic, copy) RCTDirectEventBlock onAppear;
@property (nonatomic, copy) RCTDirectEventBlock onDisappear;
@property (nonatomic, copy) RCTDirectEventBlock onDismissed;
@property (nonatomic, copy) RCTDirectEventBlock onWillAppear;
@property (nonatomic, copy) RCTDirectEventBlock onWillDisappear;
@property (weak, nonatomic) UIView<RNSScreenContainerDelegate> *reactSuperview;
@property (nonatomic, retain) UIViewController *controller;
@property (nonatomic, readonly) BOOL dismissed;
@property (nonatomic) int activityState;
@property (nonatomic) BOOL gestureEnabled;
@property (nonatomic) RNSScreenStackAnimation stackAnimation;
@property (nonatomic) RNSScreenStackPresentation stackPresentation;
@property (nonatomic) RNSScreenReplaceAnimation replaceAnimation;
- (void)notifyFinishTransitioning;
@end
@interface UIView (RNSScreen)
- (UIViewController *)parentViewController;
@end

532
node_modules/react-native-screens/ios/RNSScreen.m generated vendored Normal file
View File

@ -0,0 +1,532 @@
#import <UIKit/UIKit.h>
#import "RNSScreen.h"
#import "RNSScreenStackHeaderConfig.h"
#import "RNSScreenContainer.h"
#import <React/RCTUIManager.h>
#import <React/RCTShadowView.h>
#import <React/RCTTouchHandler.h>
@interface RNSScreenView () <UIAdaptivePresentationControllerDelegate, RCTInvalidating>
@end
@implementation RNSScreenView {
__weak RCTBridge *_bridge;
RNSScreen *_controller;
RCTTouchHandler *_touchHandler;
CGRect _reactFrame;
}
@synthesize controller = _controller;
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
_controller = [[RNSScreen alloc] initWithView:self];
_stackPresentation = RNSScreenStackPresentationPush;
_stackAnimation = RNSScreenStackAnimationDefault;
_gestureEnabled = YES;
_replaceAnimation = RNSScreenReplaceAnimationPop;
_dismissed = NO;
}
return self;
}
- (void)reactSetFrame:(CGRect)frame
{
_reactFrame = frame;
UIViewController *parentVC = self.reactViewController.parentViewController;
if (parentVC != nil && ![parentVC isKindOfClass:[UINavigationController class]]) {
[super reactSetFrame:frame];
}
// when screen is mounted under UINavigationController it's size is controller
// by the navigation controller itself. That is, it is set to fill space of
// the controller. In that case we ignore react layout system from managing
// the screen dimensions and we wait for the screen VC to update and then we
// pass the dimensions to ui view manager to take into account when laying out
// subviews
}
- (UIViewController *)reactViewController
{
return _controller;
}
- (void)updateBounds
{
[_bridge.uiManager setSize:self.bounds.size forView:self];
}
// Nil will be provided when activityState is set as an animated value and we change
// it from JS to be a plain value (non animated).
// In case when nil is received, we want to ignore such value and not make
// any updates as the actual non-nil value will follow immediately.
- (void)setActivityStateOrNil:(NSNumber *)activityStateOrNil
{
int activityState = [activityStateOrNil intValue];
if (activityStateOrNil != nil && activityState != _activityState) {
_activityState = activityState;
[_reactSuperview markChildUpdated];
}
}
- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
{
// pointer events settings are managed by the parent screen container, we ignore
// any attempt of setting that via React props
}
- (void)setStackPresentation:(RNSScreenStackPresentation)stackPresentation
{
switch (stackPresentation) {
case RNSScreenStackPresentationModal:
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
_controller.modalPresentationStyle = UIModalPresentationAutomatic;
} else {
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
}
#else
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
#endif
break;
case RNSScreenStackPresentationFullScreenModal:
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
break;
#if (TARGET_OS_IOS)
case RNSScreenStackPresentationFormSheet:
_controller.modalPresentationStyle = UIModalPresentationFormSheet;
break;
#endif
case RNSScreenStackPresentationTransparentModal:
_controller.modalPresentationStyle = UIModalPresentationOverFullScreen;
break;
case RNSScreenStackPresentationContainedModal:
_controller.modalPresentationStyle = UIModalPresentationCurrentContext;
break;
case RNSScreenStackPresentationContainedTransparentModal:
_controller.modalPresentationStyle = UIModalPresentationOverCurrentContext;
break;
case RNSScreenStackPresentationPush:
// ignored, we only need to keep in mind not to set presentation delegate
break;
}
// There is a bug in UIKit which causes retain loop when presentationController is accessed for a
// controller that is not going to be presented modally. We therefore need to avoid setting the
// delegate for screens presented using push. This also means that when controller is updated from
// modal to push type, this may cause memory leak, we warn about that as well.
if (stackPresentation != RNSScreenStackPresentationPush) {
// `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;
} else if (_stackPresentation != RNSScreenStackPresentationPush) {
RCTLogError(@"Screen presentation updated from modal to push, this may likely result in a screen object leakage. If you need to change presentation style create a new screen object instead");
}
_stackPresentation = stackPresentation;
}
- (void)setStackAnimation:(RNSScreenStackAnimation)stackAnimation
{
_stackAnimation = stackAnimation;
switch (stackAnimation) {
case RNSScreenStackAnimationFade:
_controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
break;
#if (TARGET_OS_IOS)
case RNSScreenStackAnimationFlip:
_controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
break;
#endif
case RNSScreenStackAnimationNone:
case RNSScreenStackAnimationDefault:
// Default
break;
}
}
- (void)setGestureEnabled:(BOOL)gestureEnabled
{
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
_controller.modalInPresentation = !gestureEnabled;
}
#endif
_gestureEnabled = gestureEnabled;
}
- (void)setReplaceAnimation:(RNSScreenReplaceAnimation)replaceAnimation
{
_replaceAnimation = replaceAnimation;
}
- (UIView *)reactSuperview
{
return _reactSuperview;
}
- (void)addSubview:(UIView *)view
{
if (![view isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
[super addSubview:view];
} else {
((RNSScreenStackHeaderConfig*) view).screenView = self;
}
}
- (void)notifyFinishTransitioning
{
[_controller notifyFinishTransitioning];
}
- (void)notifyDismissed
{
_dismissed = YES;
if (self.onDismissed) {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.onDismissed) {
self.onDismissed(nil);
}
});
}
}
- (void)notifyWillAppear
{
if (self.onWillAppear) {
self.onWillAppear(nil);
}
// we do it here too because at this moment the `parentViewController` is already not nil,
// so if the parent is not UINavCtr, the frame will be updated to the correct one.
[self reactSetFrame:_reactFrame];
}
- (void)notifyWillDisappear
{
if (self.onWillDisappear) {
self.onWillDisappear(nil);
}
}
- (void)notifyAppear
{
if (self.onAppear) {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.onAppear) {
self.onAppear(nil);
}
});
}
}
- (void)notifyDisappear
{
if (self.onDisappear) {
self.onDisappear(nil);
}
}
- (BOOL)isMountedUnderScreenOrReactRoot
{
for (UIView *parent = self.superview; parent != nil; parent = parent.superview) {
if ([parent isKindOfClass:[RCTRootView class]] || [parent isKindOfClass:[RNSScreenView class]]) {
return YES;
}
}
return NO;
}
- (void)didMoveToWindow
{
// For RN touches to work we need to instantiate and connect RCTTouchHandler. This only applies
// for screens that aren't mounted under RCTRootView e.g., modals that are mounted directly to
// root application window.
if (self.window != nil && ![self isMountedUnderScreenOrReactRoot]) {
if (_touchHandler == nil) {
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
}
[_touchHandler attachToView:self];
} else {
[_touchHandler detachFromView:self];
}
}
- (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController
{
// We need to call both "cancel" and "reset" here because RN's gesture recognizer
// does not handle the scenario when it gets cancelled by other top
// level gesture recognizer. In this case by the modal dismiss gesture.
// Because of that, at the moment when this method gets called the React's
// gesture recognizer is already in FAILED state but cancel events never gets
// send to JS. Calling "reset" forces RCTTouchHanler to dispatch cancel event.
// To test this behavior one need to open a dismissable modal and start
// pulling down starting at some touchable item. Without "reset" the touchable
// will never go back from highlighted state even when the modal start sliding
// down.
[_touchHandler cancel];
[_touchHandler reset];
}
- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
{
return _gestureEnabled;
}
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
{
if ([_reactSuperview respondsToSelector:@selector(presentationControllerDidDismiss:)]) {
[_reactSuperview performSelector:@selector(presentationControllerDidDismiss:)
withObject:presentationController];
}
}
- (void)invalidate
{
_controller = nil;
}
@end
@implementation RNSScreen {
__weak id _previousFirstResponder;
CGRect _lastViewFrame;
}
- (instancetype)initWithView:(UIView *)view
{
if (self = [super init]) {
self.view = view;
}
return self;
}
- (UIViewController *)childViewControllerForStatusBarStyle
{
UIViewController *vc = [self findChildVCForConfig];
return vc == self ? nil : vc;
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
RNSScreenStackHeaderConfig *config = [self findScreenConfig];
return [RNSScreenStackHeaderConfig statusBarStyleForRNSStatusBarStyle:config && config.statusBarStyle ? config.statusBarStyle : RNSStatusBarStyleAuto];
}
- (UIViewController *)childViewControllerForStatusBarHidden
{
UIViewController *vc = [self findChildVCForConfig];
return vc == self ? nil : vc;
}
- (BOOL)prefersStatusBarHidden
{
RNSScreenStackHeaderConfig *config = [self findScreenConfig];
return config && config.statusBarHidden ? config.statusBarHidden : NO;
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
UIViewController *vc = [self findChildVCForConfig];
if (vc != self && vc != nil) {
return vc.preferredStatusBarUpdateAnimation;
}
RNSScreenStackHeaderConfig *config = [self findScreenConfig];
return config && config.statusBarAnimation ? config.statusBarAnimation : UIStatusBarAnimationFade;
}
// if the returned vc is a child, it means that it can provide config;
// if the returned vc is self, it means that there is no child for config and self has config to provide,
// so we return self which results in asking self for preferredStatusBarStyle;
// if the returned vc is nil, it means none of children could provide config and self does not have config either,
// so if it was asked by parent, it will fallback to parent's option, or use default option if it is the top Screen
- (UIViewController *)findChildVCForConfig
{
UIViewController *lastViewController = [[self childViewControllers] lastObject];
if (self.presentedViewController != nil) {
lastViewController = self.presentedViewController;
// setting this makes the modal vc being asked for appearance,
// so it doesn't matter what we return here since the modal's root screen will be asked
lastViewController.modalPresentationCapturesStatusBarAppearance = YES;
return nil;
}
UIViewController *selfOrNil = [self findScreenConfig] != nil ? self : nil;
if (lastViewController == nil) {
return selfOrNil;
} else {
if ([lastViewController conformsToProtocol:@protocol(RNScreensViewControllerDelegate)]) {
// If there is a child (should be VC of ScreenContainer or ScreenStack), that has a child that could provide config,
// we recursively go into its findChildVCForConfig, and if one of the children has the config, we return it,
// otherwise we return self if this VC has config, and nil if it doesn't
// we use `childViewControllerForStatusBarStyle` for all options since the behavior is the same for all of them
UIViewController *childScreen = [lastViewController childViewControllerForStatusBarStyle];
if ([childScreen isKindOfClass:[RNSScreen class]]) {
return [(RNSScreen *)childScreen findChildVCForConfig] ?: selfOrNil;
} else {
return selfOrNil;
}
} else {
// child vc is not from this library, so we don't ask it
return selfOrNil;
}
}
}
- (RNSScreenStackHeaderConfig *)findScreenConfig
{
for (UIView *subview in self.view.reactSubviews) {
if ([subview isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
return (RNSScreenStackHeaderConfig *)subview;
}
}
return nil;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// The below code makes the screen view adapt dimensions provided by the system. We take these
// into account only when the view is mounted under UINavigationController in which case system
// provides additional padding to account for possible header, and in the case when screen is
// shown as a native modal, as the final dimensions of the modal on iOS 12+ are shorter than the
// screen size
BOOL isDisplayedWithinUINavController = [self.parentViewController isKindOfClass:[UINavigationController class]];
BOOL isPresentedAsNativeModal = self.parentViewController == nil && self.presentingViewController != nil;
if ((isDisplayedWithinUINavController || isPresentedAsNativeModal) && !CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
_lastViewFrame = self.view.frame;
[((RNSScreenView *)self.viewIfLoaded) updateBounds];
}
}
- (id)findFirstResponder:(UIView*)parent
{
if (parent.isFirstResponder) {
return parent;
}
for (UIView *subView in parent.subviews) {
id responder = [self findFirstResponder:subView];
if (responder != nil) {
return responder;
}
}
return nil;
}
- (void)willMoveToParentViewController:(UIViewController *)parent
{
[super willMoveToParentViewController:parent];
if (parent == nil) {
id responder = [self findFirstResponder:self.view];
if (responder != nil) {
_previousFirstResponder = responder;
}
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[RNSScreenStackHeaderConfig updateStatusBarAppearance];
[((RNSScreenView *)self.view) notifyWillAppear];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[((RNSScreenView *)self.view) notifyWillDisappear];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[((RNSScreenView *)self.view) notifyAppear];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[((RNSScreenView *)self.view) notifyDisappear];
if (self.parentViewController == nil && self.presentingViewController == nil) {
// screen dismissed, send event
[((RNSScreenView *)self.view) notifyDismissed];
}
[self traverseForScrollView:self.view];
}
- (void)traverseForScrollView:(UIView*)view
{
if([view isKindOfClass:[UIScrollView class]] && ([[(UIScrollView*)view delegate] respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) ) {
[[(UIScrollView*)view delegate] scrollViewDidEndDecelerating:(id)view];
}
[view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self traverseForScrollView:obj];
}];
}
- (void)notifyFinishTransitioning
{
[_previousFirstResponder becomeFirstResponder];
_previousFirstResponder = nil;
// the correct Screen for appearance is set after the transition
[RNSScreenStackHeaderConfig updateStatusBarAppearance];
}
@end
@implementation RNSScreenManager
RCT_EXPORT_MODULE()
// we want to handle the case when activityState is nil
RCT_REMAP_VIEW_PROPERTY(activityState, activityStateOrNil, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(replaceAnimation, RNSScreenReplaceAnimation)
RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)
RCT_EXPORT_VIEW_PROPERTY(stackAnimation, RNSScreenStackAnimation)
RCT_EXPORT_VIEW_PROPERTY(onWillAppear, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onWillDisappear, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onAppear, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onDisappear, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onDismissed, RCTDirectEventBlock);
- (UIView *)view
{
return [[RNSScreenView alloc] initWithBridge:self.bridge];
}
@end
@implementation RCTConvert (RNSScreen)
RCT_ENUM_CONVERTER(RNSScreenStackPresentation, (@{
@"push": @(RNSScreenStackPresentationPush),
@"modal": @(RNSScreenStackPresentationModal),
@"fullScreenModal": @(RNSScreenStackPresentationFullScreenModal),
@"formSheet": @(RNSScreenStackPresentationFormSheet),
@"containedModal": @(RNSScreenStackPresentationContainedModal),
@"transparentModal": @(RNSScreenStackPresentationTransparentModal),
@"containedTransparentModal": @(RNSScreenStackPresentationContainedTransparentModal)
}), RNSScreenStackPresentationPush, integerValue)
RCT_ENUM_CONVERTER(RNSScreenStackAnimation, (@{
@"default": @(RNSScreenStackAnimationDefault),
@"none": @(RNSScreenStackAnimationNone),
@"fade": @(RNSScreenStackAnimationFade),
@"flip": @(RNSScreenStackAnimationFlip),
}), RNSScreenStackAnimationDefault, integerValue)
RCT_ENUM_CONVERTER(RNSScreenReplaceAnimation, (@{
@"push": @(RNSScreenReplaceAnimationPush),
@"pop": @(RNSScreenReplaceAnimationPop),
}), RNSScreenReplaceAnimationPop, integerValue)
@end

View File

@ -0,0 +1,19 @@
#import <React/RCTViewManager.h>
@protocol RNSScreenContainerDelegate
- (void)markChildUpdated;
@end
@protocol RNScreensViewControllerDelegate
@end
@interface RNScreensViewController: UIViewController <RNScreensViewControllerDelegate>
@end
@interface RNSScreenContainerView : UIView <RNSScreenContainerDelegate>
@end

View File

@ -0,0 +1,279 @@
#import "RNSScreenContainer.h"
#import "RNSScreen.h"
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import <React/RCTUIManagerUtils.h>
@interface RNSScreenContainerManager : RCTViewManager
- (void)markUpdated:(RNSScreenContainerView *)screen;
@end
@interface RNSScreenContainerView () <RCTInvalidating>
@property (nonatomic, retain) UIViewController *controller;
@property (nonatomic, retain) NSMutableSet<RNSScreenView *> *activeScreens;
@property (nonatomic, retain) NSMutableArray<RNSScreenView *> *reactSubviews;
- (void)updateContainer;
@end
@implementation RNScreensViewController
- (UIViewController *)childViewControllerForStatusBarStyle
{
return [self findActiveChildVC];
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return [self findActiveChildVC].preferredStatusBarUpdateAnimation;
}
- (UIViewController *)childViewControllerForStatusBarHidden
{
return [self findActiveChildVC];
}
- (UIViewController *)findActiveChildVC
{
for (UIViewController *childVC in self.childViewControllers) {
if ([childVC isKindOfClass:[RNSScreen class]] && ((RNSScreenView *)((RNSScreen *)childVC.view)).activityState == RNSActivityStateOnTop) {
return childVC;
}
}
return [[self childViewControllers] lastObject];
}
@end
@implementation RNSScreenContainerView {
BOOL _needUpdate;
BOOL _invalidated;
__weak RNSScreenContainerManager *_manager;
}
- (instancetype)initWithManager:(RNSScreenContainerManager *)manager
{
if (self = [super init]) {
_activeScreens = [NSMutableSet new];
_reactSubviews = [NSMutableArray new];
_controller = [[RNScreensViewController alloc] init];
_needUpdate = NO;
_invalidated = NO;
_manager = manager;
[self addSubview:_controller.view];
}
return self;
}
- (void)markChildUpdated
{
// We want 'updateContainer' to be executed on main thread after all enqueued operations in
// uimanager are complete. For that we collect all marked containers in manager class and enqueue
// operation on ui thread that should run once all the updates are completed.
if (!_needUpdate) {
_needUpdate = YES;
[_manager markUpdated:self];
}
}
- (void)insertReactSubview:(RNSScreenView *)subview atIndex:(NSInteger)atIndex
{
subview.reactSuperview = self;
[_reactSubviews insertObject:subview atIndex:atIndex];
[subview reactSetFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
}
- (void)removeReactSubview:(RNSScreenView *)subview
{
subview.reactSuperview = nil;
[_reactSubviews removeObject:subview];
}
- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
}
- (UIViewController *)reactViewController
{
return _controller;
}
- (UIViewController*)findChildControllerForScreen:(RNSScreenView*)screen
{
for (UIViewController *vc in _controller.childViewControllers) {
if (vc.view == screen) {
return vc;
}
}
return nil;
}
- (void)prepareDetach:(RNSScreenView *)screen
{
[[self findChildControllerForScreen:screen] willMoveToParentViewController:nil];
}
- (void)detachScreen:(RNSScreenView *)screen
{
// We use findChildControllerForScreen method instead of directly accesing
// screen.controller because screen.controller may be reset to nil when the
// original screen view gets detached from the view hierarchy (we reset controller
// reference to avoid reference loops)
UIViewController *detachController = [self findChildControllerForScreen:screen];
[detachController willMoveToParentViewController:nil];
[screen removeFromSuperview];
[detachController removeFromParentViewController];
[_activeScreens removeObject:screen];
}
- (void)attachScreen:(RNSScreenView *)screen atIndex:(NSInteger)index
{
[_controller addChildViewController:screen.controller];
// the frame is already set at this moment because we adjust it in insertReactSubview. We don't
// want to update it here as react-driven animations may already run and hence changing the frame
// would result in visual glitches
[_controller.view insertSubview:screen.controller.view atIndex:index];
[screen.controller didMoveToParentViewController:_controller];
[_activeScreens addObject:screen];
}
- (void)updateContainer
{
_needUpdate = NO;
BOOL screenRemoved = NO;
// remove screens that are no longer active
NSMutableSet *orphaned = [NSMutableSet setWithSet:_activeScreens];
for (RNSScreenView *screen in _reactSubviews) {
if (screen.activityState == RNSActivityStateInactive && [_activeScreens containsObject:screen]) {
screenRemoved = YES;
[self detachScreen:screen];
}
[orphaned removeObject:screen];
}
for (RNSScreenView *screen in orphaned) {
screenRemoved = YES;
[self detachScreen:screen];
}
// detect if new screen is going to be activated
BOOL screenAdded = NO;
for (RNSScreenView *screen in _reactSubviews) {
if (screen.activityState != RNSActivityStateInactive && ![_activeScreens containsObject:screen]) {
screenAdded = YES;
}
}
if (screenAdded) {
// add new screens in order they are placed in subviews array
NSInteger index = 0;
for (RNSScreenView *screen in _reactSubviews) {
if (screen.activityState != RNSActivityStateInactive) {
if ([_activeScreens containsObject:screen] && screen.activityState == RNSActivityStateTransitioningOrBelowTop) {
// for screens that were already active we want to mimick the effect UINavigationController
// has when willMoveToWindow:nil is triggered before the animation starts
[self prepareDetach:screen];
} else if (![_activeScreens containsObject:screen]) {
[self attachScreen:screen atIndex:index];
}
index += 1;
}
}
}
if (screenRemoved || screenAdded) {
// we disable interaction for the duration of the transition until one of the screens changes its state to "onTop"
self.userInteractionEnabled = NO;
}
for (RNSScreenView *screen in _reactSubviews) {
if (screen.activityState == RNSActivityStateOnTop) {
// if there is an "onTop" screen it means the transition has ended so we restore interactions
self.userInteractionEnabled = YES;
[screen notifyFinishTransitioning];
}
}
if ((screenRemoved || screenAdded) && _controller.presentedViewController == nil && _controller.presentingViewController == nil) {
// if user has reachability enabled (one hand use) and the window is slided down the below
// method will force it to slide back up as it is expected to happen with UINavController when
// we push or pop views.
// We only do that if `presentedViewController` is nil, as otherwise it'd mean that modal has
// been presented on top of recently changed controller in which case the below method would
// dismiss such a modal (e.g., permission modal or alert)
[_controller dismissViewControllerAnimated:NO completion:nil];
}
}
- (void)didUpdateReactSubviews
{
[self markChildUpdated];
}
- (void)didMoveToWindow
{
if (self.window && !_invalidated) {
// We check whether the view has been invalidated before running side-effects in didMoveToWindow
// This is needed because when LayoutAnimations are used it is possible for view to be re-attached
// to a window despite the fact it has been removed from the React Native view hierarchy.
[self reactAddControllerToClosestParent:_controller];
}
}
- (void)invalidate
{
_invalidated = YES;
[_controller willMoveToParentViewController:nil];
[_controller removeFromParentViewController];
}
- (void)layoutSubviews
{
[super layoutSubviews];
_controller.view.frame = self.bounds;
for (RNSScreenView *subview in _reactSubviews) {
[subview reactSetFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
[subview setNeedsLayout];
}
}
@end
@implementation RNSScreenContainerManager {
NSMutableArray<RNSScreenContainerView *> *_markedContainers;
}
RCT_EXPORT_MODULE()
- (UIView *)view
{
if (!_markedContainers) {
_markedContainers = [NSMutableArray new];
}
return [[RNSScreenContainerView alloc] initWithManager:self];
}
- (void)markUpdated:(RNSScreenContainerView *)screen
{
RCTAssertMainQueue();
[_markedContainers addObject:screen];
if ([_markedContainers count] == 1) {
// we enqueue updates to be run on the main queue in order to make sure that
// all this updates (new screens attached etc) are executed in one batch
RCTExecuteOnMainQueue(^{
for (RNSScreenContainerView *container in self->_markedContainers) {
[container updateContainer];
}
[self->_markedContainers removeAllObjects];
});
}
}
@end

21
node_modules/react-native-screens/ios/RNSScreenStack.h generated vendored Normal file
View File

@ -0,0 +1,21 @@
#import <React/RCTViewManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import "RNSScreenContainer.h"
@interface RNScreensNavigationController: UINavigationController <RNScreensViewControllerDelegate>
@end
@interface RNSScreenStackView : UIView <RNSScreenContainerDelegate, RCTInvalidating>
@property (nonatomic, copy) RCTDirectEventBlock onFinishTransitioning;
- (void)markChildUpdated;
- (void)didUpdateChildren;
@end
@interface RNSScreenStackManager : RCTViewManager <RCTInvalidating>
@end

583
node_modules/react-native-screens/ios/RNSScreenStack.m generated vendored Normal file
View File

@ -0,0 +1,583 @@
#import "RNSScreenStack.h"
#import "RNSScreen.h"
#import "RNSScreenStackHeaderConfig.h"
#import <React/RCTBridge.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import <React/RCTShadowView.h>
#import <React/RCTRootContentView.h>
#import <React/RCTTouchHandler.h>
@interface RNSScreenStackView () <UINavigationControllerDelegate, UIAdaptivePresentationControllerDelegate, UIGestureRecognizerDelegate>
@property (nonatomic) NSMutableArray<UIViewController *> *presentedModals;
@property (nonatomic) BOOL updatingModals;
@property (nonatomic) BOOL scheduleModalsUpdate;
@end
@implementation RNScreensNavigationController
- (UIViewController *)childViewControllerForStatusBarStyle
{
return [self topViewController];
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return [self topViewController].preferredStatusBarUpdateAnimation;
}
- (UIViewController *)childViewControllerForStatusBarHidden
{
return [self topViewController];
}
@end
@interface RNSScreenStackAnimator : NSObject <UIViewControllerAnimatedTransitioning>
- (instancetype)initWithOperation:(UINavigationControllerOperation)operation;
@end
@implementation RNSScreenStackView {
UINavigationController *_controller;
NSMutableArray<RNSScreenView *> *_reactSubviews;
__weak RNSScreenStackManager *_manager;
BOOL _hasLayout;
BOOL _invalidated;
}
- (instancetype)initWithManager:(RNSScreenStackManager*)manager
{
if (self = [super init]) {
_hasLayout = NO;
_invalidated = NO;
_manager = manager;
_reactSubviews = [NSMutableArray new];
_presentedModals = [NSMutableArray new];
_controller = [[RNScreensNavigationController alloc] init];
_controller.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;
}
- (UIViewController *)reactViewController
{
return _controller;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
UIView *view = viewController.view;
RNSScreenStackHeaderConfig *config = nil;
for (UIView *subview in view.reactSubviews) {
if ([subview isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
config = (RNSScreenStackHeaderConfig*) subview;
break;
}
}
[RNSScreenStackHeaderConfig willShowViewController:viewController animated:animated withConfig:config];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (self.onFinishTransitioning) {
self.onFinishTransitioning(nil);
}
}
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
{
// We don't directly set presentation delegate but instead rely on the ScreenView's delegate to
// forward certain calls to the container (Stack).
UIView *screenView = presentationController.presentedViewController.view;
if ([screenView isKindOfClass:[RNSScreenView class]]) {
// we trigger the update of status bar's appearance here because there is no other lifecycle method
// that can handle it when dismissing a modal
[RNSScreenStackHeaderConfig updateStatusBarAppearance];
[_presentedModals removeObject:presentationController.presentedViewController];
if (self.onFinishTransitioning) {
// instead of directly triggering onFinishTransitioning this time we enqueue the event on the
// main queue. We do that because onDismiss event is also enqueued and we want for the transition
// finish event to arrive later than onDismiss (see RNSScreen#notifyDismiss)
dispatch_async(dispatch_get_main_queue(), ^{
if (self.onFinishTransitioning) {
self.onFinishTransitioning(nil);
}
});
}
}
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
RNSScreenView *screen;
if (operation == UINavigationControllerOperationPush) {
screen = (RNSScreenView *) toVC.view;
} else if (operation == UINavigationControllerOperationPop) {
screen = (RNSScreenView *) fromVC.view;
}
if (screen != nil && (screen.stackAnimation == RNSScreenStackAnimationFade || screen.stackAnimation == RNSScreenStackAnimationNone)) {
return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
}
return nil;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// cancel touches in parent, this is needed to cancel RN touch events. For example when Touchable
// item is close to an edge and we start pulling from edge we want the Touchable to be cancelled.
// 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:[RCTRootContentView class]]) parent = parent.superview;
RCTRootContentView *rootView = (RCTRootContentView *)parent;
[rootView.touchHandler cancel];
RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;
return _controller.viewControllers.count > 1 && topScreen.gestureEnabled;
}
- (void)markChildUpdated
{
// do nothing
}
- (void)didUpdateChildren
{
// do nothing
}
- (void)insertReactSubview:(RNSScreenView *)subview atIndex:(NSInteger)atIndex
{
if (![subview isKindOfClass:[RNSScreenView class]]) {
RCTLogError(@"ScreenStack only accepts children of type Screen");
return;
}
subview.reactSuperview = self;
[_reactSubviews insertObject:subview atIndex:atIndex];
}
- (void)removeReactSubview:(RNSScreenView *)subview
{
subview.reactSuperview = nil;
[_reactSubviews removeObject:subview];
}
- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
}
- (void)didUpdateReactSubviews
{
// we need to wait until children have their layout set. At this point they don't have the layout
// set yet, however the layout call is already enqueued on ui thread. Enqueuing update call on the
// ui queue will guarantee that the update will run after layout.
dispatch_async(dispatch_get_main_queue(), ^{
self->_hasLayout = YES;
[self maybeAddToParentAndUpdateContainer];
});
}
- (void)didMoveToWindow
{
[super didMoveToWindow];
if (!_invalidated) {
// We check whether the view has been invalidated before running side-effects in didMoveToWindow
// This is needed because when LayoutAnimations are used it is possible for view to be re-attached
// to a window despite the fact it has been removed from the React Native view hierarchy.
[self maybeAddToParentAndUpdateContainer];
}
}
- (void)maybeAddToParentAndUpdateContainer
{
BOOL wasScreenMounted = _controller.parentViewController != nil;
BOOL isScreenReadyForShowing = self.window && _hasLayout;
if (!isScreenReadyForShowing && !wasScreenMounted) {
// We wait with adding to parent controller until the stack is mounted and has its initial
// layout done.
// If we add it before layout, some of the items (specifically items from the navigation bar),
// won't be able to position properly. Also the position and size of such items, even if it
// happens to change, won't be properly updated (this is perhaps some internal issue of UIKit).
// If we add it when window is not attached, some of the view transitions will be bloced (i.e.
// modal transitions) and the internal view controler's state will get out of sync with what's
// on screen without us knowing.
return;
}
[self updateContainer];
if (!wasScreenMounted) {
// when stack hasn't been added to parent VC yet we do two things:
// 1) we run updateContainer (the one above) we do this because we want push view controllers to
// be installed before the VC is mounted. If we do that after it is added to parent the push
// updates operations are going to be blocked by UIKit.
// 2) we add navigation VS to parent this is needed for the VC lifecycle events to be dispatched
// properly
// 3) we again call updateContainer this time we do this to open modal controllers. Modals
// won't open in (1) because they require navigator to be added to parent. We handle that case
// gracefully in setModalViewControllers and can retry opening at any point.
[self reactAddControllerToClosestParent:_controller];
[self updateContainer];
}
}
- (void)reactAddControllerToClosestParent:(UIViewController *)controller
{
if (!controller.parentViewController) {
UIView *parentView = (UIView *)self.reactSuperview;
while (parentView) {
if (parentView.reactViewController) {
[parentView.reactViewController addChildViewController:controller];
[self addSubview:controller.view];
#if (TARGET_OS_IOS)
_controller.interactivePopGestureRecognizer.delegate = self;
#endif
[controller didMoveToParentViewController:parentView.reactViewController];
// On iOS pre 12 we observed that `willShowViewController` delegate method does not always
// get triggered when the navigation controller is instantiated. As the only thing we do in
// that delegate method is ask nav header to update to the current state it does not hurt to
// trigger that logic from here too such that we can be sure the header is properly updated.
[self navigationController:_controller willShowViewController:_controller.topViewController animated:NO];
break;
}
parentView = (UIView *)parentView.reactSuperview;
}
return;
}
}
- (void)setModalViewControllers:(NSArray<UIViewController *> *)controllers
{
// prevent re-entry
if (_updatingModals) {
_scheduleModalsUpdate = YES;
return;
}
// when there is no change we return immediately. This check is important because sometime we may
// accidently trigger modal dismiss if we don't verify to run the below code only when an actual
// change in the list of presented modal was made.
if ([_presentedModals isEqualToArray:controllers]) {
return;
}
// if view controller is not yet attached to window we skip updates now and run them when view
// is attached
if (self.window == nil && _presentedModals.lastObject.view.window == nil) {
return;
}
_updatingModals = YES;
NSMutableArray<UIViewController *> *newControllers = [NSMutableArray arrayWithArray:controllers];
[newControllers removeObjectsInArray:_presentedModals];
// find bottom-most controller that should stay on the stack for the duration of transition
NSUInteger changeRootIndex = 0;
UIViewController *changeRootController = _controller;
for (NSUInteger i = 0; i < MIN(_presentedModals.count, controllers.count); i++) {
if (_presentedModals[i] == controllers[i]) {
changeRootController = controllers[i];
changeRootIndex = i + 1;
} else {
break;
}
}
// we verify that controllers added on top of changeRootIndex are all new. Unfortunately modal
// VCs cannot be reshuffled (there are some visual glitches when we try to dismiss then show as
// even non-animated dismissal has delay and updates the screen several times)
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
if ([_presentedModals containsObject:controllers[i]]) {
RCTAssert(false, @"Modally presented controllers are being reshuffled, this is not allowed");
}
}
__weak RNSScreenStackView *weakSelf = self;
void (^afterTransitions)(void) = ^{
if (weakSelf.onFinishTransitioning) {
weakSelf.onFinishTransitioning(nil);
}
weakSelf.updatingModals = NO;
if (weakSelf.scheduleModalsUpdate) {
// if modals update was requested during setModalViewControllers we set scheduleModalsUpdate
// flag in order to perform updates at a later point. Here we are done with all modals
// transitions and check this flag again. If it was set, we reset the flag and execute updates.
weakSelf.scheduleModalsUpdate = NO;
[weakSelf updateContainer];
}
};
void (^finish)(void) = ^{
NSUInteger oldCount = weakSelf.presentedModals.count;
if (changeRootIndex < oldCount) {
[weakSelf.presentedModals
removeObjectsInRange:NSMakeRange(changeRootIndex, oldCount - changeRootIndex)];
}
BOOL isAttached = changeRootController.parentViewController != nil || changeRootController.presentingViewController != nil;
if (!isAttached || changeRootIndex >= controllers.count) {
// if change controller view is not attached, presenting modals will silently fail on iOS.
// In such a case we trigger controllers update from didMoveToWindow.
// We also don't run any present transitions if changeRootIndex is greater or equal to the size
// of new controllers array. This means that no new controllers should be presented.
afterTransitions();
return;
} else {
UIViewController *previous = changeRootController;
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
UIViewController *next = controllers[i];
BOOL lastModal = (i == controllers.count - 1);
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
// Inherit UI style from its parent - solves an issue with incorrect style being applied to some UIKit views like date picker or segmented control.
next.overrideUserInterfaceStyle = _controller.overrideUserInterfaceStyle;
}
#endif
BOOL shouldAnimate = lastModal && [next isKindOfClass:[RNSScreen class]] && ((RNSScreenView *) next.view).stackAnimation != RNSScreenStackAnimationNone;
[previous presentViewController:next
animated:shouldAnimate
completion:^{
[weakSelf.presentedModals addObject:next];
if (lastModal) {
afterTransitions();
};
}];
previous = next;
}
}
};
if (changeRootController.presentedViewController != nil
&& [_presentedModals containsObject:changeRootController.presentedViewController]) {
BOOL shouldAnimate = changeRootIndex == controllers.count && [changeRootController.presentedViewController isKindOfClass:[RNSScreen class]] && ((RNSScreenView *) changeRootController.presentedViewController.view).stackAnimation != RNSScreenStackAnimationNone;
[changeRootController
dismissViewControllerAnimated:shouldAnimate
completion:finish];
} else {
finish();
}
}
- (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers
{
// when there is no change we return immediately
if ([_controller.viewControllers isEqualToArray:controllers]) {
return;
}
// if view controller is not yet attached to window we skip updates now and run them when view
// is attached
if (self.window == nil) {
return;
}
// when transition is ongoing, any updates made to the controller will not be reflected until the
// transition is complete. In particular, when we push/pop view controllers we expect viewControllers
// property to be updated immediately. Based on that property we then calculate future updates.
// When the transition is ongoing the property won't be updated immediatly. We therefore avoid
// making any updated when transition is ongoing and schedule updates for when the transition
// is complete.
if (_controller.transitionCoordinator != nil) {
__weak RNSScreenStackView *weakSelf = self;
[_controller.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// do nothing here, we only want to be notified when transition is complete
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf updateContainer];
}];
return;
}
UIViewController *top = controllers.lastObject;
UIViewController *lastTop = _controller.viewControllers.lastObject;
// 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]];
BOOL shouldAnimate = !firstTimePush && ((RNSScreenView *) lastTop.view).stackAnimation != RNSScreenStackAnimationNone;
if (firstTimePush) {
// nothing pushed yet
[_controller setViewControllers:controllers animated:NO];
} else if (top != lastTop) {
if (![controllers containsObject:lastTop]) {
// if the previous top screen does not exist anymore and the new top was not on the stack before, probably replace was called, so we check the animation
if ( ![_controller.viewControllers containsObject:top] && ((RNSScreenView *) top.view).replaceAnimation == RNSScreenReplaceAnimationPush) {
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:controllers];
[_controller pushViewController:top animated:shouldAnimate];
[_controller setViewControllers:newControllers animated:NO];
} else {
// last top controller is no longer on stack
// in this case we set the controllers stack to the new list with
// added the last top element to it and perform (animated) pop
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:controllers];
[newControllers addObject:lastTop];
[_controller setViewControllers:newControllers animated:NO];
[_controller popViewControllerAnimated:shouldAnimate];
}
} else if (![_controller.viewControllers containsObject:top]) {
// new top controller is not on the stack
// in such case we update the stack except from the last element with
// no animation and do animated push of the last item
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:controllers];
[newControllers removeLastObject];
[_controller setViewControllers:newControllers animated:NO];
[_controller pushViewController:top animated:shouldAnimate];
} else {
// don't really know what this case could be, but may need to handle it
// somehow
[_controller setViewControllers:controllers animated:shouldAnimate];
}
} else {
// change wasn't on the top of the stack. We don't need animation.
[_controller setViewControllers:controllers animated:NO];
}
}
- (void)updateContainer
{
NSMutableArray<UIViewController *> *pushControllers = [NSMutableArray new];
NSMutableArray<UIViewController *> *modalControllers = [NSMutableArray new];
for (RNSScreenView *screen in _reactSubviews) {
if (!screen.dismissed && screen.controller != nil) {
if (pushControllers.count == 0) {
// first screen on the list needs to be places as "push controller"
[pushControllers addObject:screen.controller];
} else {
if (screen.stackPresentation == RNSScreenStackPresentationPush) {
[pushControllers addObject:screen.controller];
} else {
[modalControllers addObject:screen.controller];
}
}
}
}
[self setPushViewControllers:pushControllers];
[self setModalViewControllers:modalControllers];
}
- (void)layoutSubviews
{
[super layoutSubviews];
_controller.view.frame = self.bounds;
}
- (void)invalidate
{
_invalidated = YES;
for (UIViewController *controller in _presentedModals) {
[controller dismissViewControllerAnimated:NO completion:nil];
}
[_presentedModals removeAllObjects];
[_controller willMoveToParentViewController:nil];
[_controller removeFromParentViewController];
}
- (void)dismissOnReload
{
dispatch_async(dispatch_get_main_queue(), ^{
[self invalidate];
});
}
@end
@implementation RNSScreenStackManager {
NSPointerArray *_stacks;
}
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(onFinishTransitioning, RCTDirectEventBlock);
- (UIView *)view
{
RNSScreenStackView *view = [[RNSScreenStackView alloc] initWithManager:self];
if (!_stacks) {
_stacks = [NSPointerArray weakObjectsPointerArray];
}
[_stacks addPointer:(__bridge void *)view];
return view;
}
- (void)invalidate
{
for (RNSScreenStackView *stack in _stacks) {
[stack dismissOnReload];
}
_stacks = nil;
}
@end
@implementation RNSScreenStackAnimator {
UINavigationControllerOperation _operation;
}
- (instancetype)initWithOperation:(UINavigationControllerOperation)operation
{
if (self = [super init]) {
_operation = operation;
}
return self;
}
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
RNSScreenView *screen;
if (_operation == UINavigationControllerOperationPush) {
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
screen = (RNSScreenView *)toViewController.view;
} else if (_operation == UINavigationControllerOperationPop) {
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
screen = (RNSScreenView *)fromViewController.view;
}
if (screen != nil && screen.stackAnimation == RNSScreenStackAnimationNone) {
return 0;
}
return 0.35; // default duration
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
if (_operation == UINavigationControllerOperationPush) {
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else if (_operation == UINavigationControllerOperationPop) {
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.alpha = 0.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
}
@end

View File

@ -0,0 +1,74 @@
#import <React/RCTViewManager.h>
#import <React/RCTConvert.h>
#import "RNSScreen.h"
typedef NS_ENUM(NSInteger, RNSStatusBarStyle) {
RNSStatusBarStyleAuto,
RNSStatusBarStyleInverted,
RNSStatusBarStyleLight,
RNSStatusBarStyleDark,
};
@interface RNSScreenStackHeaderConfig : UIView
@property (nonatomic, weak) RNSScreenView *screenView;
@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *titleFontFamily;
@property (nonatomic, retain) NSNumber *titleFontSize;
@property (nonatomic, retain) UIColor *titleColor;
@property (nonatomic, retain) NSString *backTitle;
@property (nonatomic, retain) NSString *backTitleFontFamily;
@property (nonatomic, retain) NSNumber *backTitleFontSize;
@property (nonatomic, retain) UIColor *backgroundColor;
@property (nonatomic) UIBlurEffectStyle blurEffect;
@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, retain) UIColor *largeTitleBackgroundColor;
@property (nonatomic) BOOL largeTitleHideShadow;
@property (nonatomic, retain) UIColor *largeTitleColor;
@property (nonatomic) BOOL hideBackButton;
@property (nonatomic) BOOL backButtonInCustomView;
@property (nonatomic) BOOL hideShadow;
@property (nonatomic) BOOL translucent;
@property (nonatomic) UISemanticContentAttribute direction;
@property (nonatomic) RNSStatusBarStyle statusBarStyle;
@property (nonatomic) UIStatusBarAnimation statusBarAnimation;
@property (nonatomic) BOOL statusBarHidden;
+ (void)willShowViewController:(UIViewController *)vc animated:(BOOL)animated withConfig:(RNSScreenStackHeaderConfig*)config;
+ (void)updateStatusBarAppearance;
+ (UIStatusBarStyle)statusBarStyleForRNSStatusBarStyle:(RNSStatusBarStyle)statusBarStyle;
@end
@interface RNSScreenStackHeaderConfigManager : RCTViewManager
@end
typedef NS_ENUM(NSInteger, RNSScreenStackHeaderSubviewType) {
RNSScreenStackHeaderSubviewTypeBackButton,
RNSScreenStackHeaderSubviewTypeLeft,
RNSScreenStackHeaderSubviewTypeRight,
RNSScreenStackHeaderSubviewTypeTitle,
RNSScreenStackHeaderSubviewTypeCenter,
};
@interface RCTConvert (RNSScreenStackHeader)
+ (RNSScreenStackHeaderSubviewType)RNSScreenStackHeaderSubviewType:(id)json;
+ (UIBlurEffectStyle)UIBlurEffectStyle:(id)json;
+ (UISemanticContentAttribute)UISemanticContentAttribute:(id)json;
+ (RNSStatusBarStyle)RNSStatusBarStyle:(id)json;
@end
@interface RNSScreenStackHeaderSubviewManager : RCTViewManager
@property (nonatomic) RNSScreenStackHeaderSubviewType type;
@end

View File

@ -0,0 +1,664 @@
#import "RNSScreenStackHeaderConfig.h"
#import "RNSScreen.h"
#import <React/RCTBridge.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import <React/RCTShadowView.h>
#import <React/RCTImageLoader.h>
#import <React/RCTImageView.h>
#import <React/RCTImageSource.h>
#import <React/RCTFont.h>
// Some RN private method hacking below. Couldn't figure out better way to access image data
// of a given RCTImageView. See more comments in the code section processing SubviewTypeBackButton
@interface RCTImageView (Private)
- (UIImage*)image;
@end
@interface RCTImageLoader (Private)
- (id<RCTImageCache>)imageCache;
@end
@interface RNSScreenStackHeaderSubview : UIView
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, weak) UIView *reactSuperview;
@property (nonatomic) RNSScreenStackHeaderSubviewType type;
- (instancetype)initWithBridge:(RCTBridge*)bridge;
@end
@implementation RNSScreenStackHeaderSubview
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
}
return self;
}
@end
@implementation RNSScreenStackHeaderConfig {
NSMutableArray<RNSScreenStackHeaderSubview *> *_reactSubviews;
}
- (instancetype)init
{
if (self = [super init]) {
self.hidden = YES;
_translucent = YES;
_reactSubviews = [NSMutableArray new];
}
return self;
}
- (void)insertReactSubview:(RNSScreenStackHeaderSubview *)subview atIndex:(NSInteger)atIndex
{
[_reactSubviews insertObject:subview atIndex:atIndex];
subview.reactSuperview = self;
}
- (void)removeReactSubview:(RNSScreenStackHeaderSubview *)subview
{
[_reactSubviews removeObject:subview];
}
- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
}
- (UIView *)reactSuperview
{
return _screenView;
}
- (void)removeFromSuperview
{
[super removeFromSuperview];
_screenView = nil;
}
- (void)updateViewControllerIfNeeded
{
UIViewController *vc = _screenView.controller;
UINavigationController *nav = (UINavigationController*) vc.parentViewController;
UIViewController *nextVC = nav.visibleViewController;
if (nav.transitionCoordinator != nil) {
// if navigator is performing transition instead of allowing to update of `visibleConttroller`
// we look at `topController`. This is because during transitiong the `visibleController` won't
// point to the controller that is going to be revealed after transition. This check fixes the
// problem when config gets updated while the transition is ongoing.
nextVC = nav.topViewController;
}
if (vc != nil && nextVC == vc) {
[RNSScreenStackHeaderConfig updateViewController:self.screenView.controller
withConfig:self
animated:YES];
}
}
- (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;
// It is workaround for loading custom back icon when transitioning from a screen without header to the screen which has one.
// This action fails when navigating to the screen with header for the second time and loads default back button.
// It looks like changing the tint color of navbar triggers an update of the items belonging to it and it seems to load the custom back image
// so we change the tint color's alpha by a very small amount and then set it to the one it should have.
[navbar setTintColor:[config.color colorWithAlphaComponent:CGColorGetAlpha(config.color.CGColor) - 0.01]];
[navbar setTintColor:config.color];
#if defined(__IPHONE_13_0) && TARGET_OS_IOS
if (@available(iOS 13.0, *)) {
// font customized on the navigation item level, so nothing to do here
} else
#endif
{
BOOL hideShadow = config.hideShadow;
if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
[navbar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[navbar setBarTintColor:[UIColor clearColor]];
hideShadow = YES;
} else {
[navbar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
[navbar setBarTintColor:config.backgroundColor];
}
[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;
}
NSNumber *size = config.titleFontSize ?: @17;
if (config.titleFontFamily) {
attrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.titleFontFamily size:size weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
}
[navbar setTitleTextAttributes:attrs];
}
#if (TARGET_OS_IOS)
if (@available(iOS 11.0, *)) {
if (config.largeTitle && (config.largeTitleFontFamily || config.largeTitleFontSize || config.largeTitleColor || config.titleColor)) {
NSMutableDictionary *largeAttrs = [NSMutableDictionary new];
if (config.largeTitleColor || config.titleColor) {
largeAttrs[NSForegroundColorAttributeName] = config.largeTitleColor ? config.largeTitleColor : config.titleColor;
}
NSNumber *largeSize = config.largeTitleFontSize ?: @34;
if (config.largeTitleFontFamily) {
largeAttrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.largeTitleFontFamily size:largeSize weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
largeAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:[largeSize floatValue] weight:UIFontWeightBold];
}
[navbar setLargeTitleTextAttributes:largeAttrs];
}
}
#endif
}
}
+ (void)setTitleAttibutes:(NSDictionary *)attrs forButton:(UIBarButtonItem *)button
{
[button setTitleTextAttributes:attrs forState:UIControlStateNormal];
[button setTitleTextAttributes:attrs forState:UIControlStateHighlighted];
[button setTitleTextAttributes:attrs forState:UIControlStateDisabled];
[button setTitleTextAttributes:attrs forState:UIControlStateSelected];
if (@available(iOS 9.0, *)) {
[button setTitleTextAttributes:attrs forState:UIControlStateFocused];
}
}
+ (UIImage*)loadBackButtonImageInViewController:(UIViewController *)vc
withConfig:(RNSScreenStackHeaderConfig *)config
{
BOOL hasBackButtonImage = NO;
for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
if (subview.type == RNSScreenStackHeaderSubviewTypeBackButton && subview.subviews.count > 0) {
hasBackButtonImage = YES;
RCTImageView *imageView = subview.subviews[0];
if (imageView.image == nil) {
// This is yet another workaround for loading custom back icon. It turns out that under
// certain circumstances image attribute can be null despite the app running in production
// mode (when images are loaded from the filesystem). This can happen because image attribute
// is reset when image view is detached from window, and also in some cases initialization
// does not populate the frame of the image view before the loading start. The latter result
// in the image attribute not being updated. We manually set frame to the size of an image
// in order to trigger proper reload that'd update the image attribute.
RCTImageSource *source = imageView.imageSources[0];
[imageView reactSetFrame:CGRectMake(imageView.frame.origin.x,
imageView.frame.origin.y,
source.size.width,
source.size.height)];
}
UIImage *image = imageView.image;
// IMPORTANT!!!
// image can be nil in DEV MODE ONLY
//
// It is so, because in dev mode images are loaded over HTTP from the packager. In that case
// we first check if image is already loaded in cache and if it is, we take it from cache and
// display immediately. Otherwise we wait for the transition to finish and retry updating
// header config.
// Unfortunately due to some problems in UIKit we cannot update the image while the screen
// transition is ongoing. This results in the settings being reset after the transition is done
// to the state from before the transition.
if (image == nil) {
// in DEV MODE we try to load from cache (we use private API for that as it is not exposed
// publically in headers).
RCTImageSource *source = imageView.imageSources[0];
image = [subview.bridge.imageLoader.imageCache
imageForUrl:source.request.URL.absoluteString
size:source.size
scale:source.scale
resizeMode:imageView.resizeMode];
}
if (image == nil) {
// This will be triggered if the image is not in the cache yet. What we do is we wait until
// the end of transition and run header config updates again. We could potentially wait for
// image on load to trigger, but that would require even more private method hacking.
if (vc.transitionCoordinator) {
[vc.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// nothing, we just want completion
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// in order for new back button image to be loaded we need to trigger another change
// in back button props that'd make UIKit redraw the button. Otherwise the changes are
// not reflected. Here we change back button visibility which is then immediately restored
#if (TARGET_OS_IOS)
vc.navigationItem.hidesBackButton = YES;
#endif
[config updateViewControllerIfNeeded];
}];
}
return [UIImage new];
} else {
return image;
}
}
}
return nil;
}
+ (void)willShowViewController:(UIViewController *)vc animated:(BOOL)animated withConfig:(RNSScreenStackHeaderConfig *)config
{
[self updateViewController:vc withConfig:config animated:animated];
}
#if defined(__IPHONE_13_0) && TARGET_OS_IOS
+ (UINavigationBarAppearance*)buildAppearance:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
{
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
// transparent background color
[appearance configureWithTransparentBackground];
} else {
[appearance configureWithOpaqueBackground];
}
// set background color if specified
if (config.backgroundColor) {
appearance.backgroundColor = config.backgroundColor;
}
if (config.blurEffect) {
appearance.backgroundEffect = [UIBlurEffect effectWithStyle:config.blurEffect];
}
if (config.hideShadow) {
appearance.shadowColor = nil;
}
if (config.titleFontFamily || config.titleFontSize || config.titleColor) {
NSMutableDictionary *attrs = [NSMutableDictionary new];
if (config.titleColor) {
attrs[NSForegroundColorAttributeName] = config.titleColor;
}
NSNumber *size = config.titleFontSize ?: @17;
if (config.titleFontFamily) {
attrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.titleFontFamily size:size weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
}
appearance.titleTextAttributes = attrs;
}
if (config.largeTitleFontFamily || config.largeTitleFontSize || config.largeTitleColor || config.titleColor) {
NSMutableDictionary *largeAttrs = [NSMutableDictionary new];
if (config.largeTitleColor || config.titleColor) {
largeAttrs[NSForegroundColorAttributeName] = config.largeTitleColor ? config.largeTitleColor : config.titleColor;
}
NSNumber *largeSize = config.largeTitleFontSize ?: @34;
if (config.largeTitleFontFamily) {
largeAttrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.largeTitleFontFamily size:largeSize weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
largeAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:[largeSize floatValue] weight:UIFontWeightBold];
}
appearance.largeTitleTextAttributes = largeAttrs;
}
UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
if (backButtonImage) {
[appearance setBackIndicatorImage:backButtonImage transitionMaskImage:backButtonImage];
} else if (appearance.backIndicatorImage) {
[appearance setBackIndicatorImage:nil transitionMaskImage:nil];
}
return appearance;
}
#endif
+ (void)updateViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config animated:(BOOL)animated
{
UINavigationItem *navitem = vc.navigationItem;
UINavigationController *navctr = (UINavigationController *)vc.parentViewController;
NSUInteger currentIndex = [navctr.viewControllers indexOfObject:vc];
UINavigationItem *prevItem = currentIndex > 0 ? [navctr.viewControllers objectAtIndex:currentIndex - 1].navigationItem : nil;
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:animated];
#if (TARGET_OS_IOS)
// we put it before check with return because we want to apply changes to status bar even if the header is hidden
if (config != nil) {
if (config.statusBarStyle || config.statusBarAnimation || config.statusBarHidden) {
[RNSScreenStackHeaderConfig assertViewControllerBasedStatusBarAppearenceSet];
if ([vc isKindOfClass:[RNSScreen class]]) {
[RNSScreenStackHeaderConfig updateStatusBarAppearance];
}
}
}
#endif
if (shouldHide) {
return;
}
if (config.direction == UISemanticContentAttributeForceLeftToRight || config.direction == UISemanticContentAttributeForceRightToLeft) {
navctr.view.semanticContentAttribute = config.direction;
navctr.navigationBar.semanticContentAttribute = config.direction;
}
navitem.title = config.title;
#if (TARGET_OS_IOS)
if (config.backTitle != nil || config.backTitleFontFamily || config.backTitleFontSize) {
prevItem.backBarButtonItem = [[UIBarButtonItem alloc]
initWithTitle:config.backTitle ?: prevItem.title
style:UIBarButtonItemStylePlain
target:nil
action:nil];
if (config.backTitleFontFamily || config.backTitleFontSize) {
NSMutableDictionary *attrs = [NSMutableDictionary new];
NSNumber *size = config.backTitleFontSize ?: @17;
if (config.backTitleFontFamily) {
attrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.backTitleFontFamily size:size weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
}
[self setTitleAttibutes:attrs forButton:prevItem.backBarButtonItem];
}
} else {
prevItem.backBarButtonItem = nil;
}
if (@available(iOS 11.0, *)) {
if (config.largeTitle) {
navctr.navigationBar.prefersLargeTitles = YES;
}
navitem.largeTitleDisplayMode = config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;
}
#endif
#if defined(__IPHONE_13_0) && TARGET_OS_IOS
if (@available(iOS 13.0, *)) {
UINavigationBarAppearance *appearance = [self buildAppearance:vc withConfig:config];
navitem.standardAppearance = appearance;
navitem.compactAppearance = appearance;
UINavigationBarAppearance *scrollEdgeAppearance = [[UINavigationBarAppearance alloc] initWithBarAppearance:appearance];
if (config.largeTitleBackgroundColor != nil) {
scrollEdgeAppearance.backgroundColor = config.largeTitleBackgroundColor;
}
if (config.largeTitleHideShadow) {
scrollEdgeAppearance.shadowColor = nil;
}
navitem.scrollEdgeAppearance = scrollEdgeAppearance;
} else
#endif
{
#if (TARGET_OS_IOS)
// updating backIndicatotImage does not work when called during transition. On iOS pre 13 we need
// to update it before the navigation starts.
UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
if (backButtonImage) {
navctr.navigationBar.backIndicatorImage = backButtonImage;
navctr.navigationBar.backIndicatorTransitionMaskImage = backButtonImage;
} else if (navctr.navigationBar.backIndicatorImage) {
navctr.navigationBar.backIndicatorImage = nil;
navctr.navigationBar.backIndicatorTransitionMaskImage = nil;
}
#endif
}
#if (TARGET_OS_IOS)
navitem.hidesBackButton = config.hideBackButton;
#endif
navitem.leftBarButtonItem = nil;
navitem.rightBarButtonItem = nil;
navitem.titleView = nil;
for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
switch (subview.type) {
case RNSScreenStackHeaderSubviewTypeLeft: {
#if (TARGET_OS_IOS)
navitem.leftItemsSupplementBackButton = config.backButtonInCustomView;
#endif
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithCustomView:subview];
navitem.leftBarButtonItem = buttonItem;
break;
}
case RNSScreenStackHeaderSubviewTypeRight: {
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithCustomView:subview];
navitem.rightBarButtonItem = buttonItem;
break;
}
case RNSScreenStackHeaderSubviewTypeCenter:
case RNSScreenStackHeaderSubviewTypeTitle: {
navitem.titleView = subview;
break;
}
}
}
if (animated
&& vc.transitionCoordinator != nil
&& vc.transitionCoordinator.presentationStyle == UIModalPresentationNone
&& !wasHidden) {
// when there is an ongoing transition we may need to update navbar setting in animation block
// using animateAlongsideTransition. However, we only do that given the transition is not a modal
// transition (presentationStyle == UIModalPresentationNone) and that the bar was not previously
// hidden. This is because both for modal transitions and transitions from screen with hidden bar
// the transition animation block does not get triggered. This is ok, because with both of those
// types of transitions there is no "shared" navigation bar that needs to be updated in an animated
// way.
[vc.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self setAnimatedConfig:vc withConfig:config];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
if ([context isCancelled]) {
UIViewController* fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
RNSScreenStackHeaderConfig* config = nil;
for (UIView *subview in fromVC.view.reactSubviews) {
if ([subview isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
config = (RNSScreenStackHeaderConfig*) subview;
break;
}
}
[self setAnimatedConfig:fromVC withConfig:config];
}
}];
} else {
[self setAnimatedConfig:vc withConfig:config];
}
}
+ (void)assertViewControllerBasedStatusBarAppearenceSet
{
static dispatch_once_t once;
static bool viewControllerBasedAppearence;
dispatch_once(&once, ^{
viewControllerBasedAppearence = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"] boolValue];
});
if (!viewControllerBasedAppearence) {
RCTLogError(@"If you want to change the appearance of status bar, you have to change \
UIViewControllerBasedStatusBarAppearance key in the Info.plist to YES");
}
}
+ (void)updateStatusBarAppearance
{
[UIView animateWithDuration:0.4 animations:^{ // duration based on "Programming iOS 13" p. 311 implementation
if (@available(iOS 13, *)) {
UIWindow *firstWindow = [[[UIApplication sharedApplication] windows] firstObject];
if (firstWindow != nil) {
[[firstWindow rootViewController] setNeedsStatusBarAppearanceUpdate];
}
} else {
[UIApplication.sharedApplication.keyWindow.rootViewController setNeedsStatusBarAppearanceUpdate];
}
}];
}
+ (UIStatusBarStyle)statusBarStyleForRNSStatusBarStyle:(RNSStatusBarStyle)statusBarStyle
{
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
switch (statusBarStyle) {
case RNSStatusBarStyleAuto:
return [UITraitCollection.currentTraitCollection userInterfaceStyle] == UIUserInterfaceStyleDark ? UIStatusBarStyleLightContent : UIStatusBarStyleDarkContent;
case RNSStatusBarStyleInverted:
return [UITraitCollection.currentTraitCollection userInterfaceStyle] == UIUserInterfaceStyleDark ? UIStatusBarStyleDarkContent : UIStatusBarStyleLightContent;
case RNSStatusBarStyleLight:
return UIStatusBarStyleLightContent;
case RNSStatusBarStyleDark:
return UIStatusBarStyleDarkContent;
default:
return UIStatusBarStyleLightContent;
}
}
#endif
return UIStatusBarStyleLightContent;
}
@end
@implementation RNSScreenStackHeaderConfigManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [RNSScreenStackHeaderConfig new];
}
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_EXPORT_VIEW_PROPERTY(titleFontFamily, NSString)
RCT_EXPORT_VIEW_PROPERTY(titleFontSize, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(backTitle, NSString)
RCT_EXPORT_VIEW_PROPERTY(backTitleFontFamily, NSString)
RCT_EXPORT_VIEW_PROPERTY(backTitleFontSize, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(blurEffect, UIBlurEffectStyle)
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
RCT_EXPORT_VIEW_PROPERTY(direction, UISemanticContentAttribute)
RCT_EXPORT_VIEW_PROPERTY(largeTitle, BOOL)
RCT_EXPORT_VIEW_PROPERTY(largeTitleFontFamily, NSString)
RCT_EXPORT_VIEW_PROPERTY(largeTitleFontSize, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(largeTitleColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(largeTitleBackgroundColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(largeTitleHideShadow, BOOL)
RCT_EXPORT_VIEW_PROPERTY(hideBackButton, BOOL)
RCT_EXPORT_VIEW_PROPERTY(hideShadow, BOOL)
RCT_EXPORT_VIEW_PROPERTY(backButtonInCustomView, BOOL)
// `hidden` is an UIView property, we need to use different name internally
RCT_REMAP_VIEW_PROPERTY(hidden, hide, BOOL)
RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
RCT_EXPORT_VIEW_PROPERTY(statusBarStyle, RNSStatusBarStyle)
RCT_EXPORT_VIEW_PROPERTY(statusBarAnimation, UIStatusBarAnimation)
RCT_EXPORT_VIEW_PROPERTY(statusBarHidden, BOOL)
@end
@implementation RCTConvert (RNSScreenStackHeader)
+ (NSMutableDictionary *)blurEffectsForIOSVersion
{
NSMutableDictionary *blurEffects = [NSMutableDictionary new];
[blurEffects addEntriesFromDictionary:@{
@"extraLight": @(UIBlurEffectStyleExtraLight),
@"light": @(UIBlurEffectStyleLight),
@"dark": @(UIBlurEffectStyleDark),
}];
if (@available(iOS 10.0, *)) {
[blurEffects addEntriesFromDictionary:@{
@"regular": @(UIBlurEffectStyleRegular),
@"prominent": @(UIBlurEffectStyleProminent),
}];
}
#if defined(__IPHONE_13_0) && TARGET_OS_IOS
if (@available(iOS 13.0, *)) {
[blurEffects addEntriesFromDictionary:@{
@"systemUltraThinMaterial": @(UIBlurEffectStyleSystemUltraThinMaterial),
@"systemThinMaterial": @(UIBlurEffectStyleSystemThinMaterial),
@"systemMaterial": @(UIBlurEffectStyleSystemMaterial),
@"systemThickMaterial": @(UIBlurEffectStyleSystemThickMaterial),
@"systemChromeMaterial": @(UIBlurEffectStyleSystemChromeMaterial),
@"systemUltraThinMaterialLight": @(UIBlurEffectStyleSystemUltraThinMaterialLight),
@"systemThinMaterialLight": @(UIBlurEffectStyleSystemThinMaterialLight),
@"systemMaterialLight": @(UIBlurEffectStyleSystemMaterialLight),
@"systemThickMaterialLight": @(UIBlurEffectStyleSystemThickMaterialLight),
@"systemChromeMaterialLight": @(UIBlurEffectStyleSystemChromeMaterialLight),
@"systemUltraThinMaterialDark": @(UIBlurEffectStyleSystemUltraThinMaterialDark),
@"systemThinMaterialDark": @(UIBlurEffectStyleSystemThinMaterialDark),
@"systemMaterialDark": @(UIBlurEffectStyleSystemMaterialDark),
@"systemThickMaterialDark": @(UIBlurEffectStyleSystemThickMaterialDark),
@"systemChromeMaterialDark": @(UIBlurEffectStyleSystemChromeMaterialDark),
}];
}
#endif
return blurEffects;
}
RCT_ENUM_CONVERTER(RNSScreenStackHeaderSubviewType, (@{
@"back": @(RNSScreenStackHeaderSubviewTypeBackButton),
@"left": @(RNSScreenStackHeaderSubviewTypeLeft),
@"right": @(RNSScreenStackHeaderSubviewTypeRight),
@"title": @(RNSScreenStackHeaderSubviewTypeTitle),
@"center": @(RNSScreenStackHeaderSubviewTypeCenter),
}), RNSScreenStackHeaderSubviewTypeTitle, integerValue)
RCT_ENUM_CONVERTER(UISemanticContentAttribute, (@{
@"ltr": @(UISemanticContentAttributeForceLeftToRight),
@"rtl": @(UISemanticContentAttributeForceRightToLeft),
}), UISemanticContentAttributeUnspecified, integerValue)
RCT_ENUM_CONVERTER(UIBlurEffectStyle, ([self blurEffectsForIOSVersion]), UIBlurEffectStyleExtraLight, integerValue)
RCT_ENUM_CONVERTER(RNSStatusBarStyle, (@{
@"auto": @(RNSStatusBarStyleAuto),
@"inverted": @(RNSStatusBarStyleInverted),
@"light": @(RNSStatusBarStyleLight),
@"dark": @(RNSStatusBarStyleDark),
}), RNSStatusBarStyleAuto, integerValue)
@end
@implementation RNSScreenStackHeaderSubviewManager
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(type, RNSScreenStackHeaderSubviewType)
- (UIView *)view
{
return [[RNSScreenStackHeaderSubview alloc] initWithBridge:self.bridge];
}
@end

View File

@ -0,0 +1,457 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
442389EC22DF259000611BBE /* RNSScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 442389EB22DF259000611BBE /* RNSScreen.m */; };
448078F52114595900280661 /* RNSScreenContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 448078F12114595900280661 /* RNSScreenContainer.m */; };
4482D5EF22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 4482D5EE22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m */; };
44A67C3122C3B8B40017156F /* RNSScreenStack.m in Sources */ = {isa = PBXBuildFile; fileRef = 44A67C3022C3B8B40017156F /* RNSScreenStack.m */; };
B5C32A48220C6379000FFB8D /* RNSScreenContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 448078F12114595900280661 /* RNSScreenContainer.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A4A220C6379000FFB8D /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNScreens.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNScreens.a; sourceTree = BUILT_PRODUCTS_DIR; };
442389EB22DF259000611BBE /* RNSScreen.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSScreen.m; sourceTree = "<group>"; };
448078EF2114595900280661 /* RNSScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSScreen.h; sourceTree = "<group>"; };
448078F02114595900280661 /* RNSScreenContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSScreenContainer.h; sourceTree = "<group>"; };
448078F12114595900280661 /* RNSScreenContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSScreenContainer.m; sourceTree = "<group>"; };
4482D5ED22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSScreenStackHeaderConfig.h; sourceTree = "<group>"; };
4482D5EE22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSScreenStackHeaderConfig.m; sourceTree = "<group>"; };
44A67C2F22C3B8B40017156F /* RNSScreenStack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSScreenStack.h; sourceTree = "<group>"; };
44A67C3022C3B8B40017156F /* RNSScreenStack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSScreenStack.m; sourceTree = "<group>"; };
B5C32A4F220C6379000FFB8D /* libRNScreens-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNScreens-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A49220C6379000FFB8D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNScreens.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
4482D5ED22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.h */,
4482D5EE22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m */,
44A67C2F22C3B8B40017156F /* RNSScreenStack.h */,
44A67C3022C3B8B40017156F /* RNSScreenStack.m */,
448078EF2114595900280661 /* RNSScreen.h */,
442389EB22DF259000611BBE /* RNSScreen.m */,
448078F02114595900280661 /* RNSScreenContainer.h */,
448078F12114595900280661 /* RNSScreenContainer.m */,
134814211AA4EA7D00B7C361 /* Products */,
B5C32A4F220C6379000FFB8D /* libRNScreens-tvOS.a */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RNScreens */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNScreens" */;
buildPhases = (
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNScreens;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNScreens.a */;
productType = "com.apple.product-type.library.static";
};
B5C32A45220C6379000FFB8D /* RNScreens-tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = B5C32A4B220C6379000FFB8D /* Build configuration list for PBXNativeTarget "RNScreens-tvOS" */;
buildPhases = (
B5C32A46220C6379000FFB8D /* Sources */,
B5C32A49220C6379000FFB8D /* Frameworks */,
B5C32A4A220C6379000FFB8D /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "RNScreens-tvOS";
productName = RCTDataManager;
productReference = B5C32A4F220C6379000FFB8D /* libRNScreens-tvOS.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 920;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNScreens" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNScreens */,
B5C32A45220C6379000FFB8D /* RNScreens-tvOS */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
442389EC22DF259000611BBE /* RNSScreen.m in Sources */,
448078F52114595900280661 /* RNSScreenContainer.m in Sources */,
44A67C3122C3B8B40017156F /* RNSScreenStack.m in Sources */,
4482D5EF22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A46220C6379000FFB8D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5C32A48220C6379000FFB8D /* RNSScreenContainer.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
0CE596A6BAEE45CA860361AD /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNScreens;
SKIP_INSTALL = YES;
};
name = Testflight;
};
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNScreens;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNScreens;
SKIP_INSTALL = YES;
};
name = Release;
};
B5C32A4C220C6379000FFB8D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
};
name = Debug;
};
B5C32A4D220C6379000FFB8D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
};
name = Release;
};
B5C32A4E220C6379000FFB8D /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
};
name = Testflight;
};
C7F03305A3464E75B4F5A6CE /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Testflight;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNScreens" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
C7F03305A3464E75B4F5A6CE /* Testflight */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNScreens" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
0CE596A6BAEE45CA860361AD /* Testflight */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B5C32A4B220C6379000FFB8D /* Build configuration list for PBXNativeTarget "RNScreens-tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B5C32A4C220C6379000FFB8D /* Debug */,
B5C32A4D220C6379000FFB8D /* Release */,
B5C32A4E220C6379000FFB8D /* Testflight */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

View File

@ -0,0 +1,9 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController (RNScreens)
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,52 @@
#import "UIViewController+RNScreens.h"
#import "RNSScreenContainer.h"
#import <objc/runtime.h>
@implementation UIViewController (RNScreens)
- (UIViewController *)reactNativeScreensChildViewControllerForStatusBarStyle
{
UIViewController *childVC = [self findChildRNScreensViewController];
return childVC ?: [self reactNativeScreensChildViewControllerForStatusBarStyle];
}
- (UIViewController *)reactNativeScreensChildViewControllerForStatusBarHidden
{
UIViewController *childVC = [self findChildRNScreensViewController];
return childVC ?: [self reactNativeScreensChildViewControllerForStatusBarHidden];
}
- (UIStatusBarAnimation)reactNativeScreensPreferredStatusBarUpdateAnimation
{
UIViewController *childVC = [self findChildRNScreensViewController];
return childVC ? childVC.preferredStatusBarUpdateAnimation : [self reactNativeScreensPreferredStatusBarUpdateAnimation];
}
- (UIViewController *)findChildRNScreensViewController
{
UIViewController *lastViewController = [[self childViewControllers] lastObject];
if ([lastViewController conformsToProtocol:@protocol(RNScreensViewControllerDelegate)]) {
return lastViewController;
}
return nil;
}
+ (void)load
{
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
Class uiVCClass = [UIViewController class];
method_exchangeImplementations(class_getInstanceMethod(uiVCClass, @selector(childViewControllerForStatusBarStyle)),
class_getInstanceMethod(uiVCClass, @selector(reactNativeScreensChildViewControllerForStatusBarStyle)));
method_exchangeImplementations(class_getInstanceMethod(uiVCClass, @selector(childViewControllerForStatusBarHidden)),
class_getInstanceMethod(uiVCClass, @selector(reactNativeScreensChildViewControllerForStatusBarHidden)));
method_exchangeImplementations(class_getInstanceMethod(uiVCClass, @selector(preferredStatusBarUpdateAnimation)),
class_getInstanceMethod(uiVCClass, @selector(reactNativeScreensPreferredStatusBarUpdateAnimation)));
});
}
@end

View File

@ -0,0 +1,318 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _reactNative = require("react-native");
var _reactNativeScreens = require("react-native-screens");
var _reactNavigation = require("react-navigation");
var _reactNavigationStack = require("react-navigation-stack");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function renderComponentOrThunk(componentOrThunk, props) {
if (typeof componentOrThunk === 'function') {
return componentOrThunk(props);
}
return componentOrThunk;
}
const REMOVE_ACTION = 'NativeStackNavigator/REMOVE';
class StackView extends _react.default.Component {
constructor(...args) {
super(...args);
_defineProperty(this, "_removeScene", route => {
this.props.navigation.dispatch({
type: REMOVE_ACTION,
immediate: true,
key: route.key
});
});
_defineProperty(this, "_onAppear", (route, descriptor) => {
descriptor.options && descriptor.options.onAppear && descriptor.options.onAppear();
this.props.navigation.dispatch(_reactNavigation.StackActions.completeTransition({
toChildKey: route.key,
key: this.props.navigation.state.key
}));
});
_defineProperty(this, "_onFinishTransitioning", () => {
const {
routes
} = this.props.navigation.state;
const lastRoute = (routes === null || routes === void 0 ? void 0 : routes.length) && routes[routes.length - 1];
if (lastRoute) {
this.props.navigation.dispatch(_reactNavigation.StackActions.completeTransition({
toChildKey: lastRoute.key,
key: this.props.navigation.state.key
}));
}
});
_defineProperty(this, "_renderHeaderConfig", (index, route, descriptor) => {
const {
navigationConfig
} = this.props;
const {
options
} = descriptor;
const {
headerMode
} = navigationConfig;
const {
backButtonInCustomView,
direction,
headerBackTitle,
headerBackTitleStyle,
headerBackTitleVisible,
headerHideBackButton,
headerLargeTitleStyle,
headerStyle,
headerTintColor,
headerTitleStyle,
headerTopInsetEnabled = true,
hideShadow,
largeTitle,
statusBarAnimation,
statusBarHidden,
statusBarStyle,
title,
translucent
} = options;
const scene = {
index,
key: route.key,
route,
descriptor
};
const headerOptions = {
backButtonInCustomView,
backTitle: headerBackTitleVisible === false ? '' : headerBackTitle,
backTitleFontFamily: headerBackTitleStyle && headerBackTitleStyle.fontFamily,
backTitleFontSize: headerBackTitleStyle && headerBackTitleStyle.fontSize,
color: headerTintColor,
direction,
topInsetEnabled: headerTopInsetEnabled,
hideBackButton: headerHideBackButton,
hideShadow,
largeTitle,
largeTitleBackgroundColor: headerLargeTitleStyle && headerLargeTitleStyle.backgroundColor,
largeTitleColor: headerLargeTitleStyle && headerLargeTitleStyle.color,
largeTitleFontFamily: headerLargeTitleStyle && headerLargeTitleStyle.fontFamily,
largeTitleFontSize: headerLargeTitleStyle && headerLargeTitleStyle.fontSize,
statusBarAnimation,
statusBarHidden,
statusBarStyle,
title,
titleColor: headerTitleStyle && headerTitleStyle.color || headerTintColor,
titleFontFamily: headerTitleStyle && headerTitleStyle.fontFamily,
titleFontSize: headerTitleStyle && headerTitleStyle.fontSize,
translucent: translucent === undefined ? false : translucent
};
const hasHeader = headerMode !== 'none' && options.header !== null;
if (!hasHeader) {
return /*#__PURE__*/_react.default.createElement(_reactNativeScreens.ScreenStackHeaderConfig, _extends({}, headerOptions, {
hidden: true
}));
}
if (headerStyle !== undefined) {
headerOptions.backgroundColor = headerStyle.backgroundColor;
headerOptions.blurEffect = headerStyle.blurEffect;
}
const children = [];
if (options.backButtonImage) {
children.push( /*#__PURE__*/_react.default.createElement(_reactNativeScreens.ScreenStackHeaderBackButtonImage, {
key: "backImage",
source: options.backButtonImage
}));
}
if (options.headerLeft !== undefined) {
children.push( /*#__PURE__*/_react.default.createElement(_reactNativeScreens.ScreenStackHeaderLeftView, {
key: "left"
}, renderComponentOrThunk(options.headerLeft, {
scene
})));
} else if (options.headerBackImage !== undefined) {
const goBack = () => {
// Go back on next tick because button ripple effect needs to happen on Android
requestAnimationFrame(() => {
descriptor.navigation.goBack(descriptor.key);
});
};
children.push( /*#__PURE__*/_react.default.createElement(_reactNativeScreens.ScreenStackHeaderLeftView, {
key: "left"
}, /*#__PURE__*/_react.default.createElement(_reactNavigationStack.HeaderBackButton, {
onPress: goBack,
pressColorAndroid: options.headerPressColorAndroid,
tintColor: options.headerTintColor,
backImage: options.headerBackImage,
title: options.backButtonTitle,
truncatedTitle: options.truncatedBackButtonTitle,
backTitleVisible: this.props.backTitleVisible,
titleStyle: options.headerBackTitleStyle,
layoutPreset: this.props.layoutPreset,
scene: scene
})));
}
if (options.headerTitle) {
if (title === undefined && typeof options.headerTitle === 'string') {
headerOptions.title = options.headerTitle;
} else {
children.push( /*#__PURE__*/_react.default.createElement(_reactNativeScreens.ScreenStackHeaderCenterView, {
key: "center"
}, renderComponentOrThunk(options.headerTitle, {
scene
})));
}
}
if (options.headerRight) {
children.push( /*#__PURE__*/_react.default.createElement(_reactNativeScreens.ScreenStackHeaderRightView, {
key: "right"
}, renderComponentOrThunk(options.headerRight, {
scene
})));
}
if (children.length > 0) {
headerOptions.children = children;
}
return /*#__PURE__*/_react.default.createElement(_reactNativeScreens.ScreenStackHeaderConfig, headerOptions);
});
_defineProperty(this, "_renderScene", (index, route, descriptor) => {
const {
navigation,
getComponent,
options
} = descriptor;
const {
mode,
transparentCard
} = this.props.navigationConfig;
const SceneComponent = getComponent();
let stackPresentation = 'push';
if (mode === 'modal' || mode === 'containedModal') {
stackPresentation = mode;
if (transparentCard || options.cardTransparent) {
stackPresentation = mode === 'containedModal' ? 'containedTransparentModal' : 'transparentModal';
}
}
let stackAnimation = options.stackAnimation;
if (options.animationEnabled === false) {
stackAnimation = 'none';
}
const {
screenProps
} = this.props;
return /*#__PURE__*/_react.default.createElement(_reactNativeScreens.Screen, {
key: "screen_".concat(route.key),
style: [_reactNative.StyleSheet.absoluteFill, options.cardStyle],
stackAnimation: stackAnimation,
stackPresentation: stackPresentation,
replaceAnimation: options.replaceAnimation === undefined ? 'pop' : options.replaceAnimation,
pointerEvents: index === this.props.navigation.state.routes.length - 1 ? 'auto' : 'none',
gestureEnabled: _reactNative.Platform.OS === 'android' ? false : options.gestureEnabled === undefined ? true : options.gestureEnabled,
onAppear: () => this._onAppear(route, descriptor),
onDismissed: () => this._removeScene(route)
}, this._renderHeaderConfig(index, route, descriptor), /*#__PURE__*/_react.default.createElement(_reactNavigation.SceneView, {
screenProps: screenProps,
navigation: navigation,
component: SceneComponent
}));
});
}
render() {
const {
navigation,
descriptors
} = this.props;
return /*#__PURE__*/_react.default.createElement(_reactNativeScreens.ScreenStack, {
style: styles.scenes,
onFinishTransitioning: this._onFinishTransitioning
}, navigation.state.routes.map((route, i) => this._renderScene(i, route, descriptors[route.key])));
}
}
const styles = _reactNative.StyleSheet.create({
scenes: {
flex: 1
}
});
function createStackNavigator(routeConfigMap, stackConfig = {}) {
const router = (0, _reactNavigation.StackRouter)(routeConfigMap, stackConfig); // belowe we override getStateForAction method in order to add handling for
// a custom native stack navigation action. The action REMOVE that we want to
// add works in a similar way to POP, but it does not remove all the routes
// that sit on top of the removed route. For example if we have three routes
// [a,b,c] and call POP on b, then both b and c will go away. In case we
// call REMOVE on b, only b will be removed from the stack and the resulting
// state will be [a, c]
const superGetStateForAction = router.getStateForAction;
router.getStateForAction = (action, state) => {
if (action.type === REMOVE_ACTION) {
const {
key,
immediate
} = action;
let backRouteIndex = state.index;
if (key) {
const backRoute = state.routes.find(route => route.key === key);
backRouteIndex = state.routes.indexOf(backRoute);
}
if (backRouteIndex > 0) {
const newRoutes = [...state.routes];
newRoutes.splice(backRouteIndex, 1);
return { ...state,
routes: newRoutes,
index: newRoutes.length - 1,
isTransitioning: immediate !== true
};
}
}
return superGetStateForAction(action, state);
}; // Create a navigator with StackView as the view
return (0, _reactNavigation.createNavigator)(StackView, router, stackConfig);
}
var _default = createStackNavigator;
exports.default = _default;
//# sourceMappingURL=createNativeStackNavigator.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
"use strict";
//# sourceMappingURL=index.d.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[]}

View File

@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.enableScreens = enableScreens;
exports.screensEnabled = screensEnabled;
exports.NativeScreenContainer = exports.ScreenContainer = exports.Screen = exports.NativeScreen = void 0;
var _react = _interopRequireDefault(require("react"));
var _reactNative = require("react-native");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
let ENABLE_SCREENS = true;
function enableScreens(shouldEnableScreens = true) {
ENABLE_SCREENS = shouldEnableScreens;
}
function screensEnabled() {
return ENABLE_SCREENS;
}
class NativeScreen extends _react.default.Component {
render() {
const {
active,
style,
enabled = true,
...rest
} = this.props;
return /*#__PURE__*/_react.default.createElement(_reactNative.View, _extends({
style: [style, ENABLE_SCREENS && enabled && !active ? {
display: 'none'
} : null]
}, rest));
}
}
exports.NativeScreen = NativeScreen;
const Screen = _reactNative.Animated.createAnimatedComponent(NativeScreen);
exports.Screen = Screen;
const ScreenContainer = _reactNative.View;
exports.ScreenContainer = ScreenContainer;
const NativeScreenContainer = _reactNative.View;
exports.NativeScreenContainer = NativeScreenContainer;
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["index.js"],"names":["ENABLE_SCREENS","enableScreens","shouldEnableScreens","screensEnabled","NativeScreen","React","Component","render","active","style","enabled","rest","props","display","Screen","Animated","createAnimatedComponent","ScreenContainer","View","NativeScreenContainer"],"mappings":";;;;;;;;;AAAA;;AACA;;;;;;AAEA,IAAIA,cAAc,GAAG,IAArB;;AAEO,SAASC,aAAT,CAAuBC,mBAAmB,GAAG,IAA7C,EAAmD;AACxDF,EAAAA,cAAc,GAAGE,mBAAjB;AACD;;AAEM,SAASC,cAAT,GAA0B;AAC/B,SAAOH,cAAP;AACD;;AAEM,MAAMI,YAAN,SAA2BC,eAAMC,SAAjC,CAA2C;AAChDC,EAAAA,MAAM,GAAG;AACP,UAAM;AAAEC,MAAAA,MAAF;AAAUC,MAAAA,KAAV;AAAiBC,MAAAA,OAAO,GAAG,IAA3B;AAAiC,SAAGC;AAApC,QAA6C,KAAKC,KAAxD;AAEA,wBACE,6BAAC,iBAAD;AACE,MAAA,KAAK,EAAE,CACLH,KADK,EAELT,cAAc,IAAIU,OAAlB,IAA6B,CAACF,MAA9B,GAAuC;AAAEK,QAAAA,OAAO,EAAE;AAAX,OAAvC,GAA6D,IAFxD;AADT,OAKMF,IALN,EADF;AASD;;AAb+C;;;;AAgB3C,MAAMG,MAAM,GAAGC,sBAASC,uBAAT,CAAiCZ,YAAjC,CAAf;;;AAEA,MAAMa,eAAe,GAAGC,iBAAxB;;AAEA,MAAMC,qBAAqB,GAAGD,iBAA9B","sourcesContent":["import React from 'react';\nimport { Animated, View } from 'react-native';\n\nlet ENABLE_SCREENS = true;\n\nexport function enableScreens(shouldEnableScreens = true) {\n ENABLE_SCREENS = shouldEnableScreens;\n}\n\nexport function screensEnabled() {\n return ENABLE_SCREENS;\n}\n\nexport class NativeScreen extends React.Component {\n render() {\n const { active, style, enabled = true, ...rest } = this.props;\n\n return (\n <View\n style={[\n style,\n ENABLE_SCREENS && enabled && !active ? { display: 'none' } : null,\n ]}\n {...rest}\n />\n );\n }\n}\n\nexport const Screen = Animated.createAnimatedComponent(NativeScreen);\n\nexport const ScreenContainer = View;\n\nexport const NativeScreenContainer = View;\n"]}

View File

@ -0,0 +1,205 @@
"use strict";
var _react = _interopRequireDefault(require("react"));
var _reactNative = require("react-native");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
let ENABLE_SCREENS = false;
function enableScreens(shouldEnableScreens = true) {
ENABLE_SCREENS = shouldEnableScreens;
if (ENABLE_SCREENS && !_reactNative.UIManager.getViewManagerConfig('RNSScreen')) {
console.error("Screen native module hasn't been linked. Please check the react-native-screens README for more details");
}
} // const that tells if the library should use new implementation, will be undefined for older versions
const shouldUseActivityState = true;
function screensEnabled() {
return ENABLE_SCREENS;
} // We initialize these lazily so that importing the module doesn't throw error when not linked
// This is necessary coz libraries such as React Navigation import the library where it may not be enabled
let NativeScreenValue;
let NativeScreenContainerValue;
let NativeScreenStack;
let NativeScreenStackHeaderConfig;
let NativeScreenStackHeaderSubview;
let AnimatedNativeScreen;
const ScreensNativeModules = {
get NativeScreen() {
NativeScreenValue = NativeScreenValue || (0, _reactNative.requireNativeComponent)('RNSScreen', null);
return NativeScreenValue;
},
get NativeScreenContainer() {
NativeScreenContainerValue = NativeScreenContainerValue || (0, _reactNative.requireNativeComponent)('RNSScreenContainer', null);
return NativeScreenContainerValue;
},
get NativeScreenStack() {
NativeScreenStack = NativeScreenStack || (0, _reactNative.requireNativeComponent)('RNSScreenStack', null);
return NativeScreenStack;
},
get NativeScreenStackHeaderConfig() {
NativeScreenStackHeaderConfig = NativeScreenStackHeaderConfig || (0, _reactNative.requireNativeComponent)('RNSScreenStackHeaderConfig', null);
return NativeScreenStackHeaderConfig;
},
get NativeScreenStackHeaderSubview() {
NativeScreenStackHeaderSubview = NativeScreenStackHeaderSubview || (0, _reactNative.requireNativeComponent)('RNSScreenStackHeaderSubview', null);
return NativeScreenStackHeaderSubview;
}
};
class Screen extends _react.default.Component {
constructor(...args) {
super(...args);
_defineProperty(this, "setRef", ref => {
this._ref = ref;
this.props.onComponentRef && this.props.onComponentRef(ref);
});
}
setNativeProps(props) {
this._ref.setNativeProps(props);
}
render() {
const {
enabled = true
} = this.props;
if (!ENABLE_SCREENS || !enabled) {
// 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
/* eslint-disable no-unused-vars */
const {
active,
enabled,
onComponentRef,
...rest
} = this.props;
return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, _extends({}, rest, {
ref: this.setRef
}));
} else {
AnimatedNativeScreen = AnimatedNativeScreen || _reactNative.Animated.createAnimatedComponent(ScreensNativeModules.NativeScreen);
let {
enabled,
active,
activityState,
...rest
} = this.props;
if (active !== undefined && activityState === undefined) {
console.warn('It appears that you are using old version of react-navigation library. Please update @react-navigation/bottom-tabs, @react-navigation/stack and @react-navigation/drawer to version 5.10.0 or above to take full advantage of new functionality added to react-native-screens');
activityState = active !== 0 ? 2 : 0; // in the new version, we need one of the screens to have value of 2 after the transition
}
return /*#__PURE__*/_react.default.createElement(AnimatedNativeScreen, _extends({}, rest, {
activityState: activityState,
ref: this.setRef
}));
}
}
}
class ScreenContainer extends _react.default.Component {
render() {
const {
enabled = true,
...rest
} = this.props;
if (!ENABLE_SCREENS || !enabled) {
return /*#__PURE__*/_react.default.createElement(_reactNative.View, rest);
} else {
return /*#__PURE__*/_react.default.createElement(ScreensNativeModules.NativeScreenContainer, this.props);
}
}
}
const styles = _reactNative.StyleSheet.create({
headerSubview: {
position: 'absolute',
top: 0,
right: 0,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
}
});
const ScreenStackHeaderBackButtonImage = props => /*#__PURE__*/_react.default.createElement(ScreensNativeModules.NativeScreenStackHeaderSubview, {
type: "back",
style: styles.headerSubview
}, /*#__PURE__*/_react.default.createElement(_reactNative.Image, _extends({
resizeMode: "center",
fadeDuration: 0
}, props)));
const ScreenStackHeaderRightView = props => /*#__PURE__*/_react.default.createElement(ScreensNativeModules.NativeScreenStackHeaderSubview, _extends({}, props, {
type: "right",
style: styles.headerSubview
}));
const ScreenStackHeaderLeftView = props => /*#__PURE__*/_react.default.createElement(ScreensNativeModules.NativeScreenStackHeaderSubview, _extends({}, props, {
type: "left",
style: styles.headerSubview
}));
const ScreenStackHeaderCenterView = props => /*#__PURE__*/_react.default.createElement(ScreensNativeModules.NativeScreenStackHeaderSubview, _extends({}, props, {
type: "center",
style: styles.headerSubview
}));
module.exports = {
ScreenContainer,
Screen,
get NativeScreen() {
return ScreensNativeModules.NativeScreen;
},
get NativeScreenContainer() {
return ScreensNativeModules.NativeScreenContainer;
},
get ScreenStack() {
return ScreensNativeModules.NativeScreenStack;
},
get ScreenStackHeaderConfig() {
return ScreensNativeModules.NativeScreenStackHeaderConfig;
},
get ScreenStackHeaderSubview() {
return ScreensNativeModules.NativeScreenStackHeaderSubview;
},
ScreenStackHeaderBackButtonImage,
ScreenStackHeaderRightView,
ScreenStackHeaderLeftView,
ScreenStackHeaderCenterView,
enableScreens,
screensEnabled,
shouldUseActivityState
};
//# sourceMappingURL=index.native.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "createNativeStackNavigator", {
enumerable: true,
get: function () {
return _createNativeStackNavigator.default;
}
});
Object.defineProperty(exports, "NativeStackView", {
enumerable: true,
get: function () {
return _NativeStackView.default;
}
});
var _createNativeStackNavigator = _interopRequireDefault(require("./navigators/createNativeStackNavigator"));
var _NativeStackView = _interopRequireDefault(require("./views/NativeStackView"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["index.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAGA;;AAKA","sourcesContent":["/**\n * Navigators\n */\nexport { default as createNativeStackNavigator } from './navigators/createNativeStackNavigator';\n\n/**\n * Views\n */\nexport { default as NativeStackView } from './views/NativeStackView';\n\n/**\n * Types\n */\nexport type {\n NativeStackNavigationOptions,\n NativeStackNavigationProp,\n} from './types';\n"]}

View File

@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _native = require("@react-navigation/native");
var React = _interopRequireWildcard(require("react"));
var _reactNativeScreens = require("react-native-screens");
var _NativeStackView = _interopRequireDefault(require("../views/NativeStackView"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function NativeStackNavigator({
initialRouteName,
children,
screenOptions,
...rest
}) {
if (!(0, _reactNativeScreens.screensEnabled)()) {
throw new Error('Native stack is only available if React Native Screens is enabled.');
}
const {
state,
descriptors,
navigation
} = (0, _native.useNavigationBuilder)(_native.StackRouter, {
initialRouteName,
children,
screenOptions
});
React.useEffect(() => {
var _navigation$addListen;
return navigation === null || navigation === void 0 ? void 0 : (_navigation$addListen = navigation.addListener) === null || _navigation$addListen === void 0 ? void 0 : _navigation$addListen.call(navigation, 'tabPress', e => {
const isFocused = navigation.isFocused(); // Run the operation in the next frame so we're sure all listeners have been run
// This is necessary to know if preventDefault() has been called
requestAnimationFrame(() => {
if (state.index > 0 && isFocused && !e.defaultPrevented) {
// When user taps on already focused tab and we're inside the tab,
// reset the stack to replicate native behaviour
navigation.dispatch({ ..._native.StackActions.popToTop(),
target: state.key
});
}
});
});
}, [navigation, state.index, state.key]);
return /*#__PURE__*/React.createElement(_NativeStackView.default, _extends({}, rest, {
state: state,
navigation: navigation,
descriptors: descriptors
}));
}
var _default = (0, _native.createNavigatorFactory)(NativeStackNavigator);
exports.default = _default;
//# sourceMappingURL=createNativeStackNavigator.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["createNativeStackNavigator.tsx"],"names":["NativeStackNavigator","initialRouteName","children","screenOptions","rest","Error","state","descriptors","navigation","StackRouter","React","useEffect","addListener","e","isFocused","requestAnimationFrame","index","defaultPrevented","dispatch","StackActions","popToTop","target","key"],"mappings":";;;;;;;AAAA;;AAWA;;AACA;;AAMA;;;;;;;;;;AAEA,SAASA,oBAAT,CAA8B;AAC5BC,EAAAA,gBAD4B;AAE5BC,EAAAA,QAF4B;AAG5BC,EAAAA,aAH4B;AAI5B,KAAGC;AAJyB,CAA9B,EAK8B;AAC5B,MAAI,CAAC,yCAAL,EAAuB;AACrB,UAAM,IAAIC,KAAJ,CACJ,oEADI,CAAN;AAGD;;AAED,QAAM;AAAEC,IAAAA,KAAF;AAASC,IAAAA,WAAT;AAAsBC,IAAAA;AAAtB,MAAqC,kCAMzCC,mBANyC,EAM5B;AACbR,IAAAA,gBADa;AAEbC,IAAAA,QAFa;AAGbC,IAAAA;AAHa,GAN4B,CAA3C;AAYAO,EAAAA,KAAK,CAACC,SAAN,CACE;AAAA;;AAAA,WACEH,UADF,aACEA,UADF,gDACEA,UAAU,CAAEI,WADd,0DACE,2BAAAJ,UAAU,EAAgB,UAAhB,EAA6BK,CAAD,IAAO;AAC3C,YAAMC,SAAS,GAAGN,UAAU,CAACM,SAAX,EAAlB,CAD2C,CAG3C;AACA;;AACAC,MAAAA,qBAAqB,CAAC,MAAM;AAC1B,YACET,KAAK,CAACU,KAAN,GAAc,CAAd,IACAF,SADA,IAEA,CAAED,CAAD,CAAkCI,gBAHrC,EAIE;AACA;AACA;AACAT,UAAAA,UAAU,CAACU,QAAX,CAAoB,EAClB,GAAGC,qBAAaC,QAAb,EADe;AAElBC,YAAAA,MAAM,EAAEf,KAAK,CAACgB;AAFI,WAApB;AAID;AACF,OAboB,CAArB;AAcD,KAnBS,CADZ;AAAA,GADF,EAsBE,CAACd,UAAD,EAAaF,KAAK,CAACU,KAAnB,EAA0BV,KAAK,CAACgB,GAAhC,CAtBF;AAyBA,sBACE,oBAAC,wBAAD,eACMlB,IADN;AAEE,IAAA,KAAK,EAAEE,KAFT;AAGE,IAAA,UAAU,EAAEE,UAHd;AAIE,IAAA,WAAW,EAAED;AAJf,KADF;AAQD;;eAEc,oCAKbP,oBALa,C","sourcesContent":["import {\n createNavigatorFactory,\n EventArg,\n StackActions,\n StackActionHelpers,\n StackNavigationState,\n StackRouter,\n StackRouterOptions,\n ParamListBase,\n useNavigationBuilder,\n} from '@react-navigation/native';\nimport * as React from 'react';\nimport { screensEnabled } from 'react-native-screens';\nimport {\n NativeStackNavigationEventMap,\n NativeStackNavigationOptions,\n NativeStackNavigatorProps,\n} from '../types';\nimport NativeStackView from '../views/NativeStackView';\n\nfunction NativeStackNavigator({\n initialRouteName,\n children,\n screenOptions,\n ...rest\n}: NativeStackNavigatorProps) {\n if (!screensEnabled()) {\n throw new Error(\n 'Native stack is only available if React Native Screens is enabled.'\n );\n }\n\n const { state, descriptors, navigation } = useNavigationBuilder<\n StackNavigationState<ParamListBase>,\n StackRouterOptions,\n StackActionHelpers<ParamListBase>,\n NativeStackNavigationOptions,\n NativeStackNavigationEventMap\n >(StackRouter, {\n initialRouteName,\n children,\n screenOptions,\n });\n\n React.useEffect(\n () =>\n navigation?.addListener?.('tabPress', (e) => {\n const isFocused = navigation.isFocused();\n\n // Run the operation in the next frame so we're sure all listeners have been run\n // This is necessary to know if preventDefault() has been called\n requestAnimationFrame(() => {\n if (\n state.index > 0 &&\n isFocused &&\n !(e as EventArg<'tabPress', true>).defaultPrevented\n ) {\n // When user taps on already focused tab and we're inside the tab,\n // reset the stack to replicate native behaviour\n navigation.dispatch({\n ...StackActions.popToTop(),\n target: state.key,\n });\n }\n });\n }),\n [navigation, state.index, state.key]\n );\n\n return (\n <NativeStackView\n {...rest}\n state={state}\n navigation={navigation}\n descriptors={descriptors}\n />\n );\n}\n\nexport default createNavigatorFactory<\n StackNavigationState<ParamListBase>,\n NativeStackNavigationOptions,\n NativeStackNavigationEventMap,\n typeof NativeStackNavigator\n>(NativeStackNavigator);\n"]}

View File

@ -0,0 +1,2 @@
"use strict";
//# sourceMappingURL=types.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[]}

View File

@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.processFonts = processFonts;
var _expoFont = require("expo-font");
// @ts-ignore this file extension is parsed only in managed workflow, so `expo-font` should be always available there
// eslint-disable-next-line import/no-unresolved
function processFonts(fontFamilies) {
return fontFamilies.map(fontFamily => (0, _expoFont.processFontFamily)(fontFamily));
}
//# sourceMappingURL=FontProcessor.expo.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["FontProcessor.expo.tsx"],"names":["processFonts","fontFamilies","map","fontFamily"],"mappings":";;;;;;;AAEA;;AAFA;AACA;AAGO,SAASA,YAAT,CACLC,YADK,EAEmB;AACxB,SAAOA,YAAY,CAACC,GAAb,CAAkBC,UAAD,IAAgB,iCAAkBA,UAAlB,CAAjC,CAAP;AACD","sourcesContent":["// @ts-ignore this file extension is parsed only in managed workflow, so `expo-font` should be always available there\n// eslint-disable-next-line import/no-unresolved\nimport { processFontFamily } from 'expo-font';\n\nexport function processFonts(\n fontFamilies: (string | undefined)[]\n): (string | undefined)[] {\n return fontFamilies.map((fontFamily) => processFontFamily(fontFamily));\n}\n"]}

View File

@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.processFonts = processFonts;
// do nothing outside of expo
function processFonts(fontFamilies) {
return fontFamilies;
}
//# sourceMappingURL=FontProcessor.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["FontProcessor.tsx"],"names":["processFonts","fontFamilies"],"mappings":";;;;;;;AAAA;AACO,SAASA,YAAT,CACLC,YADK,EAEmB;AACxB,SAAOA,YAAP;AACD","sourcesContent":["// do nothing outside of expo\nexport function processFonts(\n fontFamilies: (string | undefined)[]\n): (string | undefined)[] {\n return fontFamilies;\n}\n"]}

View File

@ -0,0 +1,92 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = HeaderConfig;
var _native = require("@react-navigation/native");
var React = _interopRequireWildcard(require("react"));
var _reactNativeScreens = require("react-native-screens");
var _FontProcessor = require("./FontProcessor");
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function HeaderConfig({
backButtonImage,
backButtonInCustomView,
direction,
headerBackTitle,
headerBackTitleStyle = {},
headerBackTitleVisible = true,
headerCenter,
headerHideBackButton,
headerHideShadow,
headerLargeStyle = {},
headerLargeTitle,
headerLargeTitleHideShadow,
headerLargeTitleStyle = {},
headerLeft,
headerRight,
headerShown,
headerStyle = {},
headerTintColor,
headerTitle,
headerTitleStyle = {},
headerTopInsetEnabled = true,
headerTranslucent,
route,
statusBarAnimation,
statusBarHidden,
statusBarStyle,
title
}) {
const {
colors
} = (0, _native.useTheme)();
const tintColor = headerTintColor !== null && headerTintColor !== void 0 ? headerTintColor : colors.primary;
const [backTitleFontFamily, largeTitleFontFamily, titleFontFamily] = (0, _FontProcessor.processFonts)([headerBackTitleStyle.fontFamily, headerLargeTitleStyle.fontFamily, headerTitleStyle.fontFamily]);
return /*#__PURE__*/React.createElement(_reactNativeScreens.ScreenStackHeaderConfig, {
backButtonInCustomView: backButtonInCustomView,
backgroundColor: headerStyle.backgroundColor ? headerStyle.backgroundColor : colors.card,
backTitle: headerBackTitleVisible ? headerBackTitle : ' ',
backTitleFontFamily: backTitleFontFamily,
backTitleFontSize: headerBackTitleStyle.fontSize,
blurEffect: headerStyle.blurEffect,
color: tintColor,
direction: direction,
hidden: headerShown === false,
hideBackButton: headerHideBackButton,
hideShadow: headerHideShadow,
largeTitle: headerLargeTitle,
largeTitleBackgroundColor: headerLargeStyle.backgroundColor,
largeTitleColor: headerLargeTitleStyle.color,
largeTitleFontFamily: largeTitleFontFamily,
largeTitleFontSize: headerLargeTitleStyle.fontSize,
largeTitleHideShadow: headerLargeTitleHideShadow,
statusBarAnimation: statusBarAnimation,
statusBarHidden: statusBarHidden,
statusBarStyle: statusBarStyle,
title: headerTitle !== undefined ? headerTitle : title !== undefined ? title : route.name,
titleColor: headerTitleStyle.color !== undefined ? headerTitleStyle.color : headerTintColor !== undefined ? headerTintColor : colors.text,
titleFontFamily: titleFontFamily,
titleFontSize: headerTitleStyle.fontSize,
topInsetEnabled: headerTopInsetEnabled,
translucent: headerTranslucent === true
}, headerRight !== undefined ? /*#__PURE__*/React.createElement(_reactNativeScreens.ScreenStackHeaderRightView, null, headerRight({
tintColor
})) : null, backButtonImage !== undefined ? /*#__PURE__*/React.createElement(_reactNativeScreens.ScreenStackHeaderBackButtonImage, {
key: "backImage",
source: backButtonImage
}) : null, headerLeft !== undefined ? /*#__PURE__*/React.createElement(_reactNativeScreens.ScreenStackHeaderLeftView, null, headerLeft({
tintColor
})) : null, headerCenter !== undefined ? /*#__PURE__*/React.createElement(_reactNativeScreens.ScreenStackHeaderCenterView, null, headerCenter({
tintColor
})) : null);
}
//# sourceMappingURL=HeaderConfig.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,151 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = NativeStackView;
var _native = require("@react-navigation/native");
var React = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _AppContainer = _interopRequireDefault(require("react-native/Libraries/ReactNative/AppContainer"));
var _reactNativeScreens = require("react-native-screens");
var _HeaderConfig = _interopRequireDefault(require("./HeaderConfig"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const Screen = _reactNativeScreens.Screen;
const isAndroid = _reactNative.Platform.OS === 'android';
let Container = _reactNative.View;
if (__DEV__) {
const DebugContainer = props => {
const {
stackAnimation,
...rest
} = props;
if (_reactNative.Platform.OS === 'ios' && stackAnimation !== 'push') {
return /*#__PURE__*/React.createElement(_AppContainer.default, null, /*#__PURE__*/React.createElement(_reactNative.View, rest));
}
return /*#__PURE__*/React.createElement(_reactNative.View, rest);
}; // @ts-ignore Wrong props
Container = DebugContainer;
}
function NativeStackView({
state,
navigation,
descriptors
}) {
const {
key,
routes
} = state;
const {
colors
} = (0, _native.useTheme)();
return /*#__PURE__*/React.createElement(_reactNativeScreens.ScreenStack, {
style: styles.container
}, routes.map(route => {
const {
options,
render: renderScene
} = descriptors[route.key];
const {
gestureEnabled,
replaceAnimation = 'pop',
stackPresentation = 'push',
stackAnimation,
contentStyle
} = options;
const viewStyles = [styles.container, stackPresentation !== 'transparentModal' && {
backgroundColor: colors.background
}, contentStyle];
return /*#__PURE__*/React.createElement(Screen, {
key: route.key,
style: _reactNative.StyleSheet.absoluteFill,
gestureEnabled: isAndroid ? false : gestureEnabled,
replaceAnimation: replaceAnimation,
stackPresentation: stackPresentation,
stackAnimation: stackAnimation,
onWillAppear: () => {
navigation.emit({
type: 'transitionStart',
data: {
closing: false
},
target: route.key
});
},
onWillDisappear: () => {
navigation.emit({
type: 'transitionStart',
data: {
closing: true
},
target: route.key
});
},
onAppear: () => {
navigation.emit({
type: 'appear',
target: route.key
});
navigation.emit({
type: 'transitionEnd',
data: {
closing: false
},
target: route.key
});
},
onDisappear: () => {
navigation.emit({
type: 'transitionEnd',
data: {
closing: true
},
target: route.key
});
},
onDismissed: () => {
navigation.emit({
type: 'dismiss',
target: route.key
});
navigation.dispatch({ ..._native.StackActions.pop(),
source: route.key,
target: key
});
}
}, /*#__PURE__*/React.createElement(_HeaderConfig.default, _extends({}, options, {
route: route
})), /*#__PURE__*/React.createElement(Container, {
style: viewStyles // @ts-ignore Wrong props passed to View
,
stackPresentation: stackPresentation
}, renderScene()));
}));
}
const styles = _reactNative.StyleSheet.create({
container: {
flex: 1
}
});
//# sourceMappingURL=NativeStackView.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,304 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import React from 'react';
import { Platform, StyleSheet } from 'react-native';
import { Screen, ScreenStack, ScreenStackHeaderBackButtonImage, ScreenStackHeaderCenterView, ScreenStackHeaderConfig, ScreenStackHeaderLeftView, ScreenStackHeaderRightView } from 'react-native-screens';
import { createNavigator, SceneView, StackActions, StackRouter } from 'react-navigation';
import { HeaderBackButton } from 'react-navigation-stack';
function renderComponentOrThunk(componentOrThunk, props) {
if (typeof componentOrThunk === 'function') {
return componentOrThunk(props);
}
return componentOrThunk;
}
const REMOVE_ACTION = 'NativeStackNavigator/REMOVE';
class StackView extends React.Component {
constructor(...args) {
super(...args);
_defineProperty(this, "_removeScene", route => {
this.props.navigation.dispatch({
type: REMOVE_ACTION,
immediate: true,
key: route.key
});
});
_defineProperty(this, "_onAppear", (route, descriptor) => {
descriptor.options && descriptor.options.onAppear && descriptor.options.onAppear();
this.props.navigation.dispatch(StackActions.completeTransition({
toChildKey: route.key,
key: this.props.navigation.state.key
}));
});
_defineProperty(this, "_onFinishTransitioning", () => {
const {
routes
} = this.props.navigation.state;
const lastRoute = (routes === null || routes === void 0 ? void 0 : routes.length) && routes[routes.length - 1];
if (lastRoute) {
this.props.navigation.dispatch(StackActions.completeTransition({
toChildKey: lastRoute.key,
key: this.props.navigation.state.key
}));
}
});
_defineProperty(this, "_renderHeaderConfig", (index, route, descriptor) => {
const {
navigationConfig
} = this.props;
const {
options
} = descriptor;
const {
headerMode
} = navigationConfig;
const {
backButtonInCustomView,
direction,
headerBackTitle,
headerBackTitleStyle,
headerBackTitleVisible,
headerHideBackButton,
headerLargeTitleStyle,
headerStyle,
headerTintColor,
headerTitleStyle,
headerTopInsetEnabled = true,
hideShadow,
largeTitle,
statusBarAnimation,
statusBarHidden,
statusBarStyle,
title,
translucent
} = options;
const scene = {
index,
key: route.key,
route,
descriptor
};
const headerOptions = {
backButtonInCustomView,
backTitle: headerBackTitleVisible === false ? '' : headerBackTitle,
backTitleFontFamily: headerBackTitleStyle && headerBackTitleStyle.fontFamily,
backTitleFontSize: headerBackTitleStyle && headerBackTitleStyle.fontSize,
color: headerTintColor,
direction,
topInsetEnabled: headerTopInsetEnabled,
hideBackButton: headerHideBackButton,
hideShadow,
largeTitle,
largeTitleBackgroundColor: headerLargeTitleStyle && headerLargeTitleStyle.backgroundColor,
largeTitleColor: headerLargeTitleStyle && headerLargeTitleStyle.color,
largeTitleFontFamily: headerLargeTitleStyle && headerLargeTitleStyle.fontFamily,
largeTitleFontSize: headerLargeTitleStyle && headerLargeTitleStyle.fontSize,
statusBarAnimation,
statusBarHidden,
statusBarStyle,
title,
titleColor: headerTitleStyle && headerTitleStyle.color || headerTintColor,
titleFontFamily: headerTitleStyle && headerTitleStyle.fontFamily,
titleFontSize: headerTitleStyle && headerTitleStyle.fontSize,
translucent: translucent === undefined ? false : translucent
};
const hasHeader = headerMode !== 'none' && options.header !== null;
if (!hasHeader) {
return /*#__PURE__*/React.createElement(ScreenStackHeaderConfig, _extends({}, headerOptions, {
hidden: true
}));
}
if (headerStyle !== undefined) {
headerOptions.backgroundColor = headerStyle.backgroundColor;
headerOptions.blurEffect = headerStyle.blurEffect;
}
const children = [];
if (options.backButtonImage) {
children.push( /*#__PURE__*/React.createElement(ScreenStackHeaderBackButtonImage, {
key: "backImage",
source: options.backButtonImage
}));
}
if (options.headerLeft !== undefined) {
children.push( /*#__PURE__*/React.createElement(ScreenStackHeaderLeftView, {
key: "left"
}, renderComponentOrThunk(options.headerLeft, {
scene
})));
} else if (options.headerBackImage !== undefined) {
const goBack = () => {
// Go back on next tick because button ripple effect needs to happen on Android
requestAnimationFrame(() => {
descriptor.navigation.goBack(descriptor.key);
});
};
children.push( /*#__PURE__*/React.createElement(ScreenStackHeaderLeftView, {
key: "left"
}, /*#__PURE__*/React.createElement(HeaderBackButton, {
onPress: goBack,
pressColorAndroid: options.headerPressColorAndroid,
tintColor: options.headerTintColor,
backImage: options.headerBackImage,
title: options.backButtonTitle,
truncatedTitle: options.truncatedBackButtonTitle,
backTitleVisible: this.props.backTitleVisible,
titleStyle: options.headerBackTitleStyle,
layoutPreset: this.props.layoutPreset,
scene: scene
})));
}
if (options.headerTitle) {
if (title === undefined && typeof options.headerTitle === 'string') {
headerOptions.title = options.headerTitle;
} else {
children.push( /*#__PURE__*/React.createElement(ScreenStackHeaderCenterView, {
key: "center"
}, renderComponentOrThunk(options.headerTitle, {
scene
})));
}
}
if (options.headerRight) {
children.push( /*#__PURE__*/React.createElement(ScreenStackHeaderRightView, {
key: "right"
}, renderComponentOrThunk(options.headerRight, {
scene
})));
}
if (children.length > 0) {
headerOptions.children = children;
}
return /*#__PURE__*/React.createElement(ScreenStackHeaderConfig, headerOptions);
});
_defineProperty(this, "_renderScene", (index, route, descriptor) => {
const {
navigation,
getComponent,
options
} = descriptor;
const {
mode,
transparentCard
} = this.props.navigationConfig;
const SceneComponent = getComponent();
let stackPresentation = 'push';
if (mode === 'modal' || mode === 'containedModal') {
stackPresentation = mode;
if (transparentCard || options.cardTransparent) {
stackPresentation = mode === 'containedModal' ? 'containedTransparentModal' : 'transparentModal';
}
}
let stackAnimation = options.stackAnimation;
if (options.animationEnabled === false) {
stackAnimation = 'none';
}
const {
screenProps
} = this.props;
return /*#__PURE__*/React.createElement(Screen, {
key: "screen_".concat(route.key),
style: [StyleSheet.absoluteFill, options.cardStyle],
stackAnimation: stackAnimation,
stackPresentation: stackPresentation,
replaceAnimation: options.replaceAnimation === undefined ? 'pop' : options.replaceAnimation,
pointerEvents: index === this.props.navigation.state.routes.length - 1 ? 'auto' : 'none',
gestureEnabled: Platform.OS === 'android' ? false : options.gestureEnabled === undefined ? true : options.gestureEnabled,
onAppear: () => this._onAppear(route, descriptor),
onDismissed: () => this._removeScene(route)
}, this._renderHeaderConfig(index, route, descriptor), /*#__PURE__*/React.createElement(SceneView, {
screenProps: screenProps,
navigation: navigation,
component: SceneComponent
}));
});
}
render() {
const {
navigation,
descriptors
} = this.props;
return /*#__PURE__*/React.createElement(ScreenStack, {
style: styles.scenes,
onFinishTransitioning: this._onFinishTransitioning
}, navigation.state.routes.map((route, i) => this._renderScene(i, route, descriptors[route.key])));
}
}
const styles = StyleSheet.create({
scenes: {
flex: 1
}
});
function createStackNavigator(routeConfigMap, stackConfig = {}) {
const router = StackRouter(routeConfigMap, stackConfig); // belowe we override getStateForAction method in order to add handling for
// a custom native stack navigation action. The action REMOVE that we want to
// add works in a similar way to POP, but it does not remove all the routes
// that sit on top of the removed route. For example if we have three routes
// [a,b,c] and call POP on b, then both b and c will go away. In case we
// call REMOVE on b, only b will be removed from the stack and the resulting
// state will be [a, c]
const superGetStateForAction = router.getStateForAction;
router.getStateForAction = (action, state) => {
if (action.type === REMOVE_ACTION) {
const {
key,
immediate
} = action;
let backRouteIndex = state.index;
if (key) {
const backRoute = state.routes.find(route => route.key === key);
backRouteIndex = state.routes.indexOf(backRoute);
}
if (backRouteIndex > 0) {
const newRoutes = [...state.routes];
newRoutes.splice(backRouteIndex, 1);
return { ...state,
routes: newRoutes,
index: newRoutes.length - 1,
isTransitioning: immediate !== true
};
}
}
return superGetStateForAction(action, state);
}; // Create a navigator with StackView as the view
return createNavigator(StackView, router, stackConfig);
}
export default createStackNavigator;
//# sourceMappingURL=createNativeStackNavigator.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
//# sourceMappingURL=index.d.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[]}

31
node_modules/react-native-screens/lib/module/index.js generated vendored Normal file
View File

@ -0,0 +1,31 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React from 'react';
import { Animated, View } from 'react-native';
let ENABLE_SCREENS = true;
export function enableScreens(shouldEnableScreens = true) {
ENABLE_SCREENS = shouldEnableScreens;
}
export function screensEnabled() {
return ENABLE_SCREENS;
}
export class NativeScreen extends React.Component {
render() {
const {
active,
style,
enabled = true,
...rest
} = this.props;
return /*#__PURE__*/React.createElement(View, _extends({
style: [style, ENABLE_SCREENS && enabled && !active ? {
display: 'none'
} : null]
}, rest));
}
}
export const Screen = Animated.createAnimatedComponent(NativeScreen);
export const ScreenContainer = View;
export const NativeScreenContainer = View;
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["index.js"],"names":["React","Animated","View","ENABLE_SCREENS","enableScreens","shouldEnableScreens","screensEnabled","NativeScreen","Component","render","active","style","enabled","rest","props","display","Screen","createAnimatedComponent","ScreenContainer","NativeScreenContainer"],"mappings":";;AAAA,OAAOA,KAAP,MAAkB,OAAlB;AACA,SAASC,QAAT,EAAmBC,IAAnB,QAA+B,cAA/B;AAEA,IAAIC,cAAc,GAAG,IAArB;AAEA,OAAO,SAASC,aAAT,CAAuBC,mBAAmB,GAAG,IAA7C,EAAmD;AACxDF,EAAAA,cAAc,GAAGE,mBAAjB;AACD;AAED,OAAO,SAASC,cAAT,GAA0B;AAC/B,SAAOH,cAAP;AACD;AAED,OAAO,MAAMI,YAAN,SAA2BP,KAAK,CAACQ,SAAjC,CAA2C;AAChDC,EAAAA,MAAM,GAAG;AACP,UAAM;AAAEC,MAAAA,MAAF;AAAUC,MAAAA,KAAV;AAAiBC,MAAAA,OAAO,GAAG,IAA3B;AAAiC,SAAGC;AAApC,QAA6C,KAAKC,KAAxD;AAEA,wBACE,oBAAC,IAAD;AACE,MAAA,KAAK,EAAE,CACLH,KADK,EAELR,cAAc,IAAIS,OAAlB,IAA6B,CAACF,MAA9B,GAAuC;AAAEK,QAAAA,OAAO,EAAE;AAAX,OAAvC,GAA6D,IAFxD;AADT,OAKMF,IALN,EADF;AASD;;AAb+C;AAgBlD,OAAO,MAAMG,MAAM,GAAGf,QAAQ,CAACgB,uBAAT,CAAiCV,YAAjC,CAAf;AAEP,OAAO,MAAMW,eAAe,GAAGhB,IAAxB;AAEP,OAAO,MAAMiB,qBAAqB,GAAGjB,IAA9B","sourcesContent":["import React from 'react';\nimport { Animated, View } from 'react-native';\n\nlet ENABLE_SCREENS = true;\n\nexport function enableScreens(shouldEnableScreens = true) {\n ENABLE_SCREENS = shouldEnableScreens;\n}\n\nexport function screensEnabled() {\n return ENABLE_SCREENS;\n}\n\nexport class NativeScreen extends React.Component {\n render() {\n const { active, style, enabled = true, ...rest } = this.props;\n\n return (\n <View\n style={[\n style,\n ENABLE_SCREENS && enabled && !active ? { display: 'none' } : null,\n ]}\n {...rest}\n />\n );\n }\n}\n\nexport const Screen = Animated.createAnimatedComponent(NativeScreen);\n\nexport const ScreenContainer = View;\n\nexport const NativeScreenContainer = View;\n"]}

View File

@ -0,0 +1,199 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import React from 'react';
import { Animated, Image, requireNativeComponent, StyleSheet, UIManager, View } from 'react-native';
let ENABLE_SCREENS = false;
function enableScreens(shouldEnableScreens = true) {
ENABLE_SCREENS = shouldEnableScreens;
if (ENABLE_SCREENS && !UIManager.getViewManagerConfig('RNSScreen')) {
console.error("Screen native module hasn't been linked. Please check the react-native-screens README for more details");
}
} // const that tells if the library should use new implementation, will be undefined for older versions
const shouldUseActivityState = true;
function screensEnabled() {
return ENABLE_SCREENS;
} // We initialize these lazily so that importing the module doesn't throw error when not linked
// This is necessary coz libraries such as React Navigation import the library where it may not be enabled
let NativeScreenValue;
let NativeScreenContainerValue;
let NativeScreenStack;
let NativeScreenStackHeaderConfig;
let NativeScreenStackHeaderSubview;
let AnimatedNativeScreen;
const ScreensNativeModules = {
get NativeScreen() {
NativeScreenValue = NativeScreenValue || requireNativeComponent('RNSScreen', null);
return NativeScreenValue;
},
get NativeScreenContainer() {
NativeScreenContainerValue = NativeScreenContainerValue || requireNativeComponent('RNSScreenContainer', null);
return NativeScreenContainerValue;
},
get NativeScreenStack() {
NativeScreenStack = NativeScreenStack || requireNativeComponent('RNSScreenStack', null);
return NativeScreenStack;
},
get NativeScreenStackHeaderConfig() {
NativeScreenStackHeaderConfig = NativeScreenStackHeaderConfig || requireNativeComponent('RNSScreenStackHeaderConfig', null);
return NativeScreenStackHeaderConfig;
},
get NativeScreenStackHeaderSubview() {
NativeScreenStackHeaderSubview = NativeScreenStackHeaderSubview || requireNativeComponent('RNSScreenStackHeaderSubview', null);
return NativeScreenStackHeaderSubview;
}
};
class Screen extends React.Component {
constructor(...args) {
super(...args);
_defineProperty(this, "setRef", ref => {
this._ref = ref;
this.props.onComponentRef && this.props.onComponentRef(ref);
});
}
setNativeProps(props) {
this._ref.setNativeProps(props);
}
render() {
const {
enabled = true
} = this.props;
if (!ENABLE_SCREENS || !enabled) {
// 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
/* eslint-disable no-unused-vars */
const {
active,
enabled,
onComponentRef,
...rest
} = this.props;
return /*#__PURE__*/React.createElement(Animated.View, _extends({}, rest, {
ref: this.setRef
}));
} else {
AnimatedNativeScreen = AnimatedNativeScreen || Animated.createAnimatedComponent(ScreensNativeModules.NativeScreen);
let {
enabled,
active,
activityState,
...rest
} = this.props;
if (active !== undefined && activityState === undefined) {
console.warn('It appears that you are using old version of react-navigation library. Please update @react-navigation/bottom-tabs, @react-navigation/stack and @react-navigation/drawer to version 5.10.0 or above to take full advantage of new functionality added to react-native-screens');
activityState = active !== 0 ? 2 : 0; // in the new version, we need one of the screens to have value of 2 after the transition
}
return /*#__PURE__*/React.createElement(AnimatedNativeScreen, _extends({}, rest, {
activityState: activityState,
ref: this.setRef
}));
}
}
}
class ScreenContainer extends React.Component {
render() {
const {
enabled = true,
...rest
} = this.props;
if (!ENABLE_SCREENS || !enabled) {
return /*#__PURE__*/React.createElement(View, rest);
} else {
return /*#__PURE__*/React.createElement(ScreensNativeModules.NativeScreenContainer, this.props);
}
}
}
const styles = StyleSheet.create({
headerSubview: {
position: 'absolute',
top: 0,
right: 0,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
}
});
const ScreenStackHeaderBackButtonImage = props => /*#__PURE__*/React.createElement(ScreensNativeModules.NativeScreenStackHeaderSubview, {
type: "back",
style: styles.headerSubview
}, /*#__PURE__*/React.createElement(Image, _extends({
resizeMode: "center",
fadeDuration: 0
}, props)));
const ScreenStackHeaderRightView = props => /*#__PURE__*/React.createElement(ScreensNativeModules.NativeScreenStackHeaderSubview, _extends({}, props, {
type: "right",
style: styles.headerSubview
}));
const ScreenStackHeaderLeftView = props => /*#__PURE__*/React.createElement(ScreensNativeModules.NativeScreenStackHeaderSubview, _extends({}, props, {
type: "left",
style: styles.headerSubview
}));
const ScreenStackHeaderCenterView = props => /*#__PURE__*/React.createElement(ScreensNativeModules.NativeScreenStackHeaderSubview, _extends({}, props, {
type: "center",
style: styles.headerSubview
}));
module.exports = {
ScreenContainer,
Screen,
get NativeScreen() {
return ScreensNativeModules.NativeScreen;
},
get NativeScreenContainer() {
return ScreensNativeModules.NativeScreenContainer;
},
get ScreenStack() {
return ScreensNativeModules.NativeScreenStack;
},
get ScreenStackHeaderConfig() {
return ScreensNativeModules.NativeScreenStackHeaderConfig;
},
get ScreenStackHeaderSubview() {
return ScreensNativeModules.NativeScreenStackHeaderSubview;
},
ScreenStackHeaderBackButtonImage,
ScreenStackHeaderRightView,
ScreenStackHeaderLeftView,
ScreenStackHeaderCenterView,
enableScreens,
screensEnabled,
shouldUseActivityState
};
//# sourceMappingURL=index.native.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
/**
* Navigators
*/
export { default as createNativeStackNavigator } from './navigators/createNativeStackNavigator';
/**
* Views
*/
export { default as NativeStackView } from './views/NativeStackView';
/**
* Types
*/
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["index.tsx"],"names":["default","createNativeStackNavigator","NativeStackView"],"mappings":"AAAA;;;AAGA,SAASA,OAAO,IAAIC,0BAApB,QAAsD,yCAAtD;AAEA;;;;AAGA,SAASD,OAAO,IAAIE,eAApB,QAA2C,yBAA3C;AAEA","sourcesContent":["/**\n * Navigators\n */\nexport { default as createNativeStackNavigator } from './navigators/createNativeStackNavigator';\n\n/**\n * Views\n */\nexport { default as NativeStackView } from './views/NativeStackView';\n\n/**\n * Types\n */\nexport type {\n NativeStackNavigationOptions,\n NativeStackNavigationProp,\n} from './types';\n"]}

View File

@ -0,0 +1,53 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import { createNavigatorFactory, StackActions, StackRouter, useNavigationBuilder } from '@react-navigation/native';
import * as React from 'react';
import { screensEnabled } from 'react-native-screens';
import NativeStackView from '../views/NativeStackView';
function NativeStackNavigator({
initialRouteName,
children,
screenOptions,
...rest
}) {
if (!screensEnabled()) {
throw new Error('Native stack is only available if React Native Screens is enabled.');
}
const {
state,
descriptors,
navigation
} = useNavigationBuilder(StackRouter, {
initialRouteName,
children,
screenOptions
});
React.useEffect(() => {
var _navigation$addListen;
return navigation === null || navigation === void 0 ? void 0 : (_navigation$addListen = navigation.addListener) === null || _navigation$addListen === void 0 ? void 0 : _navigation$addListen.call(navigation, 'tabPress', e => {
const isFocused = navigation.isFocused(); // Run the operation in the next frame so we're sure all listeners have been run
// This is necessary to know if preventDefault() has been called
requestAnimationFrame(() => {
if (state.index > 0 && isFocused && !e.defaultPrevented) {
// When user taps on already focused tab and we're inside the tab,
// reset the stack to replicate native behaviour
navigation.dispatch({ ...StackActions.popToTop(),
target: state.key
});
}
});
});
}, [navigation, state.index, state.key]);
return /*#__PURE__*/React.createElement(NativeStackView, _extends({}, rest, {
state: state,
navigation: navigation,
descriptors: descriptors
}));
}
export default createNavigatorFactory(NativeStackNavigator);
//# sourceMappingURL=createNativeStackNavigator.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["createNativeStackNavigator.tsx"],"names":["createNavigatorFactory","StackActions","StackRouter","useNavigationBuilder","React","screensEnabled","NativeStackView","NativeStackNavigator","initialRouteName","children","screenOptions","rest","Error","state","descriptors","navigation","useEffect","addListener","e","isFocused","requestAnimationFrame","index","defaultPrevented","dispatch","popToTop","target","key"],"mappings":";;AAAA,SACEA,sBADF,EAGEC,YAHF,EAMEC,WANF,EASEC,oBATF,QAUO,0BAVP;AAWA,OAAO,KAAKC,KAAZ,MAAuB,OAAvB;AACA,SAASC,cAAT,QAA+B,sBAA/B;AAMA,OAAOC,eAAP,MAA4B,0BAA5B;;AAEA,SAASC,oBAAT,CAA8B;AAC5BC,EAAAA,gBAD4B;AAE5BC,EAAAA,QAF4B;AAG5BC,EAAAA,aAH4B;AAI5B,KAAGC;AAJyB,CAA9B,EAK8B;AAC5B,MAAI,CAACN,cAAc,EAAnB,EAAuB;AACrB,UAAM,IAAIO,KAAJ,CACJ,oEADI,CAAN;AAGD;;AAED,QAAM;AAAEC,IAAAA,KAAF;AAASC,IAAAA,WAAT;AAAsBC,IAAAA;AAAtB,MAAqCZ,oBAAoB,CAM7DD,WAN6D,EAMhD;AACbM,IAAAA,gBADa;AAEbC,IAAAA,QAFa;AAGbC,IAAAA;AAHa,GANgD,CAA/D;AAYAN,EAAAA,KAAK,CAACY,SAAN,CACE;AAAA;;AAAA,WACED,UADF,aACEA,UADF,gDACEA,UAAU,CAAEE,WADd,0DACE,2BAAAF,UAAU,EAAgB,UAAhB,EAA6BG,CAAD,IAAO;AAC3C,YAAMC,SAAS,GAAGJ,UAAU,CAACI,SAAX,EAAlB,CAD2C,CAG3C;AACA;;AACAC,MAAAA,qBAAqB,CAAC,MAAM;AAC1B,YACEP,KAAK,CAACQ,KAAN,GAAc,CAAd,IACAF,SADA,IAEA,CAAED,CAAD,CAAkCI,gBAHrC,EAIE;AACA;AACA;AACAP,UAAAA,UAAU,CAACQ,QAAX,CAAoB,EAClB,GAAGtB,YAAY,CAACuB,QAAb,EADe;AAElBC,YAAAA,MAAM,EAAEZ,KAAK,CAACa;AAFI,WAApB;AAID;AACF,OAboB,CAArB;AAcD,KAnBS,CADZ;AAAA,GADF,EAsBE,CAACX,UAAD,EAAaF,KAAK,CAACQ,KAAnB,EAA0BR,KAAK,CAACa,GAAhC,CAtBF;AAyBA,sBACE,oBAAC,eAAD,eACMf,IADN;AAEE,IAAA,KAAK,EAAEE,KAFT;AAGE,IAAA,UAAU,EAAEE,UAHd;AAIE,IAAA,WAAW,EAAED;AAJf,KADF;AAQD;;AAED,eAAed,sBAAsB,CAKnCO,oBALmC,CAArC","sourcesContent":["import {\n createNavigatorFactory,\n EventArg,\n StackActions,\n StackActionHelpers,\n StackNavigationState,\n StackRouter,\n StackRouterOptions,\n ParamListBase,\n useNavigationBuilder,\n} from '@react-navigation/native';\nimport * as React from 'react';\nimport { screensEnabled } from 'react-native-screens';\nimport {\n NativeStackNavigationEventMap,\n NativeStackNavigationOptions,\n NativeStackNavigatorProps,\n} from '../types';\nimport NativeStackView from '../views/NativeStackView';\n\nfunction NativeStackNavigator({\n initialRouteName,\n children,\n screenOptions,\n ...rest\n}: NativeStackNavigatorProps) {\n if (!screensEnabled()) {\n throw new Error(\n 'Native stack is only available if React Native Screens is enabled.'\n );\n }\n\n const { state, descriptors, navigation } = useNavigationBuilder<\n StackNavigationState<ParamListBase>,\n StackRouterOptions,\n StackActionHelpers<ParamListBase>,\n NativeStackNavigationOptions,\n NativeStackNavigationEventMap\n >(StackRouter, {\n initialRouteName,\n children,\n screenOptions,\n });\n\n React.useEffect(\n () =>\n navigation?.addListener?.('tabPress', (e) => {\n const isFocused = navigation.isFocused();\n\n // Run the operation in the next frame so we're sure all listeners have been run\n // This is necessary to know if preventDefault() has been called\n requestAnimationFrame(() => {\n if (\n state.index > 0 &&\n isFocused &&\n !(e as EventArg<'tabPress', true>).defaultPrevented\n ) {\n // When user taps on already focused tab and we're inside the tab,\n // reset the stack to replicate native behaviour\n navigation.dispatch({\n ...StackActions.popToTop(),\n target: state.key,\n });\n }\n });\n }),\n [navigation, state.index, state.key]\n );\n\n return (\n <NativeStackView\n {...rest}\n state={state}\n navigation={navigation}\n descriptors={descriptors}\n />\n );\n}\n\nexport default createNavigatorFactory<\n StackNavigationState<ParamListBase>,\n NativeStackNavigationOptions,\n NativeStackNavigationEventMap,\n typeof NativeStackNavigator\n>(NativeStackNavigator);\n"]}

View File

@ -0,0 +1,2 @@
//# sourceMappingURL=types.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[]}

View File

@ -0,0 +1,7 @@
// @ts-ignore this file extension is parsed only in managed workflow, so `expo-font` should be always available there
// eslint-disable-next-line import/no-unresolved
import { processFontFamily } from 'expo-font';
export function processFonts(fontFamilies) {
return fontFamilies.map(fontFamily => processFontFamily(fontFamily));
}
//# sourceMappingURL=FontProcessor.expo.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["FontProcessor.expo.tsx"],"names":["processFontFamily","processFonts","fontFamilies","map","fontFamily"],"mappings":"AAAA;AACA;AACA,SAASA,iBAAT,QAAkC,WAAlC;AAEA,OAAO,SAASC,YAAT,CACLC,YADK,EAEmB;AACxB,SAAOA,YAAY,CAACC,GAAb,CAAkBC,UAAD,IAAgBJ,iBAAiB,CAACI,UAAD,CAAlD,CAAP;AACD","sourcesContent":["// @ts-ignore this file extension is parsed only in managed workflow, so `expo-font` should be always available there\n// eslint-disable-next-line import/no-unresolved\nimport { processFontFamily } from 'expo-font';\n\nexport function processFonts(\n fontFamilies: (string | undefined)[]\n): (string | undefined)[] {\n return fontFamilies.map((fontFamily) => processFontFamily(fontFamily));\n}\n"]}

View File

@ -0,0 +1,5 @@
// do nothing outside of expo
export function processFonts(fontFamilies) {
return fontFamilies;
}
//# sourceMappingURL=FontProcessor.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["FontProcessor.tsx"],"names":["processFonts","fontFamilies"],"mappings":"AAAA;AACA,OAAO,SAASA,YAAT,CACLC,YADK,EAEmB;AACxB,SAAOA,YAAP;AACD","sourcesContent":["// do nothing outside of expo\nexport function processFonts(\n fontFamilies: (string | undefined)[]\n): (string | undefined)[] {\n return fontFamilies;\n}\n"]}

View File

@ -0,0 +1,77 @@
import { useTheme } from '@react-navigation/native';
import * as React from 'react';
import { ScreenStackHeaderBackButtonImage, ScreenStackHeaderCenterView, ScreenStackHeaderConfig, ScreenStackHeaderLeftView, ScreenStackHeaderRightView } from 'react-native-screens';
import { processFonts } from './FontProcessor';
export default function HeaderConfig({
backButtonImage,
backButtonInCustomView,
direction,
headerBackTitle,
headerBackTitleStyle = {},
headerBackTitleVisible = true,
headerCenter,
headerHideBackButton,
headerHideShadow,
headerLargeStyle = {},
headerLargeTitle,
headerLargeTitleHideShadow,
headerLargeTitleStyle = {},
headerLeft,
headerRight,
headerShown,
headerStyle = {},
headerTintColor,
headerTitle,
headerTitleStyle = {},
headerTopInsetEnabled = true,
headerTranslucent,
route,
statusBarAnimation,
statusBarHidden,
statusBarStyle,
title
}) {
const {
colors
} = useTheme();
const tintColor = headerTintColor !== null && headerTintColor !== void 0 ? headerTintColor : colors.primary;
const [backTitleFontFamily, largeTitleFontFamily, titleFontFamily] = processFonts([headerBackTitleStyle.fontFamily, headerLargeTitleStyle.fontFamily, headerTitleStyle.fontFamily]);
return /*#__PURE__*/React.createElement(ScreenStackHeaderConfig, {
backButtonInCustomView: backButtonInCustomView,
backgroundColor: headerStyle.backgroundColor ? headerStyle.backgroundColor : colors.card,
backTitle: headerBackTitleVisible ? headerBackTitle : ' ',
backTitleFontFamily: backTitleFontFamily,
backTitleFontSize: headerBackTitleStyle.fontSize,
blurEffect: headerStyle.blurEffect,
color: tintColor,
direction: direction,
hidden: headerShown === false,
hideBackButton: headerHideBackButton,
hideShadow: headerHideShadow,
largeTitle: headerLargeTitle,
largeTitleBackgroundColor: headerLargeStyle.backgroundColor,
largeTitleColor: headerLargeTitleStyle.color,
largeTitleFontFamily: largeTitleFontFamily,
largeTitleFontSize: headerLargeTitleStyle.fontSize,
largeTitleHideShadow: headerLargeTitleHideShadow,
statusBarAnimation: statusBarAnimation,
statusBarHidden: statusBarHidden,
statusBarStyle: statusBarStyle,
title: headerTitle !== undefined ? headerTitle : title !== undefined ? title : route.name,
titleColor: headerTitleStyle.color !== undefined ? headerTitleStyle.color : headerTintColor !== undefined ? headerTintColor : colors.text,
titleFontFamily: titleFontFamily,
titleFontSize: headerTitleStyle.fontSize,
topInsetEnabled: headerTopInsetEnabled,
translucent: headerTranslucent === true
}, headerRight !== undefined ? /*#__PURE__*/React.createElement(ScreenStackHeaderRightView, null, headerRight({
tintColor
})) : null, backButtonImage !== undefined ? /*#__PURE__*/React.createElement(ScreenStackHeaderBackButtonImage, {
key: "backImage",
source: backButtonImage
}) : null, headerLeft !== undefined ? /*#__PURE__*/React.createElement(ScreenStackHeaderLeftView, null, headerLeft({
tintColor
})) : null, headerCenter !== undefined ? /*#__PURE__*/React.createElement(ScreenStackHeaderCenterView, null, headerCenter({
tintColor
})) : null);
}
//# sourceMappingURL=HeaderConfig.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,132 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import { StackActions, useTheme } from '@react-navigation/native';
import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native'; // @ts-ignore Getting private component
import AppContainer from 'react-native/Libraries/ReactNative/AppContainer';
import { Screen as ScreenComponent, ScreenStack } from 'react-native-screens';
import HeaderConfig from './HeaderConfig';
const Screen = ScreenComponent;
const isAndroid = Platform.OS === 'android';
let Container = View;
if (__DEV__) {
const DebugContainer = props => {
const {
stackAnimation,
...rest
} = props;
if (Platform.OS === 'ios' && stackAnimation !== 'push') {
return /*#__PURE__*/React.createElement(AppContainer, null, /*#__PURE__*/React.createElement(View, rest));
}
return /*#__PURE__*/React.createElement(View, rest);
}; // @ts-ignore Wrong props
Container = DebugContainer;
}
export default function NativeStackView({
state,
navigation,
descriptors
}) {
const {
key,
routes
} = state;
const {
colors
} = useTheme();
return /*#__PURE__*/React.createElement(ScreenStack, {
style: styles.container
}, routes.map(route => {
const {
options,
render: renderScene
} = descriptors[route.key];
const {
gestureEnabled,
replaceAnimation = 'pop',
stackPresentation = 'push',
stackAnimation,
contentStyle
} = options;
const viewStyles = [styles.container, stackPresentation !== 'transparentModal' && {
backgroundColor: colors.background
}, contentStyle];
return /*#__PURE__*/React.createElement(Screen, {
key: route.key,
style: StyleSheet.absoluteFill,
gestureEnabled: isAndroid ? false : gestureEnabled,
replaceAnimation: replaceAnimation,
stackPresentation: stackPresentation,
stackAnimation: stackAnimation,
onWillAppear: () => {
navigation.emit({
type: 'transitionStart',
data: {
closing: false
},
target: route.key
});
},
onWillDisappear: () => {
navigation.emit({
type: 'transitionStart',
data: {
closing: true
},
target: route.key
});
},
onAppear: () => {
navigation.emit({
type: 'appear',
target: route.key
});
navigation.emit({
type: 'transitionEnd',
data: {
closing: false
},
target: route.key
});
},
onDisappear: () => {
navigation.emit({
type: 'transitionEnd',
data: {
closing: true
},
target: route.key
});
},
onDismissed: () => {
navigation.emit({
type: 'dismiss',
target: route.key
});
navigation.dispatch({ ...StackActions.pop(),
source: route.key,
target: key
});
}
}, /*#__PURE__*/React.createElement(HeaderConfig, _extends({}, options, {
route: route
})), /*#__PURE__*/React.createElement(Container, {
style: viewStyles // @ts-ignore Wrong props passed to View
,
stackPresentation: stackPresentation
}, renderScene()));
}));
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
//# sourceMappingURL=NativeStackView.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
/**
* Navigators
*/
export { default as createNativeStackNavigator } from './navigators/createNativeStackNavigator';
/**
* Views
*/
export { default as NativeStackView } from './views/NativeStackView';
/**
* Types
*/
export type { NativeStackNavigationOptions, NativeStackNavigationProp, } from './types';

View File

@ -0,0 +1,5 @@
import { StackNavigationState } from '@react-navigation/native';
import { NativeStackNavigationEventMap, NativeStackNavigationOptions, NativeStackNavigatorProps } from '../types';
declare function NativeStackNavigator({ initialRouteName, children, screenOptions, ...rest }: NativeStackNavigatorProps): JSX.Element;
declare const _default: <ParamList extends Record<string, object | undefined>>() => import("@react-navigation/native").TypedNavigator<ParamList, StackNavigationState<Record<string, object | undefined>>, NativeStackNavigationOptions, NativeStackNavigationEventMap, typeof NativeStackNavigator>;
export default _default;

View File

@ -0,0 +1,254 @@
import { DefaultNavigatorOptions, Descriptor, NavigationHelpers, NavigationProp, ParamListBase, StackNavigationState, StackRouterOptions, StackActionHelpers } from '@react-navigation/native';
import * as React from 'react';
import { ImageSourcePropType, StyleProp, ViewStyle } from 'react-native';
import { ScreenProps, ScreenStackHeaderConfigProps } from 'react-native-screens';
export declare type NativeStackNavigationEventMap = {
/**
* Event which fires when the screen appears.
*/
appear: {
data: undefined;
};
/**
* Event which fires when the current screen is dismissed by hardware back (on Android) or dismiss gesture (swipe back or down).
*/
dismiss: {
data: undefined;
};
/**
* Event which fires when a transition animation starts.
*/
transitionStart: {
data: {
closing: boolean;
};
};
/**
* Event which fires when a transition animation ends.
*/
transitionEnd: {
data: {
closing: boolean;
};
};
};
export declare type NativeStackNavigationProp<ParamList extends ParamListBase, RouteName extends keyof ParamList = string> = NavigationProp<ParamList, RouteName, StackNavigationState<ParamList>, NativeStackNavigationOptions, NativeStackNavigationEventMap> & StackActionHelpers<ParamList>;
export declare type NativeStackNavigationHelpers = NavigationHelpers<ParamListBase, NativeStackNavigationEventMap>;
export declare type NativeStackNavigationConfig = Record<string, unknown>;
export declare type NativeStackNavigationOptions = {
/**
* Image to display in the header as the back button.
* Defaults to back icon image for the platform (a chevron on iOS and an arrow on Android).
*/
backButtonImage?: ImageSourcePropType;
/**
* Whether to show the back button with custom left side of the header.
*/
backButtonInCustomView?: boolean;
/**
* Style object for the scene content.
*/
contentStyle?: StyleProp<ViewStyle>;
/**
* Whether the stack should be in rtl or ltr form.
*/
direction?: 'rtl' | 'ltr';
/**
* Whether you can use gestures to dismiss this screen. Defaults to `true`.
* Only supported on iOS.
*
* @platform ios
*/
gestureEnabled?: boolean;
/**
* Title to display in the back button.
* Only supported on iOS.
*
* @platform ios
*/
headerBackTitle?: string;
/**
* Style object for header back title. Supported properties:
* - fontFamily
* - fontSize
*
* Only supported on iOS.
*
* @platform ios
*/
headerBackTitleStyle?: {
fontFamily?: string;
fontSize?: number;
};
/**
* Whether the back button title should be visible or not. Defaults to `true`.
* Only supported on iOS.
*
* @platform ios
*/
headerBackTitleVisible?: boolean;
/**
* Function which returns a React Element to display in the center of the header.
*/
headerCenter?: (props: {
tintColor?: string;
}) => React.ReactNode;
/**
* Boolean indicating whether to hide the back button in header.
* Only supported on Android.
*
* @platform android
*/
headerHideBackButton?: boolean;
/**
* Boolean indicating whether to hide the elevation shadow or the bottom border on the header.
*/
headerHideShadow?: boolean;
/**
* Controls the style of the navigation header when the edge of any scrollable content reaches the matching edge of the navigation bar. Supported properties:
* - backgroundColor
*
* @platform ios
*/
headerLargeStyle?: {
backgroundColor?: string;
};
/**
* Boolean to set native property to prefer large title header (like in iOS setting).
* For large title to collapse on scroll, the content of the screen should be wrapped in a scrollable view such as `ScrollView` or `FlatList`.
* If the scrollable area doesn't fill the screen, the large title won't collapse on scroll.
* Only supported on iOS.
*
* @platform ios
*/
headerLargeTitle?: boolean;
/**
* Boolean that allows for disabling drop shadow under navigation header when the edge of any scrollable content reaches the matching edge of the navigation bar.
*/
headerLargeTitleHideShadow?: boolean;
/**
* Style object for header large title. Supported properties:
* - fontFamily
* - fontSize
*
* Only supported on iOS.
*
* @platform ios
*/
headerLargeTitleStyle?: {
fontFamily?: string;
fontSize?: number;
color?: string;
};
/**
* Function which returns a React Element to display on the left side of the header.
*/
headerLeft?: (props: {
tintColor?: string;
}) => React.ReactNode;
/**
* Function which returns a React Element to display on the right side of the header.
*/
headerRight?: (props: {
tintColor?: string;
}) => React.ReactNode;
/**
* Whether to show the header.
*/
headerShown?: boolean;
/**
* Style object for header title. Supported properties:
* - backgroundColor
* - blurEffect
*/
headerStyle?: {
backgroundColor?: string;
blurEffect?: ScreenStackHeaderConfigProps['blurEffect'];
};
/**
* Tint color for the header. Changes the color of back button and title.
*/
headerTintColor?: string;
/**
* String to display in the header as title. Defaults to scene `title`.
*/
headerTitle?: string;
/**
* Style object for header title. Supported properties:
* - fontFamily
* - fontSize
* - color
*/
headerTitleStyle?: {
fontFamily?: string;
fontSize?: number;
color?: string;
};
/**
* A flag to that lets you opt out of insetting the header. You may want to
* set this to `false` if you use an opaque status bar. Defaults to `true`.
* Only supported on Android. Insets are always applied on iOS because the
* header cannot be opaque.
*
* @platform android
*/
headerTopInsetEnabled?: boolean;
/**
* Boolean indicating whether the navigation bar is translucent.
*/
headerTranslucent?: boolean;
/**
* How should the screen replacing another screen animate. Defaults to `pop`.
* The following values are currently supported:
* - "push" the new screen will perform push animation.
* - "pop" the new screen will perform pop animation.
*/
replaceAnimation?: ScreenProps['replaceAnimation'];
/**
* How the screen should appear/disappear when pushed or popped at the top of the stack.
* The following 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
*/
stackAnimation?: ScreenProps['stackAnimation'];
/**
* How should the screen be presented.
* The following values are currently supported:
* - "push" the new screen will be pushed onto a stack which on iOS means that the default animation will be slide from the side, the animation on Android may vary depending on the OS version and theme.
* - "modal" the new screen will be presented modally. In addition this allow for a nested stack to be rendered inside such screens.
* - "transparentModal" the new screen will be presented modally but in addition the second to last screen will remain attached to the stack container such that if the top screen is non opaque the content below can still be seen. If "modal" is used instead the below screen will get unmounted as soon as the transition ends.
* - "containedModal" will use "UIModalPresentationCurrentContext" modal style on iOS and will fallback to "modal" on Android.
* - "containedTransparentModal" will use "UIModalPresentationOverCurrentContext" modal style on iOS and will fallback to "transparentModal" on Android.
* - "fullScreenModal" will use "UIModalPresentationFullScreen" modal style on iOS and will fallback to "modal" on Android.
* - "formSheet" will use "UIModalPresentationFormSheet" modal style on iOS and will fallback to "modal" on Android.
*/
stackPresentation?: ScreenProps['stackPresentation'];
/**
* Sets the status bar animation (similar to the `StatusBar` component). Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file.
*
* @platform ios
*/
statusBarAnimation?: ScreenStackHeaderConfigProps['statusBarAnimation'];
/**
* Whether the status bar should be hidden on this screen. Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file.
*
* @platform ios
*/
statusBarHidden?: boolean;
/** Sets the status bar color (similar to the `StatusBar` component). Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file.
*
* @platform ios
*/
statusBarStyle?: ScreenStackHeaderConfigProps['statusBarStyle'];
/**
* String that can be displayed in the header as a fallback for `headerTitle`.
*/
title?: string;
};
export declare type NativeStackNavigatorProps = DefaultNavigatorOptions<NativeStackNavigationOptions> & StackRouterOptions & NativeStackNavigationConfig;
export declare type NativeStackDescriptor = Descriptor<ParamListBase, string, StackNavigationState<ParamListBase>, NativeStackNavigationOptions>;
export declare type NativeStackDescriptorMap = {
[key: string]: NativeStackDescriptor;
};

View File

@ -0,0 +1 @@
export declare function processFonts(fontFamilies: (string | undefined)[]): (string | undefined)[];

View File

@ -0,0 +1 @@
export declare function processFonts(fontFamilies: (string | undefined)[]): (string | undefined)[];

View File

@ -0,0 +1,7 @@
import { Route } from '@react-navigation/native';
import { NativeStackNavigationOptions } from '../types';
declare type Props = NativeStackNavigationOptions & {
route: Route<string>;
};
export default function HeaderConfig({ backButtonImage, backButtonInCustomView, direction, headerBackTitle, headerBackTitleStyle, headerBackTitleVisible, headerCenter, headerHideBackButton, headerHideShadow, headerLargeStyle, headerLargeTitle, headerLargeTitleHideShadow, headerLargeTitleStyle, headerLeft, headerRight, headerShown, headerStyle, headerTintColor, headerTitle, headerTitleStyle, headerTopInsetEnabled, headerTranslucent, route, statusBarAnimation, statusBarHidden, statusBarStyle, title, }: Props): JSX.Element;
export {};

View File

@ -0,0 +1,9 @@
import { ParamListBase, StackNavigationState } from '@react-navigation/native';
import { NativeStackDescriptorMap, NativeStackNavigationHelpers } from '../types';
declare type Props = {
state: StackNavigationState<ParamListBase>;
navigation: NativeStackNavigationHelpers;
descriptors: NativeStackDescriptorMap;
};
export default function NativeStackView({ state, navigation, descriptors, }: Props): JSX.Element;
export {};

View File

@ -0,0 +1,393 @@
# Native Stack Navigator
Provides a way for your app to transition between screens where each new screen is placed on top of a stack.
By default the stack navigator is configured to have the familiar iOS and Android look & feel: new screens slide in from the right on iOS, fade in from the bottom on Android. On iOS, the stack navigator can also be configured to a modal style where screens slide in from the bottom.
This navigator uses native navigation primitives (`UINavigationController` on iOS and `Fragment` on Android) for navigation under the hood. The main difference from React Navigation's JS-based [stack navigator](https://reactnavigation.org/docs/stack-navigator.html) is that the JS-based navigator re-implements animations and gestures while the native stack navigator relies on the platform primitives for animations and gestures. You should use this navigator if you want native feeling and performance for navigation and don't need much customization, as the customization options of this navigator are limited.
```sh
npm install react-native-screens @react-navigation/native
```
Make sure to enable `react-native-screens`. This needs to be done before our app renders. To do it, add the following code in your entry file (e.g. `App.js`):
```js
import { enableScreens } from 'react-native-screens';
enableScreens();
```
## API Definition
To use this navigator, import it from `react-native-screens/native-stack`:
```js
import { createNativeStackNavigator } from 'react-native-screens/native-stack';
const Stack = createNativeStackNavigator();
function MyStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Notifications" component={Notifications} />
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
);
}
```
### Props
The `Stack.Navigator` component accepts following props:
#### `initialRouteName`
The name of the route to render on the first load of the navigator.
#### `screenOptions`
Default options to use for the screens in the navigator.
### Options
The `options` prop can be used to configure individual screens inside the navigator. Supported options are:
#### `backButtonInCustomView`
Boolean indicating whether to hide the back button while using `headerLeft` function.
#### `contentStyle`
Style object for the scene content.
#### `direction`
String that applies `rtl` or `ltr` form to the stack. On Android, you have to add `android:supportsRtl="true"` in the manifest of your app to enable `rtl`. On Android, if you set the above flag in the manifest, the orientation changes without the need to do it programmatically if the phone has `rtl` direction enabled. On iOS, the direction defaults to `ltr`, and only way to change it is via this prop.
#### `gestureEnabled`
Whether you can use gestures to dismiss this screen. Defaults to `true`,
Gestures are only supported on iOS. They can be disabled only when `stackPresentation` is `push`.
#### `headerBackTitle`
Title string used by the back button on iOS. Defaults to the previous scene's `headerTitle`.
#### `headerBackTitleStyle`
Style object for header back title. Supported properties:
- `fontFamily`
- `fontSize`
#### `headerBackTitleVisible`
Whether the back button title should be visible or not. Defaults to `true`. Only supported on iOS.
#### `headerCenter`
Function which returns a React Element to display in the center of the header.
#### `headerHideBackButton`
Boolean indicating whether to hide the back button in the header. Only supported on Android.
#### `headerHideShadow`
Boolean indicating whether to hide the elevation shadow on the header.
#### `headerLargeTitle`
Boolean used to set a native property to prefer a large title header (like in iOS setting).
For the large title to collapse on scroll, the content of the screen should be wrapped in a scrollable view such as `ScrollView` or `FlatList`. If the scrollable area doesn't fill the screen, the large title won't collapse on scroll.
Only supported on iOS.
#### `headerLeft`
Function which returns a React Element to display on the left side of the header. For now, on Android, using it will cause the title to also disappear.
#### `headerRight`
Function which returns a React Element to display on the right side of the header.
#### `headerShown`
Whether to show or hide the header for the screen. The header is shown by default. Setting this to `false` hides the header.
#### `headerStyle`
Style object for the header. Supported properties:
- `backgroundColor`
- `blurEffect` (iOS only). Possible values can be checked in `index.d.ts` file.
#### `headerTintColor`
Tint color for the header. Changes the color of the back button and title.
#### `headerTitle`
String to be used by the header as title string. Defaults to scene `title`.
#### `headerTitleStyle`
Style object for header title. Supported properties:
- `fontFamily`
- `fontSize`
- `color`
#### `headerTopInsetEnabled`
A Boolean to that lets you opt out of insetting the header. You may want to * set this to `false` if you use an opaque status bar. Defaults to `true`. Insets are always applied on iOS because the header cannot be opaque. Only supported on Android.
#### `headerTranslucent`
Boolean indicating whether the navigation bar is translucent.
#### `stackAnimation`
How the given screen should appear/disappear when pushed or popped at the top of the stack. Possible values:
- `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/disappears without an animation.
Defaults to `default`.
#### `stackPresentation`
How the screen should be presented. Possible values:
- `push` - The new screen will be pushed onto a stack. The default animation on iOS is to slide from the side. The animation on Android may vary depending on the OS version and theme.
- `modal` - The new screen will be presented modally. In addition, this allows for a nested stack to be rendered inside such screens.
- `transparentModal` - The new screen will be presented modally. In addition, the second to last screen will remain attached to the stack container such that if the top screen is translucent, the content below can still be seen. If `"modal"` is used instead, the below screen gets removed as soon as the transition ends.
- `containedModal` will use "UIModalPresentationCurrentContext" modal style on iOS and will fallback to `"modal"` on Android.
- `containedTransparentModal` will use "UIModalPresentationOverCurrentContext" modal style on iOS and will fallback to `"transparentModal"` on Android.
- `fullScreenModal` will use "UIModalPresentationFullScreen" modal style on iOS and will fallback to `"modal"` on Android.
- `formSheet` will use "UIModalPresentationFormSheet" modal style on iOS and will fallback to `"modal"` on Android.
Defaults to `push`.
#### `title`
A string that can be used as a fallback for `headerTitle`.
### Status bar managment
With `native-stack`, the status bar can be managed by `UIViewController` on iOS. It requires:
1. Enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file (it disables the option to use React Native's `StatusBar` component).
2. Adding `#import <RNScreens/UIViewController+RNScreens.h>` in your project's `AppDelegate.m` (you can see this change applied in the `AppDelegate.m` of `Example` project).
#### `statusBarStyle`
Sets the status bar color (similar to the `StatusBar` component). Possible values: `auto` (based on [user interface style](https://developer.apple.com/documentation/uikit/uiuserinterfacestyle?language=objc), `inverted` (colors opposite to `auto`), `light`, `dark`.
Defaults to `auto`.
#### `statusBarAnimation`
Sets the status bar animation (similar to the `StatusBar` component). Possible values: `fade`, `none`, `slide`.
Defaults to `fade`.
#### `statusBarHidden`
Boolean saying if the status bar for this screen is hidden.
Defaults to `false`.
### Events
The navigator can [emit events](https://reactnavigation.org/docs/navigation-events) on certain actions. Supported events are:
#### `appear`
Event which fires when the screen appears.
Example:
```js
React.useEffect(
() => {
const unsubscribe = navigation.addListener('appear', e => {
// Do something
});
return unsubscribe;
},
[navigation]
);
```
#### `dismiss`
Event which fires when the current screen is dismissed by hardware back (on Android) or dismiss gesture (swipe back or down).
Example:
```js
React.useEffect(
() => {
const unsubscribe = navigation.addListener('dismiss', e => {
// Do something
});
return unsubscribe;
},
[navigation]
);
```
#### `transitionStart`
Event which fires when a transition animation starts.
Event data:
- `closing` - Whether the screen will be dismissed or will appear.
Example:
```js
React.useEffect(
() => {
const unsubscribe = navigation.addListener('transitionStart', e => {
if (e.data.closing) {
// Will be dismissed
} else {
// Will appear
}
});
return unsubscribe;
},
[navigation]
);
```
#### `transitionEnd`
Event which fires when a transition animation ends.
Event data:
- `closing` - Whether the screen was dismissed or did appear.
Example:
```js
React.useEffect(
() => {
const unsubscribe = navigation.addListener('transitionEnd', e => {
if (e.data.closing) {
// Was dismissed
} else {
// Did appear
}
});
return unsubscribe;
},
[navigation]
);
```
### Helpers
The stack navigator adds the following methods to the navigation prop:
#### `push`
Pushes a new screen to the top of the stack and navigate to it. The method accepts the following arguments:
- `name` - _string_ - Name of the route to push onto the stack.
- `params` - _object_ - Screen params to merge into the destination route (found in the pushed screen through `route.params`).
```js
navigation.push('Profile', { name: 'Michaś' });
```
#### `pop`
Pops the current screen from the stack and navigates back to the previous screen. It takes one optional argument (`count`), which allows you to specify how many screens to pop back by.
```js
navigation.pop();
```
#### `popToTop`
Pops all of the screens in the stack except the first one and navigates to it.
```js
navigation.popToTop();
```
## Additional options
### Measuring header's height on iOS
Using translucent header on iOS can result in the need of measuring your header's height. In order to do it, you can use `react-native-safe-area-context`. It can be measured like this:
```js
import { useSafeAreaInsets } from 'react-native-safe-area-context';
...
const statusBarInset = useSafeAreaInsets().top; // inset of the status bar
const smallHeaderInset = statusBarInset + 44; // inset to use for a small header since it's frame is equal to 44 + the frame of status bar
const largeHeaderInset = statusBarInset + 96; // inset to use for a large header since it's frame is equal to 96 + the frame of status bar
```
You can also see an example of using these values with a `ScrollView` here: https://snack.expo.io/@wolewicki/ios-header-height.
## Example
```js
import { createNativeStackNavigator } from 'react-native-screens/native-stack';
const Stack = createNativeStackNavigator();
function MyStack() {
return (
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
headerTintColor: 'white',
headerStyle: { backgroundColor: 'tomato' },
}}>
<Stack.Screen
name="Home"
component={Home}
options={{
title: 'Awesome app',
}}
/>
<Stack.Screen
name="Profile"
component={Profile}
options={{
title: 'My profile',
}}
/>
<Stack.Screen
name="Settings"
component={Settings}
options={{
gestureEnabled: false,
}}
/>
</Stack.Navigator>
);
}
```

View File

@ -0,0 +1,6 @@
{
"main": "../lib/commonjs/native-stack/index",
"module": "../lib/module/native-stack/index",
"react-native": "../src/native-stack/index",
"types": "../lib/typescript/index.d.ts"
}

109
node_modules/react-native-screens/package.json generated vendored Normal file
View File

@ -0,0 +1,109 @@
{
"name": "react-native-screens",
"version": "2.15.2",
"description": "Native navigation primitives for your React Native app.",
"scripts": {
"check-types": "tsc --noEmit",
"start": "react-native start",
"test": "yarn format && yarn lint && yarn test:unit",
"test:unit": "jest --passWithNoTests",
"format": "prettier --write --list-different './src/**/*.{js,ts,tsx}'",
"lint": "eslint --ext '.js,.ts,.tsx' --fix src && npm run check-types",
"precommit": "yarn test",
"release": "npm login && release-it",
"prepare": "bob build"
},
"main": "lib/commonjs/index",
"module": "lib/module/index",
"react-native": "src/index",
"source": "src/index",
"types": "src/index.d.ts",
"files": [
"src/",
"lib/",
"native-stack/",
"createNativeStackNavigator/",
"android/src/main/AndroidManifest.xml",
"android/src/main/java/",
"android/build.gradle",
"ios/",
"RNScreens.podspec",
"README.md",
"!**/__tests__"
],
"repository": {
"type": "git",
"url": "git+https://github.com/kmagiera/react-native-screens.git"
},
"author": {
"email": "krzys.magiera@gmail.com",
"name": "Krzysztof Magiera"
},
"license": "MIT",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/kmagiera/react-native-screens/issues"
},
"homepage": "https://github.com/kmagiera/react-native-screens#readme",
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"devDependencies": {
"@babel/core": "^7.11.0",
"@react-native-community/bob": "^0.15.1",
"@react-navigation/native": "^5.8.0",
"@react-navigation/stack": "^5.10.0",
"@types/jest": "^26.0.8",
"@types/react": "^16.9.44",
"@types/react-native": "^0.63.2",
"@types/react-test-renderer": "^16.9.2",
"@typescript-eslint/eslint-plugin": "^3.7.1",
"@typescript-eslint/parser": "^3.7.1",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.2.2",
"eslint": "^7.6.0",
"eslint-config-prettier": "^6.11.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^23.20.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.20.5",
"eslint-plugin-react-native": "^3.2.1",
"eslint-plugin-standard": "^4.0.1",
"husky": "^0.14.3",
"jest": "^26.2.2",
"jest-react-native": "18.0.0",
"lint-staged": "^7.1.3",
"metro-react-native-babel-preset": "^0.61.0",
"prettier": "^2.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-native": "^0.63.2",
"react-navigation": "^4.4.3",
"react-navigation-stack": "^2.9.0",
"react-test-renderer": "^16.13.1",
"release-it": "^13.5.2",
"typescript": "^3.9.7"
},
"lint-staged": {
"*.{js,ts,tsx}": [
"prettier --write",
"git add"
]
},
"@react-native-community/bob": {
"source": "src",
"output": "lib",
"targets": [
"commonjs",
"module",
"typescript"
]
},
"eslintIgnore": [
"node_modules/",
"lib/"
]
}

View File

@ -0,0 +1,320 @@
import React from 'react';
import { Platform, StyleSheet } from 'react-native';
import {
Screen,
ScreenStack,
ScreenStackHeaderBackButtonImage,
ScreenStackHeaderCenterView,
ScreenStackHeaderConfig,
ScreenStackHeaderLeftView,
ScreenStackHeaderRightView,
} from 'react-native-screens';
import {
createNavigator,
SceneView,
StackActions,
StackRouter,
} from 'react-navigation';
import { HeaderBackButton } from 'react-navigation-stack';
function renderComponentOrThunk(componentOrThunk, props) {
if (typeof componentOrThunk === 'function') {
return componentOrThunk(props);
}
return componentOrThunk;
}
const REMOVE_ACTION = 'NativeStackNavigator/REMOVE';
class StackView extends React.Component {
_removeScene = (route) => {
this.props.navigation.dispatch({
type: REMOVE_ACTION,
immediate: true,
key: route.key,
});
};
_onAppear = (route, descriptor) => {
descriptor.options &&
descriptor.options.onAppear &&
descriptor.options.onAppear();
this.props.navigation.dispatch(
StackActions.completeTransition({
toChildKey: route.key,
key: this.props.navigation.state.key,
})
);
};
_onFinishTransitioning = () => {
const { routes } = this.props.navigation.state;
const lastRoute = routes?.length && routes[routes.length - 1];
if (lastRoute) {
this.props.navigation.dispatch(
StackActions.completeTransition({
toChildKey: lastRoute.key,
key: this.props.navigation.state.key,
})
);
}
};
_renderHeaderConfig = (index, route, descriptor) => {
const { navigationConfig } = this.props;
const { options } = descriptor;
const { headerMode } = navigationConfig;
const {
backButtonInCustomView,
direction,
headerBackTitle,
headerBackTitleStyle,
headerBackTitleVisible,
headerHideBackButton,
headerLargeTitleStyle,
headerStyle,
headerTintColor,
headerTitleStyle,
headerTopInsetEnabled = true,
hideShadow,
largeTitle,
statusBarAnimation,
statusBarHidden,
statusBarStyle,
title,
translucent,
} = options;
const scene = {
index,
key: route.key,
route,
descriptor,
};
const headerOptions = {
backButtonInCustomView,
backTitle: headerBackTitleVisible === false ? '' : headerBackTitle,
backTitleFontFamily:
headerBackTitleStyle && headerBackTitleStyle.fontFamily,
backTitleFontSize: headerBackTitleStyle && headerBackTitleStyle.fontSize,
color: headerTintColor,
direction,
topInsetEnabled: headerTopInsetEnabled,
hideBackButton: headerHideBackButton,
hideShadow,
largeTitle,
largeTitleBackgroundColor:
headerLargeTitleStyle && headerLargeTitleStyle.backgroundColor,
largeTitleColor: headerLargeTitleStyle && headerLargeTitleStyle.color,
largeTitleFontFamily:
headerLargeTitleStyle && headerLargeTitleStyle.fontFamily,
largeTitleFontSize:
headerLargeTitleStyle && headerLargeTitleStyle.fontSize,
statusBarAnimation,
statusBarHidden,
statusBarStyle,
title,
titleColor:
(headerTitleStyle && headerTitleStyle.color) || headerTintColor,
titleFontFamily: headerTitleStyle && headerTitleStyle.fontFamily,
titleFontSize: headerTitleStyle && headerTitleStyle.fontSize,
translucent: translucent === undefined ? false : translucent,
};
const hasHeader = headerMode !== 'none' && options.header !== null;
if (!hasHeader) {
return <ScreenStackHeaderConfig {...headerOptions} hidden />;
}
if (headerStyle !== undefined) {
headerOptions.backgroundColor = headerStyle.backgroundColor;
headerOptions.blurEffect = headerStyle.blurEffect;
}
const children = [];
if (options.backButtonImage) {
children.push(
<ScreenStackHeaderBackButtonImage
key="backImage"
source={options.backButtonImage}
/>
);
}
if (options.headerLeft !== undefined) {
children.push(
<ScreenStackHeaderLeftView key="left">
{renderComponentOrThunk(options.headerLeft, { scene })}
</ScreenStackHeaderLeftView>
);
} else if (options.headerBackImage !== undefined) {
const goBack = () => {
// Go back on next tick because button ripple effect needs to happen on Android
requestAnimationFrame(() => {
descriptor.navigation.goBack(descriptor.key);
});
};
children.push(
<ScreenStackHeaderLeftView key="left">
<HeaderBackButton
onPress={goBack}
pressColorAndroid={options.headerPressColorAndroid}
tintColor={options.headerTintColor}
backImage={options.headerBackImage}
title={options.backButtonTitle}
truncatedTitle={options.truncatedBackButtonTitle}
backTitleVisible={this.props.backTitleVisible}
titleStyle={options.headerBackTitleStyle}
layoutPreset={this.props.layoutPreset}
scene={scene}
/>
</ScreenStackHeaderLeftView>
);
}
if (options.headerTitle) {
if (title === undefined && typeof options.headerTitle === 'string') {
headerOptions.title = options.headerTitle;
} else {
children.push(
<ScreenStackHeaderCenterView key="center">
{renderComponentOrThunk(options.headerTitle, { scene })}
</ScreenStackHeaderCenterView>
);
}
}
if (options.headerRight) {
children.push(
<ScreenStackHeaderRightView key="right">
{renderComponentOrThunk(options.headerRight, { scene })}
</ScreenStackHeaderRightView>
);
}
if (children.length > 0) {
headerOptions.children = children;
}
return <ScreenStackHeaderConfig {...headerOptions} />;
};
_renderScene = (index, route, descriptor) => {
const { navigation, getComponent, options } = descriptor;
const { mode, transparentCard } = this.props.navigationConfig;
const SceneComponent = getComponent();
let stackPresentation = 'push';
if (mode === 'modal' || mode === 'containedModal') {
stackPresentation = mode;
if (transparentCard || options.cardTransparent) {
stackPresentation =
mode === 'containedModal'
? 'containedTransparentModal'
: 'transparentModal';
}
}
let stackAnimation = options.stackAnimation;
if (options.animationEnabled === false) {
stackAnimation = 'none';
}
const { screenProps } = this.props;
return (
<Screen
key={`screen_${route.key}`}
style={[StyleSheet.absoluteFill, options.cardStyle]}
stackAnimation={stackAnimation}
stackPresentation={stackPresentation}
replaceAnimation={
options.replaceAnimation === undefined
? 'pop'
: options.replaceAnimation
}
pointerEvents={
index === this.props.navigation.state.routes.length - 1
? 'auto'
: 'none'
}
gestureEnabled={
Platform.OS === 'android'
? false
: options.gestureEnabled === undefined
? true
: options.gestureEnabled
}
onAppear={() => this._onAppear(route, descriptor)}
onDismissed={() => this._removeScene(route)}>
{this._renderHeaderConfig(index, route, descriptor)}
<SceneView
screenProps={screenProps}
navigation={navigation}
component={SceneComponent}
/>
</Screen>
);
};
render() {
const { navigation, descriptors } = this.props;
return (
<ScreenStack
style={styles.scenes}
onFinishTransitioning={this._onFinishTransitioning}>
{navigation.state.routes.map((route, i) =>
this._renderScene(i, route, descriptors[route.key])
)}
</ScreenStack>
);
}
}
const styles = StyleSheet.create({
scenes: { flex: 1 },
});
function createStackNavigator(routeConfigMap, stackConfig = {}) {
const router = StackRouter(routeConfigMap, stackConfig);
// belowe we override getStateForAction method in order to add handling for
// a custom native stack navigation action. The action REMOVE that we want to
// add works in a similar way to POP, but it does not remove all the routes
// that sit on top of the removed route. For example if we have three routes
// [a,b,c] and call POP on b, then both b and c will go away. In case we
// call REMOVE on b, only b will be removed from the stack and the resulting
// state will be [a, c]
const superGetStateForAction = router.getStateForAction;
router.getStateForAction = (action, state) => {
if (action.type === REMOVE_ACTION) {
const { key, immediate } = action;
let backRouteIndex = state.index;
if (key) {
const backRoute = state.routes.find((route) => route.key === key);
backRouteIndex = state.routes.indexOf(backRoute);
}
if (backRouteIndex > 0) {
const newRoutes = [...state.routes];
newRoutes.splice(backRouteIndex, 1);
return {
...state,
routes: newRoutes,
index: newRoutes.length - 1,
isTransitioning: immediate !== true,
};
}
}
return superGetStateForAction(action, state);
};
// Create a navigator with StackView as the view
return createNavigator(StackView, router, stackConfig);
}
export default createStackNavigator;

256
node_modules/react-native-screens/src/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,256 @@
// Project: https://github.com/kmagiera/react-native-screens
// TypeScript Version: 2.8
declare module 'react-native-screens' {
import { ComponentClass } from 'react';
import {
Animated,
ImageProps,
NativeSyntheticEvent,
NativeTouchEvent,
ViewProps,
} from 'react-native';
export function enableScreens(shouldEnableScreens?: boolean): void;
export function screensEnabled(): boolean;
export type StackPresentationTypes =
| 'push'
| 'modal'
| 'transparentModal'
| 'containedModal'
| 'containedTransparentModal'
| 'fullScreenModal'
| 'formSheet';
export type StackAnimationTypes = 'default' | 'fade' | 'flip' | 'none';
export type BlurEffectTypes =
| 'extraLight'
| 'light'
| 'dark'
| 'regular'
| 'prominent'
| 'systemUltraThinMaterial'
| 'systemThinMaterial'
| 'systemMaterial'
| 'systemThickMaterial'
| 'systemChromeMaterial'
| 'systemUltraThinMaterialLight'
| 'systemThinMaterialLight'
| 'systemMaterialLight'
| 'systemThickMaterialLight'
| 'systemChromeMaterialLight'
| 'systemUltraThinMaterialDark'
| 'systemThinMaterialDark'
| 'systemMaterialDark'
| 'systemThickMaterialDark'
| 'systemChromeMaterialDark';
export type ScreenReplaceTypes = 'push' | 'pop';
export interface ScreenProps extends ViewProps {
active?: 0 | 1 | Animated.AnimatedInterpolation;
activityState?: 0 | 1 | 2 | Animated.AnimatedInterpolation;
children?: React.ReactNode;
/**
* @description All children screens should have the same value of their "enabled" prop as their container.
*/
enabled?: boolean;
/**
* @description When set to false the back swipe gesture will be disabled when the parent Screen is on top of the stack. The default value is true.
*/
gestureEnabled?: boolean;
/**
* @description A callback that gets called when the current screen appears.
*/
onAppear?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void;
onComponentRef?: (view: unknown) => void;
/**
* @description A callback that gets called when the current screen disappears.
*/
onDisappear?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void;
/**
* @description A callback that gets called when the current screen is dismissed by hardware back (on Android) or dismiss gesture (swipe back or down). The callback takes no arguments.
*/
onDismissed?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void;
/**
* @description A callback that gets called when the current screen will appear. This is called as soon as the transition begins.
*/
onWillAppear?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void;
/**
* @description A callback that gets called when the current screen will disappear. This is called as soon as the transition begins.
*/
onWillDisappear?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void;
/**
* @description Allows for the customization of the type of animation to use when this screen replaces another screen at the top of the stack. The following values are currently supported:
* @type "push" performs push animation
* @type "pop" performs pop animation (default)
*/
replaceAnimation?: ScreenReplaceTypes;
/**
* @description Allows for the customization of how the given screen should appear/dissapear when pushed or popped at the top of the stack. The following values are currently supported:
* @type "default" uses a platform default animation
* @type "fade" fades screen in or out
* @type "flip" flips the screen, requires stackPresentation: "modal" (iOS only)
* @type "none" the screen appears/dissapears without an animation
*/
stackAnimation?: StackAnimationTypes;
/**
* @type "push" the new screen will be pushed onto a stack which on iOS means that the default animation will be slide from the side, the animation on Android may vary depending on the OS version and theme.
* @type "modal" the new screen will be presented modally. In addition this allow for a nested stack to be rendered inside such screens.
* @type "transparentModal" the new screen will be presented modally but in addition the second to last screen will remain attached to the stack container such that if the top screen is non opaque the content below can still be seen. If "modal" is used instead the below screen will get unmounted as soon as the transition ends.
* @type "containedModal" will use "UIModalPresentationCurrentContext" modal style on iOS and will fallback to "modal" on Android.
* @type "containedTransparentModal" will use "UIModalPresentationOverCurrentContext" modal style on iOS and will fallback to "transparentModal" on Android.
* @type "fullScreenModal" will use "UIModalPresentationFullScreen" modal style on iOS and will fallback to "modal" on Android.
* @type "formSheet" will use "UIModalPresentationFormSheet" modal style on iOS and will fallback to "modal" on Android.
*/
stackPresentation?: StackPresentationTypes;
}
export interface ScreenContainerProps extends ViewProps {
/**
* @description A prop that gives users an option to switch between using Screens for the navigator (container). All children screens should have the same value of their "enabled" prop as their container.
*/
enabled?: boolean;
}
export interface ScreenStackProps extends ViewProps {
/**
* @description A callback that gets called when the current screen finishes its transition.
*/
onFinishTransitioning?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void;
}
export interface ScreenStackHeaderConfigProps extends ViewProps {
/**
* @description Whether to show the back button with a custom left side of the header.
*/
backButtonInCustomView?: boolean;
/**
* @description Controls the color of the navigation header.
*/
backgroundColor?: string;
/**
* @host (iOS only)
* @description Allows for controlling the string to be rendered next to back button. By default iOS uses the title of the previous screen.
*/
backTitle?: string;
/**
* @host (iOS only)
* @description Allows for customizing font family to be used for back button title on iOS.
*/
backTitleFontFamily?: string;
/**
* @host (iOS only)
* @description Allows for customizing font size to be used for back button title on iOS.
*/
backTitleFontSize?: number;
/**
* @host (iOS only)
* @description Blur effect to be applied to the header. Works with backgroundColor's alpha < 1.
*/
blurEffect?: BlurEffectTypes;
/**
* Pass HeaderLeft, HeaderRight and HeaderTitle
*/
children?: React.ReactNode;
/**
*@description Controls whether the stack should be in rtl or ltr form.
*/
direction?: 'rtl' | 'ltr';
/**
* @description When set to true the header will be hidden while the parent Screen is on the top of the stack. The default value is false.
*/
hidden?: boolean;
/**
* @description Controls the color of items rendered on the header. This includes back icon, back text (iOS only) and title text. If you want the title to have different color use titleColor property.
*/
color?: string;
/**
* @description If set to true the back button will not be rendered as a part of navigation header.
*/
hideBackButton?: boolean;
/**
* @description Boolean that allows for disabling drop shadow under navigation header. The default value is true.
*/
hideShadow?: boolean;
/**
* @host (iOS only)
* @description When set to true it makes the title display using the large title effect.
*/
largeTitle?: boolean;
/**
*@description Controls the color of the navigation header when the edge of any scrollable content reaches the matching edge of the navigation bar.
*/
largeTitleBackgroundColor?: string;
/**
* @host (iOS only)
* @description Customize the color to be used for the large title. By default uses the titleColor property.
*/
largeTitleColor?: string;
/**
* @host (iOS only)
* @description Customize font family to be used for the large title.
*/
largeTitleFontFamily?: string;
/**
* @host (iOS only)
* @description Customize the size of the font to be used for the large title.
*/
largeTitleFontSize?: number;
/**
* @description Boolean that allows for disabling drop shadow under navigation header when the edge of any scrollable content reaches the matching edge of the navigation bar.
*/
largeTitleHideShadow?: boolean;
/**
* @host (iOS only)
* @description Sets the status bar animation (similar to the `StatusBar` component). Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file. Defaults to `fade`.
*/
statusBarAnimation?: 'none' | 'fade' | 'slide';
/**
* @host (iOS only)
* @description When set to true, the status bar for this screen is hidden. Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file. Defaults to `false`.
*/
statusBarHidden?: boolean;
/**
* @host (iOS only)
* @description Sets the status bar color (similar to the `StatusBar` component). Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file. Defaults to `auto`.
*/
statusBarStyle?: 'inverted' | 'auto' | 'light' | 'dark';
/**
* @description String that representing screen title that will get rendered in the middle section of the header. On iOS the title is centered on the header while on Android it is aligned to the left and placed next to back button (if one is present).
*/
title?: string;
/**
* @description Allows for setting text color of the title.
*/
titleColor?: string;
/**
* @description Customize font family to be used for the title.
*/
titleFontFamily?: string;
/**
* @description Customize the size of the font to be used for the title.
*/
titleFontSize?: number;
/**
* @host (Android only)
* @description A flag to that lets you opt out of insetting the header. You may want to set this to `false` if you use an opaque status bar. Defaults to `true`.
*/
topInsetEnabled?: boolean;
/**
* @description When set to true, it makes native navigation bar on iOS semi transparent with blur effect. It is a common way of presenting navigation bar introduced in iOS 11. The default value is false
*/
translucent?: boolean;
}
export const Screen: ComponentClass<ScreenProps>;
export const ScreenContainer: ComponentClass<ScreenContainerProps>;
export const NativeScreen: ComponentClass<ScreenProps>;
export const NativeScreenContainer: ComponentClass<ScreenContainerProps>;
export const ScreenStack: ComponentClass<ScreenStackProps>;
export const ScreenStackHeaderBackButtonImage: ComponentClass<ImageProps>;
export const ScreenStackHeaderLeftView: ComponentClass<ViewProps>;
export const ScreenStackHeaderRightView: ComponentClass<ViewProps>;
export const ScreenStackHeaderCenterView: ComponentClass<ViewProps>;
export const ScreenStackHeaderConfig: ComponentClass<ScreenStackHeaderConfigProps>;
export const shouldUseActivityState: boolean;
}

34
node_modules/react-native-screens/src/index.js generated vendored Normal file
View File

@ -0,0 +1,34 @@
import React from 'react';
import { Animated, View } from 'react-native';
let ENABLE_SCREENS = true;
export function enableScreens(shouldEnableScreens = true) {
ENABLE_SCREENS = shouldEnableScreens;
}
export function screensEnabled() {
return ENABLE_SCREENS;
}
export class NativeScreen extends React.Component {
render() {
const { active, style, enabled = true, ...rest } = this.props;
return (
<View
style={[
style,
ENABLE_SCREENS && enabled && !active ? { display: 'none' } : null,
]}
{...rest}
/>
);
}
}
export const Screen = Animated.createAnimatedComponent(NativeScreen);
export const ScreenContainer = View;
export const NativeScreenContainer = View;

201
node_modules/react-native-screens/src/index.native.js generated vendored Normal file
View File

@ -0,0 +1,201 @@
import React from 'react';
import {
Animated,
Image,
requireNativeComponent,
StyleSheet,
UIManager,
View,
} from 'react-native';
let ENABLE_SCREENS = false;
function enableScreens(shouldEnableScreens = true) {
ENABLE_SCREENS = shouldEnableScreens;
if (ENABLE_SCREENS && !UIManager.getViewManagerConfig('RNSScreen')) {
console.error(
`Screen native module hasn't been linked. Please check the react-native-screens README for more details`
);
}
}
// const that tells if the library should use new implementation, will be undefined for older versions
const shouldUseActivityState = true;
function screensEnabled() {
return ENABLE_SCREENS;
}
// We initialize these lazily so that importing the module doesn't throw error when not linked
// This is necessary coz libraries such as React Navigation import the library where it may not be enabled
let NativeScreenValue;
let NativeScreenContainerValue;
let NativeScreenStack;
let NativeScreenStackHeaderConfig;
let NativeScreenStackHeaderSubview;
let AnimatedNativeScreen;
const ScreensNativeModules = {
get NativeScreen() {
NativeScreenValue =
NativeScreenValue || requireNativeComponent('RNSScreen', null);
return NativeScreenValue;
},
get NativeScreenContainer() {
NativeScreenContainerValue =
NativeScreenContainerValue ||
requireNativeComponent('RNSScreenContainer', null);
return NativeScreenContainerValue;
},
get NativeScreenStack() {
NativeScreenStack =
NativeScreenStack || requireNativeComponent('RNSScreenStack', null);
return NativeScreenStack;
},
get NativeScreenStackHeaderConfig() {
NativeScreenStackHeaderConfig =
NativeScreenStackHeaderConfig ||
requireNativeComponent('RNSScreenStackHeaderConfig', null);
return NativeScreenStackHeaderConfig;
},
get NativeScreenStackHeaderSubview() {
NativeScreenStackHeaderSubview =
NativeScreenStackHeaderSubview ||
requireNativeComponent('RNSScreenStackHeaderSubview', null);
return NativeScreenStackHeaderSubview;
},
};
class Screen extends React.Component {
setNativeProps(props) {
this._ref.setNativeProps(props);
}
setRef = (ref) => {
this._ref = ref;
this.props.onComponentRef && this.props.onComponentRef(ref);
};
render() {
const { enabled = true } = this.props;
if (!ENABLE_SCREENS || !enabled) {
// 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
/* eslint-disable no-unused-vars */
const { active, enabled, onComponentRef, ...rest } = this.props;
return <Animated.View {...rest} ref={this.setRef} />;
} else {
AnimatedNativeScreen =
AnimatedNativeScreen ||
Animated.createAnimatedComponent(ScreensNativeModules.NativeScreen);
let { enabled, active, activityState, ...rest } = this.props;
if (active !== undefined && activityState === undefined) {
console.warn(
'It appears that you are using old version of react-navigation library. Please update @react-navigation/bottom-tabs, @react-navigation/stack and @react-navigation/drawer to version 5.10.0 or above to take full advantage of new functionality added to react-native-screens'
);
activityState = active !== 0 ? 2 : 0; // in the new version, we need one of the screens to have value of 2 after the transition
}
return (
<AnimatedNativeScreen
{...rest}
activityState={activityState}
ref={this.setRef}
/>
);
}
}
}
class ScreenContainer extends React.Component {
render() {
const { enabled = true, ...rest } = this.props;
if (!ENABLE_SCREENS || !enabled) {
return <View {...rest} />;
} else {
return <ScreensNativeModules.NativeScreenContainer {...this.props} />;
}
}
}
const styles = StyleSheet.create({
headerSubview: {
position: 'absolute',
top: 0,
right: 0,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
});
const ScreenStackHeaderBackButtonImage = (props) => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
type="back"
style={styles.headerSubview}>
<Image resizeMode="center" fadeDuration={0} {...props} />
</ScreensNativeModules.NativeScreenStackHeaderSubview>
);
const ScreenStackHeaderRightView = (props) => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
{...props}
type="right"
style={styles.headerSubview}
/>
);
const ScreenStackHeaderLeftView = (props) => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
{...props}
type="left"
style={styles.headerSubview}
/>
);
const ScreenStackHeaderCenterView = (props) => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
{...props}
type="center"
style={styles.headerSubview}
/>
);
module.exports = {
ScreenContainer,
Screen,
get NativeScreen() {
return ScreensNativeModules.NativeScreen;
},
get NativeScreenContainer() {
return ScreensNativeModules.NativeScreenContainer;
},
get ScreenStack() {
return ScreensNativeModules.NativeScreenStack;
},
get ScreenStackHeaderConfig() {
return ScreensNativeModules.NativeScreenStackHeaderConfig;
},
get ScreenStackHeaderSubview() {
return ScreensNativeModules.NativeScreenStackHeaderSubview;
},
ScreenStackHeaderBackButtonImage,
ScreenStackHeaderRightView,
ScreenStackHeaderLeftView,
ScreenStackHeaderCenterView,
enableScreens,
screensEnabled,
shouldUseActivityState,
};

View File

@ -0,0 +1,17 @@
/**
* Navigators
*/
export { default as createNativeStackNavigator } from './navigators/createNativeStackNavigator';
/**
* Views
*/
export { default as NativeStackView } from './views/NativeStackView';
/**
* Types
*/
export type {
NativeStackNavigationOptions,
NativeStackNavigationProp,
} from './types';

View File

@ -0,0 +1,85 @@
import {
createNavigatorFactory,
EventArg,
StackActions,
StackActionHelpers,
StackNavigationState,
StackRouter,
StackRouterOptions,
ParamListBase,
useNavigationBuilder,
} from '@react-navigation/native';
import * as React from 'react';
import { screensEnabled } from 'react-native-screens';
import {
NativeStackNavigationEventMap,
NativeStackNavigationOptions,
NativeStackNavigatorProps,
} from '../types';
import NativeStackView from '../views/NativeStackView';
function NativeStackNavigator({
initialRouteName,
children,
screenOptions,
...rest
}: NativeStackNavigatorProps) {
if (!screensEnabled()) {
throw new Error(
'Native stack is only available if React Native Screens is enabled.'
);
}
const { state, descriptors, navigation } = useNavigationBuilder<
StackNavigationState<ParamListBase>,
StackRouterOptions,
StackActionHelpers<ParamListBase>,
NativeStackNavigationOptions,
NativeStackNavigationEventMap
>(StackRouter, {
initialRouteName,
children,
screenOptions,
});
React.useEffect(
() =>
navigation?.addListener?.('tabPress', (e) => {
const isFocused = navigation.isFocused();
// Run the operation in the next frame so we're sure all listeners have been run
// This is necessary to know if preventDefault() has been called
requestAnimationFrame(() => {
if (
state.index > 0 &&
isFocused &&
!(e as EventArg<'tabPress', true>).defaultPrevented
) {
// When user taps on already focused tab and we're inside the tab,
// reset the stack to replicate native behaviour
navigation.dispatch({
...StackActions.popToTop(),
target: state.key,
});
}
});
}),
[navigation, state.index, state.key]
);
return (
<NativeStackView
{...rest}
state={state}
navigation={navigation}
descriptors={descriptors}
/>
);
}
export default createNavigatorFactory<
StackNavigationState<ParamListBase>,
NativeStackNavigationOptions,
NativeStackNavigationEventMap,
typeof NativeStackNavigator
>(NativeStackNavigator);

View File

@ -0,0 +1,278 @@
import {
DefaultNavigatorOptions,
Descriptor,
NavigationHelpers,
NavigationProp,
ParamListBase,
StackNavigationState,
StackRouterOptions,
StackActionHelpers,
} from '@react-navigation/native';
import * as React from 'react';
import { ImageSourcePropType, StyleProp, ViewStyle } from 'react-native';
import {
ScreenProps,
ScreenStackHeaderConfigProps,
} from 'react-native-screens';
export type NativeStackNavigationEventMap = {
/**
* Event which fires when the screen appears.
*/
appear: { data: undefined };
/**
* Event which fires when the current screen is dismissed by hardware back (on Android) or dismiss gesture (swipe back or down).
*/
dismiss: { data: undefined };
/**
* Event which fires when a transition animation starts.
*/
transitionStart: { data: { closing: boolean } };
/**
* Event which fires when a transition animation ends.
*/
transitionEnd: { data: { closing: boolean } };
};
export type NativeStackNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = NavigationProp<
ParamList,
RouteName,
StackNavigationState<ParamList>,
NativeStackNavigationOptions,
NativeStackNavigationEventMap
> &
StackActionHelpers<ParamList>;
export type NativeStackNavigationHelpers = NavigationHelpers<
ParamListBase,
NativeStackNavigationEventMap
>;
export type NativeStackNavigationConfig = Record<string, unknown>;
export type NativeStackNavigationOptions = {
/**
* Image to display in the header as the back button.
* Defaults to back icon image for the platform (a chevron on iOS and an arrow on Android).
*/
backButtonImage?: ImageSourcePropType;
/**
* Whether to show the back button with custom left side of the header.
*/
backButtonInCustomView?: boolean;
/**
* Style object for the scene content.
*/
contentStyle?: StyleProp<ViewStyle>;
/**
* Whether the stack should be in rtl or ltr form.
*/
direction?: 'rtl' | 'ltr';
/**
* Whether you can use gestures to dismiss this screen. Defaults to `true`.
* Only supported on iOS.
*
* @platform ios
*/
gestureEnabled?: boolean;
/**
* Title to display in the back button.
* Only supported on iOS.
*
* @platform ios
*/
headerBackTitle?: string;
/**
* Style object for header back title. Supported properties:
* - fontFamily
* - fontSize
*
* Only supported on iOS.
*
* @platform ios
*/
headerBackTitleStyle?: {
fontFamily?: string;
fontSize?: number;
};
/**
* Whether the back button title should be visible or not. Defaults to `true`.
* Only supported on iOS.
*
* @platform ios
*/
headerBackTitleVisible?: boolean;
/**
* Function which returns a React Element to display in the center of the header.
*/
headerCenter?: (props: { tintColor?: string }) => React.ReactNode;
/**
* Boolean indicating whether to hide the back button in header.
* Only supported on Android.
*
* @platform android
*/
headerHideBackButton?: boolean;
/**
* Boolean indicating whether to hide the elevation shadow or the bottom border on the header.
*/
headerHideShadow?: boolean;
/**
* Controls the style of the navigation header when the edge of any scrollable content reaches the matching edge of the navigation bar. Supported properties:
* - backgroundColor
*
* @platform ios
*/
headerLargeStyle?: {
backgroundColor?: string;
};
/**
* Boolean to set native property to prefer large title header (like in iOS setting).
* For large title to collapse on scroll, the content of the screen should be wrapped in a scrollable view such as `ScrollView` or `FlatList`.
* If the scrollable area doesn't fill the screen, the large title won't collapse on scroll.
* Only supported on iOS.
*
* @platform ios
*/
headerLargeTitle?: boolean;
/**
* Boolean that allows for disabling drop shadow under navigation header when the edge of any scrollable content reaches the matching edge of the navigation bar.
*/
headerLargeTitleHideShadow?: boolean;
/**
* Style object for header large title. Supported properties:
* - fontFamily
* - fontSize
*
* Only supported on iOS.
*
* @platform ios
*/
headerLargeTitleStyle?: {
fontFamily?: string;
fontSize?: number;
color?: string;
};
/**
* Function which returns a React Element to display on the left side of the header.
*/
headerLeft?: (props: { tintColor?: string }) => React.ReactNode;
/**
* Function which returns a React Element to display on the right side of the header.
*/
headerRight?: (props: { tintColor?: string }) => React.ReactNode;
/**
* Whether to show the header.
*/
headerShown?: boolean;
/**
* Style object for header title. Supported properties:
* - backgroundColor
* - blurEffect
*/
headerStyle?: {
backgroundColor?: string;
blurEffect?: ScreenStackHeaderConfigProps['blurEffect'];
};
/**
* Tint color for the header. Changes the color of back button and title.
*/
headerTintColor?: string;
/**
* String to display in the header as title. Defaults to scene `title`.
*/
headerTitle?: string;
/**
* Style object for header title. Supported properties:
* - fontFamily
* - fontSize
* - color
*/
headerTitleStyle?: {
fontFamily?: string;
fontSize?: number;
color?: string;
};
/**
* A flag to that lets you opt out of insetting the header. You may want to
* set this to `false` if you use an opaque status bar. Defaults to `true`.
* Only supported on Android. Insets are always applied on iOS because the
* header cannot be opaque.
*
* @platform android
*/
headerTopInsetEnabled?: boolean;
/**
* Boolean indicating whether the navigation bar is translucent.
*/
headerTranslucent?: boolean;
/**
* How should the screen replacing another screen animate. Defaults to `pop`.
* The following values are currently supported:
* - "push" the new screen will perform push animation.
* - "pop" the new screen will perform pop animation.
*/
replaceAnimation?: ScreenProps['replaceAnimation'];
/**
* How the screen should appear/disappear when pushed or popped at the top of the stack.
* The following 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
*/
stackAnimation?: ScreenProps['stackAnimation'];
/**
* How should the screen be presented.
* The following values are currently supported:
* - "push" the new screen will be pushed onto a stack which on iOS means that the default animation will be slide from the side, the animation on Android may vary depending on the OS version and theme.
* - "modal" the new screen will be presented modally. In addition this allow for a nested stack to be rendered inside such screens.
* - "transparentModal" the new screen will be presented modally but in addition the second to last screen will remain attached to the stack container such that if the top screen is non opaque the content below can still be seen. If "modal" is used instead the below screen will get unmounted as soon as the transition ends.
* - "containedModal" will use "UIModalPresentationCurrentContext" modal style on iOS and will fallback to "modal" on Android.
* - "containedTransparentModal" will use "UIModalPresentationOverCurrentContext" modal style on iOS and will fallback to "transparentModal" on Android.
* - "fullScreenModal" will use "UIModalPresentationFullScreen" modal style on iOS and will fallback to "modal" on Android.
* - "formSheet" will use "UIModalPresentationFormSheet" modal style on iOS and will fallback to "modal" on Android.
*/
stackPresentation?: ScreenProps['stackPresentation'];
/**
* Sets the status bar animation (similar to the `StatusBar` component). Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file.
*
* @platform ios
*/
statusBarAnimation?: ScreenStackHeaderConfigProps['statusBarAnimation'];
/**
* Whether the status bar should be hidden on this screen. Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file.
*
* @platform ios
*/
statusBarHidden?: boolean;
/** Sets the status bar color (similar to the `StatusBar` component). Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file.
*
* @platform ios
*/
statusBarStyle?: ScreenStackHeaderConfigProps['statusBarStyle'];
/**
* String that can be displayed in the header as a fallback for `headerTitle`.
*/
title?: string;
};
export type NativeStackNavigatorProps = DefaultNavigatorOptions<
NativeStackNavigationOptions
> &
StackRouterOptions &
NativeStackNavigationConfig;
export type NativeStackDescriptor = Descriptor<
ParamListBase,
string,
StackNavigationState<ParamListBase>,
NativeStackNavigationOptions
>;
export type NativeStackDescriptorMap = {
[key: string]: NativeStackDescriptor;
};

View File

@ -0,0 +1,9 @@
// @ts-ignore this file extension is parsed only in managed workflow, so `expo-font` should be always available there
// eslint-disable-next-line import/no-unresolved
import { processFontFamily } from 'expo-font';
export function processFonts(
fontFamilies: (string | undefined)[]
): (string | undefined)[] {
return fontFamilies.map((fontFamily) => processFontFamily(fontFamily));
}

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