PLT-5930 Moved Settings to channel drawer (#376)

This commit is contained in:
enahum 2017-03-21 19:15:41 -03:00 committed by Harrison Healey
parent 913e29e37b
commit 4bb74c065a
19 changed files with 484 additions and 452 deletions

View file

@ -6,6 +6,22 @@ import Routes from 'app/navigation/routes';
import {Constants} from 'mattermost-redux/constants';
import {selectPost} from 'mattermost-redux/actions/posts';
export function closeDrawers() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS
}, getState);
};
}
export function closeModal() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_CLOSE_MODAL
}, getState);
};
}
export function goBack() {
return async (dispatch, getState) => {
dispatch({
@ -14,10 +30,99 @@ export function goBack() {
};
}
export function closeDrawers() {
export function goToAccountNotifications() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.AccountNotifications
}, getState);
};
}
export function goToAccountSettings() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.AccountSettings
}, getState);
};
}
export function goToChannelAddMembers() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.ChannelAddMembers
}, getState);
};
}
export function goToChannelInfo() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.ChannelInfo
}, getState);
};
}
export function goToChannelMembers() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.ChannelMembers
}, getState);
};
}
export function goToCreateChannel(channelType) {
return async (dispatch, getState) => {
closeDrawers()(dispatch, getState);
let type;
let route;
switch (channelType) {
case Constants.OPEN_CHANNEL:
type = NavigationTypes.NAVIGATION_PUSH;
route = Routes.CreatePublicChannel;
break;
case Constants.PRIVATE_CHANNEL:
type = NavigationTypes.NAVIGATION_MODAL;
route = Routes.CreatePrivateChannel;
break;
default:
return;
}
dispatch({
type,
route,
props: {
channelType
}
}, getState);
};
}
export function goToImagePreviewModal(post, fileId) {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_MODAL,
route: {
...Routes.ImagePreview,
props: {
post,
fileId
}
}
}, getState);
};
}
export function goToLoadTeam() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.LoadTeam
}, getState);
};
}
@ -58,52 +163,15 @@ export function goToSaml() {
};
}
export function goToLoadTeam() {
export function goToSelectTeam() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.LoadTeam
}, getState);
};
}
export function goToModalSelectTeam() {
return async (dispatch, getState) => {
closeDrawers()(dispatch, getState);
dispatch({
type: NavigationTypes.NAVIGATION_MODAL,
route: Routes.SelectTeam
}, getState);
};
}
export function goToChannelInfo() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.ChannelInfo
}, getState);
};
}
export function goToChannelMembers() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.ChannelMembers
}, getState);
};
}
export function goToChannelAddMembers() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.ChannelAddMembers
}, getState);
};
}
export function goToThread(channelId, rootId) {
return async (dispatch, getState) => {
selectPost(rootId)(dispatch, getState);
@ -145,11 +213,19 @@ export function openChannelDrawer() {
};
}
export function openRightMenuDrawer() {
export function openSettingsModal() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_OPEN_RIGHT_DRAWER,
route: Routes.RightMenuDrawer
type: NavigationTypes.NAVIGATION_MODAL,
route: Routes.Settings
}, getState);
};
}
export function requestCloseModal() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_REQUEST_CLOSE_MODAL
}, getState);
};
}
@ -185,81 +261,3 @@ export function showDirectMessagesModal() {
}, getState);
};
}
export function closeModal() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_CLOSE_MODAL
}, getState);
};
}
export function goToModalAccountSettings() {
return async (dispatch, getState) => {
closeDrawers()(dispatch, getState);
dispatch({
type: NavigationTypes.NAVIGATION_MODAL,
route: Routes.AccountSettings
}, getState);
};
}
export function goToCreateChannel(channelType) {
return async (dispatch, getState) => {
closeDrawers()(dispatch, getState);
let type;
let route;
switch (channelType) {
case Constants.OPEN_CHANNEL:
type = NavigationTypes.NAVIGATION_PUSH;
route = Routes.CreatePublicChannel;
break;
case Constants.PRIVATE_CHANNEL:
type = NavigationTypes.NAVIGATION_MODAL;
route = Routes.CreatePrivateChannel;
break;
default:
return;
}
dispatch({
type,
route,
props: {
channelType
}
}, getState);
};
}
export function goToAccountNotifications() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_PUSH,
route: Routes.AccountNotifications
}, getState);
};
}
export function requestCloseModal() {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_REQUEST_CLOSE_MODAL
}, getState);
};
}
export function goToImagePreviewModal(post, fileId) {
return async (dispatch, getState) => {
dispatch({
type: NavigationTypes.NAVIGATION_MODAL,
route: {
...Routes.ImagePreview,
props: {
post,
fileId
}
}
}, getState);
};
}

