Right Column: Show your profile (#6279)

This commit is contained in:
zubiden 2025-10-08 12:33:26 +02:00 committed by Alexander Zinchuk
parent a8099e2f34
commit 37a033ca83
19 changed files with 274 additions and 128 deletions

View File

@ -981,6 +981,7 @@
"EventLogFilterEditedMessages" = "Edited messages"; "EventLogFilterEditedMessages" = "Edited messages";
"EventLogFilterLeavingMembers" = "Members leaving"; "EventLogFilterLeavingMembers" = "Members leaving";
"ChannelManagementTitle" = "Admins"; "ChannelManagementTitle" = "Admins";
"MyProfileHeader" = "My Profile";
"EventLogAllAdmins" = "All admins"; "EventLogAllAdmins" = "All admins";
"UserRestrictionsCanDo" = "What can this user do?"; "UserRestrictionsCanDo" = "What can this user do?";
"UserRestrictionsBlock" = "Ban and remove from group"; "UserRestrictionsBlock" = "Ban and remove from group";

View File

@ -68,6 +68,7 @@ import styles from './ChatExtra.module.scss';
type OwnProps = { type OwnProps = {
chatOrUserId: string; chatOrUserId: string;
isOwnProfile?: boolean;
isSavedDialog?: boolean; isSavedDialog?: boolean;
isInSettings?: boolean; isInSettings?: boolean;
className?: string; className?: string;
@ -106,7 +107,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
user, user,
chat, chat,
userFullInfo, userFullInfo,
isInSettings, isOwnProfile,
canInviteUsers, canInviteUsers,
isMuted, isMuted,
phoneCodeList, phoneCodeList,
@ -122,6 +123,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
botVerification, botVerification,
className, className,
style, style,
isInSettings,
}) => { }) => {
const { const {
showNotification, showNotification,
@ -283,7 +285,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
}, { withNodes: true }); }, { withNodes: true });
const isRestricted = chatId ? selectIsChatRestricted(getGlobal(), chatId) : false; const isRestricted = chatId ? selectIsChatRestricted(getGlobal(), chatId) : false;
if (isRestricted || (isSelf && !isInSettings)) { if (isRestricted || (isSelf && !isOwnProfile && !isInSettings)) {
return undefined; return undefined;
} }
@ -422,7 +424,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
</div> </div>
</ListItem> </ListItem>
)} )}
{!isInSettings && ( {!isOwnProfile && !isInSettings && (
<ListItem icon={isMuted ? 'mute' : 'unmute'} narrow ripple onClick={handleToggleNotifications}> <ListItem icon={isMuted ? 'mute' : 'unmute'} narrow ripple onClick={handleToggleNotifications}>
<span>{lang('Notifications')}</span> <span>{lang('Notifications')}</span>
<Switcher <Switcher
@ -449,7 +451,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
<span className="subtitle">{oldLang('BusinessProfileLocation')}</span> <span className="subtitle">{oldLang('BusinessProfileLocation')}</span>
</ListItem> </ListItem>
)} )}
{hasSavedMessages && !isInSettings && ( {hasSavedMessages && !isOwnProfile && !isInSettings && (
<ListItem icon="saved-messages" narrow ripple onClick={handleOpenSavedDialog}> <ListItem icon="saved-messages" narrow ripple onClick={handleOpenSavedDialog}>
<span>{oldLang('SavedMessagesTab')}</span> <span>{oldLang('SavedMessagesTab')}</span>
</ListItem> </ListItem>

View File

@ -27,6 +27,7 @@
} }
.plain.minimized { .plain.minimized {
padding-block: 1.5rem 0;
color: var(--color-text); color: var(--color-text);
.userRatingNegativeWrapper, .userRatingNegativeWrapper,
@ -34,6 +35,14 @@
color: var(--color-white); color: var(--color-white);
} }
.status {
color: var(--color-text-secondary);
}
.getStatus, .userStatus {
opacity: 1;
}
:global(.VerifiedIcon), :global(.VerifiedIcon),
:global(.StarIcon) { :global(.StarIcon) {
--color-fill: var(--color-primary); --color-fill: var(--color-primary);

View File

@ -93,7 +93,7 @@ const LeftSideMenuItems = ({
const bots = useMemo(() => Object.values(attachBots).filter((bot) => bot.isForSideMenu), [attachBots]); const bots = useMemo(() => Object.values(attachBots).filter((bot) => bot.isForSideMenu), [attachBots]);
const handleSelectMyProfile = useLastCallback(() => { const handleSelectMyProfile = useLastCallback(() => {
openChatWithInfo({ id: currentUserId, shouldReplaceHistory: true, profileTab: 'stories' }); openChatWithInfo({ id: currentUserId, shouldReplaceHistory: true, isOwnProfile: true });
}); });
const handleSelectSaved = useLastCallback(() => { const handleSelectSaved = useLastCallback(() => {

View File

@ -874,6 +874,7 @@ export default memo(withGlobal<OwnProps>(
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId); const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
const savedDialog = isSavedDialog ? selectChat(global, String(threadId)) : undefined; const savedDialog = isSavedDialog ? selectChat(global, String(threadId)) : undefined;
const isAccountFrozen = selectIsCurrentUserFrozen(global); const isAccountFrozen = selectIsCurrentUserFrozen(global);
const chatInfo = selectTabState(global).chatInfo;
return { return {
chat, chat,
@ -889,8 +890,7 @@ export default memo(withGlobal<OwnProps>(
hasLinkedChat: Boolean(chatFullInfo?.linkedChatId), hasLinkedChat: Boolean(chatFullInfo?.linkedChatId),
botCommands: chatBot ? userFullInfo?.botInfo?.commands : undefined, botCommands: chatBot ? userFullInfo?.botInfo?.commands : undefined,
botPrivacyPolicyUrl: chatBot ? userFullInfo?.botInfo?.privacyPolicyUrl : undefined, botPrivacyPolicyUrl: chatBot ? userFullInfo?.botInfo?.privacyPolicyUrl : undefined,
isChatInfoShown: selectTabState(global).isChatInfoShown isChatInfoShown: chatInfo.isOpen && currentChatId === chatId && currentThreadId === threadId,
&& currentChatId === chatId && currentThreadId === threadId,
canCreateTopic, canCreateTopic,
canEditTopic, canEditTopic,
canManage, canManage,

View File

@ -147,7 +147,8 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
const handleOpenChat = useLastCallback((event: React.MouseEvent | React.TouchEvent) => { const handleOpenChat = useLastCallback((event: React.MouseEvent | React.TouchEvent) => {
if ((event.target as Element).closest('.title > .custom-emoji')) return; if ((event.target as Element).closest('.title > .custom-emoji')) return;
openThreadWithInfo({ chatId, threadId }); // Force close My Profile if clicked on Saved Messages header
openThreadWithInfo({ chatId, threadId, isOwnProfile: false });
}); });
const { const {

View File

@ -1,4 +1,3 @@
import type { FC } from '@teact';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from '@teact'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from '@teact';
import { getActions, getGlobal, withGlobal } from '../../global'; import { getActions, getGlobal, withGlobal } from '../../global';
@ -167,7 +166,7 @@ type StateProps = {
isRestricted?: boolean; isRestricted?: boolean;
activeDownloads: TabState['activeDownloads']; activeDownloads: TabState['activeDownloads'];
isChatProtected?: boolean; isChatProtected?: boolean;
nextProfileTab?: ProfileTabType; chatInfo: TabState['chatInfo'];
animationLevel: AnimationLevel; animationLevel: AnimationLevel;
shouldWarnAboutFiles?: boolean; shouldWarnAboutFiles?: boolean;
similarChannels?: string[]; similarChannels?: string[];
@ -177,7 +176,7 @@ type StateProps = {
limitSimilarPeers: number; limitSimilarPeers: number;
isTopicInfo?: boolean; isTopicInfo?: boolean;
isSavedDialog?: boolean; isSavedDialog?: boolean;
forceScrollProfileTab?: boolean; isSavedMessages?: boolean;
isSynced?: boolean; isSynced?: boolean;
hasAvatar?: boolean; hasAvatar?: boolean;
}; };
@ -197,10 +196,13 @@ const TABS: TabProps[] = [
const HIDDEN_RENDER_DELAY = 1000; const HIDDEN_RENDER_DELAY = 1000;
const INTERSECTION_THROTTLE = 500; const INTERSECTION_THROTTLE = 500;
const Profile: FC<OwnProps & StateProps> = ({ const SHARED_MEDIA_TYPES = new Set<string>(['media', 'documents', 'links', 'audio', 'voice']);
const Profile = ({
chatId, chatId,
isActive, isActive,
threadId, threadId,
chatInfo,
profileState, profileState,
theme, theme,
monoforumChannel, monoforumChannel,
@ -239,7 +241,6 @@ const Profile: FC<OwnProps & StateProps> = ({
isRestricted, isRestricted,
activeDownloads, activeDownloads,
isChatProtected, isChatProtected,
nextProfileTab,
animationLevel, animationLevel,
shouldWarnAboutFiles, shouldWarnAboutFiles,
similarChannels, similarChannels,
@ -248,11 +249,11 @@ const Profile: FC<OwnProps & StateProps> = ({
limitSimilarPeers, limitSimilarPeers,
isTopicInfo, isTopicInfo,
isSavedDialog, isSavedDialog,
forceScrollProfileTab, isSavedMessages,
isSynced, isSynced,
hasAvatar, hasAvatar,
onProfileStateChange, onProfileStateChange,
}) => { }: OwnProps & StateProps) => {
const { const {
setSharedMediaSearchType, setSharedMediaSearchType,
loadMoreMembers, loadMoreMembers,
@ -274,6 +275,7 @@ const Profile: FC<OwnProps & StateProps> = ({
loadStarGiftCollections, loadStarGiftCollections,
loadStoryAlbums, loadStoryAlbums,
resetSelectedStoryAlbum, resetSelectedStoryAlbum,
changeProfileTab,
} = getActions(); } = getActions();
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
@ -285,15 +287,18 @@ const Profile: FC<OwnProps & StateProps> = ({
const [deletingUserId, setDeletingUserId] = useState<string | undefined>(); const [deletingUserId, setDeletingUserId] = useState<string | undefined>();
const [isGiftTransitionEnabled, enableGiftTransition, disableGiftTransition] = useFlag(); const [isGiftTransitionEnabled, enableGiftTransition, disableGiftTransition] = useFlag();
const isClosed = !chatInfo.isOpen;
const { profileTab, forceScrollProfileTab, isOwnProfile } = chatInfo;
const profileId = isSavedDialog ? String(threadId) : chatId; const profileId = isSavedDialog ? String(threadId) : chatId;
const isSavedMessages = profileId === currentUserId && !isSavedDialog; const isGeneralSavedMessages = isSavedMessages && !isSavedDialog;
const [isProfileExpanded, expandProfile, collapseProfile] = useFlag(); const [isProfileExpanded, expandProfile, collapseProfile] = useFlag();
const [restoreContentHeightKey, setRestoreContentHeightKey] = useState(0); const [restoreContentHeightKey, setRestoreContentHeightKey] = useState(0);
const tabs = useMemo(() => { const tabs = useMemo(() => {
const arr: TabProps[] = []; const arr: TabProps[] = [];
if (isSavedMessages && !isSavedDialog) { if (isGeneralSavedMessages) {
arr.push({ type: 'dialogs', key: 'ProfileTabSavedDialogs' }); arr.push({ type: 'dialogs', key: 'ProfileTabSavedDialogs' });
} }
@ -301,7 +306,7 @@ const Profile: FC<OwnProps & StateProps> = ({
arr.push({ type: 'stories', key: 'ProfileTabStories' }); arr.push({ type: 'stories', key: 'ProfileTabStories' });
} }
if (hasStoriesTab && isSavedMessages) { if (hasStoriesTab && isOwnProfile) {
arr.push({ type: 'storiesArchive', key: 'ProfileTabStoriesArchive' }); arr.push({ type: 'storiesArchive', key: 'ProfileTabStoriesArchive' });
} }
@ -309,67 +314,73 @@ const Profile: FC<OwnProps & StateProps> = ({
arr.push({ type: 'gifts', key: 'ProfileTabGifts' }); arr.push({ type: 'gifts', key: 'ProfileTabGifts' });
} }
if (hasMembersTab) { if (hasMembersTab && !isOwnProfile) {
arr.push({ type: 'members', key: isChannel ? 'ProfileTabSubscribers' : 'ProfileTabMembers' }); arr.push({ type: 'members', key: isChannel ? 'ProfileTabSubscribers' : 'ProfileTabMembers' });
} }
if (hasPreviewMediaTab) { if (hasPreviewMediaTab && !isOwnProfile) {
arr.push({ type: 'previewMedia', key: 'ProfileTabBotPreview' }); arr.push({ type: 'previewMedia', key: 'ProfileTabBotPreview' });
} }
arr.push(...TABS); if (!isOwnProfile) {
arr.push(...TABS);
}
// Voice messages filter currently does not work in forum topics. Return it when it's fixed on the server side. // Voice messages filter currently does not work in forum topics. Return it when it's fixed on the server side.
if (!isTopicInfo) { if (!isTopicInfo && !isOwnProfile) {
arr.push({ type: 'voice', key: 'ProfileTabVoice' }); arr.push({ type: 'voice', key: 'ProfileTabVoice' });
} }
if (hasCommonChatsTab) { if (hasCommonChatsTab && !isOwnProfile) {
arr.push({ type: 'commonChats', key: 'ProfileTabSharedGroups' }); arr.push({ type: 'commonChats', key: 'ProfileTabSharedGroups' });
} }
if (isChannel && similarChannels?.length) { if (isChannel && similarChannels?.length && !isOwnProfile) {
arr.push({ type: 'similarChannels', key: 'ProfileTabSimilarChannels' }); arr.push({ type: 'similarChannels', key: 'ProfileTabSimilarChannels' });
} }
if (isBot && similarBots?.length) { if (isBot && similarBots?.length && !isOwnProfile) {
arr.push({ type: 'similarBots', key: 'ProfileTabSimilarBots' }); arr.push({ type: 'similarBots', key: 'ProfileTabSimilarBots' });
} }
// Fallback to prevent errors in edge cases
// TODO: Handle no tabs case, skip shared media block
if (!arr.length) {
arr.push(TABS[0]);
}
return arr.map((tab) => ({ return arr.map((tab) => ({
type: tab.type, type: tab.type,
title: lang(tab.key), title: lang(tab.key),
})); }));
}, [ }, [
isSavedMessages, isSavedDialog, hasStoriesTab, hasGiftsTab, hasMembersTab, hasPreviewMediaTab, isTopicInfo, isGeneralSavedMessages, hasStoriesTab, hasGiftsTab, hasMembersTab, hasPreviewMediaTab, isTopicInfo,
hasCommonChatsTab, isChannel, isBot, similarChannels?.length, similarBots?.length, lang, hasCommonChatsTab, isChannel, isBot, similarChannels?.length, similarBots?.length, lang, isOwnProfile,
]); ]);
const initialTab = useMemo(() => {
if (!nextProfileTab) {
return 0;
}
const index = tabs.findIndex(({ type }) => type === nextProfileTab);
return index === -1 ? 0 : index;
}, [nextProfileTab, tabs]);
const [allowAutoScrollToTabs, startAutoScrollToTabsIfNeeded, stopAutoScrollToTabs] = useFlag(false); const [allowAutoScrollToTabs, startAutoScrollToTabsIfNeeded, stopAutoScrollToTabs] = useFlag(false);
const [activeTab, setActiveTab] = useState(initialTab); const setActiveTab = useLastCallback((type: ProfileTabType) => {
if (isClosed) return;
changeProfileTab({ profileTab: type });
setSharedMediaSearchType({ mediaType: SHARED_MEDIA_TYPES.has(type) ? type as SharedMediaType : undefined });
});
useEffect(() => { useEffect(() => {
if (!nextProfileTab) return; if (isClosed) return;
const index = tabs.findIndex(({ type }) => type === nextProfileTab); if (profileTab) {
// Force reset scroll marker
changeProfileTab({ profileTab, shouldScrollTo: undefined });
return;
};
if (index === -1) return; setActiveTab(tabs[0].type); // Set default tab
setActiveTab(index); }, [isClosed, profileTab, tabs]);
}, [nextProfileTab, tabs]);
const handleSwitchTab = useCallback((index: number) => { const handleSwitchTab = useCallback((index: number) => {
startAutoScrollToTabsIfNeeded(); startAutoScrollToTabsIfNeeded();
setActiveTab(index); setActiveTab(tabs[index].type);
}, []); }, [tabs]);
useEffect(() => { useEffect(() => {
if (hasPreviewMediaTab && !botPreviewMedia) { if (hasPreviewMediaTab && !botPreviewMedia) {
@ -414,8 +425,12 @@ const Profile: FC<OwnProps & StateProps> = ({
const giftIds = useMemo(() => renderingGifts?.map((gift) => getSavedGiftKey(gift)), [renderingGifts]); const giftIds = useMemo(() => renderingGifts?.map((gift) => getSavedGiftKey(gift)), [renderingGifts]);
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab; const activeTabIndex = useMemo(() => {
const tabType = tabs[renderingActiveTab].type; const index = tabs.findIndex(({ type }) => type === profileTab);
return index === -1 ? 0 : index;
}, [profileTab, tabs]);
const tabType = tabs[activeTabIndex].type;
const handleLoadCommonChats = useCallback(() => { const handleLoadCommonChats = useCallback(() => {
loadCommonChats({ userId: chatId }); loadCommonChats({ userId: chatId });
}, [chatId]); }, [chatId]);
@ -481,7 +496,9 @@ const Profile: FC<OwnProps & StateProps> = ({
similarBots, similarBots,
}); });
const isFirstTab = (isSavedMessages && resultType === 'dialogs') const shouldRenderProfileInfo = !noProfileInfo && !isSavedMessages;
const isFirstTab = (isGeneralSavedMessages && resultType === 'dialogs')
|| (hasStoriesTab && resultType === 'stories') || (hasStoriesTab && resultType === 'stories')
|| resultType === 'members' || resultType === 'members'
|| (!hasMembersTab && resultType === 'media'); || (!hasMembersTab && resultType === 'media');
@ -562,11 +579,6 @@ const Profile: FC<OwnProps & StateProps> = ({
setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.InProgress }); setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.InProgress });
}); });
// Update search type when switching tabs or forum topics
useEffect(() => {
setSharedMediaSearchType({ mediaType: tabType as SharedMediaType });
}, [setSharedMediaSearchType, tabType, threadId]);
const handleSelectMedia = useLastCallback((messageId: number) => { const handleSelectMedia = useLastCallback((messageId: number) => {
openMediaViewer({ openMediaViewer({
chatId: profileId, chatId: profileId,
@ -602,21 +614,21 @@ const Profile: FC<OwnProps & StateProps> = ({
}); });
useEffectWithPrevDeps(([prevHasMemberTabs]) => { useEffectWithPrevDeps(([prevHasMemberTabs]) => {
if (prevHasMemberTabs === undefined || activeTab === 0 || prevHasMemberTabs === hasMembersTab) { if (prevHasMemberTabs === undefined || activeTabIndex === 0 || prevHasMemberTabs === hasMembersTab) {
return; return;
} }
const newActiveTab = activeTab + (hasMembersTab ? 1 : -1); const newActiveTab = Math.min(activeTabIndex + (hasMembersTab ? 1 : -1), tabs.length - 1);
setActiveTab(Math.min(newActiveTab, tabs.length - 1)); setActiveTab(tabs[newActiveTab].type);
}, [hasMembersTab, activeTab, tabs]); }, [hasMembersTab, activeTabIndex, tabs]);
const handleResetGiftsFilter = useLastCallback(() => { const handleResetGiftsFilter = useLastCallback(() => {
resetGiftProfileFilter({ peerId: chatId }); resetGiftProfileFilter({ peerId: chatId });
}); });
useTopOverscroll( useTopOverscroll(
containerRef, handleExpandProfile, handleCollapseProfile, !hasAvatar, containerRef, handleExpandProfile, handleCollapseProfile, !hasAvatar || !shouldRenderProfileInfo,
); );
useEffect(() => { useEffect(() => {
@ -628,17 +640,19 @@ const Profile: FC<OwnProps & StateProps> = ({
selectorToPreventScroll: '.Profile', selectorToPreventScroll: '.Profile',
onSwipe: (e, direction) => { onSwipe: (e, direction) => {
if (direction === SwipeDirection.Left) { if (direction === SwipeDirection.Left) {
setActiveTab(Math.min(renderingActiveTab + 1, tabs.length - 1)); const nextIndex = Math.min(activeTabIndex + 1, tabs.length - 1);
setActiveTab(tabs[nextIndex].type);
return true; return true;
} else if (direction === SwipeDirection.Right) { } else if (direction === SwipeDirection.Right) {
setActiveTab(Math.max(0, renderingActiveTab - 1)); const nextIndex = Math.max(0, activeTabIndex - 1);
setActiveTab(tabs[nextIndex].type);
return true; return true;
} }
return false; return false;
}, },
}); });
}, [renderingActiveTab, tabs.length]); }, [activeTabIndex, tabs]);
let renderingDelay; let renderingDelay;
// @optimization Used to unparallelize rendering of message list and profile media // @optimization Used to unparallelize rendering of message list and profile media
@ -650,7 +664,7 @@ const Profile: FC<OwnProps & StateProps> = ({
} }
const canRenderContent = useAsyncRendering([chatId, threadId, resultType, const canRenderContent = useAsyncRendering([chatId, threadId, resultType,
renderingActiveTab, activeCollectionId, selectedStoryAlbumId], renderingDelay); activeTabIndex, activeCollectionId, selectedStoryAlbumId], renderingDelay);
function getMemberContextAction(memberId: string): MenuItemContextAction[] | undefined { function getMemberContextAction(memberId: string): MenuItemContextAction[] | undefined {
return memberId === currentUserId || !canDeleteMembers ? undefined : [{ return memberId === currentUserId || !canDeleteMembers ? undefined : [{
@ -1045,6 +1059,7 @@ const Profile: FC<OwnProps & StateProps> = ({
<ChatExtra <ChatExtra
chatOrUserId={profileId} chatOrUserId={profileId}
isSavedDialog={isSavedDialog} isSavedDialog={isSavedDialog}
isOwnProfile={isOwnProfile}
style={createVtnStyle('chatExtra')} style={createVtnStyle('chatExtra')}
/> />
</div> </div>
@ -1095,10 +1110,11 @@ const Profile: FC<OwnProps & StateProps> = ({
} }
const activeListSelector = `.shared-media-transition > .Transition_slide-active`; const activeListSelector = `.shared-media-transition > .Transition_slide-active`;
// eslint-disable-next-line @stylistic/max-len
const nestedSelector = `${activeListSelector} > .Transition > .Transition_slide-active > .Transition > .Transition_slide-active`;
const itemSelector = !shouldUseTransitionForContent const itemSelector = !shouldUseTransitionForContent
? `${activeListSelector} .${resultType}-list > .scroll-item` ? `${activeListSelector} .${resultType}-list > .scroll-item`
/* eslint-disable @stylistic/max-len */ : `${nestedSelector} > .${resultType}-list > .scroll-item`;
: `${activeListSelector} > .Transition > .Transition_slide-active > .Transition > .Transition_slide-active > .gifts-list > .scroll-item`;
return ( return (
<InfiniteScroll <InfiniteScroll
@ -1142,7 +1158,7 @@ const Profile: FC<OwnProps & StateProps> = ({
> >
{renderContent()} {renderContent()}
</Transition> </Transition>
<TabList activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={handleSwitchTab} /> <TabList activeTab={activeTabIndex} tabs={tabs} onSwitchTab={handleSwitchTab} />
</div> </div>
)} )}
@ -1177,6 +1193,10 @@ export default memo(withGlobal<OwnProps>(
const userFullInfo = selectUserFullInfo(global, chatId); const userFullInfo = selectUserFullInfo(global, chatId);
const messagesById = selectChatMessages(global, chatId); const messagesById = selectChatMessages(global, chatId);
const tabState = selectTabState(global);
const { chatInfo, savedGifts } = tabState;
const { isOwnProfile } = chatInfo;
const { animationLevel, shouldWarnAboutFiles } = selectSharedSettings(global); const { animationLevel, shouldWarnAboutFiles } = selectSharedSettings(global);
const { currentType: mediaSearchType, resultsByType } = selectCurrentSharedMediaSearch(global) || {}; const { currentType: mediaSearchType, resultsByType } = selectCurrentSharedMediaSearch(global) || {};
@ -1187,7 +1207,8 @@ export default memo(withGlobal<OwnProps>(
const { byId: usersById, statusesById: userStatusesById } = global.users; const { byId: usersById, statusesById: userStatusesById } = global.users;
const { byId: chatsById } = global.chats; const { byId: chatsById } = global.chats;
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId); const isSavedMessages = chatId === global.currentUserId && !isOwnProfile;
const isSavedDialog = !isOwnProfile ? getIsSavedDialog(chatId, threadId, global.currentUserId) : undefined;
const isGroup = chat && isChatGroup(chat); const isGroup = chat && isChatGroup(chat);
const isChannel = chat && isChatChannel(chat); const isChannel = chat && isChatChannel(chat);
@ -1210,7 +1231,7 @@ export default memo(withGlobal<OwnProps>(
const peer = user || chat; const peer = user || chat;
const peerFullInfo = userFullInfo || chatFullInfo; const peerFullInfo = userFullInfo || chatFullInfo;
const hasCommonChatsTab = user && !user.isSelf && !isUserBot(user) && !isSavedDialog const hasCommonChatsTab = user && !user.isSelf && !isUserBot(user) && !isSavedMessages
&& Boolean(userFullInfo?.commonChatsCount); && Boolean(userFullInfo?.commonChatsCount);
const commonChats = selectUserCommonChats(global, chatId); const commonChats = selectUserCommonChats(global, chatId);
@ -1218,10 +1239,8 @@ export default memo(withGlobal<OwnProps>(
const botPreviewMedia = global.users.previewMediaByBotId[chatId]; const botPreviewMedia = global.users.previewMediaByBotId[chatId];
const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories)) const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories))
&& !isSavedDialog; && !isSavedMessages;
const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined; const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined;
const tabState = selectTabState(global);
const { nextProfileTab, forceScrollProfileTab, savedGifts } = tabState;
const selectedStoryAlbumId = selectActiveStoriesCollectionId(global); const selectedStoryAlbumId = selectActiveStoriesCollectionId(global);
const storyIds = selectedStoryAlbumId !== 'all' const storyIds = selectedStoryAlbumId !== 'all'
? peerStories?.idsByAlbumId?.[selectedStoryAlbumId]?.ids ? peerStories?.idsByAlbumId?.[selectedStoryAlbumId]?.ids
@ -1230,7 +1249,7 @@ export default memo(withGlobal<OwnProps>(
const storyByIds = peerStories?.byId; const storyByIds = peerStories?.byId;
const archiveStoryIds = peerStories?.archiveIds; const archiveStoryIds = peerStories?.archiveIds;
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedDialog; const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedMessages;
const activeCollectionId = selectActiveGiftsCollectionId(global, chatId); const activeCollectionId = selectActiveGiftsCollectionId(global, chatId);
const peerGifts = savedGifts.collectionsByPeerId[chatId]?.[activeCollectionId]; const peerGifts = savedGifts.collectionsByPeerId[chatId]?.[activeCollectionId];
@ -1274,8 +1293,7 @@ export default memo(withGlobal<OwnProps>(
activeCollectionId, activeCollectionId,
giftsFilter: savedGifts.filter, giftsFilter: savedGifts.filter,
isChatProtected: chat?.isProtected, isChatProtected: chat?.isProtected,
nextProfileTab, chatInfo,
forceScrollProfileTab,
animationLevel, animationLevel,
shouldWarnAboutFiles, shouldWarnAboutFiles,
similarChannels: similarChannelIds, similarChannels: similarChannelIds,
@ -1284,6 +1302,7 @@ export default memo(withGlobal<OwnProps>(
isCurrentUserPremium, isCurrentUserPremium,
isTopicInfo, isTopicInfo,
isSavedDialog, isSavedDialog,
isSavedMessages,
isSynced: global.isSynced, isSynced: global.isSynced,
limitSimilarPeers: selectPremiumLimit(global, 'recommendedChannels'), limitSimilarPeers: selectPremiumLimit(global, 'recommendedChannels'),
members: hasMembersTab ? members : undefined, members: hasMembersTab ? members : undefined,

View File

@ -2,7 +2,7 @@ import type { FC } from '@teact';
import { memo, useEffect, useRef, useState } from '@teact'; import { memo, useEffect, useRef, useState } from '@teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { AnimationLevel, ProfileTabType, ThreadId } from '../../types'; import type { AnimationLevel, ThreadId } from '../../types';
import { ManagementScreens, NewChatMembersProgress, ProfileState, RightColumnContent } from '../../types'; import { ManagementScreens, NewChatMembersProgress, ProfileState, RightColumnContent } from '../../types';
import { ANIMATION_END_DELAY, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config'; import { ANIMATION_END_DELAY, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config';
@ -55,10 +55,10 @@ type StateProps = {
animationLevel: AnimationLevel; animationLevel: AnimationLevel;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
nextManagementScreen?: ManagementScreens; nextManagementScreen?: ManagementScreens;
nextProfileTab?: ProfileTabType;
shouldCloseRightColumn?: boolean; shouldCloseRightColumn?: boolean;
isSavedMessages?: boolean; isSavedMessages?: boolean;
isSavedDialog?: boolean; isSavedDialog?: boolean;
isOwnProfile?: boolean;
}; };
const ANIMATION_DURATION = 450 + ANIMATION_END_DELAY; const ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
@ -81,10 +81,10 @@ const RightColumn: FC<OwnProps & StateProps> = ({
animationLevel, animationLevel,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
nextManagementScreen, nextManagementScreen,
nextProfileTab,
shouldCloseRightColumn, shouldCloseRightColumn,
isSavedMessages, isSavedMessages,
isSavedDialog, isSavedDialog,
isOwnProfile,
}) => { }) => {
const { const {
toggleChatInfo, toggleChatInfo,
@ -100,7 +100,6 @@ const RightColumn: FC<OwnProps & StateProps> = ({
toggleStoryStatistics, toggleStoryStatistics,
setOpenedInviteInfo, setOpenedInviteInfo,
requestNextManagementScreen, requestNextManagementScreen,
resetNextProfileTab,
closeCreateTopicPanel, closeCreateTopicPanel,
closeEditTopicPanel, closeEditTopicPanel,
closeBoostStatistics, closeBoostStatistics,
@ -260,12 +259,6 @@ const RightColumn: FC<OwnProps & StateProps> = ({
} }
}, [nextManagementScreen]); }, [nextManagementScreen]);
useEffect(() => {
if (!nextProfileTab) return;
resetNextProfileTab();
}, [nextProfileTab]);
useEffect(() => { useEffect(() => {
if (shouldCloseRightColumn) { if (shouldCloseRightColumn) {
close(); close();
@ -320,7 +313,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
case RightColumnContent.ChatInfo: case RightColumnContent.ChatInfo:
return ( return (
<Profile <Profile
key={`profile_${chatId!}_${threadId}`} key={`profile_${chatId!}_${threadId}_${Boolean(isOwnProfile)}`}
chatId={chatId!} chatId={chatId!}
threadId={threadId} threadId={threadId}
profileState={profileState} profileState={profileState}
@ -426,11 +419,12 @@ export default memo(withGlobal<OwnProps>(
const areActiveChatsLoaded = selectAreActiveChatsLoaded(global); const areActiveChatsLoaded = selectAreActiveChatsLoaded(global);
const { animationLevel } = selectSharedSettings(global); const { animationLevel } = selectSharedSettings(global);
const { const {
management, shouldSkipHistoryAnimations, nextProfileTab, shouldCloseRightColumn, management, shouldSkipHistoryAnimations, shouldCloseRightColumn, chatInfo,
} = selectTabState(global); } = selectTabState(global);
const nextManagementScreen = chatId ? management.byChatId[chatId]?.nextScreen : undefined; const nextManagementScreen = chatId ? management.byChatId[chatId]?.nextScreen : undefined;
const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined; const isOwnProfile = chatInfo?.isOwnProfile;
const isSavedMessages = chatId && !isOwnProfile ? selectIsChatWithSelf(global, chatId) : undefined;
const isSavedDialog = chatId ? getIsSavedDialog(chatId, threadId, global.currentUserId) : undefined; const isSavedDialog = chatId ? getIsSavedDialog(chatId, threadId, global.currentUserId) : undefined;
return { return {
@ -441,10 +435,10 @@ export default memo(withGlobal<OwnProps>(
animationLevel, animationLevel,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
nextManagementScreen, nextManagementScreen,
nextProfileTab,
shouldCloseRightColumn, shouldCloseRightColumn,
isSavedMessages, isSavedMessages,
isSavedDialog, isSavedDialog,
isOwnProfile,
}; };
}, },
)(RightColumn)); )(RightColumn));

View File

@ -7,7 +7,7 @@ import { getActions, withGlobal } from '../../global';
import type { ApiExportedInvite } from '../../api/types'; import type { ApiExportedInvite } from '../../api/types';
import type { GiftProfileFilterOptions, ThreadId } from '../../types'; import type { GiftProfileFilterOptions, ThreadId } from '../../types';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
import { ManagementScreens, ProfileState } from '../../types'; import { ManagementScreens, ProfileState, SettingsScreens } from '../../types';
import { ANIMATION_END_DELAY, SAVED_FOLDER_ID } from '../../config'; import { ANIMATION_END_DELAY, SAVED_FOLDER_ID } from '../../config';
import { import {
@ -94,6 +94,7 @@ type StateProps = {
isInsideTopic?: boolean; isInsideTopic?: boolean;
canEditTopic?: boolean; canEditTopic?: boolean;
isSavedMessages?: boolean; isSavedMessages?: boolean;
isOwnProfile?: boolean;
}; };
const COLUMN_ANIMATION_DURATION = 450 + ANIMATION_END_DELAY; const COLUMN_ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
@ -179,6 +180,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
giftProfileFilter, giftProfileFilter,
canUseGiftFilter, canUseGiftFilter,
canUseGiftAdminFilter, canUseGiftAdminFilter,
isOwnProfile,
onClose, onClose,
onScreenSelect, onScreenSelect,
}) => { }) => {
@ -192,6 +194,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
deleteExportedChatInvite, deleteExportedChatInvite,
openEditTopicPanel, openEditTopicPanel,
updateGiftProfileFilter, updateGiftProfileFilter,
openSettingsScreen,
} = getActions(); } = getActions();
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag(); const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
@ -246,6 +249,10 @@ const RightHeader: FC<OwnProps & StateProps> = ({
toggleStatistics(); toggleStatistics();
}); });
const handleEditProfile = useLastCallback(() => {
openSettingsScreen({ screen: SettingsScreens.EditProfile });
});
const handleClose = useLastCallback(() => { const handleClose = useLastCallback(() => {
onClose(!isSavedMessages); onClose(!isSavedMessages);
}); });
@ -345,6 +352,10 @@ const RightHeader: FC<OwnProps & StateProps> = ({
const renderingContentKey = useCurrentOrPrev(contentKey, true) ?? -1; const renderingContentKey = useCurrentOrPrev(contentKey, true) ?? -1;
function getHeaderTitle() { function getHeaderTitle() {
if (isOwnProfile) {
return lang('MyProfileHeader');
}
if (isSavedMessages) { if (isSavedMessages) {
return oldLang('SavedMessages'); return oldLang('SavedMessages');
} }
@ -673,6 +684,17 @@ const RightHeader: FC<OwnProps & StateProps> = ({
<Icon name="stats" /> <Icon name="stats" />
</Button> </Button>
)} )}
{isOwnProfile && (
<Button
round
color="translucent"
size="smaller"
ariaLabel={lang('Edit')}
onClick={handleEditProfile}
>
<Icon name="edit" />
</Button>
)}
</section> </section>
</> </>
); );
@ -738,7 +760,8 @@ export default withGlobal<OwnProps>(
const topic = isInsideTopic ? selectTopic(global, chatId!, threadId!) : undefined; const topic = isInsideTopic ? selectTopic(global, chatId!, threadId!) : undefined;
const canEditTopic = isInsideTopic && topic && getCanManageTopic(chat, topic); const canEditTopic = isInsideTopic && topic && getCanManageTopic(chat, topic);
const isBot = user && isUserBot(user); const isBot = user && isUserBot(user);
const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined; const isOwnProfile = tabState.chatInfo?.isOwnProfile;
const isSavedMessages = chatId && !isOwnProfile ? selectIsChatWithSelf(global, chatId) : undefined;
const canEditBot = isBot && user?.canEditBot; const canEditBot = isBot && user?.canEditBot;
const canAddContact = user && getCanAddContact(user); const canAddContact = user && getCanAddContact(user);
@ -775,6 +798,7 @@ export default withGlobal<OwnProps>(
giftProfileFilter, giftProfileFilter,
canUseGiftFilter, canUseGiftFilter,
canUseGiftAdminFilter, canUseGiftAdminFilter,
isOwnProfile,
}; };
}, },
)(RightHeader); )(RightHeader);

View File

@ -1,9 +1,10 @@
import type { ActionReturnType } from '../../types'; import type { ProfileTabType } from '../../../types';
import type { ActionReturnType, GlobalState } from '../../types';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { createMessageHashUrl } from '../../../util/routing'; import { createMessageHashUrl } from '../../../util/routing';
import { addActionHandler, setGlobal } from '../../index'; import { addActionHandler, execAfterActions, getGlobal, setGlobal } from '../../index';
import { import {
closeMiddleSearch, closeMiddleSearch,
exitMessageSelectMode, replaceTabThreadParam, updateCurrentMessageList, updateRequestedChatTranslation, exitMessageSelectMode, replaceTabThreadParam, updateCurrentMessageList, updateRequestedChatTranslation,
@ -69,6 +70,10 @@ addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionRe
forwardMessages: {}, forwardMessages: {},
isShareMessageModalShown: false, isShareMessageModalShown: false,
}), }),
// Reset chat info state for new chat
chatInfo: {
isOpen: tabState.chatInfo.isOpen,
},
}, tabId); }, tabId);
} }
@ -102,33 +107,69 @@ addActionHandler('openPreviousChat', (global, actions, payload): ActionReturnTyp
}); });
addActionHandler('openChatWithInfo', (global, actions, payload): ActionReturnType => { addActionHandler('openChatWithInfo', (global, actions, payload): ActionReturnType => {
const { profileTab, forceScrollProfileTab = false, tabId = getCurrentTabId() } = payload; const { profileTab, forceScrollProfileTab, isOwnProfile, tabId = getCurrentTabId(), ...rest } = payload;
global = updateTabState(global, { const currentMessageList = selectCurrentMessageList(global, tabId);
...selectTabState(global, tabId), const isSameMessageList = currentMessageList?.chatId === rest.id
isChatInfoShown: true, && currentMessageList?.threadId === MAIN_THREAD_ID
nextProfileTab: profileTab, && currentMessageList?.type === (rest.type || 'thread');
forceScrollProfileTab,
}, tabId);
global = { ...global, lastIsChatInfoShown: true };
setGlobal(global);
actions.openChat({ ...payload, tabId }); processChatInfoState({ global, isSameMessageList, profileTab, forceScrollProfileTab, isOwnProfile, tabId });
actions.openChat({ ...rest, tabId });
}); });
addActionHandler('openThreadWithInfo', (global, actions, payload): ActionReturnType => { addActionHandler('openThreadWithInfo', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload; const { profileTab, forceScrollProfileTab, isOwnProfile, tabId = getCurrentTabId(), ...rest } = payload;
global = updateTabState(global, { const currentMessageList = selectCurrentMessageList(global, tabId);
...selectTabState(global, tabId), const isSameMessageList = currentMessageList?.chatId === rest.chatId
isChatInfoShown: true, && currentMessageList?.threadId === rest.threadId
}, tabId); && currentMessageList?.type === (rest.type || 'thread');
global = { ...global, lastIsChatInfoShown: true };
setGlobal(global);
actions.openThread({ ...payload, tabId }); processChatInfoState({ global, isSameMessageList, profileTab, forceScrollProfileTab, isOwnProfile, tabId });
actions.openThread({ ...rest, tabId });
}); });
function processChatInfoState<T extends GlobalState>({
global,
isSameMessageList,
profileTab,
forceScrollProfileTab,
isOwnProfile,
tabId,
}: {
global: T;
isSameMessageList: boolean;
profileTab?: ProfileTabType;
forceScrollProfileTab?: boolean;
isOwnProfile?: boolean;
tabId: number;
}) {
const currentChatInfo = selectTabState(global, tabId).chatInfo;
const newProfileTab = profileTab ?? (isSameMessageList ? currentChatInfo.profileTab : undefined);
const newForceScrollProfileTab = forceScrollProfileTab
?? (isSameMessageList ? currentChatInfo.forceScrollProfileTab : undefined);
const newIsOwnProfile = isOwnProfile ?? (isSameMessageList ? currentChatInfo.isOwnProfile : undefined);
execAfterActions(() => {
global = getGlobal();
global = updateTabState(global, {
...selectTabState(global, tabId),
chatInfo: {
isOpen: true,
profileTab: newProfileTab,
forceScrollProfileTab: newForceScrollProfileTab,
isOwnProfile: newIsOwnProfile,
},
}, tabId);
global = { ...global, lastIsChatInfoShown: true };
setGlobal(global);
});
}
addActionHandler('openChatWithDraft', (global, actions, payload): ActionReturnType => { addActionHandler('openChatWithDraft', (global, actions, payload): ActionReturnType => {
const { const {
chatId, text, threadId = MAIN_THREAD_ID, files, filter, tabId = getCurrentTabId(), chatId, text, threadId = MAIN_THREAD_ID, files, filter, tabId = getCurrentTabId(),

View File

@ -51,10 +51,18 @@ const MAX_STORED_EMOJIS = 8 * 4; // Represents four rows of recent emojis
addActionHandler('toggleChatInfo', (global, actions, payload): ActionReturnType => { addActionHandler('toggleChatInfo', (global, actions, payload): ActionReturnType => {
const { force, tabId = getCurrentTabId() } = payload || {}; const { force, tabId = getCurrentTabId() } = payload || {};
const isChatInfoShown = force !== undefined ? force : !selectTabState(global, tabId).isChatInfoShown; const chatInfo = selectTabState(global, tabId).chatInfo;
const willChatInfoBeShown = force !== undefined ? force : !chatInfo.isOpen;
global = updateTabState(global, { isChatInfoShown }, tabId); if (willChatInfoBeShown !== chatInfo.isOpen) {
global = { ...global, lastIsChatInfoShown: isChatInfoShown }; global = updateTabState(global, {
chatInfo: {
...chatInfo,
isOpen: willChatInfoBeShown,
},
}, tabId);
}
global = { ...global, lastIsChatInfoShown: willChatInfoBeShown };
return global; return global;
}); });
@ -156,15 +164,24 @@ addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionRe
}, tabId); }, tabId);
}); });
addActionHandler('resetNextProfileTab', (global, actions, payload): ActionReturnType => { addActionHandler('changeProfileTab', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {}; const { profileTab, shouldScrollTo, tabId = getCurrentTabId() } = payload;
const { chatId } = selectCurrentMessageList(global, tabId) || {}; const { chatId } = selectCurrentMessageList(global, tabId) || {};
if (!chatId) { if (!chatId) {
return undefined; return undefined;
} }
return updateTabState(global, { nextProfileTab: undefined, forceScrollProfileTab: false }, tabId); const chatInfo = selectTabState(global, tabId).chatInfo;
return updateTabState(global, {
chatInfo: {
...chatInfo,
isOpen: true,
profileTab,
forceScrollProfileTab: shouldScrollTo,
},
}, tabId);
}); });
addActionHandler('toggleStatistics', (global, actions, payload): ActionReturnType => { addActionHandler('toggleStatistics', (global, actions, payload): ActionReturnType => {

View File

@ -52,5 +52,6 @@ export const addActionHandler = typed.addActionHandler as <ActionName extends Pr
name: ActionName, name: ActionName,
handler: ActionHandlers[ActionName], handler: ActionHandlers[ActionName],
) => void; ) => void;
export const execAfterActions = typed.execAfterActions;
export const withGlobal = typed.withGlobal; export const withGlobal = typed.withGlobal;
export type GlobalActions = ReturnType<typeof getActions>; export type GlobalActions = ReturnType<typeof getActions>;

View File

@ -34,10 +34,14 @@ addActionHandler('init', (global, actions, payload): ActionReturnType => {
const initialTabState = cloneDeep(INITIAL_TAB_STATE); const initialTabState = cloneDeep(INITIAL_TAB_STATE);
initialTabState.id = tabId; initialTabState.id = tabId;
initialTabState.isChatInfoShown = Boolean(global.lastIsChatInfoShown);
initialTabState.audioPlayer.playbackRate = global.audioPlayer.lastPlaybackRate; initialTabState.audioPlayer.playbackRate = global.audioPlayer.lastPlaybackRate;
initialTabState.audioPlayer.isPlaybackRateActive = global.audioPlayer.isLastPlaybackRateActive; initialTabState.audioPlayer.isPlaybackRateActive = global.audioPlayer.isLastPlaybackRateActive;
initialTabState.mediaViewer.playbackRate = global.mediaViewer.lastPlaybackRate; initialTabState.mediaViewer.playbackRate = global.mediaViewer.lastPlaybackRate;
if (global.lastIsChatInfoShown) {
initialTabState.chatInfo = {
isOpen: true,
};
}
global = { global = {
...global, ...global,

View File

@ -339,7 +339,6 @@ export const INITIAL_TAB_STATE: TabState = {
id: 0, id: 0,
isMasterTab: false, isMasterTab: false,
isLeftColumnShown: true, isLeftColumnShown: true,
isChatInfoShown: false,
newChatMembersProgress: NewChatMembersProgress.Closed, newChatMembersProgress: NewChatMembersProgress.Closed,
uiReadyState: 0, uiReadyState: 0,
shouldInit: true, shouldInit: true,
@ -390,6 +389,10 @@ export const INITIAL_TAB_STATE: TabState = {
byChatId: {}, byChatId: {},
}, },
chatInfo: {
isOpen: false,
},
savedGifts: { savedGifts: {
filter: { filter: {
...DEFAULT_GIFT_PROFILE_FILTER_OPTIONS, ...DEFAULT_GIFT_PROFILE_FILTER_OPTIONS,

View File

@ -59,7 +59,7 @@ export function selectRightColumnContentKey<T extends GlobalState>(
RightColumnContent.GifSearch RightColumnContent.GifSearch
) : tabState.newChatMembersProgress !== NewChatMembersProgress.Closed ? ( ) : tabState.newChatMembersProgress !== NewChatMembersProgress.Closed ? (
RightColumnContent.AddingMembers RightColumnContent.AddingMembers
) : tabState.isChatInfoShown && tabState.messageLists.length ? ( ) : tabState.chatInfo.isOpen && tabState.messageLists.length ? (
RightColumnContent.ChatInfo RightColumnContent.ChatInfo
) : undefined; ) : undefined;
} }

View File

@ -311,7 +311,7 @@ export interface ActionPayloads {
hashtag: string; hashtag: string;
} & WithTabId; } & WithTabId;
setSharedMediaSearchType: { setSharedMediaSearchType: {
mediaType: SharedMediaType; mediaType?: SharedMediaType;
} & WithTabId; } & WithTabId;
searchSharedMediaMessages: WithTabId | undefined; searchSharedMediaMessages: WithTabId | undefined;
searchChatMediaMessages: { searchChatMediaMessages: {
@ -360,8 +360,13 @@ export interface ActionPayloads {
openChatWithInfo: ActionPayloads['openChat'] & { openChatWithInfo: ActionPayloads['openChat'] & {
profileTab?: ProfileTabType; profileTab?: ProfileTabType;
forceScrollProfileTab?: boolean; forceScrollProfileTab?: boolean;
isOwnProfile?: boolean;
} & WithTabId;
openThreadWithInfo: ActionPayloads['openThread'] & {
profileTab?: ProfileTabType;
forceScrollProfileTab?: boolean;
isOwnProfile?: boolean;
} & WithTabId; } & WithTabId;
openThreadWithInfo: ActionPayloads['openThread'] & WithTabId;
openLinkedChat: { id: string } & WithTabId; openLinkedChat: { id: string } & WithTabId;
loadMoreMembers: { loadMoreMembers: {
chatId: string; chatId: string;
@ -1186,6 +1191,7 @@ export interface ActionPayloads {
chatId?: string; chatId?: string;
originMessageId: number; originMessageId: number;
originChannelId: string; originChannelId: string;
threadId?: never;
} | { } | {
isComments?: false; isComments?: false;
chatId: string; chatId: string;
@ -1241,7 +1247,10 @@ export interface ActionPayloads {
chatId: string; chatId: string;
isEnabled: boolean; isEnabled: boolean;
}; };
resetNextProfileTab: WithTabId | undefined; changeProfileTab: {
profileTab: ProfileTabType | undefined;
shouldScrollTo?: boolean;
} & WithTabId;
openForumPanel: { openForumPanel: {
chatId: string; chatId: string;

View File

@ -105,7 +105,6 @@ export type TabState = {
shouldPreventComposerAnimation?: boolean; shouldPreventComposerAnimation?: boolean;
inviteHash?: string; inviteHash?: string;
canInstall?: boolean; canInstall?: boolean;
isChatInfoShown: boolean;
isStatisticsShown?: boolean; isStatisticsShown?: boolean;
isLeftColumnShown: boolean; isLeftColumnShown: boolean;
newChatMembersProgress?: NewChatMembersProgress; newChatMembersProgress?: NewChatMembersProgress;
@ -126,8 +125,12 @@ export type TabState = {
}; };
shouldCloseRightColumn?: boolean; shouldCloseRightColumn?: boolean;
nextProfileTab?: ProfileTabType; chatInfo: {
forceScrollProfileTab?: boolean; isOpen: boolean;
profileTab?: ProfileTabType;
forceScrollProfileTab?: boolean;
isOwnProfile?: boolean;
};
nextFoldersAction?: ReducerAction<FoldersActions>; nextFoldersAction?: ReducerAction<FoldersActions>;
shareFolderScreen?: { shareFolderScreen?: {
folderId: number; folderId: number;

View File

@ -132,43 +132,59 @@ export function forceOnHeavyAnimationOnce() {
} }
let actionQueue: NoneToVoidFunction[] = []; let actionQueue: NoneToVoidFunction[] = [];
let afterActionQueue: NoneToVoidFunction[] = [];
function handleAction(name: string, payload?: ActionPayload, options?: ActionOptions): Promise<void> { function handleAction(name: string, payload?: ActionPayload, options?: ActionOptions): Promise<void> {
const deferred = new Deferred<void>(); const deferred = new Deferred<void>();
actionQueue.push(() => { actionQueue.push(() => {
actionHandlers[name]?.forEach((handler) => { actionHandlers[name]?.forEach((handler) => {
const response = handler(DEBUG ? getUntypedGlobal() : currentGlobal, actions, payload); const result = handler(DEBUG ? getUntypedGlobal() : currentGlobal, actions, payload);
if (!response) { if (!result) {
deferred.resolve(); deferred.resolve();
return; return;
} }
if (typeof response.then === 'function') { if (typeof result.then === 'function') {
response.then(() => { result.then(() => {
deferred.resolve(); deferred.resolve();
}); });
return; return;
} }
setUntypedGlobal(response as GlobalState, options); setUntypedGlobal(result as GlobalState, options);
deferred.resolve(); deferred.resolve();
}); });
}); });
// Important: Keep 1 as start requirement to avoid immediate nested action calls
// Do not remove element from array before it is executed for the same reason
if (actionQueue.length === 1) { if (actionQueue.length === 1) {
try { try {
while (actionQueue.length) { while (actionQueue.length) {
actionQueue[0](); actionQueue[0]();
actionQueue.shift(); actionQueue.shift();
} }
while (afterActionQueue.length) {
afterActionQueue[0]();
afterActionQueue.shift();
}
} finally { } finally {
actionQueue = []; actionQueue = [];
afterActionQueue = [];
} }
} }
return deferred.promise; return deferred.promise;
} }
/**
* Execute a function after all actions in stack are executed
* Call only from action handlers
*/
export function execAfterActions(fn: NoneToVoidFunction) {
afterActionQueue.push(fn);
}
function updateContainers() { function updateContainers() {
let DEBUG_startAt: number | undefined; let DEBUG_startAt: number | undefined;
if (DEBUG) { if (DEBUG) {
@ -357,6 +373,7 @@ export function typify<
handler: ActionHandlers[ActionName], handler: ActionHandlers[ActionName],
) => void, ) => void,
withGlobal: withUntypedGlobal as WithGlobalFn, withGlobal: withUntypedGlobal as WithGlobalFn,
execAfterActions,
}; };
} }

View File

@ -856,6 +856,7 @@ export interface LangPair {
'EventLogFilterEditedMessages': undefined; 'EventLogFilterEditedMessages': undefined;
'EventLogFilterLeavingMembers': undefined; 'EventLogFilterLeavingMembers': undefined;
'ChannelManagementTitle': undefined; 'ChannelManagementTitle': undefined;
'MyProfileHeader': undefined;
'EventLogAllAdmins': undefined; 'EventLogAllAdmins': undefined;
'UserRestrictionsCanDo': undefined; 'UserRestrictionsCanDo': undefined;
'UserRestrictionsBlock': undefined; 'UserRestrictionsBlock': undefined;