MM-13896 Sync channel as read between clients (including opening Push Notifications) (#2548)

* Update mattermost-redux to use latest WS changes

* Properly mark channel as read, keep the new message indicator, and fix push notification flow

* unneeded parameter in setCurrentUserStatusOffline

Co-Authored-By: enahum <nahumhbl@gmail.com>

* Feedback review

* Update mm-redux ref
This commit is contained in:
Elias Nahum 2019-02-16 15:09:22 -03:00 committed by GitHub
parent 5bb6cd34da
commit a0a1fa28ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 115 additions and 196 deletions

View file

@ -10,8 +10,8 @@ import {
fetchMyChannelsAndMembers,
getChannelByNameAndTeamName,
markChannelAsRead,
leaveChannel as serviceLeaveChannel, markChannelAsViewed,
selectChannel,
leaveChannel as serviceLeaveChannel,
} from 'mattermost-redux/actions/channels';
import {getPosts, getPostsBefore, getPostsSince, getPostThread} from 'mattermost-redux/actions/posts';
import {getFilesForPost} from 'mattermost-redux/actions/files';
@ -19,8 +19,8 @@ import {savePreferences} from 'mattermost-redux/actions/preferences';
import {getTeamMembersByIds} from 'mattermost-redux/actions/teams';
import {getProfilesInChannel} from 'mattermost-redux/actions/users';
import {General, Preferences} from 'mattermost-redux/constants';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getTeamByName} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentChannelId, getMyChannelMember} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentTeamId, getTeamByName} from 'mattermost-redux/selectors/entities/teams';
import {
getChannelByName,
@ -283,8 +283,7 @@ export function selectInitialChannel(teamId) {
lastChannel &&
(lastChannel.team_id === teamId || isDMVisible || isGMVisible)
) {
handleSelectChannel(lastChannelId)(dispatch, getState);
markChannelAsRead(lastChannelId)(dispatch, getState);
dispatch(handleSelectChannel(lastChannelId));
return;
}
@ -317,7 +316,6 @@ export function selectPenultimateChannel(teamId) {
dispatch(setChannelLoading(true));
dispatch(setChannelDisplayName(lastChannel.display_name));
dispatch(handleSelectChannel(lastChannelId));
dispatch(markChannelAsRead(lastChannelId));
return;
}
@ -327,7 +325,7 @@ export function selectPenultimateChannel(teamId) {
export function selectDefaultChannel(teamId) {
return (dispatch, getState) => {
const channels = getState().entities.channels.channels;
const {channels} = getState().entities.channels;
const channel = Object.values(channels).find((c) => c.team_id === teamId && c.name === General.DEFAULT_CHANNEL);
let channelId;
@ -345,21 +343,22 @@ export function selectDefaultChannel(teamId) {
if (channelId) {
dispatch(setChannelDisplayName(''));
dispatch(handleSelectChannel(channelId));
dispatch(markChannelAsRead(channelId));
}
};
}
export function handleSelectChannel(channelId) {
return async (dispatch, getState) => {
const {currentTeamId} = getState().entities.teams;
const state = getState();
const currentTeamId = getCurrentTeamId(state);
const currentChannelId = getCurrentChannelId(state);
const sameChannel = channelId === currentChannelId;
const member = getMyChannelMember(state, channelId);
dispatch(setLoadMorePostsVisible(true));
loadPostsIfNecessaryWithRetry(channelId)(dispatch, getState);
selectChannel(channelId)(dispatch, getState);
dispatch(loadPostsIfNecessaryWithRetry(channelId));
dispatch(batchActions([
selectChannel(channelId),
{
type: ViewTypes.SET_INITIAL_POST_VISIBILITY,
data: channelId,
@ -370,7 +369,15 @@ export function handleSelectChannel(channelId) {
teamId: currentTeamId,
channelId,
},
{
type: ViewTypes.SELECT_CHANNEL_WITH_MEMBER,
data: channelId,
member,
},
]));
dispatch(markChannelAsRead(channelId, sameChannel ? null : currentChannelId));
dispatch(markChannelAsViewed(channelId, sameChannel ? null : currentChannelId));
};
}
@ -426,7 +433,7 @@ export function toggleDMChannel(otherUserId, visible, channelId) {
value: Date.now().toString(),
}];
savePreferences(currentUserId, dm)(dispatch, getState);
dispatch(savePreferences(currentUserId, dm));
};
}
@ -442,7 +449,7 @@ export function toggleGMChannel(channelId, visible) {
value: visible,
}];
savePreferences(currentUserId, gm)(dispatch, getState);
dispatch(savePreferences(currentUserId, gm));
};
}
@ -451,9 +458,9 @@ export function closeDMChannel(channel) {
const state = getState();
const currentChannelId = getCurrentChannelId(state);
toggleDMChannel(channel.teammate_id, 'false')(dispatch, getState);
dispatch(toggleDMChannel(channel.teammate_id, 'false'));
if (channel.id === currentChannelId) {
selectInitialChannel(state.entities.teams.currentTeamId)(dispatch, getState);
dispatch(selectInitialChannel(state.entities.teams.currentTeamId));
}
};
}
@ -497,7 +504,7 @@ export function leaveChannel(channel, reset = false) {
await dispatch(selectDefaultChannel(currentTeamId));
}
await serviceLeaveChannel(channel.id)(dispatch, getState);
await dispatch(serviceLeaveChannel(channel.id));
};
}

