403 lines
11 KiB
TypeScript
403 lines
11 KiB
TypeScript
import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
|
|
|
import { ApiUpdate, MAIN_THREAD_ID } from '../../../api/types';
|
|
|
|
import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config';
|
|
import { pick } from '../../../util/iteratees';
|
|
import { closeMessageNotifications, notifyAboutNewMessage } from '../../../util/notifications';
|
|
import { updateAppBadge } from '../../../util/appBadge';
|
|
import {
|
|
updateChat,
|
|
updateChatListIds,
|
|
updateChatListType,
|
|
replaceThreadParam,
|
|
leaveChat,
|
|
} from '../../reducers';
|
|
import {
|
|
selectChat,
|
|
selectCommonBoxChatId,
|
|
selectIsChatListed,
|
|
selectChatListType,
|
|
selectCurrentMessageList,
|
|
selectCountNotMutedUnread,
|
|
} from '../../selectors';
|
|
import { throttle } from '../../../util/schedulers';
|
|
|
|
const TYPING_STATUS_CLEAR_DELAY = 6000; // 6 seconds
|
|
// Enough to animate and mark as read in Message List
|
|
const CURRENT_CHAT_UNREAD_DELAY = 1500;
|
|
|
|
const runThrottledForUpdateAppBadge = throttle((cb) => cb(), 500, true);
|
|
|
|
addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|
switch (update['@type']) {
|
|
case 'updateChat': {
|
|
if (!update.noTopChatsRequest && !selectIsChatListed(global, update.id)) {
|
|
// Chat can appear in dialogs list.
|
|
actions.loadTopChats();
|
|
}
|
|
|
|
const newGlobal = updateChat(global, update.id, update.chat, update.newProfilePhoto);
|
|
setGlobal(newGlobal);
|
|
|
|
runThrottledForUpdateAppBadge(() => updateAppBadge(selectCountNotMutedUnread(getGlobal())));
|
|
|
|
if (update.chat.id) {
|
|
closeMessageNotifications({
|
|
chatId: update.chat.id,
|
|
lastReadInboxMessageId: update.chat.lastReadInboxMessageId,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'updateChatJoin': {
|
|
const listType = selectChatListType(global, update.id);
|
|
if (!listType) {
|
|
break;
|
|
}
|
|
|
|
global = updateChatListIds(global, listType, [update.id]);
|
|
global = updateChat(global, update.id, { isNotJoined: false });
|
|
setGlobal(global);
|
|
|
|
const chat = selectChat(global, update.id);
|
|
if (chat) {
|
|
actions.requestChatUpdate({ chatId: chat.id });
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'updateChatLeave': {
|
|
setGlobal(leaveChat(global, update.id));
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateChatInbox': {
|
|
setGlobal(updateChat(global, update.id, update.chat));
|
|
|
|
runThrottledForUpdateAppBadge(() => updateAppBadge(selectCountNotMutedUnread(getGlobal())));
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateChatTypingStatus': {
|
|
const { id, typingStatus } = update;
|
|
setGlobal(updateChat(global, id, { typingStatus }));
|
|
|
|
setTimeout(() => {
|
|
const newGlobal = getGlobal();
|
|
const chat = selectChat(newGlobal, id);
|
|
if (chat && typingStatus && chat.typingStatus && chat.typingStatus.timestamp === typingStatus.timestamp) {
|
|
setGlobal(updateChat(newGlobal, id, { typingStatus: undefined }));
|
|
}
|
|
}, TYPING_STATUS_CLEAR_DELAY);
|
|
|
|
break;
|
|
}
|
|
|
|
case 'newMessage': {
|
|
const { message } = update;
|
|
const { chatId: currentChatId, threadId, type: messageListType } = selectCurrentMessageList(global) || {};
|
|
|
|
if (message.senderId === global.currentUserId && !message.isFromScheduled) {
|
|
return;
|
|
}
|
|
|
|
const chat = selectChat(global, update.chatId);
|
|
if (!chat) {
|
|
return;
|
|
}
|
|
|
|
const isActiveChat = (
|
|
messageListType === 'thread'
|
|
&& threadId === MAIN_THREAD_ID
|
|
&& update.chatId === currentChatId
|
|
);
|
|
|
|
if (isActiveChat) {
|
|
setTimeout(() => {
|
|
actions.requestChatUpdate({ chatId: update.chatId });
|
|
}, CURRENT_CHAT_UNREAD_DELAY);
|
|
} else {
|
|
setGlobal(updateChat(global, update.chatId, {
|
|
unreadCount: chat.unreadCount ? chat.unreadCount + 1 : 1,
|
|
...(update.message.hasUnreadMention && {
|
|
unreadMentionsCount: chat.unreadMentionsCount ? chat.unreadMentionsCount + 1 : 1,
|
|
}),
|
|
}));
|
|
}
|
|
|
|
updateAppBadge(selectCountNotMutedUnread(getGlobal()));
|
|
notifyAboutNewMessage({
|
|
chat,
|
|
message,
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateCommonBoxMessages':
|
|
case 'updateChannelMessages': {
|
|
const { ids, messageUpdate } = update;
|
|
if (messageUpdate.hasUnreadMention !== false) {
|
|
return;
|
|
}
|
|
|
|
ids.forEach((id) => {
|
|
const chatId = ('channelId' in update ? update.channelId : selectCommonBoxChatId(global, id))!;
|
|
const chat = selectChat(global, chatId);
|
|
if (chat?.unreadMentionsCount) {
|
|
global = updateChat(global, chatId, {
|
|
unreadMentionsCount: chat.unreadMentionsCount - 1,
|
|
});
|
|
}
|
|
});
|
|
|
|
setGlobal(global);
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateChatFullInfo': {
|
|
const { fullInfo } = update;
|
|
const targetChat = global.chats.byId[update.id];
|
|
if (!targetChat) {
|
|
return;
|
|
}
|
|
|
|
setGlobal(updateChat(global, update.id, {
|
|
fullInfo: {
|
|
...targetChat.fullInfo,
|
|
...fullInfo,
|
|
},
|
|
}));
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updatePinnedChatIds': {
|
|
const { ids, folderId } = update;
|
|
|
|
const listType = folderId === ARCHIVED_FOLDER_ID ? 'archived' : 'active';
|
|
|
|
global = {
|
|
...global,
|
|
chats: {
|
|
...global.chats,
|
|
orderedPinnedIds: {
|
|
...global.chats.orderedPinnedIds,
|
|
[listType]: ids.length ? ids : undefined,
|
|
},
|
|
},
|
|
};
|
|
|
|
setGlobal(global);
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateChatPinned': {
|
|
const { id, isPinned } = update;
|
|
const listType = selectChatListType(global, id);
|
|
if (listType) {
|
|
const { [listType]: orderedPinnedIds } = global.chats.orderedPinnedIds;
|
|
|
|
let newOrderedPinnedIds = orderedPinnedIds || [];
|
|
if (!isPinned) {
|
|
newOrderedPinnedIds = newOrderedPinnedIds.filter((pinnedId) => pinnedId !== id);
|
|
} else if (!newOrderedPinnedIds.includes(id)) {
|
|
// When moving pinned chats to archive, active ordered pinned ids don't get updated
|
|
// (to preserve chat pinned state when it returns from archive)
|
|
// If user already has max pinned chats, we should check for orderedIds
|
|
// that don't point to listed chats
|
|
if (listType === 'active' && newOrderedPinnedIds.length >= MAX_ACTIVE_PINNED_CHATS) {
|
|
const listIds = global.chats.listIds.active;
|
|
newOrderedPinnedIds = newOrderedPinnedIds.filter((pinnedId) => listIds && listIds.includes(pinnedId));
|
|
}
|
|
|
|
newOrderedPinnedIds = [id, ...newOrderedPinnedIds];
|
|
}
|
|
|
|
global = {
|
|
...global,
|
|
chats: {
|
|
...global.chats,
|
|
orderedPinnedIds: {
|
|
...global.chats.orderedPinnedIds,
|
|
[listType]: newOrderedPinnedIds.length ? newOrderedPinnedIds : undefined,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
setGlobal(global);
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateChatListType': {
|
|
const { id, folderId } = update;
|
|
|
|
setGlobal(updateChatListType(global, id, folderId));
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateChatFolder': {
|
|
const { id, folder } = update;
|
|
const { byId: chatFoldersById, orderedIds } = global.chatFolders;
|
|
|
|
const newChatFoldersById = folder
|
|
? { ...chatFoldersById, [id]: folder }
|
|
: pick(
|
|
chatFoldersById,
|
|
Object.keys(chatFoldersById).map(Number).filter((folderId) => folderId !== id),
|
|
);
|
|
|
|
const newOrderedIds = folder
|
|
? orderedIds && orderedIds.includes(id) ? orderedIds : [...(orderedIds || []), id]
|
|
: orderedIds ? orderedIds.filter((orderedId) => orderedId !== id) : undefined;
|
|
|
|
setGlobal({
|
|
...global,
|
|
chatFolders: {
|
|
...global.chatFolders,
|
|
byId: newChatFoldersById,
|
|
orderedIds: newOrderedIds,
|
|
},
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateChatFoldersOrder': {
|
|
const { orderedIds } = update;
|
|
|
|
setGlobal({
|
|
...global,
|
|
chatFolders: {
|
|
...global.chatFolders,
|
|
orderedIds,
|
|
},
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateRecommendedChatFolders': {
|
|
const { folders } = update;
|
|
|
|
setGlobal({
|
|
...global,
|
|
chatFolders: {
|
|
...global.chatFolders,
|
|
recommended: folders,
|
|
},
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updateChatMembers': {
|
|
const targetChat = global.chats.byId[update.id];
|
|
const { replacedMembers, addedMember, deletedMemberId } = update;
|
|
if (!targetChat) {
|
|
return;
|
|
}
|
|
|
|
let shouldUpdate = false;
|
|
let members = targetChat.fullInfo?.members
|
|
? [...targetChat.fullInfo.members]
|
|
: [];
|
|
|
|
if (replacedMembers) {
|
|
members = replacedMembers;
|
|
shouldUpdate = true;
|
|
} else if (addedMember) {
|
|
if (
|
|
!members.length
|
|
|| !members.some((m) => m.userId === addedMember.userId)
|
|
) {
|
|
members.push(addedMember);
|
|
shouldUpdate = true;
|
|
}
|
|
} else if (members.length && deletedMemberId) {
|
|
const deleteIndex = members.findIndex((m) => m.userId === deletedMemberId);
|
|
if (deleteIndex > -1) {
|
|
members.slice(deleteIndex, 1);
|
|
shouldUpdate = true;
|
|
}
|
|
}
|
|
|
|
if (shouldUpdate) {
|
|
const adminMembers = members.filter(({ isOwner, isAdmin }) => isOwner || isAdmin);
|
|
// TODO Kicked members?
|
|
|
|
setGlobal(updateChat(global, update.id, {
|
|
membersCount: members.length,
|
|
fullInfo: {
|
|
...targetChat.fullInfo,
|
|
members,
|
|
adminMembers,
|
|
},
|
|
}));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 'deleteProfilePhotos': {
|
|
const { chatId, ids } = update;
|
|
const chat = global.chats.byId[chatId];
|
|
|
|
if (chat?.photos) {
|
|
setGlobal(updateChat(global, chatId, {
|
|
photos: chat.photos.filter((photo) => !ids.includes(photo.id)),
|
|
}));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'draftMessage': {
|
|
const {
|
|
chatId, formattedText, date, replyingToId,
|
|
} = update;
|
|
const chat = global.chats.byId[chatId];
|
|
|
|
if (chat) {
|
|
global = replaceThreadParam(global, chatId, MAIN_THREAD_ID, 'draft', formattedText);
|
|
global = replaceThreadParam(global, chatId, MAIN_THREAD_ID, 'replyingToId', replyingToId);
|
|
global = updateChat(global, chatId, { draftDate: date });
|
|
|
|
setGlobal(global);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'showInvite': {
|
|
const { data } = update;
|
|
|
|
actions.showDialog({ data });
|
|
break;
|
|
}
|
|
|
|
case 'updatePendingJoinRequests': {
|
|
const { chatId, requestsPending, recentRequesterIds } = update;
|
|
const chat = global.chats.byId[chatId];
|
|
if (chat) {
|
|
global = updateChat(global, chatId, {
|
|
fullInfo: {
|
|
...chat.fullInfo,
|
|
requestsPending,
|
|
recentRequesterIds,
|
|
},
|
|
});
|
|
setGlobal(global);
|
|
actions.loadChatJoinRequests({ chatId });
|
|
}
|
|
}
|
|
}
|
|
});
|