Multiple performance improvements (#956)

* Update fastlane

* Multiple performance improvements

* Feedback review

* Feedback review
This commit is contained in:
enahum 2017-09-28 12:54:32 -03:00 committed by GitHub
parent a694122ffd
commit 1e434346ae
21 changed files with 325 additions and 232 deletions

View file

@ -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);
};
}

View file

@ -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);

View file

@ -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}

View file

@ -26,9 +26,9 @@ export default class ChannelItem extends PureComponent {
onPress = () => {
const {channel, onSelectChannel} = this.props;
setTimeout(() => {
requestAnimationFrame(() => {
preventDoubleTap(onSelectChannel, this, channel);
}, 100);
});
};
render() {

View file

@ -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;

View file

@ -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}>

View file

@ -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
}
};
});

View 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
}
};
});

View 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);

View file

@ -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}

View file

@ -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:

View file

@ -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}

View file

@ -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}
/>
);
}

View file

@ -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
};

View file

@ -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);
});
}
};

View file

@ -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;
}
);

View file

@ -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 = {};

View 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;

View file

@ -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)

View file

@ -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",

View file

@ -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"