Right drawer (#1275)
* Add right sidebar * initial implementation of the right drawer * Do not hide status bar on drawer open * initial options in the RHD * Fix android layout * feedback review
This commit is contained in:
parent
db3556325e
commit
a708a1755b
26 changed files with 1058 additions and 252 deletions
|
|
@ -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 (
|
||||
<SafeAreaView
|
||||
backgroundColor={theme.sidebarHeaderBg}
|
||||
footerColor={theme.sidebarHeaderBg}
|
||||
navigator={navigator}
|
||||
>
|
||||
<DrawerSwiper
|
||||
|
|
|
|||
|
|
@ -5,15 +5,12 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Platform,
|
||||
TouchableHighlight,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import FilteredList from './filtered_list';
|
||||
|
|
@ -57,30 +54,6 @@ class ChannelsList extends React.PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
openSettingsModal = wrapWithPreventDoubleTap(() => {
|
||||
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 = (
|
||||
<TouchableHighlight
|
||||
style={styles.settingsContainer}
|
||||
onPress={this.openSettingsModal}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
<AwesomeIcon
|
||||
name='cog'
|
||||
style={styles.settings}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
|
||||
list = (
|
||||
<List
|
||||
navigator={navigator}
|
||||
|
|
@ -183,7 +142,6 @@ class ChannelsList extends React.PureComponent {
|
|||
/>
|
||||
</View>
|
||||
{title}
|
||||
{settings}
|
||||
</View>
|
||||
</View>
|
||||
{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
|
||||
|
|
|
|||
|
|
@ -43,4 +43,4 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelDrawer);
|
||||
export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(ChannelDrawer);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function mapStateToProps(state, ownProps) {
|
|||
}
|
||||
|
||||
return {
|
||||
theme: ownProps.theme || getTheme(state),
|
||||
theme: getTheme(state),
|
||||
status,
|
||||
user
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
<Icon
|
||||
height={this.props.statusSize}
|
||||
width={this.props.statusSize}
|
||||
color={iconColor}
|
||||
<UserStatus
|
||||
size={this.props.statusSize}
|
||||
status={this.props.status}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
@ -100,7 +68,7 @@ export default class ProfilePicture extends PureComponent {
|
|||
source={{uri: pictureUrl}}
|
||||
defaultSource={placeholder}
|
||||
/>
|
||||
{this.props.status &&
|
||||
{this.props.showStatus &&
|
||||
<View style={[style.statusWrapper, {borderRadius: this.props.statusSize / 2}]}>
|
||||
{statusIcon}
|
||||
</View>
|
||||
|
|
@ -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'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={style.container}>
|
||||
{this.props.children}
|
||||
<View style={[style.container, {backgroundColor: bgColor}]}>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: topColor,
|
||||
height: top,
|
||||
zIndex: 10
|
||||
}}
|
||||
>
|
||||
{headerComponent}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
|
|
@ -132,7 +149,7 @@ export default class SafeAreaIos extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {backgroundColor, children, keyboardOffset, theme} = this.props;
|
||||
const {backgroundColor, children, footerColor, footerComponent, keyboardOffset, theme} = this.props;
|
||||
const {keyboard, safeAreaInsets} = this.state;
|
||||
|
||||
let bgColor = theme.centerChannelBg;
|
||||
|
|
@ -140,6 +157,11 @@ export default class SafeAreaIos extends PureComponent {
|
|||
bgColor = backgroundColor;
|
||||
}
|
||||
|
||||
let bottomColor = theme.centerChannelBg;
|
||||
if (footerColor) {
|
||||
bottomColor = footerColor;
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
if (keyboardOffset && this.isX) {
|
||||
offset = keyboardOffset;
|
||||
|
|
@ -149,12 +171,14 @@ export default class SafeAreaIos extends PureComponent {
|
|||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingBottom: keyboard ? offset : safeAreaInsets.bottom - 15,
|
||||
backgroundColor: bgColor
|
||||
}}
|
||||
>
|
||||
{this.renderTopBar()}
|
||||
{children}
|
||||
<View style={{height: keyboard ? offset : safeAreaInsets.bottom - 15, backgroundColor: bottomColor}}>
|
||||
{footerComponent}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
150
app/components/settings_drawer/drawer_item.js
Normal file
150
app/components/settings_drawer/drawer_item.js
Normal file
|
|
@ -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 = (<View style={style.divider}/>);
|
||||
}
|
||||
|
||||
let icon;
|
||||
if (leftComponent) {
|
||||
icon = leftComponent;
|
||||
} else if (iconType && iconName) {
|
||||
icon = (
|
||||
<VectorIcon
|
||||
name={iconName}
|
||||
type={iconType}
|
||||
style={[style.icon, destructor]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let label;
|
||||
if (labelComponent) {
|
||||
label = labelComponent;
|
||||
} else if (i18nId) {
|
||||
label = (
|
||||
<FormattedText
|
||||
id={i18nId}
|
||||
defaultMessage={defaultMessage}
|
||||
style={[style.label, destructor, centered ? style.centerLabel : {}]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={style.container}>
|
||||
{icon &&
|
||||
<View style={style.iconContainer}>
|
||||
{icon}
|
||||
</View>
|
||||
}
|
||||
<View style={style.wrapper}>
|
||||
<View style={style.labelContainer}>
|
||||
{label}
|
||||
</View>
|
||||
{divider}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
});
|
||||
28
app/components/settings_drawer/index.js
Normal file
28
app/components/settings_drawer/index.js
Normal file
|
|
@ -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);
|
||||
310
app/components/settings_drawer/settings_drawer.js
Normal file
310
app/components/settings_drawer/settings_drawer.js
Normal file
|
|
@ -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 (
|
||||
<UserStatus
|
||||
size={18}
|
||||
userId={userId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderUserStatusLabel = (userId) => {
|
||||
return (
|
||||
<StatusLabel userId={userId}/>
|
||||
);
|
||||
};
|
||||
|
||||
renderContent = () => {
|
||||
const {currentUser, navigator, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
backgroundColor={theme.centerChannelBg}
|
||||
navBarBackgroundColor={theme.centerChannelBg}
|
||||
footerColor={theme.centerChannelBg}
|
||||
footerComponent={<View style={style.container}/>}
|
||||
headerComponent={<View style={style.container}/>}
|
||||
navigator={navigator}
|
||||
theme={theme}
|
||||
>
|
||||
<View style={style.container}>
|
||||
<ScrollView
|
||||
alwaysBounceVertical={false}
|
||||
contentContainerStyle={style.wrapper}
|
||||
>
|
||||
<UserInfo user={currentUser}/>
|
||||
<View style={style.block}>
|
||||
<DrawerItem
|
||||
labelComponent={this.renderUserStatusLabel(currentUser.id)}
|
||||
leftComponent={this.renderUserStatusIcon(currentUser.id)}
|
||||
separator={true}
|
||||
theme={theme}
|
||||
/>
|
||||
<DrawerItem
|
||||
defaultMessage='Settings'
|
||||
i18nId='mobile.routes.settings'
|
||||
iconName='ios-options'
|
||||
iconType='ion'
|
||||
onPress={this.goToSettings}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.separator}/>
|
||||
<View style={style.block}>
|
||||
<DrawerItem
|
||||
centered={true}
|
||||
defaultMessage='Logout'
|
||||
i18nId='sidebar_right_menu.logout'
|
||||
isDestructor={true}
|
||||
onPress={this.logout}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {children} = this.props;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
ref='drawer'
|
||||
onOpenStart={this.handleDrawerOpenStart}
|
||||
onOpen={this.handleDrawerOpen}
|
||||
onClose={this.handleDrawerClose}
|
||||
onCloseStart={this.handleDrawerCloseStart}
|
||||
side='right'
|
||||
captureGestures='open'
|
||||
type='overlay'
|
||||
acceptTap={true}
|
||||
acceptPanOnDrawer={false}
|
||||
disabled={false}
|
||||
content={this.renderContent()}
|
||||
tapToClose={true}
|
||||
openDrawerOffset={DRAWER_INITIAL_OFFSET}
|
||||
onRequestClose={this.closeSettingsDrawer}
|
||||
panOpenMask={0.05}
|
||||
panCloseMask={DRAWER_INITIAL_OFFSET}
|
||||
panThreshold={0.25}
|
||||
acceptPan={true}
|
||||
negotiatePan={true}
|
||||
useInteractionManager={false}
|
||||
tweenDuration={100}
|
||||
tweenHandler={this.handleDrawerTween}
|
||||
elevation={5}
|
||||
bottomPanOffset={Platform.OS === 'ios' ? 46 : 64}
|
||||
topPanOffset={Platform.OS === 'ios' ? 64 : 46}
|
||||
styles={{
|
||||
main: {
|
||||
shadowColor: '#000000',
|
||||
shadowOpacity: 0.4,
|
||||
shadowRadius: 12,
|
||||
shadowOffset: {
|
||||
width: -4,
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
});
|
||||
18
app/components/settings_drawer/status_label/index.js
Normal file
18
app/components/settings_drawer/status_label/index.js
Normal file
|
|
@ -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);
|
||||
63
app/components/settings_drawer/status_label/status_label.js
Normal file
63
app/components/settings_drawer/status_label/status_label.js
Normal file
|
|
@ -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 (
|
||||
<FormattedText
|
||||
id={i18nId}
|
||||
defaultMessage={defaultMessage}
|
||||
style={style.label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
label: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
textAlignVertical: 'center',
|
||||
includeFontPadding: false
|
||||
}
|
||||
};
|
||||
});
|
||||
16
app/components/settings_drawer/user_info/index.js
Normal file
16
app/components/settings_drawer/user_info/index.js
Normal file
|
|
@ -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);
|
||||
85
app/components/settings_drawer/user_info/user_info.js
Normal file
85
app/components/settings_drawer/user_info/user_info.js
Normal file
|
|
@ -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 (
|
||||
<TouchableOpacity onPress={onPress}>
|
||||
<View style={style.container}>
|
||||
<ProfilePicture
|
||||
size={50}
|
||||
showStatus={false}
|
||||
userId={user.id}
|
||||
/>
|
||||
<View style={style.wrapper}>
|
||||
<View>
|
||||
<Text style={style.username}>
|
||||
{`@${user.username}`}
|
||||
</Text>
|
||||
</View>
|
||||
{Boolean(fullName) &&
|
||||
<View>
|
||||
<Text style={style.fullName}>{fullName}</Text>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
});
|
||||
23
app/components/user_status/index.js
Normal file
23
app/components/user_status/index.js
Normal file
|
|
@ -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);
|
||||
58
app/components/user_status/user_status.js
Normal file
58
app/components/user_status/user_status.js
Normal file
|
|
@ -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 (
|
||||
<Icon
|
||||
height={size}
|
||||
width={size}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<ChannelDrawer
|
||||
ref={this.channelDrawerRef}
|
||||
blurPostTextBox={this.blurPostTextBox}
|
||||
intl={intl}
|
||||
navigator={navigator}
|
||||
>
|
||||
<SafeAreaView navigator={navigator}>
|
||||
<StatusBar/>
|
||||
<OfflineIndicator/>
|
||||
<ChannelNavBar
|
||||
navigator={navigator}
|
||||
onPress={this.goToChannelInfo}
|
||||
/>
|
||||
<KeyboardLayout>
|
||||
<View style={style.postList}>
|
||||
<ChannelPostList navigator={navigator}/>
|
||||
</View>
|
||||
<ChannelLoader theme={theme}/>
|
||||
<PostTextbox
|
||||
ref={this.attachPostTextbox}
|
||||
<SettingsDrawer
|
||||
ref={this.settingsDrawerRef}
|
||||
blurPostTextBox={this.blurPostTextBox}
|
||||
navigator={navigator}
|
||||
>
|
||||
<SafeAreaView navigator={navigator}>
|
||||
<StatusBar/>
|
||||
<OfflineIndicator/>
|
||||
<ChannelNavBar
|
||||
navigator={navigator}
|
||||
openChannelDrawer={this.openChannelDrawer}
|
||||
openSettingsDrawer={this.openSettingsDrawer}
|
||||
onPress={this.goToChannelInfo}
|
||||
/>
|
||||
<ChannelLoader theme={theme}/>
|
||||
</KeyboardLayout>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
|
||||
</SafeAreaView>
|
||||
<KeyboardLayout>
|
||||
<View style={style.postList}>
|
||||
<ChannelPostList navigator={navigator}/>
|
||||
</View>
|
||||
<ChannelLoader theme={theme}/>
|
||||
<PostTextbox
|
||||
ref={this.attachPostTextbox}
|
||||
navigator={navigator}
|
||||
/>
|
||||
<ChannelLoader theme={theme}/>
|
||||
</KeyboardLayout>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
|
||||
</SafeAreaView>
|
||||
</SettingsDrawer>
|
||||
</ChannelDrawer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
<Icon
|
||||
name='md-menu'
|
||||
size={25}
|
||||
color={theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
{...this.panResponder.panHandlers}
|
||||
onPress={this.handlePress}
|
||||
style={style.container}
|
||||
>
|
||||
<View style={[style.wrapper, {opacity: this.state.opacity, zIndex: 30}]}>
|
||||
<Icon
|
||||
name='md-menu'
|
||||
size={25}
|
||||
color={theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
<View style={[style.wrapper, {opacity: this.state.opacity}]}>
|
||||
{icon}
|
||||
{badge}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
|
@ -140,7 +145,8 @@ class ChannelDrawerButton extends PureComponent {
|
|||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
width: 40
|
||||
width: 40,
|
||||
zIndex: 45
|
||||
},
|
||||
wrapper: {
|
||||
alignItems: 'center',
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={[style.header, padding, {height}]}>
|
||||
<ChannelDrawerButton/>
|
||||
<ChannelTitle onPress={onPress}/>
|
||||
<ChannelDrawerButton openDrawer={openChannelDrawer}/>
|
||||
<ChannelTitle
|
||||
onPress={onPress}
|
||||
height={height}
|
||||
/>
|
||||
<View style={{flex: 1}}/>
|
||||
<ChannelSearchButton
|
||||
navigator={navigator}
|
||||
theme={theme}
|
||||
/>
|
||||
<SettingDrawerButton openDrawer={openSettingsDrawer}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<TouchableOpacity
|
||||
onPress={this.handlePress}
|
||||
style={style.container}
|
||||
>
|
||||
<View style={style.wrapper}>
|
||||
<AwesomeIcon
|
||||
name='search'
|
||||
size={18}
|
||||
color={theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View style={style.container}>
|
||||
<TouchableOpacity
|
||||
onPress={this.handlePress}
|
||||
style={style.flex}
|
||||
>
|
||||
<View style={style.wrapper}>
|
||||
<AwesomeIcon
|
||||
name='search'
|
||||
size={18}
|
||||
style={style.icon}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<TouchableOpacity
|
||||
style={style.container}
|
||||
style={[style.container, {height, width: deviceWidth}]}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={style.wrapper}>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.text}
|
||||
>
|
||||
{channelName}
|
||||
</Text>
|
||||
{icon}
|
||||
<View style={[style.wrapper, {width: deviceWidth}]}>
|
||||
<View style={style.innerContainer}>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.text}
|
||||
>
|
||||
{channelName}
|
||||
</Text>
|
||||
{icon}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
|
@ -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'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
<Icon
|
||||
name='md-more'
|
||||
size={25}
|
||||
color={theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={this.handlePress}
|
||||
style={style.container}
|
||||
>
|
||||
<View style={style.wrapper}>
|
||||
{icon}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
<SettingsItem
|
||||
defaultMessage='Check for Upgrade'
|
||||
i18nId='mobile.settings.modal.check_for_upgrade'
|
||||
iconName='update'
|
||||
iconType='material'
|
||||
onPress={this.goToClientUpgrade}
|
||||
showArrow={showArrow}
|
||||
theme={theme}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultMessage='Check for Upgrade'
|
||||
i18nId='mobile.settings.modal.check_for_upgrade'
|
||||
iconName='update'
|
||||
iconType='material'
|
||||
onPress={this.goToClientUpgrade}
|
||||
showArrow={showArrow}
|
||||
theme={theme}
|
||||
/>
|
||||
}
|
||||
<SettingsItem
|
||||
defaultMessage='About Mattermost'
|
||||
|
|
@ -292,20 +285,6 @@ class Settings extends PureComponent {
|
|||
theme={theme}
|
||||
/>
|
||||
<View style={style.divider}/>
|
||||
<View style={style.footer}>
|
||||
<View style={style.divider}/>
|
||||
<SettingsItem
|
||||
centered={true}
|
||||
defaultMessage='Logout'
|
||||
i18nId='sidebar_right_menu.logout'
|
||||
isDestructor={true}
|
||||
onPress={this.logout}
|
||||
separator={false}
|
||||
showArrow={false}
|
||||
theme={theme}
|
||||
/>
|
||||
<View style={style.divider}/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -329,9 +308,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1
|
||||
},
|
||||
footer: {
|
||||
marginTop: 35
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue