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";
"EventLogFilterLeavingMembers" = "Members leaving";
"ChannelManagementTitle" = "Admins";
"MyProfileHeader" = "My Profile";
"EventLogAllAdmins" = "All admins";
"UserRestrictionsCanDo" = "What can this user do?";
"UserRestrictionsBlock" = "Ban and remove from group";

View File

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

View File

@ -27,6 +27,7 @@
}
.plain.minimized {
padding-block: 1.5rem 0;
color: var(--color-text);
.userRatingNegativeWrapper,
@ -34,6 +35,14 @@
color: var(--color-white);
}
.status {
color: var(--color-text-secondary);
}
.getStatus, .userStatus {
opacity: 1;
}
:global(.VerifiedIcon),
:global(.StarIcon) {
--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 handleSelectMyProfile = useLastCallback(() => {
openChatWithInfo({ id: currentUserId, shouldReplaceHistory: true, profileTab: 'stories' });
openChatWithInfo({ id: currentUserId, shouldReplaceHistory: true, isOwnProfile: true });
});
const handleSelectSaved = useLastCallback(() => {

View File

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

View File

@ -147,7 +147,8 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
const handleOpenChat = useLastCallback((event: React.MouseEvent | React.TouchEvent) => {
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 {

View File

@ -1,4 +1,3 @@
import type { FC } from '@teact';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from '@teact';
import { getActions, getGlobal, withGlobal } from '../../global';
@ -167,7 +166,7 @@ type StateProps = {
isRestricted?: boolean;
activeDownloads: TabState['activeDownloads'];
isChatProtected?: boolean;
nextProfileTab?: ProfileTabType;
chatInfo: TabState['chatInfo'];
animationLevel: AnimationLevel;
shouldWarnAboutFiles?: boolean;
similarChannels?: string[];
@ -177,7 +176,7 @@ type StateProps = {
limitSimilarPeers: number;
isTopicInfo?: boolean;
isSavedDialog?: boolean;
forceScrollProfileTab?: boolean;
isSavedMessages?: boolean;
isSynced?: boolean;
hasAvatar?: boolean;
};
@ -197,10 +196,13 @@ const TABS: TabProps[] = [
const HIDDEN_RENDER_DELAY = 1000;
const INTERSECTION_THROTTLE = 500;
const Profile: FC<OwnProps & StateProps> = ({
const SHARED_MEDIA_TYPES = new Set<string>(['media', 'documents', 'links', 'audio', 'voice']);
const Profile = ({
chatId,
isActive,
threadId,
chatInfo,
profileState,
theme,
monoforumChannel,
@ -239,7 +241,6 @@ const Profile: FC<OwnProps & StateProps> = ({
isRestricted,
activeDownloads,
isChatProtected,
nextProfileTab,
animationLevel,
shouldWarnAboutFiles,
similarChannels,
@ -248,11 +249,11 @@ const Profile: FC<OwnProps & StateProps> = ({
limitSimilarPeers,
isTopicInfo,
isSavedDialog,
forceScrollProfileTab,
isSavedMessages,
isSynced,
hasAvatar,
onProfileStateChange,
}) => {
}: OwnProps & StateProps) => {
const {
setSharedMediaSearchType,
loadMoreMembers,
@ -274,6 +275,7 @@ const Profile: FC<OwnProps & StateProps> = ({
loadStarGiftCollections,
loadStoryAlbums,
resetSelectedStoryAlbum,
changeProfileTab,
} = getActions();
const containerRef = useRef<HTMLDivElement>();
@ -285,15 +287,18 @@ const Profile: FC<OwnProps & StateProps> = ({
const [deletingUserId, setDeletingUserId] = useState<string | undefined>();
const [isGiftTransitionEnabled, enableGiftTransition, disableGiftTransition] = useFlag();
const isClosed = !chatInfo.isOpen;
const { profileTab, forceScrollProfileTab, isOwnProfile } = chatInfo;
const profileId = isSavedDialog ? String(threadId) : chatId;
const isSavedMessages = profileId === currentUserId && !isSavedDialog;
const isGeneralSavedMessages = isSavedMessages && !isSavedDialog;
const [isProfileExpanded, expandProfile, collapseProfile] = useFlag();
const [restoreContentHeightKey, setRestoreContentHeightKey] = useState(0);
const tabs = useMemo(() => {
const arr: TabProps[] = [];
if (isSavedMessages && !isSavedDialog) {
if (isGeneralSavedMessages) {
arr.push({ type: 'dialogs', key: 'ProfileTabSavedDialogs' });
}
@ -301,7 +306,7 @@ const Profile: FC<OwnProps & StateProps> = ({
arr.push({ type: 'stories', key: 'ProfileTabStories' });
}
if (hasStoriesTab && isSavedMessages) {
if (hasStoriesTab && isOwnProfile) {
arr.push({ type: 'storiesArchive', key: 'ProfileTabStoriesArchive' });
}
@ -309,67 +314,73 @@ const Profile: FC<OwnProps & StateProps> = ({
arr.push({ type: 'gifts', key: 'ProfileTabGifts' });
}
if (hasMembersTab) {
if (hasMembersTab && !isOwnProfile) {
arr.push({ type: 'members', key: isChannel ? 'ProfileTabSubscribers' : 'ProfileTabMembers' });
}
if (hasPreviewMediaTab) {
if (hasPreviewMediaTab && !isOwnProfile) {
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.
if (!isTopicInfo) {
if (!isTopicInfo && !isOwnProfile) {
arr.push({ type: 'voice', key: 'ProfileTabVoice' });
}
if (hasCommonChatsTab) {
if (hasCommonChatsTab && !isOwnProfile) {
arr.push({ type: 'commonChats', key: 'ProfileTabSharedGroups' });
}
if (isChannel && similarChannels?.length) {
if (isChannel && similarChannels?.length && !isOwnProfile) {
arr.push({ type: 'similarChannels', key: 'ProfileTabSimilarChannels' });
}
if (isBot && similarBots?.length) {
if (isBot && similarBots?.length && !isOwnProfile) {
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) => ({
type: tab.type,
title: lang(tab.key),
}));
}, [
isSavedMessages, isSavedDialog, hasStoriesTab, hasGiftsTab, hasMembersTab, hasPreviewMediaTab, isTopicInfo,
hasCommonChatsTab, isChannel, isBot, similarChannels?.length, similarBots?.length, lang,
isGeneralSavedMessages, hasStoriesTab, hasGiftsTab, hasMembersTab, hasPreviewMediaTab, isTopicInfo,
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 [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(() => {
if (!nextProfileTab) return;
const index = tabs.findIndex(({ type }) => type === nextProfileTab);
if (isClosed) return;
if (profileTab) {
// Force reset scroll marker
changeProfileTab({ profileTab, shouldScrollTo: undefined });
return;
};
if (index === -1) return;
setActiveTab(index);
}, [nextProfileTab, tabs]);
setActiveTab(tabs[0].type); // Set default tab
}, [isClosed, profileTab, tabs]);
const handleSwitchTab = useCallback((index: number) => {
startAutoScrollToTabsIfNeeded();
setActiveTab(index);
}, []);
setActiveTab(tabs[index].type);
}, [tabs]);
useEffect(() => {
if (hasPreviewMediaTab && !botPreviewMedia) {
@ -414,8 +425,12 @@ const Profile: FC<OwnProps & StateProps> = ({
const giftIds = useMemo(() => renderingGifts?.map((gift) => getSavedGiftKey(gift)), [renderingGifts]);
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab;
const tabType = tabs[renderingActiveTab].type;
const activeTabIndex = useMemo(() => {
const index = tabs.findIndex(({ type }) => type === profileTab);
return index === -1 ? 0 : index;
}, [profileTab, tabs]);
const tabType = tabs[activeTabIndex].type;
const handleLoadCommonChats = useCallback(() => {
loadCommonChats({ userId: chatId });
}, [chatId]);
@ -481,7 +496,9 @@ const Profile: FC<OwnProps & StateProps> = ({
similarBots,
});
const isFirstTab = (isSavedMessages && resultType === 'dialogs')
const shouldRenderProfileInfo = !noProfileInfo && !isSavedMessages;
const isFirstTab = (isGeneralSavedMessages && resultType === 'dialogs')
|| (hasStoriesTab && resultType === 'stories')
|| resultType === 'members'
|| (!hasMembersTab && resultType === 'media');
@ -562,11 +579,6 @@ const Profile: FC<OwnProps & StateProps> = ({
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) => {
openMediaViewer({
chatId: profileId,
@ -602,21 +614,21 @@ const Profile: FC<OwnProps & StateProps> = ({
});
useEffectWithPrevDeps(([prevHasMemberTabs]) => {
if (prevHasMemberTabs === undefined || activeTab === 0 || prevHasMemberTabs === hasMembersTab) {
if (prevHasMemberTabs === undefined || activeTabIndex === 0 || prevHasMemberTabs === hasMembersTab) {
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));
}, [hasMembersTab, activeTab, tabs]);
setActiveTab(tabs[newActiveTab].type);
}, [hasMembersTab, activeTabIndex, tabs]);
const handleResetGiftsFilter = useLastCallback(() => {
resetGiftProfileFilter({ peerId: chatId });
});
useTopOverscroll(
containerRef, handleExpandProfile, handleCollapseProfile, !hasAvatar,
containerRef, handleExpandProfile, handleCollapseProfile, !hasAvatar || !shouldRenderProfileInfo,
);
useEffect(() => {
@ -628,17 +640,19 @@ const Profile: FC<OwnProps & StateProps> = ({
selectorToPreventScroll: '.Profile',
onSwipe: (e, direction) => {
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;
} 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 false;
},
});
}, [renderingActiveTab, tabs.length]);
}, [activeTabIndex, tabs]);
let renderingDelay;
// @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,
renderingActiveTab, activeCollectionId, selectedStoryAlbumId], renderingDelay);
activeTabIndex, activeCollectionId, selectedStoryAlbumId], renderingDelay);
function getMemberContextAction(memberId: string): MenuItemContextAction[] | undefined {
return memberId === currentUserId || !canDeleteMembers ? undefined : [{
@ -1045,6 +1059,7 @@ const Profile: FC<OwnProps & StateProps> = ({
<ChatExtra
chatOrUserId={profileId}
isSavedDialog={isSavedDialog}
isOwnProfile={isOwnProfile}
style={createVtnStyle('chatExtra')}
/>
</div>
@ -1095,10 +1110,11 @@ const Profile: FC<OwnProps & StateProps> = ({
}
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
? `${activeListSelector} .${resultType}-list > .scroll-item`
/* eslint-disable @stylistic/max-len */
: `${activeListSelector} > .Transition > .Transition_slide-active > .Transition > .Transition_slide-active > .gifts-list > .scroll-item`;
: `${nestedSelector} > .${resultType}-list > .scroll-item`;
return (
<InfiniteScroll
@ -1142,7 +1158,7 @@ const Profile: FC<OwnProps & StateProps> = ({
>
{renderContent()}
</Transition>
<TabList activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={handleSwitchTab} />
<TabList activeTab={activeTabIndex} tabs={tabs} onSwitchTab={handleSwitchTab} />
</div>
)}
@ -1177,6 +1193,10 @@ export default memo(withGlobal<OwnProps>(
const userFullInfo = selectUserFullInfo(global, chatId);
const messagesById = selectChatMessages(global, chatId);
const tabState = selectTabState(global);
const { chatInfo, savedGifts } = tabState;
const { isOwnProfile } = chatInfo;
const { animationLevel, shouldWarnAboutFiles } = selectSharedSettings(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: 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 isChannel = chat && isChatChannel(chat);
@ -1210,7 +1231,7 @@ export default memo(withGlobal<OwnProps>(
const peer = user || chat;
const peerFullInfo = userFullInfo || chatFullInfo;
const hasCommonChatsTab = user && !user.isSelf && !isUserBot(user) && !isSavedDialog
const hasCommonChatsTab = user && !user.isSelf && !isUserBot(user) && !isSavedMessages
&& Boolean(userFullInfo?.commonChatsCount);
const commonChats = selectUserCommonChats(global, chatId);
@ -1218,10 +1239,8 @@ export default memo(withGlobal<OwnProps>(
const botPreviewMedia = global.users.previewMediaByBotId[chatId];
const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories))
&& !isSavedDialog;
&& !isSavedMessages;
const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined;
const tabState = selectTabState(global);
const { nextProfileTab, forceScrollProfileTab, savedGifts } = tabState;
const selectedStoryAlbumId = selectActiveStoriesCollectionId(global);
const storyIds = selectedStoryAlbumId !== 'all'
? peerStories?.idsByAlbumId?.[selectedStoryAlbumId]?.ids
@ -1230,7 +1249,7 @@ export default memo(withGlobal<OwnProps>(
const storyByIds = peerStories?.byId;
const archiveStoryIds = peerStories?.archiveIds;
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedDialog;
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedMessages;
const activeCollectionId = selectActiveGiftsCollectionId(global, chatId);
const peerGifts = savedGifts.collectionsByPeerId[chatId]?.[activeCollectionId];
@ -1274,8 +1293,7 @@ export default memo(withGlobal<OwnProps>(
activeCollectionId,
giftsFilter: savedGifts.filter,
isChatProtected: chat?.isProtected,
nextProfileTab,
forceScrollProfileTab,
chatInfo,
animationLevel,
shouldWarnAboutFiles,
similarChannels: similarChannelIds,
@ -1284,6 +1302,7 @@ export default memo(withGlobal<OwnProps>(
isCurrentUserPremium,
isTopicInfo,
isSavedDialog,
isSavedMessages,
isSynced: global.isSynced,
limitSimilarPeers: selectPremiumLimit(global, 'recommendedChannels'),
members: hasMembersTab ? members : undefined,

View File

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

View File

@ -7,7 +7,7 @@ import { getActions, withGlobal } from '../../global';
import type { ApiExportedInvite } from '../../api/types';
import type { GiftProfileFilterOptions, ThreadId } from '../../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 {
@ -94,6 +94,7 @@ type StateProps = {
isInsideTopic?: boolean;
canEditTopic?: boolean;
isSavedMessages?: boolean;
isOwnProfile?: boolean;
};
const COLUMN_ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
@ -179,6 +180,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
giftProfileFilter,
canUseGiftFilter,
canUseGiftAdminFilter,
isOwnProfile,
onClose,
onScreenSelect,
}) => {
@ -192,6 +194,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
deleteExportedChatInvite,
openEditTopicPanel,
updateGiftProfileFilter,
openSettingsScreen,
} = getActions();
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
@ -246,6 +249,10 @@ const RightHeader: FC<OwnProps & StateProps> = ({
toggleStatistics();
});
const handleEditProfile = useLastCallback(() => {
openSettingsScreen({ screen: SettingsScreens.EditProfile });
});
const handleClose = useLastCallback(() => {
onClose(!isSavedMessages);
});
@ -345,6 +352,10 @@ const RightHeader: FC<OwnProps & StateProps> = ({
const renderingContentKey = useCurrentOrPrev(contentKey, true) ?? -1;
function getHeaderTitle() {
if (isOwnProfile) {
return lang('MyProfileHeader');
}
if (isSavedMessages) {
return oldLang('SavedMessages');
}
@ -673,6 +684,17 @@ const RightHeader: FC<OwnProps & StateProps> = ({
<Icon name="stats" />
</Button>
)}
{isOwnProfile && (
<Button
round
color="translucent"
size="smaller"
ariaLabel={lang('Edit')}
onClick={handleEditProfile}
>
<Icon name="edit" />
</Button>
)}
</section>
</>
);
@ -738,7 +760,8 @@ export default withGlobal<OwnProps>(
const topic = isInsideTopic ? selectTopic(global, chatId!, threadId!) : undefined;
const canEditTopic = isInsideTopic && topic && getCanManageTopic(chat, topic);
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 canAddContact = user && getCanAddContact(user);
@ -775,6 +798,7 @@ export default withGlobal<OwnProps>(
giftProfileFilter,
canUseGiftFilter,
canUseGiftAdminFilter,
isOwnProfile,
};
},
)(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 { getCurrentTabId } from '../../../util/establishMultitabRole';
import { createMessageHashUrl } from '../../../util/routing';
import { addActionHandler, setGlobal } from '../../index';
import { addActionHandler, execAfterActions, getGlobal, setGlobal } from '../../index';
import {
closeMiddleSearch,
exitMessageSelectMode, replaceTabThreadParam, updateCurrentMessageList, updateRequestedChatTranslation,
@ -69,6 +70,10 @@ addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionRe
forwardMessages: {},
isShareMessageModalShown: false,
}),
// Reset chat info state for new chat
chatInfo: {
isOpen: tabState.chatInfo.isOpen,
},
}, tabId);
}
@ -102,33 +107,69 @@ addActionHandler('openPreviousChat', (global, actions, payload): ActionReturnTyp
});
addActionHandler('openChatWithInfo', (global, actions, payload): ActionReturnType => {
const { profileTab, forceScrollProfileTab = false, tabId = getCurrentTabId() } = payload;
const { profileTab, forceScrollProfileTab, isOwnProfile, tabId = getCurrentTabId(), ...rest } = payload;
global = updateTabState(global, {
...selectTabState(global, tabId),
isChatInfoShown: true,
nextProfileTab: profileTab,
forceScrollProfileTab,
}, tabId);
global = { ...global, lastIsChatInfoShown: true };
setGlobal(global);
const currentMessageList = selectCurrentMessageList(global, tabId);
const isSameMessageList = currentMessageList?.chatId === rest.id
&& currentMessageList?.threadId === MAIN_THREAD_ID
&& currentMessageList?.type === (rest.type || 'thread');
actions.openChat({ ...payload, tabId });
processChatInfoState({ global, isSameMessageList, profileTab, forceScrollProfileTab, isOwnProfile, tabId });
actions.openChat({ ...rest, tabId });
});
addActionHandler('openThreadWithInfo', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload;
const { profileTab, forceScrollProfileTab, isOwnProfile, tabId = getCurrentTabId(), ...rest } = payload;
global = updateTabState(global, {
...selectTabState(global, tabId),
isChatInfoShown: true,
}, tabId);
global = { ...global, lastIsChatInfoShown: true };
setGlobal(global);
const currentMessageList = selectCurrentMessageList(global, tabId);
const isSameMessageList = currentMessageList?.chatId === rest.chatId
&& currentMessageList?.threadId === rest.threadId
&& currentMessageList?.type === (rest.type || 'thread');
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 => {
const {
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 => {
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);
global = { ...global, lastIsChatInfoShown: isChatInfoShown };
if (willChatInfoBeShown !== chatInfo.isOpen) {
global = updateTabState(global, {
chatInfo: {
...chatInfo,
isOpen: willChatInfoBeShown,
},
}, tabId);
}
global = { ...global, lastIsChatInfoShown: willChatInfoBeShown };
return global;
});
@ -156,15 +164,24 @@ addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionRe
}, tabId);
});
addActionHandler('resetNextProfileTab', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
addActionHandler('changeProfileTab', (global, actions, payload): ActionReturnType => {
const { profileTab, shouldScrollTo, tabId = getCurrentTabId() } = payload;
const { chatId } = selectCurrentMessageList(global, tabId) || {};
if (!chatId) {
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 => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -132,43 +132,59 @@ export function forceOnHeavyAnimationOnce() {
}
let actionQueue: NoneToVoidFunction[] = [];
let afterActionQueue: NoneToVoidFunction[] = [];
function handleAction(name: string, payload?: ActionPayload, options?: ActionOptions): Promise<void> {
const deferred = new Deferred<void>();
actionQueue.push(() => {
actionHandlers[name]?.forEach((handler) => {
const response = handler(DEBUG ? getUntypedGlobal() : currentGlobal, actions, payload);
if (!response) {
const result = handler(DEBUG ? getUntypedGlobal() : currentGlobal, actions, payload);
if (!result) {
deferred.resolve();
return;
}
if (typeof response.then === 'function') {
response.then(() => {
if (typeof result.then === 'function') {
result.then(() => {
deferred.resolve();
});
return;
}
setUntypedGlobal(response as GlobalState, options);
setUntypedGlobal(result as GlobalState, options);
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) {
try {
while (actionQueue.length) {
actionQueue[0]();
actionQueue.shift();
}
while (afterActionQueue.length) {
afterActionQueue[0]();
afterActionQueue.shift();
}
} finally {
actionQueue = [];
afterActionQueue = [];
}
}
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() {
let DEBUG_startAt: number | undefined;
if (DEBUG) {
@ -357,6 +373,7 @@ export function typify<
handler: ActionHandlers[ActionName],
) => void,
withGlobal: withUntypedGlobal as WithGlobalFn,
execAfterActions,
};
}

View File

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