Compare commits

..

90 Commits

Author SHA1 Message Date
osdnk
25c3fc440f chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.44
 - @react-navigation/compat@5.0.0-alpha.33
 - @react-navigation/core@5.0.0-alpha.42
 - @react-navigation/drawer@5.0.0-alpha.46
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.41
 - @react-navigation/material-top-tabs@5.0.0-alpha.40
 - @react-navigation/native-stack@5.0.0-alpha.34
 - @react-navigation/native@5.0.0-alpha.34
 - @react-navigation/routers@5.0.0-alpha.32
 - @react-navigation/stack@5.0.0-alpha.69
2020-02-04 11:04:43 +01:00
Satyajit Sahoo
89fa363883 chore: don't run expo preview for contributors 2020-02-04 10:54:12 +01:00
Michał Osadnik
bec2f754d4 refactor: rename NavigationNativeContainer to NavigationContainer (#344) 2020-02-04 10:21:16 +01:00
Evan Bacon
b277927925 feat: disable pan gesture by default in the browser for Apple devices
fixes #287
2020-02-04 09:12:40 +01:00
osdnk
72993c6463 chore: delete .gitattributes 2020-02-03 14:53:11 +01:00
Satyajit Sahoo
3fbfb70699 chore: publish
- @react-navigation/stack@5.0.0-alpha.68
2020-02-03 05:48:24 +01:00
Satyajit Sahoo
abdf9d12b5 fix: use .native for masked view instead of .web 2020-02-03 05:47:25 +01:00
Satyajit Sahoo
ba85db28d4 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.43
 - @react-navigation/compat@5.0.0-alpha.32
 - @react-navigation/core@5.0.0-alpha.41
 - @react-navigation/drawer@5.0.0-alpha.45
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.40
 - @react-navigation/material-top-tabs@5.0.0-alpha.39
 - @react-navigation/native-stack@5.0.0-alpha.33
 - @react-navigation/native@5.0.0-alpha.33
 - @react-navigation/routers@5.0.0-alpha.31
 - @react-navigation/stack@5.0.0-alpha.67
2020-02-03 05:18:49 +01:00
Satyajit Sahoo
e5063b9339 fix: ignore circular references when checking serializable 2020-02-03 05:18:03 +01:00
Satyajit Sahoo
ec35bd5821 chore: add links to the currently published example app 2020-02-03 04:17:03 +01:00
Satyajit Sahoo
22e306004a chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.42
 - @react-navigation/compat@5.0.0-alpha.31
 - @react-navigation/core@5.0.0-alpha.40
 - @react-navigation/drawer@5.0.0-alpha.44
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.39
 - @react-navigation/material-top-tabs@5.0.0-alpha.38
 - @react-navigation/native-stack@5.0.0-alpha.32
 - @react-navigation/native@5.0.0-alpha.32
 - @react-navigation/routers@5.0.0-alpha.30
 - @react-navigation/stack@5.0.0-alpha.66
2020-02-03 00:40:22 +01:00
Satyajit Sahoo
8a835b3556 chore: fix versions in package.json 2020-02-03 00:40:11 +01:00
Satyajit Sahoo
d9237e9a0d chore: fix gesture handler version 2020-02-03 00:27:19 +01:00
Satyajit Sahoo
0644ac03aa chore: fix gesture handler version (#339) 2020-02-03 00:24:48 +01:00
Satyajit Sahoo
41a5e1a385 fix: wrap navigators in gesture handler root 2020-02-03 00:18:32 +01:00
Satyajit Sahoo
3d937d1e65 fix: dispatch pop early when screen is closed with gesture (#336)
fixes #267
2020-02-02 23:56:05 +01:00
osdnk
343320783f chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.40
 - @react-navigation/compat@5.0.0-alpha.29
 - @react-navigation/core@5.0.0-alpha.38
 - @react-navigation/drawer@5.0.0-alpha.42
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.37
 - @react-navigation/material-top-tabs@5.0.0-alpha.36
 - @react-navigation/native-stack@5.0.0-alpha.30
 - @react-navigation/native@5.0.0-alpha.30
 - @react-navigation/routers@5.0.0-alpha.28
 - @react-navigation/stack@5.0.0-alpha.64
2020-02-02 23:36:00 +01:00
Satyajit Sahoo
5411816188 fix: update screens for native stack 2020-02-02 23:32:12 +01:00
Satyajit Sahoo
d8bda604ee chore: fix QR code link for expo preview 2020-02-01 18:09:23 +01:00
Satyajit Sahoo
c70635b7d7 chore: tweak expo preview comment 2020-02-01 17:50:17 +01:00
Satyajit Sahoo
94d7b28c0b fix: disable screens when mode is modal on older expo versions 2020-02-01 17:21:18 +01:00
Satyajit Sahoo
b19b3b2725 chore: add workflow for automatic rebase 2020-02-01 02:43:58 +01:00
Satyajit Sahoo
3dcec142f7 chore: add .gitattributes 2020-02-01 02:43:58 +01:00
Satyajit Sahoo
0c159db4c9 fix: add licenses 2020-02-01 02:43:58 +01:00
Satyajit Sahoo
2479da98ed chore: tweak metro config 2020-02-01 02:43:58 +01:00
Satyajit Sahoo
5197ee2a9c chore: deploy pull requests to expo 2020-01-31 21:19:18 +01:00
Satyajit Sahoo
0ead2662ec fix: add accessibilityRole=header to header title 2020-01-31 17:27:47 +01:00
Satyajit Sahoo
5af5c29f07 chore: add action to publish expo app 2020-01-31 14:55:32 +01:00
Satyajit Sahoo
d448cdc11f refactor: don't pass drawerPosition as a prop to drawerContent 2020-01-31 14:55:10 +01:00
Satyajit Sahoo
0e8fda3196 fix: handle back button in drawer itself 2020-01-31 01:53:50 +01:00
Satyajit Sahoo
9198597b7f fix: fix drawerType=back when drawer is on right
fixes #316
2020-01-31 01:40:28 +01:00
osdnk
9be904d9c4 fix: increase epsilon in CardContainer.tsx 2020-01-30 22:38:13 +01:00
Satyajit Sahoo
fa4a959549 fix: add warning when passing inline function to component prop 2020-01-30 05:03:32 +01:00
Satyajit Sahoo
d0510d0220 chore: tweak the album art example 2020-01-30 05:03:31 +01:00
Satyajit Sahoo
0b4bf1dcc8 refactor: drop unmountInactiveScreens in favor of unmountOnBlur… (#317)
The `unmountInactiveScreens` prop lets user unmount all inactive screens for the whole navigator when they go out of focus. It'll be better to have the option to do that per screen, so I have added the `unmountOnBlur` option instead.

To get the previous behaviour, user can specify the option in `screenOptions`.
2020-01-29 23:49:31 +01:00
Satyajit Sahoo
5a3f8356b0 feat: integrate with history API on web 2020-01-29 23:14:12 +01:00
Satyajit Sahoo
eeae11033a refactor: track history for tabs and drawer in a history key 2020-01-29 23:14:12 +01:00
Wojciech Lewicki
b931ae62df feat: add screens prop for nested configs (#308)
Nested configs' names with their configs are now in `screens` property of the route object.
2020-01-29 16:26:17 +01:00
Satyajit Sahoo
ea66b1a3b8 chore: fix build failure 2020-01-29 12:13:37 +01:00
Michał Osadnik
4bc0c8f66f feat: add error if multiple instances of useLinking are used (#310) 2020-01-29 11:35:22 +01:00
Satyajit Sahoo
68016de385 chore: add example for transparent card 2020-01-28 21:25:27 +01:00
Michał Osadnik
e55e866af2 feat: add deeplinking to native example (#309) 2020-01-28 16:47:01 +01:00
osdnk
50b366e734 fix: rtl in native app example 2020-01-28 15:40:08 +01:00
osdnk
edf96d839f fix: web with internal interpolation listener 2020-01-28 14:52:41 +01:00
Satyajit Sahoo
141d397bdf chore: tweak types for stack 2020-01-28 14:35:31 +01:00
Evan Bacon
0f18b91690 refactor: split Overlay into a new component (#284)
* refactor: split Overlay into a new component

* Update packages/drawer/src/index.tsx

Co-Authored-By: Satyajit Sahoo <satyajit.happy@gmail.com>

Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2020-01-27 20:10:06 +01:00
Satyajit Sahoo
6262f7298b feat: add animationTypeForReplace option (#297)
Currently, when a screen is replaced the new screen comes into focus with a push animation. However, sometimes you might want to customize how the animation looks like.

For example, when the user logs out, animating out the previous screen like pop feels more natural than doing a push animation with the sign in screen. The PR adds a new `animationTypeForReplace` option to control this. Specifying `animationTypeForReplace: 'pop'` will pop the previous screen, otherwise the new screen will be pushed like before.

Co-authored-by: Michał Osadnik <micosa97@gmail.com>
2020-01-27 18:20:53 +01:00
osdnk
a6f58677dc fix: make UNVERSIONED insufficient expo version 2020-01-27 18:01:35 +01:00
Michał Osadnik
9bfb295620 fix: screens integration on Android (#294) 2020-01-27 17:48:22 +01:00
Satyajit Sahoo
ecd68afb46 feat: add useIsDrawerOpen hook (#299) 2020-01-27 12:59:24 +01:00
Satyajit Sahoo
5fe140e61b fix: fix shadow position for inverted animations 2020-01-26 17:02:00 +01:00
Satyajit Sahoo
944fa35ed4 fix: throw when assigning or accessing the router property in compat 2020-01-26 02:22:16 +01:00
Satyajit Sahoo
2243b45cc1 fix: tweak error messages for validation 2020-01-25 14:42:17 +01:00
Satyajit Sahoo
5e7cfc4ac0 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.39
 - @react-navigation/compat@5.0.0-alpha.28
 - @react-navigation/core@5.0.0-alpha.37
 - @react-navigation/drawer@5.0.0-alpha.41
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.36
 - @react-navigation/material-top-tabs@5.0.0-alpha.35
 - @react-navigation/native-stack@5.0.0-alpha.29
 - @react-navigation/native@5.0.0-alpha.29
 - @react-navigation/routers@5.0.0-alpha.27
 - @react-navigation/stack@5.0.0-alpha.63
2020-01-24 13:01:24 +01:00
NoemiRozpara
5751e7f97a fix: warn if non-serializable values found in state 2020-01-24 12:58:06 +01:00
Satyajit Sahoo
179e807a64 fix: add error message when trying to use v4 API with v5 2020-01-24 00:01:35 +01:00
Satyajit Sahoo
2f1f0af862 fix: validate screen configs 2020-01-23 23:52:27 +01:00
Satyajit Sahoo
9976a888a0 refactor: move replace to stack router 2020-01-23 20:13:40 +01:00
Satyajit Sahoo
16c64e7298 fix: pass correct previous scene to header with headerMode: screen 2020-01-23 15:29:40 +01:00
Satyajit Sahoo
f1fe951cf9 fix: use layout instead of dimensions for determining tab bar layout 2020-01-23 15:10:27 +01:00
Satyajit Sahoo
14250851d1 refactor: remove resetRoot from the navigation prop
Using `resetRoot` requires knowledge of the whole navigation tree that a specific screen shouldn't have. It's better to remove it to discourage resetting whole navigator state from inside a screen.

It's still possible if the user needs it:
- Expose `resetRoot` from container's ref via context
- Use `reset` with the target set to the root navigation state's key
2020-01-23 14:44:34 +01:00
osdnk
42586462fd chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.38
 - @react-navigation/compat@5.0.0-alpha.27
 - @react-navigation/core@5.0.0-alpha.36
 - @react-navigation/drawer@5.0.0-alpha.40
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.35
 - @react-navigation/material-top-tabs@5.0.0-alpha.34
 - @react-navigation/native-stack@5.0.0-alpha.28
 - @react-navigation/native@5.0.0-alpha.28
 - @react-navigation/routers@5.0.0-alpha.26
 - @react-navigation/stack@5.0.0-alpha.62
2020-01-23 10:45:49 +01:00
Satyajit Sahoo
3dede316cc feat: add preventDefault functionality in material bottom tabs 2020-01-22 21:57:39 +01:00
Satyajit Sahoo
63988e0da8 chore: add sideEffects: false for webpack 2020-01-22 21:47:12 +01:00
Satyajit Sahoo
67b2ecfcfc chore: update stack examples 2020-01-22 15:46:51 +01:00
Satyajit Sahoo
68ed8a7259 fix: handle popping more than available screens in stack 2020-01-22 00:33:50 +01:00
Satyajit Sahoo
6c2acbb304 fix: make sure that we return correct value if selector changes
https://github.com/react-navigation/navigation-ex/pull/273#issuecomment-576581225
2020-01-21 18:04:04 +01:00
Satyajit Sahoo
84d75b37e7 chore: add a toggle for RTL 2020-01-20 15:58:46 +01:00
Satyajit Sahoo
65e5147910 chore: add some more examples 2020-01-20 15:38:05 +01:00
Satyajit Sahoo
321fa653ad fix: handle header translation for horizontal-inverted
When going from a screen with header to screen with no header, we need to translate the header to right if the animation direction is inverted.
2020-01-20 10:55:10 +01:00
Satyajit Sahoo
2a76dc4d3c fix: improvements to the compat layer 2020-01-20 10:36:57 +01:00
Satyajit Sahoo
0a982ee698 fix: don't use native driver on web
The native driver is not supported for animations on web. It just prints a wanrning in the console. So we conditionally disable it on web.
2020-01-20 06:20:06 +01:00
Satyajit Sahoo
1da4a6437f fix: fix types for native stack 2020-01-20 05:39:08 +01:00
Satyajit Sahoo
f1df4a0808 feat: emit appear and dismiss events for native stack 2020-01-20 05:28:41 +01:00
Satyajit Sahoo
14ae3738cf fix: ensure re-render on isFirstRouteInParent change in compat layer 2020-01-19 03:44:16 +01:00
Satyajit Sahoo
32a2206513 feat: add useNavigationState hook
Sometimes it's useful to get the current navigation state inside a screen. We have the `dangerouslyGetState` method for that. However, the problem with this method is that it won't trigger a re-render when it changes, so user cannot rely on it for rendering something.

This adds a 2 things:
1. A `state` event similar to `focus` and `blur` that user can subscribe to
2. A `useNavigationState` hook that takes a selector and returns part of the state

Internally `useNavigationState` subscribes to the state event to get the current navigation state.

I have also made it mandatory to pass a selector to `useNavigationState`. This makes it harder to accidentally get the whole navigation state, which will trigger a re-render every time anything changes, even if we don't care about the change. With a selector, we can tell which part we care about, and if that part didn't change, it won't trigger a re-render.

For example, to get the same functionality as the old `isFirstRouteInParent` method:

```js
function MyComponent({ route }) {
  const isFirstRouteInParent = useNavigationState(state => state.routes[0] === route);

  // content
}
```
2020-01-18 23:25:42 +01:00
Satyajit Sahoo
38520a97ff fix: position inactivscreensws offscreen by default 2020-01-18 23:13:36 +01:00
Satyajit Sahoo
3bf5ddde2a fix: don't add ?if query params is empty 2020-01-18 22:30:39 +01:00
Satyajit Sahoo
43d2c456be fix: slide the header up to hide it for vertical animation 2020-01-18 04:13:37 +01:00
Satyajit Sahoo
fe82276b1f fix: use a fade animation for header in all presets 2020-01-18 03:54:01 +01:00
Wojciech Lewicki
1e53821d52 feat: support nested config in getPathFromState (#266)
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2020-01-17 22:43:37 +01:00
Satyajit Sahoo
23ab45aceb fix: fix types for useFocusEffect
See #270
2020-01-17 15:54:02 +01:00
Satyajit Sahoo
d9059b56d8 fix: disallow canPreventDefault option if not present in types 2020-01-15 08:48:02 +01:00
Satyajit Sahoo
ad4eaff1e9 fix: use protected for private value store 2020-01-14 16:52:29 +01:00
Satyajit Sahoo
da67e134d2 feat: let the navigator specify if default can be prevented 2020-01-14 16:48:56 +01:00
Satyajit Sahoo
ee381a4ba3 test: make sure navigation prop is cached 2020-01-14 15:26:45 +01:00
Satyajit Sahoo
3c5b8c4992 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.37
 - @react-navigation/compat@5.0.0-alpha.26
 - @react-navigation/core@5.0.0-alpha.35
 - @react-navigation/drawer@5.0.0-alpha.39
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.34
 - @react-navigation/material-top-tabs@5.0.0-alpha.33
 - @react-navigation/native-stack@5.0.0-alpha.27
 - @react-navigation/native@5.0.0-alpha.27
 - @react-navigation/routers@5.0.0-alpha.25
 - @react-navigation/stack@5.0.0-alpha.61
2020-01-14 02:24:48 +01:00
Satyajit Sahoo
a912323c1d fix: fix intellisense for CompositeNavigationProp 2020-01-14 02:21:18 +01:00
Satyajit Sahoo
805e5e8636 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.36
 - @react-navigation/compat@5.0.0-alpha.25
 - @react-navigation/core@5.0.0-alpha.34
 - @react-navigation/drawer@5.0.0-alpha.38
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.33
 - @react-navigation/material-top-tabs@5.0.0-alpha.32
 - @react-navigation/native-stack@5.0.0-alpha.26
 - @react-navigation/native@5.0.0-alpha.26
 - @react-navigation/routers@5.0.0-alpha.24
 - @react-navigation/stack@5.0.0-alpha.60
2020-01-13 18:35:00 +01:00
Satyajit Sahoo
65a5dac2bf fix: make sure paths aren't aliased when building definitions
closes #265
2020-01-13 18:31:41 +01:00
192 changed files with 6379 additions and 2007 deletions

54
.github/workflows/expo-preview.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Expo Preview
on: [pull_request]
jobs:
publish:
name: Install and publish
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.owner.login == 'react-navigation'
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 10.x
- name: Setup Expo
uses: expo/expo-github-action@v5
with:
expo-version: 3.x
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
expo-cache: true
- name: Get yarn cache
id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Check yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn
- name: Publish Expo app
working-directory: ./example
run: expo publish --release-channel=pr-${{ github.event.number }}
- name: Get expo link
id: expo
run: echo "::set-output name=path::@react-navigation/react-navigation-example?release-channel=pr-${{ github.event.number }}"
- name: Comment on PR
uses: unsplash/comment-on-pr@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
msg: The Expo app for the example from this branch is ready!<br><br>[expo.io/${{ steps.expo.outputs.path }}](https://expo.io/${{ steps.expo.outputs.path }})<br><br><a href="https://exp.host/${{ steps.expo.outputs.path }}"><img src="https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=exp://exp.host/${{ steps.expo.outputs.path }}" height="200px" width="200px"></a>.

44
.github/workflows/expo.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Expo Publish
on:
push:
branches:
- master
jobs:
publish:
name: Install and publish
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 10.x
- name: Setup Expo
uses: expo/expo-github-action@v5
with:
expo-version: 3.x
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
expo-cache: true
- name: Get yarn cache
id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn
- name: Publish Expo app
working-directory: ./example
run: expo publish

27
.github/workflows/rebase.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Automatic Rebase
on:
issue_comment:
types: [created]
jobs:
rebase:
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# https://github.community/t5/GitHub-Actions/Workflow-is-failing-if-no-job-can-be-ran-due-to-condition/m-p/38186#M3250
always_job:
name: Always run job
runs-on: ubuntu-latest
steps:
- name: Always run
run: echo "This job is used to prevent the workflow to fail when all other jobs are skipped."

View File

@@ -16,5 +16,7 @@ module.exports = {
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-optional-chaining',
'@babel/transform-flow-strip-types',
'@babel/plugin-proposal-nullish-coalescing-operator',
],
};

View File

@@ -5,3 +5,5 @@ If you want to run the example from the repo,
- Clone the repository and run `yarn` in the project root
- Run `yarn example start` to start the packager
- Follow the instructions to open it with the [Expo app](https://expo.io/)
You can also run the currently published [app on Expo](https://expo.io/@react-navigation/react-navigation-example) on your Android device or iOS simulator or the [web app](https://react-navigation-example.netlify.com/) in your browser.

View File

@@ -4,8 +4,8 @@
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" value="3.3.0" />
<option name="LAST_KNOWN_AGP_VERSION" value="3.3.0" />
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" value="3.4.2" />
<option name="LAST_KNOWN_AGP_VERSION" value="3.4.2" />
</configuration>
</facet>
<facet type="android" name="Android">
@@ -19,8 +19,8 @@
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res;file://$MODULE_DIR$/build/generated/res/rs/debug;file://$MODULE_DIR$/build/generated/res/resValues/debug" />
<option name="TEST_RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res;file://$MODULE_DIR$/build/generated/res/resValues/debug" />
<option name="TEST_RES_FOLDERS_RELATIVE_PATH" value="" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
@@ -70,6 +70,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/rncli/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
@@ -90,91 +91,103 @@
</content>
<orderEntry type="jdk" jdkName="Android API 28 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: com.android.support:collections:28.0.0@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:common:1.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-annotations:28.0.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.infer.annotation:infer-annotation:0.11.2@jar" level="project" />
<orderEntry type="library" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.google.code.findbugs:jsr305:3.0.2@jar" level="project" />
<orderEntry type="library" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
<orderEntry type="library" name="Gradle: androidx.collection:collection:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-common:2.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: androidx.arch.core:core-common:2.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: androidx.annotation:annotation:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp-urlconnection:3.12.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:3.12.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okio:okio:1.15.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:disklrucache:4.9.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:annotations:4.9.0@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.3.41@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.41@jar" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains:annotations:13.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.device.yearclass:yearclass:2.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: commons-codec:commons-codec:1.10@jar" level="project" />
<orderEntry type="library" name="Gradle: commons-io:commons-io:1.4@jar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:disklrucache:4.9.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:annotations:4.9.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.parse.bolts:bolts-tasks:1.4.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.react:react-native:0.61.5@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:appcompat-v7:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.appcompat:appcompat:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.browser:browser:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:glide:4.9.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:gifdecoder:4.9.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-location:16.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-base:16.0.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-places-placereport:16.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-tasks:16.0.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-basement:16.0.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-v4:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-fragment:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:customtabs:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-ui:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-utils:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-vector-drawable:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:loader:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-media-compat:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:viewpager:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:coordinatorlayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:drawerlayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:slidingpanelayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:customview:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:swiperefreshlayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:asynclayoutinflater:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-compat:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:versionedparcelable:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:cursoradapter:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:documentfile:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:localbroadcastmanager:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:print:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:gifdecoder:4.9.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:interpolator:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:runtime:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fresco:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:drawee:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-base:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.legacy:legacy-support-v4:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.fragment:fragment:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.media:media:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.legacy:legacy-support-core-ui:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.legacy:legacy-support-core-utils:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.appcompat:appcompat-resources:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.drawerlayout:drawerlayout:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.viewpager:viewpager:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.loader:loader:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.activity:activity:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.vectordrawable:vectordrawable-animated:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.vectordrawable:vectordrawable:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.coordinatorlayout:coordinatorlayout:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.slidingpanelayout:slidingpanelayout:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.customview:customview:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.swiperefreshlayout:swiperefreshlayout:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.asynclayoutinflater:asynclayoutinflater:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.core:core:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.cursoradapter:cursoradapter:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.versionedparcelable:versionedparcelable:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.interpolator:interpolator:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-viewmodel:2.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-runtime:2.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.documentfile:documentfile:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.localbroadcastmanager:localbroadcastmanager:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.print:print:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.savedstate:savedstate:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-livedata:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-livedata-core:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.arch.core:core-runtime:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fresco:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:drawee:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-base:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.soloader:soloader:0.6.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:nativeimagefilters:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:nativeimagetranscoder:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: io.nlopez.smartlocation:library:3.2.11@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: org.webkit:android-jsc:r245459@aar" level="project" />
<orderEntry type="module" module-name="android-expo-permissions" />
<orderEntry type="module" module-name="android-expo-constants" />
<orderEntry type="module" module-name="android-unimodules-image-loader-interface" />
<orderEntry type="module" module-name="android-expo-web-browser" />
<orderEntry type="module" module-name="android-unimodules-react-native-adapter" />
<orderEntry type="module" module-name="android-expo-file-system" />
<orderEntry type="module" module-name="android-expo-location" />
<orderEntry type="module" module-name="android-expo-error-recovery" />
<orderEntry type="module" module-name="android-unimodules-permissions-interface" />
<orderEntry type="module" module-name="android-unimodules-core" />
<orderEntry type="module" module-name="android-expo-app-loader-provider" />
<orderEntry type="module" module-name="android-expo-font" />
<orderEntry type="module" module-name="android-expo-keep-awake" />
<orderEntry type="module" module-name="android-expo-linear-gradient" />
<orderEntry type="module" module-name="android-expo-sqlite" />
<orderEntry type="module" module-name="android-unimodules-barcode-scanner-interface" />
<orderEntry type="module" module-name="android-unimodules-camera-interface" />
<orderEntry type="module" module-name="android-unimodules-constants-interface" />
<orderEntry type="module" module-name="android-unimodules-face-detector-interface" />
<orderEntry type="module" module-name="android-unimodules-file-system-interface" />
<orderEntry type="module" module-name="android-unimodules-font-interface" />
<orderEntry type="module" module-name="android-unimodules-sensors-interface" />
<orderEntry type="module" module-name="android-unimodules-task-manager-interface" />
<orderEntry type="module" module-name="android-@react-native-community_masked-view" />
<orderEntry type="module" module-name="android-react-native-gesture-handler" />
<orderEntry type="module" module-name="android-react-native-reanimated" />
<orderEntry type="module" module-name="react-native-safe-area-context" />
<orderEntry type="module" module-name="react-native-screens" />
<orderEntry type="module" module-name="react-native-reanimated" />
<orderEntry type="module" module-name="react-native-gesture-handler" />
<orderEntry type="module" module-name="expo-permissions" />
<orderEntry type="module" module-name="unimodules-core" />
<orderEntry type="module" module-name="unimodules-react-native-adapter" />
<orderEntry type="module" module-name="expo-app-loader-provider" />
<orderEntry type="module" module-name="expo-constants" />
<orderEntry type="module" module-name="expo-file-system" />
<orderEntry type="module" module-name="expo-font" />
<orderEntry type="module" module-name="expo-keep-awake" />
<orderEntry type="module" module-name="expo-linear-gradient" />
<orderEntry type="module" module-name="expo-location" />
<orderEntry type="module" module-name="expo-sqlite" />
<orderEntry type="module" module-name="expo-web-browser" />
<orderEntry type="module" module-name="unimodules-barcode-scanner-interface" />
<orderEntry type="module" module-name="unimodules-camera-interface" />
<orderEntry type="module" module-name="unimodules-constants-interface" />
<orderEntry type="module" module-name="unimodules-face-detector-interface" />
<orderEntry type="module" module-name="unimodules-file-system-interface" />
<orderEntry type="module" module-name="unimodules-font-interface" />
<orderEntry type="module" module-name="unimodules-image-loader-interface" />
<orderEntry type="module" module-name="unimodules-permissions-interface" />
<orderEntry type="module" module-name="unimodules-sensors-interface" />
<orderEntry type="module" module-name="unimodules-task-manager-interface" />
</component>
</module>
</module>

View File

@@ -36,12 +36,19 @@
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="rne" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>

View File

@@ -3,6 +3,7 @@
"displayName": "React Navigation Example",
"expo": {
"name": "@react-navigation/example",
"owner": "react-navigation",
"slug": "react-navigation-example",
"description": "Demo app to showcase various functionality of React Navigation",
"privacy": "public",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 900 B

After

Width:  |  Height:  |  Size: 683 B

View File

@@ -2,6 +2,8 @@ PODS:
- boost-for-react-native (1.63.0)
- DoubleConversion (1.1.6)
- EXAppLoaderProvider (8.0.0)
- EXBlur (8.0.0):
- UMCore
- EXConstants (8.0.0):
- UMConstantsInterface
- UMCore
@@ -48,6 +50,8 @@ PODS:
- glog
- glog (0.3.5)
- RCTRequired (0.61.5)
- RCTRestart (0.0.13):
- React
- RCTTypeSafety (0.61.5):
- FBLazyVector (= 0.61.5)
- Folly (= 2018.10.22.00)
@@ -249,11 +253,11 @@ PODS:
- ReactCommon/jscallinvoker (= 0.61.5)
- RNCMaskedView (0.1.5):
- React
- RNGestureHandler (1.5.3):
- RNGestureHandler (1.5.5):
- React
- RNReanimated (1.4.0):
- React
- RNScreens (2.0.0-alpha.22):
- RNScreens (2.0.0-alpha.33):
- React
- UMBarCodeScannerInterface (5.0.0)
- UMCameraInterface (5.0.0)
@@ -275,6 +279,7 @@ PODS:
DEPENDENCIES:
- DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EXAppLoaderProvider (from `../../node_modules/expo-app-loader-provider/ios`)
- EXBlur (from `../../node_modules/expo-blur/ios`)
- EXConstants (from `../../node_modules/expo-constants/ios`)
- EXErrorRecovery (from `../../node_modules/expo-error-recovery/ios`)
- EXFileSystem (from `../../node_modules/expo-file-system/ios`)
@@ -290,6 +295,7 @@ DEPENDENCIES:
- Folly (from `../../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
- RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`)
- RCTRestart (from `../../node_modules/react-native-restart/ios`)
- RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../../node_modules/react-native/`)
- React-Core (from `../../node_modules/react-native/`)
@@ -340,6 +346,9 @@ EXTERNAL SOURCES:
EXAppLoaderProvider:
:path: !ruby/object:Pathname
path: "../../node_modules/expo-app-loader-provider/ios"
EXBlur:
:path: !ruby/object:Pathname
path: "../../node_modules/expo-blur/ios"
EXConstants:
:path: !ruby/object:Pathname
path: "../../node_modules/expo-constants/ios"
@@ -380,6 +389,8 @@ EXTERNAL SOURCES:
:podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec"
RCTRequired:
:path: "../../node_modules/react-native/Libraries/RCTRequired"
RCTRestart:
:path: "../../node_modules/react-native-restart/ios"
RCTTypeSafety:
:path: "../../node_modules/react-native/Libraries/TypeSafety"
React:
@@ -469,6 +480,7 @@ SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
EXAppLoaderProvider: ebdb6bc2632c1ccadbe49f5e4104d8d690969c49
EXBlur: d1604f66f89a9414f5ee65dfb23874437c1bb147
EXConstants: 4051b16c17ef3defa03c541d42811dc92b249146
EXErrorRecovery: d36db99ec6a3808f313f01b0890eb443796dd1c2
EXFileSystem: 6e0d9bb6cc4ea404dbb8f583c1a8a2dcdf4b83b6
@@ -484,6 +496,7 @@ SPEC CHECKSUMS:
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
RCTRestart: dd19aab87fc1118e05b6b5b91b959105647f56b4
RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
@@ -504,9 +517,9 @@ SPEC CHECKSUMS:
React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
RNCMaskedView: dd13f9f7b146a9ad82f9b7eb6c9b5548fcf6e990
RNGestureHandler: 02905abe54e1f6e59c081a10b4bd689721e17aa6
RNGestureHandler: d2270608171c868581b840cfc692f2962c05cd17
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
RNScreens: 6adf01eb4080f44af6cca551207c6b0505066857
RNScreens: 1c7fd499b915c77c21e8e6c327890c5af9b4cf7e
UMBarCodeScannerInterface: 3802c8574ef119c150701d679ab386e2266d6a54
UMCameraInterface: 985d301f688ed392f815728f0dd906ca34b7ccb1
UMConstantsInterface: bda5f8bd3403ad99e663eb3c4da685d063c5653c

View File

@@ -13,6 +13,7 @@
#import <UMCore/UMModuleRegistry.h>
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
#import <React/RCTLinkingManager.h>
@implementation AppDelegate
@@ -52,4 +53,10 @@
#endif
}
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
return [RCTLinkingManager application:app openURL:url options:options];
}
@end

View File

@@ -20,33 +20,27 @@
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>React Navigation Example</string>
<key>CFBundleURLSchemes</key>
<array>
<string>rne</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSAppTransportSecurity</key>
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
@@ -78,5 +72,19 @@
<string>Give React Navigation Example periences permission to access your photos</string>
<key>NSRemindersUsageDescription</key>
<string>Allow React Navigation Example to access your reminders</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@@ -6,7 +6,7 @@ const escape = require('escape-string-regexp');
const blacklist = require('metro-config/src/defaults/blacklist');
const root = path.resolve(__dirname, '..');
const packages = path.resolve(__dirname, '..', 'packages');
const packages = path.resolve(root, 'packages');
// Get the list of dependencies for all packages in the monorepo
const modules = ['@expo/vector-icons']
@@ -22,7 +22,7 @@ const modules = ['@expo/vector-icons']
);
// We need to collect list of deps that this package imports
// Collecting both dependencies are peerDependencies sould do it
// Collecting both dependencies are peerDependencies should do it
return Object.keys({
...pak.dependencies,
...pak.peerDependencies,
@@ -59,7 +59,7 @@ module.exports = {
// When we import a package from the monorepo, metro won't be able to find their deps
// We need to specify them in `extraNodeModules` to tell metro where to find them
extraNodeModules: modules.reduce((acc, name) => {
acc[name] = path.join(__dirname, '..', 'node_modules', name);
acc[name] = path.join(root, 'node_modules', name);
return acc;
}, {}),
},
@@ -67,11 +67,11 @@ module.exports = {
server: {
enhanceMiddleware: middleware => {
return (req, res, next) => {
// When an asset is imported outside the project root, it has wrong path on Android
// This happens for the back button in stack, so we fix the path to correct one
const assets = '/packages/stack/src/views/assets';
if (req.url.startsWith(assets)) {
// When an asset is imported outside the project root, it has wrong path on Android
// This happens for the back button in stack, so we fix the path to correct one
req.url = req.url.replace(
assets,
'/assets/../packages/stack/src/views/assets'

View File

@@ -5,7 +5,7 @@
"private": true,
"scripts": {
"start": "expo start",
"web": "expo start --web",
"web": "expo start:web",
"native": "react-native start",
"android": "react-native run-android",
"ios": "react-native run-ios"
@@ -13,18 +13,21 @@
"dependencies": {
"@expo/vector-icons": "^10.0.0",
"@react-native-community/masked-view": "0.1.5",
"@types/react-native-restart": "^0.0.0",
"color": "^3.1.2",
"expo": "^36.0.2",
"expo-asset": "~8.0.0",
"expo-blur": "^8.0.0",
"react": "~16.9.0",
"react-dom": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "~1.5.3",
"react-native-paper": "^3.4.0",
"react-native-gesture-handler": "^1.5.5",
"react-native-paper": "^3.5.0",
"react-native-reanimated": "^1.4.0",
"react-native-restart": "^0.0.13",
"react-native-safe-area-context": "^0.6.2",
"react-native-screens": "^2.0.0-alpha.22",
"react-native-tab-view": "2.11.0",
"react-native-screens": "^2.0.0-alpha.33",
"react-native-tab-view": "2.13.0",
"react-native-unimodules": "^0.7.0",
"react-native-web": "^0.11.7"
},

View File

@@ -1 +1 @@
export default [];
export default ['rne://127.0.0.1:19000/--/'];

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { View, TextInput, ActivityIndicator, StyleSheet } from 'react-native';
import { Title, Button } from 'react-native-paper';
import { ParamListBase } from '@react-navigation/native';
import { useTheme, ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
HeaderBackButton,
@@ -40,11 +40,25 @@ const SplashScreen = () => {
const SignInScreen = () => {
const { signIn } = React.useContext(AuthContext);
const { colors } = useTheme();
return (
<View style={styles.content}>
<TextInput placeholder="Username" style={styles.input} />
<TextInput placeholder="Password" secureTextEntry style={styles.input} />
<TextInput
placeholder="Username"
style={[
styles.input,
{ backgroundColor: colors.card, color: colors.text },
]}
/>
<TextInput
placeholder="Password"
secureTextEntry
style={[
styles.input,
{ backgroundColor: colors.card, color: colors.text },
]}
/>
<Button mode="contained" onPress={signIn} style={styles.button}>
Sign in
</Button>
@@ -73,6 +87,7 @@ type Props = {
type State = {
isLoading: boolean;
isSignout: boolean;
userToken: undefined | string;
};
@@ -94,17 +109,20 @@ export default function SimpleStackScreen({ navigation }: Props) {
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
userToken: action.token,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
userToken: undefined,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: undefined,
}
);
@@ -147,7 +165,10 @@ export default function SimpleStackScreen({ navigation }: Props) {
) : state.userToken === undefined ? (
<SimpleStack.Screen
name="SignIn"
options={{ title: 'Sign in' }}
options={{
title: 'Sign in',
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
component={SignInScreen}
/>
) : (
@@ -171,7 +192,6 @@ const styles = StyleSheet.create({
input: {
margin: 8,
padding: 10,
backgroundColor: 'white',
borderRadius: 3,
borderWidth: StyleSheet.hairlineWidth,
borderColor: 'rgba(0, 0, 0, 0.08)',

View File

@@ -0,0 +1,56 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Title, Button } from 'react-native-paper';
import { Feather } from '@expo/vector-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
type BottomTabParams = {
[key: string]: undefined;
};
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
export default function BottomTabsScreen() {
const [tabs, setTabs] = React.useState([0, 1]);
return (
<BottomTabs.Navigator>
{tabs.map(i => (
<BottomTabs.Screen
key={i}
name={`tab-${i}`}
options={{
title: `Tab ${i}`,
tabBarIcon: ({ color, size }) => (
<Feather name="octagon" color={color} size={size} />
),
}}
>
{() => (
<View style={styles.container}>
<Title>Tab {i}</Title>
<Button onPress={() => setTabs(tabs => [...tabs, tabs.length])}>
Add a tab
</Button>
<Button
onPress={() =>
setTabs(tabs => (tabs.length > 1 ? tabs.slice(0, -1) : tabs))
}
>
Remove a tab
</Button>
</View>
)}
</BottomTabs.Screen>
))}
</BottomTabs.Navigator>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { View, StyleSheet, ScrollView } from 'react-native';
import { Button } from 'react-native-paper';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
@@ -25,7 +25,7 @@ const ArticleScreen = ({
route: RouteProp<ModalStackParams, 'Article'>;
}) => {
return (
<React.Fragment>
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
@@ -42,14 +42,14 @@ const ArticleScreen = ({
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} />
</React.Fragment>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};
const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
return (
<React.Fragment>
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
@@ -66,8 +66,8 @@ const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
Go back
</Button>
</View>
<Albums />
</React.Fragment>
<Albums scrollEnabled={false} />
</ScrollView>
);
};

View File

@@ -137,7 +137,7 @@ const AlbumsScreen = ({
}: {
navigation: NativeStackNavigation;
}) => (
<React.Fragment>
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
@@ -154,8 +154,8 @@ const AlbumsScreen = ({
Go back
</Button>
</View>
<Albums />
</React.Fragment>
<Albums scrollEnabled={false} />
</ScrollView>
);
const NativeStack = createNativeStackNavigator<NativeStackParams>();

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { View, StyleSheet, ScrollView } from 'react-native';
import { Button } from 'react-native-paper';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
@@ -8,9 +8,11 @@ import {
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import NewsFeed from '../Shared/NewsFeed';
type SimpleStackParams = {
Article: { author: string };
NewsFeed: undefined;
Album: undefined;
};
@@ -24,14 +26,42 @@ const ArticleScreen = ({
route: RouteProp<SimpleStackParams, 'Article'>;
}) => {
return (
<React.Fragment>
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Album')}
onPress={() => navigation.replace('NewsFeed')}
style={styles.button}
>
Push album
Replace with feed
</Button>
<Button
mode="outlined"
onPress={() => navigation.pop()}
style={styles.button}
>
Pop screen
</Button>
</View>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};
const NewsFeedScreen = ({
navigation,
}: {
navigation: SimpleStackNavigation;
}) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.navigate('Album')}
style={styles.button}
>
Navigate to album
</Button>
<Button
mode="outlined"
@@ -41,8 +71,8 @@ const ArticleScreen = ({
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} />
</React.Fragment>
<NewsFeed scrollEnabled={false} />
</ScrollView>
);
};
@@ -52,7 +82,7 @@ const AlbumsScreen = ({
navigation: SimpleStackNavigation;
}) => {
return (
<React.Fragment>
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
@@ -63,14 +93,14 @@ const AlbumsScreen = ({
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
onPress={() => navigation.pop(2)}
style={styles.button}
>
Go back
Pop by 2
</Button>
</View>
<Albums />
</React.Fragment>
<Albums scrollEnabled={false} />
</ScrollView>
);
};
@@ -95,6 +125,11 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
})}
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="NewsFeed"
component={NewsFeedScreen}
options={{ title: 'Feed' }}
/>
<SimpleStack.Screen
name="Album"
component={AlbumsScreen}

View File

@@ -0,0 +1,158 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView, Alert, Platform } from 'react-native';
import { Button, Appbar } from 'react-native-paper';
import { BlurView } from 'expo-blur';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
StackNavigationProp,
HeaderBackground,
useHeaderHeight,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
type SimpleStackParams = {
Article: { author: string };
Album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const ArticleScreen = ({
navigation,
route,
}: {
navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'Article'>;
}) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Album')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};
const AlbumsScreen = ({
navigation,
}: {
navigation: SimpleStackNavigation;
}) => {
const headerHeight = useHeaderHeight();
return (
<ScrollView contentContainerStyle={{ paddingTop: headerHeight }}>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Albums scrollEnabled={false} />
</ScrollView>
);
};
const SimpleStack = createStackNavigator<SimpleStackParams>();
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
navigation: StackNavigationProp<ParamListBase>;
};
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
navigation.setOptions({
headerShown: false,
});
return (
<SimpleStack.Navigator {...rest}>
<SimpleStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params?.author}`,
headerTintColor: '#fff',
headerStyle: { backgroundColor: '#ff005d' },
headerBackTitleVisible: false,
headerTitleAlign: 'center',
headerBackImage: ({ tintColor }) => (
<MaterialCommunityIcons
name="arrow-left-circle-outline"
color={tintColor}
size={24}
style={{ marginHorizontal: Platform.OS === 'ios' ? 8 : 0 }}
/>
),
headerRight: ({ tintColor }) => (
<Appbar.Action
color={tintColor}
icon="dots-horizontal-circle-outline"
onPress={() =>
Alert.alert(
'Never gonna give you up!',
'Never gonna let you down! Never gonna run around and desert you!'
)
}
/>
),
})}
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="Album"
component={AlbumsScreen}
options={{
title: 'Album',
headerBackTitle: 'Back',
headerTransparent: true,
headerBackground: () => (
<HeaderBackground style={{ backgroundColor: 'transparent' }}>
<BlurView
tint="light"
intensity={75}
style={StyleSheet.absoluteFill}
/>
</HeaderBackground>
),
}}
/>
</SimpleStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -0,0 +1,154 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView } from 'react-native';
import { Button, Paragraph } from 'react-native-paper';
import { RouteProp, ParamListBase, useTheme } from '@react-navigation/native';
import {
createStackNavigator,
StackNavigationProp,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
type SimpleStackParams = {
Article: { author: string };
Dialog: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const ArticleScreen = ({
navigation,
route,
}: {
navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'Article'>;
}) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Dialog')}
style={styles.button}
>
Show Dialog
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};
const DialogScreen = ({
navigation,
}: {
navigation: SimpleStackNavigation;
}) => {
const { colors } = useTheme();
return (
<View style={styles.container}>
<View style={[styles.dialog, { backgroundColor: colors.card }]}>
<Paragraph>
Mise en place is a French term that literally means put in place. It
also refers to a way cooks in professional kitchens and restaurants
set up their work stationsfirst by gathering all ingredients for a
recipes, partially preparing them (like measuring out and chopping),
and setting them all near each other. Setting up mise en place before
cooking is another top tip for home cooks, as it seriously helps with
organization. Itll pretty much guarantee you never forget to add an
ingredient and save you time from running back and forth from the
pantry ten times.
</Paragraph>
<Button style={styles.close} compact onPress={navigation.goBack}>
Okay
</Button>
</View>
</View>
);
};
const SimpleStack = createStackNavigator<SimpleStackParams>();
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
navigation: StackNavigationProp<ParamListBase>;
};
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
navigation.setOptions({
headerShown: false,
});
return (
<SimpleStack.Navigator mode="modal" {...rest}>
<SimpleStack.Screen
name="Article"
component={ArticleScreen}
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="Dialog"
component={DialogScreen}
options={{
headerShown: false,
cardStyle: { backgroundColor: 'transparent' },
cardOverlayEnabled: true,
cardStyleInterpolator: ({ current: { progress } }) => ({
cardStyle: {
opacity: progress.interpolate({
inputRange: [0, 0.5, 0.9, 1],
outputRange: [0, 0.25, 0.7, 1],
}),
transform: [
{
scale: progress.interpolate({
inputRange: [0, 1],
outputRange: [0.9, 1],
extrapolate: 'clamp',
}),
},
],
},
overlayStyle: {
opacity: progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.5],
extrapolate: 'clamp',
}),
},
}),
}}
/>
</SimpleStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
dialog: {
padding: 16,
width: '90%',
maxWidth: 400,
borderRadius: 3,
},
close: {
alignSelf: 'flex-end',
},
});

View File

@@ -1,53 +1,90 @@
/* eslint-disable import/no-commonjs */
import * as React from 'react';
import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native';
import {
View,
Image,
ScrollView,
StyleSheet,
ScrollViewProps,
Dimensions,
Platform,
} from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
const COVERS = [
require('../../assets/album-art-1.jpg'),
require('../../assets/album-art-2.jpg'),
require('../../assets/album-art-3.jpg'),
require('../../assets/album-art-4.jpg'),
require('../../assets/album-art-5.jpg'),
require('../../assets/album-art-6.jpg'),
require('../../assets/album-art-7.jpg'),
require('../../assets/album-art-8.jpg'),
require('../../assets/album-art-01.jpg'),
require('../../assets/album-art-02.jpg'),
require('../../assets/album-art-03.jpg'),
require('../../assets/album-art-04.jpg'),
require('../../assets/album-art-05.jpg'),
require('../../assets/album-art-06.jpg'),
require('../../assets/album-art-07.jpg'),
require('../../assets/album-art-08.jpg'),
require('../../assets/album-art-09.jpg'),
require('../../assets/album-art-10.jpg'),
require('../../assets/album-art-11.jpg'),
require('../../assets/album-art-12.jpg'),
require('../../assets/album-art-13.jpg'),
require('../../assets/album-art-14.jpg'),
require('../../assets/album-art-15.jpg'),
require('../../assets/album-art-16.jpg'),
require('../../assets/album-art-17.jpg'),
require('../../assets/album-art-18.jpg'),
require('../../assets/album-art-19.jpg'),
require('../../assets/album-art-20.jpg'),
require('../../assets/album-art-21.jpg'),
require('../../assets/album-art-22.jpg'),
require('../../assets/album-art-23.jpg'),
require('../../assets/album-art-24.jpg'),
];
export default function Albums() {
export default function Albums(props: Partial<ScrollViewProps>) {
const ref = React.useRef<ScrollView>(null);
useScrollToTop(ref);
return (
<ScrollView
ref={ref}
style={styles.container}
contentContainerStyle={styles.content}
>
<ScrollView ref={ref} contentContainerStyle={styles.content} {...props}>
{COVERS.map((source, i) => (
// eslint-disable-next-line react/no-array-index-key
<Image key={i} source={source} style={styles.cover} />
))}
{COVERS.map((source, i) => (
// eslint-disable-next-line react/no-array-index-key
<Image key={i + 'F'} source={source} style={styles.cover} />
<View key={i} style={styles.item}>
<Image source={source} style={styles.photo} />
</View>
))}
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#000',
},
content: {
flexDirection: 'row',
flexWrap: 'wrap',
},
cover: {
width: '50%',
height: Dimensions.get('window').width / 2,
...Platform.select({
web: {
content: {
display: 'grid' as 'none',
gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))',
},
item: {
width: '100%',
},
},
default: {
content: {
flexDirection: 'row',
flexWrap: 'wrap',
},
item: {
height: Dimensions.get('window').width / 2,
width: '50%',
},
},
}),
photo: {
flex: 1,
resizeMode: 'cover',
paddingTop: '100%',
},
});

View File

@@ -1,8 +1,15 @@
import * as React from 'react';
import { View, Text, Image, ScrollView, StyleSheet } from 'react-native';
import {
View,
Text,
Image,
ScrollView,
StyleSheet,
ScrollViewProps,
} from 'react-native';
import { useScrollToTop, useTheme } from '@react-navigation/native';
type Props = {
type Props = Partial<ScrollViewProps> & {
date?: string;
author?: {
name: string;
@@ -14,6 +21,7 @@ export default function Article({
author = {
name: 'Knowledge Bot',
},
...rest
}: Props) {
const ref = React.useRef<ScrollView>(null);
@@ -26,6 +34,7 @@ export default function Article({
ref={ref}
style={{ backgroundColor: colors.card }}
contentContainerStyle={styles.content}
{...rest}
>
<View style={styles.author}>
<Image

View File

@@ -6,6 +6,7 @@ import {
TextInput,
ScrollView,
StyleSheet,
ScrollViewProps,
} from 'react-native';
import { useScrollToTop, useTheme } from '@react-navigation/native';
import Color from 'color';
@@ -17,7 +18,7 @@ const MESSAGES = [
'make me a sandwich',
];
export default function Chat() {
export default function Chat(props: Partial<ScrollViewProps>) {
const ref = React.useRef<ScrollView>(null);
useScrollToTop(ref);
@@ -29,6 +30,7 @@ export default function Chat() {
<ScrollView
style={styles.inverted}
contentContainerStyle={styles.content}
{...props}
>
{MESSAGES.map((text, i) => {
const odd = i % 2;

View File

@@ -0,0 +1,146 @@
import * as React from 'react';
import {
View,
TextInput,
Image,
ScrollView,
StyleSheet,
ScrollViewProps,
} from 'react-native';
import { useScrollToTop, useTheme } from '@react-navigation/native';
import {
Card,
Text,
Avatar,
Subheading,
IconButton,
Divider,
} from 'react-native-paper';
import Color from 'color';
type Props = Partial<ScrollViewProps>;
const Author = () => {
return (
<View style={[styles.row, styles.attribution]}>
<Avatar.Image source={require('../../assets/avatar-1.png')} size={32} />
<Subheading style={styles.author}>Joke bot</Subheading>
</View>
);
};
const Footer = () => {
return (
<View style={styles.row}>
<IconButton style={styles.icon} size={16} icon="heart-outline" />
<IconButton style={styles.icon} size={16} icon="comment-outline" />
<IconButton style={styles.icon} size={16} icon="share-outline" />
</View>
);
};
export default function NewsFeed(props: Props) {
const ref = React.useRef<ScrollView>(null);
useScrollToTop(ref);
const { colors } = useTheme();
return (
<ScrollView ref={ref} {...props}>
<Card style={styles.card}>
<TextInput
placeholder="What's on your mind?"
placeholderTextColor={Color(colors.text)
.alpha(0.5)
.rgb()
.string()}
style={styles.input}
/>
</Card>
<Card style={styles.card}>
<Author />
<Card.Content style={styles.content}>
<Text>
If you aren&apos;t impressed with the picture of the first Black
Hole, you clearly don&apos;t understand the gravity of the
situation.
</Text>
</Card.Content>
<Divider />
<Footer />
</Card>
<Card style={styles.card}>
<Author />
<Card.Content style={styles.content}>
<Text>
I went to the zoo and I saw a baguette in a cage. I asked the
zookeeper about it and he said it was bread in captivity.
</Text>
</Card.Content>
<Image source={require('../../assets/book.jpg')} style={styles.cover} />
<Footer />
</Card>
<Card style={styles.card}>
<Author />
<Card.Content style={styles.content}>
<Text>Why didn&apos;t 4 ask 5 out? Because he was 2².</Text>
</Card.Content>
<Divider />
<Footer />
</Card>
<Card style={styles.card}>
<Author />
<Card.Content style={styles.content}>
<Text>
What did Master Yoda say when he first saw himself in 4k? HDMI.
</Text>
</Card.Content>
<Divider />
<Footer />
</Card>
<Card style={styles.card}>
<Author />
<Card.Content style={styles.content}>
<Text>
Someone broke into my house and stole 20% of my couch. Ouch!
</Text>
</Card.Content>
<Divider />
<Footer />
</Card>
</ScrollView>
);
}
const styles = StyleSheet.create({
input: {
padding: 16,
backgroundColor: 'transparent',
margin: 0,
},
card: {
marginVertical: 8,
borderRadius: 0,
},
cover: {
height: 160,
borderRadius: 0,
},
content: {
marginBottom: 12,
},
attribution: {
margin: 12,
},
author: {
marginHorizontal: 8,
},
row: {
flexDirection: 'row',
alignItems: 'center',
},
icon: {
flex: 1,
},
});

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import { View } from 'react-native';
import { Subheading, Switch } from 'react-native-paper';
type Props = {
label: string;
value: boolean;
onValueChange: () => void;
};
export default function SettingsItem({ label, value, onValueChange }: Props) {
return (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
}}
>
<Subheading>{label}</Subheading>
<Switch value={value} onValueChange={onValueChange} />
</View>
);
}

View File

@@ -1,21 +1,20 @@
import * as React from 'react';
import {
View,
ScrollView,
AsyncStorage,
YellowBox,
Platform,
StatusBar,
I18nManager,
} from 'react-native';
import RNRestart from 'react-native-restart';
import { MaterialIcons } from '@expo/vector-icons';
import {
Provider as PaperProvider,
DefaultTheme as PaperLightTheme,
DarkTheme as PaperDarkTheme,
Subheading,
Appbar,
List,
Switch,
Divider,
} from 'react-native-paper';
import { Asset } from 'expo-asset';
@@ -23,7 +22,7 @@ import {
InitialState,
useLinking,
NavigationContainerRef,
NavigationNativeContainer,
NavigationContainer,
DefaultTheme,
DarkTheme,
} from '@react-navigation/native';
@@ -42,11 +41,16 @@ import LinkingPrefixes from './LinkingPrefixes';
import SimpleStack from './Screens/SimpleStack';
import NativeStack from './Screens/NativeStack';
import ModalPresentationStack from './Screens/ModalPresentationStack';
import StackTransparent from './Screens/StackTransparent';
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
import BottomTabs from './Screens/BottomTabs';
import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
import MaterialBottomTabs from './Screens/MaterialBottomTabs';
import DynamicTabs from './Screens/DynamicTabs';
import AuthFlow from './Screens/AuthFlow';
import CompatAPI from './Screens/CompatAPI';
import SettingsItem from './Shared/SettingsItem';
import { Updates } from 'expo';
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
@@ -68,6 +72,14 @@ const SCREENS = {
title: 'Modal Presentation Stack',
component: ModalPresentationStack,
},
StackTransparent: {
title: 'Transparent Stack',
component: StackTransparent,
},
StackHeaderCustomization: {
title: 'Header Customization in Stack',
component: StackHeaderCustomization,
},
BottomTabs: { title: 'Bottom Tabs', component: BottomTabs },
MaterialTopTabs: {
title: 'Material Top Tabs',
@@ -77,6 +89,10 @@ const SCREENS = {
title: 'Material Bottom Tabs',
component: MaterialBottomTabs,
},
DynamicTabs: {
title: 'Dynamic Tabs',
component: DynamicTabs,
},
AuthFlow: {
title: 'Auth Flow',
component: AuthFlow,
@@ -101,22 +117,27 @@ export default function App() {
// To test deep linking on, run the following in the Terminal:
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
// iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack
// Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack"
// iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
const { getInitialState } = useLinking(containerRef, {
prefixes: LinkingPrefixes,
config: {
Root: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
acc[name] = name
.replace(/([A-Z]+)/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
Root: {
path: 'root',
screens: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
acc[name] = name
.replace(/([A-Z]+)/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
return acc;
},
{}
),
return acc;
},
{}
),
},
},
});
@@ -132,7 +153,7 @@ export default function App() {
try {
let state = await getInitialState();
if (state === undefined) {
if (Platform.OS !== 'web' && state === undefined) {
const savedState = await AsyncStorage.getItem(
NAVIGATION_PERSISTENCE_KEY
);
@@ -181,7 +202,7 @@ export default function App() {
{Platform.OS === 'ios' && (
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
)}
<NavigationNativeContainer
<NavigationContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
@@ -233,34 +254,39 @@ export default function App() {
<ScrollView
style={{ backgroundColor: theme.colors.background }}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 16,
<SettingsItem
label="Right to left"
value={I18nManager.isRTL}
onValueChange={() => {
I18nManager.forceRTL(!I18nManager.isRTL);
// @ts-ignore
if (global.Expo) {
Updates.reloadFromCache();
} else {
RNRestart.Restart();
}
}}
>
<Subheading>Dark theme</Subheading>
<Switch
value={theme.dark}
onValueChange={() => {
AsyncStorage.setItem(
THEME_PERSISTENCE_KEY,
theme.dark ? 'light' : 'dark'
);
/>
<Divider />
<SettingsItem
label="Dark theme"
value={theme.dark}
onValueChange={() => {
AsyncStorage.setItem(
THEME_PERSISTENCE_KEY,
theme.dark ? 'light' : 'dark'
);
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
}}
/>
</View>
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
}}
/>
<Divider />
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
<List.Item
key={name}
title={SCREENS[name].title}
onPress={() => navigation.push(name)}
onPress={() => navigation.navigate(name)}
/>
)
)}
@@ -281,7 +307,7 @@ export default function App() {
)}
</Drawer.Screen>
</Drawer.Navigator>
</NavigationNativeContainer>
</NavigationContainer>
</PaperProvider>
);
}

View File

@@ -10,6 +10,8 @@ const packages = path.resolve(__dirname, '..', 'packages');
module.exports = async function(env, argv) {
const config = await createExpoWebpackConfigAsync(env, argv);
config.context = path.resolve(__dirname, '..');
config.module.rules.push({
test: /\.(js|ts|tsx)$/,
include: /(packages|example)\/.+/,

View File

@@ -64,7 +64,8 @@
],
"moduleNameMapper": {
"@react-navigation/([^/]+)": "<rootDir>/packages/$1/src"
}
},
"preset": "react-native"
},
"prettier": {
"tabWidth": 2,

View File

@@ -3,6 +3,90 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.44](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.43...@react-navigation/bottom-tabs@5.0.0-alpha.44) (2020-02-04)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.43](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.42...@react-navigation/bottom-tabs@5.0.0-alpha.43) (2020-02-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.42](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.39...@react-navigation/bottom-tabs@5.0.0-alpha.42) (2020-02-02)
### Bug Fixes
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
# [5.0.0-alpha.40](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.39...@react-navigation/bottom-tabs@5.0.0-alpha.40) (2020-02-02)
### Bug Fixes
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
# [5.0.0-alpha.39](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.38...@react-navigation/bottom-tabs@5.0.0-alpha.39) (2020-01-24)
### Bug Fixes
* use layout instead of dimensions for determining tab bar layout ([f1fe951](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/f1fe951cf9d602e1b6d4932e3c6c77bbeaaec5c0))
# [5.0.0-alpha.38](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.37...@react-navigation/bottom-tabs@5.0.0-alpha.38) (2020-01-23)
### Bug Fixes
* don't use native driver on web ([0a982ee](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/0a982ee6984b24c0ba053a30223e255f3835e050))
### Features
* let the navigator specify if default can be prevented ([da67e13](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/da67e134d2157201360427d3c10da24f24cae7aa))
# [5.0.0-alpha.37](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.36...@react-navigation/bottom-tabs@5.0.0-alpha.37) (2020-01-14)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.36](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.35...@react-navigation/bottom-tabs@5.0.0-alpha.36) (2020-01-13)
### Bug Fixes
* make sure paths aren't aliased when building definitions ([65a5dac](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/65a5dac2bf887f4ba081ab15bd4c9870bb15697f)), closes [#265](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/issues/265)
# [5.0.0-alpha.35](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.34...@react-navigation/bottom-tabs@5.0.0-alpha.35) (2020-01-13)
**Note:** Version bump only for package @react-navigation/bottom-tabs

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 React Navigation Contributors
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.

View File

@@ -10,7 +10,7 @@
"android",
"tab"
],
"version": "5.0.0-alpha.35",
"version": "5.0.0-alpha.44",
"license": "MIT",
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs",
"main": "lib/commonjs/index.js",
@@ -21,6 +21,7 @@
"src",
"lib"
],
"sideEffects": false,
"publishConfig": {
"access": "public"
},
@@ -29,12 +30,12 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.23",
"@react-navigation/routers": "^5.0.0-alpha.32",
"color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.1",
"@react-native-community/bob": "^0.8.0",
"@types/color": "^3.0.1",
"@types/react": "^16.9.17",
"@types/react-native": "^0.60.30",
@@ -56,7 +57,12 @@
"targets": [
"commonjs",
"module",
"typescript"
[
"typescript",
{
"project": "tsconfig.build.json"
}
]
]
}
}

View File

@@ -17,11 +17,11 @@ export type BottomTabNavigationEventMap = {
/**
* Event which fires on tapping on the tab in the tab bar.
*/
tabPress: undefined;
tabPress: { data: undefined; canPreventDefault: true };
/**
* Event which fires on long press on the tab in the tab bar.
*/
tabLongPress: undefined;
tabLongPress: { data: undefined };
};
export type LabelPosition = 'beside-icon' | 'below-icon';
@@ -99,6 +99,12 @@ export type BottomTabNavigationOptions = {
* Renders `TouchableWithoutFeedback` by default.
*/
tabBarButton?: (props: BottomTabBarButtonProps) => React.ReactNode;
/**
* Whether this screen should be unmounted when navigating away from it.
* Defaults to `false`.
*/
unmountOnBlur?: boolean;
};
export type BottomTabDescriptor = Descriptor<
@@ -118,11 +124,6 @@ export type BottomTabNavigationConfig = {
* Set it to `false` if you want to render all screens on initial render.
*/
lazy?: boolean;
/**
* Whether a screen should be unmounted when navigating away from it.
* Defaults to `false`.
*/
unmountInactiveScreens?: boolean;
/**
* Function that returns a React element to display as the tab bar.
*/
@@ -176,14 +177,9 @@ export type BottomTabBarOptions = {
tabStyle?: StyleProp<ViewStyle>;
/**
* Whether the label is renderd below the icon or beside the icon.
* When a function is passed, it receives the device dimensions to render the label differently.
* By default, in `vertical` orinetation, label is rendered below and in `horizontal` orientation, it's renderd beside.
*/
labelPosition?:
| LabelPosition
| ((options: {
dimensions: { height: number; width: number };
}) => LabelPosition);
labelPosition?: LabelPosition;
/**
* Whether the label position should adapt to the orientation.
*/

View File

@@ -27,6 +27,8 @@ type Props = BottomTabBarProps & {
const DEFAULT_TABBAR_HEIGHT = 50;
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
const useNativeDriver = Platform.OS !== 'web';
export default function BottomTabBar({
state,
navigation,
@@ -48,7 +50,10 @@ export default function BottomTabBar({
const { colors } = useTheme();
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
const [layout, setLayout] = React.useState({ height: 0, width: 0 });
const [layout, setLayout] = React.useState({
height: 0,
width: dimensions.width,
});
const [keyboardShown, setKeyboardShown] = React.useState(false);
const [visible] = React.useState(() => new Animated.Value(0));
@@ -60,7 +65,7 @@ export default function BottomTabBar({
Animated.timing(visible, {
toValue: 0,
duration: 200,
useNativeDriver: true,
useNativeDriver,
}).start();
}
}, [keyboardShown, visible]);
@@ -76,7 +81,7 @@ export default function BottomTabBar({
Animated.timing(visible, {
toValue: 1,
duration: 250,
useNativeDriver: true,
useNativeDriver,
}).start(({ finished }) => {
if (finished) {
setKeyboardShown(false);
@@ -122,27 +127,15 @@ export default function BottomTabBar({
};
const shouldUseHorizontalLabels = () => {
const isLandscape = dimensions.width > dimensions.height;
if (labelPosition) {
let position;
if (typeof labelPosition === 'string') {
position = labelPosition;
} else {
position = labelPosition({ dimensions });
}
if (position) {
return position === 'beside-icon';
}
return labelPosition === 'beside-icon';
}
if (!adaptive) {
return false;
}
if (dimensions.width >= 768) {
if (layout.width >= 768) {
// Screen size matches a tablet
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
@@ -156,8 +149,10 @@ export default function BottomTabBar({
}
}
return routes.length * maxTabItemWidth <= dimensions.width;
return routes.length * maxTabItemWidth <= layout.width;
} else {
const isLandscape = dimensions.width > dimensions.height;
return isLandscape;
}
};
@@ -205,6 +200,7 @@ export default function BottomTabBar({
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!focused && !event.defaultPrevented) {

View File

@@ -92,7 +92,7 @@ export default class BottomTabView extends React.Component<Props, State> {
};
render() {
const { state, descriptors, lazy, unmountInactiveScreens } = this.props;
const { state, descriptors, lazy } = this.props;
const { routes } = state;
const { loaded } = this.state;
@@ -101,17 +101,19 @@ export default class BottomTabView extends React.Component<Props, State> {
<View style={styles.container}>
<ScreenContainer style={styles.pages}>
{routes.map((route, index) => {
if (unmountInactiveScreens && index !== state.index) {
const descriptor = descriptors[route.key];
const { unmountOnBlur } = descriptor.options;
const isFocused = state.index === index;
if (unmountOnBlur && !isFocused) {
return null;
}
if (lazy && !loaded.includes(index)) {
if (lazy && !loaded.includes(index) && !isFocused) {
// Don't render a screen if we've never navigated to it
return null;
}
const isFocused = state.index === index;
return (
<ResourceSavingScene
key={route.key}
@@ -119,7 +121,7 @@ export default class BottomTabView extends React.Component<Props, State> {
isVisible={isFocused}
>
<SceneContent isFocused={isFocused}>
{descriptors[route.key].render()}
{descriptor.render()}
</SceneContent>
</ResourceSavingScene>
);

View File

@@ -0,0 +1,6 @@
{
"extends": "./tsconfig",
"compilerOptions": {
"paths": {}
}
}

View File

@@ -3,6 +3,85 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.33](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.32...@react-navigation/compat@5.0.0-alpha.33) (2020-02-04)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.32](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.31...@react-navigation/compat@5.0.0-alpha.32) (2020-02-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.31](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.31) (2020-02-02)
### Bug Fixes
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.29) (2020-02-02)
### Bug Fixes
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.27...@react-navigation/compat@5.0.0-alpha.28) (2020-01-24)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.26...@react-navigation/compat@5.0.0-alpha.27) (2020-01-23)
### Bug Fixes
* ensure re-render on isFirstRouteInParent change in compat layer ([14ae373](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/14ae3738cf46088e082bd1c60b9dcc6dacacd1bf))
* improvements to the compat layer ([2a76dc4](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/2a76dc4d3c4cc0365a3afcff6ac321145efed026))
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.25...@react-navigation/compat@5.0.0-alpha.26) (2020-01-14)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.24...@react-navigation/compat@5.0.0-alpha.25) (2020-01-13)
### Bug Fixes
* make sure paths aren't aliased when building definitions ([65a5dac](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/65a5dac2bf887f4ba081ab15bd4c9870bb15697f)), closes [#265](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/issues/265)
# [5.0.0-alpha.24](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.23...@react-navigation/compat@5.0.0-alpha.24) (2020-01-13)
**Note:** Version bump only for package @react-navigation/compat

21
packages/compat/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 React Navigation Contributors
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.

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.0.0-alpha.24",
"version": "5.0.0-alpha.33",
"license": "MIT",
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/compat",
"main": "lib/commonjs/index.js",
@@ -12,6 +12,7 @@
"src",
"lib"
],
"sideEffects": false,
"publishConfig": {
"access": "public"
},
@@ -20,7 +21,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.23"
"@react-navigation/routers": "^5.0.0-alpha.32"
},
"devDependencies": {
"@types/react": "^16.9.17",
@@ -37,7 +38,12 @@
"targets": [
"commonjs",
"module",
"typescript"
[
"typescript",
{
"project": "tsconfig.build.json"
}
]
]
}
}

View File

@@ -5,7 +5,7 @@ import {
RouteProp,
} from '@react-navigation/native';
import ScreenPropsContext from './ScreenPropsContext';
import createCompatNavigationProp from './createCompatNavigationProp';
import useCompatNavigation from './useCompatNavigation';
type Props<ParamList extends ParamListBase> = {
navigation: NavigationProp<ParamList>;
@@ -16,12 +16,7 @@ type Props<ParamList extends ParamListBase> = {
function ScreenComponent<ParamList extends ParamListBase>(
props: Props<ParamList>
) {
const navigation = React.useMemo(
() =>
createCompatNavigationProp(props.navigation as any, props.route as any),
[props.navigation, props.route]
);
const navigation = useCompatNavigation();
const screenProps = React.useContext(ScreenPropsContext);
return <props.component navigation={navigation} screenProps={screenProps} />;

View File

@@ -19,7 +19,7 @@ export function replace({
key?: string;
newKey?: string;
action?: never;
}): CommonActions.Action {
}): StackActionType {
if (action !== undefined) {
throw new Error(
'Sub-actions are not supported for `replace`. Remove the `action` key from the options.'

View File

@@ -8,11 +8,17 @@ import {
import * as helpers from './helpers';
import { CompatNavigationProp } from './types';
type EventName = 'willFocus' | 'willBlur' | 'didFocus' | 'didBlur' | 'refocus';
type EventName =
| 'action'
| 'willFocus'
| 'willBlur'
| 'didFocus'
| 'didBlur'
| 'refocus';
const focusSubscriptions = new WeakMap<() => void, () => void>();
const blurSubscriptions = new WeakMap<() => void, () => void>();
const refocusSubscriptions = new WeakMap<() => void, () => void>();
// const focusSubscriptions = new WeakMap<() => void, () => void>();
// const blurSubscriptions = new WeakMap<() => void, () => void>();
// const refocusSubscriptions = new WeakMap<() => void, () => void>();
export default function createCompatNavigationProp<
NavigationPropType extends NavigationProp<ParamListBase>,
@@ -28,8 +34,17 @@ export default function createCompatNavigationProp<
state?: NavigationState | PartialState<NavigationState>;
})
| NavigationState
| PartialState<NavigationState>
| PartialState<NavigationState>,
context: Record<string, any>,
isFirstRouteInParent?: boolean
): CompatNavigationProp<NavigationPropType> {
context.parent = context.parent || {};
context.subscriptions = context.subscriptions || {
didFocus: new Map<() => void, () => void>(),
didBlur: new Map<() => void, () => void>(),
refocus: new Map<() => void, () => void>(),
};
return {
...navigation,
...Object.entries(helpers).reduce<{
@@ -61,7 +76,7 @@ export default function createCompatNavigationProp<
// @ts-ignore
unsubscribe = navigation.addListener('transitionEnd', listener);
focusSubscriptions.set(callback, unsubscribe);
context.subscriptions.didFocus.set(callback, unsubscribe);
break;
}
case 'didBlur': {
@@ -73,7 +88,7 @@ export default function createCompatNavigationProp<
// @ts-ignore
unsubscribe = navigation.addListener('transitionEnd', listener);
blurSubscriptions.set(callback, unsubscribe);
context.subscriptions.didBlur.set(callback, unsubscribe);
break;
}
case 'refocus': {
@@ -85,9 +100,11 @@ export default function createCompatNavigationProp<
// @ts-ignore
unsubscribe = navigation.addListener('tabPress', listener);
refocusSubscriptions.set(callback, unsubscribe);
context.subscriptions.refocus.set(callback, unsubscribe);
break;
}
case 'action':
throw new Error("Listening to 'action' events is not supported.");
default:
// @ts-ignore
unsubscribe = navigation.addListener(type, callback);
@@ -100,6 +117,8 @@ export default function createCompatNavigationProp<
return subscription;
},
removeListener(type: EventName, callback: () => void) {
context.subscriptions = context.subscriptions || {};
switch (type) {
case 'willFocus':
navigation.removeListener('focus', callback);
@@ -108,20 +127,22 @@ export default function createCompatNavigationProp<
navigation.removeListener('blur', callback);
break;
case 'didFocus': {
const unsubscribe = focusSubscriptions.get(callback);
const unsubscribe = context.subscriptions.didFocus.get(callback);
unsubscribe?.();
break;
}
case 'didBlur': {
const unsubscribe = blurSubscriptions.get(callback);
const unsubscribe = context.subscriptions.didBlur.get(callback);
unsubscribe?.();
break;
}
case 'refocus': {
const unsubscribe = refocusSubscriptions.get(callback);
const unsubscribe = context.subscriptions.refocus.get(callback);
unsubscribe?.();
break;
}
case 'action':
throw new Error("Listening to 'action' events is not supported.");
default:
// @ts-ignore
navigation.removeListener(type, callback);
@@ -174,6 +195,10 @@ export default function createCompatNavigationProp<
return defaultValue;
},
isFirstRouteInParent(): boolean {
if (typeof isFirstRouteInParent === 'boolean') {
return isFirstRouteInParent;
}
const { routes } = navigation.dangerouslyGetState();
// @ts-ignore
@@ -185,7 +210,8 @@ export default function createCompatNavigationProp<
if (parent) {
return createCompatNavigationProp(
parent,
navigation.dangerouslyGetState()
navigation.dangerouslyGetState(),
context.parent
);
}

View File

@@ -110,7 +110,7 @@ export default function createCompatNavigatorFactory<
? {
navigation: createCompatNavigationProp<
NavigationPropType
>(navigation, route),
>(navigation, route, {}),
navigationOptions: defaultNavigationOptions || {},
screenProps,
}
@@ -165,7 +165,25 @@ export default function createCompatNavigatorFactory<
return Navigator;
};
createCompatNavigator.isCompat = true;
Object.defineProperties(createCompatNavigator, {
isCompat: {
get() {
return true;
},
},
router: {
get() {
throw new Error(
"It's no longer possible to access the router with the 'router' property."
);
},
set() {
throw new Error(
"It's no longer possible to override the router by assigning the 'router' property."
);
},
},
});
return createCompatNavigator;
}

View File

@@ -4,6 +4,7 @@ import {
useRoute,
NavigationProp,
ParamListBase,
useNavigationState,
} from '@react-navigation/native';
import createCompatNavigationProp from './createCompatNavigationProp';
import { CompatNavigationProp } from './types';
@@ -14,12 +15,20 @@ export default function useCompatNavigation<
const navigation = useNavigation();
const route = useRoute();
const isFirstRouteInParent = useNavigationState(
state => state.routes[0].key === route.key
);
const context = React.useRef<Record<string, any>>({});
return React.useMemo(
() =>
createCompatNavigationProp(
navigation,
route as any
route as any,
context.current,
isFirstRouteInParent
) as CompatNavigationProp<T>,
[navigation, route]
[isFirstRouteInParent, navigation, route]
);
}

View File

@@ -0,0 +1,6 @@
{
"extends": "./tsconfig",
"compilerOptions": {
"paths": {}
}
}

View File

@@ -3,6 +3,122 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.42](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.41...@react-navigation/core@5.0.0-alpha.42) (2020-02-04)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.41](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.40...@react-navigation/core@5.0.0-alpha.41) (2020-02-03)
### Bug Fixes
* ignore circular references when checking serializable ([e5063b9](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/e5063b93398350511f3fd2ef48425559f871781f))
# [5.0.0-alpha.40](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.37...@react-navigation/core@5.0.0-alpha.40) (2020-02-02)
### Bug Fixes
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
* add warning when passing inline function to component prop ([fa4a959](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/fa4a959549ccd9dc2f9bd2ea495e99abdedc9f94))
* tweak error messages for validation ([2243b45](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/2243b45cc1addf83727166d82736d214f181b1fb))
### Features
* add `screens` prop for nested configs ([#308](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/308)) ([b931ae6](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/b931ae62dfb2c5253c94ea5ace73e9070ec17c4a))
* add useIsDrawerOpen hook ([#299](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/299)) ([ecd68af](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/ecd68afb46a4c56200748da5e5fb284fa5a839db))
* integrate with history API on web ([5a3f835](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/5a3f8356b05bff7ed20893a5db6804612da3e568))
# [5.0.0-alpha.38](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.37...@react-navigation/core@5.0.0-alpha.38) (2020-02-02)
### Bug Fixes
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
* add warning when passing inline function to component prop ([fa4a959](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/fa4a959549ccd9dc2f9bd2ea495e99abdedc9f94))
* tweak error messages for validation ([2243b45](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/2243b45cc1addf83727166d82736d214f181b1fb))
### Features
* add `screens` prop for nested configs ([#308](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/308)) ([b931ae6](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/b931ae62dfb2c5253c94ea5ace73e9070ec17c4a))
* add useIsDrawerOpen hook ([#299](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/299)) ([ecd68af](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/ecd68afb46a4c56200748da5e5fb284fa5a839db))
* integrate with history API on web ([5a3f835](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/5a3f8356b05bff7ed20893a5db6804612da3e568))
# [5.0.0-alpha.37](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.36...@react-navigation/core@5.0.0-alpha.37) (2020-01-24)
### Bug Fixes
* add error message when trying to use v4 API with v5 ([179e807](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/179e807a64a7d031d671c2c4b12edaee3c3440c5))
* validate screen configs ([2f1f0af](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/2f1f0af862ef8625da4c2aaf463d45fe17a4ac88))
* warn if non-serializable values found in state ([5751e7f](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/5751e7f97a1731a5c71862174dfd931b6ffe13e2))
# [5.0.0-alpha.36](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.35...@react-navigation/core@5.0.0-alpha.36) (2020-01-23)
### Bug Fixes
* disallow canPreventDefault option if not present in types ([d9059b5](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/d9059b56d8a89b39fec43d38a7b0514d41c0b550))
* don't add ?if query params is empty ([3bf5ddd](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/3bf5ddde2ac1ba45f1123752d37532175f18a3d9))
* fix types for useFocusEffect ([23ab45a](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/23ab45aceb72cc27ebfacdedfbf60d0c540fecfb)), closes [#270](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/270)
* make sure that we return correct value if selector changes ([6c2acbb](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/6c2acbb304a9f11789b45a410b6c41911eca3947)), closes [/github.com/react-navigation/navigation-ex/pull/273#issuecomment-576581225](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/issuecomment-576581225)
* use protected for private value store ([ad4eaff](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/ad4eaff1e99e4f9fca3a193764fd0f26efa41341))
### Features
* add useNavigationState hook ([32a2206](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/32a2206513bc084d8da07187385d11db498f1e2a))
* let the navigator specify if default can be prevented ([da67e13](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/da67e134d2157201360427d3c10da24f24cae7aa))
* support nested config in getPathFromState ([#266](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/266)) ([1e53821](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/1e53821d52be182369add07a86c72221c5dba53e))
# [5.0.0-alpha.35](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.34...@react-navigation/core@5.0.0-alpha.35) (2020-01-14)
### Bug Fixes
* fix intellisense for CompositeNavigationProp ([a912323](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/a912323c1dfa0c3564ca82c448a86f85d1658f7f))
# [5.0.0-alpha.34](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.33...@react-navigation/core@5.0.0-alpha.34) (2020-01-13)
### Bug Fixes
* make sure paths aren't aliased when building definitions ([65a5dac](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/65a5dac2bf887f4ba081ab15bd4c9870bb15697f)), closes [#265](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/265)
# [5.0.0-alpha.33](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.32...@react-navigation/core@5.0.0-alpha.33) (2020-01-13)
**Note:** Version bump only for package @react-navigation/core

21
packages/core/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 React Navigation Contributors
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.

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.33",
"version": "5.0.0-alpha.42",
"license": "MIT",
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/core",
"main": "lib/commonjs/index.js",
@@ -27,14 +27,17 @@
"dependencies": {
"escape-string-regexp": "^2.0.0",
"query-string": "^6.9.0",
"react-is": "^16.12.0",
"shortid": "^2.2.15",
"use-subscription": "^1.3.0"
},
"devDependencies": {
"@babel/core": "^7.7.7",
"@react-native-community/bob": "^0.7.1",
"@react-native-community/bob": "^0.8.0",
"@types/react": "^16.9.17",
"@types/react-is": "^16.7.1",
"@types/shortid": "^0.0.29",
"@types/use-subscription": "^1.0.0",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native-testing-library": "^1.12.0",
@@ -50,7 +53,12 @@
"targets": [
"commonjs",
"module",
"typescript"
[
"typescript",
{
"project": "tsconfig.build.json"
}
]
]
}
}

View File

@@ -0,0 +1,300 @@
import * as React from 'react';
import * as CommonActions from './CommonActions';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import NavigationBuilderContext from './NavigationBuilderContext';
import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters';
import isSerializable from './isSerializable';
import {
Route,
NavigationState,
InitialState,
PartialState,
NavigationAction,
NavigationContainerRef,
NavigationContainerProps,
} from './types';
import useEventEmitter from './useEventEmitter';
type State = NavigationState | PartialState<NavigationState> | undefined;
const MISSING_CONTEXT_ERROR =
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?";
export const NavigationStateContext = React.createContext<{
isDefault?: true;
state?: NavigationState | PartialState<NavigationState>;
getState: () => NavigationState | PartialState<NavigationState> | undefined;
setState: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
key?: string;
performTransaction: (action: () => void) => void;
}>({
isDefault: true,
get getState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get setState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get performTransaction(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
});
let hasWarnedForSerialization = false;
/**
* Remove `key` and `routeNames` from the state objects recursively to get partial state.
*
* @param state Initial state object.
*/
const getPartialState = (
state: InitialState | undefined
): PartialState<NavigationState> | undefined => {
if (state === undefined) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { key, routeNames, ...partialState } = state;
// @ts-ignore
return {
...partialState,
stale: true,
routes: state.routes.map(route => {
if (route.state === undefined) {
return route as Route<string> & {
state?: PartialState<NavigationState>;
};
}
return { ...route, state: getPartialState(route.state) };
}),
};
};
/**
* Container component which holds the navigation state.
* This should be rendered at the root wrapping the whole app.
*
* @param props.initialState Initial state object for the navigation tree.
* @param props.onStateChange Callback which is called with the latest navigation state when it changes.
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const BaseNavigationContainer = React.forwardRef(
function BaseNavigationContainer(
{
initialState,
onStateChange,
independent,
children,
}: NavigationContainerProps,
ref: React.Ref<NavigationContainerRef>
) {
const parent = React.useContext(NavigationStateContext);
if (!parent.isDefault && !independent) {
throw new Error(
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitely."
);
}
const [state, setNavigationState] = React.useState<State>(() =>
getPartialState(initialState == null ? undefined : initialState)
);
const navigationStateRef = React.useRef<State>();
const transactionStateRef = React.useRef<State | null>(null);
const isTransactionActiveRef = React.useRef<boolean>(false);
const isFirstMountRef = React.useRef<boolean>(true);
const skipTrackingRef = React.useRef<boolean>(false);
const performTransaction = React.useCallback((callback: () => void) => {
if (isTransactionActiveRef.current) {
throw new Error(
"Only one transaction can be active at a time. Did you accidentally nest 'performTransaction'?"
);
}
setNavigationState((navigationState: State) => {
isTransactionActiveRef.current = true;
transactionStateRef.current = navigationState;
try {
callback();
} finally {
isTransactionActiveRef.current = false;
}
return transactionStateRef.current;
});
}, []);
const getState = React.useCallback(
() =>
transactionStateRef.current !== null
? transactionStateRef.current
: navigationStateRef.current,
[]
);
const setState = React.useCallback((navigationState: State) => {
if (transactionStateRef.current === null) {
throw new Error(
"Any 'setState' calls need to be done inside 'performTransaction'"
);
}
transactionStateRef.current = navigationState;
}, []);
const reset = React.useCallback(
(state: NavigationState) => {
performTransaction(() => {
skipTrackingRef.current = true;
setState(state);
});
},
[performTransaction, setState]
);
const { trackState, trackAction } = useDevTools({
name: '@react-navigation',
reset,
state,
});
const {
listeners,
addListener: addFocusedListener,
} = useFocusedListeners();
const { getStateForRoute, addStateGetter } = useStateGetters();
const dispatch = (
action: NavigationAction | ((state: NavigationState) => NavigationAction)
) => {
listeners[0](navigation => navigation.dispatch(action));
};
const canGoBack = () => {
const { result, handled } = listeners[0](navigation =>
navigation.canGoBack()
);
if (handled) {
return result;
} else {
return false;
}
};
const resetRoot = React.useCallback(
(state?: PartialState<NavigationState> | NavigationState) => {
performTransaction(() => {
trackAction('@@RESET_ROOT');
setState(state);
});
},
[performTransaction, setState, trackAction]
);
const getRootState = React.useCallback(() => {
return getStateForRoute('root');
}, [getStateForRoute]);
const emitter = useEventEmitter();
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
CommonActions[name](
// @ts-ignore
...args
)
);
return acc;
}, {}),
...emitter.create('root'),
resetRoot,
dispatch,
canGoBack,
getRootState,
}));
const builderContext = React.useMemo(
() => ({
addFocusedListener,
addStateGetter,
trackAction,
}),
[addFocusedListener, trackAction, addStateGetter]
);
const context = React.useMemo(
() => ({
state,
performTransaction,
getState,
setState,
}),
[getState, performTransaction, setState, state]
);
React.useEffect(() => {
if (process.env.NODE_ENV !== 'production') {
if (
state !== undefined &&
!isSerializable(state) &&
!hasWarnedForSerialization
) {
hasWarnedForSerialization = true;
console.warn(
"We found non-serializable values in the navigation state, which can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use functions in your options, you can use 'navigation.setOptions' instead."
);
}
}
emitter.emit({
type: 'state',
data: { state },
});
if (skipTrackingRef.current) {
skipTrackingRef.current = false;
} else {
trackState(getRootState);
}
navigationStateRef.current = state;
transactionStateRef.current = null;
if (!isFirstMountRef.current && onStateChange) {
onStateChange(getRootState());
}
isFirstMountRef.current = false;
}, [state, onStateChange, trackState, getRootState, emitter]);
return (
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
);
}
);
export default BaseNavigationContainer;

View File

@@ -1,4 +1,3 @@
import shortid from 'shortid';
import { CommonAction, NavigationState, PartialState } from './types';
/**
@@ -11,35 +10,6 @@ const BaseRouter = {
action: CommonAction
): State | PartialState<State> | null {
switch (action.type) {
case 'REPLACE': {
const index = action.source
? state.routes.findIndex(r => r.key === action.source)
: state.index;
if (index === -1) {
return null;
}
const { name, key, params } = action.payload;
if (!state.routeNames.includes(name)) {
return null;
}
return {
...state,
routes: state.routes.map((route, i) =>
i === index
? {
key: key !== undefined ? key : `${name}-${shortid()}`,
name,
params,
}
: route
),
};
}
case 'SET_PARAMS': {
const index = action.source
? state.routes.findIndex(r => r.key === action.source)

View File

@@ -14,12 +14,6 @@ export type Action =
source?: string;
target?: string;
}
| {
type: 'REPLACE';
payload: { name: string; key?: string; params?: object };
source?: string;
target?: string;
}
| {
type: 'RESET';
payload: PartialState<NavigationState>;
@@ -59,10 +53,6 @@ export function navigate(...args: any): Action {
}
}
export function replace(name: string, params?: object): Action {
return { type: 'REPLACE', payload: { name, params } };
}
export function reset(state: PartialState<NavigationState>): Action {
return { type: 'RESET', payload: state };
}

View File

@@ -1,272 +0,0 @@
import * as React from 'react';
import * as CommonActions from './CommonActions';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import NavigationBuilderContext from './NavigationBuilderContext';
import ResetRootContext from './ResetRootContext';
import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters';
import {
Route,
NavigationState,
InitialState,
PartialState,
NavigationAction,
NavigationContainerRef,
NavigationContainerProps,
} from './types';
type State = NavigationState | PartialState<NavigationState> | undefined;
const MISSING_CONTEXT_ERROR =
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?";
export const NavigationStateContext = React.createContext<{
isDefault?: true;
state?: NavigationState | PartialState<NavigationState>;
getState: () => NavigationState | PartialState<NavigationState> | undefined;
setState: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
key?: string;
performTransaction: (action: () => void) => void;
}>({
isDefault: true,
get getState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get setState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get performTransaction(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
});
/**
* Remove `key` and `routeNames` from the state objects recursively to get partial state.
*
* @param state Initial state object.
*/
const getPartialState = (
state: InitialState | undefined
): PartialState<NavigationState> | undefined => {
if (state === undefined) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { key, routeNames, ...partialState } = state;
// @ts-ignore
return {
...partialState,
stale: true,
routes: state.routes.map(route => {
if (route.state === undefined) {
return route as Route<string> & {
state?: PartialState<NavigationState>;
};
}
return { ...route, state: getPartialState(route.state) };
}),
};
};
/**
* Container component which holds the navigation state.
* This should be rendered at the root wrapping the whole app.
*
* @param props.initialState Initial state object for the navigation tree.
* @param props.onStateChange Callback which is called with the latest navigation state when it changes.
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const Container = React.forwardRef(function NavigationContainer(
{
initialState,
onStateChange,
independent,
children,
}: NavigationContainerProps,
ref: React.Ref<NavigationContainerRef>
) {
const parent = React.useContext(NavigationStateContext);
if (!parent.isDefault && !independent) {
throw new Error(
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitely."
);
}
const [state, setNavigationState] = React.useState<State>(() =>
getPartialState(initialState == null ? undefined : initialState)
);
const navigationStateRef = React.useRef<State>();
const transactionStateRef = React.useRef<State | null>(null);
const isTransactionActiveRef = React.useRef<boolean>(false);
const isFirstMountRef = React.useRef<boolean>(true);
const skipTrackingRef = React.useRef<boolean>(false);
const performTransaction = React.useCallback((callback: () => void) => {
if (isTransactionActiveRef.current) {
throw new Error(
"Only one transaction can be active at a time. Did you accidentally nest 'performTransaction'?"
);
}
setNavigationState((navigationState: State) => {
isTransactionActiveRef.current = true;
transactionStateRef.current = navigationState;
try {
callback();
} finally {
isTransactionActiveRef.current = false;
}
return transactionStateRef.current;
});
}, []);
const getState = React.useCallback(
() =>
transactionStateRef.current !== null
? transactionStateRef.current
: navigationStateRef.current,
[]
);
const setState = React.useCallback((navigationState: State) => {
if (transactionStateRef.current === null) {
throw new Error(
"Any 'setState' calls need to be done inside 'performTransaction'"
);
}
transactionStateRef.current = navigationState;
}, []);
const reset = React.useCallback(
(state: NavigationState) => {
performTransaction(() => {
skipTrackingRef.current = true;
setState(state);
});
},
[performTransaction, setState]
);
const { trackState, trackAction } = useDevTools({
name: '@react-navigation',
reset,
state,
});
const { listeners, addListener: addFocusedListener } = useFocusedListeners();
const { getStateForRoute, addStateGetter } = useStateGetters();
const dispatch = (
action: NavigationAction | ((state: NavigationState) => NavigationAction)
) => {
listeners[0](navigation => navigation.dispatch(action));
};
const canGoBack = () => {
const { result, handled } = listeners[0](navigation =>
navigation.canGoBack()
);
if (handled) {
return result;
} else {
return false;
}
};
const resetRoot = React.useCallback(
(state?: PartialState<NavigationState> | NavigationState) => {
performTransaction(() => {
trackAction('@@RESET_ROOT');
setState(state);
});
},
[performTransaction, setState, trackAction]
);
const getRootState = React.useCallback(() => {
return getStateForRoute('root');
}, [getStateForRoute]);
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
CommonActions[name](
// @ts-ignore
...args
)
);
return acc;
}, {}),
resetRoot,
dispatch,
canGoBack,
getRootState,
}));
const builderContext = React.useMemo(
() => ({
addFocusedListener,
addStateGetter,
trackAction,
}),
[addFocusedListener, trackAction, addStateGetter]
);
const context = React.useMemo(
() => ({
state,
performTransaction,
getState,
setState,
}),
[getState, performTransaction, setState, state]
);
React.useEffect(() => {
if (skipTrackingRef.current) {
skipTrackingRef.current = false;
} else {
trackState(getRootState);
}
navigationStateRef.current = state;
transactionStateRef.current = null;
if (!isFirstMountRef.current && onStateChange) {
onStateChange(getRootState());
}
isFirstMountRef.current = false;
}, [state, onStateChange, trackState, getRootState]);
return (
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<ResetRootContext.Provider value={resetRoot}>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</ResetRootContext.Provider>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
);
});
export default Container;

View File

@@ -1,15 +0,0 @@
import * as React from 'react';
import { NavigationState, PartialState } from './types';
/**
* Context which holds the method to reset root navigator state.
*/
const ResetRootContext = React.createContext<
(state: PartialState<NavigationState> | NavigationState) => void
>(() => {
throw new Error(
"We couldn't find a way to reset root state. Have you wrapped your app with 'NavigationContainer'?"
);
});
export default ResetRootContext;

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { NavigationStateContext } from './NavigationContainer';
import { NavigationStateContext } from './BaseNavigationContainer';
import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext';
import StaticContainer from './StaticContainer';

View File

@@ -1,8 +1,8 @@
import * as React from 'react';
import { act, render } from 'react-native-testing-library';
import NavigationContainer, {
import BaseNavigationContainer, {
NavigationStateContext,
} from '../NavigationContainer';
} from '../BaseNavigationContainer';
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
import useNavigationBuilder from '../useNavigationBuilder';
import Screen from '../Screen';
@@ -85,9 +85,9 @@ it('throws when setState is called outside performTransaction', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<Test />
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -112,9 +112,9 @@ it('throws when nesting performTransaction', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<Test />
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -125,11 +125,11 @@ it('throws when nesting performTransaction', () => {
it('throws when nesting containers', () => {
expect(() =>
render(
<NavigationContainer>
<NavigationContainer>
<BaseNavigationContainer>
<BaseNavigationContainer>
<React.Fragment />
</NavigationContainer>
</NavigationContainer>
</BaseNavigationContainer>
</BaseNavigationContainer>
)
).toThrowError(
"Looks like you have nested a 'NavigationContainer' inside another."
@@ -137,11 +137,11 @@ it('throws when nesting containers', () => {
expect(() =>
render(
<NavigationContainer>
<NavigationContainer independent>
<BaseNavigationContainer>
<BaseNavigationContainer independent>
<React.Fragment />
</NavigationContainer>
</NavigationContainer>
</BaseNavigationContainer>
</BaseNavigationContainer>
)
).not.toThrowError(
"Looks like you have nested a 'NavigationContainer' inside another."
@@ -223,7 +223,7 @@ it('handle dispatching with ref', () => {
};
const element = (
<NavigationContainer
<BaseNavigationContainer
ref={ref}
initialState={initialState}
onStateChange={onStateChange}
@@ -233,8 +233,8 @@ it('handle dispatching with ref', () => {
<Screen name="foo2">
{() => (
<ChildNavigator>
<Screen name="qux" component={() => null} />
<Screen name="lex" component={() => null} />
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
</ChildNavigator>
)}
</Screen>
@@ -242,21 +242,19 @@ it('handle dispatching with ref', () => {
<Screen name="baz">
{() => (
<ChildNavigator>
<Screen name="qux" component={() => null} />
<Screen name="lex" component={() => null} />
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
</ChildNavigator>
)}
</Screen>
</ParentNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
act(() => {
if (ref.current != null) {
ref.current.dispatch({ type: 'REVERSE' });
}
ref.current?.dispatch({ type: 'REVERSE' });
});
expect(onStateChange).toBeCalledTimes(1);
@@ -303,14 +301,14 @@ it('handle resetting state with ref', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer ref={ref} onStateChange={onStateChange}>
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">{() => null}</Screen>
<Screen name="foo2">
{() => (
<TestNavigator>
<Screen name="qux" component={() => null} />
<Screen name="lex" component={() => null} />
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
</TestNavigator>
)}
</Screen>
@@ -318,13 +316,13 @@ it('handle resetting state with ref', () => {
<Screen name="baz">
{() => (
<TestNavigator>
<Screen name="qux" component={() => null} />
<Screen name="lex" component={() => null} />
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -350,9 +348,7 @@ it('handle resetting state with ref', () => {
};
act(() => {
if (ref.current != null) {
ref.current.resetRoot(state);
}
ref.current?.resetRoot(state);
});
expect(onStateChange).toBeCalledTimes(1);
@@ -383,7 +379,7 @@ it('handle resetting state with ref', () => {
});
});
it('handle getRootState', () => {
it('handles getRootState', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -393,19 +389,19 @@ it('handle getRootState', () => {
const ref = React.createRef<NavigationContainerRef>();
const element = (
<NavigationContainer ref={ref}>
<BaseNavigationContainer ref={ref}>
<TestNavigator initialRouteName="foo">
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="qux" component={() => null} />
<Screen name="lex" component={() => null} />
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
</TestNavigator>
)}
</Screen>
<Screen name="bar" component={() => null} />
<Screen name="bar">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -440,3 +436,68 @@ it('handle getRootState', () => {
type: 'test',
});
});
it('emits state events when the state changes', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return (
<React.Fragment>
{state.routes.map(route => descriptors[route.key].render())}
</React.Fragment>
);
};
const ref = React.createRef<NavigationContainerRef>();
const element = (
<BaseNavigationContainer ref={ref}>
<TestNavigator>
<Screen name="foo">{() => null}</Screen>
<Screen name="bar">{() => null}</Screen>
<Screen name="baz">{() => null}</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
render(element).update(element);
const listener = jest.fn();
ref.current?.addListener('state', listener);
expect(listener).not.toHaveBeenCalled();
act(() => {
ref.current?.navigate('bar');
});
expect(listener.mock.calls[0][0].data.state).toEqual({
type: 'test',
stale: false,
index: 1,
key: '9',
routeNames: ['foo', 'bar', 'baz'],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
{ key: 'baz', name: 'baz' },
],
});
act(() => {
ref.current?.navigate('baz', { answer: 42 });
});
expect(listener.mock.calls[1][0].data.state).toEqual({
type: 'test',
stale: false,
index: 2,
key: '9',
routeNames: ['foo', 'bar', 'baz'],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
{ key: 'baz', name: 'baz', params: { answer: 42 } },
],
});
});

View File

@@ -16,64 +16,6 @@ const STATE = {
routeNames: ['foo', 'bar', 'baz', 'qux'],
};
it('replaces focused screen with REPLACE', () => {
const result = BaseRouter.getStateForAction(
STATE,
CommonActions.replace('qux', { answer: 42 })
);
expect(result).toEqual({
stale: false,
type: 'test',
key: 'root',
index: 1,
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'qux-test', name: 'qux', params: { answer: 42 } },
{ key: 'baz', name: 'baz' },
],
routeNames: ['foo', 'bar', 'baz', 'qux'],
});
});
it('replaces source screen with REPLACE', () => {
const result = BaseRouter.getStateForAction(STATE, {
...CommonActions.replace('qux', { answer: 42 }),
source: 'baz',
});
expect(result).toEqual({
stale: false,
type: 'test',
key: 'root',
index: 1,
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar', params: { fruit: 'orange' } },
{ key: 'qux-test', name: 'qux', params: { answer: 42 } },
],
routeNames: ['foo', 'bar', 'baz', 'qux'],
});
});
it("doesn't handle REPLACE if source key isn't present", () => {
const result = BaseRouter.getStateForAction(STATE, {
...CommonActions.replace('qux', { answer: 42 }),
source: 'magic',
});
expect(result).toBe(null);
});
it("doesn't handle REPLACE if screen to replace with isn't present", () => {
const result = BaseRouter.getStateForAction(
STATE,
CommonActions.replace('nonexistent', { answer: 42 })
);
expect(result).toBe(null);
});
it('sets params for the focused screen with SET_PARAMS', () => {
const result = BaseRouter.getStateForAction(
STATE,

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import Screen from '../Screen';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
@@ -26,7 +26,7 @@ it('throws if NAVIGATE dispatched neither key nor name', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen
name="foo"
@@ -34,7 +34,7 @@ it('throws if NAVIGATE dispatched neither key nor name', () => {
initialParams={{ count: 10 }}
/>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(

View File

@@ -0,0 +1,12 @@
import createNavigatorFactory from '../createNavigatorFactory';
it('throws descriptive error if an argument is passed', () => {
const createDummyNavigator = createNavigatorFactory(() => null);
expect(() => createDummyNavigator()).not.toThrowError();
// @ts-ignore
expect(() => createDummyNavigator({})).toThrowError(
"Creating a navigator doesn't take an argument."
);
});

View File

@@ -1,89 +1,401 @@
import getPathFromState from '../getPathFromState';
import getStateFromPath from '../getStateFromPath';
it('converts state to path string', () => {
expect(
getPathFromState({
routes: [
{
name: 'foo',
state: {
index: 1,
routes: [
{ name: 'boo' },
{
name: 'bar',
params: { fruit: 'apple' },
state: {
routes: [
{
name: 'baz qux',
params: { author: 'jane', valid: true },
},
],
},
const state = {
routes: [
{
name: 'foo',
state: {
index: 1,
routes: [
{ name: 'boo' },
{
name: 'bar',
params: { fruit: 'apple' },
state: {
routes: [
{
name: 'baz qux',
params: { author: 'jane', valid: true },
},
],
},
],
},
},
],
},
],
})
).toMatchInlineSnapshot(`"/foo/bar/baz%20qux?author=jane&valid=true"`);
},
],
};
const path = '/foo/bar/baz%20qux?author=jane&valid=true';
expect(getPathFromState(state)).toBe(path);
expect(getPathFromState(getStateFromPath(path))).toBe(path);
});
it('converts state to path string with config', () => {
expect(
getPathFromState(
{
routes: [
{
name: 'Foo',
state: {
index: 1,
routes: [
{ name: 'boo' },
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet', avaliable: false },
state: {
routes: [
{
name: 'Baz',
params: { author: 'Jane', valid: true, id: 10 },
},
],
},
},
],
},
},
],
const path = '/few/bar/sweet/apple/baz/jane?id=x10&valid=true';
const config = {
Foo: 'few',
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
id: (id: string) => Number(id.replace(/^x/, '')),
valid: Boolean,
},
stringify: {
author: (author: string) => author.toLowerCase(),
id: (id: number) => `x${id}`,
},
},
};
const state = {
routes: [
{
Foo: 'few',
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
stringify: {
author: author => author.toLowerCase(),
id: id => `x${id}`,
},
name: 'Foo',
state: {
index: 1,
routes: [
{ name: 'boo' },
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet', avaliable: false },
state: {
routes: [
{
name: 'Baz',
params: { author: 'Jane', valid: true, id: 10 },
},
],
},
},
],
},
}
)
).toMatchInlineSnapshot(`"/few/bar/sweet/apple/baz/jane?id=x10&valid=true"`);
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles route without param', () => {
expect(
getPathFromState({
routes: [
{
name: 'foo',
state: {
routes: [{ name: 'bar' }],
const path = '/foo/bar';
const state = {
routes: [
{
name: 'foo',
state: {
routes: [{ name: 'bar' }],
},
},
],
};
expect(getPathFromState(state)).toBe(path);
expect(getPathFromState(getStateFromPath(path))).toBe(path);
});
it("doesn't add query param for empty params", () => {
const path = '/foo';
const state = {
routes: [
{
name: 'foo',
params: {},
},
],
};
expect(getPathFromState(state)).toBe(path);
expect(getPathFromState(getStateFromPath(path))).toBe(path);
});
it('handles state with config with nested screens', () => {
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) => author.toLowerCase(),
id: (id: number) => `x${id}`,
unknown: (_: unknown) => 'x',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: '10',
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles state with config with nested screens and unused configs', () => {
const path = '/foe/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
},
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) => author.replace(/^\w/, c => c.toLowerCase()),
unknown: (_: unknown) => 'x',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object with stringify in it', () => {
const path = '/bar/sweet/apple/foe/bis/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
stringify: {
author: (author: string) =>
author.replace(/^\w/, c => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
],
})
).toBe('/foo/bar');
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth', () => {
const path = '/baz';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
Bar: {
path: 'bar',
screens: {
Baz: 'baz',
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth and and path and stringify in roots', () => {
const path = '/baz';
const config = {
Foo: {
path: 'foo/:id',
stringify: {
id: (id: number) => `id=${id}`,
},
screens: {
Foe: 'foe',
Bar: {
path: 'bar/:id',
stringify: {
id: (id: number) => `id=${id}`,
},
parse: {
id: Number,
},
screens: {
Baz: 'baz',
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});

View File

@@ -1,9 +1,13 @@
import getStateFromPath from '../getStateFromPath';
import getPathFromState from '../getPathFromState';
it('returns undefined for invalid path', () => {
expect(getStateFromPath('//')).toBe(undefined);
});
it('converts path string to initial state', () => {
expect(
getStateFromPath('foo/bar/baz%20qux?author=jane%20%26%20co&valid=true')
).toEqual({
const path = 'foo/bar/baz%20qux?author=jane%20%26%20co&valid=true';
const state = {
routes: [
{
name: 'foo',
@@ -24,28 +28,31 @@ it('converts path string to initial state', () => {
},
},
],
});
};
expect(getStateFromPath(path)).toEqual(state);
expect(getStateFromPath(getPathFromState(state))).toEqual(state);
});
it('converts path string to initial state with config', () => {
expect(
getStateFromPath(
'/few/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true',
{
Foo: 'few',
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
}
)
).toEqual({
const path = '/foo/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true';
const config = {
Foo: 'foo',
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) => author.toLowerCase(),
},
},
};
const state = {
routes: [
{
name: 'Foo',
@@ -72,7 +79,12 @@ it('converts path string to initial state with config', () => {
},
},
],
});
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles leading slash when converting', () => {
@@ -112,7 +124,8 @@ it('handles ending slash when converting', () => {
});
it('handles route without param', () => {
expect(getStateFromPath('foo/bar')).toEqual({
const path = 'foo/bar';
const state = {
routes: [
{
name: 'foo',
@@ -121,34 +134,36 @@ it('handles route without param', () => {
},
},
],
});
});
};
it('returns undefined for invalid path', () => {
expect(getStateFromPath('//')).toBe(undefined);
expect(getStateFromPath(path)).toEqual(state);
expect(getStateFromPath(getPathFromState(state))).toEqual(state);
});
it('converts path string to initial state with config with nested screens', () => {
expect(
getStateFromPath(
'/few/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true',
{
Foo: {
Foe: 'few',
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
}
)
).toEqual({
const path = '/foe/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) => author.toLowerCase(),
},
},
};
const state = {
routes: [
{
name: 'Foo',
@@ -182,26 +197,35 @@ it('converts path string to initial state with config with nested screens', () =
},
},
],
});
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('converts path string to initial state with config with nested screens and unused configs', () => {
expect(
getStateFromPath('/few/baz/jane?count=10&answer=42&valid=true', {
Foo: {
Foe: 'few',
it('converts path string to initial state with config with nested screens and unused parse functions', () => {
const path = '/foe/baz/jane?count=10&answer=42&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
id: Boolean,
},
})
).toEqual({
},
};
const state = {
routes: [
{
name: 'Foo',
@@ -227,32 +251,46 @@ it('converts path string to initial state with config with nested screens and un
},
},
],
});
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles parse in nested object with parse in it', () => {
expect(
getStateFromPath(
'/bar/sweet/apple/few/bis/jane?count=10&answer=42&valid=true',
{
Foo: {
Foe: 'few',
},
Bar: 'bar/:type/:fruit',
Baz: {
Bis: {
path: 'bis/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
it('handles nested object with unused configs and with parse in it', () => {
const path = '/bar/sweet/apple/foe/bis/jane?count=10&answer=42&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
stringify: {
author: (author: string) =>
author.replace(/^\w/, c => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
}
)
).toEqual({
},
},
};
const state = {
routes: [
{
name: 'Bar',
@@ -293,21 +331,32 @@ it('handles parse in nested object with parse in it', () => {
},
},
],
});
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles parse in nested object for second route depth', () => {
expect(
getStateFromPath('/baz', {
Foo: {
path: 'foo',
const path = '/baz';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
Bar: {
Baz: 'baz',
path: 'bar',
screens: {
Baz: 'baz',
},
},
},
})
).toEqual({
},
};
const state = {
routes: [
{
name: 'Foo',
@@ -323,28 +372,44 @@ it('handles parse in nested object for second route depth', () => {
},
},
],
});
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles parse in nested object for second route depth and and path and parse in roots', () => {
expect(
getStateFromPath('/baz', {
Foo: {
path: 'foo/:id',
parse: {
id: Number,
},
const path = '/baz';
const config = {
Foo: {
path: 'foo/:id',
parse: {
id: Number,
},
stringify: {
id: (id: number) => `id=${id}`,
},
screens: {
Foe: 'foe',
Bar: {
path: 'bar/:id',
parse: {
id: Number,
},
Baz: 'baz',
stringify: {
id: (id: number) => `id=${id}`,
},
screens: {
Baz: 'baz',
},
},
},
})
).toEqual({
},
};
const state = {
routes: [
{
name: 'Foo',
@@ -360,5 +425,41 @@ it('handles parse in nested object for second route depth and and path and parse
},
},
],
});
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('returns undefined if path is empty', () => {
const config = {
Foo: {
path: 'foo/:id',
starting: true,
stringify: {
id: (id: number) => `id=${id}`,
},
screens: {
Foe: 'foe',
Bar: {
path: 'bar/:id',
parse: {
id: Number,
},
stringify: {
id: (id: number) => `id=${id}`,
},
screens: {
Baz: 'baz',
},
},
},
},
};
const path = '';
expect(getStateFromPath(path, config)).toEqual(undefined);
});

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import Screen from '../Screen';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigation from '../useNavigation';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
@@ -28,7 +28,7 @@ it('initializes state for a navigator on navigation', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen
name="foo"
@@ -44,7 +44,7 @@ it('initializes state for a navigator on navigation', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -75,11 +75,11 @@ it("doesn't crash when initialState is null", () => {
const element = (
// @ts-ignore
<NavigationContainer initialState={null}>
<BaseNavigationContainer initialState={null}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element)).not.toThrowError();
@@ -112,7 +112,7 @@ it('rehydrates state for a navigator on navigation', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer
<BaseNavigationContainer
initialState={initialState}
onStateChange={onStateChange}
>
@@ -120,7 +120,7 @@ it('rehydrates state for a navigator on navigation', () => {
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={BarScreen} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -166,7 +166,7 @@ it("doesn't rehydrate state if the type of state didn't match router", () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer
<BaseNavigationContainer
initialState={initialState}
onStateChange={onStateChange}
>
@@ -178,7 +178,7 @@ it("doesn't rehydrate state if the type of state didn't match router", () => {
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -219,7 +219,7 @@ it('initializes state for nested screens in React.Fragment', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
<React.Fragment>
@@ -227,7 +227,7 @@ it('initializes state for nested screens in React.Fragment', () => {
<Screen name="baz" component={jest.fn()} />
</React.Fragment>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -266,7 +266,7 @@ it('initializes state for nested navigator on navigation', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="baz">
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={jest.fn()} />
@@ -278,7 +278,7 @@ it('initializes state for nested navigator on navigation', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -328,12 +328,12 @@ it("doesn't update state if nothing changed", () => {
const onStateChange = jest.fn();
render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(onStateChange).toBeCalledTimes(0);
@@ -360,12 +360,12 @@ it("doesn't update state if action wasn't handled", () => {
const spy = jest.spyOn(console, 'error').mockImplementation();
render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(onStateChange).toBeCalledTimes(0);
@@ -396,12 +396,12 @@ it('cleans up state when the navigator unmounts', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
const root = render(element);
@@ -422,7 +422,7 @@ it('cleans up state when the navigator unmounts', () => {
});
root.update(
<NavigationContainer onStateChange={onStateChange} children={null} />
<BaseNavigationContainer onStateChange={onStateChange} children={null} />
);
expect(onStateChange).toBeCalledTimes(2);
@@ -454,12 +454,12 @@ it('allows state updates by dispatching a function returning an action', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={BarScreen} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -496,12 +496,12 @@ it('updates route params with setParams', () => {
const onStateChange = jest.fn();
render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
act(() => setParams({ username: 'alice' }));
@@ -556,7 +556,7 @@ it('updates route params with setParams applied to parent', () => {
const onStateChange = jest.fn();
render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo">
{() => (
@@ -567,7 +567,7 @@ it('updates route params with setParams applied to parent', () => {
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
act(() => setParams({ username: 'alice' }));
@@ -634,22 +634,22 @@ it('handles change in route names', () => {
const onStateChange = jest.fn();
const root = render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="bar">
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
root.update(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
<Screen name="baz" component={jest.fn()} />
<Screen name="qux" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(onStateChange).toBeCalledWith({
@@ -677,7 +677,7 @@ it('navigates to nested child in a navigator', () => {
const navigation = React.createRef<NavigationContainerRef>();
const element = render(
<NavigationContainer ref={navigation} onStateChange={onStateChange}>
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">
{() => (
@@ -704,7 +704,7 @@ it('navigates to nested child in a navigator', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
@@ -752,11 +752,11 @@ it('gives access to internal state', () => {
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator initialRouteName="bar">
<Screen name="bar" component={Test} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);
@@ -778,9 +778,9 @@ it("throws if navigator doesn't have any screens", () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator />
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -812,14 +812,14 @@ it('throws if multiple navigators rendered under one container', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -836,12 +836,12 @@ it('throws when Screen is not the direct children', () => {
const Bar = () => null;
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
<Bar />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -856,12 +856,12 @@ it('throws when a React Element is not the direct children', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
Hello world
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -877,7 +877,7 @@ it("doesn't throw when direct children is Screen or empty element", () => {
};
render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
{null}
@@ -885,7 +885,7 @@ it("doesn't throw when direct children is Screen or empty element", () => {
{false}
{true}
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
});
@@ -896,13 +896,13 @@ it('throws when multiple screens with same name are defined', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={jest.fn()} />
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -917,22 +917,157 @@ it('switches rendered navigators', () => {
};
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator key="a">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() =>
root.update(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator key="b">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
)
).not.toThrowError(
'Another navigator is already registered for this container.'
);
});
it('throws if no name is passed to Screen', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const element = (
<BaseNavigationContainer>
<TestNavigator>
<Screen name={undefined as any} component={jest.fn()} />
</TestNavigator>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
'Got an invalid name (undefined) for the screen. It must be a non-empty string.'
);
});
it('throws if invalid name is passed to Screen', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const element = (
<BaseNavigationContainer>
<TestNavigator>
<Screen name={[] as any} component={jest.fn()} />
</TestNavigator>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
'Got an invalid name ([]) for the screen. It must be a non-empty string.'
);
});
it('throws if both children and component are passed', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const element = (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()}>
{jest.fn()}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
"Got both 'component' and 'children' props for the screen 'foo'. You must pass only one of them."
);
});
it('throws descriptive error for undefined screen component', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const element = (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={undefined as any} />
</TestNavigator>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
"Couldn't find a 'component' or 'children' prop for the screen 'foo'"
);
});
it('throws descriptive error for invalid screen component', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const element = (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={{} as any} />
</TestNavigator>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
"Got an invalid value for 'component' prop for the screen 'foo'. It must be a a valid React Component."
);
});
it('throws descriptive error for invalid children', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const element = (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo">{[] as any}</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
"Got an invalid value for 'children' prop for the screen 'foo'. It must be a function returning a React Element."
);
});
it("doesn't throw if children is null", () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const element = (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()}>
{null as any}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).not.toThrowError();
});

View File

@@ -0,0 +1,70 @@
import isSerializable from '../isSerializable';
it('returns true for serializable object', () => {
expect(
isSerializable({
index: 0,
key: '7',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '8',
routeNames: ['qux', 'lex'],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
],
},
},
],
})
).toBe(true);
});
it('returns false for non-serializable object', () => {
expect(
isSerializable({
index: 0,
key: '7',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '8',
routeNames: ['qux', 'lex'],
routes: [
{ key: 'qux', name: 'qux', params: () => 42 },
{ key: 'lex', name: 'lex' },
],
},
},
],
})
).toBe(false);
});
it('returns false for circular references', () => {
const o = {
index: 0,
key: '7',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
},
],
};
// @ts-ignore
o.routes[0].state = o;
expect(isSerializable(o)).toBe(false);
});

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter, {
MockActions,
@@ -34,7 +34,7 @@ it('sets options with options prop as an object', () => {
const TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen
name="foo"
@@ -43,7 +43,7 @@ it('sets options with options prop as an object', () => {
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -79,7 +79,7 @@ it('sets options with options prop as a fuction', () => {
const TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen
name="foo"
@@ -89,7 +89,7 @@ it('sets options with options prop as a fuction', () => {
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -134,12 +134,12 @@ it('sets options with screenOptions prop as an object', () => {
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator screenOptions={{ title: 'Hello world' }}>
<Screen name="foo" component={TestScreenA} />
<Screen name="bar" component={TestScreenB} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -194,7 +194,7 @@ it('sets options with screenOptions prop as a fuction', () => {
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator
screenOptions={({ route }: any) => ({
title: `${route.name}: ${route.params.author || route.params.fruit}`,
@@ -211,7 +211,7 @@ it('sets options with screenOptions prop as a fuction', () => {
initialParams={{ fruit: 'Apple' }}
/>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -266,14 +266,14 @@ it('sets initial options with setOptions', () => {
};
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -331,14 +331,14 @@ it('updates options with setOptions', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
const root = render(element);
@@ -396,7 +396,7 @@ it("returns correct value for canGoBack when it's not overridden", () => {
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen
name="foo"
@@ -405,7 +405,7 @@ it("returns correct value for canGoBack when it's not overridden", () => {
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);
@@ -452,11 +452,11 @@ it(`returns false for canGoBack when current router doesn't handle GO_BACK`, ()
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="baz" component={TestScreen} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);
@@ -513,7 +513,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<ParentNavigator>
<Screen name="baz">
{() => (
@@ -523,7 +523,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
)}
</Screen>
</ParentNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);
@@ -580,7 +580,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="baz">
{() => (
@@ -592,12 +592,12 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
<Screen name="qux">
{() => (
<OverrodeNavigator>
<Screen name="qux" component={() => null} />
<Screen name="qux">{() => null}</Screen>
</OverrodeNavigator>
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { Router, NavigationState } from '../types';
@@ -47,7 +47,7 @@ it('fires focus and blur events in root navigator', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator ref={navigation}>
<Screen
name="first"
@@ -66,7 +66,7 @@ it('fires focus and blur events in root navigator', () => {
component={createComponent(fourthFocusCallback, fourthBlurCallback)}
/>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -139,7 +139,7 @@ it('fires focus and blur events in nested navigator', () => {
const child = React.createRef<any>();
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator ref={parent}>
<Screen
name="first"
@@ -170,7 +170,7 @@ it('fires focus and blur events in nested navigator', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -307,12 +307,12 @@ it('fires blur event when a route is removed with a delay', async () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator ref={navigation}>
<Screen name="first" component={First} />
<Screen name="second" component={Second} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -363,13 +363,13 @@ it('fires custom events', () => {
const ref = React.createRef<any>();
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator ref={ref}>
<Screen name="first" component={createComponent(firstCallback)} />
<Screen name="second" component={createComponent(secondCallback)} />
<Screen name="third" component={createComponent(thirdCallback)} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -391,6 +391,8 @@ it('fires custom events', () => {
expect(thirdCallback).toBeCalledTimes(1);
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
act(() => {
ref.current.navigation.emit({ type: eventName });
@@ -400,3 +402,62 @@ it('fires custom events', () => {
expect(secondCallback).toBeCalledTimes(1);
expect(thirdCallback).toBeCalledTimes(2);
});
it('has option to prevent default', () => {
expect.assertions(5);
const eventName = 'someSuperCoolEvent';
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation, descriptors } = useNavigationBuilder(
MockRouter,
props
);
React.useImperativeHandle(ref, () => ({ navigation, state }), [
navigation,
state,
]);
return state.routes.map(route => descriptors[route.key].render());
});
const callback = (e: any) => {
expect(e.type).toBe('someSuperCoolEvent');
expect(e.data).toBe(42);
expect(e.defaultPrevented).toBe(false);
expect(e.preventDefault).not.toBe(undefined);
e.preventDefault();
expect(e.defaultPrevented).toBe(true);
};
const Test = ({ navigation }: any) => {
React.useEffect(() => navigation.addListener(eventName, callback), [
navigation,
]);
return null;
};
const ref = React.createRef<any>();
const element = (
<BaseNavigationContainer>
<TestNavigator ref={ref}>
<Screen name="first" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
act(() => {
ref.current.navigation.emit({
type: eventName,
data: 42,
canPreventDefault: true,
});
});
});

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useFocusEffect from '../useFocusEffect';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
@@ -31,13 +31,13 @@ it('runs focus effect on focus change', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer ref={navigation}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="second" component={Test} />
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -84,12 +84,12 @@ it('runs focus effect on deps change', () => {
};
const App = ({ count }: { count: number }) => (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="first">{() => <Test count={count} />}</Screen>
<Screen name="second">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
const root = render(<App count={1} />);
@@ -135,13 +135,13 @@ it('runs focus effect when initial state is given', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer ref={navigation} initialState={initialState}>
<BaseNavigationContainer ref={navigation} initialState={initialState}>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="second">{() => null}</Screen>
<Screen name="third" component={Test} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -178,13 +178,13 @@ it('runs focus effect when only focused route is rendered', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer ref={navigation}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first" component={Test} />
<Screen name="second">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -221,12 +221,12 @@ it('runs cleanup when component is unmounted', () => {
const TestB = () => null;
const App = ({ mounted }: { mounted: boolean }) => (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="first" component={mounted ? TestA : TestB} />
<Screen name="second">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
const root = render(<App mounted />);

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useIsFocused from '../useIsFocused';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
@@ -24,13 +24,13 @@ it('renders correct focus state', () => {
const navigation = React.createRef<any>();
const root = render(
<NavigationContainer ref={navigation}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="second" component={Test} />
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`"unfocused"`);

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { render } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigation from '../useNavigation';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
@@ -24,11 +24,11 @@ it('gets navigation prop from context', () => {
};
render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={Test} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
});
@@ -50,7 +50,7 @@ it("gets navigation's parent from context", () => {
};
render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo">
{() => (
@@ -60,7 +60,7 @@ it("gets navigation's parent from context", () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
});
@@ -86,7 +86,7 @@ it("gets navigation's parent's parent from context", () => {
};
render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo">
{() => (
@@ -102,7 +102,7 @@ it("gets navigation's parent's parent from context", () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
});

View File

@@ -0,0 +1,58 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import useEventEmitter from '../useEventEmitter';
import useNavigationCache from '../useNavigationCache';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
beforeEach(() => (MockRouterKey.current = 0));
it('preserves reference for navigation objects', () => {
expect.assertions(2);
const state = {
type: 'tab',
stale: false as const,
index: 1,
key: 'State',
routeNames: ['Foo', 'Bar'],
routes: [
{ key: 'Foo', name: 'Foo' },
{ key: 'Bar', name: 'Bar' },
],
};
const getState = () => state;
const navigation = {} as any;
const setOptions = (() => {}) as any;
const router = MockRouter({});
const Test = () => {
const previous = React.useRef<any>();
const emitter = useEventEmitter();
const navigations = useNavigationCache({
state,
getState,
navigation,
setOptions,
router,
emitter,
});
if (previous.current) {
Object.keys(navigations).forEach(key => {
expect(navigations[key]).toBe(previous.current[key]);
});
}
React.useEffect(() => {
previous.current = navigations;
});
return null;
};
const root = render(<Test />);
root.update(<Test />);
});

View File

@@ -0,0 +1,156 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigationState from '../useNavigationState';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { NavigationState } from '../types';
it('gets the current navigation state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
};
const callback = jest.fn();
const Test = () => {
const state = useNavigationState(state => state);
callback(state);
return null;
};
const navigation = React.createRef<any>();
const element = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first" component={Test} />
<Screen name="second">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
expect(callback).toBeCalledTimes(1);
expect(callback.mock.calls[0][0].index).toBe(0);
act(() => navigation.current.navigate('second'));
expect(callback).toBeCalledTimes(2);
expect(callback.mock.calls[1][0].index).toBe(1);
act(() => navigation.current.navigate('third'));
expect(callback).toBeCalledTimes(3);
expect(callback.mock.calls[2][0].index).toBe(2);
act(() => navigation.current.navigate('second', { answer: 42 }));
expect(callback).toBeCalledTimes(4);
expect(callback.mock.calls[3][0].index).toBe(1);
expect(callback.mock.calls[3][0].routes[1].params).toEqual({ answer: 42 });
});
it('gets the current navigation state with selector', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
};
const callback = jest.fn();
const Test = () => {
const index = useNavigationState(state => state.index);
callback(index);
return null;
};
const navigation = React.createRef<any>();
const element = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first" component={Test} />
<Screen name="second">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
expect(callback).toBeCalledTimes(1);
expect(callback.mock.calls[0][0]).toBe(0);
act(() => navigation.current.navigate('second'));
expect(callback).toBeCalledTimes(2);
expect(callback.mock.calls[1][0]).toBe(1);
act(() => navigation.current.navigate('third'));
expect(callback).toBeCalledTimes(3);
expect(callback.mock.calls[1][0]).toBe(1);
act(() => navigation.current.navigate('second'));
expect(callback).toBeCalledTimes(4);
expect(callback.mock.calls[3][0]).toBe(1);
});
it('gets the correct value if selector changes', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
};
const callback = jest.fn();
const SelectorContext = React.createContext<any>(null);
const Test = () => {
const selector = React.useContext(SelectorContext);
const result = useNavigationState(selector);
callback(result);
return null;
};
const navigation = React.createRef<any>();
const App = ({ selector }: { selector: (state: NavigationState) => any }) => {
return (
<SelectorContext.Provider value={selector}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first" component={Test} />
<Screen name="second">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</BaseNavigationContainer>
</SelectorContext.Provider>
);
};
const root = render(<App selector={state => state.index} />);
expect(callback).toBeCalledTimes(1);
expect(callback.mock.calls[0][0]).toBe(0);
root.update(<App selector={state => state.routes[state.index].name} />);
expect(callback).toBeCalledTimes(2);
expect(callback.mock.calls[1][0]).toBe('first');
});

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