[Perf] Introduce Folder Manager optimization
This commit is contained in:
parent
1f1c30fd0f
commit
30121c903c
31
src/components/common/UnreadCounter.tsx
Normal file
31
src/components/common/UnreadCounter.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { FC, memo, useEffect } from '../../lib/teact/teact';
|
||||
|
||||
import { formatIntegerCompact } from '../../util/textFormat';
|
||||
import { useFolderManagerForUnreadCounters } from '../../hooks/useFolderManager';
|
||||
import { getAllNotificationsCount } from '../../util/folderManager';
|
||||
import { updateAppBadge } from '../../util/appBadge';
|
||||
|
||||
interface OwnProps {
|
||||
isForAppBadge?: boolean;
|
||||
}
|
||||
|
||||
const UnreadCounter: FC<OwnProps> = ({ isForAppBadge }) => {
|
||||
useFolderManagerForUnreadCounters();
|
||||
const unreadNotificationsCount = getAllNotificationsCount();
|
||||
|
||||
useEffect(() => {
|
||||
if (isForAppBadge) {
|
||||
updateAppBadge(unreadNotificationsCount);
|
||||
}
|
||||
}, [isForAppBadge, unreadNotificationsCount]);
|
||||
|
||||
if (isForAppBadge || !unreadNotificationsCount) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="unread-count active">{formatIntegerCompact(unreadNotificationsCount)}</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(UnreadCounter);
|
||||
@ -3,27 +3,23 @@ import React, {
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { ApiChat, ApiChatFolder, ApiUser } from '../../../api/types';
|
||||
import { GlobalState } from '../../../global/types';
|
||||
import { NotifyException, NotifySettings, SettingsScreens } from '../../../types';
|
||||
import { ApiChatFolder } from '../../../api/types';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
|
||||
|
||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { ALL_FOLDER_ID } from '../../../config';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
|
||||
import { getFolderUnreadDialogs } from '../../../modules/helpers';
|
||||
import { selectNotifyExceptions, selectNotifySettings } from '../../../modules/selectors';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useThrottledMemo from '../../../hooks/useThrottledMemo';
|
||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
|
||||
import Transition from '../../ui/Transition';
|
||||
import TabList from '../../ui/TabList';
|
||||
import ChatList from './ChatList';
|
||||
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
|
||||
|
||||
type OwnProps = {
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
@ -31,12 +27,7 @@ type OwnProps = {
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
allListIds: GlobalState['chats']['listIds'];
|
||||
chatsById: Record<string, ApiChat>;
|
||||
usersById: Record<string, ApiUser>;
|
||||
chatFoldersById: Record<number, ApiChatFolder>;
|
||||
notifySettings: NotifySettings;
|
||||
notifyExceptions?: Record<number, NotifyException>;
|
||||
orderedFolderIds?: number[];
|
||||
activeChatFolder: number;
|
||||
currentUserId?: string;
|
||||
@ -44,23 +35,17 @@ type StateProps = {
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
};
|
||||
|
||||
const INFO_THROTTLE = 3000;
|
||||
const SAVED_MESSAGES_HOTKEY = '0';
|
||||
|
||||
const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
allListIds,
|
||||
chatsById,
|
||||
usersById,
|
||||
foldersDispatch,
|
||||
onScreenSelect,
|
||||
chatFoldersById,
|
||||
notifySettings,
|
||||
notifyExceptions,
|
||||
orderedFolderIds,
|
||||
activeChatFolder,
|
||||
currentUserId,
|
||||
lastSyncTime,
|
||||
shouldSkipHistoryAnimations,
|
||||
foldersDispatch,
|
||||
onScreenSelect,
|
||||
}) => {
|
||||
const {
|
||||
loadChatFolders,
|
||||
@ -85,36 +70,22 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
: undefined;
|
||||
}, [chatFoldersById, orderedFolderIds]);
|
||||
|
||||
const folderCountersById = useThrottledMemo(() => {
|
||||
if (!displayedFolders || !displayedFolders.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const counters = displayedFolders.map((folder) => {
|
||||
const {
|
||||
unreadDialogsCount, hasActiveDialogs,
|
||||
} = getFolderUnreadDialogs(allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions) || {};
|
||||
|
||||
return {
|
||||
id: folder.id,
|
||||
badgeCount: unreadDialogsCount,
|
||||
isBadgeActive: hasActiveDialogs,
|
||||
};
|
||||
});
|
||||
|
||||
return buildCollectionByKey(counters, 'id');
|
||||
}, INFO_THROTTLE, [displayedFolders, allListIds, chatsById, usersById, notifySettings, notifyExceptions]);
|
||||
|
||||
const folderCountersById = useFolderManagerForUnreadCounters();
|
||||
const folderTabs = useMemo(() => {
|
||||
if (!displayedFolders || !displayedFolders.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
{ title: lang.code === 'en' ? 'All' : lang('FilterAllChats'), id: ALL_FOLDER_ID },
|
||||
...displayedFolders.map((folder) => ({
|
||||
title: folder.title,
|
||||
...(folderCountersById?.[folder.id]),
|
||||
{
|
||||
id: ALL_FOLDER_ID,
|
||||
title: lang.code === 'en' ? 'All' : lang('FilterAllChats'),
|
||||
},
|
||||
...displayedFolders.map(({ id, title }) => ({
|
||||
id,
|
||||
title,
|
||||
badgeCount: folderCountersById[id]?.chatsCount,
|
||||
isBadgeActive: Boolean(folderCountersById[id]?.notificationsCount),
|
||||
})),
|
||||
];
|
||||
}, [displayedFolders, folderCountersById, lang]);
|
||||
@ -204,6 +175,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
<ChatList
|
||||
folderType="all"
|
||||
isActive={isActive}
|
||||
lastSyncTime={lastSyncTime}
|
||||
foldersDispatch={foldersDispatch}
|
||||
onScreenSelect={onScreenSelect}
|
||||
/>
|
||||
@ -215,6 +187,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
folderType="folder"
|
||||
folderId={activeFolder.id}
|
||||
isActive={isActive}
|
||||
lastSyncTime={lastSyncTime}
|
||||
onScreenSelect={onScreenSelect}
|
||||
foldersDispatch={foldersDispatch}
|
||||
/>
|
||||
@ -243,8 +216,6 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const {
|
||||
chats: { listIds: allListIds, byId: chatsById },
|
||||
users: { byId: usersById },
|
||||
chatFolders: {
|
||||
byId: chatFoldersById,
|
||||
orderedIds: orderedFolderIds,
|
||||
@ -256,16 +227,11 @@ export default memo(withGlobal<OwnProps>(
|
||||
} = global;
|
||||
|
||||
return {
|
||||
allListIds,
|
||||
chatsById,
|
||||
usersById,
|
||||
chatFoldersById,
|
||||
orderedFolderIds,
|
||||
lastSyncTime,
|
||||
notifySettings: selectNotifySettings(global),
|
||||
notifyExceptions: selectNotifyExceptions(global),
|
||||
activeChatFolder,
|
||||
currentUserId,
|
||||
lastSyncTime,
|
||||
shouldSkipHistoryAnimations,
|
||||
};
|
||||
},
|
||||
|
||||
@ -1,26 +1,24 @@
|
||||
import React, {
|
||||
FC, memo, useMemo, useCallback, useEffect,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||
import { getDispatch } from '../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalState } from '../../../global/types';
|
||||
import {
|
||||
ApiChat, ApiChatFolder, ApiUser,
|
||||
} from '../../../api/types';
|
||||
import { NotifyException, NotifySettings, SettingsScreens } from '../../../types';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
|
||||
|
||||
import { ALL_CHATS_PRELOAD_DISABLED, CHAT_HEIGHT_PX, CHAT_LIST_SLICE } from '../../../config';
|
||||
import {
|
||||
ALL_CHATS_PRELOAD_DISABLED,
|
||||
ALL_FOLDER_ID,
|
||||
ARCHIVED_FOLDER_ID,
|
||||
CHAT_HEIGHT_PX,
|
||||
CHAT_LIST_SLICE,
|
||||
} from '../../../config';
|
||||
import { IS_ANDROID, IS_MAC_OS, IS_PWA } from '../../../util/environment';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import { mapValues } from '../../../util/iteratees';
|
||||
import {
|
||||
getChatOrder, prepareChatList, prepareFolderListIds, reduceChatList,
|
||||
} from '../../../modules/helpers';
|
||||
import {
|
||||
selectChatFolder, selectNotifyExceptions, selectNotifySettings,
|
||||
} from '../../../modules/selectors';
|
||||
import { getPinnedChatsCount } from '../../../util/folderManager';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||
import { useChatAnimationType } from './hooks';
|
||||
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
@ -32,40 +30,16 @@ type OwnProps = {
|
||||
folderType: 'all' | 'archived' | 'folder';
|
||||
folderId?: number;
|
||||
isActive: boolean;
|
||||
onScreenSelect?: (screen: SettingsScreens) => void;
|
||||
foldersDispatch?: FolderEditDispatch;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
allListIds: GlobalState['chats']['listIds'];
|
||||
chatsById: Record<string, ApiChat>;
|
||||
usersById: Record<string, ApiUser>;
|
||||
listIds?: string[];
|
||||
orderedPinnedIds?: string[];
|
||||
chatFolder?: ApiChatFolder;
|
||||
lastSyncTime?: number;
|
||||
notifySettings: NotifySettings;
|
||||
notifyExceptions?: Record<number, NotifyException>;
|
||||
foldersDispatch?: FolderEditDispatch;
|
||||
onScreenSelect?: (screen: SettingsScreens) => void;
|
||||
};
|
||||
|
||||
enum FolderTypeToListType {
|
||||
'all' = 'active',
|
||||
'archived' = 'archived',
|
||||
}
|
||||
|
||||
const ChatList: FC<OwnProps & StateProps> = ({
|
||||
const ChatList: FC<OwnProps> = ({
|
||||
folderType,
|
||||
folderId,
|
||||
isActive,
|
||||
allListIds,
|
||||
chatsById,
|
||||
usersById,
|
||||
listIds,
|
||||
orderedPinnedIds,
|
||||
chatFolder,
|
||||
lastSyncTime,
|
||||
notifySettings,
|
||||
notifyExceptions,
|
||||
foldersDispatch,
|
||||
onScreenSelect,
|
||||
}) => {
|
||||
@ -77,30 +51,22 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
||||
openNextChat,
|
||||
} = getDispatch();
|
||||
|
||||
const [currentListIds, currentPinnedIds] = useMemo(() => {
|
||||
return folderType === 'folder' && chatFolder
|
||||
? prepareFolderListIds(allListIds, chatsById, usersById, chatFolder, notifySettings, notifyExceptions)
|
||||
: [listIds, orderedPinnedIds];
|
||||
}, [
|
||||
folderType, chatFolder, allListIds, chatsById, usersById,
|
||||
notifySettings, notifyExceptions, listIds, orderedPinnedIds,
|
||||
]);
|
||||
const virtualFolderId = (
|
||||
folderType === 'all' ? ALL_FOLDER_ID : folderType === 'archived' ? ARCHIVED_FOLDER_ID : folderId!
|
||||
);
|
||||
|
||||
const [orderById, orderedIds, chatArrays] = useMemo(() => {
|
||||
if (!currentListIds || (folderType === 'folder' && !chatFolder)) {
|
||||
return [];
|
||||
const orderedIds = useFolderManagerForOrderedIds(virtualFolderId);
|
||||
|
||||
const orderById = useMemo(() => {
|
||||
if (!orderedIds) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const newChatArrays = prepareChatList(chatsById, currentListIds, currentPinnedIds, folderType);
|
||||
const singleList = ([] as ApiChat[]).concat(newChatArrays.pinnedChats, newChatArrays.otherChats);
|
||||
const newOrderedIds = singleList.map(({ id }) => id);
|
||||
const newOrderById = singleList.reduce((acc, chat, i) => {
|
||||
acc[chat.id] = i;
|
||||
return orderedIds.reduce((acc, id, i) => {
|
||||
acc[id] = i;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
return [newOrderById, newOrderedIds, newChatArrays];
|
||||
}, [currentListIds, currentPinnedIds, folderType, chatFolder, chatsById]);
|
||||
}, [orderedIds]);
|
||||
|
||||
const prevOrderById = usePrevious(orderById);
|
||||
|
||||
@ -126,14 +92,6 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
||||
folderType === 'all' && !ALL_CHATS_PRELOAD_DISABLED,
|
||||
);
|
||||
|
||||
const viewportChatArrays = useMemo(() => {
|
||||
if (!viewportIds || !chatArrays) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return reduceChatList(chatArrays, viewportIds);
|
||||
}, [chatArrays, viewportIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastSyncTime && folderType === 'all') {
|
||||
preloadTopChatMessages();
|
||||
@ -178,7 +136,7 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
||||
|
||||
function renderChats() {
|
||||
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
|
||||
const pinnedOffset = viewportOffset + viewportChatArrays!.pinnedChats.length;
|
||||
const pinnedCount = getPinnedChatsCount(virtualFolderId) || 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -187,12 +145,12 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
||||
style={IS_ANDROID ? `height: ${orderedIds!.length * CHAT_HEIGHT_PX}px` : undefined}
|
||||
teactFastList
|
||||
>
|
||||
{viewportChatArrays!.pinnedChats.map(({ id }, i) => (
|
||||
{viewportIds!.map((id, i) => (
|
||||
<Chat
|
||||
key={id}
|
||||
teactOrderKey={i}
|
||||
chatId={id}
|
||||
isPinned
|
||||
isPinned={viewportOffset + i < pinnedCount}
|
||||
folderId={folderId}
|
||||
animationType={getAnimationType(id)}
|
||||
orderDiff={orderDiffById[id]}
|
||||
@ -200,18 +158,6 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
||||
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
|
||||
/>
|
||||
))}
|
||||
{viewportChatArrays!.otherChats.map((chat, i) => (
|
||||
<Chat
|
||||
key={chat.id}
|
||||
teactOrderKey={getChatOrder(chat)}
|
||||
chatId={chat.id}
|
||||
folderId={folderId}
|
||||
animationType={getAnimationType(chat.id)}
|
||||
orderDiff={orderDiffById[chat.id]}
|
||||
// @ts-ignore
|
||||
style={`top: ${(pinnedOffset + i) * CHAT_HEIGHT_PX}px;`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -225,7 +171,7 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
||||
noFastList
|
||||
noScrollRestore
|
||||
>
|
||||
{viewportIds?.length && viewportChatArrays ? (
|
||||
{viewportIds?.length ? (
|
||||
renderChats()
|
||||
) : viewportIds && !viewportIds.length ? (
|
||||
(
|
||||
@ -243,33 +189,4 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { folderType, folderId }): StateProps => {
|
||||
const {
|
||||
chats: {
|
||||
listIds,
|
||||
byId: chatsById,
|
||||
orderedPinnedIds,
|
||||
},
|
||||
users: { byId: usersById },
|
||||
lastSyncTime,
|
||||
} = global;
|
||||
const listType = folderType !== 'folder' ? FolderTypeToListType[folderType] : undefined;
|
||||
const chatFolder = folderId ? selectChatFolder(global, folderId) : undefined;
|
||||
|
||||
return {
|
||||
allListIds: listIds,
|
||||
chatsById,
|
||||
usersById,
|
||||
lastSyncTime,
|
||||
notifySettings: selectNotifySettings(global),
|
||||
notifyExceptions: selectNotifyExceptions(global),
|
||||
...(listType ? {
|
||||
listIds: listIds[listType],
|
||||
orderedPinnedIds: orderedPinnedIds[listType],
|
||||
} : {
|
||||
chatFolder,
|
||||
}),
|
||||
};
|
||||
},
|
||||
)(ChatList));
|
||||
export default memo(ChatList);
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||
import { getDispatch, getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalState } from '../../../global/types';
|
||||
import { ApiChat } from '../../../api/types';
|
||||
import { ApiPrivacySettings, SettingsScreens } from '../../../types';
|
||||
|
||||
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID } from '../../../config';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import { filterChatsByName, isChatGroup, isUserId } from '../../../modules/helpers';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import searchWords from '../../../util/searchWords';
|
||||
import { getPrivacyKey } from './helper/privacy';
|
||||
import {
|
||||
getChatTitle, isChatGroup, isUserId, prepareChatList,
|
||||
} from '../../../modules/helpers';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||
import { getPrivacyKey } from './helper/privacy';
|
||||
|
||||
import Picker from '../../common/Picker';
|
||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
@ -28,27 +27,17 @@ export type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
currentUserId?: string;
|
||||
chatsById: Record<string, ApiChat>;
|
||||
listIds?: string[];
|
||||
orderedPinnedIds?: string[];
|
||||
archivedListIds?: string[];
|
||||
archivedPinnedIds?: string[];
|
||||
settings?: ApiPrivacySettings;
|
||||
};
|
||||
|
||||
const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
||||
currentUserId,
|
||||
isAllowList,
|
||||
screen,
|
||||
settings,
|
||||
chatsById,
|
||||
listIds,
|
||||
orderedPinnedIds,
|
||||
archivedListIds,
|
||||
archivedPinnedIds,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
currentUserId,
|
||||
settings,
|
||||
}) => {
|
||||
const { setPrivacySettings } = getDispatch();
|
||||
|
||||
@ -69,46 +58,23 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
||||
const [isSubmitShown, setIsSubmitShown] = useState<boolean>(false);
|
||||
const [newSelectedContactIds, setNewSelectedContactIds] = useState<string[]>(selectedContactIds);
|
||||
|
||||
const chats = useMemo(() => {
|
||||
const activeChatArrays = listIds
|
||||
? prepareChatList(chatsById, listIds, orderedPinnedIds, 'all')
|
||||
: undefined;
|
||||
const archivedChatArrays = archivedListIds
|
||||
? prepareChatList(chatsById, archivedListIds, archivedPinnedIds, 'archived')
|
||||
: undefined;
|
||||
|
||||
if (!activeChatArrays && !archivedChatArrays) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
...(activeChatArrays
|
||||
? [
|
||||
...activeChatArrays.pinnedChats,
|
||||
...activeChatArrays.otherChats,
|
||||
]
|
||||
: []
|
||||
),
|
||||
...(archivedChatArrays ? archivedChatArrays.otherChats : []),
|
||||
];
|
||||
}, [chatsById, listIds, orderedPinnedIds, archivedListIds, archivedPinnedIds]);
|
||||
|
||||
const folderAllOrderedIds = useFolderManagerForOrderedIds(ALL_FOLDER_ID);
|
||||
const folderArchivedOrderedIds = useFolderManagerForOrderedIds(ARCHIVED_FOLDER_ID);
|
||||
const displayedIds = useMemo(() => {
|
||||
if (!chats) {
|
||||
return undefined;
|
||||
}
|
||||
// No need for expensive global updates on chats, so we avoid them
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
|
||||
return chats
|
||||
.filter((chat) => (
|
||||
((isUserId(chat.id) && chat.id !== currentUserId) || isChatGroup(chat))
|
||||
&& (
|
||||
!searchQuery
|
||||
|| searchWords(getChatTitle(lang, chat), searchQuery)
|
||||
|| selectedContactIds.includes(chat.id)
|
||||
)
|
||||
))
|
||||
.map(({ id }) => id);
|
||||
}, [chats, currentUserId, lang, searchQuery, selectedContactIds]);
|
||||
const chatIds = unique([...folderAllOrderedIds, ...folderArchivedOrderedIds])
|
||||
.filter((chatId) => {
|
||||
const chat = chatsById[chatId];
|
||||
return chat && ((isUserId(chat.id) && chat.id !== currentUserId) || isChatGroup(chat));
|
||||
});
|
||||
|
||||
return unique([
|
||||
...selectedContactIds,
|
||||
...filterChatsByName(lang, chatIds, chatsById, searchQuery),
|
||||
]);
|
||||
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedContactIds, lang, searchQuery, currentUserId]);
|
||||
|
||||
const handleSelectedContactIdsChange = useCallback((value: string[]) => {
|
||||
setNewSelectedContactIds(value);
|
||||
@ -175,22 +141,8 @@ function getCurrentPrivacySettings(global: GlobalState, screen: SettingsScreens)
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { screen }): StateProps => {
|
||||
const {
|
||||
chats: {
|
||||
byId: chatsById,
|
||||
listIds,
|
||||
orderedPinnedIds,
|
||||
},
|
||||
currentUserId,
|
||||
} = global;
|
||||
|
||||
return {
|
||||
currentUserId,
|
||||
chatsById,
|
||||
listIds: listIds.active,
|
||||
orderedPinnedIds: orderedPinnedIds.active,
|
||||
archivedPinnedIds: orderedPinnedIds.archived,
|
||||
archivedListIds: listIds.archived,
|
||||
currentUserId: global.currentUserId,
|
||||
settings: getCurrentPrivacySettings(global, screen),
|
||||
};
|
||||
},
|
||||
|
||||
@ -1,23 +1,24 @@
|
||||
import React, {
|
||||
FC, memo, useMemo, useCallback,
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { getDispatch, withGlobal } from '../../../../lib/teact/teactn';
|
||||
import { getDispatch, getGlobal } from '../../../../lib/teact/teactn';
|
||||
|
||||
import { ApiChat } from '../../../../api/types';
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import { unique } from '../../../../util/iteratees';
|
||||
|
||||
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID } from '../../../../config';
|
||||
import { filterChatsByName } from '../../../../modules/helpers';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import searchWords from '../../../../util/searchWords';
|
||||
import { prepareChatList, getChatTitle } from '../../../../modules/helpers';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
import { useFolderManagerForOrderedIds } from '../../../../hooks/useFolderManager';
|
||||
import {
|
||||
FoldersState,
|
||||
FolderEditDispatch,
|
||||
selectChatFilters,
|
||||
} from '../../../../hooks/reducers/useFoldersReducer';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import SettingsFoldersChatsPicker from './SettingsFoldersChatsPicker';
|
||||
|
||||
import Loading from '../../../ui/Loading';
|
||||
|
||||
type OwnProps = {
|
||||
@ -29,26 +30,13 @@ type OwnProps = {
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
chatsById: Record<string, ApiChat>;
|
||||
listIds?: string[];
|
||||
orderedPinnedIds?: string[];
|
||||
archivedListIds?: string[];
|
||||
archivedPinnedIds?: string[];
|
||||
};
|
||||
|
||||
const SettingsFoldersChatFilters: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
const SettingsFoldersChatFilters: FC<OwnProps> = ({
|
||||
mode,
|
||||
state,
|
||||
dispatch,
|
||||
chatsById,
|
||||
listIds,
|
||||
orderedPinnedIds,
|
||||
archivedListIds,
|
||||
archivedPinnedIds,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
}) => {
|
||||
const { loadMoreChats } = getDispatch();
|
||||
|
||||
@ -56,38 +44,20 @@ const SettingsFoldersChatFilters: FC<OwnProps & StateProps> = ({
|
||||
const { selectedChatIds, selectedChatTypes } = selectChatFilters(state, mode, true);
|
||||
|
||||
const lang = useLang();
|
||||
const chats = useMemo(() => {
|
||||
const activeChatArrays = listIds
|
||||
? prepareChatList(chatsById, listIds, orderedPinnedIds, 'all')
|
||||
: undefined;
|
||||
const archivedChatArrays = archivedListIds
|
||||
? prepareChatList(chatsById, archivedListIds, archivedPinnedIds, 'archived')
|
||||
: undefined;
|
||||
|
||||
if (!activeChatArrays && !archivedChatArrays) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
...(activeChatArrays?.pinnedChats || []),
|
||||
...(activeChatArrays?.otherChats || []),
|
||||
...(archivedChatArrays?.otherChats || []),
|
||||
];
|
||||
}, [chatsById, listIds, orderedPinnedIds, archivedListIds, archivedPinnedIds]);
|
||||
const folderAllOrderedIds = useFolderManagerForOrderedIds(ALL_FOLDER_ID);
|
||||
const folderArchivedOrderedIds = useFolderManagerForOrderedIds(ARCHIVED_FOLDER_ID);
|
||||
|
||||
const displayedIds = useMemo(() => {
|
||||
if (!chats) {
|
||||
return undefined;
|
||||
}
|
||||
// No need for expensive global updates on chats, so we avoid them
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
|
||||
return chats
|
||||
.filter((chat) => (
|
||||
!chatFilter
|
||||
|| searchWords(getChatTitle(lang, chat), chatFilter)
|
||||
|| selectedChatIds.includes(chat.id)
|
||||
))
|
||||
.map(({ id }) => id);
|
||||
}, [chats, chatFilter, lang, selectedChatIds]);
|
||||
const chatIds = [...folderAllOrderedIds, ...folderArchivedOrderedIds];
|
||||
return unique([
|
||||
...selectedChatIds,
|
||||
...filterChatsByName(lang, chatIds, chatsById, chatFilter),
|
||||
]);
|
||||
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedChatIds, lang, chatFilter]);
|
||||
|
||||
const handleFilterChange = useCallback((newFilter: string) => {
|
||||
dispatch({
|
||||
@ -159,22 +129,4 @@ const SettingsFoldersChatFilters: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const {
|
||||
chats: {
|
||||
byId: chatsById,
|
||||
listIds,
|
||||
orderedPinnedIds,
|
||||
},
|
||||
} = global;
|
||||
|
||||
return {
|
||||
chatsById,
|
||||
listIds: listIds.active,
|
||||
orderedPinnedIds: orderedPinnedIds.active,
|
||||
archivedPinnedIds: orderedPinnedIds.archived,
|
||||
archivedListIds: listIds.archived,
|
||||
};
|
||||
},
|
||||
)(SettingsFoldersChatFilters));
|
||||
export default memo(SettingsFoldersChatFilters);
|
||||
|
||||
@ -3,17 +3,16 @@ import React, {
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { getDispatch, withGlobal } from '../../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalState } from '../../../../global/types';
|
||||
import { ApiChatFolder, ApiChat, ApiUser } from '../../../../api/types';
|
||||
import { NotifyException, NotifySettings, SettingsScreens } from '../../../../types';
|
||||
import { ApiChatFolder } from '../../../../api/types';
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||
import { selectNotifyExceptions, selectNotifySettings } from '../../../../modules/selectors';
|
||||
import { throttle } from '../../../../util/schedulers';
|
||||
import getAnimationData from '../../../common/helpers/animatedAssets';
|
||||
import { getFolderDescriptionText } from '../../../../modules/helpers';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
import { useFolderManagerForChatsCount } from '../../../../hooks/useFolderManager';
|
||||
|
||||
import ListItem from '../../../ui/ListItem';
|
||||
import Button from '../../../ui/Button';
|
||||
@ -29,14 +28,9 @@ type OwnProps = {
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
allListIds: GlobalState['chats']['listIds'];
|
||||
chatsById: Record<string, ApiChat>;
|
||||
usersById: Record<string, ApiUser>;
|
||||
orderedFolderIds?: number[];
|
||||
foldersById: Record<number, ApiChatFolder>;
|
||||
recommendedChatFolders?: ApiChatFolder[];
|
||||
notifySettings: NotifySettings;
|
||||
notifyExceptions?: Record<number, NotifyException>;
|
||||
};
|
||||
|
||||
const runThrottledForLoadRecommended = throttle((cb) => cb(), 60000, true);
|
||||
@ -45,18 +39,13 @@ const MAX_ALLOWED_FOLDERS = 10;
|
||||
|
||||
const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
allListIds,
|
||||
chatsById,
|
||||
usersById,
|
||||
orderedFolderIds,
|
||||
foldersById,
|
||||
recommendedChatFolders,
|
||||
notifySettings,
|
||||
notifyExceptions,
|
||||
onCreateFolder,
|
||||
onEditFolder,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
orderedFolderIds,
|
||||
foldersById,
|
||||
recommendedChatFolders,
|
||||
}) => {
|
||||
const {
|
||||
loadRecommendedChatFolders,
|
||||
@ -101,6 +90,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Folders);
|
||||
|
||||
const chatsCountByFolderId = useFolderManagerForChatsCount();
|
||||
const userFolders = useMemo(() => {
|
||||
if (!orderedFolderIds) {
|
||||
return undefined;
|
||||
@ -112,12 +102,10 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||
return {
|
||||
id: folder.id,
|
||||
title: folder.title,
|
||||
subtitle: getFolderDescriptionText(
|
||||
lang, allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions,
|
||||
),
|
||||
subtitle: getFolderDescriptionText(lang, folder, chatsCountByFolderId[folder.id]),
|
||||
};
|
||||
});
|
||||
}, [lang, allListIds, foldersById, chatsById, usersById, orderedFolderIds, notifySettings, notifyExceptions]);
|
||||
}, [orderedFolderIds, foldersById, lang, chatsCountByFolderId]);
|
||||
|
||||
const handleCreateFolderFromRecommended = useCallback((folder: ApiChatFolder) => {
|
||||
if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) {
|
||||
@ -228,11 +216,6 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const {
|
||||
chats: { listIds: allListIds, byId: chatsById },
|
||||
users: { byId: usersById },
|
||||
} = global;
|
||||
|
||||
const {
|
||||
orderedIds: orderedFolderIds,
|
||||
byId: foldersById,
|
||||
@ -240,14 +223,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
} = global.chatFolders;
|
||||
|
||||
return {
|
||||
allListIds,
|
||||
chatsById,
|
||||
usersById,
|
||||
orderedFolderIds,
|
||||
foldersById,
|
||||
recommendedChatFolders,
|
||||
notifySettings: selectNotifySettings(global),
|
||||
notifyExceptions: selectNotifyExceptions(global),
|
||||
};
|
||||
},
|
||||
)(SettingsFoldersMain));
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, {
|
||||
FC, useEffect, memo, useCallback,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getDispatch, getGlobal, withGlobal } from '../../lib/teact/teactn';
|
||||
import { getDispatch, withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { LangCode } from '../../types';
|
||||
import { ApiMessage } from '../../api/types';
|
||||
@ -12,7 +12,6 @@ import {
|
||||
} from '../../config';
|
||||
import {
|
||||
selectChatMessage,
|
||||
selectCountNotMutedUnreadOptimized,
|
||||
selectIsForwardModalOpen,
|
||||
selectIsMediaViewerOpen,
|
||||
selectIsRightColumnShown,
|
||||
@ -25,6 +24,7 @@ import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
|
||||
import { processDeepLink } from '../../util/deeplink';
|
||||
import stopEvent from '../../util/stopEvent';
|
||||
import windowSize from '../../util/windowSize';
|
||||
import { getAllNotificationsCount } from '../../util/folderManager';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
||||
import useBeforeUnload from '../../hooks/useBeforeUnload';
|
||||
@ -32,6 +32,8 @@ import useOnChange from '../../hooks/useOnChange';
|
||||
import usePreventPinchZoomGesture from '../../hooks/usePreventPinchZoomGesture';
|
||||
import { LOCATION_HASH } from '../../hooks/useHistoryBack';
|
||||
|
||||
import StickerSetModal from '../common/StickerSetModal.async';
|
||||
import UnreadCount from '../common/UnreadCounter';
|
||||
import LeftColumn from '../left/LeftColumn';
|
||||
import MiddleColumn from '../middle/MiddleColumn';
|
||||
import RightColumn from '../right/RightColumn';
|
||||
@ -43,7 +45,6 @@ import Dialogs from './Dialogs.async';
|
||||
import ForwardPicker from './ForwardPicker.async';
|
||||
import SafeLinkModal from './SafeLinkModal.async';
|
||||
import HistoryCalendar from './HistoryCalendar.async';
|
||||
import StickerSetModal from '../common/StickerSetModal.async';
|
||||
import GroupCall from '../calls/group/GroupCall.async';
|
||||
import ActiveCallHeader from '../calls/ActiveCallHeader.async';
|
||||
import CallFallbackConfirm from '../calls/CallFallbackConfirm.async';
|
||||
@ -248,7 +249,7 @@ const Main: FC<StateProps> = ({
|
||||
const handleBlur = useCallback(() => {
|
||||
updateIsOnline(false);
|
||||
|
||||
const initialUnread = selectCountNotMutedUnreadOptimized(getGlobal());
|
||||
const initialUnread = getAllNotificationsCount();
|
||||
let index = 0;
|
||||
|
||||
clearInterval(notificationInterval);
|
||||
@ -259,7 +260,7 @@ const Main: FC<StateProps> = ({
|
||||
}
|
||||
|
||||
if (index % 2 === 0) {
|
||||
const newUnread = selectCountNotMutedUnreadOptimized(getGlobal()) - initialUnread;
|
||||
const newUnread = getAllNotificationsCount() - initialUnread;
|
||||
if (newUnread > 0) {
|
||||
updatePageTitle(`${newUnread} notification${newUnread > 1 ? 's' : ''}`);
|
||||
updateIcon(true);
|
||||
@ -321,6 +322,7 @@ const Main: FC<StateProps> = ({
|
||||
)}
|
||||
<DownloadManager />
|
||||
<CallFallbackConfirm isOpen={isCallFallbackConfirmOpen} />
|
||||
<UnreadCount isForAppBadge />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -46,13 +46,13 @@ import useConnectionStatus from '../../hooks/useConnectionStatus';
|
||||
|
||||
import PrivateChatInfo from '../common/PrivateChatInfo';
|
||||
import GroupChatInfo from '../common/GroupChatInfo';
|
||||
import UnreadCounter from '../common/UnreadCounter';
|
||||
import Transition from '../ui/Transition';
|
||||
import Button from '../ui/Button';
|
||||
import HeaderActions from './HeaderActions';
|
||||
import HeaderPinnedMessage from './HeaderPinnedMessage';
|
||||
import AudioPlayer from './AudioPlayer';
|
||||
import GroupCallTopPane from '../calls/group/GroupCallTopPane';
|
||||
import UnreadCount from './UnreadCount';
|
||||
|
||||
import './MiddleHeader.scss';
|
||||
|
||||
@ -346,7 +346,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
function renderBackButton(asClose = false, withUnreadCount = false) {
|
||||
function renderBackButton(asClose = false, withUnreadCounter = false) {
|
||||
return (
|
||||
<div className="back-button">
|
||||
<Button
|
||||
@ -358,7 +358,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<div className={buildClassName('animated-close-icon', !asClose && 'state-back')} />
|
||||
</Button>
|
||||
{withUnreadCount && <UnreadCount />}
|
||||
{withUnreadCounter && <UnreadCounter />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
import React, { FC, memo } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { GlobalState } from '../../global/types';
|
||||
|
||||
import { selectCountNotMutedUnreadOptimized } from '../../modules/selectors';
|
||||
import { formatIntegerCompact } from '../../util/textFormat';
|
||||
|
||||
type StateProps = {
|
||||
unreadCount: number;
|
||||
};
|
||||
|
||||
const UnreadCount: FC<StateProps> = ({
|
||||
unreadCount,
|
||||
}) => {
|
||||
if (!unreadCount) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="unread-count active">{formatIntegerCompact(unreadCount)}</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal(
|
||||
(global: GlobalState): StateProps => {
|
||||
return {
|
||||
unreadCount: selectCountNotMutedUnreadOptimized(global),
|
||||
};
|
||||
},
|
||||
)(UnreadCount));
|
||||
33
src/hooks/useFolderManager.ts
Normal file
33
src/hooks/useFolderManager.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { useEffect, useState } from '../lib/teact/teact';
|
||||
import {
|
||||
getOrderedIds,
|
||||
getUnreadCounters,
|
||||
getChatsCount,
|
||||
addOrderedIdsCallback,
|
||||
addUnreadCountersCallback,
|
||||
addChatsCountCallback,
|
||||
} from '../util/folderManager';
|
||||
|
||||
export function useFolderManagerForOrderedIds(folderId: number) {
|
||||
const [orderedIds, setOrderedIds] = useState(getOrderedIds(folderId));
|
||||
|
||||
useEffect(() => addOrderedIdsCallback(folderId, setOrderedIds), [folderId]);
|
||||
|
||||
return orderedIds;
|
||||
}
|
||||
|
||||
export function useFolderManagerForUnreadCounters() {
|
||||
const [unreadCounters, setUnreadCounters] = useState(getUnreadCounters());
|
||||
|
||||
useEffect(() => addUnreadCountersCallback(setUnreadCounters), []);
|
||||
|
||||
return unreadCounters;
|
||||
}
|
||||
|
||||
export function useFolderManagerForChatsCount() {
|
||||
const [chatsCount, setChatsCount] = useState(getChatsCount());
|
||||
|
||||
useEffect(() => addChatsCountCallback(setChatsCount), []);
|
||||
|
||||
return chatsCount;
|
||||
}
|
||||
@ -16,7 +16,7 @@ import {
|
||||
LOCALIZED_TIPS,
|
||||
RE_TG_LINK,
|
||||
SERVICE_NOTIFICATIONS_USER_ID,
|
||||
TMP_CHAT_ID,
|
||||
TMP_CHAT_ID, ALL_FOLDER_ID,
|
||||
} from '../../../config';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import {
|
||||
@ -46,11 +46,12 @@ import {
|
||||
import { buildCollectionByKey, omit } from '../../../util/iteratees';
|
||||
import { debounce, pause, throttle } from '../../../util/schedulers';
|
||||
import {
|
||||
isChatSummaryOnly, isChatArchived, prepareChatList, isChatBasicGroup,
|
||||
isChatSummaryOnly, isChatArchived, isChatBasicGroup,
|
||||
} from '../../helpers';
|
||||
import { processDeepLink } from '../../../util/deeplink';
|
||||
import { updateGroupCall } from '../../reducers/calls';
|
||||
import { selectGroupCall } from '../../selectors/calls';
|
||||
import { getOrderedIds } from '../../../util/folderManager';
|
||||
|
||||
const TOP_CHAT_MESSAGES_PRELOAD_INTERVAL = 100;
|
||||
const CHATS_PRELOAD_INTERVAL = 300;
|
||||
@ -61,31 +62,21 @@ const runDebouncedForLoadFullChat = debounce((cb) => cb(), 500, false, true);
|
||||
|
||||
addReducer('preloadTopChatMessages', (global, actions) => {
|
||||
(async () => {
|
||||
const preloadedChatIds: string[] = [];
|
||||
const preloadedChatIds = new Set<string>();
|
||||
|
||||
for (let i = 0; i < TOP_CHAT_MESSAGES_PRELOAD_LIMIT; i++) {
|
||||
await pause(TOP_CHAT_MESSAGES_PRELOAD_INTERVAL);
|
||||
|
||||
const {
|
||||
byId,
|
||||
listIds: { active: listIds },
|
||||
orderedPinnedIds: { active: orderedPinnedIds },
|
||||
} = getGlobal().chats;
|
||||
if (!listIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { chatId: currentChatId } = selectCurrentMessageList(global) || {};
|
||||
const { pinnedChats, otherChats } = prepareChatList(byId, listIds, orderedPinnedIds, 'all', true);
|
||||
const topChats = [...pinnedChats, ...otherChats];
|
||||
const chatToPreload = topChats.find(({ id }) => id !== currentChatId && !preloadedChatIds.includes(id));
|
||||
if (!chatToPreload) {
|
||||
const folderAllOrderedIds = getOrderedIds(ALL_FOLDER_ID);
|
||||
const nextChatId = folderAllOrderedIds?.find((id) => id !== currentChatId && !preloadedChatIds.has(id));
|
||||
if (!nextChatId) {
|
||||
return;
|
||||
}
|
||||
|
||||
preloadedChatIds.push(chatToPreload.id);
|
||||
preloadedChatIds.add(nextChatId);
|
||||
|
||||
actions.loadViewportMessages({ chatId: chatToPreload.id, threadId: MAIN_THREAD_ID });
|
||||
actions.loadViewportMessages({ chatId: nextChatId, threadId: MAIN_THREAD_ID });
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
@ -11,7 +11,6 @@ import {
|
||||
} from '../../../config';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { updateAppBadge } from '../../../util/appBadge';
|
||||
import {
|
||||
replaceChatListIds,
|
||||
replaceChats,
|
||||
@ -34,7 +33,6 @@ import {
|
||||
selectDraft,
|
||||
selectChatMessage,
|
||||
selectThreadInfo,
|
||||
selectCountNotMutedUnreadOptimized,
|
||||
selectLastServiceNotification,
|
||||
} from '../../selectors';
|
||||
import { isUserId } from '../../helpers';
|
||||
@ -102,8 +100,6 @@ async function afterSync() {
|
||||
|
||||
await callApi('fetchCurrentUser');
|
||||
|
||||
updateAppBadge(selectCountNotMutedUnreadOptimized(getGlobal()));
|
||||
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('>>> FINISH AFTER-SYNC');
|
||||
|
||||
@ -5,7 +5,6 @@ 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,
|
||||
@ -19,16 +18,12 @@ import {
|
||||
selectIsChatListed,
|
||||
selectChatListType,
|
||||
selectCurrentMessageList,
|
||||
selectCountNotMutedUnreadOptimized,
|
||||
} 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': {
|
||||
@ -40,8 +35,6 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||
const newGlobal = updateChat(global, update.id, update.chat, update.newProfilePhoto);
|
||||
setGlobal(newGlobal);
|
||||
|
||||
runThrottledForUpdateAppBadge(() => updateAppBadge(selectCountNotMutedUnreadOptimized(getGlobal())));
|
||||
|
||||
if (update.chat.id) {
|
||||
closeMessageNotifications({
|
||||
chatId: update.chat.id,
|
||||
@ -77,8 +70,6 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||
case 'updateChatInbox': {
|
||||
setGlobal(updateChat(global, update.id, update.chat));
|
||||
|
||||
runThrottledForUpdateAppBadge(() => updateAppBadge(selectCountNotMutedUnreadOptimized(getGlobal())));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -129,7 +120,6 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||
}));
|
||||
}
|
||||
|
||||
updateAppBadge(selectCountNotMutedUnreadOptimized(getGlobal()));
|
||||
notifyAboutNewMessage({
|
||||
chat,
|
||||
message,
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
MAIN_THREAD_ID,
|
||||
} from '../../api/types';
|
||||
|
||||
import { GlobalState } from '../../global/types';
|
||||
import { NotifyException, NotifySettings } from '../../types';
|
||||
import { LangFn } from '../../hooks/useLang';
|
||||
|
||||
@ -270,207 +269,7 @@ export function getCanDeleteChat(chat: ApiChat) {
|
||||
return isChatBasicGroup(chat) || ((isChatSuperGroup(chat) || isChatChannel(chat)) && chat.isCreator);
|
||||
}
|
||||
|
||||
export function prepareFolderListIds(
|
||||
allListIds: GlobalState['chats']['listIds'],
|
||||
chatsById: Record<string, ApiChat>,
|
||||
usersById: Record<string, ApiUser>,
|
||||
folder: ApiChatFolder,
|
||||
notifySettings: NotifySettings,
|
||||
notifyExceptions?: Record<number, NotifyException>,
|
||||
) {
|
||||
const excludedChatIds = folder.excludedChatIds ? new Set(folder.excludedChatIds) : undefined;
|
||||
const includedChatIds = folder.excludedChatIds ? new Set(folder.includedChatIds) : undefined;
|
||||
const pinnedChatIds = folder.excludedChatIds ? new Set(folder.pinnedChatIds) : undefined;
|
||||
const listIds = ([] as string[]).concat(allListIds.active || [], allListIds.archived || [])
|
||||
.filter((id) => {
|
||||
const chat = chatsById[id];
|
||||
return chat && filterChatFolder(
|
||||
chat,
|
||||
folder,
|
||||
usersById,
|
||||
notifySettings,
|
||||
notifyExceptions,
|
||||
excludedChatIds,
|
||||
includedChatIds,
|
||||
pinnedChatIds,
|
||||
);
|
||||
});
|
||||
|
||||
return [listIds, folder.pinnedChatIds] as const;
|
||||
}
|
||||
|
||||
// This function is the most expensive in the project, so any possible optimizations are welcome
|
||||
function filterChatFolder(
|
||||
chat: ApiChat,
|
||||
folder: ApiChatFolder,
|
||||
usersById: Record<string, ApiUser>,
|
||||
notifySettings: NotifySettings,
|
||||
notifyExceptions?: Record<number, NotifyException>,
|
||||
excludedChatIds?: Set<string>,
|
||||
includedChatIds?: Set<string>,
|
||||
pinnedChatIds?: Set<string>,
|
||||
) {
|
||||
if (!chat.isListed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { id: chatId, type, unreadMentionsCount } = chat;
|
||||
|
||||
if (excludedChatIds?.has(chatId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (includedChatIds?.has(chatId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pinnedChatIds?.has(chatId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (folder.excludeArchived && chat.folderId === ARCHIVED_FOLDER_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (folder.excludeRead && !chat.unreadCount && !unreadMentionsCount && !chat.hasUnreadMark) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (folder.excludeMuted && !unreadMentionsCount && selectIsChatMuted(chat, notifySettings, notifyExceptions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type === 'chatTypePrivate') {
|
||||
const user = usersById[chatId];
|
||||
if (user) {
|
||||
const { type: userType, isContact } = user;
|
||||
|
||||
if (userType === 'userTypeBot') {
|
||||
if (folder.bots) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (folder.contacts && isContact) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (folder.nonContacts && !isContact) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type === 'chatTypeChannel') {
|
||||
return Boolean(folder.channels);
|
||||
} else if (type === 'chatTypeBasicGroup' || type === 'chatTypeSuperGroup') {
|
||||
return Boolean(folder.groups);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function prepareChatList(
|
||||
chatsById: Record<string, ApiChat>,
|
||||
listIds: string[],
|
||||
orderedPinnedIds?: string[],
|
||||
folderType: 'all' | 'archived' | 'folder' = 'all',
|
||||
noOrder = false,
|
||||
) {
|
||||
const listIdsSet = new Set(listIds);
|
||||
const orderedPinnedIdsSet = orderedPinnedIds ? new Set(orderedPinnedIds) : undefined;
|
||||
|
||||
const pinnedChats = orderedPinnedIds?.reduce((acc, id) => {
|
||||
const chat = chatsById[id];
|
||||
|
||||
if (chat && listIdsSet.has(chat.id) && checkChat(chat, folderType)) {
|
||||
acc.push(chat);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [] as ApiChat[]) || [];
|
||||
|
||||
const otherChats = listIds.reduce((acc, id) => {
|
||||
const chat = chatsById[id];
|
||||
|
||||
if (chat && (!orderedPinnedIdsSet || !orderedPinnedIdsSet.has(chat.id)) && checkChat(chat, folderType)) {
|
||||
acc.push(chat);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [] as ApiChat[]);
|
||||
|
||||
return {
|
||||
pinnedChats,
|
||||
otherChats: noOrder ? otherChats : orderBy(otherChats, getChatOrder, 'desc'),
|
||||
};
|
||||
}
|
||||
|
||||
function checkChat(chat: ApiChat, folderType: 'all' | 'archived' | 'folder') {
|
||||
return (
|
||||
chat.lastMessage && !chat.migratedTo && !chat.isRestricted && !chat.isNotJoined
|
||||
&& !(folderType === 'all' && chat.folderId === ARCHIVED_FOLDER_ID)
|
||||
&& !(folderType === 'archived' && chat.folderId !== ARCHIVED_FOLDER_ID)
|
||||
);
|
||||
}
|
||||
|
||||
export function reduceChatList(
|
||||
chatArrays: { pinnedChats: ApiChat[]; otherChats: ApiChat[] },
|
||||
filteredIds: string[],
|
||||
) {
|
||||
const filteredIdsSet = new Set(filteredIds);
|
||||
|
||||
return {
|
||||
pinnedChats: chatArrays.pinnedChats.filter(({ id }) => filteredIdsSet.has(id)),
|
||||
otherChats: chatArrays.otherChats.filter(({ id }) => filteredIdsSet.has(id)),
|
||||
};
|
||||
}
|
||||
|
||||
export function getFolderUnreadDialogs(
|
||||
allListIds: GlobalState['chats']['listIds'],
|
||||
chatsById: Record<string, ApiChat>,
|
||||
usersById: Record<string, ApiUser>,
|
||||
folder: ApiChatFolder,
|
||||
notifySettings: NotifySettings,
|
||||
notifyExceptions?: Record<number, NotifyException>,
|
||||
) {
|
||||
const [listIds] = prepareFolderListIds(allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions);
|
||||
|
||||
let hasActiveDialogs = false;
|
||||
const unreadDialogsCount = listIds.reduce((acc, id) => {
|
||||
const chat = chatsById[id];
|
||||
if (!chat?.lastMessage || chat?.isRestricted || chat?.isNotJoined) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const isUnread = chat.unreadCount || chat.hasUnreadMark;
|
||||
|
||||
if (isUnread) {
|
||||
acc++;
|
||||
}
|
||||
|
||||
if (!hasActiveDialogs && (
|
||||
chat.unreadMentionsCount || (isUnread && !selectIsChatMuted(chat, notifySettings, notifyExceptions))
|
||||
)) {
|
||||
hasActiveDialogs = true;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
unreadDialogsCount,
|
||||
hasActiveDialogs,
|
||||
};
|
||||
}
|
||||
|
||||
export function getFolderDescriptionText(
|
||||
lang: LangFn,
|
||||
allListIds: GlobalState['chats']['listIds'],
|
||||
chatsById: Record<string, ApiChat>,
|
||||
usersById: Record<string, ApiUser>,
|
||||
folder: ApiChatFolder,
|
||||
notifySettings: NotifySettings,
|
||||
notifyExceptions?: Record<number, NotifyException>,
|
||||
) {
|
||||
export function getFolderDescriptionText(lang: LangFn, folder: ApiChatFolder, chatsCount?: number) {
|
||||
const {
|
||||
id, title, emoticon, description, pinnedChatIds,
|
||||
excludedChatIds, includedChatIds,
|
||||
@ -481,12 +280,12 @@ export function getFolderDescriptionText(
|
||||
// If folder has multiple additive filters or uses include/exclude lists,
|
||||
// we display folder chats count
|
||||
if (
|
||||
Object.values(filters).filter(Boolean).length > 1
|
||||
|| (excludedChatIds?.length)
|
||||
|| (includedChatIds?.length)
|
||||
) {
|
||||
const length = getFolderChatsCount(allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions);
|
||||
return lang('Chats', length);
|
||||
chatsCount !== undefined && (
|
||||
Object.values(filters).filter(Boolean).length > 1
|
||||
|| (excludedChatIds?.length)
|
||||
|| (includedChatIds?.length)
|
||||
)) {
|
||||
return lang('Chats', chatsCount);
|
||||
}
|
||||
|
||||
// Otherwise, we return a short description of a single filter
|
||||
@ -505,21 +304,6 @@ export function getFolderDescriptionText(
|
||||
}
|
||||
}
|
||||
|
||||
function getFolderChatsCount(
|
||||
allListIds: GlobalState['chats']['listIds'],
|
||||
chatsById: Record<string, ApiChat>,
|
||||
usersById: Record<string, ApiUser>,
|
||||
folder: ApiChatFolder,
|
||||
notifySettings: NotifySettings,
|
||||
notifyExceptions?: Record<string, NotifyException>,
|
||||
) {
|
||||
const [listIds, pinnedIds] = prepareFolderListIds(
|
||||
allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions,
|
||||
);
|
||||
const { pinnedChats, otherChats } = prepareChatList(chatsById, listIds, pinnedIds, 'folder', true);
|
||||
return pinnedChats.length + otherChats.length;
|
||||
}
|
||||
|
||||
export function getMessageSenderName(lang: LangFn, chatId: string, sender?: ApiUser) {
|
||||
if (!sender || isUserId(chatId)) {
|
||||
return undefined;
|
||||
|
||||
@ -2,14 +2,12 @@ import { ApiChat, MAIN_THREAD_ID } from '../../api/types';
|
||||
import { GlobalState } from '../../global/types';
|
||||
|
||||
import {
|
||||
getPrivateChatUserId, isChatChannel, isUserId, isHistoryClearMessage, isUserBot, isUserOnline, selectIsChatMuted,
|
||||
getPrivateChatUserId, isChatChannel, isUserId, isHistoryClearMessage, isUserBot, isUserOnline,
|
||||
} from '../helpers';
|
||||
import { selectUser } from './users';
|
||||
import {
|
||||
ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SERVICE_NOTIFICATIONS_USER_ID,
|
||||
} from '../../config';
|
||||
import { selectNotifyExceptions, selectNotifySettings } from './settings';
|
||||
import memoized from '../../util/memoized';
|
||||
|
||||
export function selectChat(global: GlobalState, chatId: string): ApiChat | undefined {
|
||||
return global.chats.byId[chatId];
|
||||
@ -154,40 +152,6 @@ export function selectChatByUsername(global: GlobalState, username: string) {
|
||||
);
|
||||
}
|
||||
|
||||
const selectCountNotMutedUnreadMemo = memoized((
|
||||
activeChatIds: GlobalState['chats']['listIds']['active'],
|
||||
chatsById: GlobalState['chats']['byId'],
|
||||
notifySettings: GlobalState['settings']['byKey'],
|
||||
notifyExceptions: GlobalState['settings']['notifyExceptions'],
|
||||
) => {
|
||||
return activeChatIds?.reduce((acc, chatId) => {
|
||||
const chat = chatsById[chatId];
|
||||
|
||||
if (
|
||||
chat
|
||||
&& chat.unreadCount
|
||||
&& chat.isListed
|
||||
&& !chat.isNotJoined
|
||||
&& !chat.isRestricted
|
||||
&& (chat.unreadMentionsCount || !selectIsChatMuted(chat, notifySettings, notifyExceptions))
|
||||
) {
|
||||
return acc + chat.unreadCount;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, 0) || 0;
|
||||
});
|
||||
|
||||
// Still slow but at least memoized
|
||||
export function selectCountNotMutedUnreadOptimized(global: GlobalState) {
|
||||
return selectCountNotMutedUnreadMemo(
|
||||
global.chats.listIds.active,
|
||||
global.chats.byId,
|
||||
selectNotifySettings(global),
|
||||
selectNotifyExceptions(global),
|
||||
);
|
||||
}
|
||||
|
||||
export function selectIsServiceChatReady(global: GlobalState) {
|
||||
return Boolean(selectChat(global, SERVICE_NOTIFICATIONS_USER_ID));
|
||||
}
|
||||
|
||||
@ -22,9 +22,16 @@ export function createCallbackManager() {
|
||||
});
|
||||
}
|
||||
|
||||
function hasCallbacks() {
|
||||
return Boolean(callbacks.length);
|
||||
}
|
||||
|
||||
return {
|
||||
runCallbacks,
|
||||
addCallback,
|
||||
removeCallback,
|
||||
hasCallbacks,
|
||||
};
|
||||
}
|
||||
|
||||
export type CallbackManager = ReturnType<typeof createCallbackManager>;
|
||||
|
||||
626
src/util/folderManager.ts
Normal file
626
src/util/folderManager.ts
Normal file
@ -0,0 +1,626 @@
|
||||
import { addCallback, getGlobal } from '../lib/teact/teactn';
|
||||
|
||||
import { GlobalState } from '../global/types';
|
||||
import { NotifyException, NotifySettings } from '../types';
|
||||
import { ApiChat, ApiChatFolder, ApiUser } from '../api/types';
|
||||
|
||||
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, DEBUG } from '../config';
|
||||
import { selectNotifySettings, selectNotifyExceptions } from '../modules/selectors';
|
||||
import { selectIsChatMuted } from '../modules/helpers';
|
||||
import { onIdle, throttle } from './schedulers';
|
||||
import { areSortedArraysEqual, unique } from './iteratees';
|
||||
import arePropsShallowEqual from './arePropsShallowEqual';
|
||||
import { CallbackManager, createCallbackManager } from './callbacks';
|
||||
|
||||
interface FolderSummary {
|
||||
id: number;
|
||||
listIds?: Set<string>;
|
||||
orderedPinnedIds?: string[];
|
||||
contacts?: true;
|
||||
nonContacts?: true;
|
||||
groups?: true;
|
||||
channels?: true;
|
||||
bots?: true;
|
||||
excludeMuted?: true;
|
||||
excludeRead?: true;
|
||||
excludeArchived?: true;
|
||||
excludedChatIds?: Set<string>;
|
||||
includedChatIds?: Set<string>;
|
||||
pinnedChatIds?: Set<string>;
|
||||
}
|
||||
|
||||
interface ChatSummary {
|
||||
id: string;
|
||||
type: ApiChat['type'];
|
||||
isListed: boolean;
|
||||
isArchived: boolean;
|
||||
isMuted: boolean;
|
||||
isUnread: boolean;
|
||||
unreadCount?: number;
|
||||
unreadMentionsCount?: number;
|
||||
order: number;
|
||||
isUserBot?: boolean;
|
||||
isUserContact?: boolean;
|
||||
}
|
||||
|
||||
const UPDATE_THROTTLE = 500;
|
||||
const DEBUG_DURATION_LIMIT = 6;
|
||||
|
||||
const prevGlobal: {
|
||||
allFolderListIds?: GlobalState['chats']['listIds']['active'];
|
||||
allFolderPinnedIds?: GlobalState['chats']['orderedPinnedIds']['active'];
|
||||
archivedFolderListIds?: GlobalState['chats']['listIds']['archived'];
|
||||
archivedFolderPinnedIds?: GlobalState['chats']['orderedPinnedIds']['archived'];
|
||||
chatsById: Record<string, ApiChat>;
|
||||
foldersById: Record<string, ApiChatFolder>;
|
||||
usersById: Record<string, ApiUser>;
|
||||
notifySettings: NotifySettings;
|
||||
notifyExceptions?: Record<number, NotifyException>;
|
||||
} = {
|
||||
foldersById: {},
|
||||
chatsById: {},
|
||||
usersById: {},
|
||||
notifySettings: {} as NotifySettings,
|
||||
notifyExceptions: {},
|
||||
};
|
||||
|
||||
const prepared: {
|
||||
folderSummariesById: Record<string, FolderSummary>;
|
||||
chatSummariesById: Map<string, ChatSummary>;
|
||||
folderIdsByChatId: Record<string, number[]>;
|
||||
chatIdsByFolderId: Record<number, Set<string>>;
|
||||
isOrderedListJustPatched: Record<number, boolean | undefined>;
|
||||
} = {
|
||||
folderSummariesById: {},
|
||||
chatSummariesById: new Map(),
|
||||
folderIdsByChatId: {},
|
||||
chatIdsByFolderId: {},
|
||||
isOrderedListJustPatched: {},
|
||||
};
|
||||
|
||||
const results: {
|
||||
orderedIdsByFolderId: Record<string, string[]>;
|
||||
chatsCountByFolderId: Record<string, number>;
|
||||
unreadCountersByFolderId: Record<string, {
|
||||
chatsCount: number;
|
||||
notificationsCount: number;
|
||||
}>;
|
||||
} = {
|
||||
orderedIdsByFolderId: {},
|
||||
chatsCountByFolderId: {},
|
||||
unreadCountersByFolderId: {},
|
||||
};
|
||||
|
||||
const callbacks: {
|
||||
orderedIdsByFolderId: Record<number, CallbackManager>;
|
||||
chatsCountByFolderId: CallbackManager;
|
||||
unreadCountersByFolderId: CallbackManager;
|
||||
} = {
|
||||
orderedIdsByFolderId: {},
|
||||
chatsCountByFolderId: createCallbackManager(),
|
||||
unreadCountersByFolderId: createCallbackManager(),
|
||||
};
|
||||
|
||||
const updateFolderManagerThrottled = throttle(() => {
|
||||
onIdle(() => {
|
||||
updateFolderManager(getGlobal());
|
||||
});
|
||||
}, UPDATE_THROTTLE);
|
||||
|
||||
let inited = false;
|
||||
|
||||
function init() {
|
||||
addCallback(updateFolderManagerThrottled);
|
||||
updateFolderManager(getGlobal());
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
|
||||
export function getOrderedIds(folderId: number) {
|
||||
if (!inited) init();
|
||||
|
||||
return results.orderedIdsByFolderId[folderId];
|
||||
}
|
||||
|
||||
export function getChatsCount() {
|
||||
if (!inited) init();
|
||||
|
||||
return results.chatsCountByFolderId;
|
||||
}
|
||||
|
||||
export function getUnreadCounters() {
|
||||
if (!inited) init();
|
||||
|
||||
return results.unreadCountersByFolderId;
|
||||
}
|
||||
|
||||
export function getAllNotificationsCount() {
|
||||
return getUnreadCounters()[ALL_FOLDER_ID]?.notificationsCount || 0;
|
||||
}
|
||||
|
||||
export function getPinnedChatsCount(folderId: number) {
|
||||
return prepared.folderSummariesById[folderId]?.pinnedChatIds?.size;
|
||||
}
|
||||
|
||||
/* Callback managers */
|
||||
|
||||
export function addOrderedIdsCallback(folderId: number, callback: (orderedIds: string[]) => void) {
|
||||
if (!callbacks.orderedIdsByFolderId[folderId]) {
|
||||
callbacks.orderedIdsByFolderId[folderId] = createCallbackManager();
|
||||
}
|
||||
|
||||
return callbacks.orderedIdsByFolderId[folderId].addCallback(callback);
|
||||
}
|
||||
|
||||
export function addChatsCountCallback(callback: (chatsCount: typeof results.chatsCountByFolderId) => void) {
|
||||
return callbacks.chatsCountByFolderId.addCallback(callback);
|
||||
}
|
||||
|
||||
export function addUnreadCountersCallback(callback: (unreadCounters: typeof results.unreadCountersByFolderId) => void) {
|
||||
return callbacks.unreadCountersByFolderId.addCallback(callback);
|
||||
}
|
||||
|
||||
/* Global update handlers */
|
||||
|
||||
function updateFolderManager(global: GlobalState) {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
let DEBUG_startedAt: number;
|
||||
if (DEBUG) {
|
||||
DEBUG_startedAt = performance.now();
|
||||
}
|
||||
|
||||
const isAllFolderChanged = Boolean(
|
||||
global.chats.listIds.active
|
||||
&& isMainFolderChanged(ALL_FOLDER_ID, global.chats.listIds.active, global.chats.orderedPinnedIds.active),
|
||||
);
|
||||
const isArchivedFolderChanged = Boolean(
|
||||
global.chats.listIds.archived
|
||||
&& isMainFolderChanged(ARCHIVED_FOLDER_ID, global.chats.listIds.archived, global.chats.orderedPinnedIds.archived),
|
||||
);
|
||||
|
||||
const areFoldersChanged = global.chatFolders.byId !== prevGlobal.foldersById;
|
||||
const areChatsChanged = global.chats.byId !== prevGlobal.chatsById;
|
||||
const areUsersChanged = global.users.byId !== prevGlobal.usersById;
|
||||
const areNotifySettingsChanged = selectNotifySettings(global) !== prevGlobal.notifySettings;
|
||||
const areNotifyExceptionsChanged = selectNotifyExceptions(global) !== prevGlobal.notifyExceptions;
|
||||
|
||||
if (!(
|
||||
isAllFolderChanged || isArchivedFolderChanged || areFoldersChanged
|
||||
|| areChatsChanged || areUsersChanged || areNotifySettingsChanged || areNotifyExceptionsChanged
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevAllFolderListIds = prevGlobal.allFolderListIds;
|
||||
const prevArchivedFolderListIds = prevGlobal.archivedFolderListIds;
|
||||
|
||||
updateFolders(global, isAllFolderChanged, isArchivedFolderChanged, areFoldersChanged);
|
||||
|
||||
const affectedFolderIds = updateChats(
|
||||
global, areFoldersChanged, areNotifySettingsChanged, areNotifyExceptionsChanged,
|
||||
prevAllFolderListIds, prevArchivedFolderListIds,
|
||||
);
|
||||
|
||||
updateResults(affectedFolderIds);
|
||||
|
||||
if (DEBUG) {
|
||||
const duration = performance.now() - DEBUG_startedAt!;
|
||||
if (duration > DEBUG_DURATION_LIMIT) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Slow \`updateFolderManager\`: ${Math.round(duration)} ms`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isMainFolderChanged(folderId: number, newListIds?: string[], newPinnedIds?: string[]) {
|
||||
const currentListIds = folderId === ALL_FOLDER_ID
|
||||
? prevGlobal.allFolderListIds
|
||||
: prevGlobal.archivedFolderListIds;
|
||||
const currentPinnedIds = folderId === ALL_FOLDER_ID
|
||||
? prevGlobal.allFolderPinnedIds
|
||||
: prevGlobal.archivedFolderPinnedIds;
|
||||
|
||||
return currentListIds !== newListIds || currentPinnedIds !== newPinnedIds;
|
||||
}
|
||||
|
||||
function updateFolders(
|
||||
global: GlobalState, isAllFolderChanged: boolean, isArchivedFolderChanged: boolean, areFoldersChanged: boolean,
|
||||
) {
|
||||
const changedFolders = [];
|
||||
|
||||
if (isAllFolderChanged) {
|
||||
const newListIds = global.chats.listIds.active!;
|
||||
const newPinnedIds = global.chats.orderedPinnedIds.active;
|
||||
|
||||
prepared.folderSummariesById[ALL_FOLDER_ID] = buildFolderSummaryFromMainList(
|
||||
ALL_FOLDER_ID, newListIds, newPinnedIds,
|
||||
);
|
||||
|
||||
prevGlobal.allFolderListIds = newListIds;
|
||||
prevGlobal.allFolderPinnedIds = newPinnedIds;
|
||||
|
||||
changedFolders.push(ALL_FOLDER_ID);
|
||||
}
|
||||
|
||||
if (isArchivedFolderChanged) {
|
||||
const newListIds = global.chats.listIds.archived!;
|
||||
const newPinnedIds = global.chats.orderedPinnedIds.archived;
|
||||
|
||||
prepared.folderSummariesById[ARCHIVED_FOLDER_ID] = buildFolderSummaryFromMainList(
|
||||
ARCHIVED_FOLDER_ID, newListIds, newPinnedIds,
|
||||
);
|
||||
|
||||
prevGlobal.archivedFolderListIds = newListIds;
|
||||
prevGlobal.archivedFolderPinnedIds = newPinnedIds;
|
||||
|
||||
changedFolders.push(ARCHIVED_FOLDER_ID);
|
||||
}
|
||||
|
||||
if (areFoldersChanged) {
|
||||
const newFoldersById = global.chatFolders.byId;
|
||||
|
||||
Object.values(newFoldersById).forEach((folder) => {
|
||||
if (folder === prevGlobal.foldersById[folder.id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
prepared.folderSummariesById[folder.id] = buildFolderSummary(folder);
|
||||
|
||||
changedFolders.push(folder.id);
|
||||
});
|
||||
|
||||
prevGlobal.foldersById = newFoldersById;
|
||||
}
|
||||
|
||||
return changedFolders;
|
||||
}
|
||||
|
||||
function buildFolderSummaryFromMainList(
|
||||
folderId: number, listIds: string[], orderedPinnedIds?: string[],
|
||||
): FolderSummary {
|
||||
return {
|
||||
id: folderId,
|
||||
listIds: new Set(listIds),
|
||||
orderedPinnedIds: orderedPinnedIds,
|
||||
pinnedChatIds: new Set(orderedPinnedIds),
|
||||
};
|
||||
}
|
||||
|
||||
function buildFolderSummary(folder: ApiChatFolder): FolderSummary {
|
||||
return {
|
||||
...folder,
|
||||
orderedPinnedIds: folder.pinnedChatIds,
|
||||
excludedChatIds: folder.excludedChatIds ? new Set(folder.excludedChatIds) : undefined,
|
||||
includedChatIds: folder.excludedChatIds ? new Set(folder.includedChatIds) : undefined,
|
||||
pinnedChatIds: folder.excludedChatIds ? new Set(folder.pinnedChatIds) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function updateChats(
|
||||
global: GlobalState,
|
||||
areFoldersChanged: boolean,
|
||||
areNotifySettingsChanged: boolean,
|
||||
areNotifyExceptionsChanged: boolean,
|
||||
prevAllFolderListIds?: string[],
|
||||
prevArchivedFolderListIds?: string[],
|
||||
) {
|
||||
const newChatsById = global.chats.byId;
|
||||
const newUsersById = global.users.byId;
|
||||
const newNotifySettings = selectNotifySettings(global);
|
||||
const newNotifyExceptions = selectNotifyExceptions(global);
|
||||
const folderSummaries = Object.values(prepared.folderSummariesById);
|
||||
const affectedFolderIds = new Set<number>();
|
||||
|
||||
const newAllFolderListIds = global.chats.listIds.active;
|
||||
const newArchivedFolderListIds = global.chats.listIds.archived;
|
||||
let allIds = [...newAllFolderListIds || [], ...newArchivedFolderListIds || []];
|
||||
if (newAllFolderListIds !== prevAllFolderListIds || newArchivedFolderListIds !== prevArchivedFolderListIds) {
|
||||
allIds = unique(allIds.concat(prevAllFolderListIds || [], prevArchivedFolderListIds || []));
|
||||
}
|
||||
|
||||
allIds.forEach((chatId) => {
|
||||
const chat = newChatsById[chatId];
|
||||
|
||||
if (
|
||||
!areFoldersChanged
|
||||
&& !areNotifySettingsChanged
|
||||
&& !areNotifyExceptionsChanged
|
||||
&& chat === prevGlobal.chatsById[chatId]
|
||||
&& newUsersById[chatId] === prevGlobal.usersById[chatId]
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newFolderIds: number[];
|
||||
if (chat) {
|
||||
const currentSummary = prepared.chatSummariesById.get(chatId);
|
||||
const newSummary = buildChatSummary(chat, newNotifySettings, newNotifyExceptions, newUsersById[chatId]);
|
||||
if (!areFoldersChanged && currentSummary && arePropsShallowEqual(newSummary, currentSummary)) {
|
||||
return;
|
||||
}
|
||||
|
||||
prepared.chatSummariesById.set(chatId, newSummary);
|
||||
|
||||
newFolderIds = buildChatFolderIds(newSummary, folderSummaries);
|
||||
newFolderIds.forEach((folderId) => {
|
||||
affectedFolderIds.add(folderId);
|
||||
});
|
||||
} else {
|
||||
prepared.chatSummariesById.delete(chatId);
|
||||
newFolderIds = [];
|
||||
}
|
||||
|
||||
const currentFolderIds = prepared.folderIdsByChatId[chatId] || [];
|
||||
if (areSortedArraysEqual(newFolderIds, currentFolderIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deletedFolderIds = updateListsForChat(chatId, currentFolderIds, newFolderIds);
|
||||
deletedFolderIds.forEach((folderId) => {
|
||||
affectedFolderIds.add(folderId);
|
||||
});
|
||||
});
|
||||
|
||||
prevGlobal.chatsById = newChatsById;
|
||||
prevGlobal.usersById = newUsersById;
|
||||
prevGlobal.notifySettings = newNotifySettings;
|
||||
prevGlobal.notifyExceptions = newNotifyExceptions;
|
||||
|
||||
return Array.from(affectedFolderIds);
|
||||
}
|
||||
|
||||
function buildChatSummary(
|
||||
chat: ApiChat,
|
||||
notifySettings: NotifySettings,
|
||||
notifyExceptions?: Record<number, NotifyException>,
|
||||
user?: ApiUser,
|
||||
): ChatSummary {
|
||||
const {
|
||||
id, type, lastMessage, isRestricted, isNotJoined, folderId,
|
||||
unreadCount, unreadMentionsCount, hasUnreadMark,
|
||||
joinDate, draftDate,
|
||||
} = chat;
|
||||
|
||||
const userInfo = type === 'chatTypePrivate' && user;
|
||||
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
isListed: Boolean(lastMessage && !isRestricted && !isNotJoined),
|
||||
isArchived: folderId === ARCHIVED_FOLDER_ID,
|
||||
isMuted: selectIsChatMuted(chat, notifySettings, notifyExceptions),
|
||||
isUnread: Boolean(unreadCount || unreadMentionsCount || hasUnreadMark),
|
||||
unreadCount,
|
||||
unreadMentionsCount,
|
||||
order: Math.max(joinDate || 0, draftDate || 0, lastMessage?.date || 0),
|
||||
isUserBot: userInfo ? userInfo.type === 'userTypeBot' : undefined,
|
||||
isUserContact: userInfo ? userInfo.isContact : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function buildChatFolderIds(chatSummary: ChatSummary, folderSummaries: FolderSummary[]) {
|
||||
return folderSummaries.reduce<number[]>((acc, folderSummary) => {
|
||||
if (isChatInFolder(chatSummary, folderSummary)) {
|
||||
acc.push(folderSummary.id);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []).sort();
|
||||
}
|
||||
|
||||
function isChatInFolder(
|
||||
chatSummary: ChatSummary,
|
||||
folderSummary: FolderSummary,
|
||||
) {
|
||||
if (!chatSummary.isListed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { id: chatId, type } = chatSummary;
|
||||
|
||||
if (folderSummary.listIds) {
|
||||
if (
|
||||
(chatSummary.isArchived && folderSummary.id === ALL_FOLDER_ID)
|
||||
|| (!chatSummary.isArchived && folderSummary.id === ARCHIVED_FOLDER_ID)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return folderSummary.listIds.has(chatId);
|
||||
}
|
||||
|
||||
if (folderSummary.excludedChatIds?.has(chatId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (folderSummary.includedChatIds?.has(chatId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (folderSummary.pinnedChatIds?.has(chatId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (folderSummary.excludeArchived && chatSummary.isArchived) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (folderSummary.excludeRead && !chatSummary.isUnread) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (folderSummary.excludeMuted && chatSummary.isMuted && !chatSummary.unreadMentionsCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type === 'chatTypePrivate') {
|
||||
if (chatSummary.isUserBot) {
|
||||
if (folderSummary.bots) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (folderSummary.contacts && chatSummary.isUserContact) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (folderSummary.nonContacts && !chatSummary.isUserContact) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (type === 'chatTypeChannel') {
|
||||
return Boolean(folderSummary.channels);
|
||||
} else if (type === 'chatTypeBasicGroup' || type === 'chatTypeSuperGroup') {
|
||||
return Boolean(folderSummary.groups);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateListsForChat(chatId: string, currentFolderIds: number[], newFolderIds: number[]) {
|
||||
const currentFolderIdsSet = new Set(currentFolderIds);
|
||||
const newFolderIdsSet = new Set(newFolderIds);
|
||||
const deletedFolderIds: number[] = [];
|
||||
|
||||
unique([...currentFolderIds, ...newFolderIds]).forEach((folderId) => {
|
||||
let currentFolderOrderedIds = results.orderedIdsByFolderId[folderId];
|
||||
|
||||
if (currentFolderIdsSet.has(folderId) && !newFolderIdsSet.has(folderId)) {
|
||||
prepared.chatIdsByFolderId[folderId].delete(chatId);
|
||||
|
||||
deletedFolderIds.push(folderId);
|
||||
|
||||
if (currentFolderOrderedIds) {
|
||||
currentFolderOrderedIds = currentFolderOrderedIds.filter((id) => id !== chatId);
|
||||
prepared.isOrderedListJustPatched[folderId] = true;
|
||||
}
|
||||
} else if (!currentFolderIdsSet.has(folderId) && newFolderIdsSet.has(folderId)) {
|
||||
if (!prepared.chatIdsByFolderId[folderId]) {
|
||||
prepared.chatIdsByFolderId[folderId] = new Set();
|
||||
}
|
||||
|
||||
prepared.chatIdsByFolderId[folderId].add(chatId);
|
||||
|
||||
if (currentFolderOrderedIds) {
|
||||
currentFolderOrderedIds.push(chatId);
|
||||
prepared.isOrderedListJustPatched[folderId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
results.orderedIdsByFolderId[folderId] = currentFolderOrderedIds;
|
||||
});
|
||||
|
||||
prepared.folderIdsByChatId[chatId] = newFolderIds;
|
||||
|
||||
return deletedFolderIds;
|
||||
}
|
||||
|
||||
function updateResults(affectedFolderIds: number[]) {
|
||||
let wasUnreadCountersChanged = false;
|
||||
let wasChatsCountChanged = false;
|
||||
|
||||
Array.from(affectedFolderIds).forEach((folderId) => {
|
||||
const newOrderedIds = buildFolderOrderedIds(folderId);
|
||||
|
||||
const currentOrderedIds = results.orderedIdsByFolderId[folderId];
|
||||
const areOrderedIdsChanged = (
|
||||
!currentOrderedIds
|
||||
|| prepared.isOrderedListJustPatched[folderId]
|
||||
|| !areSortedArraysEqual(newOrderedIds, currentOrderedIds)
|
||||
);
|
||||
if (areOrderedIdsChanged) {
|
||||
prepared.isOrderedListJustPatched[folderId] = false;
|
||||
results.orderedIdsByFolderId[folderId] = newOrderedIds;
|
||||
callbacks.orderedIdsByFolderId[folderId]?.runCallbacks(newOrderedIds);
|
||||
}
|
||||
|
||||
const currentChatsCount = results.chatsCountByFolderId[folderId];
|
||||
const newChatsCount = newOrderedIds.length;
|
||||
if (!wasChatsCountChanged) {
|
||||
wasChatsCountChanged = currentChatsCount !== newChatsCount;
|
||||
}
|
||||
results.chatsCountByFolderId[folderId] = newChatsCount;
|
||||
|
||||
const currentUnreadCounters = results.unreadCountersByFolderId[folderId];
|
||||
const newUnreadCounters = buildFolderUnreadCounters(folderId);
|
||||
if (!wasUnreadCountersChanged) {
|
||||
wasUnreadCountersChanged = (
|
||||
!currentUnreadCounters || !arePropsShallowEqual(newUnreadCounters, currentUnreadCounters)
|
||||
);
|
||||
}
|
||||
results.unreadCountersByFolderId[folderId] = newUnreadCounters;
|
||||
});
|
||||
|
||||
if (wasChatsCountChanged) {
|
||||
// We need to update the entire object as it will be returned from a hook
|
||||
const newValue = { ...results.chatsCountByFolderId };
|
||||
results.chatsCountByFolderId = newValue;
|
||||
callbacks.chatsCountByFolderId.runCallbacks(newValue);
|
||||
}
|
||||
|
||||
if (wasUnreadCountersChanged) {
|
||||
// We need to update the entire object as it will be returned from a hook
|
||||
const newValue = { ...results.unreadCountersByFolderId };
|
||||
results.unreadCountersByFolderId = newValue;
|
||||
callbacks.unreadCountersByFolderId.runCallbacks(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
function buildFolderOrderedIds(folderId: number) {
|
||||
const {
|
||||
folderSummariesById: { [folderId]: { orderedPinnedIds, pinnedChatIds } },
|
||||
chatSummariesById,
|
||||
chatIdsByFolderId: { [folderId]: chatIds },
|
||||
} = prepared;
|
||||
const {
|
||||
orderedIdsByFolderId: { [folderId]: prevOrderedIds },
|
||||
} = results;
|
||||
|
||||
const allListIds = prevOrderedIds || Array.from(chatIds);
|
||||
const notPinnedIds = pinnedChatIds ? allListIds.filter((id) => !pinnedChatIds.has(id)) : allListIds;
|
||||
const sortedNotPinnedIds = notPinnedIds.sort((chatId1: string, chatId2: string) => {
|
||||
return chatSummariesById.get(chatId2)!.order - chatSummariesById.get(chatId1)!.order;
|
||||
});
|
||||
|
||||
return [
|
||||
...(orderedPinnedIds || []),
|
||||
...sortedNotPinnedIds,
|
||||
];
|
||||
}
|
||||
|
||||
function buildFolderUnreadCounters(folderId: number) {
|
||||
const {
|
||||
chatSummariesById,
|
||||
} = prepared;
|
||||
const {
|
||||
orderedIdsByFolderId: { [folderId]: orderedIds },
|
||||
} = results;
|
||||
|
||||
return orderedIds.reduce((newUnreadCounters, chatId) => {
|
||||
const chatSummary = chatSummariesById.get(chatId);
|
||||
if (!chatSummary) {
|
||||
return newUnreadCounters;
|
||||
}
|
||||
|
||||
if (chatSummary.isUnread) {
|
||||
newUnreadCounters.chatsCount++;
|
||||
|
||||
if (chatSummary.unreadMentionsCount) {
|
||||
newUnreadCounters.notificationsCount += chatSummary.unreadMentionsCount;
|
||||
}
|
||||
|
||||
if (!chatSummary.isMuted) {
|
||||
if (chatSummary.unreadCount) {
|
||||
newUnreadCounters.notificationsCount += chatSummary.unreadCount;
|
||||
} else if (!chatSummary.unreadMentionsCount) {
|
||||
newUnreadCounters.notificationsCount += 1; // Manually marked unread
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newUnreadCounters;
|
||||
}, {
|
||||
chatsCount: 0,
|
||||
notificationsCount: 0,
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user