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:
parent
b3b3d9a39a
commit
088a2c7737
24 changed files with 373 additions and 144 deletions
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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([{
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -15,4 +15,3 @@ function mapStateToProps(state, ownProps) {
|
|||
}
|
||||
|
||||
export default connect(mapStateToProps)(PostList);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
63
app/navigation/navigation_modal.js
Normal file
63
app/navigation/navigation_modal.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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}/>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue