mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-28 20:35:19 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea19ceaa6a | ||
|
|
57ae6e4736 | ||
|
|
858a0d7a53 | ||
|
|
cf36f22e68 | ||
|
|
7385c244b7 |
@@ -9,7 +9,7 @@ import type {
|
|||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Button, ScrollView, StatusBar } from 'react-native';
|
import { Button, ScrollView, StatusBar } from 'react-native';
|
||||||
import { StackNavigator, SafeAreaView } from 'react-navigation';
|
import { StackNavigator, SafeAreaView, withNavigation } from 'react-navigation';
|
||||||
import SampleText from './SampleText';
|
import SampleText from './SampleText';
|
||||||
|
|
||||||
type MyNavScreenProps = {
|
type MyNavScreenProps = {
|
||||||
@@ -17,6 +17,20 @@ type MyNavScreenProps = {
|
|||||||
banner: React.Node,
|
banner: React.Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MyBackButton extends React.Component<any, any> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Button onPress={this._navigateBack} title="Custom Back" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_navigateBack = () => {
|
||||||
|
this.props.navigation.goBack(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MyBackButtonWithNavigation = withNavigation(MyBackButton);
|
||||||
|
|
||||||
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||||
render() {
|
render() {
|
||||||
const { navigation, banner } = this.props;
|
const { navigation, banner } = this.props;
|
||||||
@@ -94,6 +108,7 @@ type MyPhotosScreenProps = {
|
|||||||
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
|
||||||
static navigationOptions = {
|
static navigationOptions = {
|
||||||
title: 'Photos',
|
title: 'Photos',
|
||||||
|
headerLeft: <MyBackButtonWithNavigation />
|
||||||
};
|
};
|
||||||
_s0: NavigationEventSubscription;
|
_s0: NavigationEventSubscription;
|
||||||
_s1: NavigationEventSubscription;
|
_s1: NavigationEventSubscription;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "react-navigation",
|
"name": "react-navigation",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "Routing and navigation for your React Native apps",
|
"description": "Routing and navigation for your React Native apps",
|
||||||
"main": "src/react-navigation.js",
|
"main": "src/react-navigation.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"path-to-regexp": "^1.7.0",
|
"path-to-regexp": "^1.7.0",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"react-native-drawer-layout-polyfill": "^1.3.2",
|
"react-native-drawer-layout-polyfill": "^1.3.2",
|
||||||
|
"react-native-safe-area-view": "^0.6.0",
|
||||||
"react-native-tab-view": "^0.0.74"
|
"react-native-tab-view": "^0.0.74"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ const navigate = createAction(NAVIGATE, payload => {
|
|||||||
if (payload.key) {
|
if (payload.key) {
|
||||||
action.key = payload.key;
|
action.key = payload.key;
|
||||||
}
|
}
|
||||||
|
if (payload.immediate) {
|
||||||
|
action.immediate = payload.immediate;
|
||||||
|
}
|
||||||
return action;
|
return action;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,6 +73,9 @@ const push = createAction(PUSH, payload => {
|
|||||||
if (payload.action) {
|
if (payload.action) {
|
||||||
action.action = payload.action;
|
action.action = payload.action;
|
||||||
}
|
}
|
||||||
|
if (payload.immediate) {
|
||||||
|
action.immediate = payload.immediate;
|
||||||
|
}
|
||||||
return action;
|
return action;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ describe('NavigationContainer', () => {
|
|||||||
// First dispatch
|
// First dispatch
|
||||||
expect(
|
expect(
|
||||||
navigationContainer.dispatch(
|
navigationContainer.dispatch(
|
||||||
NavigationActions.navigate({ routeName: 'bar' })
|
NavigationActions.navigate({ routeName: 'bar', immediate: true })
|
||||||
)
|
)
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ describe('NavigationContainer', () => {
|
|||||||
// Second dispatch
|
// Second dispatch
|
||||||
expect(
|
expect(
|
||||||
navigationContainer.dispatch(
|
navigationContainer.dispatch(
|
||||||
NavigationActions.navigate({ routeName: 'baz' })
|
NavigationActions.navigate({ routeName: 'baz', immediate: true })
|
||||||
)
|
)
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ describe('NavigationContainer', () => {
|
|||||||
// First dispatch
|
// First dispatch
|
||||||
expect(
|
expect(
|
||||||
navigationContainer.dispatch(
|
navigationContainer.dispatch(
|
||||||
NavigationActions.navigate({ routeName: 'bar' })
|
NavigationActions.navigate({ routeName: 'bar', immediate: true })
|
||||||
)
|
)
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
|
|
||||||
@@ -157,28 +157,28 @@ describe('NavigationContainer', () => {
|
|||||||
// Second dispatch
|
// Second dispatch
|
||||||
expect(
|
expect(
|
||||||
navigationContainer.dispatch(
|
navigationContainer.dispatch(
|
||||||
NavigationActions.navigate({ routeName: 'baz' })
|
NavigationActions.navigate({ routeName: 'baz', immediate: true })
|
||||||
)
|
)
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
|
|
||||||
// Third dispatch
|
// Third dispatch
|
||||||
expect(
|
expect(
|
||||||
navigationContainer.dispatch(
|
navigationContainer.dispatch(
|
||||||
NavigationActions.navigate({ routeName: 'car' })
|
NavigationActions.navigate({ routeName: 'car', immediate: true })
|
||||||
)
|
)
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
|
|
||||||
// Fourth dispatch
|
// Fourth dispatch
|
||||||
expect(
|
expect(
|
||||||
navigationContainer.dispatch(
|
navigationContainer.dispatch(
|
||||||
NavigationActions.navigate({ routeName: 'dog' })
|
NavigationActions.navigate({ routeName: 'dog', immediate: true })
|
||||||
)
|
)
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
|
|
||||||
// Fifth dispatch
|
// Fifth dispatch
|
||||||
expect(
|
expect(
|
||||||
navigationContainer.dispatch(
|
navigationContainer.dispatch(
|
||||||
NavigationActions.navigate({ routeName: 'elk' })
|
NavigationActions.navigate({ routeName: 'elk', immediate: true })
|
||||||
)
|
)
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Dimensions, Platform, ScrollView } from 'react-native';
|
import { Dimensions, Platform, ScrollView } from 'react-native';
|
||||||
|
import SafeAreaView from 'react-native-safe-area-view';
|
||||||
|
|
||||||
import createNavigator from './createNavigator';
|
import createNavigator from './createNavigator';
|
||||||
import createNavigationContainer from '../createNavigationContainer';
|
import createNavigationContainer from '../createNavigationContainer';
|
||||||
@@ -7,7 +8,6 @@ import TabRouter from '../routers/TabRouter';
|
|||||||
import DrawerScreen from '../views/Drawer/DrawerScreen';
|
import DrawerScreen from '../views/Drawer/DrawerScreen';
|
||||||
import DrawerView from '../views/Drawer/DrawerView';
|
import DrawerView from '../views/Drawer/DrawerView';
|
||||||
import DrawerItems from '../views/Drawer/DrawerNavigatorItems';
|
import DrawerItems from '../views/Drawer/DrawerNavigatorItems';
|
||||||
import SafeAreaView from '../views/SafeAreaView';
|
|
||||||
|
|
||||||
// A stack navigators props are the intersection between
|
// A stack navigators props are the intersection between
|
||||||
// the base navigator props (navgiation, screenProps, etc)
|
// the base navigator props (navgiation, screenProps, etc)
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ exports[`DrawerNavigator renders successfully 1`] = `
|
|||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
onLayout={[Function]}
|
onLayout={[Function]}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"paddingBottom": 0,
|
"paddingBottom": 0,
|
||||||
@@ -187,6 +188,7 @@ exports[`DrawerNavigator renders successfully 1`] = `
|
|||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
onLayout={[Function]}
|
onLayout={[Function]}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "rgba(0, 0, 0, .04)",
|
"backgroundColor": "rgba(0, 0, 0, .04)",
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
|||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
onLayout={[Function]}
|
onLayout={[Function]}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#F7F7F7",
|
"backgroundColor": "#F7F7F7",
|
||||||
@@ -371,6 +372,7 @@ exports[`StackNavigator renders successfully 1`] = `
|
|||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
onLayout={[Function]}
|
onLayout={[Function]}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#F7F7F7",
|
"backgroundColor": "#F7F7F7",
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ exports[`TabNavigator renders successfully 1`] = `
|
|||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
onLayout={[Function]}
|
onLayout={[Function]}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#F7F7F7",
|
"backgroundColor": "#F7F7F7",
|
||||||
|
|||||||
2
src/react-navigation.js
vendored
2
src/react-navigation.js
vendored
@@ -51,7 +51,7 @@ module.exports = {
|
|||||||
return require('./views/CardStack/Card').default;
|
return require('./views/CardStack/Card').default;
|
||||||
},
|
},
|
||||||
get SafeAreaView() {
|
get SafeAreaView() {
|
||||||
return require('./views/SafeAreaView').default;
|
return require('react-native-safe-area-view').default;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
|
|||||||
@@ -148,6 +148,17 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.type === NavigationActions.COMPLETE_TRANSITION &&
|
||||||
|
(action.key == null || action.key === state.key) &&
|
||||||
|
state.isTransitioning
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isTransitioning: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Check if a child scene wants to handle the action as long as it is not a reset to the root stack
|
// Check if a child scene wants to handle the action as long as it is not a reset to the root stack
|
||||||
if (action.type !== NavigationActions.RESET || action.key !== null) {
|
if (action.type !== NavigationActions.RESET || action.key !== null) {
|
||||||
const keyIndex = action.key
|
const keyIndex = action.key
|
||||||
@@ -218,6 +229,10 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
behavesLikePushAction(action) &&
|
behavesLikePushAction(action) &&
|
||||||
childRouters[action.routeName] !== undefined
|
childRouters[action.routeName] !== undefined
|
||||||
) {
|
) {
|
||||||
|
if (state.isTransitioning) {
|
||||||
|
return { ...state };
|
||||||
|
}
|
||||||
|
|
||||||
const childRouter = childRouters[action.routeName];
|
const childRouter = childRouters[action.routeName];
|
||||||
let route;
|
let route;
|
||||||
|
|
||||||
@@ -290,17 +305,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
action.type === NavigationActions.COMPLETE_TRANSITION &&
|
|
||||||
(action.key == null || action.key === state.key) &&
|
|
||||||
state.isTransitioning
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle navigation to other child routers that are not yet pushed
|
// Handle navigation to other child routers that are not yet pushed
|
||||||
if (behavesLikePushAction(action)) {
|
if (behavesLikePushAction(action)) {
|
||||||
const childRouterNames = Object.keys(childRouters);
|
const childRouterNames = Object.keys(childRouters);
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ import NavigationActions from '../NavigationActions';
|
|||||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||||
import getScreenConfigDeprecated from './getScreenConfigDeprecated';
|
import getScreenConfigDeprecated from './getScreenConfigDeprecated';
|
||||||
|
|
||||||
|
function childrenUpdateWithoutSwitchingIndex(actionType) {
|
||||||
|
return [
|
||||||
|
NavigationActions.SET_PARAMS,
|
||||||
|
NavigationActions.COMPLETE_TRANSITION,
|
||||||
|
].includes(actionType);
|
||||||
|
}
|
||||||
|
|
||||||
export default (routeConfigs, config = {}) => {
|
export default (routeConfigs, config = {}) => {
|
||||||
// Fail fast on invalid route definitions
|
// Fail fast on invalid route definitions
|
||||||
validateRouteConfigMap(routeConfigs);
|
validateRouteConfigMap(routeConfigs);
|
||||||
@@ -213,9 +220,13 @@ export default (routeConfigs, config = {}) => {
|
|||||||
});
|
});
|
||||||
// console.log(`${order.join('-')}: Processed other tabs:`, {lastIndex: state.index, index});
|
// console.log(`${order.join('-')}: Processed other tabs:`, {lastIndex: state.index, index});
|
||||||
|
|
||||||
// keep active tab index if action type is SET_PARAMS
|
// Nested routers can be updated after switching tabs with actions such as SET_PARAMS
|
||||||
index =
|
// and COMPLETE_TRANSITION.
|
||||||
action.type === NavigationActions.SET_PARAMS ? state.index : index;
|
// NOTE: This may be problematic with custom routers because we whitelist the actions
|
||||||
|
// that can be handled by child routers without automatically changing index.
|
||||||
|
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
|
||||||
|
index = state.index;
|
||||||
|
}
|
||||||
|
|
||||||
if (index !== state.index || routes !== state.routes) {
|
if (index !== state.index || routes !== state.routes) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import TabRouter from '../TabRouter';
|
|||||||
import NavigationActions from '../../NavigationActions';
|
import NavigationActions from '../../NavigationActions';
|
||||||
import addNavigationHelpers from '../../addNavigationHelpers';
|
import addNavigationHelpers from '../../addNavigationHelpers';
|
||||||
|
|
||||||
|
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
_TESTING_ONLY_normalize_keys();
|
||||||
|
});
|
||||||
|
|
||||||
const ROUTERS = {
|
const ROUTERS = {
|
||||||
TabRouter,
|
TabRouter,
|
||||||
StackRouter,
|
StackRouter,
|
||||||
@@ -105,8 +111,8 @@ test('Handles no-op actions with tabs within stack router', () => {
|
|||||||
type: NavigationActions.NAVIGATE,
|
type: NavigationActions.NAVIGATE,
|
||||||
routeName: 'Qux',
|
routeName: 'Qux',
|
||||||
});
|
});
|
||||||
expect(state1.routes[0].key).toEqual('Init-id-0-0');
|
expect(state1.routes[0].key).toEqual('Init-id-0');
|
||||||
expect(state2.routes[0].key).toEqual('Init-id-0-1');
|
expect(state2.routes[0].key).toEqual('Init-id-1');
|
||||||
state1.routes[0].key = state2.routes[0].key;
|
state1.routes[0].key = state2.routes[0].key;
|
||||||
expect(state1).toEqual(state2);
|
expect(state1).toEqual(state2);
|
||||||
const state3 = TestRouter.getStateForAction(
|
const state3 = TestRouter.getStateForAction(
|
||||||
@@ -134,7 +140,7 @@ test('Handles deep action', () => {
|
|||||||
key: 'StackRouterRoot',
|
key: 'StackRouterRoot',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
key: 'Init-id-0-2',
|
key: 'Init-id-0',
|
||||||
routeName: 'Bar',
|
routeName: 'Bar',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -174,8 +180,8 @@ test('Supports lazily-evaluated getScreen', () => {
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
routeName: 'Qux',
|
routeName: 'Qux',
|
||||||
});
|
});
|
||||||
expect(state1.routes[0].key).toEqual('Init-id-0-4');
|
expect(state1.routes[0].key).toEqual('Init-id-0');
|
||||||
expect(state2.routes[0].key).toEqual('Init-id-0-5');
|
expect(state2.routes[0].key).toEqual('Init-id-1');
|
||||||
state1.routes[0].key = state2.routes[0].key;
|
state1.routes[0].key = state2.routes[0].key;
|
||||||
expect(state1).toEqual(state2);
|
expect(state1).toEqual(state2);
|
||||||
const state3 = TestRouter.getStateForAction(
|
const state3 = TestRouter.getStateForAction(
|
||||||
@@ -188,3 +194,60 @@ test('Supports lazily-evaluated getScreen', () => {
|
|||||||
);
|
);
|
||||||
expect(state2).toEqual(state3);
|
expect(state2).toEqual(state3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Does not switch tab index when TabRouter child handles COMPLETE_NAVIGATION or SET_PARAMS', () => {
|
||||||
|
const FooStackNavigator = () => <div />;
|
||||||
|
const BarView = () => <div />;
|
||||||
|
FooStackNavigator.router = StackRouter({
|
||||||
|
Foo: {
|
||||||
|
screen: BarView,
|
||||||
|
},
|
||||||
|
Bar: {
|
||||||
|
screen: BarView,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const TestRouter = TabRouter({
|
||||||
|
Zap: { screen: FooStackNavigator },
|
||||||
|
Zoo: { screen: FooStackNavigator },
|
||||||
|
});
|
||||||
|
|
||||||
|
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
||||||
|
|
||||||
|
// Navigate to the second screen in the first tab
|
||||||
|
const state2 = TestRouter.getStateForAction(
|
||||||
|
{
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'Bar',
|
||||||
|
},
|
||||||
|
state1
|
||||||
|
);
|
||||||
|
|
||||||
|
// Switch tabs
|
||||||
|
const state3 = TestRouter.getStateForAction(
|
||||||
|
{
|
||||||
|
type: NavigationActions.NAVIGATE,
|
||||||
|
routeName: 'Zoo',
|
||||||
|
},
|
||||||
|
state2
|
||||||
|
);
|
||||||
|
|
||||||
|
const stateAfterCompleteTransition = TestRouter.getStateForAction(
|
||||||
|
{
|
||||||
|
type: NavigationActions.COMPLETE_TRANSITION,
|
||||||
|
key: state2.routes[0].key,
|
||||||
|
},
|
||||||
|
state3
|
||||||
|
);
|
||||||
|
const stateAfterSetParams = TestRouter.getStateForAction(
|
||||||
|
{
|
||||||
|
type: NavigationActions.SET_PARAMS,
|
||||||
|
key: state1.routes[0].routes[0].key,
|
||||||
|
params: { key: 'value' },
|
||||||
|
},
|
||||||
|
state3
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(stateAfterCompleteTransition.index).toEqual(1);
|
||||||
|
expect(stateAfterSetParams.index).toEqual(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -493,6 +493,23 @@ describe('StackRouter', () => {
|
|||||||
expect(poppedImmediatelyState.isTransitioning).toBe(false);
|
expect(poppedImmediatelyState.isTransitioning).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Navigate does not happen while transitioning', () => {
|
||||||
|
const TestRouter = StackRouter({
|
||||||
|
foo: { screen: () => <div /> },
|
||||||
|
bar: { screen: () => <div /> },
|
||||||
|
});
|
||||||
|
const initState = {
|
||||||
|
...TestRouter.getStateForAction(NavigationActions.init()),
|
||||||
|
isTransitioning: true,
|
||||||
|
};
|
||||||
|
const pushedState = TestRouter.getStateForAction(
|
||||||
|
NavigationActions.navigate({ routeName: 'bar' }),
|
||||||
|
initState
|
||||||
|
);
|
||||||
|
expect(pushedState.index).toEqual(0);
|
||||||
|
expect(pushedState.routes.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('Navigate Pushes duplicate routeName', () => {
|
test('Navigate Pushes duplicate routeName', () => {
|
||||||
const TestRouter = StackRouter({
|
const TestRouter = StackRouter({
|
||||||
foo: { screen: () => <div /> },
|
foo: { screen: () => <div /> },
|
||||||
@@ -500,13 +517,13 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
||||||
const pushedState = TestRouter.getStateForAction(
|
const pushedState = TestRouter.getStateForAction(
|
||||||
NavigationActions.navigate({ routeName: 'bar' }),
|
NavigationActions.navigate({ routeName: 'bar', immediate: true }),
|
||||||
initState
|
initState
|
||||||
);
|
);
|
||||||
expect(pushedState.index).toEqual(1);
|
expect(pushedState.index).toEqual(1);
|
||||||
expect(pushedState.routes[1].routeName).toEqual('bar');
|
expect(pushedState.routes[1].routeName).toEqual('bar');
|
||||||
const pushedTwiceState = TestRouter.getStateForAction(
|
const pushedTwiceState = TestRouter.getStateForAction(
|
||||||
NavigationActions.navigate({ routeName: 'bar' }),
|
NavigationActions.navigate({ routeName: 'bar', immediate: true }),
|
||||||
pushedState
|
pushedState
|
||||||
);
|
);
|
||||||
expect(pushedTwiceState.index).toEqual(2);
|
expect(pushedTwiceState.index).toEqual(2);
|
||||||
@@ -540,7 +557,7 @@ describe('StackRouter', () => {
|
|||||||
});
|
});
|
||||||
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
const initState = TestRouter.getStateForAction(NavigationActions.init());
|
||||||
const pushedState = TestRouter.getStateForAction(
|
const pushedState = TestRouter.getStateForAction(
|
||||||
NavigationActions.push({ routeName: 'bar' }),
|
NavigationActions.push({ routeName: 'bar', immediate: true }),
|
||||||
initState
|
initState
|
||||||
);
|
);
|
||||||
expect(pushedState.index).toEqual(1);
|
expect(pushedState.index).toEqual(1);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, Platform, StyleSheet } from 'react-native';
|
import { View, Text, Platform, StyleSheet } from 'react-native';
|
||||||
|
import SafeAreaView from 'react-native-safe-area-view';
|
||||||
|
|
||||||
import SafeAreaView from '../SafeAreaView';
|
|
||||||
import TouchableItem from '../TouchableItem';
|
import TouchableItem from '../TouchableItem';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
import SafeAreaView from 'react-native-safe-area-view';
|
||||||
|
|
||||||
import withCachedChildNavigation from '../../withCachedChildNavigation';
|
import withCachedChildNavigation from '../../withCachedChildNavigation';
|
||||||
import NavigationActions from '../../NavigationActions';
|
import NavigationActions from '../../NavigationActions';
|
||||||
import invariant from '../../utils/invariant';
|
import invariant from '../../utils/invariant';
|
||||||
|
|
||||||
import SafeAreaView from '../SafeAreaView';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders the sidebar screen of the drawer.
|
* Component that renders the sidebar screen of the drawer.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import {
|
|||||||
View,
|
View,
|
||||||
ViewPropTypes,
|
ViewPropTypes,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import SafeAreaView from 'react-native-safe-area-view';
|
||||||
|
|
||||||
import HeaderTitle from './HeaderTitle';
|
import HeaderTitle from './HeaderTitle';
|
||||||
import HeaderBackButton from './HeaderBackButton';
|
import HeaderBackButton from './HeaderBackButton';
|
||||||
import HeaderStyleInterpolator from './HeaderStyleInterpolator';
|
import HeaderStyleInterpolator from './HeaderStyleInterpolator';
|
||||||
import SafeAreaView from '../SafeAreaView';
|
|
||||||
import withOrientation from '../withOrientation';
|
import withOrientation from '../withOrientation';
|
||||||
|
|
||||||
const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56;
|
const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56;
|
||||||
|
|||||||
@@ -1,316 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import {
|
|
||||||
DeviceInfo,
|
|
||||||
Dimensions,
|
|
||||||
InteractionManager,
|
|
||||||
NativeModules,
|
|
||||||
Platform,
|
|
||||||
StyleSheet,
|
|
||||||
Animated,
|
|
||||||
} from 'react-native';
|
|
||||||
import withOrientation from './withOrientation';
|
|
||||||
|
|
||||||
// See https://mydevice.io/devices/ for device dimensions
|
|
||||||
const X_WIDTH = 375;
|
|
||||||
const X_HEIGHT = 812;
|
|
||||||
const PAD_WIDTH = 768;
|
|
||||||
const PAD_HEIGHT = 1024;
|
|
||||||
|
|
||||||
const { height: D_HEIGHT, width: D_WIDTH } = Dimensions.get('window');
|
|
||||||
|
|
||||||
const { PlatformConstants = {} } = NativeModules;
|
|
||||||
const { minor = 0 } = PlatformConstants.reactNativeVersion || {};
|
|
||||||
|
|
||||||
const isIPhoneX = (() => {
|
|
||||||
if (Platform.OS === 'web') return false;
|
|
||||||
|
|
||||||
if (minor >= 50) {
|
|
||||||
return DeviceInfo.isIPhoneX_deprecated;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
Platform.OS === 'ios' &&
|
|
||||||
((D_HEIGHT === X_HEIGHT && D_WIDTH === X_WIDTH) ||
|
|
||||||
(D_HEIGHT === X_WIDTH && D_WIDTH === X_HEIGHT))
|
|
||||||
);
|
|
||||||
})();
|
|
||||||
|
|
||||||
const isIPad = (() => {
|
|
||||||
if (Platform.OS !== 'ios' || isIPhoneX) return false;
|
|
||||||
|
|
||||||
// if portrait and width is smaller than iPad width
|
|
||||||
if (D_HEIGHT > D_WIDTH && D_WIDTH < PAD_WIDTH) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if landscape and height is smaller that iPad height
|
|
||||||
if (D_WIDTH > D_HEIGHT && D_HEIGHT < PAD_WIDTH) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})();
|
|
||||||
|
|
||||||
let _customStatusBarHeight = null;
|
|
||||||
const statusBarHeight = isLandscape => {
|
|
||||||
if (_customStatusBarHeight !== null) {
|
|
||||||
return _customStatusBarHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a temporary workaround because we don't have a way to detect
|
|
||||||
* if the status bar is translucent or opaque. If opaque, we don't need to
|
|
||||||
* factor in the height here; if translucent (content renders under it) then
|
|
||||||
* we do.
|
|
||||||
*/
|
|
||||||
if (Platform.OS === 'android') {
|
|
||||||
if (global.Expo) {
|
|
||||||
return global.Expo.Constants.statusBarHeight;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIPhoneX) {
|
|
||||||
return isLandscape ? 0 : 44;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIPad) {
|
|
||||||
return 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isLandscape ? 0 : 20;
|
|
||||||
};
|
|
||||||
|
|
||||||
const doubleFromPercentString = percent => {
|
|
||||||
if (!percent.includes('%')) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dbl = parseFloat(percent) / 100;
|
|
||||||
|
|
||||||
if (isNaN(dbl)) return 0;
|
|
||||||
|
|
||||||
return dbl;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SafeView extends Component {
|
|
||||||
static setStatusBarHeight = height => {
|
|
||||||
_customStatusBarHeight = height;
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
touchesTop: true,
|
|
||||||
touchesBottom: true,
|
|
||||||
touchesLeft: true,
|
|
||||||
touchesRight: true,
|
|
||||||
orientation: null,
|
|
||||||
viewWidth: 0,
|
|
||||||
viewHeight: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
InteractionManager.runAfterInteractions(() => {
|
|
||||||
this._onLayout();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps() {
|
|
||||||
this._onLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { forceInset = false, isLandscape, children, style } = this.props;
|
|
||||||
|
|
||||||
const safeAreaStyle = this._getSafeAreaStyle();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Animated.View
|
|
||||||
ref={c => (this.view = c)}
|
|
||||||
onLayout={this._onLayout}
|
|
||||||
style={safeAreaStyle}
|
|
||||||
>
|
|
||||||
{this.props.children}
|
|
||||||
</Animated.View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onLayout = () => {
|
|
||||||
if (!this.view) return;
|
|
||||||
|
|
||||||
const { isLandscape } = this.props;
|
|
||||||
const { orientation } = this.state;
|
|
||||||
const newOrientation = isLandscape ? 'landscape' : 'portrait';
|
|
||||||
if (orientation && orientation === newOrientation) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WIDTH = isLandscape ? X_HEIGHT : X_WIDTH;
|
|
||||||
const HEIGHT = isLandscape ? X_WIDTH : X_HEIGHT;
|
|
||||||
|
|
||||||
this.view._component.measureInWindow((winX, winY, winWidth, winHeight) => {
|
|
||||||
let realY = winY;
|
|
||||||
let realX = winX;
|
|
||||||
|
|
||||||
if (realY >= HEIGHT) {
|
|
||||||
realY = realY % HEIGHT;
|
|
||||||
} else if (realY < 0) {
|
|
||||||
realY = realY % HEIGHT + HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (realX >= WIDTH) {
|
|
||||||
realX = realX % WIDTH;
|
|
||||||
} else if (realX < 0) {
|
|
||||||
realX = realX % WIDTH + WIDTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
const touchesTop = realY === 0;
|
|
||||||
const touchesBottom = realY + winHeight >= HEIGHT;
|
|
||||||
const touchesLeft = realX === 0;
|
|
||||||
const touchesRight = realX + winWidth >= WIDTH;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
touchesTop,
|
|
||||||
touchesBottom,
|
|
||||||
touchesLeft,
|
|
||||||
touchesRight,
|
|
||||||
orientation: newOrientation,
|
|
||||||
viewWidth: winWidth,
|
|
||||||
viewHeight: winHeight,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
_getSafeAreaStyle = () => {
|
|
||||||
const { touchesTop, touchesBottom, touchesLeft, touchesRight } = this.state;
|
|
||||||
const { forceInset, isLandscape } = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
paddingTop,
|
|
||||||
paddingBottom,
|
|
||||||
paddingLeft,
|
|
||||||
paddingRight,
|
|
||||||
viewStyle,
|
|
||||||
} = this._getViewStyles();
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
...viewStyle,
|
|
||||||
paddingTop: touchesTop ? this._getInset('top') : 0,
|
|
||||||
paddingBottom: touchesBottom ? this._getInset('bottom') : 0,
|
|
||||||
paddingLeft: touchesLeft ? this._getInset('left') : 0,
|
|
||||||
paddingRight: touchesRight ? this._getInset('right') : 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (forceInset) {
|
|
||||||
Object.keys(forceInset).forEach(key => {
|
|
||||||
let inset = forceInset[key];
|
|
||||||
|
|
||||||
if (inset === 'always') {
|
|
||||||
inset = this._getInset(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inset === 'never') {
|
|
||||||
inset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case 'horizontal': {
|
|
||||||
style.paddingLeft = inset;
|
|
||||||
style.paddingRight = inset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'vertical': {
|
|
||||||
style.paddingTop = inset;
|
|
||||||
style.paddingBottom = inset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'left':
|
|
||||||
case 'right':
|
|
||||||
case 'top':
|
|
||||||
case 'bottom': {
|
|
||||||
const padding = `padding${key[0].toUpperCase()}${key.slice(1)}`;
|
|
||||||
style[padding] = inset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// new height/width should only include padding from insets
|
|
||||||
// height/width should not be affected by padding from style obj
|
|
||||||
if (style.height && typeof style.height === 'number') {
|
|
||||||
style.height += style.paddingTop + style.paddingBottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style.width && typeof style.width === 'number') {
|
|
||||||
style.width += style.paddingLeft + style.paddingRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
style.paddingTop = Math.max(style.paddingTop, paddingTop);
|
|
||||||
style.paddingBottom = Math.max(style.paddingBottom, paddingBottom);
|
|
||||||
style.paddingLeft = Math.max(style.paddingLeft, paddingLeft);
|
|
||||||
style.paddingRight = Math.max(style.paddingRight, paddingRight);
|
|
||||||
|
|
||||||
return style;
|
|
||||||
};
|
|
||||||
|
|
||||||
_getViewStyles = () => {
|
|
||||||
const { viewWidth } = this.state;
|
|
||||||
// get padding values from style to add back in after insets are determined
|
|
||||||
// default precedence: padding[Side] -> vertical | horizontal -> padding -> 0
|
|
||||||
let {
|
|
||||||
padding = 0,
|
|
||||||
paddingVertical = padding,
|
|
||||||
paddingHorizontal = padding,
|
|
||||||
paddingTop = paddingVertical,
|
|
||||||
paddingBottom = paddingVertical,
|
|
||||||
paddingLeft = paddingHorizontal,
|
|
||||||
paddingRight = paddingHorizontal,
|
|
||||||
...viewStyle
|
|
||||||
} = StyleSheet.flatten(this.props.style || {});
|
|
||||||
|
|
||||||
if (typeof paddingTop !== 'number') {
|
|
||||||
paddingTop = doubleFromPercentString(paddingTop) * viewWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof paddingBottom !== 'number') {
|
|
||||||
paddingBottom = doubleFromPercentString(paddingBottom) * viewWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof paddingLeft !== 'number') {
|
|
||||||
paddingLeft = doubleFromPercentString(paddingLeft) * viewWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof paddingRight !== 'number') {
|
|
||||||
paddingRight = doubleFromPercentString(paddingRight) * viewWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
paddingTop,
|
|
||||||
paddingBottom,
|
|
||||||
paddingLeft,
|
|
||||||
paddingRight,
|
|
||||||
viewStyle,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
_getInset = key => {
|
|
||||||
const { isLandscape } = this.props;
|
|
||||||
switch (key) {
|
|
||||||
case 'horizontal':
|
|
||||||
case 'right':
|
|
||||||
case 'left': {
|
|
||||||
return isLandscape ? (isIPhoneX ? 44 : 0) : 0;
|
|
||||||
}
|
|
||||||
case 'vertical':
|
|
||||||
case 'top': {
|
|
||||||
return statusBarHeight(isLandscape);
|
|
||||||
}
|
|
||||||
case 'bottom': {
|
|
||||||
return isIPhoneX ? (isLandscape ? 24 : 34) : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withOrientation(SafeView);
|
|
||||||
@@ -7,8 +7,9 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import SafeAreaView from 'react-native-safe-area-view';
|
||||||
|
|
||||||
import TabBarIcon from './TabBarIcon';
|
import TabBarIcon from './TabBarIcon';
|
||||||
import SafeAreaView from '../SafeAreaView';
|
|
||||||
import withOrientation from '../withOrientation';
|
import withOrientation from '../withOrientation';
|
||||||
|
|
||||||
const majorVersion = parseInt(Platform.Version, 10);
|
const majorVersion = parseInt(Platform.Version, 10);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, StyleSheet, Platform } from 'react-native';
|
import { View, StyleSheet, Platform } from 'react-native';
|
||||||
import { TabViewAnimated, TabViewPagerPan } from 'react-native-tab-view';
|
import { TabViewAnimated, TabViewPagerPan } from 'react-native-tab-view';
|
||||||
|
import SafeAreaView from 'react-native-safe-area-view';
|
||||||
|
|
||||||
import SceneView from '../SceneView';
|
import SceneView from '../SceneView';
|
||||||
import withCachedChildNavigation from '../../withCachedChildNavigation';
|
import withCachedChildNavigation from '../../withCachedChildNavigation';
|
||||||
import SafeAreaView from '../SafeAreaView';
|
|
||||||
|
|
||||||
class TabView extends React.PureComponent {
|
class TabView extends React.PureComponent {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ exports[`TabBarBottom renders successfully 1`] = `
|
|||||||
<View
|
<View
|
||||||
collapsable={undefined}
|
collapsable={undefined}
|
||||||
onLayout={[Function]}
|
onLayout={[Function]}
|
||||||
|
pointerEvents="box-none"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#F7F7F7",
|
"backgroundColor": "#F7F7F7",
|
||||||
|
|||||||
@@ -2512,7 +2512,7 @@ hoek@4.x.x:
|
|||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
|
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
|
||||||
|
|
||||||
hoist-non-react-statics@^2.2.0:
|
hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
|
||||||
|
|
||||||
@@ -4416,6 +4416,12 @@ react-native-drawer-layout@1.3.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react-native-dismiss-keyboard "1.0.0"
|
react-native-dismiss-keyboard "1.0.0"
|
||||||
|
|
||||||
|
react-native-safe-area-view@^0.6.0:
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.6.0.tgz#ce01eb27905a77780219537e0f53fe9c783a8b3d"
|
||||||
|
dependencies:
|
||||||
|
hoist-non-react-statics "^2.3.1"
|
||||||
|
|
||||||
react-native-tab-view@^0.0.74:
|
react-native-tab-view@^0.0.74:
|
||||||
version "0.0.74"
|
version "0.0.74"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.74.tgz#62c0c882d9232b461ce181d440d683b4f99d1bd8"
|
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.74.tgz#62c0c882d9232b461ce181d440d683b4f99d1bd8"
|
||||||
|
|||||||
Reference in New Issue
Block a user