View file

@ -36,9 +36,9 @@ class ChannelDrawerList extends Component {
actions: PropTypes.shape({
closeDMChannel: PropTypes.func.isRequired,
goToCreateChannel: PropTypes.func.isRequired,
goToModalAccountSettings: React.PropTypes.func.isRequired,
leaveChannel: PropTypes.func.isRequired,
markFavorite: PropTypes.func.isRequired,
openSettingsModal: React.PropTypes.func.isRequired,
unmarkFavorite: PropTypes.func.isRequired,
showOptionsModal: PropTypes.func.isRequired,
showDirectMessagesModal: PropTypes.func.isRequired,
@ -412,7 +412,7 @@ class ChannelDrawerList extends Component {
const settings = (
<TouchableHighlight
style={styles.settingsContainer}
onPress={() => this.props.actions.goToModalAccountSettings()}
onPress={() => this.props.actions.openSettingsModal()}
>
<AwesomeIcon
name='cog'

View file

@ -5,8 +5,8 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {
goToModalAccountSettings,
goToCreateChannel,
openSettingsModal,
requestCloseModal,
showMoreChannelsModal,
showDirectMessagesModal,
@ -33,9 +33,9 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
closeDMChannel,
goToCreateChannel,
goToModalAccountSettings,
leaveChannel,
markFavorite,
openSettingsModal,
unmarkFavorite,
showOptionsModal,
showDirectMessagesModal,

View file

@ -272,7 +272,7 @@ const state = {
leftDrawerOpen: false,
leftDrawerRoute: Routes.ChannelDrawer,
rightDrawerOpen: false,
rightDrawerRoute: Routes.RightMenuDrawer
rightDrawerRoute: null
},
views: {
channel: {

View file

@ -244,7 +244,6 @@ class Router extends React.Component {
leftDrawerOpen,
leftDrawerRoute,
modal,
rightDrawerOpen,
rightDrawerRoute,
routes
} = this.props.navigation;
@ -288,10 +287,10 @@ class Router extends React.Component {
tweenHandler={this.handleDrawerTween}
>
<Drawer
open={rightDrawerOpen}
open={false}
type='displace'
side='right'
disabled={modalVisible}
disabled={true}
content={rightDrawerContent}
tapToClose={true}
openDrawerOffset={50}
@ -300,7 +299,7 @@ class Router extends React.Component {
panCloseMask={50}
panThreshold={0.2}
acceptPan={navigationProps.allowMenuSwipe}
negotiatePan={true}
negotiatePan={false}
useInteractionManager={true}
tweenHandler={this.handleDrawerTween}
>

View file

@ -18,7 +18,7 @@ import {
MoreChannels,
MoreDirectMessages,
OptionsModal,
RightMenuDrawer,
Settings,
Root,
Saml,
Search,
@ -149,10 +149,6 @@ export const Routes = {
key: 'OptionsModal',
component: OptionsModal
},
RightMenuDrawer: {
key: 'RightMenuDrawer',
component: RightMenuDrawer
},
Root: {
key: 'Root',
component: Root
@ -180,10 +176,18 @@ export const Routes = {
SelectTeam: {
key: 'SelectTeam',
component: SelectTeam,
transition: RouteTransitions.Horizontal,
navigationProps: {
title: {id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}
}
},
Settings: {
key: 'Settings',
component: Settings,
navigationProps: {
title: {id: 'mobile.routes.settings', defaultMessage: 'Settings'}
}
},
Thread: {
key: 'Thread',
transition: RouteTransitions.Horizontal,

View file

@ -16,7 +16,6 @@ import {Constants} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import ChannelDrawerButton from './channel_drawer_button';
import ChannelMenuButton from './channel_menu_button';
import ChannelTitle from './channel_title';
import ChannelPostList from './channel_post_list';
@ -28,7 +27,6 @@ export default class Channel extends React.PureComponent {
selectFirstAvailableTeam: React.PropTypes.func.isRequired,
selectInitialChannel: React.PropTypes.func.isRequired,
openChannelDrawer: React.PropTypes.func.isRequired,
openRightMenuDrawer: React.PropTypes.func.isRequired,
handlePostDraftChanged: React.PropTypes.func.isRequired,
goToChannelInfo: React.PropTypes.func.isRequired,
initWebSocket: React.PropTypes.func.isRequired,
@ -51,15 +49,11 @@ export default class Channel extends React.PureComponent {
},
renderTitleComponent: (props, emitter) => {
return <ChannelTitle emitter={emitter}/>;
},
renderRightComponent: (props, emitter) => {
return <ChannelMenuButton emitter={emitter}/>;
}
};
componentWillMount() {
this.props.subscribeToHeaderEvent('open_channel_drawer', this.openChannelDrawer);
this.props.subscribeToHeaderEvent('open_right_menu', this.openRightMenuDrawer);
this.props.subscribeToHeaderEvent('show_channel_info', this.props.actions.goToChannelInfo);
EventEmitter.on('leave_team', this.handleLeaveTeam);
this.props.actions.initWebSocket(Platform.OS);
@ -99,11 +93,6 @@ export default class Channel extends React.PureComponent {
this.props.actions.openChannelDrawer();
};
openRightMenuDrawer = () => {
this.postTextbox.getWrappedInstance().blur();
this.props.actions.openRightMenuDrawer();
};
attachPostTextbox = (ref) => {
this.postTextbox = ref;
};

View file

@ -7,8 +7,7 @@ import navigationSceneConnect from '../navigationSceneConnect';
import {
goToChannelInfo,
openChannelDrawer,
openRightMenuDrawer
openChannelDrawer
} from 'app/actions/navigation';
import {
loadChannelsIfNecessary,
@ -48,7 +47,6 @@ function mapDispatchToProps(dispatch) {
selectFirstAvailableTeam,
selectInitialChannel,
openChannelDrawer,
openRightMenuDrawer,
handlePostDraftChanged,
goToChannelInfo,
initWebSocket,

View file

@ -1,69 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PropTypes, PureComponent} from 'react';
import {connect} from 'react-redux';
import {
TouchableOpacity,
View
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import {getTheme} from 'app/selectors/preferences';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
class ChannelMenuButton extends PureComponent {
static propTypes = {
emitter: PropTypes.func.isRequired,
theme: PropTypes.object
};
static defaultProps = {
currentChannel: {},
theme: {}
};
constructor(props) {
super(props);
this.state = {
opacity: 1
};
}
componentDidMount() {
EventEmitter.on('drawer_opacity', this.setOpacity);
}
componentWillUnmount() {
EventEmitter.off('drawer_opacity', this.setOpacity);
}
setOpacity = (value) => {
this.setState({opacity: value > 0 ? 0.1 : 1});
};
render() {
return (
<View style={{flexDirection: 'column', justifyContent: 'center', alignItems: 'center', flex: 1, opacity: this.state.opacity}}>
<TouchableOpacity onPress={() => this.props.emitter('open_right_menu')}>
<Icon
name='ellipsis-v'
size={25}
color={this.props.theme.sidebarHeaderTextColor}
style={{paddingHorizontal: 20}}
/>
</TouchableOpacity>
</View>
);
}
}
function mapStateToProps(state) {
return {
theme: getTheme(state)
};
}
export default connect(mapStateToProps)(ChannelMenuButton);

View file

@ -17,7 +17,7 @@ import Mfa from './mfa';
import MoreChannels from './more_channels';
import MoreDirectMessages from './more_dms';
import OptionsModal from './options_modal';
import RightMenuDrawer from './right_menu_drawer';
import Settings from './settings';
import Root from './root/root_container.js';
import Search from './search/search_container.js';
import SelectServer from './select_server/select_server_container.js';
@ -43,7 +43,7 @@ module.exports = {
MoreChannels,
MoreDirectMessages,
OptionsModal,
RightMenuDrawer,
Settings,
Root,
Search,
SelectServer,

View file

@ -1,6 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import RightMenuDrawerContainer from './right_menu_drawer_container';
export default RightMenuDrawerContainer;

View file

@ -1,159 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {
ScrollView,
StyleSheet,
Platform,
View,
Linking
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import FormattedText from 'app/components/formatted_text';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import RightMenuDrawerItem from './right_menu_drawer_item';
export default class RightMenuDrawer extends React.Component {
static propTypes = {
errors: React.PropTypes.array.isRequired,
currentUserId: React.PropTypes.string.isRequired,
currentTeamId: React.PropTypes.string.isRequired,
actions: React.PropTypes.shape({
goToModalSelectTeam: React.PropTypes.func.isRequired,
clearErrors: React.PropTypes.func.isRequired,
logout: React.PropTypes.func.isRequired
}).isRequired,
theme: React.PropTypes.object
};
openErrorEmail = () => {
const recipient = 'feedback@mattermost.com';
const subject = 'Problem with Mattermost React Native app';
Linking.openURL(
`mailto:${recipient}?subject=${subject}&body=${this.errorEmailBody()}`
);
this.props.actions.clearErrors();
};
errorEmailBody = () => {
const {currentUserId, currentTeamId, errors} = this.props;
let contents = [
`Current User Id: ${currentUserId}`,
`Current Team Id: ${currentTeamId}`
];
if (errors.length) {
contents = contents.concat([
'',
'Errors:',
JSON.stringify(errors.map((e) => e.error))
]);
}
return contents.join('\n');
};
render() {
const Styles = getStyleSheet(this.props.theme);
return (
<ScrollView style={Styles.container}>
<Divider style={Styles.divider}/>
<RightMenuDrawerItem onPress={this.props.actions.goToModalSelectTeam}>
<Icon
style={Styles.icon}
name='exchange'
/>
<FormattedText
style={Styles.itemText}
id='sidebar_right_menu.switch_team'
defaultMessage='Team Selection'
/>
</RightMenuDrawerItem>
<Divider style={Styles.divider}/>
<RightMenuDrawerItem>
<Icon
style={Styles.icon}
name='question'
/>
<FormattedText
style={Styles.itemText}
id='sidebar_right_menu.help'
defaultMessage='Help'
/>
</RightMenuDrawerItem>
<RightMenuDrawerItem onPress={this.openErrorEmail}>
<Icon
style={Styles.icon}
name='phone'
/>
<FormattedText
style={Styles.itemText}
id='sidebar_right_menu.report'
defaultMessage='Report a Problem'
/>
</RightMenuDrawerItem>
<RightMenuDrawerItem>
<Icon
style={Styles.icon}
name='info'
/>
<FormattedText
style={Styles.itemText}
id='navbar_dropdown.about'
defaultMessage='About Mattermost'
/>
</RightMenuDrawerItem>
<Divider style={Styles.divider}/>
<RightMenuDrawerItem onPress={this.props.actions.logout}>
<Icon
style={Styles.icon}
name='sign-out'
/>
<FormattedText
style={Styles.itemText}
id='sidebar_right_menu.logout'
defaultMessage='Logout'
/>
</RightMenuDrawerItem>
</ScrollView>
);
}
}
function Divider(props) {
return <View {...props}/>;
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return StyleSheet.create({
container: {
backgroundColor: theme.sidebarBg,
flex: 1,
...Platform.select({
ios: {
paddingTop: 20
},
android: {
paddingTop: 5
}
})
},
itemText: {
color: theme.sidebarText
},
icon: {
color: theme.sidebarText,
width: 25
},
mentionIcon: {
fontSize: 17,
fontWeight: 'bold'
},
divider: {
borderColor: changeOpacity(theme.sidebarText, 0.2),
borderTopWidth: StyleSheet.hairlineWidth
}
});
});

View file

@ -1,38 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {Component, PropTypes} from 'react';
import {StyleSheet, TouchableHighlight, View} from 'react-native';
const styles = StyleSheet.create({
item: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
height: 40,
paddingHorizontal: 10
}
});
export default class RightMenuDrawerItem extends Component {
static propTypes = {
children: PropTypes.node,
onPress: PropTypes.func,
style: View.propTypes.style
}
render() {
const {onPress, style, children} = this.props;
return (
<TouchableHighlight
underlayColor='rgba(255, 255, 255, 0.3)'
onPress={onPress}
style={style}
>
<View style={styles.item}>
{children}
</View>
</TouchableHighlight>
);
}
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import React, {PropTypes, PureComponent} from 'react';
import {
Image,
InteractionManager,
@ -17,16 +17,18 @@ import {GlobalStyles} from 'app/styles';
import logo from 'assets/images/logo.png';
export default class SelectTeam extends React.Component {
export default class SelectTeam extends PureComponent {
static propTypes = {
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
config: PropTypes.object.isRequired,
teams: PropTypes.object.isRequired,
myMembers: PropTypes.object.isRequired,
subscribeToHeaderEvent: PropTypes.func.isRequired,
unsubscribeFromHeaderEvent: PropTypes.func.isRequired,
actions: PropTypes.shape({
closeDrawers: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired,
goBack: PropTypes.func.isRequired,
handleTeamChange: PropTypes.func.isRequired
}).isRequired
};
@ -48,7 +50,7 @@ export default class SelectTeam extends React.Component {
};
componentWillMount() {
this.props.subscribeToHeaderEvent('close', this.props.actions.goBackToChannelView);
this.props.subscribeToHeaderEvent('close', this.props.actions.goBack);
}
componentWillUnmount() {
@ -57,23 +59,24 @@ export default class SelectTeam extends React.Component {
onSelectTeam = async (team) => {
const {
goBackToChannelView,
closeDrawers,
closeModal,
handleTeamChange
} = this.props.actions;
await handleTeamChange(team);
InteractionManager.runAfterInteractions(goBackToChannelView);
handleTeamChange(team);
closeDrawers();
InteractionManager.runAfterInteractions(closeModal);
};
render() {
const teams = [];
for (const id of Object.keys(this.props.teams)) {
const team = this.props.teams[id];
if (this.props.myMembers.hasOwnProperty(id)) {
teams.push(
const content = [];
const teams = Object.values(this.props.teams);
for (const team of teams) {
if (this.props.myMembers.hasOwnProperty(team.id)) {
content.push(
<Button
key={id}
key={team.id}
onPress={() => this.onSelectTeam(team)}
style={GlobalStyles.buttonListItemText}
containerStyle={GlobalStyles.buttonListItem}
@ -108,7 +111,7 @@ export default class SelectTeam extends React.Component {
id='signup_team.choose'
defaultMessage='Your teams:'
/>
{teams}
{content}
</View>
);
}

View file

@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
import navigationSceneConnect from '../navigationSceneConnect';
import {goBack} from 'app/actions/navigation';
import {closeDrawers, closeModal, goBack} from 'app/actions/navigation';
import {handleTeamChange} from 'app/actions/views/select_team';
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
@ -25,7 +25,9 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
goBackToChannelView: goBack,
closeDrawers,
closeModal,
goBack,
handleTeamChange
}, dispatch)
};

View file

@ -2,15 +2,20 @@
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {goBack, goToModalSelectTeam} from 'app/actions/navigation';
import {
closeDrawers,
goBack,
goToAccountSettings,
goToSelectTeam
} from 'app/actions/navigation';
import {clearErrors} from 'mattermost-redux/actions/errors';
import {logout} from 'mattermost-redux/actions/users';
import navigationSceneConnect from 'app/scenes/navigationSceneConnect';
import {getTheme} from 'app/selectors/preferences';
import RightMenuDrawer from './right_menu_drawer';
import Settings from './settings';
function mapStateToProps(state, ownProps) {
return {
@ -25,12 +30,14 @@ function mapStateToProps(state, ownProps) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
closeDrawers,
goBack,
goToModalSelectTeam,
goToAccountSettings,
goToSelectTeam,
clearErrors,
logout
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(RightMenuDrawer);
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(Settings);

View file

@ -0,0 +1,169 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PropTypes, PureComponent} from 'react';
import {
InteractionManager,
Linking,
Platform,
StyleSheet,
TouchableHighlight,
View
} from 'react-native';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import SettingsItem from './settings_item';
export default class Settings extends PureComponent {
static propTypes = {
errors: PropTypes.array.isRequired,
currentUserId: PropTypes.string.isRequired,
currentTeamId: PropTypes.string.isRequired,
actions: PropTypes.shape({
closeDrawers: PropTypes.func.isRequired,
goBack: PropTypes.func.isRequired,
goToAccountSettings: PropTypes.func.isRequired,
goToSelectTeam: PropTypes.func.isRequired,
clearErrors: PropTypes.func.isRequired,
logout: PropTypes.func.isRequired
}).isRequired,
theme: PropTypes.object
};
static navigationProps = {
renderLeftComponent: (props, emitter, theme) => {
const style = getStyleSheet(theme);
return (
<TouchableHighlight
style={style.leftComponent}
onPress={props.onNavigateBack}
underlayColor={changeOpacity(theme.centerChannelColor, 0.9)}
>
<MaterialIcon
size={20}
name='close'
color={theme.sidebarHeaderTextColor}
/>
</TouchableHighlight>
);
}
};
logout = () => {
const {closeDrawers, logout} = this.props.actions;
closeDrawers();
InteractionManager.runAfterInteractions(logout);
};
openErrorEmail = () => {
const recipient = 'feedback@mattermost.com';
const subject = 'Problem with Mattermost React Native app';
Linking.openURL(
`mailto:${recipient}?subject=${subject}&body=${this.errorEmailBody()}`
);
this.props.actions.clearErrors();
};
errorEmailBody = () => {
const {currentUserId, currentTeamId, errors} = this.props;
let contents = [
`Current User Id: ${currentUserId}`,
`Current Team Id: ${currentTeamId}`
];
if (errors.length) {
contents = contents.concat([
'',
'Errors:',
JSON.stringify(errors.map((e) => e.error))
]);
}
return contents.join('\n');
};
render() {
const {theme} = this.props;
const style = getStyleSheet(theme);
return (
<View style={style.container}>
<View style={style.wrapper}>
<SettingsItem
defaultMessage='Account Settings'
i18nId='sidebar_right_menu.accountSettings'
iconName='cog'
iconType='fontawesome'
onPress={this.props.actions.goToAccountSettings}
separator={true}
theme={theme}
/>
<SettingsItem
defaultMessage='Team Selection'
i18nId='sidebar_right_menu.switch_team'
iconName='ios-people'
iconType='ion'
onPress={this.props.actions.goToSelectTeam}
separator={true}
theme={theme}
/>
<SettingsItem
defaultMessage='Report a Problem'
i18nId='sidebar_right_menu.report'
iconName='warning'
iconType='material'
onPress={this.openErrorEmail}
separator={false}
theme={theme}
/>
<View style={style.divider}/>
</View>
<View style={style.footer}>
<View style={style.divider}/>
<SettingsItem
defaultMessage='Logout'
i18nId='sidebar_right_menu.logout'
iconName='exit-to-app'
iconType='material'
isDestructor={true}
onPress={this.logout}
separator={false}
theme={theme}
/>
</View>
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return StyleSheet.create({
leftComponent: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
width: 44
},
container: {
flex: 1,
backgroundColor: theme.centerChannelBg
},
wrapper: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
flex: 1
},
divider: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
height: 1
},
footer: {
height: 51,
justifyContent: 'flex-end',
...Platform.select({
android: {
marginBottom: 20
}
})
}
});
});

View file

@ -0,0 +1,134 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PropTypes, PureComponent} from 'react';
import {StyleSheet, TouchableOpacity, View} from 'react-native';
import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
import IonIcon from 'react-native-vector-icons/Ionicons';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import FormattedText from 'app/components/formatted_text';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
export default class SettingsItem extends PureComponent {
static propTypes = {
defaultMessage: PropTypes.string.isRequired,
i18nId: PropTypes.string.isRequired,
iconName: PropTypes.string.isRequired,
iconType: PropTypes.oneOf(['fontawesome', 'ion', 'material']).isRequired,
isDestructor: PropTypes.bool,
onPress: PropTypes.func,
separator: PropTypes.bool,
theme: PropTypes.object.isRequired
};
static defaultProps = {
isDestructor: false,
separator: true
};
render() {
const {
defaultMessage,
i18nId,
iconName,
iconType,
isDestructor,
onPress,
separator,
theme
} = this.props;
const style = getStyleSheet(theme);
const destructor = {};
if (isDestructor) {
destructor.color = '#CA3B27';
}
let divider;
if (separator) {
divider = (<View style={style.divider}/>);
}
let icon;
switch (iconType) {
case 'fontawesome':
icon = (
<FontAwesomeIcon
name={iconName}
style={[style.icon, destructor]}
/>
);
break;
case 'ion':
icon = (
<IonIcon
name={iconName}
style={[style.icon, destructor]}
/>
);
break;
case 'material':
icon = (
<MaterialIcon
name={iconName}
style={[style.icon, destructor]}
/>
);
break;
}
return (
<View style={style.container}>
<TouchableOpacity
onPress={onPress}
style={{flex: 1}}
>
<View style={style.wrapper}>
{icon}
<FormattedText
id={i18nId}
defaultMessage={defaultMessage}
style={[style.label, destructor]}
/>
</View>
</TouchableOpacity>
{divider}
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return StyleSheet.create({
container: {
height: 51,
flexDirection: 'column',
justifyContent: 'center'
},
wrapper: {
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
flexDirection: 'row',
height: 50,
paddingLeft: 16
},
icon: {
color: theme.linkColor,
fontSize: 16
},
label: {
color: theme.centerChannelColor,
fontSize: 16,
fontWeight: '600',
lineHeight: 19,
marginLeft: 8
},
divider: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
height: 1,
marginLeft: 16
}
});
});

View file

@ -1548,6 +1548,7 @@
"mobile.server_ping_failed": "Cannot connect to the server. Please check your server URL and internet connection.",
"mobile.server_url.invalid_format": "URL must start with http:// or https://",
"mobile.routes.saml": "Single SignOn",
"mobile.routes.settings": "Settings",
"more_channels.close": "Close",
"more_channels.create": "Create New Channel",
"more_channels.createClick": "Click 'Create New Channel' to make a new one",