From a0a1fa28ce8e791c6bbc055aa7e8df43780b06f8 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Sat, 16 Feb 2019 15:09:22 -0300 Subject: [PATCH] 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 * Feedback review * Update mm-redux ref --- app/actions/views/channel.js | 43 +++++++++++-------- app/actions/views/create_channel.js | 4 +- app/actions/views/root.js | 13 +++--- app/actions/views/select_team.js | 24 ++--------- app/actions/views/user.js | 20 +-------- app/actions/views/user.test.js | 25 +---------- app/app.js | 1 - .../channel_loader/channel_loader.js | 16 +------ app/components/channel_loader/index.js | 3 -- app/components/network_indicator/index.js | 9 ++-- .../network_indicator/network_indicator.js | 5 +-- app/constants/view.js | 1 + app/mattermost.js | 23 ++++------ app/reducers/views/channel.js | 17 ++++++++ app/screens/channel/channel.js | 11 +++-- .../channel/channel_post_list/index.js | 5 +-- app/screens/channel_info/channel_info.js | 4 +- app/screens/channel_info/index.js | 4 +- app/screens/entry/entry.js | 4 -- app/screens/permalink/index.js | 4 +- app/screens/permalink/permalink.js | 14 +----- app/screens/permalink/permalink.test.js | 2 - app/screens/select_team/index.js | 4 -- app/screens/select_team/select_team.js | 9 +--- app/screens/select_team/select_team.test.js | 1 - app/utils/push_notifications.js | 7 +-- package-lock.json | 36 ++++++++-------- package.json | 2 +- 28 files changed, 115 insertions(+), 196 deletions(-) diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index 2a62d4bcf..725f6eaaf 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -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)); }; } diff --git a/app/actions/views/create_channel.js b/app/actions/views/create_channel.js index 0c6bd7c27..7ecaf6428 100644 --- a/app/actions/views/create_channel.js +++ b/app/actions/views/create_channel.js @@ -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)); } }; } diff --git a/app/actions/views/root.js b/app/actions/views/root.js index f34c82e15..be5035e0a 100644 --- a/app/actions/views/root.js +++ b/app/actions/views/root.js @@ -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)); } diff --git a/app/actions/views/select_team.js b/app/actions/views/select_team.js index 874d6c162..45a4055ec 100644 --- a/app/actions/views/select_team.js +++ b/app/actions/views/select_team.js @@ -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}); }; } diff --git a/app/actions/views/user.js b/app/actions/views/user.js index 52405bba2..ef631e891 100644 --- a/app/actions/views/user.js +++ b/app/actions/views/user.js @@ -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()); - }; -} diff --git a/app/actions/views/user.test.js b/app/actions/views/user.test.js index b8890548a..41fcfee42 100644 --- a/app/actions/views/user.test.js +++ b/app/actions/views/user.test.js @@ -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]); - }); }); diff --git a/app/app.js b/app/app.js index 7ea327d2c..c66f2e7df 100644 --- a/app/app.js +++ b/app/app.js @@ -314,7 +314,6 @@ export default class App { break; } - this.setStartAppFromPushNotification(false); this.setAppStarted(true); } } diff --git a/app/components/channel_loader/channel_loader.js b/app/components/channel_loader/channel_loader.js index aed9b2d6a..53bbc8e1f 100644 --- a/app/components/channel_loader/channel_loader.js +++ b/app/components/channel_loader/channel_loader.js @@ -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}); } }; diff --git a/app/components/channel_loader/index.js b/app/components/channel_loader/index.js index 11f80c3e2..94494744f 100644 --- a/app/components/channel_loader/index.js +++ b/app/components/channel_loader/index.js @@ -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), }; } diff --git a/app/components/network_indicator/index.js b/app/components/network_indicator/index.js index e718afcae..7075f8ff3 100644 --- a/app/components/network_indicator/index.js +++ b/app/components/network_indicator/index.js @@ -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), diff --git a/app/components/network_indicator/network_indicator.js b/app/components/network_indicator/network_indicator.js index c6b32600a..9ab511f6e 100644 --- a/app/components/network_indicator/network_indicator.js +++ b/app/components/network_indicator/network_indicator.js @@ -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(); }); }; diff --git a/app/constants/view.js b/app/constants/view.js index 9b9e5570b..13d8bb670 100644 --- a/app/constants/view.js +++ b/app/constants/view.js @@ -87,6 +87,7 @@ const ViewTypes = keyMirror({ SELECTED_ACTION_MENU: null, SUBMIT_ATTACHMENT_MENU_ACTION: null, + SELECT_CHANNEL_WITH_MEMBER: null, }); export default { diff --git a/app/mattermost.js b/app/mattermost.js index ffef181ef..e09612109 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -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 = () => { diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js index 4e8040be1..8fd862ee3 100644 --- a/app/reducers/views/channel.js +++ b/app/reducers/views/channel.js @@ -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, }); diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js index 24fe49862..99748f8a0 100644 --- a/app/screens/channel/channel.js +++ b/app/screens/channel/channel.js @@ -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); + } }); }; diff --git a/app/screens/channel/channel_post_list/index.js b/app/screens/channel/channel_post_list/index.js index 74705d22e..2e34bdea1 100644 --- a/app/screens/channel/channel_post_list/index.js +++ b/app/screens/channel/channel_post_list/index.js @@ -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), diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js index 67587ccfb..a6ed97a98 100644 --- a/app/screens/channel_info/channel_info.js +++ b/app/screens/channel_info/channel_info.js @@ -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}); diff --git a/app/screens/channel_info/index.js b/app/screens/channel_info/index.js index 6006a797d..086ea975f 100644 --- a/app/screens/channel_info/index.js +++ b/app/screens/channel_info/index.js @@ -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), }; diff --git a/app/screens/entry/entry.js b/app/screens/entry/entry.js index d75d7550b..1a6fa9073 100644 --- a/app/screens/entry/entry.js +++ b/app/screens/entry/entry.js @@ -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(); } diff --git a/app/screens/permalink/index.js b/app/screens/permalink/index.js index b38552dfd..b4de7a6f0 100644 --- a/app/screens/permalink/index.js +++ b/app/screens/permalink/index.js @@ -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, diff --git a/app/screens/permalink/permalink.js b/app/screens/permalink/permalink.js index 67cb217c8..e6a36caf5 100644 --- a/app/screens/permalink/permalink.js +++ b/app/screens/permalink/permalink.js @@ -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); - } - }); } }; diff --git a/app/screens/permalink/permalink.test.js b/app/screens/permalink/permalink.test.js index 1f66ce64f..ddcbc81bb 100644 --- a/app/screens/permalink/permalink.test.js +++ b/app/screens/permalink/permalink.test.js @@ -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(), diff --git a/app/screens/select_team/index.js b/app/screens/select_team/index.js index 011743c81..00e8c78f8 100644 --- a/app/screens/select_team/index.js +++ b/app/screens/select_team/index.js @@ -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), }; } diff --git a/app/screens/select_team/select_team.js b/app/screens/select_team/select_team.js index 55785a390..fd6de7f10 100644 --- a/app/screens/select_team/select_team.js +++ b/app/screens/select_team/select_team.js @@ -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); diff --git a/app/screens/select_team/select_team.test.js b/app/screens/select_team/select_team.test.js index 64311f8a9..bc8cb9c15 100644 --- a/app/screens/select_team/select_team.test.js +++ b/app/screens/select_team/select_team.test.js @@ -29,7 +29,6 @@ describe('SelectTeam', () => { handleTeamChange: jest.fn(), joinTeam: jest.fn(), logout: jest.fn(), - markChannelAsRead: jest.fn(), }; const baseProps = { diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js index 419a96f55..cae19426e 100644 --- a/app/utils/push_notifications.js +++ b/app/utils/push_notifications.js @@ -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); diff --git a/package-lock.json b/package-lock.json index d734d9a2e..6731eefe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index dcbf840e1..d4ea8a6cc 100644 --- a/package.json +++ b/package.json @@ -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",