[Perf] Various optimizations for calculating chat lists

This commit is contained in:
Alexander Zinchuk 2021-12-04 13:44:10 +01:00
parent 865ed08d82
commit 8015a7360e
6 changed files with 153 additions and 140 deletions

View File

@ -4,7 +4,7 @@ import React, {
import { withGlobal } from '../../../lib/teact/teactn'; import { withGlobal } from '../../../lib/teact/teactn';
import { ApiChat, ApiChatFolder, ApiUser } from '../../../api/types'; import { ApiChat, ApiChatFolder, ApiUser } from '../../../api/types';
import { GlobalActions } from '../../../global/types'; import { GlobalActions, GlobalState } from '../../../global/types';
import { NotifyException, NotifySettings, SettingsScreens } from '../../../types'; import { NotifyException, NotifySettings, SettingsScreens } from '../../../types';
import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
@ -30,6 +30,7 @@ type OwnProps = {
}; };
type StateProps = { type StateProps = {
allListIds: GlobalState['chats']['listIds'];
chatsById: Record<string, ApiChat>; chatsById: Record<string, ApiChat>;
usersById: Record<string, ApiUser>; usersById: Record<string, ApiUser>;
chatFoldersById: Record<number, ApiChatFolder>; chatFoldersById: Record<number, ApiChatFolder>;
@ -48,6 +49,7 @@ const INFO_THROTTLE = 3000;
const SAVED_MESSAGES_HOTKEY = '0'; const SAVED_MESSAGES_HOTKEY = '0';
const ChatFolders: FC<OwnProps & StateProps & DispatchProps> = ({ const ChatFolders: FC<OwnProps & StateProps & DispatchProps> = ({
allListIds,
chatsById, chatsById,
usersById, usersById,
chatFoldersById, chatFoldersById,
@ -86,11 +88,10 @@ const ChatFolders: FC<OwnProps & StateProps & DispatchProps> = ({
return undefined; return undefined;
} }
const chatIds = Object.keys(chatsById);
const counters = displayedFolders.map((folder) => { const counters = displayedFolders.map((folder) => {
const { const {
unreadDialogsCount, hasActiveDialogs, unreadDialogsCount, hasActiveDialogs,
} = getFolderUnreadDialogs(chatsById, usersById, folder, chatIds, notifySettings, notifyExceptions) || {}; } = getFolderUnreadDialogs(allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions) || {};
return { return {
id: folder.id, id: folder.id,
@ -100,7 +101,7 @@ const ChatFolders: FC<OwnProps & StateProps & DispatchProps> = ({
}); });
return buildCollectionByKey(counters, 'id'); return buildCollectionByKey(counters, 'id');
}, INFO_THROTTLE, [displayedFolders, chatsById, usersById, notifySettings, notifyExceptions]); }, INFO_THROTTLE, [displayedFolders, allListIds, chatsById, usersById, notifySettings, notifyExceptions]);
const folderTabs = useMemo(() => { const folderTabs = useMemo(() => {
if (!displayedFolders || !displayedFolders.length) { if (!displayedFolders || !displayedFolders.length) {
@ -240,7 +241,7 @@ const ChatFolders: FC<OwnProps & StateProps & DispatchProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { const {
chats: { byId: chatsById }, chats: { listIds: allListIds, byId: chatsById },
users: { byId: usersById }, users: { byId: usersById },
chatFolders: { chatFolders: {
byId: chatFoldersById, byId: chatFoldersById,
@ -253,6 +254,7 @@ export default memo(withGlobal<OwnProps>(
} = global; } = global;
return { return {
allListIds,
chatsById, chatsById,
usersById, usersById,
chatFoldersById, chatFoldersById,

View File

@ -3,7 +3,7 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { withGlobal } from '../../../lib/teact/teactn'; import { withGlobal } from '../../../lib/teact/teactn';
import { GlobalActions } from '../../../global/types'; import { GlobalActions, GlobalState } from '../../../global/types';
import { import {
ApiChat, ApiChatFolder, ApiUser, ApiChat, ApiChatFolder, ApiUser,
} from '../../../api/types'; } from '../../../api/types';
@ -37,11 +37,12 @@ type OwnProps = {
}; };
type StateProps = { type StateProps = {
allListIds: GlobalState['chats']['listIds'];
chatsById: Record<string, ApiChat>; chatsById: Record<string, ApiChat>;
usersById: Record<string, ApiUser>; usersById: Record<string, ApiUser>;
chatFolder?: ApiChatFolder;
listIds?: string[]; listIds?: string[];
orderedPinnedIds?: string[]; orderedPinnedIds?: string[];
chatFolder?: ApiChatFolder;
lastSyncTime?: number; lastSyncTime?: number;
notifySettings: NotifySettings; notifySettings: NotifySettings;
notifyExceptions?: Record<number, NotifyException>; notifyExceptions?: Record<number, NotifyException>;
@ -60,15 +61,16 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
folderType, folderType,
folderId, folderId,
isActive, isActive,
chatFolder, allListIds,
chatsById, chatsById,
usersById, usersById,
listIds, listIds,
orderedPinnedIds, orderedPinnedIds,
chatFolder,
lastSyncTime, lastSyncTime,
foldersDispatch,
notifySettings, notifySettings,
notifyExceptions, notifyExceptions,
foldersDispatch,
onScreenSelect, onScreenSelect,
loadMoreChats, loadMoreChats,
preloadTopChatMessages, preloadTopChatMessages,
@ -78,9 +80,12 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
}) => { }) => {
const [currentListIds, currentPinnedIds] = useMemo(() => { const [currentListIds, currentPinnedIds] = useMemo(() => {
return folderType === 'folder' && chatFolder return folderType === 'folder' && chatFolder
? prepareFolderListIds(chatsById, usersById, chatFolder, notifySettings, notifyExceptions) ? prepareFolderListIds(allListIds, chatsById, usersById, chatFolder, notifySettings, notifyExceptions)
: [listIds, orderedPinnedIds]; : [listIds, orderedPinnedIds];
}, [folderType, chatFolder, chatsById, usersById, notifySettings, notifyExceptions, listIds, orderedPinnedIds]); }, [
folderType, chatFolder, allListIds, chatsById, usersById,
notifySettings, notifyExceptions, listIds, orderedPinnedIds,
]);
const [orderById, orderedIds, chatArrays] = useMemo(() => { const [orderById, orderedIds, chatArrays] = useMemo(() => {
if (!currentListIds || (folderType === 'folder' && !chatFolder)) { if (!currentListIds || (folderType === 'folder' && !chatFolder)) {
@ -106,7 +111,7 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
} }
return mapValues(orderById, (order, id) => { return mapValues(orderById, (order, id) => {
return order - (prevOrderById[id] !== undefined ? prevOrderById[id] : Infinity); return prevOrderById[id] !== undefined ? order - prevOrderById[id] : -Infinity;
}); });
}, [orderById, prevOrderById]); }, [orderById, prevOrderById]);
@ -137,6 +142,39 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
} }
}, [lastSyncTime, folderType, preloadTopChatMessages, preloadArchivedChats]); }, [lastSyncTime, folderType, preloadTopChatMessages, preloadArchivedChats]);
// Support <Cmd>+<Digit> and <Alt>+<Up/Down> to navigate between chats
useEffect(() => {
if (!isActive || !orderedIds) {
return undefined;
}
function handleKeyDown(e: KeyboardEvent) {
if (IS_PWA && ((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.code.startsWith('Digit')) {
const [, digit] = e.code.match(/Digit(\d)/) || [];
if (!digit) return;
const position = Number(digit) - 1;
if (position > orderedIds!.length - 1) return;
openChat({ id: orderedIds![position], shouldReplaceHistory: true });
}
if (e.altKey) {
const targetIndexDelta = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : undefined;
if (!targetIndexDelta) return;
e.preventDefault();
openNextChat({ targetIndexDelta, orderedIds });
}
}
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isActive, openChat, openNextChat, orderedIds]);
const getAnimationType = useChatAnimationType(orderDiffById); const getAnimationType = useChatAnimationType(orderDiffById);
function renderChats() { function renderChats() {
@ -179,36 +217,6 @@ const ChatList: FC<OwnProps & StateProps & DispatchProps> = ({
); );
} }
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (isActive && orderedIds) {
if (IS_PWA && ((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.code.startsWith('Digit')) {
const [, digit] = e.code.match(/Digit(\d)/) || [];
if (!digit) return;
const position = Number(digit) - 1;
if (position > orderedIds.length - 1) return;
openChat({ id: orderedIds[position], shouldReplaceHistory: true });
}
if (e.altKey) {
const targetIndexDelta = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : undefined;
if (!targetIndexDelta) return;
e.preventDefault();
openNextChat({ targetIndexDelta, orderedIds });
}
}
};
document.addEventListener('keydown', handleKeyDown, false);
return () => {
document.removeEventListener('keydown', handleKeyDown, false);
};
});
return ( return (
<InfiniteScroll <InfiniteScroll
className="chat-list custom-scroll" className="chat-list custom-scroll"
@ -251,6 +259,7 @@ export default memo(withGlobal<OwnProps>(
const chatFolder = folderId ? selectChatFolder(global, folderId) : undefined; const chatFolder = folderId ? selectChatFolder(global, folderId) : undefined;
return { return {
allListIds: listIds,
chatsById, chatsById,
usersById, usersById,
lastSyncTime, lastSyncTime,

View File

@ -1,4 +1,4 @@
import { useCallback } from '../../../../lib/teact/teact'; import { useMemo } from '../../../../lib/teact/teact';
export enum ChatAnimationTypes { export enum ChatAnimationTypes {
Move, Move,
@ -7,29 +7,27 @@ export enum ChatAnimationTypes {
} }
export function useChatAnimationType(orderDiffById: Record<string, number>) { export function useChatAnimationType(orderDiffById: Record<string, number>) {
const movesUp = useCallback((id: string) => orderDiffById[id] < 0, [orderDiffById]); return useMemo(() => {
const movesDown = useCallback((id: string) => orderDiffById[id] > 0, [orderDiffById]); const orderDiffs = Object.values(orderDiffById);
const numberOfUp = orderDiffs.filter((diff) => diff < 0).length;
const numberOfDown = orderDiffs.filter((diff) => diff > 0).length;
const orderDiffIds = Object.keys(orderDiffById); return (chatId: string): ChatAnimationTypes => {
const numberOfUp = orderDiffIds.filter(movesUp).length; const orderDiff = orderDiffById[chatId];
const numberOfDown = orderDiffIds.filter(movesDown).length; if (orderDiff === 0) {
return ChatAnimationTypes.None;
}
return useCallback((chatId: string): ChatAnimationTypes => { if (
const orderDiff = orderDiffById[chatId]; orderDiff === Infinity
|| orderDiff === -Infinity
|| (numberOfUp <= numberOfDown && orderDiff < 0)
|| (numberOfDown < numberOfUp && orderDiff > 0)
) {
return ChatAnimationTypes.Opacity;
}
if (orderDiff === 0) { return ChatAnimationTypes.Move;
return ChatAnimationTypes.None; };
} }, [orderDiffById]);
if (
orderDiff === Infinity
|| orderDiff === -Infinity
|| (numberOfUp <= numberOfDown && movesUp(chatId))
|| (numberOfDown < numberOfUp && movesDown(chatId))
) {
return ChatAnimationTypes.Opacity;
}
return ChatAnimationTypes.Move;
}, [movesDown, movesUp, numberOfDown, numberOfUp, orderDiffById]);
} }

View File

@ -3,7 +3,7 @@ import React, {
} from '../../../../lib/teact/teact'; } from '../../../../lib/teact/teact';
import { withGlobal } from '../../../../lib/teact/teactn'; import { withGlobal } from '../../../../lib/teact/teactn';
import { GlobalActions } from '../../../../global/types'; import { GlobalActions, GlobalState } from '../../../../global/types';
import { ApiChatFolder, ApiChat, ApiUser } from '../../../../api/types'; import { ApiChatFolder, ApiChat, ApiUser } from '../../../../api/types';
import { NotifyException, NotifySettings, SettingsScreens } from '../../../../types'; import { NotifyException, NotifySettings, SettingsScreens } from '../../../../types';
@ -22,14 +22,15 @@ import Loading from '../../../ui/Loading';
import AnimatedSticker from '../../../common/AnimatedSticker'; import AnimatedSticker from '../../../common/AnimatedSticker';
type OwnProps = { type OwnProps = {
isActive?: boolean;
onCreateFolder: () => void; onCreateFolder: () => void;
onEditFolder: (folder: ApiChatFolder) => void; onEditFolder: (folder: ApiChatFolder) => void;
isActive?: boolean;
onScreenSelect: (screen: SettingsScreens) => void; onScreenSelect: (screen: SettingsScreens) => void;
onReset: () => void; onReset: () => void;
}; };
type StateProps = { type StateProps = {
allListIds: GlobalState['chats']['listIds'];
chatsById: Record<string, ApiChat>; chatsById: Record<string, ApiChat>;
usersById: Record<string, ApiUser>; usersById: Record<string, ApiUser>;
orderedFolderIds?: number[]; orderedFolderIds?: number[];
@ -46,11 +47,8 @@ const runThrottledForLoadRecommended = throttle((cb) => cb(), 60000, true);
const MAX_ALLOWED_FOLDERS = 10; const MAX_ALLOWED_FOLDERS = 10;
const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
onCreateFolder,
onEditFolder,
isActive, isActive,
onScreenSelect, allListIds,
onReset,
chatsById, chatsById,
usersById, usersById,
orderedFolderIds, orderedFolderIds,
@ -58,6 +56,10 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
recommendedChatFolders, recommendedChatFolders,
notifySettings, notifySettings,
notifyExceptions, notifyExceptions,
onCreateFolder,
onEditFolder,
onScreenSelect,
onReset,
loadRecommendedChatFolders, loadRecommendedChatFolders,
addChatFolder, addChatFolder,
showDialog, showDialog,
@ -104,8 +106,6 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
return undefined; return undefined;
} }
const chatIds = Object.keys(chatsById);
return orderedFolderIds.map((id) => { return orderedFolderIds.map((id) => {
const folder = foldersById[id]; const folder = foldersById[id];
@ -113,11 +113,11 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
id: folder.id, id: folder.id,
title: folder.title, title: folder.title,
subtitle: getFolderDescriptionText( subtitle: getFolderDescriptionText(
lang, chatsById, usersById, folder, chatIds, notifySettings, notifyExceptions, lang, allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions,
), ),
}; };
}); });
}, [orderedFolderIds, chatsById, foldersById, usersById, notifySettings, notifyExceptions, lang]); }, [lang, allListIds, foldersById, chatsById, usersById, orderedFolderIds, notifySettings, notifyExceptions]);
const handleCreateFolderFromRecommended = useCallback((folder: ApiChatFolder) => { const handleCreateFolderFromRecommended = useCallback((folder: ApiChatFolder) => {
if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) { if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) {
@ -229,7 +229,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { const {
chats: { byId: chatsById }, chats: { listIds: allListIds, byId: chatsById },
users: { byId: usersById }, users: { byId: usersById },
} = global; } = global;
@ -240,6 +240,7 @@ export default memo(withGlobal<OwnProps>(
} = global.chatFolders; } = global.chatFolders;
return { return {
allListIds,
chatsById, chatsById,
usersById, usersById,
orderedFolderIds, orderedFolderIds,

View File

@ -76,7 +76,7 @@ addReducer('preloadTopChatMessages', (global, actions) => {
} }
const { chatId: currentChatId } = selectCurrentMessageList(global) || {}; const { chatId: currentChatId } = selectCurrentMessageList(global) || {};
const { pinnedChats, otherChats } = prepareChatList(byId, listIds, orderedPinnedIds); const { pinnedChats, otherChats } = prepareChatList(byId, listIds, orderedPinnedIds, 'all', true);
const topChats = [...pinnedChats, ...otherChats]; const topChats = [...pinnedChats, ...otherChats];
const chatToPreload = topChats.find(({ id }) => id !== currentChatId && !preloadedChatIds.includes(id)); const chatToPreload = topChats.find(({ id }) => id !== currentChatId && !preloadedChatIds.includes(id));
if (!chatToPreload) { if (!chatToPreload) {

View File

@ -7,6 +7,7 @@ import {
MAIN_THREAD_ID, MAIN_THREAD_ID,
} from '../../api/types'; } from '../../api/types';
import { GlobalState } from '../../global/types';
import { NotifyException, NotifySettings } from '../../types'; import { NotifyException, NotifySettings } from '../../types';
import { LangFn } from '../../hooks/useLang'; import { LangFn } from '../../hooks/useLang';
@ -269,17 +270,17 @@ export function getCanDeleteChat(chat: ApiChat) {
} }
export function prepareFolderListIds( export function prepareFolderListIds(
allListIds: GlobalState['chats']['listIds'],
chatsById: Record<string, ApiChat>, chatsById: Record<string, ApiChat>,
usersById: Record<string, ApiUser>, usersById: Record<string, ApiUser>,
folder: ApiChatFolder, folder: ApiChatFolder,
notifySettings: NotifySettings, notifySettings: NotifySettings,
notifyExceptions?: Record<number, NotifyException>, notifyExceptions?: Record<number, NotifyException>,
chatIdsCache?: string[],
) { ) {
const excludedChatIds = folder.excludedChatIds ? new Set(folder.excludedChatIds) : undefined; const excludedChatIds = folder.excludedChatIds ? new Set(folder.excludedChatIds) : undefined;
const includedChatIds = folder.excludedChatIds ? new Set(folder.includedChatIds) : undefined; const includedChatIds = folder.excludedChatIds ? new Set(folder.includedChatIds) : undefined;
const pinnedChatIds = folder.excludedChatIds ? new Set(folder.pinnedChatIds) : undefined; const pinnedChatIds = folder.excludedChatIds ? new Set(folder.pinnedChatIds) : undefined;
const listIds = (chatIdsCache || Object.keys(chatsById)) const listIds = ([] as string[]).concat(allListIds.active || [], allListIds.archived || [])
.filter((id) => { .filter((id) => {
return filterChatFolder( return filterChatFolder(
chatsById[id], chatsById[id],
@ -296,6 +297,7 @@ export function prepareFolderListIds(
return [listIds, folder.pinnedChatIds] as const; return [listIds, folder.pinnedChatIds] as const;
} }
// This function is the most expensive in the project, so any possible optimizations are welcome
function filterChatFolder( function filterChatFolder(
chat: ApiChat, chat: ApiChat,
folder: ApiChatFolder, folder: ApiChatFolder,
@ -310,51 +312,55 @@ function filterChatFolder(
return false; return false;
} }
if (excludedChatIds && excludedChatIds.has(chat.id)) { const { id: chatId, type, unreadMentionsCount } = chat;
if (excludedChatIds?.has(chatId)) {
return false; return false;
} }
if (includedChatIds && includedChatIds.has(chat.id)) { if (includedChatIds?.has(chatId)) {
return true; return true;
} }
if (pinnedChatIds && pinnedChatIds.has(chat.id)) { if (pinnedChatIds?.has(chatId)) {
return true; return true;
} }
if (isChatArchived(chat) && folder.excludeArchived) { if (folder.excludeArchived && chat.folderId === ARCHIVED_FOLDER_ID) {
return false; return false;
} }
if (folder.excludeMuted && !chat.unreadMentionsCount && selectIsChatMuted(chat, notifySettings, notifyExceptions)) { if (folder.excludeRead && !chat.unreadCount && !unreadMentionsCount && !chat.hasUnreadMark) {
return false; return false;
} }
if (!chat.unreadCount && !chat.unreadMentionsCount && !chat.hasUnreadMark && folder.excludeRead) { if (folder.excludeMuted && !unreadMentionsCount && selectIsChatMuted(chat, notifySettings, notifyExceptions)) {
return false; return false;
} }
if (isUserId(chat.id)) { if (type === 'chatTypePrivate') {
const privateChatUser = usersById[chat.id]; const user = usersById[chatId];
if (user) {
const { type: userType, isContact } = user;
const isChatWithBot = privateChatUser && privateChatUser.type === 'userTypeBot'; if (userType === 'userTypeBot') {
if (isChatWithBot) { if (folder.bots) {
if (folder.bots) { return true;
return true; }
} } else {
} else { if (folder.contacts && isContact) {
if (folder.contacts && privateChatUser && privateChatUser.isContact) { return true;
return true; }
}
if (folder.nonContacts && privateChatUser && !privateChatUser.isContact) { if (folder.nonContacts && !isContact) {
return true; return true;
}
} }
} }
} else if (isChatGroup(chat)) { } else if (type === 'chatTypeChannel') {
return !!folder.groups;
} else if (isChatChannel(chat)) {
return !!folder.channels; return !!folder.channels;
} else if (type === 'chatTypeBasicGroup' || type === 'chatTypeSuperGroup') {
return !!folder.groups;
} }
return false; return false;
@ -365,6 +371,7 @@ export function prepareChatList(
listIds: string[], listIds: string[],
orderedPinnedIds?: string[], orderedPinnedIds?: string[],
folderType: 'all' | 'archived' | 'folder' = 'all', folderType: 'all' | 'archived' | 'folder' = 'all',
noOrder = false,
) { ) {
const listIdsSet = new Set(listIds); const listIdsSet = new Set(listIds);
const orderedPinnedIdsSet = orderedPinnedIds ? new Set(orderedPinnedIds) : undefined; const orderedPinnedIdsSet = orderedPinnedIds ? new Set(orderedPinnedIds) : undefined;
@ -372,7 +379,7 @@ export function prepareChatList(
const pinnedChats = orderedPinnedIds?.reduce((acc, id) => { const pinnedChats = orderedPinnedIds?.reduce((acc, id) => {
const chat = chatsById[id]; const chat = chatsById[id];
if (chat && listIdsSet.has(chat.id) && chatFilter(chat, folderType)) { if (chat && listIdsSet.has(chat.id) && checkChat(chat, folderType)) {
acc.push(chat); acc.push(chat);
} }
@ -382,39 +389,25 @@ export function prepareChatList(
const otherChats = listIds.reduce((acc, id) => { const otherChats = listIds.reduce((acc, id) => {
const chat = chatsById[id]; const chat = chatsById[id];
if (chat && (!orderedPinnedIdsSet || !orderedPinnedIdsSet.has(chat.id)) && chatFilter(chat, folderType)) { if (chat && (!orderedPinnedIdsSet || !orderedPinnedIdsSet.has(chat.id)) && checkChat(chat, folderType)) {
acc.push(chat); acc.push(chat);
} }
return acc; return acc;
}, [] as ApiChat[]); }, [] as ApiChat[]);
const otherChatsOrdered = orderBy(otherChats, getChatOrder, 'desc');
return { return {
pinnedChats, pinnedChats,
otherChats: otherChatsOrdered, otherChats: noOrder ? otherChats : orderBy(otherChats, getChatOrder, 'desc'),
}; };
} }
function chatFilter(chat: ApiChat, folderType: 'all' | 'archived' | 'folder') { function checkChat(chat: ApiChat, folderType: 'all' | 'archived' | 'folder') {
if (!chat.lastMessage || chat.migratedTo) { return (
return false; chat.lastMessage && !chat.migratedTo && !chat.isRestricted && !chat.isNotJoined
} && !(folderType === 'all' && chat.folderId === ARCHIVED_FOLDER_ID)
&& !(folderType === 'archived' && chat.folderId !== ARCHIVED_FOLDER_ID)
switch (folderType) { );
case 'all':
if (isChatArchived(chat)) {
return false;
}
break;
case 'archived':
if (!isChatArchived(chat)) {
return false;
}
break;
}
return !chat.isRestricted && !chat.isNotJoined;
} }
export function reduceChatList( export function reduceChatList(
@ -430,26 +423,36 @@ export function reduceChatList(
} }
export function getFolderUnreadDialogs( export function getFolderUnreadDialogs(
allListIds: GlobalState['chats']['listIds'],
chatsById: Record<string, ApiChat>, chatsById: Record<string, ApiChat>,
usersById: Record<string, ApiUser>, usersById: Record<string, ApiUser>,
folder: ApiChatFolder, folder: ApiChatFolder,
chatIdsCache: string[],
notifySettings: NotifySettings, notifySettings: NotifySettings,
notifyExceptions?: Record<number, NotifyException>, notifyExceptions?: Record<number, NotifyException>,
) { ) {
const [listIds] = prepareFolderListIds(chatsById, usersById, folder, notifySettings, notifyExceptions, chatIdsCache); const [listIds] = prepareFolderListIds(allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions);
const listedChats = listIds let hasActiveDialogs = false;
.map((id) => chatsById[id]) const unreadDialogsCount = listIds.reduce((acc, id) => {
.filter((chat) => (chat?.lastMessage && !chat.isRestricted && !chat.isNotJoined)); const chat = chatsById[id];
if (!chat?.lastMessage || chat?.isRestricted || chat?.isNotJoined) {
return acc;
}
const unreadDialogsCount = listedChats const isUnread = chat.unreadCount || chat.hasUnreadMark;
.reduce((total, chat) => (chat.unreadCount || chat.hasUnreadMark ? total + 1 : total), 0);
const hasActiveDialogs = listedChats.some((chat) => ( if (isUnread) {
chat.unreadMentionsCount acc++;
|| (!selectIsChatMuted(chat, notifySettings, notifyExceptions) && (chat.unreadCount || chat.hasUnreadMark)) }
));
if (!hasActiveDialogs && (
chat.unreadMentionsCount || (isUnread && !selectIsChatMuted(chat, notifySettings, notifyExceptions))
)) {
hasActiveDialogs = true;
}
return acc;
}, 0);
return { return {
unreadDialogsCount, unreadDialogsCount,
@ -459,10 +462,10 @@ export function getFolderUnreadDialogs(
export function getFolderDescriptionText( export function getFolderDescriptionText(
lang: LangFn, lang: LangFn,
allListIds: GlobalState['chats']['listIds'],
chatsById: Record<string, ApiChat>, chatsById: Record<string, ApiChat>,
usersById: Record<string, ApiUser>, usersById: Record<string, ApiUser>,
folder: ApiChatFolder, folder: ApiChatFolder,
chatIdsCache: string[],
notifySettings: NotifySettings, notifySettings: NotifySettings,
notifyExceptions?: Record<number, NotifyException>, notifyExceptions?: Record<number, NotifyException>,
) { ) {
@ -480,7 +483,7 @@ export function getFolderDescriptionText(
|| (excludedChatIds?.length) || (excludedChatIds?.length)
|| (includedChatIds?.length) || (includedChatIds?.length)
) { ) {
const length = getFolderChatsCount(chatsById, usersById, folder, chatIdsCache, notifySettings, notifyExceptions); const length = getFolderChatsCount(allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions);
return lang('Chats', length); return lang('Chats', length);
} }
@ -501,17 +504,17 @@ export function getFolderDescriptionText(
} }
function getFolderChatsCount( function getFolderChatsCount(
allListIds: GlobalState['chats']['listIds'],
chatsById: Record<string, ApiChat>, chatsById: Record<string, ApiChat>,
usersById: Record<string, ApiUser>, usersById: Record<string, ApiUser>,
folder: ApiChatFolder, folder: ApiChatFolder,
chatIdsCache: string[],
notifySettings: NotifySettings, notifySettings: NotifySettings,
notifyExceptions?: Record<string, NotifyException>, notifyExceptions?: Record<string, NotifyException>,
) { ) {
const [listIds, pinnedIds] = prepareFolderListIds( const [listIds, pinnedIds] = prepareFolderListIds(
chatsById, usersById, folder, notifySettings, notifyExceptions, chatIdsCache, allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions,
); );
const { pinnedChats, otherChats } = prepareChatList(chatsById, listIds, pinnedIds, 'folder'); const { pinnedChats, otherChats } = prepareChatList(chatsById, listIds, pinnedIds, 'folder', true);
return pinnedChats.length + otherChats.length; return pinnedChats.length + otherChats.length;
} }