[Refactoring] User: Extract nested data to a separate path (#4953)

This commit is contained in:
zubiden 2024-09-19 20:43:20 +02:00 committed by Alexander Zinchuk
parent 8b76de86ba
commit 25b4aae4cd
25 changed files with 223 additions and 141 deletions

View File

@ -5,7 +5,6 @@ import type {
ApiChat, ApiPeer, ApiSticker, ApiUser, ApiChat, ApiPeer, ApiSticker, ApiUser,
} from '../../types'; } from '../../types';
import { COMMON_CHATS_LIMIT } from '../../../config';
import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { buildApiPhoto } from '../apiBuilders/common'; import { buildApiPhoto } from '../apiBuilders/common';
import { buildApiPeerId } from '../apiBuilders/peers'; import { buildApiPeerId } from '../apiBuilders/peers';
@ -87,21 +86,21 @@ export async function fetchFullUser({
}; };
} }
export async function fetchCommonChats(id: string, accessHash?: string, maxId?: string) { export async function fetchCommonChats(user: ApiUser, maxId?: string) {
const commonChats = await invokeRequest(new GramJs.messages.GetCommonChats({ const result = await invokeRequest(new GramJs.messages.GetCommonChats({
userId: buildInputEntity(id, accessHash) as GramJs.InputUser, userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
maxId: maxId ? buildMtpPeerId(maxId, getEntityTypeById(maxId)) : undefined, maxId: maxId ? buildMtpPeerId(maxId, getEntityTypeById(maxId)) : undefined,
limit: COMMON_CHATS_LIMIT,
})); }));
if (!commonChats) { if (!result) {
return undefined; return undefined;
} }
const chats = commonChats.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean); const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
const chatIds = chats.map(({ id: chatId }) => chatId); const chatIds = chats.map(({ id: chatId }) => chatId);
const count = 'count' in result ? result.count : chatIds.length;
return { chatIds, isFullyLoaded: chatIds.length < COMMON_CHATS_LIMIT }; return { chatIds, count };
} }
export async function fetchNearestCountry() { export async function fetchNearestCountry() {

View File

@ -3,7 +3,7 @@ import type { ApiBotCommand } from './bots';
import type { import type {
ApiChatReactions, ApiFormattedText, ApiPhoto, ApiStickerSet, ApiChatReactions, ApiFormattedText, ApiPhoto, ApiStickerSet,
} from './messages'; } from './messages';
import type { ApiChatInviteImporter, ApiPeerPhotos } from './misc'; import type { ApiChatInviteImporter } from './misc';
import type { import type {
ApiEmojiStatus, ApiFakeType, ApiUser, ApiUsername, ApiEmojiStatus, ApiFakeType, ApiUser, ApiUsername,
} from './users'; } from './users';
@ -41,7 +41,6 @@ export interface ApiChat {
membersCount?: number; membersCount?: number;
creationDate?: number; creationDate?: number;
isSupport?: true; isSupport?: true;
profilePhotos?: ApiPeerPhotos;
draftDate?: number; draftDate?: number;
isProtected?: boolean; isProtected?: boolean;
fakeType?: ApiFakeType; fakeType?: ApiFakeType;

View File

@ -3,7 +3,6 @@ import type { ApiBotInfo } from './bots';
import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from './business'; import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from './business';
import type { ApiPeerColor } from './chats'; import type { ApiPeerColor } from './chats';
import type { ApiDocument, ApiPhoto } from './messages'; import type { ApiDocument, ApiPhoto } from './messages';
import type { ApiPeerPhotos } from './misc';
export interface ApiUser { export interface ApiUser {
id: string; id: string;
@ -23,14 +22,8 @@ export interface ApiUser {
accessHash?: string; accessHash?: string;
hasVideoAvatar?: boolean; hasVideoAvatar?: boolean;
avatarPhotoId?: string; avatarPhotoId?: string;
profilePhotos?: ApiPeerPhotos;
botPlaceholder?: string; botPlaceholder?: string;
canBeInvitedToGroup?: boolean; canBeInvitedToGroup?: boolean;
commonChats?: {
ids: string[];
maxId: string;
isFullyLoaded: boolean;
};
fakeType?: ApiFakeType; fakeType?: ApiFakeType;
isAttachBot?: boolean; isAttachBot?: boolean;
emojiStatus?: ApiEmojiStatus; emojiStatus?: ApiEmojiStatus;
@ -82,6 +75,12 @@ export interface ApiUserStatus {
isReadDateRestricted?: boolean; isReadDateRestricted?: boolean;
} }
export interface ApiUserCommonChats {
ids: string[];
maxId?: string;
isFullyLoaded: boolean;
}
export interface ApiUsername { export interface ApiUsername {
username: string; username: string;
isActive?: boolean; isActive?: boolean;

View File

@ -3,7 +3,7 @@ import React, { memo, useEffect, useState } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { import type {
ApiChat, ApiSticker, ApiTopic, ApiUser, ApiUserStatus, ApiChat, ApiPeerPhotos, ApiSticker, ApiTopic, ApiUser, ApiUserStatus,
} from '../../api/types'; } from '../../api/types';
import { MediaViewerOrigin } from '../../types'; import { MediaViewerOrigin } from '../../types';
@ -13,6 +13,7 @@ import {
import { import {
selectChat, selectChat,
selectCurrentMessageList, selectCurrentMessageList,
selectPeerPhotos,
selectTabState, selectTabState,
selectThreadMessagesCount, selectThreadMessagesCount,
selectUser, selectUser,
@ -54,6 +55,7 @@ type StateProps =
topic?: ApiTopic; topic?: ApiTopic;
messagesCount?: number; messagesCount?: number;
emojiStatusSticker?: ApiSticker; emojiStatusSticker?: ApiSticker;
profilePhotos?: ApiPeerPhotos;
}; };
const EMOJI_STATUS_SIZE = 24; const EMOJI_STATUS_SIZE = 24;
@ -72,6 +74,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
topic, topic,
messagesCount, messagesCount,
emojiStatusSticker, emojiStatusSticker,
profilePhotos,
peerId, peerId,
}) => { }) => {
const { const {
@ -84,9 +87,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
const lang = useOldLang(); const lang = useOldLang();
const userProfilePhotos = user?.profilePhotos; const photos = profilePhotos?.photos || MEMO_EMPTY_ARRAY;
const chatProfilePhotos = chat?.profilePhotos;
const photos = userProfilePhotos?.photos || chatProfilePhotos?.photos || MEMO_EMPTY_ARRAY;
const prevMediaIndex = usePreviousDeprecated(mediaIndex); const prevMediaIndex = usePreviousDeprecated(mediaIndex);
const prevAvatarOwnerId = usePreviousDeprecated(avatarOwnerId); const prevAvatarOwnerId = usePreviousDeprecated(avatarOwnerId);
const [hasSlideAnimation, setHasSlideAnimation] = useState(true); const [hasSlideAnimation, setHasSlideAnimation] = useState(true);
@ -213,7 +214,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
} }
function renderPhotoTabs() { function renderPhotoTabs() {
const totalPhotosLength = Math.max(photos.length, userProfilePhotos?.count || 0, chatProfilePhotos?.count || 0); const totalPhotosLength = Math.max(photos.length, profilePhotos?.count || 0);
if (!photos || totalPhotosLength <= 1) { if (!photos || totalPhotosLength <= 1) {
return undefined; return undefined;
} }
@ -293,18 +294,18 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
> >
<div className={styles.photoWrapper}> <div className={styles.photoWrapper}>
{renderPhotoTabs()} {renderPhotoTabs()}
{!forceShowSelf && userProfilePhotos?.personalPhoto && ( {!forceShowSelf && profilePhotos?.personalPhoto && (
<div className={buildClassName( <div className={buildClassName(
styles.fallbackPhoto, styles.fallbackPhoto,
isFirst && styles.fallbackPhotoVisible, isFirst && styles.fallbackPhotoVisible,
)} )}
> >
<div className={styles.fallbackPhotoContents}> <div className={styles.fallbackPhotoContents}>
{lang(userProfilePhotos.personalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')} {lang(profilePhotos.personalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')}
</div> </div>
</div> </div>
)} )}
{forceShowSelf && userProfilePhotos?.fallbackPhoto && ( {forceShowSelf && profilePhotos?.fallbackPhoto && (
<div className={buildClassName( <div className={buildClassName(
styles.fallbackPhoto, styles.fallbackPhoto,
(isFirst || isLast) && styles.fallbackPhotoVisible, (isFirst || isLast) && styles.fallbackPhotoVisible,
@ -313,12 +314,12 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
<div className={styles.fallbackPhotoContents} onClick={handleSelectFallbackPhoto}> <div className={styles.fallbackPhotoContents} onClick={handleSelectFallbackPhoto}>
{!isLast && ( {!isLast && (
<Avatar <Avatar
photo={userProfilePhotos.fallbackPhoto} photo={profilePhotos.fallbackPhoto}
className={styles.fallbackPhotoAvatar} className={styles.fallbackPhotoAvatar}
size="mini" size="mini"
/> />
)} )}
{lang(userProfilePhotos.fallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')} {lang(profilePhotos.fallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
</div> </div>
</div> </div>
)} )}
@ -368,6 +369,7 @@ export default memo(withGlobal<OwnProps>(
const user = selectUser(global, peerId); const user = selectUser(global, peerId);
const userStatus = selectUserStatus(global, peerId); const userStatus = selectUserStatus(global, peerId);
const chat = selectChat(global, peerId); const chat = selectChat(global, peerId);
const profilePhotos = selectPeerPhotos(global, peerId);
const { mediaIndex, chatId: avatarOwnerId } = selectTabState(global).mediaViewer; const { mediaIndex, chatId: avatarOwnerId } = selectTabState(global).mediaViewer;
const isForum = chat?.isForum; const isForum = chat?.isForum;
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {}; const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
@ -383,6 +385,7 @@ export default memo(withGlobal<OwnProps>(
mediaIndex, mediaIndex,
avatarOwnerId, avatarOwnerId,
emojiStatusSticker, emojiStatusSticker,
profilePhotos,
...(topic && { ...(topic && {
topic, topic,
messagesCount: selectThreadMessagesCount(global, peerId, currentTopicId!), messagesCount: selectThreadMessagesCount(global, peerId, currentTopicId!),

View File

@ -6,7 +6,11 @@ import { getActions, withGlobal } from '../../global';
import type { import type {
ApiChat, ApiChat,
ApiMessage, ApiPeer, ApiPhoto, ApiSponsoredMessage, ApiMessage,
ApiPeer,
ApiPeerPhotos,
ApiPhoto,
ApiSponsoredMessage,
} from '../../api/types'; } from '../../api/types';
import { type MediaViewerMedia, MediaViewerOrigin, type ThreadId } from '../../types'; import { type MediaViewerMedia, MediaViewerOrigin, type ThreadId } from '../../types';
@ -25,6 +29,7 @@ import {
selectListedIds, selectListedIds,
selectOutlyingListByMessageId, selectOutlyingListByMessageId,
selectPeer, selectPeer,
selectPeerPhotos,
selectPerformanceSettingsValue, selectPerformanceSettingsValue,
selectScheduledMessage, selectSponsoredMessage, selectScheduledMessage, selectSponsoredMessage,
selectTabState, selectTabState,
@ -69,6 +74,7 @@ type StateProps = {
origin?: MediaViewerOrigin; origin?: MediaViewerOrigin;
avatar?: ApiPhoto; avatar?: ApiPhoto;
avatarOwner?: ApiPeer; avatarOwner?: ApiPeer;
profilePhotos?: ApiPeerPhotos;
chatMessages?: Record<number, ApiMessage>; chatMessages?: Record<number, ApiMessage>;
sponsoredMessage?: ApiSponsoredMessage; sponsoredMessage?: ApiSponsoredMessage;
standaloneMedia?: MediaViewerMedia[]; standaloneMedia?: MediaViewerMedia[];
@ -95,6 +101,7 @@ const MediaViewer = ({
origin, origin,
avatar, avatar,
avatarOwner, avatarOwner,
profilePhotos,
chatMessages, chatMessages,
sponsoredMessage, sponsoredMessage,
standaloneMedia, standaloneMedia,
@ -132,7 +139,7 @@ const MediaViewer = ({
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag(); const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
const currentItem = getMediaViewerItem({ const currentItem = getMediaViewerItem({
message, avatarOwner, standaloneMedia, mediaIndex, sponsoredMessage, message, avatarOwner, standaloneMedia, profilePhotos, mediaIndex, sponsoredMessage,
}); });
const { media, isSingle } = getViewableMedia(currentItem) || {}; const { media, isSingle } = getViewableMedia(currentItem) || {};
@ -275,7 +282,7 @@ const MediaViewer = ({
if (!item || isLoadingMoreMedia) return; if (!item || isLoadingMoreMedia) return;
if (item.type === 'avatar') { if (item.type === 'avatar') {
const isNearEnd = item.mediaIndex >= item.avatarOwner.profilePhotos!.photos.length - AVATAR_LOAD_TRIGGER; const isNearEnd = item.mediaIndex >= item.profilePhotos.photos.length - AVATAR_LOAD_TRIGGER;
if (!isNearEnd) return; if (!isNearEnd) return;
loadMoreProfilePhotos({ peerId: item.avatarOwner.id }); loadMoreProfilePhotos({ peerId: item.avatarOwner.id });
} }
@ -299,10 +306,15 @@ const MediaViewer = ({
} }
if (from.type === 'avatar') { if (from.type === 'avatar') {
const { avatarOwner: fromAvatarOwner, mediaIndex: fromMediaIndex } = from; const { avatarOwner: fromAvatarOwner, profilePhotos: fromProfilePhotos, mediaIndex: fromMediaIndex } = from;
const nextIndex = fromMediaIndex + direction; const nextIndex = fromMediaIndex + direction;
if (nextIndex >= 0 && fromAvatarOwner.profilePhotos && nextIndex < fromAvatarOwner.profilePhotos.photos.length) { if (nextIndex >= 0 && fromProfilePhotos && nextIndex < fromProfilePhotos.photos.length) {
return { type: 'avatar', avatarOwner: fromAvatarOwner, mediaIndex: nextIndex }; return {
type: 'avatar',
avatarOwner: fromAvatarOwner,
profilePhotos: fromProfilePhotos,
mediaIndex: nextIndex,
};
} }
return undefined; return undefined;
@ -367,7 +379,7 @@ const MediaViewer = ({
}); });
const handleBeforeDelete = useLastCallback(() => { const handleBeforeDelete = useLastCallback(() => {
const mediaCount = avatarOwner?.profilePhotos?.photos.length const mediaCount = profilePhotos?.photos.length
|| standaloneMedia?.length || messageMediaIds?.length || 0; || standaloneMedia?.length || messageMediaIds?.length || 0;
if (mediaCount <= 1 || !currentItem) { if (mediaCount <= 1 || !currentItem) {
handleClose(); handleClose();
@ -489,9 +501,10 @@ export default memo(withGlobal(
canUpdateMedia = isUserId(peer.id) ? peer.id === currentUserId : isChatAdmin(peer as ApiChat); canUpdateMedia = isUserId(peer.id) ? peer.id === currentUserId : isChatAdmin(peer as ApiChat);
} }
const profilePhotos = peer?.profilePhotos; const profilePhotos = selectPeerPhotos(global, chatId!);
return { return {
profilePhotos,
avatar: profilePhotos?.photos[mediaIndex!], avatar: profilePhotos?.photos[mediaIndex!],
avatarOwner: peer, avatarOwner: peer,
isLoadingMoreMedia: profilePhotos?.isLoading, isLoadingMoreMedia: profilePhotos?.isLoading,

View File

@ -128,8 +128,8 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
const handleUpdate = useLastCallback(() => { const handleUpdate = useLastCallback(() => {
if (item?.type !== 'avatar') return; if (item?.type !== 'avatar') return;
const { avatarOwner, mediaIndex } = item; const { avatarOwner, profilePhotos, mediaIndex } = item;
const avatarPhoto = avatarOwner.profilePhotos?.photos[mediaIndex]!; const avatarPhoto = profilePhotos?.photos[mediaIndex]!;
if (isUserId(avatarOwner.id)) { if (isUserId(avatarOwner.id)) {
updateProfilePhoto({ photo: avatarPhoto }); updateProfilePhoto({ photo: avatarPhoto });
} else { } else {
@ -170,7 +170,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
onClose={closeDeleteModal} onClose={closeDeleteModal}
onConfirm={onBeforeDelete} onConfirm={onBeforeDelete}
profileId={item.avatarOwner.id} profileId={item.avatarOwner.id}
photo={item.avatarOwner.profilePhotos!.photos[item.mediaIndex!]} photo={item.profilePhotos.photos[item.mediaIndex!]}
/> />
) : undefined; ) : undefined;
} }
@ -390,7 +390,7 @@ export default memo(withGlobal<OwnProps>(
const message = item?.type === 'message' ? item.message : undefined; const message = item?.type === 'message' ? item.message : undefined;
const avatarOwner = item?.type === 'avatar' ? item.avatarOwner : undefined; const avatarOwner = item?.type === 'avatar' ? item.avatarOwner : undefined;
const avatarPhoto = avatarOwner?.profilePhotos?.photos[item!.mediaIndex!]; const avatarPhoto = item?.type === 'avatar' && item.profilePhotos.photos[item.mediaIndex];
const currentMessageList = selectCurrentMessageList(global); const currentMessageList = selectCurrentMessageList(global);
const { threadId } = selectCurrentMessageList(global) || {}; const { threadId } = selectCurrentMessageList(global) || {};

View File

@ -68,14 +68,15 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
if (!item || item.type === 'standalone') return undefined; if (!item || item.type === 'standalone') return undefined;
const avatarOwner = item.type === 'avatar' ? item.avatarOwner : undefined; const avatarOwner = item.type === 'avatar' ? item.avatarOwner : undefined;
const avatar = avatarOwner?.profilePhotos?.photos[item.mediaIndex!]; const profilePhotos = item.type === 'avatar' ? item.profilePhotos : undefined;
const isFallbackAvatar = avatar?.id === avatarOwner?.profilePhotos?.fallbackPhoto?.id; const avatar = profilePhotos?.photos[item.mediaIndex!];
const isFallbackAvatar = avatar?.id === profilePhotos?.fallbackPhoto?.id;
const date = item.type === 'message' ? item.message.date : avatar?.date; const date = item.type === 'message' ? item.message.date : avatar?.date;
if (!date) return undefined; if (!date) return undefined;
const formattedDate = formatMediaDateTime(lang, date * 1000, true); const formattedDate = formatMediaDateTime(lang, date * 1000, true);
const count = avatarOwner?.profilePhotos?.count const count = profilePhotos?.count
&& (avatarOwner.profilePhotos.count + (avatarOwner?.profilePhotos?.fallbackPhoto ? 1 : 0)); && (profilePhotos.count + (profilePhotos?.fallbackPhoto ? 1 : 0));
const countText = count && lang('Of', [item.mediaIndex! + 1, count]); const countText = count && lang('Of', [item.mediaIndex! + 1, count]);
const parts: string[] = []; const parts: string[] = [];

View File

@ -1,4 +1,6 @@
import type { ApiMessage, ApiPeer, ApiSponsoredMessage } from '../../../api/types'; import type {
ApiMessage, ApiPeer, ApiPeerPhotos, ApiSponsoredMessage,
} from '../../../api/types';
import type { MediaViewerMedia } from '../../../types'; import type { MediaViewerMedia } from '../../../types';
import { getMessageContent, isDocumentPhoto, isDocumentVideo } from '../../../global/helpers'; import { getMessageContent, isDocumentPhoto, isDocumentVideo } from '../../../global/helpers';
@ -10,6 +12,7 @@ export type MediaViewerItem = {
} | { } | {
type: 'avatar'; type: 'avatar';
avatarOwner: ApiPeer; avatarOwner: ApiPeer;
profilePhotos: ApiPeerPhotos;
mediaIndex: number; mediaIndex: number;
} | { } | {
type: 'standalone'; type: 'standalone';
@ -27,18 +30,20 @@ type ViewableMedia = {
}; };
export function getMediaViewerItem({ export function getMediaViewerItem({
message, avatarOwner, standaloneMedia, mediaIndex, sponsoredMessage, message, avatarOwner, profilePhotos, standaloneMedia, mediaIndex, sponsoredMessage,
}: { }: {
message?: ApiMessage; message?: ApiMessage;
avatarOwner?: ApiPeer; avatarOwner?: ApiPeer;
profilePhotos?: ApiPeerPhotos;
standaloneMedia?: MediaViewerMedia[]; standaloneMedia?: MediaViewerMedia[];
sponsoredMessage?: ApiSponsoredMessage; sponsoredMessage?: ApiSponsoredMessage;
mediaIndex?: number; mediaIndex?: number;
}): MediaViewerItem | undefined { }): MediaViewerItem | undefined {
if (avatarOwner) { if (avatarOwner && profilePhotos) {
return { return {
type: 'avatar', type: 'avatar',
avatarOwner, avatarOwner,
profilePhotos,
mediaIndex: mediaIndex!, mediaIndex: mediaIndex!,
}; };
} }
@ -81,7 +86,7 @@ export default function getViewableMedia(params?: MediaViewerItem): ViewableMedi
} }
if (params.type === 'avatar') { if (params.type === 'avatar') {
const avatar = params.avatarOwner.profilePhotos?.photos[params.mediaIndex]; const avatar = params.profilePhotos?.photos[params.mediaIndex];
if (avatar) { if (avatar) {
return { return {
media: avatar, media: avatar,

View File

@ -37,7 +37,6 @@ import {
isChatChannel, isChatChannel,
isChatGroup, isChatGroup,
isUserBot, isUserBot,
isUserId,
isUserRightBanned, isUserRightBanned,
} from '../../global/helpers'; } from '../../global/helpers';
import { import {
@ -48,12 +47,12 @@ import {
selectCurrentSharedMediaSearch, selectCurrentSharedMediaSearch,
selectIsCurrentUserPremium, selectIsCurrentUserPremium,
selectIsRightColumnShown, selectIsRightColumnShown,
selectPeerFullInfo,
selectPeerStories, selectPeerStories,
selectSimilarChannelIds, selectSimilarChannelIds,
selectTabState, selectTabState,
selectTheme, selectTheme,
selectUser, selectUser,
selectUserCommonChats,
selectUserFullInfo, selectUserFullInfo,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectPremiumLimit } from '../../global/selectors/limits'; import { selectPremiumLimit } from '../../global/selectors/limits';
@ -110,7 +109,6 @@ type StateProps = {
theme: ISettings['theme']; theme: ISettings['theme'];
isChannel?: boolean; isChannel?: boolean;
currentUserId?: string; currentUserId?: string;
resolvedUserId?: string;
messagesById?: Record<number, ApiMessage>; messagesById?: Record<number, ApiMessage>;
foundIds?: number[]; foundIds?: number[];
mediaSearchType?: SharedMediaType; mediaSearchType?: SharedMediaType;
@ -166,10 +164,8 @@ const Profile: FC<OwnProps & StateProps> = ({
chatId, chatId,
threadId, threadId,
profileState, profileState,
onProfileStateChange,
theme, theme,
isChannel, isChannel,
resolvedUserId,
currentUserId, currentUserId,
messagesById, messagesById,
foundIds, foundIds,
@ -205,6 +201,7 @@ const Profile: FC<OwnProps & StateProps> = ({
isSavedDialog, isSavedDialog,
forceScrollProfileTab, forceScrollProfileTab,
isSynced, isSynced,
onProfileStateChange,
}) => { }) => {
const { const {
setSharedMediaSearchType, setSharedMediaSearchType,
@ -230,7 +227,7 @@ const Profile: FC<OwnProps & StateProps> = ({
const lang = useOldLang(); const lang = useOldLang();
const [deletingUserId, setDeletingUserId] = useState<string | undefined>(); const [deletingUserId, setDeletingUserId] = useState<string | undefined>();
const profileId = isSavedDialog ? String(threadId) : (resolvedUserId || chatId); const profileId = isSavedDialog ? String(threadId) : chatId;
const isSavedMessages = profileId === currentUserId && !isSavedDialog; const isSavedMessages = profileId === currentUserId && !isSavedDialog;
const tabs = useMemo(() => ([ const tabs = useMemo(() => ([
@ -303,6 +300,9 @@ const Profile: FC<OwnProps & StateProps> = ({
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab; const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab;
const tabType = tabs[renderingActiveTab].type as ProfileTabType; const tabType = tabs[renderingActiveTab].type as ProfileTabType;
const handleLoadCommonChats = useCallback(() => {
loadCommonChats({ userId: chatId });
}, [chatId]);
const handleLoadPeerStories = useCallback(({ offsetId }: { offsetId: number }) => { const handleLoadPeerStories = useCallback(({ offsetId }: { offsetId: number }) => {
loadPeerProfileStories({ peerId: chatId, offsetId }); loadPeerProfileStories({ peerId: chatId, offsetId });
}, [chatId]); }, [chatId]);
@ -312,7 +312,7 @@ const Profile: FC<OwnProps & StateProps> = ({
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds( const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds(
loadMoreMembers, loadMoreMembers,
loadCommonChats, handleLoadCommonChats,
searchSharedMediaMessages, searchSharedMediaMessages,
handleLoadPeerStories, handleLoadPeerStories,
handleLoadStoriesArchive, handleLoadStoriesArchive,
@ -746,9 +746,12 @@ export default memo(withGlobal<OwnProps>(
(global, { (global, {
chatId, threadId, isMobile, chatId, threadId, isMobile,
}): StateProps => { }): StateProps => {
const user = selectUser(global, chatId);
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
const chatFullInfo = selectChatFullInfo(global, chatId); const chatFullInfo = selectChatFullInfo(global, chatId);
const userFullInfo = selectUserFullInfo(global, chatId);
const messagesById = selectChatMessages(global, chatId); const messagesById = selectChatMessages(global, chatId);
const { currentType: mediaSearchType, resultsByType } = selectCurrentSharedMediaSearch(global) || {}; const { currentType: mediaSearchType, resultsByType } = selectCurrentSharedMediaSearch(global) || {};
const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {}; const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {};
@ -774,19 +777,13 @@ export default memo(withGlobal<OwnProps>(
const { similarChannelIds } = selectSimilarChannelIds(global, chatId) || {}; const { similarChannelIds } = selectSimilarChannelIds(global, chatId) || {};
const isCurrentUserPremium = selectIsCurrentUserPremium(global); const isCurrentUserPremium = selectIsCurrentUserPremium(global);
let hasCommonChatsTab;
let resolvedUserId;
let user;
if (isUserId(chatId)) {
resolvedUserId = chatId;
user = selectUser(global, resolvedUserId);
hasCommonChatsTab = user && !user.isSelf && !isUserBot(user) && !isSavedDialog;
}
const peer = user || chat; const peer = user || chat;
const peerFullInfo = selectPeerFullInfo(global, chatId); const peerFullInfo = userFullInfo || chatFullInfo;
const hasCommonChatsTab = user && !user.isSelf && !isUserBot(user) && !isSavedDialog
&& Boolean(userFullInfo?.commonChatsCount);
const commonChats = selectUserCommonChats(global, chatId);
const userFullInfo = selectUserFullInfo(global, chatId);
const hasPreviewMediaTab = userFullInfo?.botInfo?.hasPreviewMedia; const hasPreviewMediaTab = userFullInfo?.botInfo?.hasPreviewMedia;
const botPreviewMedia = global.users.previewMediaByBotId[chatId]; const botPreviewMedia = global.users.previewMediaByBotId[chatId];
@ -801,7 +798,6 @@ export default memo(withGlobal<OwnProps>(
return { return {
theme: selectTheme(global), theme: selectTheme(global),
isChannel, isChannel,
resolvedUserId,
messagesById, messagesById,
foundIds, foundIds,
mediaSearchType, mediaSearchType,
@ -835,7 +831,7 @@ export default memo(withGlobal<OwnProps>(
isSynced: global.isSynced, isSynced: global.isSynced,
limitSimilarChannels: selectPremiumLimit(global, 'recommendedChannels'), limitSimilarChannels: selectPremiumLimit(global, 'recommendedChannels'),
...(hasMembersTab && members && { members, adminMembersById }), ...(hasMembersTab && members && { members, adminMembersById }),
...(hasCommonChatsTab && user && { commonChatIds: user.commonChats?.ids }), ...(hasCommonChatsTab && user && { commonChatIds: commonChats?.ids }),
}; };
}, },
)(Profile)); )(Profile));

View File

@ -86,7 +86,6 @@ export const PINNED_MESSAGES_LIMIT = 50;
export const BLOCKED_LIST_LIMIT = 100; export const BLOCKED_LIST_LIMIT = 100;
export const PROFILE_SENSITIVE_AREA = 500; export const PROFILE_SENSITIVE_AREA = 500;
export const TOPIC_LIST_SENSITIVE_AREA = 600; export const TOPIC_LIST_SENSITIVE_AREA = 600;
export const COMMON_CHATS_LIMIT = 100;
export const GROUP_CALL_PARTICIPANTS_LIMIT = 100; export const GROUP_CALL_PARTICIPANTS_LIMIT = 100;
export const STORY_LIST_LIMIT = 100; export const STORY_LIST_LIMIT = 100;
export const API_GENERAL_ID_LIMIT = 100; export const API_GENERAL_ID_LIMIT = 100;

View File

@ -23,6 +23,7 @@ import {
updatePeerPhotos, updatePeerPhotos,
updatePeerPhotosIsLoading, updatePeerPhotosIsLoading,
updateUser, updateUser,
updateUserCommonChats,
updateUserFullInfo, updateUserFullInfo,
updateUsers, updateUsers,
updateUserSearch, updateUserSearch,
@ -31,10 +32,11 @@ import {
import { import {
selectChat, selectChat,
selectChatFullInfo, selectChatFullInfo,
selectCurrentMessageList,
selectPeer, selectPeer,
selectPeerPhotos,
selectTabState, selectTabState,
selectUser, selectUser,
selectUserCommonChats,
selectUserFullInfo, selectUserFullInfo,
} from '../../selectors'; } from '../../selectors';
@ -56,6 +58,7 @@ addActionHandler('loadFullUser', async (global, actions, payload): Promise<void>
global = getGlobal(); global = getGlobal();
const fullInfo = selectUserFullInfo(global, userId); const fullInfo = selectUserFullInfo(global, userId);
const { user: newUser, fullInfo: newFullInfo } = result; const { user: newUser, fullInfo: newFullInfo } = result;
const profilePhotos = selectPeerPhotos(global, userId);
const hasChangedAvatar = user.avatarPhotoId !== newUser.avatarPhotoId; const hasChangedAvatar = user.avatarPhotoId !== newUser.avatarPhotoId;
const hasChangedProfilePhoto = fullInfo?.profilePhoto?.id !== newFullInfo?.profilePhoto?.id; const hasChangedProfilePhoto = fullInfo?.profilePhoto?.id !== newFullInfo?.profilePhoto?.id;
const hasChangedFallbackPhoto = fullInfo?.fallbackPhoto?.id !== newFullInfo?.fallbackPhoto?.id; const hasChangedFallbackPhoto = fullInfo?.fallbackPhoto?.id !== newFullInfo?.fallbackPhoto?.id;
@ -71,7 +74,7 @@ addActionHandler('loadFullUser', async (global, actions, payload): Promise<void>
global = updateChats(global, buildCollectionByKey(result.chats, 'id')); global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
setGlobal(global); setGlobal(global);
if (withPhotos || (user.profilePhotos?.count && hasChangedPhoto)) { if (withPhotos || (profilePhotos?.count && hasChangedPhoto)) {
actions.loadMoreProfilePhotos({ peerId: userId, shouldInvalidateCache: true }); actions.loadMoreProfilePhotos({ peerId: userId, shouldInvalidateCache: true });
} }
}); });
@ -156,28 +159,27 @@ addActionHandler('loadCurrentUser', (): ActionReturnType => {
}); });
addActionHandler('loadCommonChats', async (global, actions, payload): Promise<void> => { addActionHandler('loadCommonChats', async (global, actions, payload): Promise<void> => {
const { tabId = getCurrentTabId() } = payload || {}; const { userId } = payload;
const { chatId } = selectCurrentMessageList(global, tabId) || {}; const user = selectUser(global, userId);
const user = chatId ? selectUser(global, chatId) : undefined; const commonChats = selectUserCommonChats(global, userId);
if (!user || isUserBot(user) || user.commonChats?.isFullyLoaded) { if (!user || isUserBot(user) || commonChats?.isFullyLoaded) {
return; return;
} }
const maxId = user.commonChats?.maxId; const result = await callApi('fetchCommonChats', user, commonChats?.maxId);
const result = await callApi('fetchCommonChats', user.id, user.accessHash!, maxId);
if (!result) { if (!result) {
return; return;
} }
const { chatIds, isFullyLoaded } = result; const { chatIds, count } = result;
const ids = unique((commonChats?.ids || []).concat(chatIds));
global = getGlobal(); global = getGlobal();
global = updateUser(global, user.id, { global = updateUserCommonChats(global, user.id, {
commonChats: { maxId: chatIds.length ? chatIds[chatIds.length - 1] : undefined,
maxId: chatIds.length ? chatIds[chatIds.length - 1] : '0', ids,
ids: unique((user.commonChats?.ids || []).concat(chatIds)), isFullyLoaded: ids.length >= count,
isFullyLoaded,
},
}); });
setGlobal(global); setGlobal(global);
@ -258,11 +260,12 @@ addActionHandler('loadMoreProfilePhotos', async (global, actions, payload): Prom
const user = isPrivate ? selectUser(global, peerId) : undefined; const user = isPrivate ? selectUser(global, peerId) : undefined;
const chat = !isPrivate ? selectChat(global, peerId) : undefined; const chat = !isPrivate ? selectChat(global, peerId) : undefined;
const peer = user || chat; const peer = user || chat;
const profilePhotos = selectPeerPhotos(global, peerId);
if (!peer?.avatarPhotoId) { if (!peer?.avatarPhotoId) {
return; return;
} }
if (peer.profilePhotos && !shouldInvalidateCache && (isPreload || !peer.profilePhotos.nextOffset)) return; if (profilePhotos && !shouldInvalidateCache && (isPreload || !profilePhotos.nextOffset)) return;
global = updatePeerPhotosIsLoading(global, peerId, true); global = updatePeerPhotosIsLoading(global, peerId, true);
setGlobal(global); setGlobal(global);
@ -292,7 +295,7 @@ addActionHandler('loadMoreProfilePhotos', async (global, actions, payload): Prom
const peerFullInfo = userFullInfo || chatFullInfo; const peerFullInfo = userFullInfo || chatFullInfo;
if (!peerFullInfo) return; if (!peerFullInfo) return;
const offset = peer.profilePhotos?.nextOffset; const offset = profilePhotos?.nextOffset;
const limit = !offset || isPreload || shouldInvalidateCache ? PROFILE_PHOTOS_FIRST_LOAD_LIMIT : undefined; const limit = !offset || isPreload || shouldInvalidateCache ? PROFILE_PHOTOS_FIRST_LOAD_LIMIT : undefined;
const result = await callApi('fetchProfilePhotos', { const result = await callApi('fetchProfilePhotos', {

View File

@ -16,6 +16,7 @@ import {
deletePeerPhoto, deletePeerPhoto,
leaveChat, leaveChat,
removeUnreadMentions, removeUnreadMentions,
replacePeerPhotos,
replaceThreadParam, replaceThreadParam,
updateChat, updateChat,
updateChatFullInfo, updateChatFullInfo,
@ -548,8 +549,8 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
if (!photoId || peer.avatarPhotoId === photoId) { if (!photoId || peer.avatarPhotoId === photoId) {
global = updateChat(global, peerId, { global = updateChat(global, peerId, {
avatarPhotoId: undefined, avatarPhotoId: undefined,
profilePhotos: undefined,
}); });
global = replacePeerPhotos(global, peerId, undefined);
} else { } else {
global = deletePeerPhoto(global, peerId, photoId); global = deletePeerPhoto(global, peerId, photoId);
} }

View File

@ -245,6 +245,9 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
if (!cached.topBotApps) { if (!cached.topBotApps) {
cached.topBotApps = initialState.topBotApps; cached.topBotApps = initialState.topBotApps;
} }
if (!cached.users.commonChatsById) {
cached.users.commonChatsById = initialState.users.commonChatsById;
}
} }
function updateCache(force?: boolean) { function updateCache(force?: boolean) {
@ -390,10 +393,10 @@ function reduceUsers<T extends GlobalState>(global: T): GlobalState['users'] {
]).slice(0, GLOBAL_STATE_CACHE_USER_LIST_LIMIT); ]).slice(0, GLOBAL_STATE_CACHE_USER_LIST_LIMIT);
return { return {
...INITIAL_GLOBAL_STATE.users,
byId: pickTruthy(byId, idsToSave), byId: pickTruthy(byId, idsToSave),
statusesById: pickTruthy(statusesById, idsToSave), statusesById: pickTruthy(statusesById, idsToSave),
fullInfoById: pickTruthy(fullInfoById, idsToSave), fullInfoById: pickTruthy(fullInfoById, idsToSave),
previewMediaByBotId: {},
}; };
} }

View File

@ -98,6 +98,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
statusesById: {}, statusesById: {},
fullInfoById: {}, fullInfoById: {},
previewMediaByBotId: {}, previewMediaByBotId: {},
commonChatsById: {},
}, },
chats: { chats: {
@ -298,6 +299,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
isHidden: false, isHidden: false,
}, },
profilePhotosById: {},
monetizationInfo: {}, monetizationInfo: {},
}; };

View File

@ -12,5 +12,5 @@ export * from './payments';
export * from './statistics'; export * from './statistics';
export * from './stories'; export * from './stories';
export * from './translations'; export * from './translations';
export * from './peers';
export * from './password'; export * from './password';
export * from './general';

View File

@ -1,11 +1,16 @@
import type { import type {
ApiChat, ApiChatFullInfo, ApiPhoto, ApiUser, ApiUserFullInfo, ApiChat, ApiChatFullInfo, ApiPeerPhotos, ApiPhoto, ApiUser, ApiUserFullInfo,
} from '../../api/types'; } from '../../api/types';
import type { GlobalState } from '../types'; import type { GlobalState } from '../types';
import { uniqueByField } from '../../util/iteratees'; import { omit, uniqueByField } from '../../util/iteratees';
import { isChatChannel, isUserId } from '../helpers'; import { isChatChannel, isUserId } from '../helpers';
import { selectChatFullInfo, selectPeer, selectUserFullInfo } from '../selectors'; import {
selectChatFullInfo,
selectPeer,
selectPeerPhotos,
selectUserFullInfo,
} from '../selectors';
import { updateChat, updateChatFullInfo } from './chats'; import { updateChat, updateChatFullInfo } from './chats';
import { updateUser, updateUserFullInfo } from './users'; import { updateUser, updateUserFullInfo } from './users';
@ -38,19 +43,38 @@ export function updatePeerPhotosIsLoading<T extends GlobalState>(
peerId: string, peerId: string,
isLoading: boolean, isLoading: boolean,
) { ) {
const peer = selectPeer(global, peerId); const profilePhotos = selectPeerPhotos(global, peerId);
if (!peer || !peer.profilePhotos) { if (!profilePhotos) {
return global; return global;
} }
return updatePeer(global, peerId, { return replacePeerPhotos(global, peerId, {
profilePhotos: { ...profilePhotos,
...peer.profilePhotos, isLoading,
isLoading,
},
}); });
} }
export function replacePeerPhotos<T extends GlobalState>(
global: T,
peerId: string,
value?: ApiPeerPhotos,
) {
if (!value) {
return {
...global,
profilePhotosById: omit(global.profilePhotosById, [peerId]),
};
}
return {
...global,
profilePhotosById: {
...global.profilePhotosById,
[peerId]: value,
},
};
}
export function updatePeerPhotos<T extends GlobalState>( export function updatePeerPhotos<T extends GlobalState>(
global: T, global: T,
peerId: string, peerId: string,
@ -62,15 +86,12 @@ export function updatePeerPhotos<T extends GlobalState>(
shouldInvalidateCache?: boolean; shouldInvalidateCache?: boolean;
}, },
) { ) {
const peer = selectPeer(global, peerId); const profilePhotos = selectPeerPhotos(global, peerId);
if (!peer) {
return global;
}
const { const {
newPhotos, count, nextOffset, fullInfo, shouldInvalidateCache, newPhotos, count, nextOffset, fullInfo, shouldInvalidateCache,
} = update; } = update;
const currentPhotos = peer.profilePhotos; const currentPhotos = profilePhotos;
const profilePhoto = fullInfo.profilePhoto; const profilePhoto = fullInfo.profilePhoto;
const fallbackPhoto = 'fallbackPhoto' in fullInfo ? fullInfo.fallbackPhoto : undefined; const fallbackPhoto = 'fallbackPhoto' in fullInfo ? fullInfo.fallbackPhoto : undefined;
const personalPhoto = 'personalPhoto' in fullInfo ? fullInfo.personalPhoto : undefined; const personalPhoto = 'personalPhoto' in fullInfo ? fullInfo.personalPhoto : undefined;
@ -89,15 +110,13 @@ export function updatePeerPhotos<T extends GlobalState>(
newPhotos.push(fallbackPhoto); newPhotos.push(fallbackPhoto);
} }
return updatePeer(global, peerId, { return replacePeerPhotos(global, peerId, {
profilePhotos: { fallbackPhoto,
fallbackPhoto, personalPhoto,
personalPhoto, photos: newPhotos,
photos: newPhotos, count,
count, nextOffset,
nextOffset, isLoading: false,
isLoading: false,
},
}); });
} }
@ -105,15 +124,13 @@ export function updatePeerPhotos<T extends GlobalState>(
const currentPhotoArray = hasFallbackPhoto ? currentPhotos.photos.slice(0, -1) : currentPhotos.photos; const currentPhotoArray = hasFallbackPhoto ? currentPhotos.photos.slice(0, -1) : currentPhotos.photos;
const photos = uniqueByField([...currentPhotoArray, ...newPhotos, fallbackPhoto].filter(Boolean), 'id'); const photos = uniqueByField([...currentPhotoArray, ...newPhotos, fallbackPhoto].filter(Boolean), 'id');
return updatePeer(global, peerId, { return replacePeerPhotos(global, peerId, {
profilePhotos: { fallbackPhoto,
fallbackPhoto, personalPhoto,
personalPhoto, photos,
photos, count,
count, nextOffset,
nextOffset, isLoading: false,
isLoading: false,
},
}); });
} }
@ -124,7 +141,8 @@ export function deletePeerPhoto<T extends GlobalState>(
isFromActionMessage?: boolean, isFromActionMessage?: boolean,
) { ) {
const peer = selectPeer(global, peerId); const peer = selectPeer(global, peerId);
if (!peer || !peer.profilePhotos) { const profilePhotos = selectPeerPhotos(global, peerId);
if (!peer || !profilePhotos) {
return global; return global;
} }
const isChannel = 'title' in peer && isChatChannel(peer); const isChannel = 'title' in peer && isChatChannel(peer);
@ -133,7 +151,7 @@ export function deletePeerPhoto<T extends GlobalState>(
const chatFullInfo = selectChatFullInfo(global, peerId); const chatFullInfo = selectChatFullInfo(global, peerId);
const isAvatar = peer.avatarPhotoId === photoId && (!isChannel || isFromActionMessage); const isAvatar = peer.avatarPhotoId === photoId && (!isChannel || isFromActionMessage);
const nextAvatarPhoto = isAvatar ? peer.profilePhotos.photos[1] : undefined; const nextAvatarPhoto = isAvatar ? profilePhotos.photos[1] : undefined;
if (userFullInfo) { if (userFullInfo) {
const newFallbackPhoto = userFullInfo.fallbackPhoto?.id === photoId ? undefined : userFullInfo.fallbackPhoto; const newFallbackPhoto = userFullInfo.fallbackPhoto?.id === photoId ? undefined : userFullInfo.fallbackPhoto;
@ -156,13 +174,17 @@ export function deletePeerPhoto<T extends GlobalState>(
const avatarPhotoId = isAvatar ? nextAvatarPhoto?.id : peer.avatarPhotoId; const avatarPhotoId = isAvatar ? nextAvatarPhoto?.id : peer.avatarPhotoId;
const shouldKeepInPhotos = isAvatar && 'title' in peer && isChatChannel(peer); const shouldKeepInPhotos = isAvatar && 'title' in peer && isChatChannel(peer);
const photos = shouldKeepInPhotos const photos = shouldKeepInPhotos
? peer.profilePhotos.photos.filter((photo) => photo.id !== photoId) : peer.profilePhotos.photos.slice(); ? profilePhotos.photos.filter((photo) => photo.id !== photoId) : profilePhotos.photos.slice();
return updatePeer(global, peerId, {
global = updatePeer(global, peerId, {
avatarPhotoId, avatarPhotoId,
profilePhotos: avatarPhotoId ? {
...peer.profilePhotos,
photos,
count: peer.profilePhotos.count - 1,
} : undefined,
}); });
global = replacePeerPhotos(global, peerId, avatarPhotoId ? {
...profilePhotos,
photos,
count: profilePhotos.count - 1,
} : undefined);
return global;
} }

View File

@ -19,7 +19,7 @@ import {
selectIsChatWithSelf, selectIsChatWithSelf,
selectPeer, selectPeerStories, selectPeerStory, selectTabState, selectUser, selectPeer, selectPeerStories, selectPeerStory, selectTabState, selectUser,
} from '../selectors'; } from '../selectors';
import { updatePeer } from './general'; import { updatePeer } from './peers';
import { updateTabState } from './tabs'; import { updateTabState } from './tabs';
export function addStories<T extends GlobalState>(global: T, newStoriesByPeerId: Record<string, ApiPeerStories>): T { export function addStories<T extends GlobalState>(global: T, newStoriesByPeerId: Record<string, ApiPeerStories>): T {

View File

@ -1,5 +1,5 @@
import type { import type {
ApiMissingInvitedUser, ApiUser, ApiUserFullInfo, ApiUserStatus, ApiMissingInvitedUser, ApiUser, ApiUserCommonChats, ApiUserFullInfo, ApiUserStatus,
} from '../../api/types'; } from '../../api/types';
import type { GlobalState, TabArgs, TabState } from '../types'; import type { GlobalState, TabArgs, TabState } from '../types';
@ -240,6 +240,21 @@ export function updateUserFullInfo<T extends GlobalState>(
}; };
} }
export function updateUserCommonChats<T extends GlobalState>(
global: T, userId: string, commonChats: ApiUserCommonChats,
): T {
return {
...global,
users: {
...global.users,
commonChatsById: {
...global.users.commonChatsById,
[userId]: commonChats,
},
},
};
}
// @optimization Allows to avoid redundant updates which cause a lot of renders // @optimization Allows to avoid redundant updates which cause a lot of renders
export function addUserStatuses<T extends GlobalState>(global: T, newById: Record<string, ApiUserStatus>): T { export function addUserStatuses<T extends GlobalState>(global: T, newById: Record<string, ApiUserStatus>): T {
const { statusesById } = global.users; const { statusesById } = global.users;

View File

@ -1,5 +1,5 @@
import type { import type {
ApiChat, ApiChatFullInfo, ApiChatType, ApiPeer, ApiChat, ApiChatFullInfo, ApiChatType,
} from '../../api/types'; } from '../../api/types';
import type { ChatListType, GlobalState, TabArgs } from '../types'; import type { ChatListType, GlobalState, TabArgs } from '../types';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
@ -24,10 +24,6 @@ import {
selectBot, selectIsCurrentUserPremium, selectUser, selectUserFullInfo, selectBot, selectIsCurrentUserPremium, selectUser, selectUserFullInfo,
} from './users'; } from './users';
export function selectPeer<T extends GlobalState>(global: T, peerId: string): ApiPeer | undefined {
return selectUser(global, peerId) || selectChat(global, peerId);
}
export function selectChat<T extends GlobalState>(global: T, chatId: string): ApiChat | undefined { export function selectChat<T extends GlobalState>(global: T, chatId: string): ApiChat | undefined {
return global.chats.byId[chatId]; return global.chats.byId[chatId];
} }

View File

@ -11,3 +11,4 @@ export * from './settings';
export * from './statistics'; export * from './statistics';
export * from './stories'; export * from './stories';
export * from './tabs'; export * from './tabs';
export * from './peers';

View File

@ -64,9 +64,9 @@ import {
selectChatFullInfo, selectChatFullInfo,
selectChatLastMessageId, selectChatLastMessageId,
selectIsChatWithSelf, selectIsChatWithSelf,
selectPeer,
selectRequestedChatTranslationLanguage, selectRequestedChatTranslationLanguage,
} from './chats'; } from './chats';
import { selectPeer } from './peers';
import { selectPeerStory } from './stories'; import { selectPeerStory } from './stories';
import { selectIsStickerFavorite } from './symbols'; import { selectIsStickerFavorite } from './symbols';
import { selectTabState } from './tabs'; import { selectTabState } from './tabs';

View File

@ -0,0 +1,13 @@
import type { ApiPeer } from '../../api/types';
import type { GlobalState } from '../types';
import { selectChat } from './chats';
import { selectUser } from './users';
export function selectPeer<T extends GlobalState>(global: T, peerId: string): ApiPeer | undefined {
return selectUser(global, peerId) || selectChat(global, peerId);
}
export function selectPeerPhotos<T extends GlobalState>(global: T, peerId: string) {
return global.profilePhotosById[peerId];
}

View File

@ -2,7 +2,7 @@ import type { ApiPeerStories, ApiTypeStory } from '../../api/types';
import type { GlobalState, TabArgs } from '../types'; import type { GlobalState, TabArgs } from '../types';
import { getCurrentTabId } from '../../util/establishMultitabRole'; import { getCurrentTabId } from '../../util/establishMultitabRole';
import { selectPeer } from './chats'; import { selectPeer } from './peers';
import { selectTabState } from './tabs'; import { selectTabState } from './tabs';
export function selectCurrentViewedStory<T extends GlobalState>( export function selectCurrentViewedStory<T extends GlobalState>(

View File

@ -1,5 +1,5 @@
import type { import type {
ApiUser, ApiUserFullInfo, ApiUserStatus, ApiUser, ApiUserCommonChats, ApiUserFullInfo, ApiUserStatus,
} from '../../api/types'; } from '../../api/types';
import type { GlobalState } from '../types'; import type { GlobalState } from '../types';
@ -17,6 +17,12 @@ export function selectUserFullInfo<T extends GlobalState>(global: T, userId: str
return global.users.fullInfoById[userId]; return global.users.fullInfoById[userId];
} }
export function selectUserCommonChats<T extends GlobalState>(
global: T, userId: string,
): ApiUserCommonChats | undefined {
return global.users.commonChatsById[userId];
}
export function selectIsUserBlocked<T extends GlobalState>(global: T, userId: string) { export function selectIsUserBlocked<T extends GlobalState>(global: T, userId: string) {
return selectUserFullInfo(global, userId)?.isBlocked; return selectUserFullInfo(global, userId)?.isBlocked;
} }

View File

@ -47,6 +47,7 @@ import type {
ApiPaymentFormNativeParams, ApiPaymentFormNativeParams,
ApiPaymentSavedInfo, ApiPaymentSavedInfo,
ApiPeerColors, ApiPeerColors,
ApiPeerPhotos,
ApiPeerStories, ApiPeerStories,
ApiPhoneCall, ApiPhoneCall,
ApiPhoto, ApiPhoto,
@ -80,6 +81,7 @@ import type {
ApiUpdateAuthorizationStateType, ApiUpdateAuthorizationStateType,
ApiUpdateConnectionStateType, ApiUpdateConnectionStateType,
ApiUser, ApiUser,
ApiUserCommonChats,
ApiUserFullInfo, ApiUserFullInfo,
ApiUserStatus, ApiUserStatus,
ApiVideo, ApiVideo,
@ -933,7 +935,9 @@ export type GlobalState = {
// Obtained from GetFullUser / UserFullInfo // Obtained from GetFullUser / UserFullInfo
fullInfoById: Record<string, ApiUserFullInfo>; fullInfoById: Record<string, ApiUserFullInfo>;
previewMediaByBotId: Record<string, ApiBotPreviewMedia[]>; previewMediaByBotId: Record<string, ApiBotPreviewMedia[]>;
commonChatsById: Record<string, ApiUserCommonChats>;
}; };
profilePhotosById: Record<string, ApiPeerPhotos>;
chats: { chats: {
// TODO Replace with `Partial<Record>` to properly handle missing keys // TODO Replace with `Partial<Record>` to properly handle missing keys
@ -2672,7 +2676,9 @@ export interface ActionPayloads {
deleteContact: { userId: string }; deleteContact: { userId: string };
loadUser: { userId: string }; loadUser: { userId: string };
setUserSearchQuery: { query?: string } & WithTabId; setUserSearchQuery: { query?: string } & WithTabId;
loadCommonChats: WithTabId | undefined; loadCommonChats: {
userId: string;
};
reportSpam: { chatId: string }; reportSpam: { chatId: string };
loadFullUser: { userId: string; withPhotos?: boolean }; loadFullUser: { userId: string; withPhotos?: boolean };
openAddContactDialog: { userId?: string } & WithTabId; openAddContactDialog: { userId?: string } & WithTabId;