Cold-start channel drawer improvements (#1702)
This commit is contained in:
parent
efa3f59d12
commit
e72bb48d98
13 changed files with 81 additions and 58 deletions
|
|
@ -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 = [];
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
BIN
assets/base/images/status/archive_avatar.png
Normal file
BIN
assets/base/images/status/archive_avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 325 B |
BIN
assets/base/images/status/away_avatar.png
Normal file
BIN
assets/base/images/status/away_avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 675 B |
BIN
assets/base/images/status/dnd_avatar.png
Normal file
BIN
assets/base/images/status/dnd_avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 553 B |
BIN
assets/base/images/status/offline_avatar.png
Normal file
BIN
assets/base/images/status/offline_avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 727 B |
BIN
assets/base/images/status/online_avatar.png
Normal file
BIN
assets/base/images/status/online_avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 664 B |
Loading…
Reference in a new issue