Merge branch 'master' into @ericvicenti/drawer-fix

This commit is contained in:
Brent Vatne
2018-03-05 16:23:10 -08:00
committed by GitHub
9 changed files with 152 additions and 83 deletions

View File

@@ -109,10 +109,10 @@ const ExampleInfo = {
name: 'Animated Tabs Example',
description: 'Tab transitions have custom animations',
},
// TabsWithNavigationFocus: {
// name: 'withNavigationFocus',
// description: 'Receive the focus prop to know when a screen is focused',
// },
TabsWithNavigationFocus: {
name: 'withNavigationFocus',
description: 'Receive the focus prop to know when a screen is focused',
},
};
const ExampleRoutes = {
@@ -139,8 +139,8 @@ const ExampleRoutes = {
screen: SimpleTabs,
path: 'settings',
},
TabAnimations: TabAnimations,
// TabsWithNavigationFocus: TabsWithNavigationFocus,
TabAnimations,
TabsWithNavigationFocus,
};
type State = {

View File

@@ -3,40 +3,75 @@
*/
import React from 'react';
import { SafeAreaView, Text } from 'react-native';
import { Button, SafeAreaView, Text } from 'react-native';
import { TabNavigator, withNavigationFocus } from 'react-navigation';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import SampleText from './SampleText';
const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
const TabScreen = ({ isFocused }) => (
<SafeAreaView
forceInset={{ horizontal: 'always', top: 'always' }}
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text style={{ fontWeight: '700', fontSize: 16, marginBottom: 5 }}>
{'Tab ' + name.toLowerCase()}
class Child extends React.Component<any, any> {
render() {
return (
<Text style={{ color: this.props.isFocused ? 'green' : 'maroon' }}>
{this.props.isFocused
? 'I know that my parent is focused!'
: 'My parent is not focused! :O'}
</Text>
<Text>{'props.isFocused: ' + (isFocused ? ' true' : 'false')}</Text>
</SafeAreaView>
);
);
}
}
TabScreen.navigationOptions = {
tabBarLabel: name,
tabBarIcon: ({ tintColor, focused }) => (
<MaterialCommunityIcons
name={focused ? focusedIcon : icon}
size={26}
style={{ color: focused ? tintColor : '#ccc' }}
/>
),
};
const ChildWithNavigationFocus = withNavigationFocus(Child);
const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
class TabScreen extends React.Component<any, any> {
static navigationOptions = {
tabBarLabel: name,
tabBarIcon: ({ tintColor, focused }) => (
<MaterialCommunityIcons
name={focused ? focusedIcon : icon}
size={26}
style={{ color: focused ? tintColor : '#ccc' }}
/>
),
};
state = { showChild: false };
render() {
const { isFocused } = this.props;
return (
<SafeAreaView
forceInset={{ horizontal: 'always', top: 'always' }}
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text style={{ fontWeight: '700', fontSize: 16, marginBottom: 5 }}>
{'Tab ' + name.toLowerCase()}
</Text>
<Text style={{ marginBottom: 20 }}>
{'props.isFocused: ' + (isFocused ? ' true' : 'false')}
</Text>
{this.state.showChild ? (
<ChildWithNavigationFocus />
) : (
<Button
title="Press me"
onPress={() => this.setState({ showChild: true })}
/>
)}
<Button
onPress={() => this.props.navigation.goBack(null)}
title="Back to other examples"
/>
</SafeAreaView>
);
}
}
return withNavigationFocus(TabScreen);
};

View File

