Navigation Drawer and Modal (#220)

Navigation header now responds to theme changes
Improve channel selection transition
Implement navigation modal
Reword options modal
This commit is contained in:
Chris Duarte 2017-02-08 12:16:24 -08:00 committed by enahum
parent b3b3d9a39a
commit 088a2c7737
24 changed files with 373 additions and 144 deletions

View file

@ -47,10 +47,10 @@ export function goToLoadTeam() {
};
}
export function goToSelectTeam() {
export function goToModalSelectTeam() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
type: NavigationTypes.NAVIGATION_MODAL,
route: Routes.SelectTeam
}, getState);
};
@ -100,3 +100,24 @@ export function openRightMenuDrawer() {
}, getState);
};
}
export function showOptionsModal(title, options) {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_MODAL,
route: Routes.OptionsModal,
props: {
title,
options
}
}, getState);
};
}
export function closeModal() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_CLOSE_MODAL
}, getState);
};
}

View file

@ -4,6 +4,7 @@
import {batchActions} from 'redux-batched-actions';
import {ViewTypes} from 'app/constants';
import {updateStorage} from 'app/actions/storage';
import {closeDrawers} from 'app/actions/navigation';
import {
fetchMyChannelsAndMembers,
@ -125,8 +126,12 @@ export function selectInitialChannel(teamId) {
export function handleSelectChannel(channelId) {
return async (dispatch, getState) => {
const currentTeamId = getState().entities.teams.currentId;
await updateStorage(currentTeamId, {currentChannelId: channelId});
await selectChannel(channelId)(dispatch, getState);
setTimeout(async () => {
await closeDrawers()(dispatch, getState); // trying to smooth out channel switch transitions
}, 200);
};
}

View file

@ -6,17 +6,6 @@ import {batchActions} from 'redux-batched-actions';
import {NavigationTypes} from 'app/constants';
import Routes from 'app/navigation/routes';
export function goToSelectTeam() {
return async (dispatch, getState) => {
dispatch(batchActions([{
type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS
}, {
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.SelectTeam
}]), getState);
};
}
export function goToRecentMentions() {
return async (dispatch, getState) => {
dispatch(batchActions([{

View file

@ -73,8 +73,8 @@ class ChannelList extends React.Component {
leaveChannel: React.PropTypes.func.isRequired,
markFavorite: React.PropTypes.func.isRequired,
unmarkFavorite: React.PropTypes.func.isRequired,
openModal: React.PropTypes.func.isRequired,
closeModal: React.PropTypes.func.isRequired
showOptionsModal: React.PropTypes.func.isRequired,
closeOptionsModal: React.PropTypes.func.isRequired
}).isRequired
};
@ -176,7 +176,7 @@ class ChannelList extends React.Component {
}, {
text: formatMessage({id: 'mobile.channel_list.alertYes', defaultMessage: 'Yes'}),
onPress: () => {
this.setState({showOptions: false});
this.props.actions.closeOptionsModal();
this.props.actions.leaveChannel(channel);
}
}]
@ -201,7 +201,7 @@ class ChannelList extends React.Component {
open = {
action: () => {
this.props.actions.closeModal();
this.props.actions.closeOptionsModal();
this.onSelectChannel(channel);
},
text: formatMessage({id: 'mobile.channel_list.openDM', defaultMessage: 'Open Direct Message'})
@ -231,7 +231,7 @@ class ChannelList extends React.Component {
open = {
action: () => {
this.props.actions.closeModal();
this.props.actions.closeOptionsModal();
this.onSelectChannel(channel);
},
text: formatMessage({id: 'mobile.channel_list.openChannel', defaultMessage: 'Open {term}'}, {
@ -257,7 +257,7 @@ class ChannelList extends React.Component {
if (channel.isFavorite) {
favorite = {
action: () => {
this.props.actions.closeModal();
this.props.actions.closeOptionsModal();
this.props.actions.unmarkFavorite(channel.id);
},
text: formatMessage({id: 'channelHeader.removeFromFavorites', defaultMessage: 'Remove from Favorites'})
@ -265,7 +265,7 @@ class ChannelList extends React.Component {
} else {
favorite = {
action: () => {
this.props.actions.closeModal();
this.props.actions.closeOptionsModal();
this.props.actions.markFavorite(channel.id);
},
text: formatMessage({id: 'channelHeader.addToFavorites', defaultMessage: 'Add to Favorites'})
@ -276,7 +276,7 @@ class ChannelList extends React.Component {
if (close) {
options.push(close);
}
this.props.actions.openModal(title, options);
this.props.actions.showOptionsModal(title, options);
};
getUnreadMessages = (channel) => {

View file

@ -4,7 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {openModal, closeModal} from 'app/actions/views/options_modal';
import {showOptionsModal, closeModal} from 'app/actions/navigation';
import {closeDMChannel, leaveChannel, markFavorite, unmarkFavorite} from 'app/actions/views/channel';
import {viewChannel} from 'service/actions/channels';
@ -24,8 +24,8 @@ function mapDispatchToProps(dispatch) {
leaveChannel,
markFavorite,
unmarkFavorite,
openModal,
closeModal
showOptionsModal,
closeOptionsModal: closeModal
}, dispatch)
};
}

View file

@ -2,7 +2,10 @@
// See License.txt for license information.
import React from 'react';
import {ListView, StyleSheet} from 'react-native';
import {
ListView,
StyleSheet
} from 'react-native';
import Post from 'app/components/post';

View file

@ -15,4 +15,3 @@ function mapStateToProps(state, ownProps) {
}
export default connect(mapStateToProps)(PostList);

View file

@ -12,7 +12,9 @@ const NavigationTypes = keyMirror({
NAVIGATION_JUMP: null,
NAVIGATION_JUMP_TO_INDEX: null,
NAVIGATION_REPLACE: null,
NAVIGATION_RESET: null
NAVIGATION_RESET: null,
NAVIGATION_MODAL: null,
NAVIGATION_CLOSE_MODAL: null
});
export default NavigationTypes;

View file

@ -258,8 +258,13 @@ const state = {
routes: [
Routes.Root
],
modal: {
index: 0,
routes: []
},
isModal: false,
leftDrawerOpen: false,
leftDrawerRoute: null,
leftDrawerRoute: Routes.ChannelDrawer,
rightDrawerOpen: false,
rightDrawerRoute: null
},

View file

@ -0,0 +1,63 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PropTypes, PureComponent} from 'react';
import {
Animated,
Dimensions
} from 'react-native';
const {View: AnimatedView} = Animated;
const {width: deviceWidth, height: deviceHeight} = Dimensions.get('window');
export default class NavigationModal extends PureComponent {
static propTypes = {
show: PropTypes.bool
}
static defaultProps = {
show: false
}
state = {
top: new Animated.Value(deviceHeight)
}
componentWillReceiveProps(nextProps) {
if (this.props.show === nextProps.show) {
return;
}
// In order for the scene to be shown throughout the
// animated slide down we have to hang on to it by
// storing it in state
if (!this.state.children) {
this.setState({
children: nextProps.children
});
}
const animateValue = nextProps.show ? 0 : deviceHeight;
Animated.timing(this.state.top, {
toValue: animateValue,
duration: 400
}).start(() => {
// Once the scene has finished sliding down we can release the child scene
// which will unmount the scene correctly.
if (!this.props.show && !nextProps.show) {
this.setState({
children: null
});
}
});
}
render() {
return (
<AnimatedView style={{position: 'absolute', width: deviceWidth, height: deviceHeight, top: this.state.top, left: 0, backgroundColor: '#0000'}}>
{this.state.children}
</AnimatedView>
);
}
}

View file

@ -13,15 +13,15 @@ import {bindActionCreators} from 'redux';
import {closeDrawers, goBack} from 'app/actions/navigation';
import Drawer from 'app/components/drawer';
import FormattedText from 'app/components/formatted_text';
import OptionsModal from 'app/components/options_modal';
import {RouteTransitions} from 'app/navigation/routes';
import {getTheme} from 'service/selectors/entities/preferences';
import NavigationModal from './navigation_modal';
class Router extends React.Component {
static propTypes = {
navigation: React.PropTypes.object,
theme: React.PropTypes.object,
modalVisible: React.PropTypes.bool.isRequired,
actions: React.PropTypes.shape({
closeDrawers: React.PropTypes.func.isRequired,
goBack: React.PropTypes.func.isRequired
@ -49,7 +49,7 @@ class Router extends React.Component {
wrapHeaderComponent = (fx) => (props) => {
if (fx && props.scene.isActive) {
return fx(props, this.emitHeaderEvent);
return fx(props, this.emitHeaderEvent, this.props.theme);
}
return null;
@ -74,10 +74,7 @@ class Router extends React.Component {
renderLeftComponent={renderLeftComponent}
renderTitleComponent={renderTitleComponent}
renderRightComponent={renderRightComponent}
style={[
navigationProps.headerStyle,
{backgroundColor: this.props.theme.sidebarHeaderBg}
]}
style={[{backgroundColor: this.props.theme.sidebarHeaderBg}, navigationProps.headerStyle]}
/>
);
}
@ -94,7 +91,14 @@ class Router extends React.Component {
...cardProps
});
} else {
style = {};
// have to override shadow props for transparent modals
style = {
backgroundColor: 'transparent',
shadowColor: null,
shadowOffset: null,
shadowOpacity: null,
shadowRadius: null
};
}
return (
@ -154,7 +158,7 @@ class Router extends React.Component {
configureTransition = () => {
return {
duration: 500,
duration: 300,
easing: Easing.inOut(Easing.ease)
};
};
@ -166,14 +170,13 @@ class Router extends React.Component {
leftDrawerRoute,
rightDrawerOpen,
rightDrawerRoute,
routes
routes,
isModal: modalVisible
} = this.props.navigation;
const navigationProps = this.extractNavigationProps(routes[index]);
let leftDrawerContent;
if (leftDrawerRoute) {
// TODO: We should make it so that this is availabe once we login
// son when sliding it renders correctly without the neeed to open using the top-bar
leftDrawerContent = this.renderRoute(leftDrawerRoute);
}
@ -182,52 +185,58 @@ class Router extends React.Component {
rightDrawerContent = this.renderRoute(rightDrawerRoute);
}
const {modalVisible} = this.props;
return (
<Drawer
open={leftDrawerOpen}
type='displace'
disabled={modalVisible}
content={leftDrawerContent}
tapToClose={true}
openDrawerOffset={0.2}
onRequestClose={this.props.actions.closeDrawers}
panOpenMask={0.1}
panCloseMask={0.2}
panThreshold={0.2}
acceptPan={navigationProps.allowSwipe}
negotiatePan={true}
>
<View style={{flex: 1}}>
<Drawer
open={rightDrawerOpen}
open={leftDrawerOpen}
type='displace'
side='right'
disabled={modalVisible}
content={rightDrawerContent}
content={leftDrawerContent}
tapToClose={true}
openDrawerOffset={0.2}
onRequestClose={this.props.actions.closeDrawers}
panOpenMask={0.1}
panCloseMask={0.2}
panThreshold={0.2}
acceptPan={navigationProps.allowSwipe}
negotiatePan={true}
useInteractionManager={true}
>
<Drawer
open={rightDrawerOpen}
type='displace'
side='right'
disabled={modalVisible}
content={rightDrawerContent}
tapToClose={true}
openDrawerOffset={0.2}
onRequestClose={this.props.actions.closeDrawers}
>
<NavigationExperimental.Transitioner
style={{flex: 1}}
navigationState={this.props.navigation}
render={this.renderTransition}
configureTransition={this.configureTransition}
/>
</Drawer>
</Drawer>
<NavigationModal show={modalVisible}>
<NavigationExperimental.Transitioner
style={{flex: 1}}
navigationState={this.props.navigation}
navigationState={this.props.navigation.modal}
render={this.renderTransition}
configureTransition={this.configureTransition}
/>
<OptionsModal/>
</Drawer>
</Drawer>
</NavigationModal>
</View>
);
};
}
function mapStateToProps(state) {
const modalVisible = state.views.optionsModal.visible;
return {
navigation: state.navigation,
theme: getTheme(state),
modalVisible
theme: getTheme(state)
};
}

View file

@ -10,6 +10,7 @@ import {
LoadTeam,
Login,
Mfa,
OptionsModal,
RightMenuDrawer,
Root,
Search,
@ -77,6 +78,13 @@ export const Routes = {
title: {id: 'mobile.routes.mfa', defaultMessage: 'Multi-factor Authentication'}
}
},
OptionsModal: {
key: 'OptionsModal',
component: OptionsModal,
navigationProps: {
hideNavBar: true
}
},
RightMenuDrawer: {
key: 'RightMenuDrawer',
component: RightMenuDrawer

View file

@ -13,6 +13,11 @@ const initialState = {
routes: [
Routes.Root
],
modal: {
index: 0,
routes: []
},
isModal: false,
leftDrawerOpen: false,
leftDrawerRoute: null,
rightDrawerOpen: false,
@ -21,11 +26,19 @@ const initialState = {
export default function(state = initialState, action) {
switch (action.type) {
case NavigationTypes.NAVIGATION_PUSH:
case NavigationTypes.NAVIGATION_PUSH: {
if (state.isModal) {
const modalState = NavigationExperimental.StateUtils.push(state.modal, {props: action.props, ...action.route});
return {
...state,
modal: modalState
};
}
return NavigationExperimental.StateUtils.push(state, action.route);
}
case NavigationTypes.NAVIGATION_POP:
if (state.leftDrawerOpen || state.rightDrawerOpen) {
if (!state.isModal && (state.leftDrawerOpen || state.rightDrawerOpen)) {
return {
...state,
leftDrawerOpen: false,
@ -33,6 +46,17 @@ export default function(state = initialState, action) {
};
}
if (state.isModal) {
return {
...state,
modal: {
index: 0,
routes: []
},
isModal: false
};
}
return NavigationExperimental.StateUtils.pop(state);
case NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER:
@ -56,22 +80,81 @@ export default function(state = initialState, action) {
rightDrawerOpen: false
};
case NavigationTypes.NAVIGATION_JUMP:
return NavigationExperimental.StateUtils.jumpTo(state, action.key);
case NavigationTypes.NAVIGATION_JUMP: {
if (state.isModal) {
const modalState = NavigationExperimental.StateUtils.jumpTo(state.modal, action.key);
return {
...state,
modal: modalState
};
}
return NavigationExperimental.StateUtils.jumpTo(state, action.key);
}
case NavigationTypes.NAVIGATION_JUMP_TO_INDEX: {
if (state.isModal) {
const modalState = NavigationExperimental.StateUtils.jumpToIndex(state.modal, action.index);
return {
...state,
modal: modalState
};
}
case NavigationTypes.NAVIGATION_JUMP_TO_INDEX:
return NavigationExperimental.StateUtils.jumpToIndex(state, action.index);
}
case NavigationTypes.NAVIGATION_RESET:
return {
...state,
...NavigationExperimental.StateUtils.reset(state, action.routes, action.index),
modal: {
index: 0,
routes: []
},
isModal: false,
leftDrawerOpen: false,
rightDrawerOpen: false
};
case NavigationTypes.NAVIGATION_REPLACE:
case NavigationTypes.NAVIGATION_REPLACE: {
if (state.isModal) {
const modalState = NavigationExperimental.StateUtils.replaceAtIndex(state.modal, state.modal.index, action.index);
return {
...state,
modal: modalState
};
}
return NavigationExperimental.StateUtils.replaceAtIndex(state, state.index, action.route);
}
case NavigationTypes.NAVIGATION_MODAL: {
const modal = {
index: 0,
routes: [
{
...action.route,
props: action.props
}
]
};
return {
...state,
modal,
isModal: true
};
}
case NavigationTypes.NAVIGATION_CLOSE_MODAL:
return {
...state,
modal: {
index: 0,
routes: []
},
isModal: false
};
case UsersTypes.LOGOUT_SUCCESS:
return NavigationExperimental.StateUtils.reset(state, initialState.routes, initialState.index);

View file

@ -14,14 +14,12 @@ import {getTheme} from 'service/selectors/entities/preferences';
function ChannelMenuButton(props) {
return (
<View style={{flexDirection: 'column', justifyContent: 'center', alignItems: 'center', flex: 1}}>
<TouchableOpacity
onPress={() => props.emitter('open_right_menu')}
style={{height: 25, width: 25, marginLeft: 10, marginRight: 10}}
>
<TouchableOpacity onPress={() => props.emitter('open_right_menu')}>
<Icon
name='ellipsis-v'
size={25}
color={props.theme.sidebarHeaderTextColor}
style={{paddingHorizontal: 20}}
/>
</TouchableOpacity>
</View>

View file

@ -18,9 +18,13 @@ export default class ChannelDrawer extends React.PureComponent {
theme: React.PropTypes.object.isRequired
};
static defaultProps = {
currentTeam: {},
currentChannel: {}
}
selectChannel = (id) => {
this.props.actions.handleSelectChannel(id);
this.props.actions.closeDrawers();
};
render() {

View file

@ -9,6 +9,7 @@ import ChannelAddMembers from './channel_add_members';
import LoadTeam from './load_team';
import Login from './login/login_container.js';
import Mfa from './mfa';
import OptionsModal from './options_modal';
import RightMenuDrawer from './right_menu_drawer';
import Root from './root/root_container.js';
import Search from './search/search_container.js';
@ -24,6 +25,7 @@ module.exports = {
LoadTeam,
Login,
Mfa,
OptionsModal,
RightMenuDrawer,
Root,
Search,

View file

@ -15,7 +15,7 @@ import Icon from 'react-native-vector-icons/FontAwesome';
import FormattedText from 'app/components/formatted_text';
const defaults = {
renderBackButton: (props) => {
renderBackButton: (props, emitter, theme) => {
return (
<TouchableOpacity
style={{flexDirection: 'row', ...Platform.select({ios: {marginTop: 10}, android: {marginTop: 15}}), marginLeft: 15, alignItems: 'center'}}
@ -24,17 +24,17 @@ const defaults = {
<Icon
name='angle-left'
size={25}
color='#fff'
color={theme.sidebarHeaderTextColor}
/>
<FormattedText
id='mobile.routes.back'
defaultMessage='Back'
style={{color: '#fff', marginLeft: 10}}
style={{color: theme.sidebarHeaderTextColor, marginLeft: 10}}
/>
</TouchableOpacity>
);
},
renderTitleComponent: (props) => {
renderTitleComponent: (props, emitter, theme) => {
const navProps = props.scene.route.navigationProps || {};
const title = navProps.title;
if (title) {
@ -43,7 +43,7 @@ const defaults = {
<FormattedText
id={title.id}
defaultMessage={title.defaultMessage}
style={{color: '#fff', fontSize: 15, fontWeight: 'bold', textAlign: 'center'}}
style={{color: theme.sidebarHeaderTextColor, fontSize: 15, fontWeight: 'bold'}}
/>
</View>
);

View file

@ -7,7 +7,6 @@ import {
View,
StyleSheet,
Dimensions,
Modal,
Text,
ScrollView,
TouchableOpacity,
@ -84,7 +83,6 @@ export default class OptionsModal extends PureComponent {
static propTypes = {
title: PropTypes.string,
options: PropTypes.array,
visible: PropTypes.bool,
onOptionSelected: PropTypes.func,
style: View.propTypes.style,
titleStyle: View.propTypes.style,
@ -101,7 +99,6 @@ export default class OptionsModal extends PureComponent {
static defaultProps = {
title: '',
options: [],
visible: false,
style: {},
cancelStyle: {},
cancelTextStyle: {},
@ -203,17 +200,10 @@ export default class OptionsModal extends PureComponent {
render() {
if (this.props.options.length) {
return (
<View style={this.props.style}>
<Modal
transparent={true}
visible={this.props.visible}
onRequestClose={() => this.onOptionSelected(null)}
animationType={this.state.animationType}
>
<TouchableWithoutFeedback onPress={() => this.onOptionSelected(null)}>
{this.renderOptionList()}
</TouchableWithoutFeedback>
</Modal>
<View style={[{flex: 1}, this.props.style]}>
<TouchableWithoutFeedback onPress={() => this.onOptionSelected(null)}>
{this.renderOptionList()}
</TouchableWithoutFeedback>
</View>
);
}

View file

@ -2,24 +2,25 @@
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {closeModal} from 'app/actions/views/options_modal';
import navigationSceneConnect from '../navigationSceneConnect';
import {goBack} from 'app/actions/navigation';
import OptionsModal from './options_modal';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
...state.views.optionsModal
...state.views.optionsModal,
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
closeModal
closeModal: goBack
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(OptionsModal);
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(OptionsModal);

View file

@ -12,37 +12,40 @@ import {
import Icon from 'react-native-vector-icons/FontAwesome';
import FormattedText from 'app/components/formatted_text';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import RightMenuDrawerItem from './right_menu_drawer_item';
const Styles = StyleSheet.create({
container: {
backgroundColor: '#2071a7',
flex: 1,
...Platform.select({
ios: {
paddingTop: 20
},
android: {
paddingTop: 5
}
})
},
itemText: {
color: 'white'
},
icon: {
color: 'white',
width: 25
},
mentionIcon: {
fontSize: 17,
fontWeight: 'bold'
},
divider: {
borderColor: 'rgba(255, 255, 255, 0.6)',
borderTopWidth: StyleSheet.hairlineWidth
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return StyleSheet.create({
container: {
backgroundColor: theme.sidebarBg,
flex: 1,
...Platform.select({
ios: {
paddingTop: 20
},
android: {
paddingTop: 5
}
})
},
itemText: {
color: 'white'
},
icon: {
color: 'white',
width: 25
},
mentionIcon: {
fontSize: 17,
fontWeight: 'bold'
},
divider: {
borderColor: 'rgba(255, 255, 255, 0.6)',
borderTopWidth: StyleSheet.hairlineWidth
}
});
});
export default class RightMenuDrawer extends React.Component {
@ -50,12 +53,15 @@ export default class RightMenuDrawer extends React.Component {
actions: React.PropTypes.shape({
goToFlaggedPosts: React.PropTypes.func.isRequired,
goToRecentMentions: React.PropTypes.func.isRequired,
goToSelectTeam: React.PropTypes.func.isRequired,
goToModalSelectTeam: React.PropTypes.func.isRequired,
logout: React.PropTypes.func.isRequired
}).isRequired
}).isRequired,
theme: React.PropTypes.object
}
render() {
const Styles = getStyleSheet(this.props.theme);
return (
<ScrollView style={Styles.container}>
<RightMenuDrawerItem onPress={this.props.actions.goToRecentMentions}>
@ -77,7 +83,7 @@ export default class RightMenuDrawer extends React.Component {
defaultMessage='Flagged Posts'
/>
</RightMenuDrawerItem>
<Divider/>
<Divider style={Styles.divider}/>
<RightMenuDrawerItem>
<Icon
style={Styles.icon}
@ -111,7 +117,7 @@ export default class RightMenuDrawer extends React.Component {
defaultMessage='Get Team Invite Link'
/>
</RightMenuDrawerItem>
<Divider/>
<Divider style={Styles.divider}/>
<RightMenuDrawerItem>
<Icon
style={Styles.icon}
@ -134,7 +140,7 @@ export default class RightMenuDrawer extends React.Component {
defaultMessage='Manage Members'
/>
</RightMenuDrawerItem>
<RightMenuDrawerItem onPress={this.props.actions.goToSelectTeam}>
<RightMenuDrawerItem onPress={this.props.actions.goToModalSelectTeam}>
<Icon
style={Styles.icon}
name='exchange'
@ -145,7 +151,7 @@ export default class RightMenuDrawer extends React.Component {
defaultMessage='Team Selection'
/>
</RightMenuDrawerItem>
<Divider/>
<Divider style={Styles.divider}/>
<RightMenuDrawerItem>
<Icon
style={Styles.icon}
@ -179,7 +185,7 @@ export default class RightMenuDrawer extends React.Component {
defaultMessage='About Mattermost'
/>
</RightMenuDrawerItem>
<Divider/>
<Divider style={Styles.divider}/>
<RightMenuDrawerItem onPress={this.props.actions.logout}>
<Icon
style={Styles.icon}
@ -196,6 +202,6 @@ export default class RightMenuDrawer extends React.Component {
}
}
function Divider() {
return <View style={Styles.divider}/>;
function Divider(props) {
return <View {...props}/>;
}

View file

@ -4,15 +4,19 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {goBack} from 'app/actions/navigation';
import {goToFlaggedPosts, goToRecentMentions, goToSelectTeam} from 'app/actions/views/right_menu_drawer';
import {goBack, goToModalSelectTeam} from 'app/actions/navigation';
import {goToFlaggedPosts, goToRecentMentions} from 'app/actions/views/right_menu_drawer';
import {logout} from 'service/actions/users';
import {getTheme} from 'service/selectors/entities/preferences';
import RightMenuDrawer from './right_menu_drawer';
function mapStateToProps(state, ownProps) {
return ownProps;
return {
...ownProps,
theme: getTheme(state)
};
}
function mapDispatchToProps(dispatch) {
@ -21,7 +25,7 @@ function mapDispatchToProps(dispatch) {
goBack,
goToFlaggedPosts,
goToRecentMentions,
goToSelectTeam,
goToModalSelectTeam,
logout
}, dispatch)
};

View file

@ -2,7 +2,12 @@
// See License.txt for license information.
import React from 'react';
import {View, Image, Text} from 'react-native';
import {
Image,
Text,
TouchableOpacity,
View
} from 'react-native';
import Button from 'react-native-button';
import Icon from 'react-native-vector-icons/MaterialIcons';
@ -16,12 +21,39 @@ export default class SelectTeam extends React.Component {
config: React.PropTypes.object.isRequired,
teams: React.PropTypes.object.isRequired,
myMembers: React.PropTypes.object.isRequired,
subscribeToHeaderEvent: React.PropTypes.func.isRequired,
unsubscribeFromHeaderEvent: React.PropTypes.func.isRequired,
actions: React.PropTypes.shape({
goBackToChannelView: React.PropTypes.func.isRequired,
handleTeamChange: React.PropTypes.func.isRequired
}).isRequired
};
static navigationProps = {
renderLeftComponent: (props, emitter, theme) => {
return (
<TouchableOpacity
style={{flex: 1, paddingHorizontal: 15, justifyContent: 'center'}}
onPress={() => emitter('close')}
>
<FormattedText
id='admin.select_team.close'
defaultMessage='Close'
style={{color: theme.sidebarHeaderTextColor}}
/>
</TouchableOpacity>
);
}
}
componentWillMount() {
this.props.subscribeToHeaderEvent('close', this.props.actions.goBackToChannelView);
}
componentWillUnmount() {
this.props.unsubscribeFromHeaderEvent('close');
}
onSelectTeam(team) {
this.props.actions.handleTeamChange(team).then(this.props.actions.goBackToChannelView);
}

View file

@ -23,6 +23,11 @@ describe('Reducers.Navigation', () => {
assert.deepEqual(state, {
index: 0,
routes: [Routes.Root],
modal: {
index: 0,
routes: []
},
isModal: false,
leftDrawerOpen: false,
leftDrawerRoute: null,
rightDrawerOpen: false,