Multiple performance improvements (#956)
* Update fastlane * Multiple performance improvements * Feedback review * Feedback review
This commit is contained in:
parent
a694122ffd
commit
1e434346ae
21 changed files with 325 additions and 232 deletions
|
|
@ -184,34 +184,35 @@ export function loadThreadIfNecessary(rootId, channelId) {
|
|||
export function selectInitialChannel(teamId) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {channels, currentChannelId, myMembers} = state.entities.channels;
|
||||
const {channels, myMembers} = state.entities.channels;
|
||||
const {currentUserId} = state.entities.users;
|
||||
const currentChannel = channels[currentChannelId];
|
||||
const {myPreferences} = state.entities.preferences;
|
||||
const lastChannelId = state.views.team.lastChannelForTeam[teamId] || '';
|
||||
const lastChannel = channels[lastChannelId];
|
||||
|
||||
const isDMVisible = currentChannel && currentChannel.type === General.DM_CHANNEL &&
|
||||
isDirectChannelVisible(currentUserId, myPreferences, currentChannel);
|
||||
const isDMVisible = lastChannel && lastChannel.type === General.DM_CHANNEL &&
|
||||
isDirectChannelVisible(currentUserId, myPreferences, lastChannel);
|
||||
|
||||
const isGMVisible = currentChannel && currentChannel.type === General.GM_CHANNEL &&
|
||||
isGroupChannelVisible(myPreferences, currentChannel);
|
||||
const isGMVisible = lastChannel && lastChannel.type === General.GM_CHANNEL &&
|
||||
isGroupChannelVisible(myPreferences, lastChannel);
|
||||
|
||||
if (currentChannel && myMembers[currentChannelId] &&
|
||||
(currentChannel.team_id === teamId || isDMVisible || isGMVisible)) {
|
||||
await handleSelectChannel(currentChannelId)(dispatch, getState);
|
||||
if (lastChannelId && myMembers[lastChannelId] &&
|
||||
(lastChannel.team_id === teamId || isDMVisible || isGMVisible)) {
|
||||
handleSelectChannel(lastChannelId)(dispatch, getState);
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = Object.values(channels).find((c) => c.team_id === teamId && c.name === General.DEFAULT_CHANNEL);
|
||||
if (channel) {
|
||||
dispatch(setChannelDisplayName(''));
|
||||
await handleSelectChannel(channel.id)(dispatch, getState);
|
||||
handleSelectChannel(channel.id)(dispatch, getState);
|
||||
} else {
|
||||
// Handle case when the default channel cannot be found
|
||||
// so we need to get the first available channel of the team
|
||||
const channelsInTeam = Object.values(channels).filter((c) => c.team_id === teamId);
|
||||
const firstChannel = channelsInTeam.length ? channelsInTeam[0].id : {id: ''};
|
||||
dispatch(setChannelDisplayName(''));
|
||||
await handleSelectChannel(firstChannel.id)(dispatch, getState);
|
||||
handleSelectChannel(firstChannel.id)(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -220,13 +221,15 @@ export function handleSelectChannel(channelId) {
|
|||
return async (dispatch, getState) => {
|
||||
const {currentTeamId} = getState().entities.teams;
|
||||
|
||||
selectChannel(channelId)(dispatch, getState);
|
||||
dispatch(setChannelLoading(false));
|
||||
|
||||
dispatch({
|
||||
type: ViewTypes.SET_LAST_CHANNEL_FOR_TEAM,
|
||||
teamId: currentTeamId,
|
||||
channelId
|
||||
});
|
||||
getChannelStats(channelId)(dispatch, getState);
|
||||
selectChannel(channelId)(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ export function handleTeamChange(team, selectChannel = true) {
|
|||
];
|
||||
|
||||
if (selectChannel) {
|
||||
const lastChannelId = state.views.team.lastChannelForTeam[team.id] || '';
|
||||
actions.push({type: ChannelTypes.SELECT_CHANNEL, data: lastChannelId});
|
||||
actions.push({type: ChannelTypes.SELECT_CHANNEL, data: ''});
|
||||
|
||||
const lastChannelId = state.views.team.lastChannelForTeam[team.id] || '';
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
viewChannel(lastChannelId, currentChannelId)(dispatch, getState);
|
||||
markChannelAsRead(lastChannelId, currentChannelId)(dispatch, getState);
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
|
||||
EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
|
||||
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
|
@ -94,6 +95,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
|
||||
EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
handleAndroidBack = () => {
|
||||
|
|
@ -106,7 +108,9 @@ export default class ChannelDrawer extends PureComponent {
|
|||
};
|
||||
|
||||
closeChannelDrawer = () => {
|
||||
this.setState({openDrawer: false});
|
||||
if (this.mounted) {
|
||||
this.setState({openDrawer: false});
|
||||
}
|
||||
};
|
||||
|
||||
drawerSwiperRef = (ref) => {
|
||||
|
|
@ -121,7 +125,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
this.closeLeftHandle = null;
|
||||
}
|
||||
|
||||
if (this.state.openDrawer) {
|
||||
if (this.state.openDrawer && this.mounted) {
|
||||
// The state doesn't get updated if you swipe to close
|
||||
this.setState({
|
||||
openDrawer: false
|
||||
|
|
@ -133,7 +137,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
if (!this.closeLeftHandle) {
|
||||
this.closeLeftHandle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleDrawerOpen = () => {
|
||||
if (this.state.openDrawerOffset !== 0) {
|
||||
|
|
@ -151,7 +155,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
this.openLeftHandle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
|
||||
if (!this.state.openDrawer) {
|
||||
if (!this.state.openDrawer && this.mounted) {
|
||||
// The state doesn't get updated if you swipe to open
|
||||
this.setState({
|
||||
openDrawer: true
|
||||
|
|
@ -188,9 +192,11 @@ export default class ChannelDrawer extends PureComponent {
|
|||
openChannelDrawer = () => {
|
||||
this.props.blurPostTextBox();
|
||||
|
||||
this.setState({
|
||||
openDrawer: true
|
||||
});
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
openDrawer: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
selectChannel = (channel) => {
|
||||
|
|
@ -207,18 +213,17 @@ export default class ChannelDrawer extends PureComponent {
|
|||
viewChannel
|
||||
} = actions;
|
||||
|
||||
markChannelAsRead(channel.id, currentChannelId);
|
||||
|
||||
if (channel.id !== currentChannelId) {
|
||||
setChannelLoading();
|
||||
viewChannel(currentChannelId);
|
||||
setChannelDisplayName(channel.display_name);
|
||||
}
|
||||
setChannelLoading();
|
||||
setChannelDisplayName(channel.display_name);
|
||||
|
||||
this.closeChannelDrawer();
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
handleSelectChannel(channel.id);
|
||||
markChannelAsRead(channel.id, currentChannelId);
|
||||
if (channel.id !== currentChannelId) {
|
||||
viewChannel(currentChannelId);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -368,6 +373,7 @@ export default class ChannelDrawer extends PureComponent {
|
|||
onOpenStart={this.handleDrawerOpenStart}
|
||||
onOpen={this.handleDrawerOpen}
|
||||
onClose={this.handleDrawerClose}
|
||||
onCloseStart={this.handleDrawerCloseStart}
|
||||
captureGestures='open'
|
||||
type='static'
|
||||
acceptTap={true}
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ export default class ChannelItem extends PureComponent {
|
|||
|
||||
onPress = () => {
|
||||
const {channel, onSelectChannel} = this.props;
|
||||
setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
preventDoubleTap(onSelectChannel, this, channel);
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
|||
|
||||
export default class SwitchTeams extends React.PureComponent {
|
||||
static propTypes = {
|
||||
currentTeam: PropTypes.object.isRequired,
|
||||
currentTeam: PropTypes.object,
|
||||
searching: PropTypes.bool.isRequired,
|
||||
showTeams: PropTypes.func.isRequired,
|
||||
teamMembers: PropTypes.object.isRequired,
|
||||
|
|
@ -78,6 +78,10 @@ export default class SwitchTeams extends React.PureComponent {
|
|||
theme
|
||||
} = this.props;
|
||||
|
||||
if (!currentTeam) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
badgeCount
|
||||
} = this.state;
|
||||
|
|
|
|||
|
|
@ -45,14 +45,14 @@ class TeamsList extends PureComponent {
|
|||
}
|
||||
|
||||
selectTeam = (team) => {
|
||||
const {actions, closeChannelDrawer, currentTeamId} = this.props;
|
||||
if (team.id === currentTeamId) {
|
||||
closeChannelDrawer();
|
||||
} else {
|
||||
actions.handleTeamChange(team);
|
||||
requestAnimationFrame(() => {
|
||||
const {actions, closeChannelDrawer, currentTeamId} = this.props;
|
||||
if (team.id !== currentTeamId) {
|
||||
actions.handleTeamChange(team);
|
||||
}
|
||||
|
||||
closeChannelDrawer();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
goToSelectTeam = () => {
|
||||
|
|
@ -125,11 +125,7 @@ class TeamsList extends PureComponent {
|
|||
<View style={styles.teamWrapper}>
|
||||
<TouchableHighlight
|
||||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
onPress={() => {
|
||||
setTimeout(() => {
|
||||
preventDoubleTap(this.selectTeam, this, item);
|
||||
}, 100);
|
||||
}}
|
||||
onPress={() => preventDoubleTap(this.selectTeam, this, item)}
|
||||
>
|
||||
<View style={styles.teamContainer}>
|
||||
<View style={styles.teamIconContainer}>
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View
|
||||
} from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const GRADIENT_START = 0.05;
|
||||
const GRADIENT_MIDDLE = 0.1;
|
||||
const GRADIENT_END = 0.01;
|
||||
|
||||
function buildSections(key, style, theme, top) {
|
||||
return (
|
||||
<View
|
||||
key={key}
|
||||
style={[style.section, (top && {marginTop: -15})]}
|
||||
>
|
||||
<View style={style.avatar}/>
|
||||
<View style={style.sectionMessage}>
|
||||
<LinearGradient
|
||||
start={{x: 0.0, y: 1.0}}
|
||||
end={{x: 1.0, y: 1.0}}
|
||||
colors={[
|
||||
changeOpacity('#e5e5e4', GRADIENT_START),
|
||||
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END)
|
||||
]}
|
||||
locations={[0.1, 0.3, 0.7]}
|
||||
style={[style.messageText, {width: 106}]}
|
||||
/>
|
||||
<LinearGradient
|
||||
start={{x: 0.0, y: 1.0}}
|
||||
end={{x: 1.0, y: 1.0}}
|
||||
colors={[
|
||||
changeOpacity('#e5e5e4', GRADIENT_START),
|
||||
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END)
|
||||
]}
|
||||
locations={[0.1, 0.3, 0.7]}
|
||||
style={[style.messageText, {alignSelf: 'stretch'}]}
|
||||
/>
|
||||
<LinearGradient
|
||||
start={{x: 0.0, y: 1.0}}
|
||||
end={{x: 1.0, y: 1.0}}
|
||||
colors={[
|
||||
changeOpacity('#e5e5e4', GRADIENT_START),
|
||||
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END)
|
||||
]}
|
||||
locations={[0.1, 0.3, 0.7]}
|
||||
style={[style.messageText, {alignSelf: 'stretch'}]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default function channelLoader(props) {
|
||||
const style = getStyleSheet(props.theme);
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
{Array(10).fill().map((item, index) => buildSections(index, style, props.theme, index === 0))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
channelLoader.propTypes = {
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
avatar: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderRadius: 16,
|
||||
height: 32,
|
||||
width: 32
|
||||
},
|
||||
container: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flex: 1
|
||||
},
|
||||
messageText: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 10,
|
||||
marginBottom: 10
|
||||
},
|
||||
section: {
|
||||
flexDirection: 'row',
|
||||
paddingLeft: 12,
|
||||
paddingRight: 20,
|
||||
marginVertical: 10
|
||||
},
|
||||
sectionMessage: {
|
||||
marginLeft: 12,
|
||||
flex: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
127
app/components/channel_loader/channel_loader.js
Normal file
127
app/components/channel_loader/channel_loader.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// 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 {
|
||||
Platform,
|
||||
View
|
||||
} from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const GRADIENT_START = 0.05;
|
||||
const GRADIENT_MIDDLE = 0.1;
|
||||
const GRADIENT_END = 0.01;
|
||||
|
||||
export default class ChannelLoader extends PureComponent {
|
||||
static propTypes = {
|
||||
channelIsLoading: PropTypes.bool.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
buildSections(key, style, top) {
|
||||
return (
|
||||
<View
|
||||
key={key}
|
||||
style={[style.section, (top && {marginTop: Platform.OS === 'android' ? 0 : -15, paddingTop: 10})]}
|
||||
>
|
||||
<View style={style.avatar}/>
|
||||
<View style={style.sectionMessage}>
|
||||
<LinearGradient
|
||||
start={{x: 0.0, y: 1.0}}
|
||||
end={{x: 1.0, y: 1.0}}
|
||||
colors={[
|
||||
changeOpacity('#e5e5e4', GRADIENT_START),
|
||||
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END)
|
||||
]}
|
||||
locations={[0.1, 0.3, 0.7]}
|
||||
style={[style.messageText, {width: 106}]}
|
||||
/>
|
||||
<LinearGradient
|
||||
start={{x: 0.0, y: 1.0}}
|
||||
end={{x: 1.0, y: 1.0}}
|
||||
colors={[
|
||||
changeOpacity('#e5e5e4', GRADIENT_START),
|
||||
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END)
|
||||
]}
|
||||
locations={[0.1, 0.3, 0.7]}
|
||||
style={[style.messageText, {alignSelf: 'stretch'}]}
|
||||
/>
|
||||
<LinearGradient
|
||||
start={{x: 0.0, y: 1.0}}
|
||||
end={{x: 1.0, y: 1.0}}
|
||||
colors={[
|
||||
changeOpacity('#e5e5e4', GRADIENT_START),
|
||||
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END)
|
||||
]}
|
||||
locations={[0.1, 0.3, 0.7]}
|
||||
style={[style.messageText, {alignSelf: 'stretch'}]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {channelIsLoading, deviceWidth, theme} = this.props;
|
||||
|
||||
if (!channelIsLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View style={[style.container, {width: deviceWidth}]}>
|
||||
{Array(20).fill().map((item, index) => this.buildSections(index, style, index === 0))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flex: 1,
|
||||
position: 'absolute',
|
||||
...Platform.select({
|
||||
android: {
|
||||
top: 0
|
||||
},
|
||||
ios: {
|
||||
top: 15
|
||||
}
|
||||
})
|
||||
},
|
||||
avatar: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderRadius: 16,
|
||||
height: 32,
|
||||
width: 32
|
||||
},
|
||||
messageText: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 10,
|
||||
marginBottom: 10
|
||||
},
|
||||
section: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flexDirection: 'row',
|
||||
paddingLeft: 12,
|
||||
paddingRight: 20,
|
||||
marginVertical: 10
|
||||
},
|
||||
sectionMessage: {
|
||||
marginLeft: 12,
|
||||
flex: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
19
app/components/channel_loader/index.js
Normal file
19
app/components/channel_loader/index.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelLoader from './channel_loader';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {deviceWidth} = state.device.dimension;
|
||||
return {
|
||||
...ownProps,
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
deviceWidth,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ChannelLoader);
|
||||
|
|
@ -25,8 +25,7 @@ export default class PostList extends PureComponent {
|
|||
actions: PropTypes.shape({
|
||||
refreshChannelWithRetry: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channel: PropTypes.object,
|
||||
channelIsLoading: PropTypes.bool.isRequired,
|
||||
channelId: PropTypes.string,
|
||||
currentUserId: PropTypes.string,
|
||||
indicateNewMessages: PropTypes.bool,
|
||||
isLoadingMore: PropTypes.bool,
|
||||
|
|
@ -44,11 +43,6 @@ export default class PostList extends PureComponent {
|
|||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
channel: {},
|
||||
channelIsLoading: false
|
||||
};
|
||||
|
||||
getPostsWithDates = () => {
|
||||
const {posts, indicateNewMessages, currentUserId, lastViewedAt, showLoadMore} = this.props;
|
||||
const list = addDatesToPostList(posts, {indicateNewMessages, currentUserId, lastViewedAt});
|
||||
|
|
@ -81,12 +75,12 @@ export default class PostList extends PureComponent {
|
|||
onRefresh = () => {
|
||||
const {
|
||||
actions,
|
||||
channel,
|
||||
channelId,
|
||||
onRefresh
|
||||
} = this.props;
|
||||
|
||||
if (Object.keys(channel).length) {
|
||||
actions.refreshChannelWithRetry(channel.id);
|
||||
if (channelId) {
|
||||
actions.refreshChannelWithRetry(channelId);
|
||||
}
|
||||
|
||||
if (onRefresh) {
|
||||
|
|
@ -95,9 +89,9 @@ export default class PostList extends PureComponent {
|
|||
};
|
||||
|
||||
renderChannelIntro = () => {
|
||||
const {channel, channelIsLoading, navigator, refreshing, showLoadMore} = this.props;
|
||||
const {channelId, navigator, refreshing, showLoadMore} = this.props;
|
||||
|
||||
if (channel.hasOwnProperty('id') && !showLoadMore && !refreshing && !channelIsLoading) {
|
||||
if (channelId && !showLoadMore && !refreshing) {
|
||||
return (
|
||||
<View>
|
||||
<ChannelIntro navigator={navigator}/>
|
||||
|
|
@ -169,13 +163,13 @@ export default class PostList extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {channel, refreshing, theme} = this.props;
|
||||
const {channelId, refreshing, theme} = this.props;
|
||||
|
||||
const refreshControl = {
|
||||
refreshing
|
||||
};
|
||||
|
||||
if (Object.keys(channel).length) {
|
||||
if (channelId) {
|
||||
refreshControl.onRefresh = this.onRefresh;
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +181,7 @@ export default class PostList extends PureComponent {
|
|||
keyExtractor={this.keyExtractor}
|
||||
ListFooterComponent={this.renderChannelIntro}
|
||||
onEndReached={this.loadMorePosts}
|
||||
onEndReachedThreshold={700}
|
||||
onEndReachedThreshold={0}
|
||||
{...refreshControl}
|
||||
renderItem={this.renderItem}
|
||||
theme={theme}
|
||||
|
|
|
|||
|
|
@ -176,8 +176,6 @@ function drafts(state = {}, action) {
|
|||
|
||||
function loading(state = false, action) {
|
||||
switch (action.type) {
|
||||
case ChannelTypes.SELECT_CHANNEL:
|
||||
return false;
|
||||
case ViewTypes.SET_CHANNEL_LOADER:
|
||||
return action.loading;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {RequestStatus} from 'mattermost-redux/constants';
|
|||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import ChannelDrawer from 'app/components/channel_drawer';
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
import Loading from 'app/components/loading';
|
||||
import OfflineIndicator from 'app/components/offline_indicator';
|
||||
|
|
@ -227,6 +228,7 @@ class Channel extends PureComponent {
|
|||
navigator={navigator}
|
||||
/>
|
||||
</View>
|
||||
<ChannelLoader theme={theme}/>
|
||||
<PostTextbox
|
||||
ref={this.attachPostTextbox}
|
||||
onChangeText={this.handleDraftChanged}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {
|
|||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import PostList from 'app/components/post_list';
|
||||
import PostListRetry from 'app/components/post_list_retry';
|
||||
|
|
@ -29,22 +28,26 @@ class ChannelPostList extends PureComponent {
|
|||
selectPost: PropTypes.func.isRequired,
|
||||
refreshChannelWithRetry: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channel: PropTypes.object.isRequired,
|
||||
channelIsLoading: PropTypes.bool,
|
||||
channelDisplayName: PropTypes.string,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
channelIsRefreshing: PropTypes.bool,
|
||||
channelRefreshingFailed: PropTypes.bool,
|
||||
channelType: PropTypes.string,
|
||||
currentUserId: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
lastViewedAt: PropTypes.number,
|
||||
loadingPosts: PropTypes.bool,
|
||||
myMember: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
posts: PropTypes.array.isRequired,
|
||||
postVisibility: PropTypes.number,
|
||||
totalMessageCount: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
posts: [],
|
||||
loadingPosts: false,
|
||||
postVisibility: 0
|
||||
postVisibility: 30
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -53,44 +56,42 @@ class ChannelPostList extends PureComponent {
|
|||
this.state = {
|
||||
retryMessageHeight: new Animated.Value(0),
|
||||
visiblePosts: this.getVisiblePosts(props),
|
||||
showLoadMore: false
|
||||
showLoadMore: props.posts.length >= props.postVisibility
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {channel, posts, channelRefreshingFailed} = this.props;
|
||||
const {channelId} = this.props;
|
||||
this.mounted = true;
|
||||
this.loadPosts(this.props.channel.id);
|
||||
this.shouldMarkChannelAsLoaded(posts.length, channel.total_msg_count === 0, channelRefreshingFailed);
|
||||
this.loadPosts(channelId);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {channel: currentChannel} = this.props;
|
||||
const {channel: nextChannel, channelRefreshingFailed: nextChannelRefreshingFailed, posts: nextPosts} = nextProps;
|
||||
const {channelId: currentChannelId} = this.props;
|
||||
const {channelId: nextChannelId, channelRefreshingFailed: nextChannelRefreshingFailed, posts: nextPosts} = nextProps;
|
||||
|
||||
if (currentChannel.id !== nextChannel.id) {
|
||||
if (currentChannelId !== nextChannelId) {
|
||||
// Load the posts when the channel actually changes
|
||||
this.loadPosts(nextChannel.id);
|
||||
this.loadPosts(nextChannelId);
|
||||
}
|
||||
|
||||
if (nextChannelRefreshingFailed && this.state.channelLoaded && nextPosts.length) {
|
||||
if (nextChannelRefreshingFailed && nextPosts.length) {
|
||||
this.toggleRetryMessage();
|
||||
} else if (!nextChannelRefreshingFailed || !nextPosts.length) {
|
||||
this.toggleRetryMessage(false);
|
||||
}
|
||||
|
||||
this.shouldMarkChannelAsLoaded(nextPosts.length, nextChannel.total_msg_count === 0, nextChannelRefreshingFailed);
|
||||
|
||||
const showLoadMore = nextProps.posts.length >= nextProps.postVisibility;
|
||||
this.setState({
|
||||
showLoadMore
|
||||
});
|
||||
let visiblePosts = this.state.visiblePosts;
|
||||
|
||||
if (nextProps.posts !== this.props.posts || nextProps.postVisibility !== this.props.postVisibility) {
|
||||
this.setState({
|
||||
visiblePosts: this.getVisiblePosts(nextProps)
|
||||
});
|
||||
visiblePosts = this.getVisiblePosts(nextProps);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
showLoadMore,
|
||||
visiblePosts
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
@ -98,17 +99,7 @@ class ChannelPostList extends PureComponent {
|
|||
}
|
||||
|
||||
getVisiblePosts = (props) => {
|
||||
return props.posts.slice(0, props.posts.postVisibility);
|
||||
}
|
||||
|
||||
shouldMarkChannelAsLoaded = (postsCount, channelHasMessages, channelRefreshingFailed) => {
|
||||
if (postsCount || channelHasMessages || channelRefreshingFailed) {
|
||||
this.channelLoaded();
|
||||
}
|
||||
};
|
||||
|
||||
channelLoaded = () => {
|
||||
this.setState({channelLoaded: true});
|
||||
return props.posts.slice(0, props.postVisibility);
|
||||
};
|
||||
|
||||
toggleRetryMessage = (show = true) => {
|
||||
|
|
@ -120,19 +111,17 @@ class ChannelPostList extends PureComponent {
|
|||
};
|
||||
|
||||
goToThread = (post) => {
|
||||
const {actions, channel, intl, navigator, theme} = this.props;
|
||||
const channelId = post.channel_id;
|
||||
const {actions, channelId, channelDisplayName, channelType, intl, navigator, theme} = this.props;
|
||||
const rootId = (post.root_id || post.id);
|
||||
|
||||
actions.loadThreadIfNecessary(post.root_id, channelId);
|
||||
actions.selectPost(rootId);
|
||||
|
||||
let title;
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
if (channelType === General.DM_CHANNEL) {
|
||||
title = intl.formatMessage({id: 'mobile.routes.thread_dm', defaultMessage: 'Direct Message Thread'});
|
||||
} else {
|
||||
const channelName = channel.display_name;
|
||||
title = intl.formatMessage({id: 'mobile.routes.thread', defaultMessage: '{channelName} Thread'}, {channelName});
|
||||
title = intl.formatMessage({id: 'mobile.routes.thread', defaultMessage: '{channelName} Thread'}, {channelName: channelDisplayName});
|
||||
}
|
||||
|
||||
const options = {
|
||||
|
|
@ -161,29 +150,28 @@ class ChannelPostList extends PureComponent {
|
|||
|
||||
loadMorePosts = () => {
|
||||
if (this.state.showLoadMore) {
|
||||
const {actions, channel} = this.props;
|
||||
actions.increasePostVisibility(channel.id);
|
||||
const {actions, channelId} = this.props;
|
||||
actions.increasePostVisibility(channelId);
|
||||
}
|
||||
};
|
||||
|
||||
loadPosts = (channelId) => {
|
||||
this.setState({channelLoaded: false});
|
||||
this.props.actions.loadPostsIfNecessaryWithRetry(channelId);
|
||||
};
|
||||
|
||||
loadPostsRetry = () => {
|
||||
this.loadPosts(this.props.channel.id);
|
||||
this.loadPosts(this.props.channelId);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
actions,
|
||||
channel,
|
||||
channelIsLoading,
|
||||
channelId,
|
||||
channelIsRefreshing,
|
||||
channelRefreshingFailed,
|
||||
currentUserId,
|
||||
lastViewedAt,
|
||||
loadingPosts,
|
||||
myMember,
|
||||
navigator,
|
||||
posts,
|
||||
theme
|
||||
|
|
@ -203,8 +191,6 @@ class ChannelPostList extends PureComponent {
|
|||
theme={theme}
|
||||
/>
|
||||
);
|
||||
} else if (channelIsLoading) {
|
||||
component = <ChannelLoader theme={theme}/>;
|
||||
} else {
|
||||
component = (
|
||||
<PostList
|
||||
|
|
@ -216,12 +202,11 @@ class ChannelPostList extends PureComponent {
|
|||
onRefresh={actions.setChannelRefreshing}
|
||||
renderReplies={true}
|
||||
indicateNewMessages={true}
|
||||
currentUserId={myMember.user_id}
|
||||
lastViewedAt={myMember.last_viewed_at}
|
||||
channel={channel}
|
||||
currentUserId={currentUserId}
|
||||
lastViewedAt={lastViewedAt}
|
||||
channelId={channelId}
|
||||
navigator={navigator}
|
||||
refreshing={channelIsRefreshing}
|
||||
channelIsLoading={channelIsLoading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {selectPost} from 'mattermost-redux/actions/posts';
|
|||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {makeGetPostsInChannel} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getMyCurrentChannelMembership, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel';
|
||||
import {getConnection} from 'app/selectors/device';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
|
@ -40,16 +41,21 @@ function makeMapStateToProps() {
|
|||
channelRefreshingFailed = false;
|
||||
}
|
||||
|
||||
const channel = getChannel(state, {id: channelId});
|
||||
|
||||
return {
|
||||
channel: getChannel(state, {id: channelId}),
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
channelId,
|
||||
channelIsRefreshing,
|
||||
channelRefreshingFailed,
|
||||
currentUserId: getCurrentUserId(state),
|
||||
channelType: channel.type,
|
||||
channelDisplayName: channel.display_name,
|
||||
posts,
|
||||
postVisibility: state.views.channel.postVisibility[channelId],
|
||||
loadingPosts: state.views.channel.loadingPosts[channelId],
|
||||
myMember: getMyCurrentChannelMembership(state),
|
||||
LastViewedAt: getMyCurrentChannelMembership(state).last_viewed_at,
|
||||
networkOnline,
|
||||
totalMessageCount: channel.total_msg_count,
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
|
|||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Keyboard,
|
||||
InteractionManager,
|
||||
Platform,
|
||||
SectionList,
|
||||
StyleSheet,
|
||||
|
|
@ -97,13 +98,13 @@ class Search extends Component {
|
|||
const recentLenght = recent.length;
|
||||
const shouldScroll = prevStatus !== status && (status === RequestStatus.SUCCESS || status === RequestStatus.STARTED);
|
||||
|
||||
if (shouldScroll && !this.state.isFocused) {
|
||||
setTimeout(() => {
|
||||
if (shouldScroll) {
|
||||
requestAnimationFrame(() => {
|
||||
this.refs.list._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle
|
||||
animated: true,
|
||||
offset: SECTION_HEIGHT + (2 * MODIFIER_LABEL_HEIGHT) + (recentLenght * RECENT_LABEL_HEIGHT) + ((recentLenght + 1) * RECENT_SEPARATOR_HEIGHT)
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -430,6 +431,8 @@ class Search extends Component {
|
|||
viewChannel
|
||||
} = actions;
|
||||
|
||||
setChannelLoading();
|
||||
|
||||
const channel = channels.find((c) => c.id === channelId);
|
||||
let displayName = '';
|
||||
|
||||
|
|
@ -440,10 +443,11 @@ class Search extends Component {
|
|||
this.props.navigator.dismissModal({animationType: 'none'});
|
||||
|
||||
markChannelAsRead(channelId, currentChannelId);
|
||||
setChannelLoading();
|
||||
viewChannel(channelId, currentChannelId);
|
||||
setChannelDisplayName(displayName);
|
||||
handleSelectChannel(channelId);
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
handleSelectChannel(channelId);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
const emptyDraft = {
|
||||
draft: '',
|
||||
|
|
@ -20,9 +20,9 @@ function getThreadDrafts(state) {
|
|||
|
||||
export const getCurrentChannelDraft = createSelector(
|
||||
getChannelDrafts,
|
||||
getCurrentChannel,
|
||||
(drafts, currentChannel) => {
|
||||
return drafts[currentChannel.id] || emptyDraft;
|
||||
getCurrentChannelId,
|
||||
(drafts, currentChannelId) => {
|
||||
return drafts[currentChannelId] || emptyDraft;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export default function configureAppStore(initialState) {
|
|||
['typing']
|
||||
);
|
||||
|
||||
const channelViewBlackList = {loading: true, refreshing: true, tooltipVisible: true, postVisibility: true, loadingPosts: true};
|
||||
const channelViewBlackList = {loading: true, refreshing: true, tooltipVisible: true, loadingPosts: true};
|
||||
const channelViewBlackListFilter = createTransform(
|
||||
(inboundState) => {
|
||||
const channel = {};
|
||||
|
|
|
|||
49
app/utils/why_did_you_update.js
Normal file
49
app/utils/why_did_you_update.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/* eslint-disable */
|
||||
|
||||
// Gist created by https://benchling.engineering/a-deep-dive-into-react-perf-debugging-fd2063f5a667
|
||||
|
||||
import _ from 'underscore';
|
||||
|
||||
function isRequiredUpdateObject(o) {
|
||||
return Array.isArray(o) || (o && o.constructor === Object.prototype.constructor);
|
||||
}
|
||||
|
||||
function deepDiff(o1, o2, p) {
|
||||
const notify = (status) => {
|
||||
console.warn('Update %s', status);
|
||||
console.log('%cbefore', 'font-weight: bold', o1);
|
||||
console.log('%cafter ', 'font-weight: bold', o2);
|
||||
};
|
||||
if (!_.isEqual(o1, o2)) {
|
||||
console.group(p);
|
||||
if ([o1, o2].every(_.isFunction)) {
|
||||
notify('avoidable?');
|
||||
} else if (![o1, o2].every(isRequiredUpdateObject)) {
|
||||
notify('required.');
|
||||
} else {
|
||||
const keys = _.union(_.keys(o1), _.keys(o2));
|
||||
for (const key of keys) {
|
||||
deepDiff(o1[key], o2[key], key);
|
||||
}
|
||||
}
|
||||
console.groupEnd();
|
||||
} else if (o1 !== o2) {
|
||||
console.group(p);
|
||||
notify('avoidable!');
|
||||
if (_.isObject(o1) && _.isObject(o2)) {
|
||||
const keys = _.union(_.keys(o1), _.keys(o2));
|
||||
for (const key of keys) {
|
||||
deepDiff(o1[key], o2[key], key);
|
||||
}
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
function whyDidYouUpdate(prevProps, prevState) {
|
||||
deepDiff({props: prevProps, state: prevState},
|
||||
{props: this.props, state: this.state},
|
||||
this.constructor.name);
|
||||
}
|
||||
|
||||
export default whyDidYouUpdate;
|
||||
|
|
@ -24,7 +24,7 @@ GEM
|
|||
faraday_middleware (0.12.2)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
fastimage (2.1.0)
|
||||
fastlane (2.58.0)
|
||||
fastlane (2.59.0)
|
||||
CFPropertyList (>= 2.3, < 3.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
babosa (>= 1.0.2, < 2.0.0)
|
||||
|
|
@ -126,7 +126,7 @@ GEM
|
|||
unf_ext (0.0.7.4)
|
||||
unicode-display_width (1.3.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.5.1)
|
||||
xcodeproj (1.5.2)
|
||||
CFPropertyList (~> 2.3.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
|
|
|
|||
|
|
@ -88,7 +88,8 @@
|
|||
"react-native-svg-mock": "1.0.2",
|
||||
"react-test-renderer": "16.0.0-alpha.12",
|
||||
"remote-redux-devtools": "0.5.12",
|
||||
"remote-redux-devtools-on-debugger": "0.8.2"
|
||||
"remote-redux-devtools-on-debugger": "0.8.2",
|
||||
"underscore": "1.8.3"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "NODE_ENV=test nyc --reporter=text mocha --opts test/mocha.opts",
|
||||
|
|
|
|||
|
|
@ -3878,7 +3878,7 @@ makeerror@1.0.x:
|
|||
|
||||
mattermost-redux@mattermost/mattermost-redux#master:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/850e7df06cd7dcae7e592f78ca7bbdd6ab2ec56a"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/85db14af9a6d8c402213b0eb3c372f1ac6adad34"
|
||||
dependencies:
|
||||
deep-equal "1.0.1"
|
||||
harmony-reflect "1.5.1"
|
||||
|
|
@ -6411,6 +6411,10 @@ ultron@~1.1.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864"
|
||||
|
||||
underscore@1.8.3:
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
|
||||
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
|
|
|
|||
Loading…
Reference in a new issue