import type { FC } from '../../lib/teact/teact'; import React, { memo, useEffect, useState } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { ApiChat, ApiPhoto, ApiTopic, ApiUser, ApiUserStatus, } from '../../api/types'; import type { GlobalState } from '../../global/types'; import { MediaViewerOrigin } from '../../types'; import { getUserStatus, isChatChannel, isUserId, isUserOnline, } from '../../global/helpers'; import { selectChat, selectChatFullInfo, selectCurrentMessageList, selectTabState, selectThreadMessagesCount, selectUser, selectUserFullInfo, selectUserStatus, } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; import { captureEvents, SwipeDirection } from '../../util/captureEvents'; import { MEMO_EMPTY_ARRAY } from '../../util/memo'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; import renderText from './helpers/renderText'; import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; import usePrevious from '../../hooks/usePrevious'; import { useStateRef } from '../../hooks/useStateRef'; import usePhotosPreload from './hooks/usePhotosPreload'; import Transition from '../ui/Transition'; import Avatar from './Avatar'; import FullNameTitle from './FullNameTitle'; import ProfilePhoto from './ProfilePhoto'; import TopicIcon from './TopicIcon'; import './ProfileInfo.scss'; import styles from './ProfileInfo.module.scss'; type OwnProps = { userId: string; forceShowSelf?: boolean; canPlayVideo: boolean; }; type StateProps = { user?: ApiUser; userStatus?: ApiUserStatus; chat?: ApiChat; isSavedMessages?: boolean; mediaId?: number; avatarOwnerId?: string; topic?: ApiTopic; messagesCount?: number; userPersonalPhoto?: ApiPhoto; userProfilePhoto?: ApiPhoto; userFallbackPhoto?: ApiPhoto; chatProfilePhoto?: ApiPhoto; } & Pick; const EMOJI_STATUS_SIZE = 24; const EMOJI_TOPIC_SIZE = 120; const ProfileInfo: FC = ({ forceShowSelf, canPlayVideo, user, userStatus, chat, isSavedMessages, connectionState, mediaId, avatarOwnerId, topic, messagesCount, userPersonalPhoto, userProfilePhoto, userFallbackPhoto, chatProfilePhoto, }) => { const { loadFullUser, openMediaViewer, openPremiumModal, } = getActions(); const lang = useLang(); const { id: userId } = user || {}; const { id: chatId } = chat || {}; const photos = user?.photos || chat?.photos || MEMO_EMPTY_ARRAY; const prevMediaId = usePrevious(mediaId); const prevAvatarOwnerId = usePrevious(avatarOwnerId); const mediaIdRef = useStateRef(mediaId); const [hasSlideAnimation, setHasSlideAnimation] = useState(true); // slideOptimized doesn't work well when animation is dynamically disabled const slideAnimation = hasSlideAnimation ? (lang.isRtl ? 'slideRtl' : 'slide') : 'none'; const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0); const isFirst = isSavedMessages || photos.length <= 1 || currentPhotoIndex === 0; const isLast = isSavedMessages || photos.length <= 1 || currentPhotoIndex === photos.length - 1; // Set the current avatar photo to the last selected photo in Media Viewer after it is closed useEffect(() => { if (prevAvatarOwnerId && prevMediaId !== undefined && mediaId === undefined) { setHasSlideAnimation(false); setCurrentPhotoIndex(prevMediaId); } }, [mediaId, prevMediaId, prevAvatarOwnerId]); // Reset the current avatar photo to the one selected in Media Viewer if photos have changed useEffect(() => { setHasSlideAnimation(false); setCurrentPhotoIndex(mediaIdRef.current || 0); }, [mediaIdRef, photos]); // Deleting the last profile photo may result in an error useEffect(() => { if (currentPhotoIndex > photos.length) { setHasSlideAnimation(false); setCurrentPhotoIndex(Math.max(0, photos.length - 1)); } }, [currentPhotoIndex, photos.length]); useEffect(() => { if (connectionState === 'connectionStateReady' && userId && !forceShowSelf) { loadFullUser({ userId }); } }, [userId, loadFullUser, connectionState, forceShowSelf]); usePhotosPreload(photos, currentPhotoIndex); const handleProfilePhotoClick = useLastCallback(() => { openMediaViewer({ avatarOwnerId: userId || chatId, mediaId: currentPhotoIndex, origin: forceShowSelf ? MediaViewerOrigin.SettingsAvatar : MediaViewerOrigin.ProfileAvatar, }); }); const handleStatusClick = useLastCallback(() => { if (!userId) return; openPremiumModal({ fromUserId: userId }); }); const selectPreviousMedia = useLastCallback(() => { if (isFirst) { return; } setHasSlideAnimation(true); setCurrentPhotoIndex(currentPhotoIndex - 1); }); const selectNextMedia = useLastCallback(() => { if (isLast) { return; } setHasSlideAnimation(true); setCurrentPhotoIndex(currentPhotoIndex + 1); }); function handleSelectFallbackPhoto() { if (!isFirst) return; setHasSlideAnimation(true); setCurrentPhotoIndex(photos.length - 1); } // Swipe gestures useEffect(() => { const element = document.querySelector(`.${styles.photoWrapper}`); if (!element) { return undefined; } return captureEvents(element, { selectorToPreventScroll: '.Profile, .settings-content', onSwipe: IS_TOUCH_ENV ? (e, direction) => { if (direction === SwipeDirection.Right) { selectPreviousMedia(); return true; } else if (direction === SwipeDirection.Left) { selectNextMedia(); return true; } return false; } : undefined, }); }, [selectNextMedia, selectPreviousMedia]); if (!user && !chat) { return undefined; } function renderTopic() { return (

{renderText(topic!.title)}

{messagesCount ? lang('Chat.Title.Topic', messagesCount, 'i') : lang('lng_forum_no_messages')}

); } function renderPhotoTabs() { if (isSavedMessages || !photos || photos.length <= 1) { return undefined; } return (
{photos.map((_, i) => ( ))}
); } function renderPhoto(isActive?: boolean) { const photo = !isSavedMessages && photos.length > 0 ? photos[currentPhotoIndex] : undefined; const profilePhoto = photo || userPersonalPhoto || userProfilePhoto || chatProfilePhoto || userFallbackPhoto; return ( ); } function renderStatus() { if (user) { return (
{getUserStatus(lang, user, userStatus)}
); } return ( { isChatChannel(chat!) ? lang('Subscribers', chat!.membersCount ?? 0, 'i') : lang('Members', chat!.membersCount ?? 0, 'i') } ); } if (topic) { return renderTopic(); } return (
{renderPhotoTabs()} {!forceShowSelf && userPersonalPhoto && (
{lang(userPersonalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')}
)} {forceShowSelf && userFallbackPhoto && (
{!isLast && ( )} {lang(userFallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
)} {renderPhoto} {!isFirst && (
{(user || chat) && ( )} {!isSavedMessages && renderStatus()}
); }; export default memo(withGlobal( (global, { userId, forceShowSelf }): StateProps => { const { connectionState } = global; const user = selectUser(global, userId); const isPrivate = isUserId(userId); const userStatus = selectUserStatus(global, userId); const chat = selectChat(global, userId); const isSavedMessages = !forceShowSelf && user && user.isSelf; const { mediaId, avatarOwnerId } = selectTabState(global).mediaViewer; const isForum = chat?.isForum; const { threadId: currentTopicId } = selectCurrentMessageList(global) || {}; const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined; const userFullInfo = isPrivate ? selectUserFullInfo(global, userId) : undefined; const chatFullInfo = !isPrivate ? selectChatFullInfo(global, userId) : undefined; return { connectionState, user, userStatus, chat, userPersonalPhoto: userFullInfo?.personalPhoto, userProfilePhoto: userFullInfo?.profilePhoto, userFallbackPhoto: userFullInfo?.fallbackPhoto, chatProfilePhoto: chatFullInfo?.profilePhoto, isSavedMessages, mediaId, avatarOwnerId, ...(topic && { topic, messagesCount: selectThreadMessagesCount(global, userId, currentTopicId!), }), }; }, )(ProfileInfo));