forked from Ivasoft/mattermost-mobile
Use post retry actions to avoid missing messages (#796)
* Use post retry actions to avoid missing messages * Review feedback * Fix race condition and update redux-offline * Review feedback
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
||||
selectChannel,
|
||||
leaveChannel as serviceLeaveChannel
|
||||
} from 'mattermost-redux/actions/channels';
|
||||
import {getPosts, getPostsBefore, getPostsSince, getPostThread} from 'mattermost-redux/actions/posts';
|
||||
import {getPosts, getPostsWithRetry, getPostsBefore, getPostsSinceWithRetry, getPostThread} from 'mattermost-redux/actions/posts';
|
||||
import {getFilesForPost} from 'mattermost-redux/actions/files';
|
||||
import {savePreferences, deletePreferences} from 'mattermost-redux/actions/preferences';
|
||||
import {getTeamMembersByIds} from 'mattermost-redux/actions/teams';
|
||||
@@ -136,8 +136,8 @@ export function loadProfilesAndTeamMembersForDMSidebar(teamId) {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadPostsIfNecessary(channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
export function loadPostsIfNecessaryWithRetry(channelId) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {posts, postsInChannel} = state.entities.posts;
|
||||
|
||||
@@ -145,13 +145,14 @@ export function loadPostsIfNecessary(channelId) {
|
||||
|
||||
// Get the first page of posts if it appears we haven't gotten it yet, like the webapp
|
||||
if (!postsIds || postsIds.length < ViewTypes.POST_VISIBILITY_CHUNK_SIZE) {
|
||||
return getPosts(channelId)(dispatch, getState);
|
||||
getPostsWithRetry(channelId)(dispatch, getState);
|
||||
return;
|
||||
}
|
||||
|
||||
const postsForChannel = postsIds.map((id) => posts[id]);
|
||||
const latestPostTime = getLastCreateAt(postsForChannel);
|
||||
|
||||
return getPostsSince(channelId, latestPostTime)(dispatch, getState);
|
||||
getPostsSinceWithRetry(channelId, latestPostTime)(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -325,11 +326,9 @@ export function unmarkFavorite(channelId) {
|
||||
};
|
||||
}
|
||||
|
||||
export function refreshChannel(channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch(setChannelRefreshing());
|
||||
await getPosts(channelId)(dispatch, getState);
|
||||
dispatch(setChannelRefreshing(false));
|
||||
export function refreshChannelWithRetry(channelId) {
|
||||
return (dispatch, getState) => {
|
||||
getPostsWithRetry(channelId)(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -350,13 +349,6 @@ export function setChannelLoading(loading = true) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setChannelRefreshing(refreshing = true) {
|
||||
return {
|
||||
type: ViewTypes.SET_CHANNEL_REFRESHING,
|
||||
refreshing
|
||||
};
|
||||
}
|
||||
|
||||
export function setPostTooltipVisible(visible = true) {
|
||||
return {
|
||||
type: ViewTypes.POST_TOOLTIP_VISIBLE,
|
||||
|
||||
@@ -4,18 +4,14 @@
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {refreshChannel} from 'app/actions/views/channel';
|
||||
import {refreshChannelWithRetry} from 'app/actions/views/channel';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import PostList from './post_list';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {loading, refreshing} = state.views.channel;
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
channelIsLoading: loading,
|
||||
refreshing,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
@@ -23,7 +19,7 @@ function mapStateToProps(state, ownProps) {
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
refreshChannel
|
||||
refreshChannelWithRetry
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const LOAD_MORE_POSTS = 'load-more-posts';
|
||||
export default class PostList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
refreshChannel: PropTypes.func.isRequired
|
||||
refreshChannelWithRetry: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channel: PropTypes.object,
|
||||
channelIsLoading: PropTypes.bool.isRequired,
|
||||
@@ -79,7 +79,7 @@ export default class PostList extends PureComponent {
|
||||
const {actions, channel} = this.props;
|
||||
|
||||
if (Object.keys(channel).length) {
|
||||
actions.refreshChannel(channel.id);
|
||||
actions.refreshChannelWithRetry(channel.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ const ViewTypes = keyMirror({
|
||||
ADD_FILE_TO_FETCH_CACHE: null,
|
||||
|
||||
SET_CHANNEL_LOADER: null,
|
||||
SET_CHANNEL_REFRESHING: null,
|
||||
SET_CHANNEL_DISPLAY_NAME: null,
|
||||
|
||||
POST_TOOLTIP_VISIBLE: null,
|
||||
|
||||
@@ -251,7 +251,6 @@ const state = {
|
||||
channel: {
|
||||
drafts: {},
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
tooltipVisible: false
|
||||
},
|
||||
connection: true,
|
||||
|
||||
@@ -181,15 +181,6 @@ function loading(state = false, action) {
|
||||
}
|
||||
}
|
||||
|
||||
function refreshing(state = false, action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.SET_CHANNEL_REFRESHING:
|
||||
return action.refreshing;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function tooltipVisible(state = false, action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.POST_TOOLTIP_VISIBLE:
|
||||
@@ -245,7 +236,6 @@ export default combineReducers({
|
||||
displayName,
|
||||
drafts,
|
||||
loading,
|
||||
refreshing,
|
||||
tooltipVisible,
|
||||
postVisibility,
|
||||
loadingPosts
|
||||
|
||||
@@ -8,12 +8,14 @@ import {
|
||||
Animated,
|
||||
Dimensions,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
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';
|
||||
|
||||
@@ -23,14 +25,16 @@ const {height: deviceHeight, width: deviceWidth} = Dimensions.get('window');
|
||||
class ChannelPostList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
loadPostsIfNecessary: PropTypes.func.isRequired,
|
||||
loadPostsIfNecessaryWithRetry: PropTypes.func.isRequired,
|
||||
loadThreadIfNecessary: PropTypes.func.isRequired,
|
||||
increasePostVisibility: PropTypes.func.isRequired,
|
||||
selectPost: PropTypes.func.isRequired
|
||||
selectPost: PropTypes.func.isRequired,
|
||||
refreshChannelWithRetry: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channel: PropTypes.object.isRequired,
|
||||
channelIsLoading: PropTypes.bool,
|
||||
channelIsRefreshing: PropTypes.bool,
|
||||
channelRefreshingFailed: PropTypes.bool,
|
||||
currentChannelId: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
loadingPosts: PropTypes.bool,
|
||||
@@ -51,26 +55,44 @@ class ChannelPostList extends PureComponent {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
loaderOpacity: new Animated.Value(1)
|
||||
loaderOpacity: new Animated.Value(1),
|
||||
retryMessageHeight: new Animated.Value(0)
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {channel, posts, channelRefreshingFailed} = this.props;
|
||||
this.mounted = true;
|
||||
this.loadPosts(this.props.channel.id);
|
||||
if (posts.length || channel.total_msg_count === 0 || channelRefreshingFailed) {
|
||||
this.channelLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {currentChannelId, channel: currentChannel} = this.props;
|
||||
const {currentChannelId: nextChannelId, channel: nextChannel, channelRefreshingFailed: nextChannelRefreshingFailed, posts: nextPosts} = nextProps;
|
||||
|
||||
// Show the loader if the channel id change
|
||||
if (this.props.currentChannelId !== nextProps.currentChannelId) {
|
||||
if (currentChannelId !== nextChannelId) {
|
||||
this.setState({
|
||||
loaderOpacity: new Animated.Value(1)
|
||||
}, () => {
|
||||
if (nextPosts.length || nextChannel.total_msg_count === 0 || nextChannelRefreshingFailed) {
|
||||
this.channelLoaded();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.channel.id !== nextProps.channel.id) {
|
||||
if (currentChannel.id !== nextChannel.id) {
|
||||
// Load the posts when the channel actually changes
|
||||
this.loadPosts(nextProps.channel.id);
|
||||
this.loadPosts(nextChannel.id);
|
||||
}
|
||||
|
||||
if (nextChannelRefreshingFailed && this.state.channelLoaded && nextPosts.length) {
|
||||
this.toggleRetryMessage();
|
||||
} else if (!nextChannelRefreshingFailed || !nextPosts.length) {
|
||||
this.toggleRetryMessage(false);
|
||||
}
|
||||
|
||||
const showLoadMore = nextProps.posts.length >= nextProps.postVisibility;
|
||||
@@ -94,6 +116,14 @@ class ChannelPostList extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
toggleRetryMessage = (show = true) => {
|
||||
const value = show ? 38 : 0;
|
||||
Animated.timing(this.state.retryMessageHeight, {
|
||||
toValue: value,
|
||||
duration: 350
|
||||
}).start();
|
||||
};
|
||||
|
||||
goToThread = (post) => {
|
||||
const {actions, channel, intl, navigator, theme} = this.props;
|
||||
const channelId = post.channel_id;
|
||||
@@ -141,10 +171,9 @@ class ChannelPostList extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
loadPosts = async (channelId) => {
|
||||
loadPosts = (channelId) => {
|
||||
this.setState({channelLoaded: false});
|
||||
await this.props.actions.loadPostsIfNecessary(channelId);
|
||||
this.channelLoaded();
|
||||
this.props.actions.loadPostsIfNecessaryWithRetry(channelId);
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -152,27 +181,26 @@ class ChannelPostList extends PureComponent {
|
||||
channel,
|
||||
channelIsLoading,
|
||||
channelIsRefreshing,
|
||||
channelRefreshingFailed,
|
||||
loadingPosts,
|
||||
myMember,
|
||||
navigator,
|
||||
networkOnline,
|
||||
posts,
|
||||
postVisibility,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
const {channelLoaded, loaderOpacity} = this.state;
|
||||
const {loaderOpacity, retryMessageHeight} = this.state;
|
||||
|
||||
let component;
|
||||
if (!posts.length && channel.total_msg_count > 0 && (!channelIsLoading || !networkOnline)) {
|
||||
// If no posts has been loaded and we are offline
|
||||
if (!posts.length && channelRefreshingFailed) {
|
||||
component = (
|
||||
<PostListRetry
|
||||
retry={() => this.loadPosts(channel.id)}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
} else if ((channelIsLoading || !channelLoaded) && !channelIsRefreshing && !loadingPosts) {
|
||||
} else if (channelIsLoading) {
|
||||
component = <ChannelLoader theme={theme}/>;
|
||||
} else {
|
||||
component = (
|
||||
@@ -188,6 +216,8 @@ class ChannelPostList extends PureComponent {
|
||||
lastViewedAt={myMember.last_viewed_at}
|
||||
channel={channel}
|
||||
navigator={navigator}
|
||||
refreshing={channelIsRefreshing}
|
||||
channelIsLoading={channelIsLoading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -208,9 +238,28 @@ class ChannelPostList extends PureComponent {
|
||||
>
|
||||
<ChannelLoader theme={theme}/>
|
||||
</AnimatedView>
|
||||
<AnimatedView style={[style.refreshIndicator, {height: retryMessageHeight}]}>
|
||||
<FormattedText
|
||||
id='mobile.retry_message'
|
||||
defaultMessage='Refreshing messages failed. Pull up to try again.'
|
||||
style={{color: 'white', flex: 1, fontSize: 12}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
refreshIndicator: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fb8000',
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 10,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
width: deviceWidth
|
||||
}
|
||||
});
|
||||
|
||||
export default injectIntl(ChannelPostList);
|
||||
|
||||
@@ -8,8 +8,7 @@ import {selectPost} from 'mattermost-redux/actions/posts';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {makeGetPostsInChannel} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getCurrentChannelId, getMyCurrentChannelMembership} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {loadPostsIfNecessary, loadThreadIfNecessary, increasePostVisibility} from 'app/actions/views/channel';
|
||||
import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelPostList from './channel_post_list';
|
||||
@@ -19,19 +18,36 @@ function makeMapStateToProps() {
|
||||
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const channelId = ownProps.channel.id;
|
||||
const {refreshing} = state.views.channel;
|
||||
const {getPosts} = state.requests.posts;
|
||||
const {getPosts, getPostsRetryAttempts, getPostsSince, getPostsSinceRetryAttempts} = state.requests.posts;
|
||||
const posts = getPostsInChannel(state, channelId) || [];
|
||||
const {websocket: websocketRequest} = state.requests.general;
|
||||
const {connection} = state.views;
|
||||
const networkOnline = connection && websocketRequest.status === RequestStatus.SUCCESS;
|
||||
|
||||
let getPostsStatus;
|
||||
if (getPostsRetryAttempts > 0) {
|
||||
getPostsStatus = getPosts.status;
|
||||
} else if (getPostsSinceRetryAttempts > 1) {
|
||||
getPostsStatus = getPostsSince.status;
|
||||
}
|
||||
|
||||
let channelIsRefreshing = getPostsStatus === RequestStatus.STARTED;
|
||||
let channelRefreshingFailed = getPostsStatus === RequestStatus.FAILURE;
|
||||
if (!networkOnline) {
|
||||
channelIsRefreshing = false;
|
||||
channelRefreshingFailed = true;
|
||||
}
|
||||
|
||||
return {
|
||||
channelIsLoading: getPosts.status === RequestStatus.STARTED || state.views.channel.loading,
|
||||
channelIsRefreshing: refreshing,
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
channelIsRefreshing,
|
||||
channelRefreshingFailed,
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
posts,
|
||||
postVisibility: state.views.channel.postVisibility[channelId],
|
||||
loadingPosts: state.views.channel.loadingPosts[channelId],
|
||||
myMember: getMyCurrentChannelMembership(state),
|
||||
networkOnline: state.offline.online,
|
||||
networkOnline,
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
};
|
||||
@@ -41,10 +57,11 @@ function makeMapStateToProps() {
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
loadPostsIfNecessary,
|
||||
loadPostsIfNecessaryWithRetry,
|
||||
loadThreadIfNecessary,
|
||||
increasePostVisibility,
|
||||
selectPost
|
||||
selectPost,
|
||||
refreshChannelWithRetry
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4791,7 +4791,7 @@ redux-devtools-instrument@^1.3.3:
|
||||
|
||||
"redux-offline@git+https://github.com/enahum/redux-offline.git":
|
||||
version "1.1.4"
|
||||
resolved "git+https://github.com/enahum/redux-offline.git#454d32a14ae50b9727bccbcdd295cad3cd037335"
|
||||
resolved "git+https://github.com/enahum/redux-offline.git#3907341aa13fe913ec02a939af76e381d369a91d"
|
||||
dependencies:
|
||||
redux-persist "^4.5.0"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user