Global Search: Implement Channels tab (#4549)

This commit is contained in:
Alexander Zinchuk 2024-05-03 14:38:32 +02:00
parent 6b60fb4759
commit dd6bc51b59
12 changed files with 129 additions and 66 deletions

View File

@ -1998,12 +1998,9 @@ export function setViewForumAsMessages({ chat, isEnabled }: { chat: ApiChat; isE
}); });
} }
export async function fetchChannelRecommendations({ chat }: { chat: ApiChat }) { export async function fetchChannelRecommendations({ chat }: { chat?: ApiChat }) {
const { id, accessHash } = chat;
const channel = buildInputEntity(id, accessHash);
const result = await invokeRequest(new GramJs.channels.GetChannelRecommendations({ const result = await invokeRequest(new GramJs.channels.GetChannelRecommendations({
channel: channel as GramJs.InputChannel, channel: chat && buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
})); }));
if (!result) { if (!result) {
return undefined; return undefined;
@ -2011,12 +2008,13 @@ export async function fetchChannelRecommendations({ chat }: { chat: ApiChat }) {
updateLocalDb(result); updateLocalDb(result);
const similarChannels = result?.chats
.map((c) => buildApiChatFromPreview(c))
.filter(Boolean);
return { return {
similarChannels: result?.chats similarChannels,
.map((_chat) => buildApiChatFromPreview(_chat)) count: result instanceof GramJs.messages.ChatsSlice ? result.count : similarChannels.length,
.filter(Boolean),
count:
result instanceof GramJs.messages.ChatsSlice ? result.count : undefined,
}; };
} }

View File

@ -1232,6 +1232,7 @@ export async function searchMessagesGlobal({
q: query, q: query,
offsetRate, offsetRate,
offsetPeer: new GramJs.InputPeerEmpty(), offsetPeer: new GramJs.InputPeerEmpty(),
broadcastsOnly: type === 'channels' || undefined,
limit, limit,
filter, filter,
folderId: ALL_FOLDER_ID, folderId: ALL_FOLDER_ID,

View File

@ -786,7 +786,7 @@ export type ApiReplyKeyboard = {
}; };
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'profilePhoto'; export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'profilePhoto';
export type ApiGlobalMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice'; export type ApiGlobalMessageSearchType = 'text' | 'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice';
export type ApiReportReason = 'spam' | 'violence' | 'pornography' | 'childAbuse' export type ApiReportReason = 'spam' | 'violence' | 'pornography' | 'childAbuse'
| 'copyright' | 'geoIrrelevant' | 'fake' | 'illegalDrugs' | 'personalDetails' | 'other'; | 'copyright' | 'geoIrrelevant' | 'fake' | 'illegalDrugs' | 'personalDetails' | 'other';

View File

@ -4,15 +4,16 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global'; import { getActions, getGlobal, withGlobal } from '../../../global';
import type { ApiChat, ApiMessage } from '../../../api/types'; import type { ApiMessage } from '../../../api/types';
import { LoadMoreDirection } from '../../../types'; import { LoadMoreDirection } from '../../../types';
import { ALL_FOLDER_ID } from '../../../config'; import { ALL_FOLDER_ID, GLOBAL_SUGGESTED_CHANNELS_ID } from '../../../config';
import { import {
filterChatsByName, filterChatsByName,
filterUsersByName, filterUsersByName,
isChatChannel,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { selectTabState } from '../../../global/selectors'; import { selectSimilarChannelIds, selectTabState } from '../../../global/selectors';
import { getOrderedIds } from '../../../util/folderManager'; import { getOrderedIds } from '../../../util/folderManager';
import { unique } from '../../../util/iteratees'; import { unique } from '../../../util/iteratees';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
@ -21,6 +22,7 @@ import { renderMessageSummary } from '../../common/helpers/renderMessageText';
import sortChatIds from '../../common/helpers/sortChatIds'; import sortChatIds from '../../common/helpers/sortChatIds';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
import useEffectOnce from '../../../hooks/useEffectOnce';
import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -37,6 +39,7 @@ export type OwnProps = {
searchQuery?: string; searchQuery?: string;
dateSearchQuery?: string; dateSearchQuery?: string;
searchDate?: number; searchDate?: number;
isChannelList?: boolean;
onReset: () => void; onReset: () => void;
onSearchDateSelect: (value: Date) => void; onSearchDateSelect: (value: Date) => void;
}; };
@ -50,8 +53,8 @@ type StateProps = {
globalUserIds?: string[]; globalUserIds?: string[];
foundIds?: string[]; foundIds?: string[];
globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>; globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>;
chatsById: Record<string, ApiChat>;
fetchingStatus?: { chats?: boolean; messages?: boolean }; fetchingStatus?: { chats?: boolean; messages?: boolean };
suggestedChannelIds?: string[];
}; };
const MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH = 4; const MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH = 4;
@ -60,6 +63,7 @@ const LESS_LIST_ITEMS_AMOUNT = 5;
const runThrottled = throttle((cb) => cb(), 500, false); const runThrottled = throttle((cb) => cb(), 500, false);
const ChatResults: FC<OwnProps & StateProps> = ({ const ChatResults: FC<OwnProps & StateProps> = ({
isChannelList,
searchQuery, searchQuery,
searchDate, searchDate,
dateSearchQuery, dateSearchQuery,
@ -71,13 +75,13 @@ const ChatResults: FC<OwnProps & StateProps> = ({
globalUserIds, globalUserIds,
foundIds, foundIds,
globalMessagesByChatId, globalMessagesByChatId,
chatsById,
fetchingStatus, fetchingStatus,
suggestedChannelIds,
onReset, onReset,
onSearchDateSelect, onSearchDateSelect,
}) => { }) => {
const { const {
openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId, openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId, loadChannelRecommendations,
} = getActions(); } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -89,11 +93,15 @@ const ChatResults: FC<OwnProps & StateProps> = ({
const [shouldShowMoreLocal, setShouldShowMoreLocal] = useState<boolean>(false); const [shouldShowMoreLocal, setShouldShowMoreLocal] = useState<boolean>(false);
const [shouldShowMoreGlobal, setShouldShowMoreGlobal] = useState<boolean>(false); const [shouldShowMoreGlobal, setShouldShowMoreGlobal] = useState<boolean>(false);
useEffectOnce(() => {
if (isChannelList) loadChannelRecommendations({});
});
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (direction === LoadMoreDirection.Backwards) { if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => { runThrottled(() => {
searchMessagesGlobal({ searchMessagesGlobal({
type: 'text', type: isChannelList ? 'channels' : 'text',
}); });
}); });
} }
@ -120,21 +128,32 @@ const ChatResults: FC<OwnProps & StateProps> = ({
}, [setGlobalSearchChatId]); }, [setGlobalSearchChatId]);
const localResults = useMemo(() => { const localResults = useMemo(() => {
if (!searchQuery || (searchQuery.startsWith('@') && searchQuery.length < 2)) { if (!isChannelList && (!searchQuery || (searchQuery.startsWith('@') && searchQuery.length < 2))) {
return MEMO_EMPTY_ARRAY; return MEMO_EMPTY_ARRAY;
} }
// No need for expensive global updates, so we avoid them
const usersById = getGlobal().users.byId;
const chatsById = getGlobal().chats.byId;
const orderedChatIds = getOrderedIds(ALL_FOLDER_ID) ?? [];
const filteredChatIds = orderedChatIds.filter((id) => {
if (!isChannelList) return true;
const chat = chatsById[id];
return chat && isChatChannel(chat);
});
const localChatIds = filterChatsByName(lang, filteredChatIds, chatsById, searchQuery, currentUserId);
if (isChannelList) return localChatIds;
const contactIdsWithMe = [ const contactIdsWithMe = [
...(currentUserId ? [currentUserId] : []), ...(currentUserId ? [currentUserId] : []),
...(contactIds || []), ...(contactIds || []),
]; ];
// No need for expensive global updates on users, so we avoid them
const usersById = getGlobal().users.byId;
const localContactIds = filterUsersByName( const localContactIds = filterUsersByName(
contactIdsWithMe, usersById, searchQuery, currentUserId, lang('SavedMessages'), contactIdsWithMe, usersById, searchQuery, currentUserId, lang('SavedMessages'),
); );
const orderedChatIds = getOrderedIds(ALL_FOLDER_ID) ?? [];
const localChatIds = filterChatsByName(lang, orderedChatIds, chatsById, searchQuery, currentUserId);
const localPeerIds = unique([ const localPeerIds = unique([
...localContactIds, ...localContactIds,
@ -150,20 +169,27 @@ const ChatResults: FC<OwnProps & StateProps> = ({
...sortChatIds(localPeerIds, undefined, currentUserId ? [currentUserId] : undefined), ...sortChatIds(localPeerIds, undefined, currentUserId ? [currentUserId] : undefined),
...sortChatIds(accountPeerIds), ...sortChatIds(accountPeerIds),
]; ];
}, [searchQuery, currentUserId, contactIds, lang, accountChatIds, accountUserIds, chatsById]); }, [searchQuery, lang, currentUserId, contactIds, accountChatIds, accountUserIds, isChannelList]);
useHorizontalScroll(chatSelectionRef, !localResults.length, true); useHorizontalScroll(chatSelectionRef, !localResults.length || isChannelList, true);
const globalResults = useMemo(() => { const globalResults = useMemo(() => {
if (!searchQuery || searchQuery.length < MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH || !globalChatIds || !globalUserIds) { if (!searchQuery || searchQuery.length < MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH || !globalChatIds || !globalUserIds) {
return MEMO_EMPTY_ARRAY; return MEMO_EMPTY_ARRAY;
} }
return sortChatIds( // No need for expensive global updates, so we avoid them
unique([...globalChatIds, ...globalUserIds]), const chatsById = getGlobal().chats.byId;
true,
); const ids = unique([...globalChatIds, ...globalUserIds]);
}, [globalChatIds, globalUserIds, searchQuery]); const filteredIds = ids.filter((id) => {
if (!isChannelList) return true;
const chat = chatsById[id];
return chat && isChatChannel(chat);
});
return sortChatIds(filteredIds, true);
}, [globalChatIds, globalUserIds, isChannelList, searchQuery]);
const foundMessages = useMemo(() => { const foundMessages = useMemo(() => {
if ((!searchQuery && !searchDate) || !foundIds || foundIds.length === 0) { if ((!searchQuery && !searchDate) || !foundIds || foundIds.length === 0) {
@ -188,6 +214,8 @@ const ChatResults: FC<OwnProps & StateProps> = ({
}, [shouldShowMoreGlobal]); }, [shouldShowMoreGlobal]);
function renderFoundMessage(message: ApiMessage) { function renderFoundMessage(message: ApiMessage) {
const chatsById = getGlobal().chats.byId;
const text = renderMessageSummary(lang, message); const text = renderMessageSummary(lang, message);
const chat = chatsById[message.chatId]; const chat = chatsById[message.chatId];
@ -207,7 +235,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
const nothingFound = fetchingStatus && !fetchingStatus.chats && !fetchingStatus.messages const nothingFound = fetchingStatus && !fetchingStatus.chats && !fetchingStatus.messages
&& !localResults.length && !globalResults.length && !foundMessages.length; && !localResults.length && !globalResults.length && !foundMessages.length;
if (!searchQuery && !searchDate) { if (!searchQuery && !searchDate && !isChannelList) {
return <RecentContacts onReset={onReset} />; return <RecentContacts onReset={onReset} />;
} }
@ -234,7 +262,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
description={lang('ChatList.Search.NoResultsDescription')} description={lang('ChatList.Search.NoResultsDescription')}
/> />
)} )}
{Boolean(localResults.length) && ( {Boolean(localResults.length) && !isChannelList && (
<div <div
className="chat-selection no-scrollbar" className="chat-selection no-scrollbar"
dir={lang.isRtl ? 'rtl' : undefined} dir={lang.isRtl ? 'rtl' : undefined}
@ -257,7 +285,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
{lang(shouldShowMoreLocal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')} {lang(shouldShowMoreLocal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
</Link> </Link>
)} )}
{lang('DialogList.SearchSectionDialogs')} {lang(isChannelList ? 'SearchMyChannels' : 'DialogList.SearchSectionDialogs')}
</h3> </h3>
{localResults.map((id, index) => { {localResults.map((id, index) => {
if (!shouldShowMoreLocal && index >= LESS_LIST_ITEMS_AMOUNT) { if (!shouldShowMoreLocal && index >= LESS_LIST_ITEMS_AMOUNT) {
@ -298,6 +326,22 @@ const ChatResults: FC<OwnProps & StateProps> = ({
})} })}
</div> </div>
)} )}
{Boolean(suggestedChannelIds?.length) && !searchQuery && (
<div className="search-section">
<h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>
{lang('SearchRecommendedChannels')}
</h3>
{suggestedChannelIds.map((id) => {
return (
<LeftSearchResultChat
chatId={id}
withUsername
onClick={handleChatClick}
/>
);
})}
</div>
)}
{Boolean(foundMessages.length) && ( {Boolean(foundMessages.length) && (
<div className="search-section"> <div className="search-section">
<h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>{lang('SearchMessages')}</h3> <h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>{lang('SearchMessages')}</h3>
@ -309,18 +353,14 @@ const ChatResults: FC<OwnProps & StateProps> = ({
}; };
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global, { isChannelList }): StateProps => {
const { byId: chatsById } = global.chats;
const { userIds: contactIds } = global.contactList || {}; const { userIds: contactIds } = global.contactList || {};
const { const {
currentUserId, messages, currentUserId, messages,
} = global; } = global;
if (!contactIds) { if (!contactIds) {
return { return {};
chatsById,
};
} }
const { const {
@ -329,7 +369,8 @@ export default memo(withGlobal<OwnProps>(
const { chatIds: globalChatIds, userIds: globalUserIds } = globalResults || {}; const { chatIds: globalChatIds, userIds: globalUserIds } = globalResults || {};
const { chatIds: accountChatIds, userIds: accountUserIds } = localResults || {}; const { chatIds: accountChatIds, userIds: accountUserIds } = localResults || {};
const { byChatId: globalMessagesByChatId } = messages; const { byChatId: globalMessagesByChatId } = messages;
const foundIds = resultsByType?.text?.foundIds; const foundIds = resultsByType?.[isChannelList ? 'channels' : 'text']?.foundIds;
const { similarChannelIds } = selectSimilarChannelIds(global, GLOBAL_SUGGESTED_CHANNELS_ID) || {};
return { return {
currentUserId, currentUserId,
@ -340,8 +381,8 @@ export default memo(withGlobal<OwnProps>(
globalUserIds, globalUserIds,
foundIds, foundIds,
globalMessagesByChatId, globalMessagesByChatId,
chatsById,
fetchingStatus, fetchingStatus,
suggestedChannelIds: similarChannelIds,
}; };
}, },
)(ChatResults)); )(ChatResults));

View File

@ -1,6 +1,8 @@
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
memo, useCallback, useMemo, useRef, memo,
useMemo,
useRef,
useState, useState,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
@ -13,6 +15,7 @@ import { parseDateString } from '../../../util/date/dateFormat';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation'; import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import TabList from '../../ui/TabList'; import TabList from '../../ui/TabList';
import Transition from '../../ui/Transition'; import Transition from '../../ui/Transition';
@ -39,6 +42,7 @@ type StateProps = {
const TABS = [ const TABS = [
{ type: GlobalSearchContent.ChatList, title: 'SearchAllChatsShort' }, { type: GlobalSearchContent.ChatList, title: 'SearchAllChatsShort' },
{ type: GlobalSearchContent.ChannelList, title: 'ChannelsTab' },
{ type: GlobalSearchContent.Media, title: 'SharedMediaTab2' }, { type: GlobalSearchContent.Media, title: 'SharedMediaTab2' },
{ type: GlobalSearchContent.Links, title: 'SharedLinksTab2' }, { type: GlobalSearchContent.Links, title: 'SharedLinksTab2' },
{ type: GlobalSearchContent.Files, title: 'SharedFilesTab2' }, { type: GlobalSearchContent.Files, title: 'SharedFilesTab2' },
@ -48,11 +52,9 @@ const TABS = [
const CHAT_TABS = [ const CHAT_TABS = [
{ type: GlobalSearchContent.ChatList, title: 'All Messages' }, { type: GlobalSearchContent.ChatList, title: 'All Messages' },
...TABS.slice(1), ...TABS.slice(2), // Skip ChatList and ChannelList, replaced with All Messages
]; ];
const TRANSITION_RENDER_COUNT = Object.keys(GlobalSearchContent).length / 2;
const LeftSearch: FC<OwnProps & StateProps> = ({ const LeftSearch: FC<OwnProps & StateProps> = ({
searchQuery, searchQuery,
searchDate, searchDate,
@ -70,15 +72,17 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
const [activeTab, setActiveTab] = useState(currentContent); const [activeTab, setActiveTab] = useState(currentContent);
const dateSearchQuery = useMemo(() => parseDateString(searchQuery), [searchQuery]); const dateSearchQuery = useMemo(() => parseDateString(searchQuery), [searchQuery]);
const handleSwitchTab = useCallback((index: number) => { const tabs = chatId ? CHAT_TABS : TABS;
const tab = TABS[index];
const handleSwitchTab = useLastCallback((index: number) => {
const tab = tabs[index];
setGlobalSearchContent({ content: tab.type }); setGlobalSearchContent({ content: tab.type });
setActiveTab(index); setActiveTab(index);
}, [setGlobalSearchContent]); });
const handleSearchDateSelect = useCallback((value: Date) => { const handleSearchDateSelect = useLastCallback((value: Date) => {
setGlobalSearchDate({ date: value.getTime() / 1000 }); setGlobalSearchDate({ date: value.getTime() / 1000 });
}, [setGlobalSearchDate]); });
useHistoryBack({ useHistoryBack({
isActive, isActive,
@ -91,15 +95,16 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
return ( return (
<div className="LeftSearch" ref={containerRef} onKeyDown={handleKeyDown}> <div className="LeftSearch" ref={containerRef} onKeyDown={handleKeyDown}>
<TabList activeTab={activeTab} tabs={chatId ? CHAT_TABS : TABS} onSwitchTab={handleSwitchTab} /> <TabList activeTab={activeTab} tabs={tabs} onSwitchTab={handleSwitchTab} />
<Transition <Transition
name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'} name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'}
renderCount={TRANSITION_RENDER_COUNT} renderCount={tabs.length}
activeKey={currentContent} activeKey={currentContent}
> >
{(() => { {(() => {
switch (currentContent) { switch (currentContent) {
case GlobalSearchContent.ChatList: case GlobalSearchContent.ChatList:
case GlobalSearchContent.ChannelList:
if (chatId) { if (chatId) {
return ( return (
<ChatMessageResults <ChatMessageResults
@ -112,6 +117,7 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
} }
return ( return (
<ChatResults <ChatResults
isChannelList={currentContent === GlobalSearchContent.ChannelList}
searchQuery={searchQuery} searchQuery={searchQuery}
searchDate={searchDate} searchDate={searchDate}
dateSearchQuery={dateSearchQuery} dateSearchQuery={dateSearchQuery}

View File

@ -197,7 +197,7 @@ const Profile: FC<OwnProps & StateProps> = ({
loadPeerPinnedStories, loadPeerPinnedStories,
loadStoriesArchive, loadStoriesArchive,
openPremiumModal, openPremiumModal,
fetchChannelRecommendations, loadChannelRecommendations,
} = getActions(); } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -257,7 +257,7 @@ const Profile: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
if (isChannel && !similarChannels) { if (isChannel && !similarChannels) {
fetchChannelRecommendations({ chatId }); loadChannelRecommendations({ chatId });
} }
}, [chatId, isChannel, similarChannels]); }, [chatId, isChannel, similarChannels]);

View File

@ -93,6 +93,8 @@ export const STORY_VIEWS_MIN_SEARCH = 15;
export const STORY_MIN_REACTIONS_SORT = 10; export const STORY_MIN_REACTIONS_SORT = 10;
export const STORY_VIEWS_MIN_CONTACTS_FILTER = 20; export const STORY_VIEWS_MIN_CONTACTS_FILTER = 20;
export const GLOBAL_SUGGESTED_CHANNELS_ID = 'global';
// As in Telegram for Android // As in Telegram for Android
// https://github.com/DrKLO/Telegram/blob/51e9947527/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java#L7799 // https://github.com/DrKLO/Telegram/blob/51e9947527/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java#L7799
export const TOP_REACTIONS_LIMIT = 100; export const TOP_REACTIONS_LIMIT = 100;

View File

@ -21,6 +21,7 @@ import {
CHAT_LIST_LOAD_SLICE, CHAT_LIST_LOAD_SLICE,
DEBUG, DEBUG,
GLOBAL_STATE_CACHE_ARCHIVED_CHAT_LIST_LIMIT, GLOBAL_STATE_CACHE_ARCHIVED_CHAT_LIST_LIMIT,
GLOBAL_SUGGESTED_CHANNELS_ID,
RE_TG_LINK, RE_TG_LINK,
SAVED_FOLDER_ID, SAVED_FOLDER_ID,
SERVICE_NOTIFICATIONS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID,
@ -105,6 +106,7 @@ import {
selectIsChatPinned, selectIsChatPinned,
selectIsChatWithSelf, selectIsChatWithSelf,
selectLastServiceNotification, selectLastServiceNotification,
selectSimilarChannelIds,
selectStickerSet, selectStickerSet,
selectSupportChat, selectSupportChat,
selectTabState, selectTabState,
@ -2615,25 +2617,34 @@ addActionHandler('setViewForumAsMessages', (global, actions, payload): ActionRet
void callApi('setViewForumAsMessages', { chat, isEnabled }); void callApi('setViewForumAsMessages', { chat, isEnabled });
}); });
addActionHandler('fetchChannelRecommendations', async (global, actions, payload): Promise<void> => { addActionHandler('loadChannelRecommendations', async (global, actions, payload): Promise<void> => {
const { chatId } = payload; const { chatId } = payload;
const chat = selectChat(global, chatId); const chat = chatId ? selectChat(global, chatId) : undefined;
if (!chat) { if (chatId && !chat) {
return; return;
} }
const { similarChannels, count } = await callApi('fetchChannelRecommendations', { if (!chatId) {
const similarChannelIds = selectSimilarChannelIds(global, GLOBAL_SUGGESTED_CHANNELS_ID);
if (similarChannelIds) return; // Already cached
}
const result = await callApi('fetchChannelRecommendations', {
chat, chat,
}) || {}; });
if (!similarChannels) { if (!result) {
return; return;
} }
const { similarChannels, count } = result;
const chatsById = buildCollectionByKey(similarChannels, 'id');
global = getGlobal(); global = getGlobal();
global = addChats(global, buildCollectionByKey(similarChannels, 'id')); global = addChats(global, chatsById);
global = addSimilarChannels(global, chatId, similarChannels.map((channel) => channel.id), count); global = addSimilarChannels(global, chatId || GLOBAL_SUGGESTED_CHANNELS_ID, Object.keys(chatsById), count);
setGlobal(global); setGlobal(global);
}); });

View File

@ -95,7 +95,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
const listType = selectChatListType(global, update.id); const listType = selectChatListType(global, update.id);
const chat = selectChat(global, update.id); const chat = selectChat(global, update.id);
if (chat && isChatChannel(chat)) { if (chat && isChatChannel(chat)) {
actions.fetchChannelRecommendations({ chatId: chat.id }); actions.loadChannelRecommendations({ chatId: chat.id });
const lastMessageId = selectChatLastMessageId(global, chat.id); const lastMessageId = selectChatLastMessageId(global, chat.id);
const localMessage = buildLocalMessage(chat, lastMessageId); const localMessage = buildLocalMessage(chat, lastMessageId);
localMessage.content.action = { localMessage.content.action = {

View File

@ -184,7 +184,10 @@ export function addChats<T extends GlobalState>(global: T, newById: Record<strin
const existingChat = byId[id]; const existingChat = byId[id];
const newChat = newById[id]; const newChat = newById[id];
if (existingChat && !existingChat.isMin && (newChat.isMin || existingChat.accessHash === newChat.accessHash)) { const membersCountChanged = !existingChat?.membersCount && newChat.membersCount;
if (existingChat && !existingChat.isMin && !membersCountChanged
&& (newChat.isMin || existingChat.accessHash === newChat.accessHash)) {
return acc; return acc;
} }

View File

@ -1931,8 +1931,8 @@ export interface ActionPayloads {
fetchChat: { fetchChat: {
chatId: string; chatId: string;
}; };
fetchChannelRecommendations: { loadChannelRecommendations: {
chatId: string; chatId?: string;
}; };
toggleChannelRecommendations: { toggleChannelRecommendations: {
chatId: string; chatId: string;

View File

@ -276,6 +276,7 @@ export enum LeftColumnContent {
export enum GlobalSearchContent { export enum GlobalSearchContent {
ChatList, ChatList,
ChannelList,
Media, Media,
Links, Links,
Files, Files,