Cold-start channel drawer improvements (#1702)

This commit is contained in:
Elias Nahum 2018-05-24 11:04:24 -04:00 committed by Harrison Healey
parent efa3f59d12
commit e72bb48d98
13 changed files with 81 additions and 58 deletions

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
@ -70,11 +70,11 @@ export default class Autocomplete extends PureComponent {
keyboardDidShow = (e) => {
const {height} = e.endCoordinates;
this.setState({keyboardOffset: height});
}
};
keyboardDidHide = () => {
this.setState({keyboardOffset: 0});
}
};
listHeight() {
let offset = Platform.select({ios: 65, android: 75});
@ -85,6 +85,10 @@ export default class Autocomplete extends PureComponent {
}
render() {
if (!this.props.value) {
return null;
}
const style = getStyleFromTheme(this.props.theme);
const wrapperStyle = [];

View file

@ -18,7 +18,6 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
import Drawer from 'app/components/drawer';
import SafeAreaView from 'app/components/safe_area_view';
import {ViewTypes} from 'app/constants';
import {alertErrorWithFallback} from 'app/utils/general';
import tracker from 'app/utils/time_tracker';
import ChannelsList from './channels_list';
@ -69,6 +68,7 @@ export default class ChannelDrawer extends Component {
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
}
this.state = {
show: false,
openDrawerOffset,
};
}
@ -79,6 +79,7 @@ export default class ChannelDrawer extends Component {
componentDidMount() {
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.on('renderDrawer', this.handleShowDrawerContent);
EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
}
@ -100,7 +101,7 @@ export default class ChannelDrawer extends Component {
const {currentTeamId, isLandscape, teamsCount} = this.props;
const {openDrawerOffset} = this.state;
if (nextState.openDrawerOffset !== openDrawerOffset) {
if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== this.state.show) {
return true;
}
@ -112,6 +113,7 @@ export default class ChannelDrawer extends Component {
componentWillUnmount() {
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
EventEmitter.off('renderDrawer', this.handleShowDrawerContent);
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
}
@ -124,6 +126,10 @@ export default class ChannelDrawer extends Component {
return false;
};
handleShowDrawerContent = () => {
this.setState({show: true});
};
closeChannelDrawer = () => {
if (this.refs.drawer && this.refs.drawer.isOpened()) {
this.refs.drawer.close();
@ -246,6 +252,7 @@ export default class ChannelDrawer extends Component {
} = actions;
const displayValue = {displayName: channel.display_name};
const utils = require('app/utils/general');
let result;
if (channel.type === General.DM_CHANNEL) {
@ -256,7 +263,7 @@ export default class ChannelDrawer extends Component {
id: 'mobile.open_dm.error',
defaultMessage: "We couldn't open a direct message with {displayName}. Please check your connection and try again.",
};
alertErrorWithFallback(intl, result.error, dmFailedMessage, displayValue);
utils.alertErrorWithFallback(intl, result.error, dmFailedMessage, displayValue);
}
} else {
result = await joinChannel(currentUserId, currentTeamId, channel.id);
@ -266,7 +273,7 @@ export default class ChannelDrawer extends Component {
id: 'mobile.join_channel.error',
defaultMessage: "We couldn't join the channel {displayName}. Please check your connection and try again.",
};
alertErrorWithFallback(intl, result.error, joinFailedMessage, displayValue);
utils.alertErrorWithFallback(intl, result.error, joinFailedMessage, displayValue);
}
}
@ -324,9 +331,14 @@ export default class ChannelDrawer extends Component {
} = this.props;
const {
show,
openDrawerOffset,
} = this.state;
if (!show) {
return null;
}
const multipleTeams = teamsCount > 1;
const showTeams = openDrawerOffset !== 0 && multipleTeams;
if (this.drawerSwiper) {
@ -403,7 +415,7 @@ export default class ChannelDrawer extends Component {
onClose={this.handleDrawerClose}
onCloseStart={this.handleDrawerCloseStart}
captureGestures='open'
type='static'
type={Platform.OS === 'ios' ? 'static' : 'displace'}
acceptTap={true}
acceptPanOnDrawer={false}
disabled={false}

View file

@ -1,28 +1,27 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Platform,
View,
} from 'react-native';
import {injectIntl, intlShape} from 'react-intl';
import {intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import SearchBar from 'app/components/search_bar';
import {ViewTypes} from 'app/constants';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import FilteredList from './filtered_list';
import List from './list';
import SwitchTeamsButton from './switch_teams_button';
const {ANDROID_TOP_PORTRAIT} = ViewTypes;
let FilteredList = null;
class ChannelsList extends React.PureComponent {
export default class ChannelsList extends PureComponent {
static propTypes = {
intl: intlShape.isRequired,
navigator: PropTypes.object,
onJoinChannel: PropTypes.func.isRequired,
onSearchEnds: PropTypes.func.isRequired,
@ -32,6 +31,10 @@ class ChannelsList extends React.PureComponent {
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape.isRequired,
};
constructor(props) {
super(props);
@ -62,6 +65,9 @@ class ChannelsList extends React.PureComponent {
};
onSearchFocused = () => {
if (!FilteredList) {
FilteredList = require('./filtered_list').default;
}
this.setState({searching: true});
this.props.onSearchStart();
};
@ -73,8 +79,8 @@ class ChannelsList extends React.PureComponent {
};
render() {
const {intl} = this.context;
const {
intl,
navigator,
onShowTeams,
theme,
@ -243,5 +249,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
},
};
});
export default injectIntl(ChannelsList);

View file

@ -1,4 +1,4 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
@ -17,7 +17,6 @@ import {General} from 'mattermost-redux/constants';
import {debounce} from 'mattermost-redux/actions/helpers';
import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item';
import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_indicator';
import {ListTypes} from 'app/constants';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity} from 'app/utils/theme';
@ -27,6 +26,8 @@ const VIEWABILITY_CONFIG = {
waitForInteraction: true,
};
let UnreadIndicator = null;
export default class List extends PureComponent {
static propTypes = {
canCreatePrivateChannels: PropTypes.bool.isRequired,
@ -311,6 +312,9 @@ export default class List extends PureComponent {
};
emitUnreadIndicatorChange = debounce((showIndicator) => {
if (showIndicator && !UnreadIndicator) {
UnreadIndicator = require('app/components/channel_drawer/channels_list/unread_indicator').default;
}
this.setState({showIndicator});
}, 100);
@ -349,12 +353,14 @@ export default class List extends PureComponent {
stickySectionHeadersEnabled={false}
viewabilityConfig={VIEWABILITY_CONFIG}
/>
{showIndicator &&
<UnreadIndicator
show={showIndicator}
style={[styles.above, {width}]}
onPress={this.scrollToTop}
theme={theme}
/>
}
</View>
);
}

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
@ -6,12 +6,12 @@ import {connect} from 'react-redux';
import {joinChannel, markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/channels';
import {getTeams} from 'mattermost-redux/actions/teams';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeamId, getMyTeamsCount} from 'mattermost-redux/selectors/entities/teams';
import {handleSelectChannel, setChannelDisplayName, setChannelLoading} from 'app/actions/views/channel';
import {makeDirectChannel} from 'app/actions/views/more_dms';
import {isLandscape, isTablet} from 'app/selectors/device';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import ChannelDrawer from './channel_drawer.js';

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
@ -11,7 +11,7 @@ import {
TouchableHighlight,
View,
} from 'react-native';
import {injectIntl, intlShape} from 'react-intl';
import {intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import FormattedText from 'app/components/formatted_text';
@ -28,7 +28,7 @@ const VIEWABILITY_CONFIG = {
waitForInteraction: true,
};
class TeamsList extends PureComponent {
export default class TeamsList extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
handleTeamChange: PropTypes.func.isRequired,
@ -36,12 +36,15 @@ class TeamsList extends PureComponent {
closeChannelDrawer: PropTypes.func.isRequired,
currentTeamId: PropTypes.string.isRequired,
currentUrl: PropTypes.string.isRequired,
intl: intlShape.isRequired,
navigator: PropTypes.object.isRequired,
teamIds: PropTypes.array.isRequired,
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape.isRequired,
};
constructor(props) {
super(props);
@ -64,7 +67,8 @@ class TeamsList extends PureComponent {
};
goToSelectTeam = preventDoubleTap(() => {
const {currentUrl, intl, navigator, theme} = this.props;
const {intl} = this.context;
const {currentUrl, navigator, theme} = this.props;
navigator.showModal({
screen: 'SelectTeam',
@ -195,5 +199,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
},
};
});
export default injectIntl(TeamsList);

View file

@ -1,22 +1,15 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {
Image,
Text,
View,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import {
ArchiveIcon,
AwayAvatar,
DndAvatar,
OfflineAvatar,
OnlineAvatar,
} from 'app/components/status_icons';
import {General} from 'mattermost-redux/constants';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@ -108,47 +101,42 @@ export default class ChannelIcon extends React.PureComponent {
);
} else if (type === General.DM_CHANNEL && teammateDeletedAt) {
icon = (
<ArchiveIcon
width={size}
height={size}
color={offlineColor}
<Image
source={require('assets/images/status/archive_avatar.png')}
style={{width: size, height: size, tintColor: offlineColor}}
/>
);
} else if (type === General.DM_CHANNEL) {
switch (status) {
case General.AWAY:
icon = (
<AwayAvatar
width={size}
height={size}
color={theme.awayIndicator}
<Image
source={require('assets/images/status/away_avatar.png')}
style={{width: size, height: size, tintColor: theme.awayIndicator}}
/>
);
break;
case General.DND:
icon = (
<DndAvatar
width={size}
height={size}
color={theme.dndIndicator}
<Image
source={require('assets/images/status/dnd_avatar.png')}
style={{width: size, height: size, tintColor: theme.dndIndicator}}
/>
);
break;
case General.ONLINE:
icon = (
<OnlineAvatar
width={size}
height={size}
color={theme.onlineIndicator}
<Image
source={require('assets/images/status/online_avatar.png')}
style={{width: size, height: size, tintColor: theme.onlineIndicator}}
/>
);
break;
default:
icon = (
<OfflineAvatar
width={size}
height={size}
color={offlineColor}
<Image
source={require('assets/images/status/offline_avatar.png')}
style={{width: size, height: size, tintColor: offlineColor}}
/>
);
break;

View file

@ -81,6 +81,8 @@ export default class Channel extends PureComponent {
if (tracker.initialLoad) {
this.props.actions.recordLoadTime('Start time', 'initialLoad');
}
EventEmitter.emit('renderDrawer');
}
componentWillReceiveProps(nextProps) {
@ -99,10 +101,15 @@ export default class Channel extends PureComponent {
}
}
componentDidUpdate() {
componentDidUpdate(prevProps) {
if (tracker.teamSwitch) {
this.props.actions.recordLoadTime('Switch Team', 'teamSwitch');
}
// When the team changes emit the event to render the drawer content
if (this.props.currentChannelId && !prevProps.currentChannelId) {
EventEmitter.emit('renderDrawer');
}
}
componentWillUnmount() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B