mattermost-mobile/app/mm-redux/actions/threads.ts
Anurag Shivarathri fc929891ea
MM-37485 MM-37602 MM-37610 MM-37684 MM-37719 MM-37522 MM-37720 App crash, Sync issue fixes (#5612)
Summary
Fixes app crash on opening channel header
Fixes app crash on opening permalink from permalink screen
Fixes showing duplicates channels while searching in "Jump to..."
Fixes auto following the thread on opening the thread view when there are unreads
Fixes updation of threads state (Read/Unread/Deleted) of already loaded threads
Fixes counter showing as blank due to threads sync issue
Doesn't show Follow Thread/Message when CRT is not enabled

Ticket Link
https://mattermost.atlassian.net/browse/MM-37485
https://mattermost.atlassian.net/browse/MM-37602
https://mattermost.atlassian.net/browse/MM-37610
https://mattermost.atlassian.net/browse/MM-37684
https://mattermost.atlassian.net/browse/MM-37719
https://mattermost.atlassian.net/browse/MM-37522
https://mattermost.atlassian.net/browse/MM-37720

Device Information
This PR was tested on: iOS 14.5
2021-08-11 15:56:38 +05:30

289 lines
9.5 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Client4} from '@client/rest';
import {ViewTypes} from '@constants';
import {ThreadTypes, PostTypes, UserTypes} from '@mm-redux/action_types';
import {getCurrentUserId} from '@mm-redux/selectors/entities/common';
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
import {getThread as getThreadSelector, getThreadTeamId} from '@mm-redux/selectors/entities/threads';
import {ActionResult, batchActions, DispatchFunc, GenericAction, GetStateFunc} from '@mm-redux/types/actions';
import {logError} from './errors';
import {forceLogoutIfNecessary} from './helpers';
import {getMissingProfilesByIds} from './users';
import type {Post} from '@mm-redux/types/posts';
import type {UserThread, UserThreadList} from '@mm-redux/types/threads';
export function getThreads(userId: string, teamId: string, before = '', after = '', perPage = ViewTypes.CRT_CHUNK_SIZE, deleted = false, unread = true, since = 0) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let userThreadList: UserThreadList;
try {
userThreadList = await Client4.getUserThreads(userId, teamId, before, after, perPage, true, deleted, unread, since);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
if (userThreadList) {
const currentUserId = getCurrentUserId(getState());
const data = {
threads: [] as UserThread[],
participants: [] as UserThread['participants'],
participantIds: [] as string[],
posts: [] as Post[],
};
userThreadList.threads?.forEach((thread) => {
// threads
data.threads.push({
...thread,
is_following: true,
});
// data.participantIds - Get Missing Profiles
// data.participants - Received Profile List
thread.participants?.forEach((participant) => {
data.participantIds.push(participant.id);
// Exclude current user
if (participant.id !== currentUserId) {
data.participants.push(participant);
}
});
// posts
data.posts.push({
...thread.post,
participants: thread.participants,
});
});
const getThreadsActions: Array<GenericAction> = [
{
type: ThreadTypes.RECEIVED_THREADS,
data: {
...userThreadList,
threads: data.threads,
team_id: teamId,
removeOldThreads:
!before && !after && // When loading on mount
perPage >= ViewTypes.CRT_CHUNK_SIZE && // Exclude on load where we get 5 threads
!since && // Exclude reconnect
!unread, // Exclude when user is loading unreads or on switching to "All your threads" it will take time to fetch from server
},
},
];
if (userThreadList.threads?.length) {
const flat = require('array.prototype.flat');
dispatch(
getMissingProfilesByIds(Array.from(
new Set(
flat(data.participantIds) as string[],
),
)),
);
getThreadsActions.push(
{
type: UserTypes.RECEIVED_PROFILES_LIST,
data: flat(data.participants),
},
{
type: PostTypes.RECEIVED_POSTS,
data: {posts: data.posts},
},
);
}
dispatch(batchActions(getThreadsActions));
}
return {data: userThreadList};
};
}
export function getThread(userId: string, teamId: string, threadId: string, extended = false) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let thread;
try {
thread = await Client4.getUserThread(userId, teamId, threadId, extended);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
if (thread) {
const {data} = dispatch(handleThreadArrived(thread, teamId)) as ActionResult;
thread = data;
}
return {data: thread};
};
}
export function handleThreadArrived(threadData: UserThread, teamId: string) {
return (dispatch: DispatchFunc, getState: GetStateFunc) => {
const thread = {...threadData, is_following: true};
const state = getState();
const currentUserId = getCurrentUserId(state);
const currentTeamId = getCurrentTeamId(state);
dispatch(batchActions([
{
type: UserTypes.RECEIVED_PROFILES_LIST,
data: thread.participants?.filter((user) => user.id !== currentUserId),
},
{
type: PostTypes.RECEIVED_POSTS,
data: {posts: [{...thread.post, participants: thread.participants}]},
},
{
type: ThreadTypes.RECEIVED_THREAD,
data: {
thread,
team_id: teamId,
},
},
]));
const oldThreadData = getThreadSelector(state, threadData.id);
dispatch(handleReadChanged(
thread.id,
teamId || currentTeamId,
thread.post.channel_id,
{
lastViewedAt: thread.last_viewed_at,
prevUnreadMentions: oldThreadData?.unread_mentions ?? 0,
newUnreadMentions: thread.unread_mentions,
prevUnreadReplies: oldThreadData?.unread_replies ?? 0,
newUnreadReplies: thread.unread_replies,
},
));
return {data: thread};
};
}
export function handleAllMarkedRead(teamId: string) {
return (dispatch: DispatchFunc) => {
dispatch({
type: ThreadTypes.ALL_TEAM_THREADS_READ,
data: {
team_id: teamId,
},
});
return {data: true};
};
}
export function markAllThreadsInTeamRead(userId: string, teamId: string) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
try {
await Client4.updateThreadsReadForUser(userId, teamId);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
dispatch(handleAllMarkedRead(teamId));
return {data: true};
};
}
export function updateThreadRead(userId: string, threadId: string, timestamp: number) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
const teamId = getThreadTeamId(getState(), threadId);
try {
await Client4.updateThreadReadForUser(userId, teamId, threadId, timestamp);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
return {};
};
}
export function handleFollowChanged(threadId: string, teamId: string, following: boolean) {
return (dispatch: DispatchFunc) => {
dispatch({
type: ThreadTypes.FOLLOW_CHANGED_THREAD,
data: {
id: threadId,
team_id: teamId,
following,
},
});
return {data: true};
};
}
export function setThreadFollow(userId: string, threadId: string, newState: boolean) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
const teamId = getThreadTeamId(getState(), threadId);
try {
await Client4.updateThreadFollowForUser(userId, teamId, threadId, newState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
dispatch({
type: ThreadTypes.FOLLOW_CHANGED_THREAD,
data: {
id: threadId,
team_id: teamId,
following: newState,
},
});
return {data: true};
};
}
export function handleReadChanged(
threadId: string,
teamId: string,
channelId: string,
{
lastViewedAt,
prevUnreadMentions,
newUnreadMentions,
prevUnreadReplies,
newUnreadReplies,
}: {
lastViewedAt: number;
prevUnreadMentions: number;
newUnreadMentions: number;
prevUnreadReplies: number;
newUnreadReplies: number;
},
) {
return (dispatch: DispatchFunc) => {
dispatch({
type: ThreadTypes.READ_CHANGED_THREAD,
data: {
id: threadId,
teamId,
channelId,
lastViewedAt,
prevUnreadMentions,
newUnreadMentions,
prevUnreadReplies,
newUnreadReplies,
},
});
return {data: true};
};
}