[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';
|
} from '../../../lib/teact/teact';
|
||||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiChat, ApiChatFolder, ApiUser } from '../../../api/types';
|
import { ApiChatFolder } from '../../../api/types';
|
||||||
import { GlobalState } from '../../../global/types';
|
import { SettingsScreens } from '../../../types';
|
||||||
import { NotifyException, NotifySettings, SettingsScreens } from '../../../types';
|
|
||||||
import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
|
import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
|
||||||
|
|
||||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
|
||||||
import { ALL_FOLDER_ID } from '../../../config';
|
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 { 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 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 useLang from '../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
|
||||||
|
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
import TabList from '../../ui/TabList';
|
import TabList from '../../ui/TabList';
|
||||||
import ChatList from './ChatList';
|
import ChatList from './ChatList';
|
||||||
|
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onScreenSelect: (screen: SettingsScreens) => void;
|
onScreenSelect: (screen: SettingsScreens) => void;
|
||||||
@ -31,12 +27,7 @@ type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
allListIds: GlobalState['chats']['listIds'];
|
|
||||||
chatsById: Record<string, ApiChat>;
|
|
||||||
usersById: Record<string, ApiUser>;
|
|
||||||
chatFoldersById: Record<number, ApiChatFolder>;
|
chatFoldersById: Record<number, ApiChatFolder>;
|
||||||
notifySettings: NotifySettings;
|
|
||||||
notifyExceptions?: Record<number, NotifyException>;
|
|
||||||
orderedFolderIds?: number[];
|
orderedFolderIds?: number[];
|
||||||
activeChatFolder: number;
|
activeChatFolder: number;
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
@ -44,23 +35,17 @@ type StateProps = {
|
|||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const INFO_THROTTLE = 3000;
|
|
||||||
const SAVED_MESSAGES_HOTKEY = '0';
|
const SAVED_MESSAGES_HOTKEY = '0';
|
||||||
|
|
||||||
const ChatFolders: FC<OwnProps & StateProps> = ({
|
const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||||
allListIds,
|
foldersDispatch,
|
||||||
chatsById,
|
onScreenSelect,
|
||||||
usersById,
|
|
||||||
chatFoldersById,
|
chatFoldersById,
|
||||||
notifySettings,
|
|
||||||
notifyExceptions,
|
|
||||||
orderedFolderIds,
|
orderedFolderIds,
|
||||||
activeChatFolder,
|
activeChatFolder,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
foldersDispatch,
|
|
||||||
onScreenSelect,
|
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadChatFolders,
|
loadChatFolders,
|
||||||
@ -85,36 +70,22 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
: undefined;
|
: undefined;
|
||||||
}, [chatFoldersById, orderedFolderIds]);
|
}, [chatFoldersById, orderedFolderIds]);
|
||||||
|
|
||||||
const folderCountersById = useThrottledMemo(() => {
|
const folderCountersById = useFolderManagerForUnreadCounters();
|
||||||
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 folderTabs = useMemo(() => {
|
const folderTabs = useMemo(() => {
|
||||||
if (!displayedFolders || !displayedFolders.length) {
|
if (!displayedFolders || !displayedFolders.length) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ title: lang.code === 'en' ? 'All' : lang('FilterAllChats'), id: ALL_FOLDER_ID },
|
{
|
||||||
...displayedFolders.map((folder) => ({
|
id: ALL_FOLDER_ID,
|
||||||
title: folder.title,
|
title: lang.code === 'en' ? 'All' : lang('FilterAllChats'),
|
||||||
...(folderCountersById?.[folder.id]),
|
},
|
||||||
|
...displayedFolders.map(({ id, title }) => ({
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
badgeCount: folderCountersById[id]?.chatsCount,
|
||||||
|
isBadgeActive: Boolean(folderCountersById[id]?.notificationsCount),
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
}, [displayedFolders, folderCountersById, lang]);
|
}, [displayedFolders, folderCountersById, lang]);
|
||||||
@ -204,6 +175,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
<ChatList
|
<ChatList
|
||||||
folderType="all"
|
folderType="all"
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
|
lastSyncTime={lastSyncTime}
|
||||||
foldersDispatch={foldersDispatch}
|
foldersDispatch={foldersDispatch}
|
||||||
onScreenSelect={onScreenSelect}
|
onScreenSelect={onScreenSelect}
|
||||||
/>
|
/>
|
||||||
@ -215,6 +187,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
folderType="folder"
|
folderType="folder"
|
||||||
folderId={activeFolder.id}
|
folderId={activeFolder.id}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
|
lastSyncTime={lastSyncTime}
|
||||||
onScreenSelect={onScreenSelect}
|
onScreenSelect={onScreenSelect}
|
||||||
foldersDispatch={foldersDispatch}
|
foldersDispatch={foldersDispatch}
|
||||||
/>
|
/>
|
||||||
@ -243,8 +216,6 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const {
|
const {
|
||||||
chats: { listIds: allListIds, byId: chatsById },
|
|
||||||
users: { byId: usersById },
|
|
||||||
chatFolders: {
|
chatFolders: {
|
||||||
byId: chatFoldersById,
|
byId: chatFoldersById,
|
||||||
orderedIds: orderedFolderIds,
|
orderedIds: orderedFolderIds,
|
||||||
@ -256,16 +227,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
} = global;
|
} = global;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allListIds,
|
|
||||||
chatsById,
|
|
||||||
usersById,
|
|
||||||
chatFoldersById,
|
chatFoldersById,
|
||||||
orderedFolderIds,
|
orderedFolderIds,
|
||||||
lastSyncTime,
|
|
||||||
notifySettings: selectNotifySettings(global),
|
|
||||||
notifyExceptions: selectNotifyExceptions(global),
|
|
||||||
activeChatFolder,
|
activeChatFolder,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
lastSyncTime,
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,26 +1,24 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useMemo, useCallback, useEffect,
|
FC, memo, useMemo, useCallback, useEffect,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
import { getDispatch } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalState } from '../../../global/types';
|
import { SettingsScreens } from '../../../types';
|
||||||
import {
|
|
||||||
ApiChat, ApiChatFolder, ApiUser,
|
|
||||||
} from '../../../api/types';
|
|
||||||
import { NotifyException, NotifySettings, SettingsScreens } from '../../../types';
|
|
||||||
import { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
|
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 { IS_ANDROID, IS_MAC_OS, IS_PWA } from '../../../util/environment';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
|
||||||
import { mapValues } from '../../../util/iteratees';
|
import { mapValues } from '../../../util/iteratees';
|
||||||
import {
|
import { getPinnedChatsCount } from '../../../util/folderManager';
|
||||||
getChatOrder, prepareChatList, prepareFolderListIds, reduceChatList,
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
} from '../../../modules/helpers';
|
|
||||||
import {
|
|
||||||
selectChatFolder, selectNotifyExceptions, selectNotifySettings,
|
|
||||||
} from '../../../modules/selectors';
|
|
||||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
|
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||||
import { useChatAnimationType } from './hooks';
|
import { useChatAnimationType } from './hooks';
|
||||||
|
|
||||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
@ -32,40 +30,16 @@ type OwnProps = {
|
|||||||
folderType: 'all' | 'archived' | 'folder';
|
folderType: 'all' | 'archived' | 'folder';
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
isActive: boolean;
|
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;
|
lastSyncTime?: number;
|
||||||
notifySettings: NotifySettings;
|
foldersDispatch?: FolderEditDispatch;
|
||||||
notifyExceptions?: Record<number, NotifyException>;
|
onScreenSelect?: (screen: SettingsScreens) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum FolderTypeToListType {
|
const ChatList: FC<OwnProps> = ({
|
||||||
'all' = 'active',
|
|
||||||
'archived' = 'archived',
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChatList: FC<OwnProps & StateProps> = ({
|
|
||||||
folderType,
|
folderType,
|
||||||
folderId,
|
folderId,
|
||||||
isActive,
|
isActive,
|
||||||
allListIds,
|
|
||||||
chatsById,
|
|
||||||
usersById,
|
|
||||||
listIds,
|
|
||||||
orderedPinnedIds,
|
|
||||||
chatFolder,
|
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
notifySettings,
|
|
||||||
notifyExceptions,
|
|
||||||
foldersDispatch,
|
foldersDispatch,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
}) => {
|
}) => {
|
||||||
@ -77,30 +51,22 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
|||||||
openNextChat,
|
openNextChat,
|
||||||
} = getDispatch();
|
} = getDispatch();
|
||||||
|
|
||||||
const [currentListIds, currentPinnedIds] = useMemo(() => {
|
const virtualFolderId = (
|
||||||
return folderType === 'folder' && chatFolder
|
folderType === 'all' ? ALL_FOLDER_ID : folderType === 'archived' ? ARCHIVED_FOLDER_ID : folderId!
|
||||||
? prepareFolderListIds(allListIds, chatsById, usersById, chatFolder, notifySettings, notifyExceptions)
|
);
|
||||||
: [listIds, orderedPinnedIds];
|
|
||||||
}, [
|
|
||||||
folderType, chatFolder, allListIds, chatsById, usersById,
|
|
||||||
notifySettings, notifyExceptions, listIds, orderedPinnedIds,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [orderById, orderedIds, chatArrays] = useMemo(() => {
|
const orderedIds = useFolderManagerForOrderedIds(virtualFolderId);
|
||||||
if (!currentListIds || (folderType === 'folder' && !chatFolder)) {
|
|
||||||
return [];
|
const orderById = useMemo(() => {
|
||||||
|
if (!orderedIds) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newChatArrays = prepareChatList(chatsById, currentListIds, currentPinnedIds, folderType);
|
return orderedIds.reduce((acc, id, i) => {
|
||||||
const singleList = ([] as ApiChat[]).concat(newChatArrays.pinnedChats, newChatArrays.otherChats);
|
acc[id] = i;
|
||||||
const newOrderedIds = singleList.map(({ id }) => id);
|
|
||||||
const newOrderById = singleList.reduce((acc, chat, i) => {
|
|
||||||
acc[chat.id] = i;
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, number>);
|
}, {} as Record<string, number>);
|
||||||
|
}, [orderedIds]);
|
||||||
return [newOrderById, newOrderedIds, newChatArrays];
|
|
||||||
}, [currentListIds, currentPinnedIds, folderType, chatFolder, chatsById]);
|
|
||||||
|
|
||||||
const prevOrderById = usePrevious(orderById);
|
const prevOrderById = usePrevious(orderById);
|
||||||
|
|
||||||
@ -126,14 +92,6 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
|||||||
folderType === 'all' && !ALL_CHATS_PRELOAD_DISABLED,
|
folderType === 'all' && !ALL_CHATS_PRELOAD_DISABLED,
|
||||||
);
|
);
|
||||||
|
|
||||||
const viewportChatArrays = useMemo(() => {
|
|
||||||
if (!viewportIds || !chatArrays) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return reduceChatList(chatArrays, viewportIds);
|
|
||||||
}, [chatArrays, viewportIds]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime && folderType === 'all') {
|
if (lastSyncTime && folderType === 'all') {
|
||||||
preloadTopChatMessages();
|
preloadTopChatMessages();
|
||||||
@ -178,7 +136,7 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
function renderChats() {
|
function renderChats() {
|
||||||
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
|
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
|
||||||
const pinnedOffset = viewportOffset + viewportChatArrays!.pinnedChats.length;
|
const pinnedCount = getPinnedChatsCount(virtualFolderId) || 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -187,12 +145,12 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
|||||||
style={IS_ANDROID ? `height: ${orderedIds!.length * CHAT_HEIGHT_PX}px` : undefined}
|
style={IS_ANDROID ? `height: ${orderedIds!.length * CHAT_HEIGHT_PX}px` : undefined}
|
||||||
teactFastList
|
teactFastList
|
||||||
>
|
>
|
||||||
{viewportChatArrays!.pinnedChats.map(({ id }, i) => (
|
{viewportIds!.map((id, i) => (
|
||||||
<Chat
|
<Chat
|
||||||
key={id}
|
key={id}
|
||||||
teactOrderKey={i}
|
teactOrderKey={i}
|
||||||
chatId={id}
|
chatId={id}
|
||||||
isPinned
|
isPinned={viewportOffset + i < pinnedCount}
|
||||||
folderId={folderId}
|
folderId={folderId}
|
||||||
animationType={getAnimationType(id)}
|
animationType={getAnimationType(id)}
|
||||||
orderDiff={orderDiffById[id]}
|
orderDiff={orderDiffById[id]}
|
||||||
@ -200,18 +158,6 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
|||||||
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -225,7 +171,7 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
|||||||
noFastList
|
noFastList
|
||||||
noScrollRestore
|
noScrollRestore
|
||||||
>
|
>
|
||||||
{viewportIds?.length && viewportChatArrays ? (
|
{viewportIds?.length ? (
|
||||||
renderChats()
|
renderChats()
|
||||||
) : viewportIds && !viewportIds.length ? (
|
) : viewportIds && !viewportIds.length ? (
|
||||||
(
|
(
|
||||||
@ -243,33 +189,4 @@ const ChatList: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(ChatList);
|
||||||
(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));
|
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useMemo, useState,
|
FC, memo, useCallback, useMemo, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} 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 { GlobalState } from '../../../global/types';
|
||||||
import { ApiChat } from '../../../api/types';
|
|
||||||
import { ApiPrivacySettings, SettingsScreens } from '../../../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 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 useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||||
|
import { getPrivacyKey } from './helper/privacy';
|
||||||
|
|
||||||
import Picker from '../../common/Picker';
|
import Picker from '../../common/Picker';
|
||||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||||
@ -28,27 +27,17 @@ export type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
chatsById: Record<string, ApiChat>;
|
|
||||||
listIds?: string[];
|
|
||||||
orderedPinnedIds?: string[];
|
|
||||||
archivedListIds?: string[];
|
|
||||||
archivedPinnedIds?: string[];
|
|
||||||
settings?: ApiPrivacySettings;
|
settings?: ApiPrivacySettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
||||||
currentUserId,
|
|
||||||
isAllowList,
|
isAllowList,
|
||||||
screen,
|
screen,
|
||||||
settings,
|
|
||||||
chatsById,
|
|
||||||
listIds,
|
|
||||||
orderedPinnedIds,
|
|
||||||
archivedListIds,
|
|
||||||
archivedPinnedIds,
|
|
||||||
isActive,
|
isActive,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
onReset,
|
onReset,
|
||||||
|
currentUserId,
|
||||||
|
settings,
|
||||||
}) => {
|
}) => {
|
||||||
const { setPrivacySettings } = getDispatch();
|
const { setPrivacySettings } = getDispatch();
|
||||||
|
|
||||||
@ -69,46 +58,23 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
const [isSubmitShown, setIsSubmitShown] = useState<boolean>(false);
|
const [isSubmitShown, setIsSubmitShown] = useState<boolean>(false);
|
||||||
const [newSelectedContactIds, setNewSelectedContactIds] = useState<string[]>(selectedContactIds);
|
const [newSelectedContactIds, setNewSelectedContactIds] = useState<string[]>(selectedContactIds);
|
||||||
|
|
||||||
const chats = useMemo(() => {
|
const folderAllOrderedIds = useFolderManagerForOrderedIds(ALL_FOLDER_ID);
|
||||||
const activeChatArrays = listIds
|
const folderArchivedOrderedIds = useFolderManagerForOrderedIds(ARCHIVED_FOLDER_ID);
|
||||||
? 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 displayedIds = useMemo(() => {
|
const displayedIds = useMemo(() => {
|
||||||
if (!chats) {
|
// No need for expensive global updates on chats, so we avoid them
|
||||||
return undefined;
|
const chatsById = getGlobal().chats.byId;
|
||||||
}
|
|
||||||
|
|
||||||
return chats
|
const chatIds = unique([...folderAllOrderedIds, ...folderArchivedOrderedIds])
|
||||||
.filter((chat) => (
|
.filter((chatId) => {
|
||||||
((isUserId(chat.id) && chat.id !== currentUserId) || isChatGroup(chat))
|
const chat = chatsById[chatId];
|
||||||
&& (
|
return chat && ((isUserId(chat.id) && chat.id !== currentUserId) || isChatGroup(chat));
|
||||||
!searchQuery
|
});
|
||||||
|| searchWords(getChatTitle(lang, chat), searchQuery)
|
|
||||||
|| selectedContactIds.includes(chat.id)
|
return unique([
|
||||||
)
|
...selectedContactIds,
|
||||||
))
|
...filterChatsByName(lang, chatIds, chatsById, searchQuery),
|
||||||
.map(({ id }) => id);
|
]);
|
||||||
}, [chats, currentUserId, lang, searchQuery, selectedContactIds]);
|
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedContactIds, lang, searchQuery, currentUserId]);
|
||||||
|
|
||||||
const handleSelectedContactIdsChange = useCallback((value: string[]) => {
|
const handleSelectedContactIdsChange = useCallback((value: string[]) => {
|
||||||
setNewSelectedContactIds(value);
|
setNewSelectedContactIds(value);
|
||||||
@ -175,22 +141,8 @@ function getCurrentPrivacySettings(global: GlobalState, screen: SettingsScreens)
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { screen }): StateProps => {
|
(global, { screen }): StateProps => {
|
||||||
const {
|
|
||||||
chats: {
|
|
||||||
byId: chatsById,
|
|
||||||
listIds,
|
|
||||||
orderedPinnedIds,
|
|
||||||
},
|
|
||||||
currentUserId,
|
|
||||||
} = global;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentUserId,
|
currentUserId: global.currentUserId,
|
||||||
chatsById,
|
|
||||||
listIds: listIds.active,
|
|
||||||
orderedPinnedIds: orderedPinnedIds.active,
|
|
||||||
archivedPinnedIds: orderedPinnedIds.archived,
|
|
||||||
archivedListIds: listIds.archived,
|
|
||||||
settings: getCurrentPrivacySettings(global, screen),
|
settings: getCurrentPrivacySettings(global, screen),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,23 +1,24 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useMemo, useCallback,
|
FC, memo, useMemo, useCallback,
|
||||||
} from '../../../../lib/teact/teact';
|
} 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 { 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 useLang from '../../../../hooks/useLang';
|
||||||
import searchWords from '../../../../util/searchWords';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
import { prepareChatList, getChatTitle } from '../../../../modules/helpers';
|
import { useFolderManagerForOrderedIds } from '../../../../hooks/useFolderManager';
|
||||||
import {
|
import {
|
||||||
FoldersState,
|
FoldersState,
|
||||||
FolderEditDispatch,
|
FolderEditDispatch,
|
||||||
selectChatFilters,
|
selectChatFilters,
|
||||||
} from '../../../../hooks/reducers/useFoldersReducer';
|
} from '../../../../hooks/reducers/useFoldersReducer';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
|
||||||
|
|
||||||
import SettingsFoldersChatsPicker from './SettingsFoldersChatsPicker';
|
import SettingsFoldersChatsPicker from './SettingsFoldersChatsPicker';
|
||||||
|
|
||||||
import Loading from '../../../ui/Loading';
|
import Loading from '../../../ui/Loading';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@ -29,26 +30,13 @@ type OwnProps = {
|
|||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
const SettingsFoldersChatFilters: FC<OwnProps> = ({
|
||||||
chatsById: Record<string, ApiChat>;
|
|
||||||
listIds?: string[];
|
|
||||||
orderedPinnedIds?: string[];
|
|
||||||
archivedListIds?: string[];
|
|
||||||
archivedPinnedIds?: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const SettingsFoldersChatFilters: FC<OwnProps & StateProps> = ({
|
|
||||||
isActive,
|
|
||||||
onScreenSelect,
|
|
||||||
onReset,
|
|
||||||
mode,
|
mode,
|
||||||
state,
|
state,
|
||||||
dispatch,
|
dispatch,
|
||||||
chatsById,
|
isActive,
|
||||||
listIds,
|
onScreenSelect,
|
||||||
orderedPinnedIds,
|
onReset,
|
||||||
archivedListIds,
|
|
||||||
archivedPinnedIds,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { loadMoreChats } = getDispatch();
|
const { loadMoreChats } = getDispatch();
|
||||||
|
|
||||||
@ -56,38 +44,20 @@ const SettingsFoldersChatFilters: FC<OwnProps & StateProps> = ({
|
|||||||
const { selectedChatIds, selectedChatTypes } = selectChatFilters(state, mode, true);
|
const { selectedChatIds, selectedChatTypes } = selectChatFilters(state, mode, true);
|
||||||
|
|
||||||
const lang = useLang();
|
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) {
|
const folderAllOrderedIds = useFolderManagerForOrderedIds(ALL_FOLDER_ID);
|
||||||
return undefined;
|
const folderArchivedOrderedIds = useFolderManagerForOrderedIds(ARCHIVED_FOLDER_ID);
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
...(activeChatArrays?.pinnedChats || []),
|
|
||||||
...(activeChatArrays?.otherChats || []),
|
|
||||||
...(archivedChatArrays?.otherChats || []),
|
|
||||||
];
|
|
||||||
}, [chatsById, listIds, orderedPinnedIds, archivedListIds, archivedPinnedIds]);
|
|
||||||
|
|
||||||
const displayedIds = useMemo(() => {
|
const displayedIds = useMemo(() => {
|
||||||
if (!chats) {
|
// No need for expensive global updates on chats, so we avoid them
|
||||||
return undefined;
|
const chatsById = getGlobal().chats.byId;
|
||||||
}
|
|
||||||
|
|
||||||
return chats
|
const chatIds = [...folderAllOrderedIds, ...folderArchivedOrderedIds];
|
||||||
.filter((chat) => (
|
return unique([
|
||||||
!chatFilter
|
...selectedChatIds,
|
||||||
|| searchWords(getChatTitle(lang, chat), chatFilter)
|
...filterChatsByName(lang, chatIds, chatsById, chatFilter),
|
||||||
|| selectedChatIds.includes(chat.id)
|
]);
|
||||||
))
|
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedChatIds, lang, chatFilter]);
|
||||||
.map(({ id }) => id);
|
|
||||||
}, [chats, chatFilter, lang, selectedChatIds]);
|
|
||||||
|
|
||||||
const handleFilterChange = useCallback((newFilter: string) => {
|
const handleFilterChange = useCallback((newFilter: string) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -159,22 +129,4 @@ const SettingsFoldersChatFilters: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(SettingsFoldersChatFilters);
|
||||||
(global): StateProps => {
|
|
||||||
const {
|
|
||||||
chats: {
|
|
||||||
byId: chatsById,
|
|
||||||
listIds,
|
|
||||||
orderedPinnedIds,
|
|
||||||
},
|
|
||||||
} = global;
|
|
||||||
|
|
||||||
return {
|
|
||||||
chatsById,
|
|
||||||
listIds: listIds.active,
|
|
||||||
orderedPinnedIds: orderedPinnedIds.active,
|
|
||||||
archivedPinnedIds: orderedPinnedIds.archived,
|
|
||||||
archivedListIds: listIds.archived,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
)(SettingsFoldersChatFilters));
|
|
||||||
|
|||||||
@ -3,17 +3,16 @@ import React, {
|
|||||||
} from '../../../../lib/teact/teact';
|
} from '../../../../lib/teact/teact';
|
||||||
import { getDispatch, withGlobal } from '../../../../lib/teact/teactn';
|
import { getDispatch, withGlobal } from '../../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalState } from '../../../../global/types';
|
import { ApiChatFolder } from '../../../../api/types';
|
||||||
import { ApiChatFolder, ApiChat, ApiUser } from '../../../../api/types';
|
import { SettingsScreens } from '../../../../types';
|
||||||
import { NotifyException, NotifySettings, SettingsScreens } from '../../../../types';
|
|
||||||
|
|
||||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||||
import { selectNotifyExceptions, selectNotifySettings } from '../../../../modules/selectors';
|
|
||||||
import { throttle } from '../../../../util/schedulers';
|
import { throttle } from '../../../../util/schedulers';
|
||||||
import getAnimationData from '../../../common/helpers/animatedAssets';
|
import getAnimationData from '../../../common/helpers/animatedAssets';
|
||||||
import { getFolderDescriptionText } from '../../../../modules/helpers';
|
import { getFolderDescriptionText } from '../../../../modules/helpers';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
import { useFolderManagerForChatsCount } from '../../../../hooks/useFolderManager';
|
||||||
|
|
||||||
import ListItem from '../../../ui/ListItem';
|
import ListItem from '../../../ui/ListItem';
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../../ui/Button';
|
||||||
@ -29,14 +28,9 @@ type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
allListIds: GlobalState['chats']['listIds'];
|
|
||||||
chatsById: Record<string, ApiChat>;
|
|
||||||
usersById: Record<string, ApiUser>;
|
|
||||||
orderedFolderIds?: number[];
|
orderedFolderIds?: number[];
|
||||||
foldersById: Record<number, ApiChatFolder>;
|
foldersById: Record<number, ApiChatFolder>;
|
||||||
recommendedChatFolders?: ApiChatFolder[];
|
recommendedChatFolders?: ApiChatFolder[];
|
||||||
notifySettings: NotifySettings;
|
|
||||||
notifyExceptions?: Record<number, NotifyException>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const runThrottledForLoadRecommended = throttle((cb) => cb(), 60000, true);
|
const runThrottledForLoadRecommended = throttle((cb) => cb(), 60000, true);
|
||||||
@ -45,18 +39,13 @@ const MAX_ALLOWED_FOLDERS = 10;
|
|||||||
|
|
||||||
const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||||
isActive,
|
isActive,
|
||||||
allListIds,
|
|
||||||
chatsById,
|
|
||||||
usersById,
|
|
||||||
orderedFolderIds,
|
|
||||||
foldersById,
|
|
||||||
recommendedChatFolders,
|
|
||||||
notifySettings,
|
|
||||||
notifyExceptions,
|
|
||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
onEditFolder,
|
onEditFolder,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
onReset,
|
onReset,
|
||||||
|
orderedFolderIds,
|
||||||
|
foldersById,
|
||||||
|
recommendedChatFolders,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadRecommendedChatFolders,
|
loadRecommendedChatFolders,
|
||||||
@ -101,6 +90,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Folders);
|
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Folders);
|
||||||
|
|
||||||
|
const chatsCountByFolderId = useFolderManagerForChatsCount();
|
||||||
const userFolders = useMemo(() => {
|
const userFolders = useMemo(() => {
|
||||||
if (!orderedFolderIds) {
|
if (!orderedFolderIds) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -112,12 +102,10 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
return {
|
return {
|
||||||
id: folder.id,
|
id: folder.id,
|
||||||
title: folder.title,
|
title: folder.title,
|
||||||
subtitle: getFolderDescriptionText(
|
subtitle: getFolderDescriptionText(lang, folder, chatsCountByFolderId[folder.id]),
|
||||||
lang, allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [lang, allListIds, foldersById, chatsById, usersById, orderedFolderIds, notifySettings, notifyExceptions]);
|
}, [orderedFolderIds, foldersById, lang, chatsCountByFolderId]);
|
||||||
|
|
||||||
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) {
|
||||||
@ -228,11 +216,6 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const {
|
|
||||||
chats: { listIds: allListIds, byId: chatsById },
|
|
||||||
users: { byId: usersById },
|
|
||||||
} = global;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
orderedIds: orderedFolderIds,
|
orderedIds: orderedFolderIds,
|
||||||
byId: foldersById,
|
byId: foldersById,
|
||||||
@ -240,14 +223,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
} = global.chatFolders;
|
} = global.chatFolders;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allListIds,
|
|
||||||
chatsById,
|
|
||||||
usersById,
|
|
||||||
orderedFolderIds,
|
orderedFolderIds,
|
||||||
foldersById,
|
foldersById,
|
||||||
recommendedChatFolders,
|
recommendedChatFolders,
|
||||||
notifySettings: selectNotifySettings(global),
|
|
||||||
notifyExceptions: selectNotifyExceptions(global),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(SettingsFoldersMain));
|
)(SettingsFoldersMain));
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, useEffect, memo, useCallback,
|
FC, useEffect, memo, useCallback,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getDispatch, getGlobal, withGlobal } from '../../lib/teact/teactn';
|
import { getDispatch, withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import { LangCode } from '../../types';
|
import { LangCode } from '../../types';
|
||||||
import { ApiMessage } from '../../api/types';
|
import { ApiMessage } from '../../api/types';
|
||||||
@ -12,7 +12,6 @@ import {
|
|||||||
} from '../../config';
|
} from '../../config';
|
||||||
import {
|
import {
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectCountNotMutedUnreadOptimized,
|
|
||||||
selectIsForwardModalOpen,
|
selectIsForwardModalOpen,
|
||||||
selectIsMediaViewerOpen,
|
selectIsMediaViewerOpen,
|
||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
@ -25,6 +24,7 @@ import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
|
|||||||
import { processDeepLink } from '../../util/deeplink';
|
import { processDeepLink } from '../../util/deeplink';
|
||||||
import stopEvent from '../../util/stopEvent';
|
import stopEvent from '../../util/stopEvent';
|
||||||
import windowSize from '../../util/windowSize';
|
import windowSize from '../../util/windowSize';
|
||||||
|
import { getAllNotificationsCount } from '../../util/folderManager';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
||||||
import useBeforeUnload from '../../hooks/useBeforeUnload';
|
import useBeforeUnload from '../../hooks/useBeforeUnload';
|
||||||
@ -32,6 +32,8 @@ import useOnChange from '../../hooks/useOnChange';
|
|||||||
import usePreventPinchZoomGesture from '../../hooks/usePreventPinchZoomGesture';
|
import usePreventPinchZoomGesture from '../../hooks/usePreventPinchZoomGesture';
|
||||||
import { LOCATION_HASH } from '../../hooks/useHistoryBack';
|
import { LOCATION_HASH } from '../../hooks/useHistoryBack';
|
||||||
|
|
||||||
|
import StickerSetModal from '../common/StickerSetModal.async';
|
||||||
|
import UnreadCount from '../common/UnreadCounter';
|
||||||
import LeftColumn from '../left/LeftColumn';
|
import LeftColumn from '../left/LeftColumn';
|
||||||
import MiddleColumn from '../middle/MiddleColumn';
|
import MiddleColumn from '../middle/MiddleColumn';
|
||||||
import RightColumn from '../right/RightColumn';
|
import RightColumn from '../right/RightColumn';
|
||||||
@ -43,7 +45,6 @@ import Dialogs from './Dialogs.async';
|
|||||||
import ForwardPicker from './ForwardPicker.async';
|
import ForwardPicker from './ForwardPicker.async';
|
||||||
import SafeLinkModal from './SafeLinkModal.async';
|
import SafeLinkModal from './SafeLinkModal.async';
|
||||||
import HistoryCalendar from './HistoryCalendar.async';
|
import HistoryCalendar from './HistoryCalendar.async';
|
||||||
import StickerSetModal from '../common/StickerSetModal.async';
|
|
||||||
import GroupCall from '../calls/group/GroupCall.async';
|
import GroupCall from '../calls/group/GroupCall.async';
|
||||||
import ActiveCallHeader from '../calls/ActiveCallHeader.async';
|
import ActiveCallHeader from '../calls/ActiveCallHeader.async';
|
||||||
import CallFallbackConfirm from '../calls/CallFallbackConfirm.async';
|
import CallFallbackConfirm from '../calls/CallFallbackConfirm.async';
|
||||||
@ -248,7 +249,7 @@ const Main: FC<StateProps> = ({
|
|||||||
const handleBlur = useCallback(() => {
|
const handleBlur = useCallback(() => {
|
||||||
updateIsOnline(false);
|
updateIsOnline(false);
|
||||||
|
|
||||||
const initialUnread = selectCountNotMutedUnreadOptimized(getGlobal());
|
const initialUnread = getAllNotificationsCount();
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
|
||||||
clearInterval(notificationInterval);
|
clearInterval(notificationInterval);
|
||||||
@ -259,7 +260,7 @@ const Main: FC<StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (index % 2 === 0) {
|
if (index % 2 === 0) {
|
||||||
const newUnread = selectCountNotMutedUnreadOptimized(getGlobal()) - initialUnread;
|
const newUnread = getAllNotificationsCount() - initialUnread;
|
||||||
if (newUnread > 0) {
|
if (newUnread > 0) {
|
||||||
updatePageTitle(`${newUnread} notification${newUnread > 1 ? 's' : ''}`);
|
updatePageTitle(`${newUnread} notification${newUnread > 1 ? 's' : ''}`);
|
||||||
updateIcon(true);
|
updateIcon(true);
|
||||||
@ -321,6 +322,7 @@ const Main: FC<StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
<DownloadManager />
|
<DownloadManager />
|
||||||
<CallFallbackConfirm isOpen={isCallFallbackConfirmOpen} />
|
<CallFallbackConfirm isOpen={isCallFallbackConfirmOpen} />
|
||||||
|
<UnreadCount isForAppBadge />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -46,13 +46,13 @@ import useConnectionStatus from '../../hooks/useConnectionStatus';
|
|||||||
|
|
||||||
import PrivateChatInfo from '../common/PrivateChatInfo';
|
import PrivateChatInfo from '../common/PrivateChatInfo';
|
||||||
import GroupChatInfo from '../common/GroupChatInfo';
|
import GroupChatInfo from '../common/GroupChatInfo';
|
||||||
|
import UnreadCounter from '../common/UnreadCounter';
|
||||||
import Transition from '../ui/Transition';
|
import Transition from '../ui/Transition';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import HeaderActions from './HeaderActions';
|
import HeaderActions from './HeaderActions';
|
||||||
import HeaderPinnedMessage from './HeaderPinnedMessage';
|
import HeaderPinnedMessage from './HeaderPinnedMessage';
|
||||||
import AudioPlayer from './AudioPlayer';
|
import AudioPlayer from './AudioPlayer';
|
||||||
import GroupCallTopPane from '../calls/group/GroupCallTopPane';
|
import GroupCallTopPane from '../calls/group/GroupCallTopPane';
|
||||||
import UnreadCount from './UnreadCount';
|
|
||||||
|
|
||||||
import './MiddleHeader.scss';
|
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 (
|
return (
|
||||||
<div className="back-button">
|
<div className="back-button">
|
||||||
<Button
|
<Button
|
||||||
@ -358,7 +358,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
<div className={buildClassName('animated-close-icon', !asClose && 'state-back')} />
|
<div className={buildClassName('animated-close-icon', !asClose && 'state-back')} />
|
||||||
</Button>
|
</Button>
|
||||||
{withUnreadCount && <UnreadCount />}
|
{withUnreadCounter && <UnreadCounter />}
|
||||||
</div>
|
</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,
|
LOCALIZED_TIPS,
|
||||||
RE_TG_LINK,
|
RE_TG_LINK,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
TMP_CHAT_ID,
|
TMP_CHAT_ID, ALL_FOLDER_ID,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import {
|
import {
|
||||||
@ -46,11 +46,12 @@ import {
|
|||||||
import { buildCollectionByKey, omit } from '../../../util/iteratees';
|
import { buildCollectionByKey, omit } from '../../../util/iteratees';
|
||||||
import { debounce, pause, throttle } from '../../../util/schedulers';
|
import { debounce, pause, throttle } from '../../../util/schedulers';
|
||||||
import {
|
import {
|
||||||
isChatSummaryOnly, isChatArchived, prepareChatList, isChatBasicGroup,
|
isChatSummaryOnly, isChatArchived, isChatBasicGroup,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
import { processDeepLink } from '../../../util/deeplink';
|
import { processDeepLink } from '../../../util/deeplink';
|
||||||
import { updateGroupCall } from '../../reducers/calls';
|
import { updateGroupCall } from '../../reducers/calls';
|
||||||
import { selectGroupCall } from '../../selectors/calls';
|
import { selectGroupCall } from '../../selectors/calls';
|
||||||
|
import { getOrderedIds } from '../../../util/folderManager';
|
||||||
|
|
||||||
const TOP_CHAT_MESSAGES_PRELOAD_INTERVAL = 100;
|
const TOP_CHAT_MESSAGES_PRELOAD_INTERVAL = 100;
|
||||||
const CHATS_PRELOAD_INTERVAL = 300;
|
const CHATS_PRELOAD_INTERVAL = 300;
|
||||||
@ -61,31 +62,21 @@ const runDebouncedForLoadFullChat = debounce((cb) => cb(), 500, false, true);
|
|||||||
|
|
||||||
addReducer('preloadTopChatMessages', (global, actions) => {
|
addReducer('preloadTopChatMessages', (global, actions) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const preloadedChatIds: string[] = [];
|
const preloadedChatIds = new Set<string>();
|
||||||
|
|
||||||
for (let i = 0; i < TOP_CHAT_MESSAGES_PRELOAD_LIMIT; i++) {
|
for (let i = 0; i < TOP_CHAT_MESSAGES_PRELOAD_LIMIT; i++) {
|
||||||
await pause(TOP_CHAT_MESSAGES_PRELOAD_INTERVAL);
|
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 { chatId: currentChatId } = selectCurrentMessageList(global) || {};
|
||||||
const { pinnedChats, otherChats } = prepareChatList(byId, listIds, orderedPinnedIds, 'all', true);
|
const folderAllOrderedIds = getOrderedIds(ALL_FOLDER_ID);
|
||||||
const topChats = [...pinnedChats, ...otherChats];
|
const nextChatId = folderAllOrderedIds?.find((id) => id !== currentChatId && !preloadedChatIds.has(id));
|
||||||
const chatToPreload = topChats.find(({ id }) => id !== currentChatId && !preloadedChatIds.includes(id));
|
if (!nextChatId) {
|
||||||
if (!chatToPreload) {
|
|
||||||
return;
|
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';
|
} from '../../../config';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import { updateAppBadge } from '../../../util/appBadge';
|
|
||||||
import {
|
import {
|
||||||
replaceChatListIds,
|
replaceChatListIds,
|
||||||
replaceChats,
|
replaceChats,
|
||||||
@ -34,7 +33,6 @@ import {
|
|||||||
selectDraft,
|
selectDraft,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectCountNotMutedUnreadOptimized,
|
|
||||||
selectLastServiceNotification,
|
selectLastServiceNotification,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { isUserId } from '../../helpers';
|
import { isUserId } from '../../helpers';
|
||||||
@ -102,8 +100,6 @@ async function afterSync() {
|
|||||||
|
|
||||||
await callApi('fetchCurrentUser');
|
await callApi('fetchCurrentUser');
|
||||||
|
|
||||||
updateAppBadge(selectCountNotMutedUnreadOptimized(getGlobal()));
|
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('>>> FINISH AFTER-SYNC');
|
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 { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
import { closeMessageNotifications, notifyAboutNewMessage } from '../../../util/notifications';
|
import { closeMessageNotifications, notifyAboutNewMessage } from '../../../util/notifications';
|
||||||
import { updateAppBadge } from '../../../util/appBadge';
|
|
||||||
import {
|
import {
|
||||||
updateChat,
|
updateChat,
|
||||||
updateChatListIds,
|
updateChatListIds,
|
||||||
@ -19,16 +18,12 @@ import {
|
|||||||
selectIsChatListed,
|
selectIsChatListed,
|
||||||
selectChatListType,
|
selectChatListType,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectCountNotMutedUnreadOptimized,
|
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { throttle } from '../../../util/schedulers';
|
|
||||||
|
|
||||||
const TYPING_STATUS_CLEAR_DELAY = 6000; // 6 seconds
|
const TYPING_STATUS_CLEAR_DELAY = 6000; // 6 seconds
|
||||||
// Enough to animate and mark as read in Message List
|
// Enough to animate and mark as read in Message List
|
||||||
const CURRENT_CHAT_UNREAD_DELAY = 1500;
|
const CURRENT_CHAT_UNREAD_DELAY = 1500;
|
||||||
|
|
||||||
const runThrottledForUpdateAppBadge = throttle((cb) => cb(), 500, true);
|
|
||||||
|
|
||||||
addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||||
switch (update['@type']) {
|
switch (update['@type']) {
|
||||||
case 'updateChat': {
|
case 'updateChat': {
|
||||||
@ -40,8 +35,6 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
const newGlobal = updateChat(global, update.id, update.chat, update.newProfilePhoto);
|
const newGlobal = updateChat(global, update.id, update.chat, update.newProfilePhoto);
|
||||||
setGlobal(newGlobal);
|
setGlobal(newGlobal);
|
||||||
|
|
||||||
runThrottledForUpdateAppBadge(() => updateAppBadge(selectCountNotMutedUnreadOptimized(getGlobal())));
|
|
||||||
|
|
||||||
if (update.chat.id) {
|
if (update.chat.id) {
|
||||||
closeMessageNotifications({
|
closeMessageNotifications({
|
||||||
chatId: update.chat.id,
|
chatId: update.chat.id,
|
||||||
@ -77,8 +70,6 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
case 'updateChatInbox': {
|
case 'updateChatInbox': {
|
||||||
setGlobal(updateChat(global, update.id, update.chat));
|
setGlobal(updateChat(global, update.id, update.chat));
|
||||||
|
|
||||||
runThrottledForUpdateAppBadge(() => updateAppBadge(selectCountNotMutedUnreadOptimized(getGlobal())));
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +120,6 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAppBadge(selectCountNotMutedUnreadOptimized(getGlobal()));
|
|
||||||
notifyAboutNewMessage({
|
notifyAboutNewMessage({
|
||||||
chat,
|
chat,
|
||||||
message,
|
message,
|
||||||
|
|||||||
@ -7,7 +7,6 @@ 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';
|
||||||
|
|
||||||
@ -270,207 +269,7 @@ export function getCanDeleteChat(chat: ApiChat) {
|
|||||||
return isChatBasicGroup(chat) || ((isChatSuperGroup(chat) || isChatChannel(chat)) && chat.isCreator);
|
return isChatBasicGroup(chat) || ((isChatSuperGroup(chat) || isChatChannel(chat)) && chat.isCreator);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prepareFolderListIds(
|
export function getFolderDescriptionText(lang: LangFn, folder: ApiChatFolder, chatsCount?: number) {
|
||||||
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>,
|
|
||||||
) {
|
|
||||||
const {
|
const {
|
||||||
id, title, emoticon, description, pinnedChatIds,
|
id, title, emoticon, description, pinnedChatIds,
|
||||||
excludedChatIds, includedChatIds,
|
excludedChatIds, includedChatIds,
|
||||||
@ -481,12 +280,12 @@ export function getFolderDescriptionText(
|
|||||||
// If folder has multiple additive filters or uses include/exclude lists,
|
// If folder has multiple additive filters or uses include/exclude lists,
|
||||||
// we display folder chats count
|
// we display folder chats count
|
||||||
if (
|
if (
|
||||||
Object.values(filters).filter(Boolean).length > 1
|
chatsCount !== undefined && (
|
||||||
|| (excludedChatIds?.length)
|
Object.values(filters).filter(Boolean).length > 1
|
||||||
|| (includedChatIds?.length)
|
|| (excludedChatIds?.length)
|
||||||
) {
|
|| (includedChatIds?.length)
|
||||||
const length = getFolderChatsCount(allListIds, chatsById, usersById, folder, notifySettings, notifyExceptions);
|
)) {
|
||||||
return lang('Chats', length);
|
return lang('Chats', chatsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we return a short description of a single filter
|
// 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) {
|
export function getMessageSenderName(lang: LangFn, chatId: string, sender?: ApiUser) {
|
||||||
if (!sender || isUserId(chatId)) {
|
if (!sender || isUserId(chatId)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -2,14 +2,12 @@ import { ApiChat, MAIN_THREAD_ID } from '../../api/types';
|
|||||||
import { GlobalState } from '../../global/types';
|
import { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getPrivateChatUserId, isChatChannel, isUserId, isHistoryClearMessage, isUserBot, isUserOnline, selectIsChatMuted,
|
getPrivateChatUserId, isChatChannel, isUserId, isHistoryClearMessage, isUserBot, isUserOnline,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { selectUser } from './users';
|
import { selectUser } from './users';
|
||||||
import {
|
import {
|
||||||
ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SERVICE_NOTIFICATIONS_USER_ID,
|
ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
import { selectNotifyExceptions, selectNotifySettings } from './settings';
|
|
||||||
import memoized from '../../util/memoized';
|
|
||||||
|
|
||||||
export function selectChat(global: GlobalState, chatId: string): ApiChat | undefined {
|
export function selectChat(global: GlobalState, chatId: string): ApiChat | undefined {
|
||||||
return global.chats.byId[chatId];
|
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) {
|
export function selectIsServiceChatReady(global: GlobalState) {
|
||||||
return Boolean(selectChat(global, SERVICE_NOTIFICATIONS_USER_ID));
|
return Boolean(selectChat(global, SERVICE_NOTIFICATIONS_USER_ID));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,9 +22,16 @@ export function createCallbackManager() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasCallbacks() {
|
||||||
|
return Boolean(callbacks.length);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
runCallbacks,
|
runCallbacks,
|
||||||
addCallback,
|
addCallback,
|
||||||
removeCallback,
|
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