diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js
index bf44d9403..a9f82316c 100644
--- a/app/components/channel_drawer/channel_drawer.js
+++ b/app/components/channel_drawer/channel_drawer.js
@@ -50,8 +50,8 @@ export default class ChannelDrawer extends Component {
theme: PropTypes.object.isRequired
};
- closeLeftHandle = null;
- openLeftHandle = null;
+ closeHandle = null;
+ openHandle = null;
swiperIndex = 1;
constructor(props) {
@@ -71,7 +71,6 @@ export default class ChannelDrawer extends Component {
}
componentDidMount() {
- EventEmitter.on('open_channel_drawer', this.openChannelDrawer);
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
@@ -104,7 +103,6 @@ export default class ChannelDrawer extends Component {
}
componentWillUnmount() {
- EventEmitter.off('open_channel_drawer', this.openChannelDrawer);
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
@@ -132,15 +130,15 @@ export default class ChannelDrawer extends Component {
handleDrawerClose = () => {
this.resetDrawer();
- if (this.closeLeftHandle) {
- InteractionManager.clearInteractionHandle(this.closeLeftHandle);
- this.closeLeftHandle = null;
+ if (this.closeHandle) {
+ InteractionManager.clearInteractionHandle(this.closeHandle);
+ this.closeHandle = null;
}
};
handleDrawerCloseStart = () => {
- if (!this.closeLeftHandle) {
- this.closeLeftHandle = InteractionManager.createInteractionHandle();
+ if (!this.closeHandle) {
+ this.closeHandle = InteractionManager.createInteractionHandle();
}
};
@@ -149,15 +147,15 @@ export default class ChannelDrawer extends Component {
Keyboard.dismiss();
}
- if (this.openLeftHandle) {
- InteractionManager.clearInteractionHandle(this.openLeftHandle);
- this.openLeftHandle = null;
+ if (this.openHandle) {
+ InteractionManager.clearInteractionHandle(this.openHandle);
+ this.openHandle = null;
}
};
handleDrawerOpenStart = () => {
- if (!this.openLeftHandle) {
- this.openLeftHandle = InteractionManager.createInteractionHandle();
+ if (!this.openHandle) {
+ this.openHandle = InteractionManager.createInteractionHandle();
}
};
@@ -367,6 +365,7 @@ export default class ChannelDrawer extends Component {
return (
{
- const {intl, navigator, theme} = this.props;
-
- navigator.showModal({
- screen: 'Settings',
- title: intl.formatMessage({id: 'mobile.routes.settings', defaultMessage: 'Settings'}),
- animationType: 'slide-up',
- animated: true,
- backButtonTitle: '',
- navigatorStyle: {
- navBarTextColor: theme.sidebarHeaderTextColor,
- navBarBackgroundColor: theme.sidebarHeaderBg,
- navBarButtonColor: theme.sidebarHeaderTextColor,
- screenBackgroundColor: theme.centerChannelBg
- },
- navigatorButtons: {
- leftButtons: [{
- id: 'close-settings',
- icon: this.closeButton
- }]
- }
- });
- });
-
onSearch = (term) => {
this.setState({term});
};
@@ -107,7 +80,6 @@ class ChannelsList extends React.PureComponent {
const {searching, term} = this.state;
const styles = getStyleSheet(theme);
- let settings;
let list;
if (searching) {
list = (
@@ -118,19 +90,6 @@ class ChannelsList extends React.PureComponent {
/>
);
} else {
- settings = (
-
-
-
- );
-
list = (
{title}
- {settings}
{list}
@@ -228,26 +186,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
position: 'relative',
top: -1
},
- settingsContainer: {
- alignItems: 'center',
- justifyContent: 'center',
- paddingHorizontal: 10,
- ...Platform.select({
- android: {
- height: 46,
- marginRight: 6
- },
- ios: {
- height: 44,
- marginRight: 8
- }
- })
- },
- settings: {
- color: theme.sidebarHeaderTextColor,
- fontSize: 18,
- fontWeight: '300'
- },
titleContainer: { // These aren't used by this component, but they are passed down to the list component
alignItems: 'center',
flex: 1,
@@ -266,6 +204,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
},
searchContainer: {
flex: 1,
+ paddingRight: 10,
...Platform.select({
android: {
marginBottom: 1
diff --git a/app/components/channel_drawer/index.js b/app/components/channel_drawer/index.js
index 4324d3834..f3f01faaa 100644
--- a/app/components/channel_drawer/index.js
+++ b/app/components/channel_drawer/index.js
@@ -43,4 +43,4 @@ function mapDispatchToProps(dispatch) {
};
}
-export default connect(mapStateToProps, mapDispatchToProps)(ChannelDrawer);
+export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(ChannelDrawer);
diff --git a/app/components/channel_drawer/teams_list/teams_list.js b/app/components/channel_drawer/teams_list/teams_list.js
index 290b93eb0..d2f90c6a3 100644
--- a/app/components/channel_drawer/teams_list/teams_list.js
+++ b/app/components/channel_drawer/teams_list/teams_list.js
@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
import {
FlatList,
Platform,
+ StatusBar,
Text,
TouchableHighlight,
View
@@ -44,6 +45,7 @@ class TeamsList extends PureComponent {
}
selectTeam = (teamId) => {
+ StatusBar.setHidden(false, 'slide');
requestAnimationFrame(() => {
const {actions, closeChannelDrawer, currentTeamId} = this.props;
if (teamId !== currentTeamId) {
diff --git a/app/components/profile_picture/index.js b/app/components/profile_picture/index.js
index 90cb284d5..f9911663a 100644
--- a/app/components/profile_picture/index.js
+++ b/app/components/profile_picture/index.js
@@ -18,7 +18,7 @@ function mapStateToProps(state, ownProps) {
}
return {
- theme: ownProps.theme || getTheme(state),
+ theme: getTheme(state),
status,
user
};
diff --git a/app/components/profile_picture/profile_picture.js b/app/components/profile_picture/profile_picture.js
index a2fdda87d..74a40b12e 100644
--- a/app/components/profile_picture/profile_picture.js
+++ b/app/components/profile_picture/profile_picture.js
@@ -5,22 +5,13 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Image, Platform, View} from 'react-native';
-import {General} from 'mattermost-redux/constants';
-
-import {AwayIcon, DndIcon, OfflineIcon, OnlineIcon} from 'app/components/status_icons';
+import UserStatus from 'app/components/user_status';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import placeholder from 'assets/images/profile.jpg';
import {Client4} from 'mattermost-redux/client';
-const statusToIcon = {
- away: AwayIcon,
- dnd: DndIcon,
- offline: OfflineIcon,
- online: OnlineIcon
-};
-
const STATUS_BUFFER = Platform.select({
ios: 3,
android: 2
@@ -32,6 +23,7 @@ export default class ProfilePicture extends PureComponent {
statusBorderWidth: PropTypes.number,
statusSize: PropTypes.number,
user: PropTypes.object,
+ showStatus: PropTypes.bool,
status: PropTypes.string,
theme: PropTypes.object.isRequired,
actions: PropTypes.shape({
@@ -40,6 +32,7 @@ export default class ProfilePicture extends PureComponent {
};
static defaultProps = {
+ showStatus: true,
size: 128,
statusBorderWidth: 2,
statusSize: 14
@@ -60,35 +53,10 @@ export default class ProfilePicture extends PureComponent {
pictureUrl = Client4.getProfilePictureUrl(this.props.user.id, this.props.user.last_picture_update);
}
- let Icon;
- let iconColor;
- if (this.props.status && statusToIcon[this.props.status]) {
- Icon = statusToIcon[this.props.status];
-
- switch (this.props.status) {
- case General.AWAY:
- iconColor = theme.awayIndicator;
- break;
- case General.DND:
- iconColor = theme.dndIndicator;
- break;
- case General.ONLINE:
- iconColor = theme.onlineIndicator;
- break;
- default:
- iconColor = theme.centerChannelColor;
- break;
- }
- } else {
- Icon = statusToIcon.offline;
- iconColor = theme.centerChannelColor;
- }
-
const statusIcon = (
-
);
@@ -100,7 +68,7 @@ export default class ProfilePicture extends PureComponent {
source={{uri: pictureUrl}}
defaultSource={placeholder}
/>
- {this.props.status &&
+ {this.props.showStatus &&
{statusIcon}
@@ -123,21 +91,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
},
status: {
color: theme.centerChannelBg
- },
- online: {
- backgroundColor: theme.onlineIndicator
- },
- away: {
- backgroundColor: theme.awayIndicator
- },
- dnd: {
- backgroundColor: 'red'
- },
- offline: {
- backgroundColor: theme.centerChannelBg
- },
- offlineIcon: {
- borderColor: '#bababa'
}
};
});
diff --git a/app/components/safe_area_view/safe_area_view.android.js b/app/components/safe_area_view/safe_area_view.android.js
index 2bbdc2dac..7fdea6e86 100644
--- a/app/components/safe_area_view/safe_area_view.android.js
+++ b/app/components/safe_area_view/safe_area_view.android.js
@@ -7,13 +7,21 @@ import {StyleSheet, View} from 'react-native';
export default class SafeAreaAndroid extends PureComponent {
static propTypes = {
- children: PropTypes.node.isRequired
+ backgroundColor: PropTypes.string,
+ children: PropTypes.node.isRequired,
+ theme: PropTypes.object.isRequired
};
render() {
+ const {backgroundColor, children, theme} = this.props;
+ let bgColor = theme.centerChannelBg;
+ if (backgroundColor) {
+ bgColor = backgroundColor;
+ }
+
return (
-
- {this.props.children}
+
+ {children}
);
}
diff --git a/app/components/safe_area_view/safe_area_view.ios.js b/app/components/safe_area_view/safe_area_view.ios.js
index b27ba6caf..bd2404476 100644
--- a/app/components/safe_area_view/safe_area_view.ios.js
+++ b/app/components/safe_area_view/safe_area_view.ios.js
@@ -15,10 +15,13 @@ export default class SafeAreaIos extends PureComponent {
backgroundColor: PropTypes.string,
children: PropTypes.node.isRequired,
excludeHeader: PropTypes.bool,
+ footerColor: PropTypes.string,
+ footerComponent: PropTypes.node,
forceTop: PropTypes.number,
keyboardOffset: PropTypes.number.isRequired,
navBarBackgroundColor: PropTypes.string,
navigator: PropTypes.object,
+ headerComponent: PropTypes.node,
theme: PropTypes.object.isRequired
};
@@ -103,7 +106,7 @@ export default class SafeAreaIos extends PureComponent {
renderTopBar = () => {
const {safeAreaInsets, statusBarHeight} = this.state;
- const {excludeHeader, forceTop, navBarBackgroundColor, theme} = this.props;
+ const {headerComponent, excludeHeader, forceTop, navBarBackgroundColor, theme} = this.props;
const hideTopBar = excludeHeader || !statusBarHeight;
if (hideTopBar) {
@@ -120,6 +123,20 @@ export default class SafeAreaIos extends PureComponent {
top = forceTop;
}
+ if (headerComponent) {
+ return (
+
+ {headerComponent}
+
+ );
+ }
+
return (
{this.renderTopBar()}
{children}
+
+ {footerComponent}
+
);
}
diff --git a/app/components/settings_drawer/drawer_item.js b/app/components/settings_drawer/drawer_item.js
new file mode 100644
index 000000000..915f06353
--- /dev/null
+++ b/app/components/settings_drawer/drawer_item.js
@@ -0,0 +1,150 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {TouchableOpacity, View} from 'react-native';
+
+import FormattedText from 'app/components/formatted_text';
+import VectorIcon from 'app/components/vector_icon.js';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default class DrawerItem extends PureComponent {
+ static propTypes = {
+ centered: PropTypes.bool,
+ defaultMessage: PropTypes.string,
+ i18nId: PropTypes.string,
+ iconName: PropTypes.string,
+ iconType: PropTypes.oneOf(['fontawesome', 'foundation', 'ion', 'material']),
+ isDestructor: PropTypes.bool,
+ labelComponent: PropTypes.node,
+ leftComponent: PropTypes.node,
+ onPress: PropTypes.func,
+ separator: PropTypes.bool,
+ theme: PropTypes.object.isRequired
+ };
+
+ static defaultProps = {
+ defaultMessage: '',
+ isDestructor: false,
+ separator: true
+ };
+
+ render() {
+ const {
+ centered,
+ defaultMessage,
+ i18nId,
+ iconName,
+ iconType,
+ isDestructor,
+ labelComponent,
+ leftComponent,
+ onPress,
+ separator,
+ theme
+ } = this.props;
+ const style = getStyleSheet(theme);
+
+ const destructor = {};
+ if (isDestructor) {
+ destructor.color = theme.errorTextColor;
+ }
+
+ let divider;
+ if (separator) {
+ divider = ();
+ }
+
+ let icon;
+ if (leftComponent) {
+ icon = leftComponent;
+ } else if (iconType && iconName) {
+ icon = (
+
+ );
+ }
+
+ let label;
+ if (labelComponent) {
+ label = labelComponent;
+ } else if (i18nId) {
+ label = (
+
+ );
+ }
+
+ return (
+
+
+ {icon &&
+
+ {icon}
+
+ }
+
+
+ {label}
+
+ {divider}
+
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ alignItems: 'center',
+ backgroundColor: theme.centerChannelBg,
+ flexDirection: 'row',
+ height: 50
+ },
+ iconContainer: {
+ width: 45,
+ height: 50,
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginLeft: 5
+ },
+ icon: {
+ color: theme.linkColor,
+ fontSize: 22
+ },
+ wrapper: {
+ flex: 1
+ },
+ labelContainer: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row'
+ },
+ centerLabel: {
+ textAlign: 'center',
+ textAlignVertical: 'center'
+ },
+ label: {
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ flex: 1,
+ fontSize: 17,
+ textAlignVertical: 'center',
+ includeFontPadding: false
+ },
+ divider: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
+ height: 1
+ }
+ };
+});
diff --git a/app/components/settings_drawer/index.js b/app/components/settings_drawer/index.js
new file mode 100644
index 000000000..37315c3c0
--- /dev/null
+++ b/app/components/settings_drawer/index.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+
+import {logout} from 'mattermost-redux/actions/users';
+import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+
+import SettingsDrawer from './settings_drawer';
+import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
+
+function mapStateToProps(state) {
+ return {
+ currentUser: getCurrentUser(state),
+ theme: getTheme(state)
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ logout
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(SettingsDrawer);
diff --git a/app/components/settings_drawer/settings_drawer.js b/app/components/settings_drawer/settings_drawer.js
new file mode 100644
index 000000000..030e84b6d
--- /dev/null
+++ b/app/components/settings_drawer/settings_drawer.js
@@ -0,0 +1,310 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {intlShape} from 'react-intl';
+import {
+ BackHandler,
+ InteractionManager,
+ Keyboard,
+ Platform,
+ ScrollView,
+ View
+} from 'react-native';
+import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
+
+import EventEmitter from 'mattermost-redux/utils/event_emitter';
+
+import SafeAreaView from 'app/components/safe_area_view';
+import Drawer from 'app/components/drawer';
+import UserStatus from 'app/components/user_status';
+import {wrapWithPreventDoubleTap} from 'app/utils/tap';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+import DrawerItem from './drawer_item';
+import UserInfo from './user_info';
+import StatusLabel from './status_label';
+
+const DRAWER_INITIAL_OFFSET = 80;
+
+export default class SettingsDrawer extends PureComponent {
+ static propTypes = {
+ actions: PropTypes.shape({
+ logout: PropTypes.func.isRequired
+ }).isRequired,
+ blurPostTextBox: PropTypes.func.isRequired,
+ children: PropTypes.node,
+ currentUser: PropTypes.object.isRequired,
+ navigator: PropTypes.object,
+ theme: PropTypes.object.isRequired
+ };
+
+ static defaultProps = {
+ currentUser: {}
+ };
+
+ static contextTypes = {
+ intl: intlShape
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.closeHandle = null;
+ this.openHandle = null;
+
+ MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
+ this.closeButton = source;
+ });
+ }
+
+ componentDidMount() {
+ BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
+ }
+
+ componentWillUnmount() {
+ BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
+ }
+
+ handleAndroidBack = () => {
+ if (this.refs.drawer && this.refs.drawer.isOpened()) {
+ this.refs.drawer.close();
+ return true;
+ }
+
+ return false;
+ };
+
+ openSettingsDrawer = () => {
+ this.props.blurPostTextBox();
+
+ if (this.refs.drawer && !this.refs.drawer.isOpened()) {
+ this.refs.drawer.open();
+ }
+ };
+
+ closeSettingsDrawer = () => {
+ if (this.refs.drawer && this.refs.drawer.isOpened()) {
+ this.refs.drawer.close();
+ }
+ };
+
+ handleDrawerTween = (ratio) => {
+ const opacity = (ratio / 2);
+
+ EventEmitter.emit('drawer_opacity', opacity);
+
+ return {
+ mainOverlay: {
+ backgroundColor: '#000',
+ elevation: 3,
+ opacity
+ }
+ };
+ };
+
+ handleDrawerClose = () => {
+ if (this.closeHandle) {
+ InteractionManager.clearInteractionHandle(this.closeHandle);
+ this.closeHandle = null;
+ }
+ };
+
+ handleDrawerCloseStart = () => {
+ if (!this.closeHandle) {
+ this.closeHandle = InteractionManager.createInteractionHandle();
+ }
+ };
+
+ handleDrawerOpen = () => {
+ Keyboard.dismiss();
+
+ if (this.openHandle) {
+ InteractionManager.clearInteractionHandle(this.openHandle);
+ this.openHandle = null;
+ }
+ };
+
+ handleDrawerOpenStart = () => {
+ if (!this.openHandle) {
+ this.openHandle = InteractionManager.createInteractionHandle();
+ }
+ };
+
+ goToSettings = wrapWithPreventDoubleTap(() => {
+ const {intl} = this.context;
+ const {navigator, theme} = this.props;
+
+ this.closeSettingsDrawer();
+ navigator.showModal({
+ screen: 'Settings',
+ title: intl.formatMessage({id: 'mobile.routes.settings', defaultMessage: 'Settings'}),
+ animationType: 'slide-up',
+ animated: true,
+ backButtonTitle: '',
+ navigatorStyle: {
+ navBarTextColor: theme.sidebarHeaderTextColor,
+ navBarBackgroundColor: theme.sidebarHeaderBg,
+ navBarButtonColor: theme.sidebarHeaderTextColor,
+ screenBackgroundColor: theme.centerChannelBg
+ },
+ navigatorButtons: {
+ leftButtons: [{
+ id: 'close-settings',
+ icon: this.closeButton
+ }]
+ }
+ });
+ });
+
+ logout = wrapWithPreventDoubleTap(() => {
+ const {logout} = this.props.actions;
+ this.closeSettingsDrawer();
+ InteractionManager.runAfterInteractions(logout);
+ });
+
+ renderUserStatusIcon = (userId) => {
+ return (
+
+ );
+ };
+
+ renderUserStatusLabel = (userId) => {
+ return (
+
+ );
+ };
+
+ renderContent = () => {
+ const {currentUser, navigator, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ return (
+ }
+ headerComponent={}
+ navigator={navigator}
+ theme={theme}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ };
+
+ render() {
+ const {children} = this.props;
+
+ return (
+
+ {children}
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.03)
+ },
+ wrapper: {
+ flex: 1,
+ paddingTop: 0
+ },
+ block: {
+ borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
+ borderBottomWidth: 1,
+ borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
+ borderTopWidth: 1
+ },
+ divider: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ height: 1
+ },
+ separator: {
+ marginTop: 35
+ }
+ };
+});
diff --git a/app/components/settings_drawer/status_label/index.js b/app/components/settings_drawer/status_label/index.js
new file mode 100644
index 000000000..2908adb66
--- /dev/null
+++ b/app/components/settings_drawer/status_label/index.js
@@ -0,0 +1,18 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+import {getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
+
+import StatusLabel from './status_label';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ status: getStatusForUserId(state, ownProps.userId),
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(StatusLabel);
diff --git a/app/components/settings_drawer/status_label/status_label.js b/app/components/settings_drawer/status_label/status_label.js
new file mode 100644
index 000000000..4286f7b3e
--- /dev/null
+++ b/app/components/settings_drawer/status_label/status_label.js
@@ -0,0 +1,63 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+
+import {General} from 'mattermost-redux/constants';
+
+import FormattedText from 'app/components/formatted_text';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default class UserInfo extends PureComponent {
+ static propTypes = {
+ status: PropTypes.string,
+ theme: PropTypes.object.isRequired
+ };
+
+ static defaultProps = {
+ status: General.OFFLINE
+ };
+
+ render() {
+ const {status, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ let i18nId = 'status_dropdown.set_offline';
+ let defaultMessage = 'Offline';
+ switch (status) {
+ case General.AWAY:
+ i18nId = 'status_dropdown.set_away';
+ defaultMessage = 'Away';
+ break;
+ case General.DND:
+ i18nId = 'status_dropdown.set_dnd';
+ defaultMessage = 'Do Not Disturb';
+ break;
+ case General.ONLINE:
+ i18nId = 'status_dropdown.set_online';
+ defaultMessage = 'Online';
+ break;
+ }
+
+ return (
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ label: {
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ flex: 1,
+ fontSize: 17,
+ textAlignVertical: 'center',
+ includeFontPadding: false
+ }
+ };
+});
diff --git a/app/components/settings_drawer/user_info/index.js b/app/components/settings_drawer/user_info/index.js
new file mode 100644
index 000000000..ed2502623
--- /dev/null
+++ b/app/components/settings_drawer/user_info/index.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+
+import UserInfo from './user_info';
+
+function mapStateToProps(state) {
+ return {
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(UserInfo);
diff --git a/app/components/settings_drawer/user_info/user_info.js b/app/components/settings_drawer/user_info/user_info.js
new file mode 100644
index 000000000..63a955e89
--- /dev/null
+++ b/app/components/settings_drawer/user_info/user_info.js
@@ -0,0 +1,85 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {Text, TouchableOpacity, View} from 'react-native';
+
+import {emptyFunction} from 'app/utils/general';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+import ProfilePicture from 'app/components/profile_picture';
+
+export default class UserInfo extends PureComponent {
+ static propTypes = {
+ user: PropTypes.object.isRequired,
+ onPress: PropTypes.func,
+ theme: PropTypes.object.isRequired
+ };
+
+ static defaultProps = {
+ onPress: emptyFunction
+ };
+
+ render() {
+ const {user, onPress, theme} = this.props;
+ const {first_name: firstName, last_name: lastName} = user;
+ const style = getStyleSheet(theme);
+
+ let fullName;
+ if (firstName && lastName) {
+ fullName = `${firstName} ${lastName}`;
+ } else if (firstName) {
+ fullName = firstName;
+ } else if (lastName) {
+ fullName = lastName;
+ }
+
+ return (
+
+
+
+
+
+
+ {`@${user.username}`}
+
+
+ {Boolean(fullName) &&
+
+ {fullName}
+
+ }
+
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ alignItems: 'center',
+ padding: 15,
+ flexDirection: 'row'
+ },
+ wrapper: {
+ marginLeft: 10
+ },
+ username: {
+ color: theme.centerChannelColor,
+ marginVertical: 5,
+ fontWeight: '600',
+ fontSize: 15
+ },
+ fullName: {
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ fontSize: 15
+ }
+ };
+});
diff --git a/app/components/user_status/index.js b/app/components/user_status/index.js
new file mode 100644
index 000000000..8aa725cea
--- /dev/null
+++ b/app/components/user_status/index.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+import {getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
+
+import UserStatus from './user_status';
+
+function mapStateToProps(state, ownProps) {
+ let status = ownProps.status;
+ if (!status && ownProps.userId) {
+ status = getStatusForUserId(state, ownProps.userId);
+ }
+
+ return {
+ status,
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(UserStatus);
diff --git a/app/components/user_status/user_status.js b/app/components/user_status/user_status.js
new file mode 100644
index 000000000..e195800f4
--- /dev/null
+++ b/app/components/user_status/user_status.js
@@ -0,0 +1,58 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+
+import {General} from 'mattermost-redux/constants';
+
+import {AwayIcon, DndIcon, OfflineIcon, OnlineIcon} from 'app/components/status_icons';
+
+const statusToIcon = {
+ away: AwayIcon,
+ dnd: DndIcon,
+ offline: OfflineIcon,
+ online: OnlineIcon
+};
+
+export default class UserStatus extends PureComponent {
+ static propTypes = {
+ size: PropTypes.number,
+ status: PropTypes.string,
+ theme: PropTypes.object.isRequired
+ };
+
+ static defaultProps = {
+ size: 14,
+ status: General.OFFLINE
+ };
+
+ render() {
+ const {size, status, theme} = this.props;
+ const Icon = statusToIcon[status];
+
+ let iconColor;
+ switch (status) {
+ case General.AWAY:
+ iconColor = theme.awayIndicator;
+ break;
+ case General.DND:
+ iconColor = theme.dndIndicator;
+ break;
+ case General.ONLINE:
+ iconColor = theme.onlineIndicator;
+ break;
+ default:
+ iconColor = theme.centerChannelColor;
+ break;
+ }
+
+ return (
+
+ );
+ }
+}
diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js
index 6465fdf4c..8131c5b07 100644
--- a/app/screens/channel/channel.js
+++ b/app/screens/channel/channel.js
@@ -8,10 +8,12 @@ import {
Platform,
View
} from 'react-native';
+
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import ClientUpgradeListener from 'app/components/client_upgrade_listener';
import ChannelDrawer from 'app/components/channel_drawer';
+import SettingsDrawer from 'app/components/settings_drawer';
import ChannelLoader from 'app/components/channel_loader';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import Loading from 'app/components/loading';
@@ -103,6 +105,18 @@ class Channel extends PureComponent {
this.postTextbox.getWrappedInstance().getWrappedInstance().blur();
};
+ channelDrawerRef = (ref) => {
+ if (ref) {
+ this.channelDrawer = ref.getWrappedInstance();
+ }
+ };
+
+ settingsDrawerRef = (ref) => {
+ if (ref) {
+ this.settingsDrawer = ref.getWrappedInstance();
+ }
+ };
+
goToChannelInfo = wrapWithPreventDoubleTap(() => {
const {intl, navigator, theme} = this.props;
const options = {
@@ -164,6 +178,18 @@ class Channel extends PureComponent {
});
};
+ openChannelDrawer = () => {
+ if (this.channelDrawer) {
+ this.channelDrawer.openChannelDrawer();
+ }
+ };
+
+ openSettingsDrawer = () => {
+ if (this.settingsDrawer) {
+ this.settingsDrawer.openSettingsDrawer();
+ }
+ };
+
retryLoadChannels = () => {
this.loadChannels(this.props.currentTeamId);
};
@@ -197,30 +223,39 @@ class Channel extends PureComponent {
return (
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
- {LocalConfig.EnableMobileClientUpgrade && }
-
+
+
+
+
+
+
+
+
+ {LocalConfig.EnableMobileClientUpgrade && }
+
+
);
}
diff --git a/app/screens/channel/channel_nav_bar/channel_drawer_button.js b/app/screens/channel/channel_nav_bar/channel_drawer_button.js
index 701c1558c..a6c6c47fd 100644
--- a/app/screens/channel/channel_nav_bar/channel_drawer_button.js
+++ b/app/screens/channel/channel_nav_bar/channel_drawer_button.js
@@ -16,7 +16,7 @@ import Icon from 'react-native-vector-icons/Ionicons';
import Badge from 'app/components/badge';
import PushNotifications from 'app/push_notifications';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
-import {preventDoubleTap} from 'app/utils/tap';
+import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import {getUnreadsInCurrentTeam} from 'mattermost-redux/selectors/entities/channels';
@@ -26,10 +26,11 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
class ChannelDrawerButton extends PureComponent {
static propTypes = {
currentTeamId: PropTypes.string.isRequired,
- myTeamMembers: PropTypes.object,
- theme: PropTypes.object,
+ openDrawer: PropTypes.func.isRequired,
messageCount: PropTypes.number,
- mentionCount: PropTypes.number
+ mentionCount: PropTypes.number,
+ myTeamMembers: PropTypes.object,
+ theme: PropTypes.object
};
static defaultProps = {
@@ -74,9 +75,9 @@ class ChannelDrawerButton extends PureComponent {
this.setState({opacity: value > 0 ? 0.1 : 1});
};
- handlePress = () => {
- EventEmitter.emit('open_channel_drawer');
- };
+ handlePress = wrapWithPreventDoubleTap(() => {
+ this.props.openDrawer();
+ });
render() {
const {
@@ -113,23 +114,27 @@ class ChannelDrawerButton extends PureComponent {
count={badgeCount}
minHeight={20}
minWidth={20}
- onPress={() => preventDoubleTap(this.handlePress, this)}
+ onPress={this.handlePress}
/>
);
}
+ const icon = (
+
+ );
+
return (
-
-
+
+ {icon}
{badge}
@@ -140,7 +145,8 @@ class ChannelDrawerButton extends PureComponent {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
container: {
- width: 40
+ width: 40,
+ zIndex: 45
},
wrapper: {
alignItems: 'center',
diff --git a/app/screens/channel/channel_nav_bar/channel_nav_bar.js b/app/screens/channel/channel_nav_bar/channel_nav_bar.js
index 63e0e2296..5ade262e5 100644
--- a/app/screens/channel/channel_nav_bar/channel_nav_bar.js
+++ b/app/screens/channel/channel_nav_bar/channel_nav_bar.js
@@ -11,11 +11,14 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
import ChannelDrawerButton from './channel_drawer_button';
import ChannelSearchButton from './channel_search_button';
import ChannelTitle from './channel_title';
+import SettingDrawerButton from './settings_drawer_button';
export default class ChannelNavBar extends PureComponent {
static propTypes = {
isLandscape: PropTypes.bool.isRequired,
navigator: PropTypes.object.isRequired,
+ openChannelDrawer: PropTypes.func.isRequired,
+ openSettingsDrawer: PropTypes.func.isRequired,
onPress: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired
};
@@ -27,7 +30,8 @@ export default class ChannelNavBar extends PureComponent {
}
render() {
- const {isLandscape, navigator, theme, onPress} = this.props;
+ const {isLandscape, navigator, onPress, theme} = this.props;
+ const {openChannelDrawer, openSettingsDrawer} = this.props;
const style = getStyleFromTheme(theme);
const padding = {paddingHorizontal: 0};
@@ -45,12 +49,17 @@ export default class ChannelNavBar extends PureComponent {
return (
-
-
+
+
+
+
);
}
diff --git a/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js b/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js
index e7eecab89..6439edd89 100644
--- a/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js
+++ b/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js
@@ -7,13 +7,13 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
- StyleSheet,
TouchableOpacity,
View
} from 'react-native';
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
+import {makeStyleSheetFromTheme} from 'app/utils/theme';
const SEARCH = 'search';
@@ -50,33 +50,45 @@ export default class ChannelSearchButton extends PureComponent {
theme
} = this.props;
+ const style = getStyle(theme);
+
return (
-
-
-
-
-
+
+
+
+
+
+
+
);
}
}
-const style = StyleSheet.create({
- container: {
- width: 40
- },
- wrapper: {
- alignItems: 'center',
- flex: 1,
- flexDirection: 'column',
- justifyContent: 'center',
- paddingHorizontal: 10,
- zIndex: 30
- }
+const getStyle = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ width: 40,
+ zIndex: 45
+ },
+ flex: {
+ flex: 1
+ },
+ wrapper: {
+ alignItems: 'flex-end',
+ flex: 1,
+ flexDirection: 'column',
+ justifyContent: 'center'
+ },
+ icon: {
+ backgroundColor: theme.sidebarHeaderBg,
+ color: theme.sidebarHeaderTextColor
+ }
+ };
});
diff --git a/app/screens/channel/channel_nav_bar/channel_title/channel_title.js b/app/screens/channel/channel_nav_bar/channel_title/channel_title.js
index c2c2526cd..edb8d335e 100644
--- a/app/screens/channel/channel_nav_bar/channel_title/channel_title.js
+++ b/app/screens/channel/channel_nav_bar/channel_title/channel_title.js
@@ -16,7 +16,9 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
export default class ChannelTitle extends PureComponent {
static propTypes = {
currentChannelName: PropTypes.string,
+ deviceWidth: PropTypes.number,
displayName: PropTypes.string,
+ height: PropTypes.number,
onPress: PropTypes.func,
theme: PropTypes.object
};
@@ -28,7 +30,7 @@ export default class ChannelTitle extends PureComponent {
};
render() {
- const {currentChannelName, displayName, onPress, theme} = this.props;
+ const {currentChannelName, deviceWidth, displayName, height, onPress, theme} = this.props;
const channelName = displayName || currentChannelName;
const style = getStyle(theme);
let icon;
@@ -44,18 +46,20 @@ export default class ChannelTitle extends PureComponent {
return (
-
-
- {channelName}
-
- {icon}
+
+
+
+ {channelName}
+
+ {icon}
+
);
@@ -65,24 +69,28 @@ export default class ChannelTitle extends PureComponent {
const getStyle = makeStyleSheetFromTheme((theme) => {
return {
container: {
- flex: 1,
- flexDirection: 'row'
+ position: 'absolute',
+ zIndex: 40
},
wrapper: {
alignItems: 'center',
flex: 1,
+ justifyContent: 'center'
+ },
+ innerContainer: {
+ alignItems: 'center',
flexDirection: 'row',
- justifyContent: 'center',
- marginHorizontal: 15
+ marginHorizontal: 55
},
icon: {
color: theme.sidebarHeaderTextColor,
- marginHorizontal: 6
+ marginHorizontal: 5
},
text: {
color: theme.sidebarHeaderTextColor,
fontSize: 17,
- fontWeight: 'bold'
+ fontWeight: 'bold',
+ textAlign: 'center'
}
};
});
diff --git a/app/screens/channel/channel_nav_bar/channel_title/index.js b/app/screens/channel/channel_nav_bar/channel_title/index.js
index c7cabb106..663465b17 100644
--- a/app/screens/channel/channel_nav_bar/channel_title/index.js
+++ b/app/screens/channel/channel_nav_bar/channel_title/index.js
@@ -4,15 +4,17 @@
import {connect} from 'react-redux';
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
-
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+import {getDimensions} from 'app/selectors/device';
+
import ChannelTitle from './channel_title';
function mapStateToProps(state) {
const currentChannel = getCurrentChannel(state);
return {
+ ...getDimensions(state),
currentChannelName: currentChannel ? currentChannel.display_name : '',
displayName: state.views.channel.displayName,
theme: getTheme(state)
diff --git a/app/screens/channel/channel_nav_bar/settings_drawer_button.js b/app/screens/channel/channel_nav_bar/settings_drawer_button.js
new file mode 100644
index 000000000..cb69c1c14
--- /dev/null
+++ b/app/screens/channel/channel_nav_bar/settings_drawer_button.js
@@ -0,0 +1,84 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {connect} from 'react-redux';
+import {
+ TouchableOpacity,
+ View
+} from 'react-native';
+
+import Icon from 'react-native-vector-icons/Ionicons';
+
+import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+import {wrapWithPreventDoubleTap} from 'app/utils/tap';
+import {makeStyleSheetFromTheme} from 'app/utils/theme';
+
+class SettingDrawerButton extends PureComponent {
+ static propTypes = {
+ openDrawer: PropTypes.func.isRequired,
+ theme: PropTypes.object
+ };
+
+ static defaultProps = {
+ theme: {}
+ };
+
+ handlePress = wrapWithPreventDoubleTap(() => {
+ this.props.openDrawer();
+ });
+
+ render() {
+ const {theme} = this.props;
+ const style = getStyleFromTheme(theme);
+
+ const icon = (
+
+ );
+
+ return (
+
+
+ {icon}
+
+
+ );
+ }
+}
+
+const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ width: 44,
+ zIndex: 45
+ },
+ wrapper: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ marginLeft: 8,
+ zIndex: 30
+ },
+ mention: {
+ color: theme.mentionColor,
+ fontSize: 10
+ }
+ };
+});
+
+function mapStateToProps(state) {
+ return {
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(SettingDrawerButton);
diff --git a/app/screens/settings/general/index.js b/app/screens/settings/general/index.js
index 9bcb427d1..ce7cc36bd 100644
--- a/app/screens/settings/general/index.js
+++ b/app/screens/settings/general/index.js
@@ -5,7 +5,6 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {clearErrors} from 'mattermost-redux/actions/errors';
-import {logout} from 'mattermost-redux/actions/users';
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
import {getJoinableTeams} from 'mattermost-redux/selectors/entities/teams';
@@ -33,7 +32,6 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
clearErrors,
- logout,
purgeOfflineStore
}, dispatch)
};
diff --git a/app/screens/settings/general/settings.js b/app/screens/settings/general/settings.js
index 3822b8fcc..9fd3953fc 100644
--- a/app/screens/settings/general/settings.js
+++ b/app/screens/settings/general/settings.js
@@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape, injectIntl} from 'react-intl';
import {
- InteractionManager,
Linking,
Platform,
ScrollView,
@@ -25,7 +24,6 @@ class Settings extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
clearErrors: PropTypes.func.isRequired,
- logout: PropTypes.func.isRequired,
purgeOfflineStore: PropTypes.func.isRequired
}).isRequired,
config: PropTypes.object.isRequired,
@@ -166,11 +164,6 @@ class Settings extends PureComponent {
});
});
- logout = wrapWithPreventDoubleTap(() => {
- const {logout} = this.props.actions;
- InteractionManager.runAfterInteractions(logout);
- });
-
onNavigatorEvent = (event) => {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'close-settings') {
@@ -271,15 +264,15 @@ class Settings extends PureComponent {
theme={theme}
/>
{LocalConfig.EnableMobileClientUpgrade && LocalConfig.EnableMobileClientUpgradeUserSetting &&
-
+
}
-
-
-
-
-
);
@@ -329,9 +308,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
divider: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
height: 1
- },
- footer: {
- marginTop: 35
}
};
});