View file

@ -21,10 +21,10 @@ export function handleCreateChannel(displayName, purpose, header, type) {
type,
};
const {data} = await createChannel(channel, currentUserId)(dispatch, getState);
const {data} = await dispatch(createChannel(channel, currentUserId));
if (data && data.id) {
dispatch(setChannelDisplayName(displayName));
handleSelectChannel(data.id)(dispatch, getState);
dispatch(handleSelectChannel(data.id));
}
};
}

View file

@ -4,7 +4,7 @@
import {GeneralTypes, PostTypes} from 'mattermost-redux/action_types';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
import {fetchMyChannelsAndMembers, markChannelAsRead} from 'mattermost-redux/actions/channels';
import {fetchMyChannelsAndMembers, markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/channels';
import {getClientConfig, getDataRetentionPolicy, getLicenseConfig} from 'mattermost-redux/actions/general';
import {getMyTeams, getMyTeamMembers, selectTeam} from 'mattermost-redux/actions/teams';
@ -49,7 +49,7 @@ export function loadConfigAndLicense() {
};
}
export function loadFromPushNotification(notification) {
export function loadFromPushNotification(notification, startAppFromPushNotification) {
return async (dispatch, getState) => {
const state = getState();
const {data} = notification;
@ -86,12 +86,11 @@ export function loadFromPushNotification(notification) {
dispatch(selectTeam({id: teamId}));
}
// mark channel as read
dispatch(markChannelAsRead(channelId, channelId === currentChannelId ? null : currentChannelId, false));
if (channelId !== currentChannelId) {
if (channelId === currentChannelId && !startAppFromPushNotification) {
dispatch(markChannelAsRead(channelId, null, true));
dispatch(markChannelAsViewed(channelId));
} else if (channelId !== currentChannelId) {
// when the notification is from a channel other than the current channel
dispatch(markChannelAsRead(channelId, currentChannelId, false));
dispatch(setChannelDisplayName(''));
dispatch(handleSelectChannel(channelId));
}

View file

@ -1,22 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {batchActions} from 'redux-batched-actions';
import {ChannelTypes, TeamTypes} from 'mattermost-redux/action_types';
import {markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/channels';
import {TeamTypes} from 'mattermost-redux/action_types';
import {getMyTeams} from 'mattermost-redux/actions/teams';
import {RequestStatus} from 'mattermost-redux/constants';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {NavigationTypes} from 'app/constants';
import {selectFirstAvailableTeam} from 'app/utils/teams';
import {setChannelDisplayName} from './channel';
export function handleTeamChange(teamId, selectChannel = true) {
export function handleTeamChange(teamId) {
return async (dispatch, getState) => {
const state = getState();
const {currentTeamId} = state.entities.teams;
@ -24,19 +18,7 @@ export function handleTeamChange(teamId, selectChannel = true) {
return;
}
const actions = [setChannelDisplayName(''), {type: TeamTypes.SELECT_TEAM, data: teamId}];
if (selectChannel) {
actions.push({type: ChannelTypes.SELECT_CHANNEL, data: ''});
const lastChannels = state.views.team.lastChannelForTeam[teamId] || [];
const lastChannelId = lastChannels[0] || '';
const currentChannelId = getCurrentChannelId(state);
markChannelAsViewed(currentChannelId)(dispatch, getState);
markChannelAsRead(lastChannelId, currentChannelId)(dispatch, getState);
}
dispatch(batchActions(actions, 'BATCH_SELECT_TEAM'), getState);
dispatch({type: TeamTypes.SELECT_TEAM, data: teamId});
};
}

View file

@ -2,18 +2,13 @@
// See LICENSE.txt for license information.
import {UserTypes} from 'mattermost-redux/action_types';
import {getStatus, getStatusesByIds, startPeriodicStatusUpdates} from 'mattermost-redux/actions/users';
import {General} from 'mattermost-redux/constants';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
export function setCurrentUserStatus(isOnline) {
export function setCurrentUserStatusOffline() {
return (dispatch, getState) => {
const currentUserId = getCurrentUserId(getState());
if (isOnline) {
return dispatch(getStatus(currentUserId));
}
return dispatch({
type: UserTypes.RECEIVED_STATUS,
data: {
@ -23,16 +18,3 @@ export function setCurrentUserStatus(isOnline) {
});
};
}
export function initUserStatuses() {
return (dispatch, getState) => {
const {statuses} = getState().entities.users || {};
const userIds = Object.keys(statuses);
if (userIds.length) {
dispatch(getStatusesByIds(userIds));
}
dispatch(startPeriodicStatusUpdates());
};
}

View file

@ -7,7 +7,7 @@ import thunk from 'redux-thunk';
import {UserTypes} from 'mattermost-redux/action_types';
import {General} from 'mattermost-redux/constants';
import {initUserStatuses, setCurrentUserStatus} from 'app/actions/views/user';
import {setCurrentUserStatusOffline} from 'app/actions/views/user';
const mockStore = configureStore([thunk]);
@ -44,28 +44,7 @@ describe('Actions.Views.User', () => {
},
};
await store.dispatch(setCurrentUserStatus(false));
await store.dispatch(setCurrentUserStatusOffline());
expect(store.getActions()).toEqual([action]);
});
test('should fetch the current user status from the server', async () => {
const action = {
type: 'MOCK_GET_STATUS',
args: ['current-user-id'],
};
await store.dispatch(setCurrentUserStatus(true));
expect(store.getActions()).toEqual([action]);
});
test('should initialize the periodic status updates and get the current user statuses', () => {
const actionStatusByIds = {
type: 'MOCK_GET_STATUS_BY_IDS',
args: [['current-user-id', 'another-user-id1', 'another-user-id2']],
};
const actionPeriodicUpdates = {type: 'MOCK_PERIODIC_STATUS_UPDATES'};
store.dispatch(initUserStatuses());
expect(store.getActions()).toEqual([actionStatusByIds, actionPeriodicUpdates]);
});
});

View file

@ -314,7 +314,6 @@ export default class App {
break;
}
this.setStartAppFromPushNotification(false);
this.setAppStarted(true);
}
}

View file

@ -18,8 +18,6 @@ export default class ChannelLoader extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
handleSelectChannel: PropTypes.func.isRequired,
markChannelAsViewed: PropTypes.func.isRequired,
markChannelAsRead: PropTypes.func.isRequired,
setChannelLoading: PropTypes.func.isRequired,
}).isRequired,
backgroundColor: PropTypes.string,
@ -42,7 +40,6 @@ export default class ChannelLoader extends PureComponent {
return {
switch: false,
channel: null,
currentChannelId: null,
};
}
@ -61,22 +58,13 @@ export default class ChannelLoader extends PureComponent {
if (this.state.switch) {
const {
handleSelectChannel,
markChannelAsRead,
markChannelAsViewed,
setChannelLoading,
} = this.props.actions;
const {channel, currentChannelId} = this.state;
const {channel} = this.state;
setTimeout(() => {
handleSelectChannel(channel.id);
// mark the channel as viewed after all the frame has flushed
markChannelAsRead(channel.id, currentChannelId);
if (channel.id !== currentChannelId) {
markChannelAsViewed(currentChannelId);
}
setChannelLoading(false);
}, 250);
}
@ -106,7 +94,7 @@ export default class ChannelLoader extends PureComponent {
if (channel.id === currentChannelId) {
this.props.actions.setChannelLoading(false);
} else {
this.setState({switch: true, channel, currentChannelId});
this.setState({switch: true, channel});
}
};

View file

@ -4,7 +4,6 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/channels';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {handleSelectChannel, setChannelLoading} from 'app/actions/views/channel';
@ -22,9 +21,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
handleSelectChannel,
markChannelAsRead,
setChannelLoading,
markChannelAsViewed,
}, dispatch),
};
}