@@ -459,7 +459,7 @@ declare module 'react-navigation' {
type: EventType,
action: NavigationAction,
state: NavigationState,
lastState: NavigationState,
lastState: ?NavigationState,
};
declare export type NavigationEventCallback = (
@@ -686,7 +686,7 @@ declare module 'react-navigation' {
SET_PARAMS: 'Navigation/SET_PARAMS',
URI: 'Navigation/URI',
back: {
(payload: { key?: ?string }): NavigationBackAction,
(payload?: { key?: ?string }): NavigationBackAction,
toString: () => string,
},
init: {

View File

@@ -11,10 +11,8 @@ test('child action events only flow when focused', () => {
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const testState = {
key: 'foo',
routeName: 'FooRoute',
@@ -66,11 +64,9 @@ test('grandchildren subscription', () => {
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
);
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const parentBlurState = {
key: 'foo',
routeName: 'FooRoute',
@@ -135,11 +131,9 @@ test('grandchildren transitions', () => {
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
);
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const makeFakeState = (childIndex, childIsTransitioning) => ({
index: 1,
isTransitioning: false,
@@ -230,11 +224,9 @@ test('grandchildren pass through transitions', () => {
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
);
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const makeFakeState = (childIndex, childIsTransitioning) => ({
index: childIndex,
isTransitioning: childIsTransitioning,
@@ -322,10 +314,8 @@ test('child focus with transition', () => {
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const randomAction = { type: 'FooAction' };
const testState = {
key: 'foo',
@@ -417,10 +407,8 @@ test('child focus with immediate transition', () => {
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(
parentSubscriber,
'key1'
);
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const randomAction = { type: 'FooAction' };
const testState = {
key: 'foo',

View File

@@ -11,6 +11,18 @@ export default function getChildEventSubscriber(addListener, key) {
const willBlurSubscribers = new Set();
const didBlurSubscribers = new Set();
const removeAll = () => {
[
actionSubscribers,
willFocusSubscribers,
didFocusSubscribers,
willBlurSubscribers,
didBlurSubscribers,
].forEach(set => set.clear());
upstreamSubscribers.forEach(subs => subs && subs.remove());
};
const getChildSubscribers = evtName => {
switch (evtName) {
case 'action':
@@ -43,10 +55,6 @@ export default function getChildEventSubscriber(addListener, key) {
// considered blurred
let lastEmittedEvent = 'didBlur';
const cleanup = () => {
upstreamSubscribers.forEach(subs => subs && subs.remove());
};
const upstreamEvents = [
'willFocus',
'didFocus',
@@ -133,15 +141,18 @@ export default function getChildEventSubscriber(addListener, key) {
})
);
return (eventName, eventHandler) => {
const subscribers = getChildSubscribers(eventName);
if (!subscribers) {
throw new Error(`Invalid event name "${eventName}"`);
}
subscribers.add(eventHandler);
const remove = () => {
subscribers.delete(eventHandler);
};
return { remove };
return {
removeAll,
addListener(eventName, eventHandler) {
const subscribers = getChildSubscribers(eventName);
if (!subscribers) {
throw new Error(`Invalid event name "${eventName}"`);
}
subscribers.add(eventHandler);
const remove = () => {
subscribers.delete(eventHandler);
};
return { remove };
},
};
}

View File

@@ -8,6 +8,30 @@ function createNavigator(NavigatorView, router, navigationConfig) {
static router = router;
static navigationOptions = null;
childEventSubscribers = {};
// Cleanup subscriptions for routes that no longer exist
componentDidUpdate() {
const activeKeys = this.props.navigation.state.routes.map(r => r.key);
Object.keys(this.childEventSubscribers).forEach(key => {
if (!activeKeys.includes(key)) {
this.childEventSubscribers[key].removeAll();
delete this.childEventSubscribers[key];
}
});
}
// Remove all subscriptions
componentWillUnmount() {
Object.values(this.childEventSubscribers).map(s => s.removeAll());
}
_isRouteFocused = route => () => {
const { state } = this.props.navigation;
const focusedRoute = state.routes[state.index];
return route === focusedRoute;
};
render() {
const { navigation, screenProps } = this.props;
const { dispatch, state, addListener } = navigation;
@@ -18,10 +42,18 @@ function createNavigator(NavigatorView, router, navigationConfig) {
const getComponent = () =>
router.getComponentForRouteName(route.routeName);
if (!this.childEventSubscribers[route.key]) {
this.childEventSubscribers[route.key] = getChildEventSubscriber(
addListener,
route.key
);
}
const childNavigation = addNavigationHelpers({
dispatch,
state: route,
addListener: getChildEventSubscriber(addListener, route.key),
addListener: this.childEventSubscribers[route.key].addListener,
isFocused: this._isRouteFocused.bind(this, route),
});
const options = router.getScreenOptions(childNavigation, screenProps);
descriptors[route.key] = {

View File

@@ -2,7 +2,6 @@ import invariant from '../utils/invariant';
import getScreenForRouteName from './getScreenForRouteName';
import validateScreenOptions from './validateScreenOptions';
import getChildEventSubscriber from '../getChildEventSubscriber';
function applyConfig(configurer, navigationOptions, configProps) {
if (typeof configurer === 'function') {

View File

@@ -4,9 +4,9 @@ import DrawerLayout from 'react-native-drawer-layout-polyfill';
import addNavigationHelpers from '../../addNavigationHelpers';
import DrawerSidebar from './DrawerSidebar';
import getChildEventSubscriber from '../../getChildEventSubscriber';
import NavigationActions from '../../NavigationActions';
/**
* Component that renders the drawer.
*/

View File

@@ -12,9 +12,13 @@ export default function withNavigationFocus(Component) {
navigation: propTypes.object.isRequired,
};
state = {
isFocused: false,
};
constructor(props, context) {
super();
this.state = {
isFocused: this.getNavigation(props, context).isFocused(),
};
}
componentDidMount() {
const navigation = this.getNavigation();
@@ -32,8 +36,8 @@ export default function withNavigationFocus(Component) {
this.subscriptions.forEach(sub => sub.remove());
}
getNavigation = () => {
const navigation = this.props.navigation || this.context.navigation;
getNavigation = (props = this.props, context = this.context) => {
const navigation = props.navigation || context.navigation;
invariant(
!!navigation,
'withNavigationFocus can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context.'