forked from Ivasoft/mattermost-mobile
Fix app hanging when switching to a team after opening push notification (#4015)
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
@@ -38,10 +38,6 @@ import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences'
|
||||
import {getCurrentUserId, getUserIdsInChannels, getUsers} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getTeamByName} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {getChannelReachable} from 'app/selectors/channel';
|
||||
|
||||
import telemetry from 'app/telemetry';
|
||||
|
||||
import {
|
||||
getChannelByName,
|
||||
getDirectChannelName,
|
||||
@@ -55,6 +51,8 @@ import {getLastCreateAt} from 'mattermost-redux/utils/post_utils';
|
||||
import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils';
|
||||
|
||||
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT} from 'app/constants/post_textbox';
|
||||
import {getChannelReachable} from 'app/selectors/channel';
|
||||
import telemetry from 'app/telemetry';
|
||||
import {isDirectChannelVisible, isGroupChannelVisible, isDirectMessageVisible, isGroupMessageVisible, isDirectChannelAutoClosed} from 'app/utils/channels';
|
||||
import {buildPreference} from 'app/utils/preferences';
|
||||
|
||||
@@ -349,21 +347,18 @@ export function selectDefaultChannel(teamId) {
|
||||
};
|
||||
}
|
||||
|
||||
export function handleSelectChannel(channelId, fromPushNotification = false) {
|
||||
export function handleSelectChannel(channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
const dt = Date.now();
|
||||
const state = getState();
|
||||
const {channels, myMembers} = state.entities.channels;
|
||||
const {channels, currentChannelId, myMembers} = state.entities.channels;
|
||||
const {currentTeamId} = state.entities.teams;
|
||||
const channel = channels[channelId];
|
||||
const member = myMembers[channelId];
|
||||
|
||||
// If the app is open from push notification, we already fetched the posts.
|
||||
if (!fromPushNotification) {
|
||||
dispatch(loadPostsIfNecessaryWithRetry(channelId));
|
||||
}
|
||||
dispatch(loadPostsIfNecessaryWithRetry(channelId));
|
||||
|
||||
if (channel) {
|
||||
if (channel && currentChannelId !== channelId) {
|
||||
dispatch({
|
||||
type: ChannelTypes.SELECT_CHANNEL,
|
||||
data: channelId,
|
||||
@@ -373,9 +368,11 @@ export function handleSelectChannel(channelId, fromPushNotification = false) {
|
||||
teamId: channel.team_id || currentTeamId,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(markChannelViewedAndRead(channelId, currentChannelId));
|
||||
}
|
||||
|
||||
console.log('channel switch in', channel?.display_name, (Date.now() - dt), 'ms'); //eslint-disable-line
|
||||
console.log('channel switch to', channel?.display_name, (Date.now() - dt), 'ms'); //eslint-disable-line
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -118,9 +118,11 @@ describe('Actions.Views.Channel', () => {
|
||||
currentChannelId,
|
||||
channels: {
|
||||
'channel-id': {id: 'channel-id', display_name: 'Test Channel'},
|
||||
'channel-id-2': {id: 'channel-id-2', display_name: 'Test Channel'},
|
||||
},
|
||||
myMembers: {
|
||||
'channel-id': {channel_id: 'channel-id', user_id: currentUserId, mention_count: 0, msg_count: 0},
|
||||
'channel-id-2': {channel_id: 'channel-id-2', user_id: currentUserId, mention_count: 0, msg_count: 0},
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
@@ -282,17 +284,17 @@ describe('Actions.Views.Channel', () => {
|
||||
});
|
||||
|
||||
const handleSelectChannelCases = [
|
||||
[currentChannelId, true],
|
||||
[currentChannelId, false],
|
||||
[`not-${currentChannelId}`, true],
|
||||
[`not-${currentChannelId}`, false],
|
||||
[currentChannelId],
|
||||
[`${currentChannelId}-2`],
|
||||
[`not-${currentChannelId}`],
|
||||
[`not-${currentChannelId}-2`],
|
||||
];
|
||||
test.each(handleSelectChannelCases)('handleSelectChannel dispatches selectChannelWithMember', async (channelId, fromPushNotification) => {
|
||||
test.each(handleSelectChannelCases)('handleSelectChannel dispatches selectChannelWithMember', async (channelId) => {
|
||||
const testObj = {...storeObj};
|
||||
testObj.entities.teams.currentTeamId = currentTeamId;
|
||||
store = mockStore(testObj);
|
||||
|
||||
await store.dispatch(handleSelectChannel(channelId, fromPushNotification));
|
||||
await store.dispatch(handleSelectChannel(channelId));
|
||||
const storeActions = store.getActions();
|
||||
const selectChannelWithMember = storeActions.find(({type}) => type === ChannelTypes.SELECT_CHANNEL);
|
||||
const viewedAction = storeActions.find(({type}) => type === MOCK_CHANNEL_MARK_AS_VIEWED);
|
||||
@@ -315,7 +317,7 @@ describe('Actions.Views.Channel', () => {
|
||||
teamId: currentTeamId,
|
||||
},
|
||||
};
|
||||
if (channelId.includes('not')) {
|
||||
if (channelId.includes('not') || channelId === currentChannelId) {
|
||||
expect(selectChannelWithMember).toBe(undefined);
|
||||
} else {
|
||||
expect(selectChannelWithMember).toStrictEqual(expectedSelectChannelWithMember);
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {GeneralTypes} from 'mattermost-redux/action_types';
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
|
||||
import {ChannelTypes, GeneralTypes, TeamTypes} from 'mattermost-redux/action_types';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {fetchMyChannelsAndMembers} from 'mattermost-redux/actions/channels';
|
||||
import {getClientConfig, getDataRetentionPolicy, getLicenseConfig} from 'mattermost-redux/actions/general';
|
||||
import {receivedNewPost} from 'mattermost-redux/actions/posts';
|
||||
import {getMyTeams, getMyTeamMembers, selectTeam} from 'mattermost-redux/actions/teams';
|
||||
import {getMyTeams, getMyTeamMembers} from 'mattermost-redux/actions/teams';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import {recordTime} from 'app/utils/segment';
|
||||
|
||||
import {handleSelectChannel} from 'app/actions/views/channel';
|
||||
import {markChannelViewedAndRead} from './channel';
|
||||
|
||||
export function startDataCleanup() {
|
||||
return async (dispatch, getState) => {
|
||||
@@ -79,12 +82,46 @@ export function loadFromPushNotification(notification) {
|
||||
await Promise.all(loading);
|
||||
}
|
||||
|
||||
dispatch(handleSelectTeamAndChannel(teamId, channelId));
|
||||
};
|
||||
}
|
||||
|
||||
export function handleSelectTeamAndChannel(teamId, channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
const dt = Date.now();
|
||||
const state = getState();
|
||||
const {channels, currentChannelId, myMembers} = state.entities.channels;
|
||||
const {currentTeamId} = state.entities.teams;
|
||||
const channel = channels[channelId];
|
||||
const member = myMembers[channelId];
|
||||
const actions = [];
|
||||
|
||||
// when the notification is from a team other than the current team
|
||||
if (teamId !== currentTeamId) {
|
||||
dispatch(selectTeam({id: teamId}));
|
||||
actions.push({type: TeamTypes.SELECT_TEAM, data: teamId});
|
||||
}
|
||||
|
||||
dispatch(handleSelectChannel(channelId, true));
|
||||
if (channel && currentChannelId !== channelId) {
|
||||
actions.push({
|
||||
type: ChannelTypes.SELECT_CHANNEL,
|
||||
data: channelId,
|
||||
extra: {
|
||||
channel,
|
||||
member,
|
||||
teamId: channel.team_id || currentTeamId,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(markChannelViewedAndRead(channelId));
|
||||
}
|
||||
|
||||
if (actions.length) {
|
||||
dispatch(batchActions(actions));
|
||||
}
|
||||
|
||||
EphemeralStore.setStartFromNotification(false);
|
||||
|
||||
console.log('channel switch from push notification to', channel?.display_name, (Date.now() - dt), 'ms'); //eslint-disable-line
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import NotificationsIOS, {
|
||||
} from 'react-native-notifications';
|
||||
|
||||
import {getBadgeCount} from 'app/selectors/views';
|
||||
import ephemeralStore from 'app/store/ephemeral_store';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
import {getLocalizedMessage} from 'app/i18n';
|
||||
import {t} from 'app/utils/i18n';
|
||||
@@ -58,7 +58,7 @@ class PushNotification {
|
||||
if (notification) {
|
||||
const data = notification.getData();
|
||||
if (data) {
|
||||
ephemeralStore.appStartedFromPushNotification = true;
|
||||
EphemeralStore.setStartFromNotification(true);
|
||||
this.handleNotification(data, false, true);
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class PushNotification {
|
||||
|
||||
// mark the app as started as soon as possible
|
||||
if (userInteraction) {
|
||||
ephemeralStore.appStartedFromPushNotification = true;
|
||||
EphemeralStore.setStartFromNotification(true);
|
||||
}
|
||||
|
||||
const data = notification.getData();
|
||||
|
||||
@@ -32,7 +32,6 @@ export default class ChannelBase extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
loadChannelsForTeam: PropTypes.func.isRequired,
|
||||
markChannelViewedAndRead: PropTypes.func.isRequired,
|
||||
selectDefaultTeam: PropTypes.func.isRequired,
|
||||
selectInitialChannel: PropTypes.func.isRequired,
|
||||
recordLoadTime: PropTypes.func.isRequired,
|
||||
@@ -87,7 +86,6 @@ export default class ChannelBase extends PureComponent {
|
||||
PushNotifications.clearChannelNotifications(this.props.currentChannelId);
|
||||
requestAnimationFrame(() => {
|
||||
this.props.actions.getChannelStats(this.props.currentChannelId);
|
||||
this.props.actions.markChannelViewedAndRead(this.props.currentChannelId);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,11 +121,9 @@ export default class ChannelBase extends PureComponent {
|
||||
}
|
||||
|
||||
if (this.props.currentChannelId && this.props.currentChannelId !== prevProps.currentChannelId) {
|
||||
const previousChannelId = EphemeralStore.appStartedFromPushNotification ? null : prevProps.currentChannelId;
|
||||
PushNotifications.clearChannelNotifications(this.props.currentChannelId);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.props.actions.markChannelViewedAndRead(this.props.currentChannelId, previousChannelId);
|
||||
this.props.actions.getChannelStats(this.props.currentChannelId);
|
||||
this.updateNativeScrollView();
|
||||
});
|
||||
@@ -207,19 +203,17 @@ export default class ChannelBase extends PureComponent {
|
||||
|
||||
loadChannels = (teamId) => {
|
||||
const {loadChannelsForTeam, selectInitialChannel} = this.props.actions;
|
||||
loadChannelsForTeam(teamId).then((result) => {
|
||||
if (result?.error) {
|
||||
this.setState({channelsRequestFailed: true});
|
||||
return;
|
||||
}
|
||||
if (!EphemeralStore.getStartFromNotification()) {
|
||||
loadChannelsForTeam(teamId).then((result) => {
|
||||
if (result?.error) {
|
||||
this.setState({channelsRequestFailed: true});
|
||||
return;
|
||||
}
|
||||
|
||||
if (EphemeralStore.appStartedFromPushNotification) {
|
||||
EphemeralStore.appStartedFromPushNotification = false;
|
||||
} else {
|
||||
this.setState({channelsRequestFailed: false});
|
||||
selectInitialChannel(teamId);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
retryLoadChannels = () => {
|
||||
|
||||
@@ -14,7 +14,6 @@ import {getChannelStats} from 'mattermost-redux/actions/channels';
|
||||
import {
|
||||
loadChannelsForTeam,
|
||||
selectInitialChannel,
|
||||
markChannelViewedAndRead,
|
||||
} from 'app/actions/views/channel';
|
||||
import {connection} from 'app/actions/device';
|
||||
import {recordLoadTime} from 'app/actions/views/root';
|
||||
@@ -41,7 +40,6 @@ function mapDispatchToProps(dispatch) {
|
||||
connection,
|
||||
loadChannelsForTeam,
|
||||
logout,
|
||||
markChannelViewedAndRead,
|
||||
selectDefaultTeam,
|
||||
selectInitialChannel,
|
||||
recordLoadTime,
|
||||
|
||||
@@ -45,6 +45,14 @@ class EphemeralStore {
|
||||
this.navigationComponentIdStack.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
getStartFromNotification = () => {
|
||||
return this.appStartedFromPushNotification;
|
||||
};
|
||||
|
||||
setStartFromNotification = (value) => {
|
||||
this.appStartedFromPushNotification = value;
|
||||
};
|
||||
}
|
||||
|
||||
export default new EphemeralStore();
|
||||
|
||||
@@ -47,7 +47,7 @@ class PushNotificationUtils {
|
||||
|
||||
loadFromNotification = async (notification) => {
|
||||
// Set appStartedFromPushNotification to avoid channel screen to call selectInitialChannel
|
||||
EphemeralStore.appStartedFromPushNotification = true;
|
||||
EphemeralStore.setStartFromNotification(true);
|
||||
await this.store.dispatch(loadFromPushNotification(notification));
|
||||
|
||||
// if we have a componentId means that the app is already initialized
|
||||
@@ -80,11 +80,8 @@ class PushNotificationUtils {
|
||||
if (foreground) {
|
||||
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
|
||||
} else if (userInteraction && !notification?.data?.localNotification) {
|
||||
EventEmitter.emit(NavigationTypes.CLOSE_MAIN_SIDEBAR);
|
||||
if (getState().views.root.hydrationComplete) { //TODO: Replace when realm is ready
|
||||
setTimeout(() => {
|
||||
this.loadFromNotification(notification);
|
||||
}, 0);
|
||||
this.loadFromNotification(notification);
|
||||
} else {
|
||||
waitForHydration(this.store, () => {
|
||||
this.loadFromNotification(notification);
|
||||
|
||||
Reference in New Issue
Block a user