View file

@ -4,15 +4,12 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {stopPeriodicStatusUpdates, logout} from 'mattermost-redux/actions/users';
import {stopPeriodicStatusUpdates, startPeriodicStatusUpdates, logout} from 'mattermost-redux/actions/users';
import {init as initWebSocket, close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {connection} from 'app/actions/device';
import {
initUserStatuses as startPeriodicStatusUpdates,
setCurrentUserStatus,
} from 'app/actions/views/user';
import {setCurrentUserStatusOffline} from 'app/actions/views/user';
import {getConnection, isLandscape} from 'app/selectors/device';
import NetworkIndicator from './network_indicator';
@ -37,7 +34,7 @@ function mapDispatchToProps(dispatch) {
connection,
initWebSocket,
logout,
setCurrentUserStatus,
setCurrentUserStatusOffline,
startPeriodicStatusUpdates,
stopPeriodicStatusUpdates,
}, dispatch),

View file

@ -45,7 +45,7 @@ export default class NetworkIndicator extends PureComponent {
connection: PropTypes.func.isRequired,
initWebSocket: PropTypes.func.isRequired,
logout: PropTypes.func.isRequired,
setCurrentUserStatus: PropTypes.func.isRequired,
setCurrentUserStatusOffline: PropTypes.func.isRequired,
startPeriodicStatusUpdates: PropTypes.func.isRequired,
stopPeriodicStatusUpdates: PropTypes.func.isRequired,
}).isRequired,
@ -165,7 +165,6 @@ export default class NetworkIndicator extends PureComponent {
};
connected = () => {
this.props.actions.setCurrentUserStatus(true);
Animated.sequence([
Animated.timing(
this.backgroundColor, {
@ -337,7 +336,7 @@ export default class NetworkIndicator extends PureComponent {
duration: 300,
}
).start(() => {
this.props.actions.setCurrentUserStatus(false);
this.props.actions.setCurrentUserStatusOffline();
});
};

View file

@ -87,6 +87,7 @@ const ViewTypes = keyMirror({
SELECTED_ACTION_MENU: null,
SUBMIT_ATTACHMENT_MENU_ACTION: null,
SELECT_CHANNEL_WITH_MEMBER: null,
});
export default {

View file

@ -24,12 +24,15 @@ import {setAppState, setServerVersion} from 'mattermost-redux/actions/general';
import {loadMe, logout} from 'mattermost-redux/actions/users';
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {General} from 'mattermost-redux/constants';
import {handleLoginIdChanged} from 'app/actions/views/login';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {selectDefaultChannel} from 'app/actions/views/channel';
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from 'app/actions/device';
import {handleLoginIdChanged} from 'app/actions/views/login';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
import {loadConfigAndLicense, startDataCleanup} from 'app/actions/views/root';
import initialState from 'app/initial_state';
import configureStore from 'app/store';
import {NavigationTypes} from 'app/constants';
@ -38,14 +41,6 @@ import mattermostManaged from 'app/mattermost_managed';
import {configurePushNotifications} from 'app/utils/push_notifications';
import PushNotifications from 'app/push_notifications';
import {registerScreens} from 'app/screens';
import {
setDeviceDimensions,
setDeviceOrientation,
setDeviceAsTablet,
setStatusBarHeight,
} from 'app/actions/device';
import {loadConfigAndLicense, startDataCleanup} from 'app/actions/views/root';
import {setChannelDisplayName} from 'app/actions/views/channel';
import {deleteFileCache} from 'app/utils/file';
import avoidNativeBridge from 'app/utils/avoid_native_bridge';
import {t} from 'app/utils/i18n';
@ -96,7 +91,7 @@ const initializeModules = () => {
EventEmitter.on(NavigationTypes.RESTART_APP, restartApp);
EventEmitter.on(General.SERVER_VERSION_CHANGED, handleServerVersionChanged);
EventEmitter.on(General.CONFIG_CHANGED, handleConfigChanged);
EventEmitter.on(General.DEFAULT_CHANNEL, handleResetChannelDisplayName);
EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, handleSwithToDefaultChannel);
Dimensions.addEventListener('change', handleOrientationChange);
mattermostManaged.addEventListener('managedConfigDidChange', () => {
handleManagedConfig(true);
@ -342,8 +337,8 @@ const handleAuthentication = async (vendor) => {
return true;
};
const handleResetChannelDisplayName = (displayName) => {
store.dispatch(setChannelDisplayName(displayName));
const handleSwithToDefaultChannel = (teamId) => {
store.dispatch(selectDefaultChannel(teamId));
};
const launchSelectServer = () => {

View file

@ -332,6 +332,22 @@ function loadMorePostsVisible(state = true, action) {
}
}
function lastChannelViewTime(state = {}, action) {
switch (action.type) {
case ViewTypes.SELECT_CHANNEL_WITH_MEMBER: {
if (action.member) {
const nextState = {...state};
nextState[action.data] = action.member.last_viewed_at;
return nextState;
}
return state;
}
default:
return state;
}
}
export default combineReducers({
displayName,
drafts,
@ -343,4 +359,5 @@ export default combineReducers({
lastGetPosts,
retryFailed,
loadMorePostsVisible,
lastChannelViewTime,
});

View file

@ -14,6 +14,8 @@ import {
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {app} from 'app/mattermost';
import InteractiveDialogController from 'app/components/interactive_dialog_controller';
import EmptyToolbar from 'app/components/start/empty_toolbar';
import ChannelLoader from 'app/components/channel_loader';
@ -247,9 +249,12 @@ export default class Channel extends PureComponent {
loadChannelsIfNecessary(teamId).then(() => {
loadProfilesAndTeamMembersForDMSidebar(teamId);
selectInitialChannel(teamId);
}).catch(() => {
selectInitialChannel(teamId);
if (app.startAppFromPushNotification) {
app.setStartAppFromPushNotification(false);
} else {
selectInitialChannel(teamId);
}
});
};

View file

@ -6,7 +6,7 @@ import {connect} from 'react-redux';
import {selectPost} from 'mattermost-redux/actions/posts';
import {getPostIdsInCurrentChannel} from 'mattermost-redux/selectors/entities/posts';
import {getCurrentChannelId, getMyCurrentChannelMembership} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@ -18,7 +18,6 @@ import ChannelPostList from './channel_post_list';
function mapStateToProps(state) {
const channelId = getCurrentChannelId(state);
const channelRefreshingFailed = state.views.channel.retryFailed;
const currentChannelMember = getMyCurrentChannelMembership(state);
return {
channelId,
@ -27,7 +26,7 @@ function mapStateToProps(state) {
deviceHeight: state.device.dimension.deviceHeight,
postIds: getPostIdsInCurrentChannel(state),
postVisibility: state.views.channel.postVisibility[channelId],
lastViewedAt: currentChannelMember && currentChannelMember.last_viewed_at,
lastViewedAt: state.views.channel.lastChannelViewTime[channelId],
loadMorePostsVisible: state.views.channel.loadMorePostsVisible,
refreshing: state.views.channel.refreshing,
theme: getTheme(state),

View file

@ -12,7 +12,6 @@ import {
} from 'react-native';
import {General, Users} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import StatusBar from 'app/components/status_bar';
import {preventDoubleTap} from 'app/utils/tap';
@ -42,6 +41,7 @@ export default class ChannelInfo extends PureComponent {
updateChannelNotifyProps: PropTypes.func.isRequired,
selectPenultimateChannel: PropTypes.func.isRequired,
handleSelectChannel: PropTypes.func.isRequired,
setChannelDisplayName: PropTypes.func.isRequired,
}),
viewArchivedChannels: PropTypes.bool.isRequired,
canDeleteChannel: PropTypes.bool.isRequired,
@ -104,7 +104,7 @@ export default class ChannelInfo extends PureComponent {
close = (redirect = true) => {
if (redirect) {
EventEmitter.emit(General.DEFAULT_CHANNEL, '');
this.props.actions.setChannelDisplayName('');
}
if (Platform.OS === 'android') {
this.props.navigator.dismissModal({animated: true});

View file

@ -33,10 +33,11 @@ import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general
import {
closeDMChannel,
closeGMChannel,
handleSelectChannel,
leaveChannel,
loadChannelsByTeamName,
selectPenultimateChannel,
handleSelectChannel,
setChannelDisplayName,
} from 'app/actions/views/channel';
import ChannelInfo from './channel_info';
@ -107,6 +108,7 @@ function mapDispatchToProps(dispatch) {
selectFocusedPostId,
updateChannelNotifyProps,
selectPenultimateChannel,
setChannelDisplayName,
handleSelectChannel,
}, dispatch),
};

View file

@ -19,7 +19,6 @@ import {
app,
store,
} from 'app/mattermost';
import {loadFromPushNotification} from 'app/actions/views/root';
import {ViewTypes} from 'app/constants';
import PushNotifications from 'app/push_notifications';
import {stripTrailingSlashes} from 'app/utils/url';
@ -202,11 +201,8 @@ export default class Entry extends PureComponent {
const {data, text, badge, completed} = notificationData;
// if the notification has a completed property it means that we are replying to a notification
// and in case it doesn't it means we just opened the notification
if (completed) {
onPushNotificationReply(data, text, badge, completed);
} else {
await store.dispatch(loadFromPushNotification(notification));
}
PushNotifications.resetNotification();
}

View file

@ -4,7 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getChannel as getChannelAction, joinChannel, markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/channels';
import {getChannel as getChannelAction, joinChannel} from 'mattermost-redux/actions/channels';
import {getPostsAfter, getPostsBefore, getPostThread, selectPost} from 'mattermost-redux/actions/posts';
import {makeGetChannel, getMyChannelMemberships} from 'mattermost-redux/selectors/entities/channels';
import {makeGetPostIdsAroundPost, getPost} from 'mattermost-redux/selectors/entities/posts';
@ -68,8 +68,6 @@ function mapDispatchToProps(dispatch) {
handleTeamChange,
joinChannel,
loadThreadIfNecessary,
markChannelAsRead,
markChannelAsViewed,
selectPost,
setChannelDisplayName,
setChannelLoading,

View file

@ -4,7 +4,6 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
InteractionManager,
Text,
TouchableOpacity,
View,
@ -53,8 +52,6 @@ export default class Permalink extends PureComponent {
handleTeamChange: PropTypes.func.isRequired,
joinChannel: PropTypes.func.isRequired,
loadThreadIfNecessary: PropTypes.func.isRequired,
markChannelAsRead: PropTypes.func.isRequired,
markChannelAsViewed: PropTypes.func.isRequired,
selectPost: PropTypes.func.isRequired,
setChannelDisplayName: PropTypes.func.isRequired,
setChannelLoading: PropTypes.func.isRequired,
@ -232,10 +229,8 @@ export default class Permalink extends PureComponent {
const {
handleSelectChannel,
handleTeamChange,
markChannelAsRead,
setChannelLoading,
setChannelDisplayName,
markChannelAsViewed,
} = actions;
actions.selectPost('');
@ -266,19 +261,12 @@ export default class Permalink extends PureComponent {
}
if (channelTeamId && currentTeamId !== channelTeamId) {
handleTeamChange(channelTeamId, false);
handleTeamChange(channelTeamId);
}
setChannelLoading(channelId !== currentChannelId);
setChannelDisplayName(channelDisplayName);
handleSelectChannel(channelId);
InteractionManager.runAfterInteractions(async () => {
markChannelAsRead(channelId, currentChannelId);
if (channelId !== currentChannelId) {
markChannelAsViewed(currentChannelId);
}
});
}
};

View file

@ -28,8 +28,6 @@ describe('Permalink', () => {
handleTeamChange: jest.fn(),
joinChannel: jest.fn(),
loadThreadIfNecessary: jest.fn(),
markChannelAsRead: jest.fn(),
markChannelAsViewed: jest.fn(),
selectPost: jest.fn(),
setChannelDisplayName: jest.fn(),
setChannelLoading: jest.fn(),

View file

@ -6,10 +6,8 @@ import {connect} from 'react-redux';
import {handleTeamChange} from 'app/actions/views/select_team';
import {markChannelAsRead} from 'mattermost-redux/actions/channels';
import {getTeams, joinTeam} from 'mattermost-redux/actions/teams';
import {logout} from 'mattermost-redux/actions/users';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getJoinableTeams} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentLocale} from 'app/selectors/i18n';
@ -30,7 +28,6 @@ function mapStateToProps(state) {
return {
teamsRequest: state.requests.teams.getTeams,
teams: Object.values(getJoinableTeams(state)).sort(sortTeams),
currentChannelId: getCurrentChannelId(state),
};
}
@ -41,7 +38,6 @@ function mapDispatchToProps(dispatch) {
handleTeamChange,
joinTeam,
logout,
markChannelAsRead,
}, dispatch),
};
}

View file

@ -47,9 +47,7 @@ export default class SelectTeam extends PureComponent {
handleTeamChange: PropTypes.func.isRequired,
joinTeam: PropTypes.func.isRequired,
logout: PropTypes.func.isRequired,
markChannelAsRead: PropTypes.func.isRequired,
}).isRequired,
currentChannelId: PropTypes.string,
currentUrl: PropTypes.string.isRequired,
navigator: PropTypes.object,
userWithoutTeams: PropTypes.bool,
@ -143,17 +141,12 @@ export default class SelectTeam extends PureComponent {
onSelectTeam = async (team) => {
this.setState({joining: true});
const {currentChannelId, userWithoutTeams} = this.props;
const {userWithoutTeams} = this.props;
const {
joinTeam,
handleTeamChange,
markChannelAsRead,
} = this.props.actions;
if (currentChannelId) {
markChannelAsRead(currentChannelId);
}
const {error} = await joinTeam(team.invite_id, team.id);
if (error) {
Alert.alert(error.message);

View file

@ -29,7 +29,6 @@ describe('SelectTeam', () => {
handleTeamChange: jest.fn(),
joinTeam: jest.fn(),
logout: jest.fn(),
markChannelAsRead: jest.fn(),
};
const baseProps = {

View file

@ -4,7 +4,7 @@
import {Platform} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import {markChannelAsRead} from 'mattermost-redux/actions/channels';
import {markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/channels';
import {setDeviceToken} from 'mattermost-redux/actions/general';
import {getPosts} from 'mattermost-redux/actions/posts';
import {Client4} from 'mattermost-redux/client';
@ -50,7 +50,7 @@ const onRegisterDevice = (data) => {
};
const loadFromNotification = async (notification) => {
await store.dispatch(loadFromPushNotification(notification));
await store.dispatch(loadFromPushNotification(notification, app.startAppFromPushNotification));
if (!app.startAppFromPushNotification) {
EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED);
PushNotifications.resetNotification();
@ -63,7 +63,7 @@ const onPushNotification = async (deviceNotification) => {
let stopLoadingNotification = false;
// mark the app as started as soon as possible
if (Platform.OS === 'android' && !app.appStarted) {
if (!app.appStarted) {
app.setStartAppFromPushNotification(true);
}
@ -79,6 +79,7 @@ const onPushNotification = async (deviceNotification) => {
if (data.type === 'clear') {
dispatch(markChannelAsRead(data.channel_id, null, false));
dispatch(markChannelAsViewed(data.channel_id, null));
} else {
// get the posts for the channel as soon as possible
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);

36
package-lock.json generated
View file

@ -1949,7 +1949,7 @@
},
"babel-plugin-istanbul": {
"version": "4.1.6",
"resolved": "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz",
"integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==",
"dev": true,
"requires": {
@ -1988,7 +1988,7 @@
},
"babel-plugin-syntax-object-rest-spread": {
"version": "6.13.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
"integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=",
"dev": true
},
@ -2566,6 +2566,7 @@
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
@ -2610,7 +2611,8 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
"dev": true,
"optional": true
},
"is-glob": {
"version": "4.0.0",
@ -2806,7 +2808,7 @@
},
"colors": {
"version": "0.5.1",
"resolved": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
"resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
"integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=",
"dev": true
},
@ -3258,7 +3260,7 @@
"dependencies": {
"domelementtype": {
"version": "1.1.3",
"resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
"dev": true
}
@ -4146,7 +4148,7 @@
},
"form-data": {
"version": "2.3.2",
"resolved": "http://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"dev": true,
"requires": {
@ -6420,7 +6422,7 @@
},
"jest-get-type": {
"version": "22.4.3",
"resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz",
"integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==",
"dev": true
},
@ -8381,8 +8383,8 @@
"integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w="
},
"mattermost-redux": {
"version": "github:mattermost/mattermost-redux#656b40147b82e5914aea9c0e7818ef9f042c298a",
"from": "github:mattermost/mattermost-redux#656b40147b82e5914aea9c0e7818ef9f042c298a",
"version": "github:mattermost/mattermost-redux#457943d7154a4a5c62ece9e28e720136d25c8bb4",
"from": "github:mattermost/mattermost-redux#457943d7154a4a5c62ece9e28e720136d25c8bb4",
"requires": {
"deep-equal": "1.0.1",
"eslint-plugin-header": "2.0.0",
@ -10496,7 +10498,7 @@
},
"os-homedir": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true
},
@ -10680,7 +10682,7 @@
},
"pegjs": {
"version": "0.10.0",
"resolved": "http://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
"resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
"integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0="
},
"performance-now": {
@ -10786,7 +10788,7 @@
},
"pretty-format": {
"version": "4.3.1",
"resolved": "http://registry.npmjs.org/pretty-format/-/pretty-format-4.3.1.tgz",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-4.3.1.tgz",
"integrity": "sha1-UwvlxCs8BbNkFKeipDN6qArNDo0="
},
"private": {
@ -11360,7 +11362,7 @@
},
"react-native-tab-view": {
"version": "0.0.77",
"resolved": "http://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-0.0.77.tgz",
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-0.0.77.tgz",
"integrity": "sha512-9vjD4Ly1Zlum1Y4g23ODpi/F3gYIUIsKWrsZO/Oh5cuX1eiB1DRVn11nY1z+j/hsQfhfyW6nDlmySyDvYQvYCA==",
"requires": {
"prop-types": "^15.6.0"
@ -12134,7 +12136,7 @@
"dependencies": {
"jsesc": {
"version": "0.5.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
}
}
@ -12267,7 +12269,7 @@
},
"require-uncached": {
"version": "1.0.3",
"resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
"resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
"integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
"dev": true,
"requires": {
@ -13330,7 +13332,7 @@
},
"ws": {
"version": "5.1.1",
"resolved": "http://registry.npmjs.org/ws/-/ws-5.1.1.tgz",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz",
"integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==",
"dev": true,
"requires": {
@ -13718,7 +13720,7 @@
},
"load-json-file": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {

View file

@ -18,7 +18,7 @@
"intl": "1.2.5",
"jail-monkey": "1.0.0",
"jsc-android": "236355.1.0",
"mattermost-redux": "github:mattermost/mattermost-redux#656b40147b82e5914aea9c0e7818ef9f042c298a",
"mattermost-redux": "github:mattermost/mattermost-redux#457943d7154a4a5c62ece9e28e720136d25c8bb4",
"mime-db": "1.37.0",
"moment-timezone": "0.5.23",
"prop-types": "15.6.2",