[Refactoring] Simplify avatars (#3365)

This commit is contained in:
Alexander Zinchuk 2023-07-05 13:14:20 +02:00
parent bb31151402
commit 05ff8ce87a
38 changed files with 149 additions and 193 deletions

View File

@ -29,14 +29,12 @@ type OwnProps = {
};
type StateProps = {
user?: ApiUser;
chat?: ApiChat;
peer?: ApiUser | ApiChat;
};
const GroupCallParticipant: FC<OwnProps & StateProps> = ({
participant,
user,
chat,
peer,
}) => {
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
@ -124,13 +122,13 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
isMutedByMe, isRaiseHand, isSelf, hasCustomVolume, isMuted, isSpeaking, participant.about, participant.volume, lang,
]);
if (!user && !chat) {
if (!peer) {
return undefined;
}
return (
<ListItem
leftElement={<Avatar user={user} chat={chat} className={styles.avatar} />}
leftElement={<Avatar peer={peer} className={styles.avatar} />}
rightElement={<OutlinedMicrophoneIcon participant={participant} className={styles.icon} />}
className={styles.root}
onClick={handleContextMenu}
@ -140,7 +138,7 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
ripple
ref={ref}
>
<FullNameTitle peer={user || chat!} withEmojiStatus className={styles.title} />
<FullNameTitle peer={peer} withEmojiStatus className={styles.title} />
<span className={buildClassName(styles.subtitle, 'subtitle', aboutColor)}>
{hasPresentationStream && <i className="icon icon-share-screen" aria-hidden />}
{hasVideoStream && <i className="icon icon-video" aria-hidden />}
@ -166,8 +164,7 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global, { participant }): StateProps => {
return {
user: participant.isUser ? selectUser(global, participant.id) : undefined,
chat: !participant.isUser ? selectChat(global, participant.id) : undefined,
peer: selectUser(global, participant.id) || selectChat(global, participant.id),
};
},
)(GroupCallParticipant));

View File

@ -27,6 +27,8 @@ type StateProps = {
isActive: boolean;
};
const PREVIEW_AVATARS_COUNT = 3;
const GroupCallTopPane: FC<OwnProps & StateProps> = ({
chatId,
isActive,
@ -58,19 +60,10 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
const usersById = getGlobal().users.byId;
const chatsById = getGlobal().chats.byId;
return Object.values(participants).filter((_, i) => i < 3).map(({ id, isUser }) => {
if (isUser) {
if (!usersById[id]) {
return undefined;
}
return { user: usersById[id] };
} else {
if (!chatsById[id]) {
return undefined;
}
return { chat: chatsById[id] };
}
}).filter(Boolean);
return Object.values(participants)
.slice(0, PREVIEW_AVATARS_COUNT)
.map(({ id }) => usersById[id] || chatsById[id])
.filter(Boolean);
}, [participants]);
useEffect(() => {
@ -107,17 +100,12 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
<span className="participants">{lang('Participants', groupCall.participantsCount || 0, 'i')}</span>
</div>
<div className="avatars">
{fetchedParticipants.map((p) => {
if (!p) return undefined;
return (
<Avatar
key={p.user ? p.user.id : p.chat.id}
chat={p.chat}
user={p.user}
/>
);
})}
{fetchedParticipants.map((peer) => (
<Avatar
key={peer.id}
peer={peer}
/>
))}
</div>
<Button round className="join">
{lang('VoipChatJoin')}

View File

@ -234,7 +234,7 @@ const PhoneCall: FC<StateProps> = ({
dialogRef={containerRef}
>
<Avatar
user={user}
peer={user}
size="jumbo"
className={hasVideo || hasPresentation ? styles.blurred : ''}
/>

View File

@ -7,16 +7,16 @@
width: 3.375rem;
height: 3.375rem;
border-radius: var(--radius);
background: linear-gradient(var(--color-white) -125%, var(--color-user));
background-image: linear-gradient(var(--color-white) -125%, var(--color-user));
color: white;
font-weight: bold;
display: flex;
white-space: nowrap;
user-select: none;
position: relative;
overflow: hidden;
&__media {
border-radius: var(--radius);
width: 100%;
height: 100%;
}
@ -124,29 +124,6 @@
}
}
&.online::after {
content: "";
display: block;
position: absolute;
bottom: 0.0625rem;
right: 0.0625rem;
width: 0.875rem;
height: 0.875rem;
border-radius: 50%;
border: 2px solid var(--color-background);
background-color: #0ac630;
flex-shrink: 0;
opacity: 0.5;
transform: scale(0);
transition: opacity 200ms, transform 200ms;
}
&.online-open::after {
opacity: 1;
transform: scale(1);
}
&.interactive {
cursor: var(--custom-cursor, pointer);
}

View File

@ -1,11 +1,11 @@
import type { MouseEvent as ReactMouseEvent } from 'react';
import React, {
memo, useMemo, useRef,
memo, useRef,
} from '../../lib/teact/teact';
import type { FC, TeactNode } from '../../lib/teact/teact';
import type {
ApiChat, ApiPhoto, ApiUser, ApiUserStatus,
ApiChat, ApiPhoto, ApiUser,
} from '../../api/types';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { ApiMediaFormat } from '../../api/types';
@ -19,7 +19,6 @@ import {
isUserId,
isChatWithRepliesBot,
isDeletedUser,
isUserOnline,
} from '../../global/helpers';
import { getFirstLetters } from '../../util/textFormat';
import buildClassName, { createClassNameBuilder } from '../../util/buildClassName';
@ -44,10 +43,8 @@ cn.icon = cn('icon');
type OwnProps = {
className?: string;
size?: 'micro' | 'tiny' | 'mini' | 'small' | 'small-mobile' | 'medium' | 'large' | 'jumbo';
chat?: ApiChat;
user?: ApiUser;
peer?: ApiChat | ApiUser;
photo?: ApiPhoto;
userStatus?: ApiUserStatus;
text?: string;
isSavedMessages?: boolean;
withVideo?: boolean;
@ -60,10 +57,8 @@ type OwnProps = {
const Avatar: FC<OwnProps> = ({
className,
size = 'large',
chat,
user,
peer,
photo,
userStatus,
text,
isSavedMessages,
withVideo,
@ -74,8 +69,10 @@ const Avatar: FC<OwnProps> = ({
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
const videoLoopCountRef = useRef(0);
const user = peer && isUserId(peer.id) ? peer as ApiUser : undefined;
const chat = peer && !isUserId(peer.id) ? peer as ApiChat : undefined;
const isDeleted = user && isDeletedUser(user);
const isReplies = user && isChatWithRepliesBot(user.id);
const isReplies = peer && isChatWithRepliesBot(peer.id);
const isForum = chat?.isForum;
let imageHash: string | undefined;
let videoHash: string | undefined;
@ -84,10 +81,8 @@ const Avatar: FC<OwnProps> = ({
const shouldFetchBig = size === 'jumbo';
if (!isSavedMessages && !isDeleted) {
if (user && !noPersonalPhoto) {
imageHash = getChatAvatarHash(user, shouldFetchBig ? 'big' : undefined);
} else if (chat) {
imageHash = getChatAvatarHash(chat, shouldFetchBig ? 'big' : undefined);
if ((user && !noPersonalPhoto) || chat) {
imageHash = getChatAvatarHash(peer!, shouldFetchBig ? 'big' : undefined);
} else if (photo) {
imageHash = `photo${photo.id}?size=m`;
if (photo.isVideo && withVideo) {
@ -104,12 +99,6 @@ const Avatar: FC<OwnProps> = ({
const transitionClassNames = useMediaTransition(hasBlobUrl);
const isOnline = !isSavedMessages && user && userStatus && isUserOnline(user, userStatus);
const onlineTransitionClassNames = useMediaTransition(isOnline);
const onlineClassNamesPrefixed = useMemo(() => {
return onlineTransitionClassNames.split(' ').map((c) => (c === 'shown' ? 'online' : `online-${c}`)).join(' ');
}, [onlineTransitionClassNames]);
const handleVideoEnded = useLastCallback((e) => {
const video = e.currentTarget;
if (!videoBlobUrl) return;
@ -200,12 +189,11 @@ const Avatar: FC<OwnProps> = ({
const fullClassName = buildClassName(
`Avatar size-${size}`,
className,
`color-bg-${getUserColorKey(user || chat)}`,
`color-bg-${getUserColorKey(peer)}`,
isSavedMessages && 'saved-messages',
isDeleted && 'deleted-account',
isReplies && 'replies-bot-account',
isForum && 'forum',
onlineClassNamesPrefixed,
onClick && 'interactive',
(!isSavedMessages && !imgBlobUrl) && 'no-photo',
);
@ -218,13 +206,11 @@ const Avatar: FC<OwnProps> = ({
}
});
const senderId = (user || chat) && (user || chat)!.id;
return (
<div
ref={ref}
className={fullClassName}
data-test-sender-id={IS_TEST ? senderId : undefined}
data-test-sender-id={IS_TEST ? peer?.id : undefined}
aria-label={typeof content === 'string' ? author : undefined}
onClick={handleClick}
onMouseDown={handleMouseDown}

View File

@ -123,7 +123,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
<div className="modal-header" dir={lang.isRtl ? 'rtl' : undefined}>
<Avatar
size="tiny"
chat={chat}
peer={chat}
isSavedMessages={isChatWithSelf}
/>
<h3 className="modal-title">{lang(renderTitle())}</h3>

View File

@ -182,7 +182,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
<Avatar
key={chat.id}
size={avatarSize}
chat={chat}
peer={chat}
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
/>
)}

View File

@ -56,11 +56,10 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
);
titleText = title;
} else if (chat || user) {
} else if (user || chat) {
iconElement = (
<Avatar
chat={chat}
user={user}
peer={user || chat}
size="small"
isSavedMessages={user?.isSelf}
/>

View File

@ -175,7 +175,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
<Avatar
key={user.id}
size={avatarSize}
user={user}
peer={user}
isSavedMessages={isSavedMessages}
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
/>

View File

@ -222,6 +222,27 @@
transition: opacity var(--layer-transition);
}
}
.avatar-online {
position: absolute;
bottom: 0.0625rem;
right: 0.0625rem;
width: 0.875rem;
height: 0.875rem;
border-radius: 50%;
border: 2px solid var(--color-background);
background-color: #0ac630;
flex-shrink: 0;
opacity: 0.5;
transform: scale(0);
transition: opacity 200ms, transform 200ms;
&.avatar-online-shown {
opacity: 1;
transform: scale(1);
}
}
}
.info {

View File

@ -21,6 +21,7 @@ import {
getMessageAction,
getPrivateChatUserId,
isUserId,
isUserOnline,
selectIsChatMuted,
} from '../../../global/helpers';
import {
@ -49,6 +50,7 @@ import useFlag from '../../../hooks/useFlag';
import useChatListEntry from './hooks/useChatListEntry';
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
import useAppLayout from '../../../hooks/useAppLayout';
import useShowTransition from '../../../hooks/useShowTransition';
import ListItem from '../../ui/ListItem';
import Avatar from '../../common/Avatar';
@ -225,10 +227,15 @@ const Chat: FC<OwnProps & StateProps> = ({
}
}, [chat, chatId, isForum, isIntersecting]);
const isOnline = user && userStatus && isUserOnline(user, userStatus);
const { hasShownClass: isAvatarOnlineShown } = useShowTransition(isOnline);
if (!chat) {
return undefined;
}
const peer = user || chat;
const className = buildClassName(
'Chat chat-item-clickable',
isUserId(chatId) ? 'private' : 'group',
@ -251,12 +258,11 @@ const Chat: FC<OwnProps & StateProps> = ({
>
<div className="status">
<Avatar
chat={chat}
user={user}
userStatus={userStatus}
peer={peer}
isSavedMessages={user?.isSelf}
/>
<div className="avatar-badge-wrapper">
<div className={buildClassName('avatar-online', isAvatarOnlineShown && 'avatar-online-shown')} />
<ChatBadge chat={chat} isMuted={isMuted} shouldShowOnlyMostImportant forceHidden={getIsForumPanelClosed} />
</div>
{chat.isCallActive && chat.isCallNotEmpty && (
@ -266,7 +272,7 @@ const Chat: FC<OwnProps & StateProps> = ({
<div className="info">
<div className="info-row">
<FullNameTitle
peer={user || chat}
peer={peer}
withEmojiStatus
isSavedMessages={chatId === user?.id && user?.isSelf}
observeIntersection={observeIntersection}

View File

@ -72,6 +72,8 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
return undefined;
}
const peer = privateChatUser || chat;
return (
<ListItem
className="ChatMessage chat-item-clickable"
@ -80,14 +82,13 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
buttonRef={buttonRef}
>
<Avatar
chat={chat}
user={privateChatUser}
peer={peer}
isSavedMessages={privateChatUser?.isSelf}
/>
<div className="info">
<div className="info-row">
<FullNameTitle
peer={privateChatUser || chat}
peer={peer}
withEmojiStatus
isSavedMessages={chatId === privateChatUser?.id && privateChatUser?.isSelf}
/>

View File

@ -83,7 +83,7 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
onClick={() => handleClick(userId)}
dir={lang.isRtl ? 'rtl' : undefined}
>
<Avatar user={usersById[userId]} />
<Avatar peer={usersById[userId]} />
<div className="top-peer-name">{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}</div>
</div>
))}

View File

@ -75,7 +75,7 @@ const SettingsActiveWebsite: FC<OwnProps & StateProps> = ({
>
<Avatar
className={styles.avatar}
user={renderingBot}
peer={renderingBot}
size="large"
/>
{renderingBot && <FullNameTitle className={styles.title} peer={renderingBot} />}

View File

@ -110,7 +110,7 @@ const SettingsActiveWebsites: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleOpenSessionModal(session.hash)}
>
<Avatar className={styles.avatar} user={bot} size="tiny" />
<Avatar className={styles.avatar} peer={bot} size="tiny" />
<div className="multiline-menu-item full-size" dir="auto">
<span className="date">{formatPastTimeShort(lang, session.dateActive * 1000)}</span>
{bot && <FullNameTitle className={styles.title} peer={bot} />}

View File

@ -68,9 +68,9 @@ const SettingsPrivacyBlockedUsers: FC<OwnProps & StateProps> = ({
function renderContact(contactId: string, i: number, viewportOffset: number) {
const isPrivate = isUserId(contactId);
const user = isPrivate ? usersByIds[contactId] : undefined;
const chat = !isPrivate ? chatsByIds[contactId] : undefined;
const userOrChat = user || chat;
const user = usersByIds[contactId];
const chat = chatsByIds[contactId];
const peer = user || chat;
const className = buildClassName(
'Chat chat-item-clickable blocked-list-item small-icon',
@ -96,11 +96,10 @@ const SettingsPrivacyBlockedUsers: FC<OwnProps & StateProps> = ({
>
<Avatar
size="medium"
user={user}
chat={chat}
peer={peer}
/>
<div className="contact-info" dir="auto">
{userOrChat && <FullNameTitle peer={userOrChat} />}
{peer && <FullNameTitle peer={peer} />}
{user?.phoneNumber && (
<div className="contact-phone" dir="auto">{formatPhoneNumberWithCode(phoneCodeList, user.phoneNumber)}</div>
)}

View File

@ -122,7 +122,7 @@ const NewContactModal: FC<OwnProps & StateProps> = ({
<div className="NewContactModal__profile" dir={lang.isRtl ? 'rtl' : undefined}>
<Avatar
size="jumbo"
user={renderingUser}
peer={renderingUser}
text={`${firstName} ${lastName}`}
/>
<div className="NewContactModal__profile-info">

View File

@ -125,7 +125,7 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
<i className="icon icon-close" />
</Button>
<Avatar
user={renderedUser}
peer={renderedUser}
size="jumbo"
className={styles.avatar}
/>

View File

@ -77,11 +77,7 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
return (
<div className="SenderInfo" onClick={handleFocusMessage}>
{isUserId(sender.id) ? (
<Avatar key={sender.id} size="medium" user={sender as ApiUser} />
) : (
<Avatar key={sender.id} size="medium" chat={sender as ApiChat} />
)}
<Avatar key={sender.id} size="medium" peer={sender} />
<div className="meta">
<div className="title" dir="auto">
{senderTitle && renderText(senderTitle)}

View File

@ -1,20 +1,17 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useMemo, useEffect, useState, useRef,
memo, useEffect, useMemo, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global';
import type { ApiAvailableReaction, ApiMessage, ApiReaction } from '../../api/types';
import { LoadMoreDirection } from '../../types';
import {
selectChatMessage,
selectTabState,
} from '../../global/selectors';
import { selectChatMessage, selectTabState } from '../../global/selectors';
import buildClassName from '../../util/buildClassName';
import { formatIntegerCompact } from '../../util/textFormat';
import { unique } from '../../util/iteratees';
import { isSameReaction, getReactionUniqueKey } from '../../global/helpers';
import { getReactionUniqueKey, isSameReaction } from '../../global/helpers';
import { formatDateAtTime } from '../../util/dateFormat';
import useLang from '../../hooks/useLang';
@ -63,6 +60,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
// No need for expensive global updates on users, so we avoid them
const usersById = getGlobal().users.byId;
const chatsById = getGlobal().chats.byId;
const lang = useLang();
const [isClosing, startClosing, stopClosing] = useFlag(false);
@ -187,25 +185,26 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
onLoadMore={getMore}
>
{viewportIds?.flatMap(
(userId) => {
const user = usersById[userId];
const userReactions = reactors?.reactions.filter((reactor) => reactor.userId === userId);
(peerId) => {
const peer = usersById[peerId] || chatsById[peerId];
const userReactions = reactors?.reactions.filter((reactor) => reactor.userId === peerId);
const items: React.ReactNode[] = [];
const seenByUser = seenByDates?.[userId];
const seenByUser = seenByDates?.[peerId];
userReactions?.forEach((r) => {
if (chosenTab && !isSameReaction(r.reaction, chosenTab)) return;
items.push(
<ListItem
key={`${userId}-${getReactionUniqueKey(r.reaction)}`}
key={`${peerId}-${getReactionUniqueKey(r.reaction)}`}
className="chat-item-clickable reactors-list-item"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleClick(userId)}
onClick={() => handleClick(peerId)}
>
<Avatar user={user} size="medium" />
<Avatar peer={peer} size="medium" />
<div className="info">
<FullNameTitle peer={user} withEmojiStatus />
<FullNameTitle peer={peer} withEmojiStatus />
<span className="status" dir="auto">
<i className="icon icon-heart-outline status-icon" />
{formatDateAtTime(lang, r.addedDate * 1000)}
@ -225,13 +224,13 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
if (!chosenTab && !userReactions?.length) {
items.push(
<ListItem
key={`${userId}-seen-by`}
key={`${peerId}-seen-by`}
className="chat-item-clickable scroll-item small-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleClick(userId)}
onClick={() => handleClick(peerId)}
>
<PrivateChatInfo
userId={userId}
userId={peerId}
noStatusOrTyping
avatarSize="medium"
status={seenByUser ? formatDateAtTime(lang, seenByUser * 1000) : undefined}

View File

@ -36,7 +36,7 @@ const BotCommand: FC<OwnProps> = ({
focus={focus}
>
{withAvatar && (
<Avatar size="small" user={bot} />
<Avatar size="small" peer={bot} />
)}
<div className="content-inner">
<span className="title">/{botCommand.command}</span>

View File

@ -1383,8 +1383,7 @@ const Composer: FC<OwnProps & StateProps> = ({
className={buildClassName('send-as-button', shouldAnimateSendAsButtonRef.current && 'appear-animation')}
>
<Avatar
user={sendAsUser}
chat={sendAsChat}
peer={sendAsUser || sendAsChat}
size="tiny"
/>
</Button>

View File

@ -7,7 +7,6 @@ import type { ApiSendAsPeerId } from '../../../api/types';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import { isUserId } from '../../../global/helpers';
import useLastCallback from '../../../hooks/useLastCallback';
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
@ -95,9 +94,9 @@ const SendAsMenu: FC<OwnProps> = ({
>
<div className="send-as-title" dir="auto">{lang('SendMessageAsTitle')}</div>
{usersById && chatsById && sendAsPeerIds?.map(({ id, isPremium }, index) => {
const user = isUserId(id) ? usersById[id] : undefined;
const chat = !user ? chatsById[id] : undefined;
const userOrChat = user || chat;
const user = usersById[id];
const chat = chatsById[id];
const peer = user || chat;
const handleClick = () => {
if (!isPremium || isCurrentUserPremium) {
@ -128,12 +127,11 @@ const SendAsMenu: FC<OwnProps> = ({
>
<Avatar
size="small"
chat={chat}
user={user}
peer={peer}
className={avatarClassName}
/>
<div className="info">
{userOrChat && <FullNameTitle peer={userOrChat} noFake />}
{peer && <FullNameTitle peer={peer} noFake />}
<span className="subtitle">{user
? lang('VoipGroupPersonalAccount')
: lang('Subscribers', chat?.membersCount, 'i')}

View File

@ -282,7 +282,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
) : stickerSet.id === FAVORITE_SYMBOL_SET_ID ? (
<i className="icon icon-favorite" />
) : stickerSet.id === CHAT_STICKER_SET_ID ? (
<Avatar chat={chat} size="small" />
<Avatar peer={chat} size="small" />
) : (
<StickerSetCover
stickerSet={stickerSet as ApiStickerSet}

View File

@ -3,7 +3,7 @@ import React, { memo, useMemo } from '../../../lib/teact/teact';
import { getActions, getGlobal } from '../../../global';
import type {
ApiChat, ApiThreadInfo, ApiUser,
ApiThreadInfo,
} from '../../../api/types';
import { isUserId } from '../../../global/helpers';
@ -57,14 +57,13 @@ const CommentButton: FC<OwnProps> = ({
function renderRecentRepliers() {
return (
recentRepliers && recentRepliers.length > 0 && (
Boolean(recentRepliers?.length) && (
<div className="recent-repliers" dir={lang.isRtl ? 'rtl' : 'ltr'}>
{recentRepliers.map((user) => (
{recentRepliers!.map((peer) => (
<Avatar
key={user.id}
key={peer.id}
size="small"
user={isUserId(user.id) ? user as ApiUser : undefined}
chat={!isUserId(user.id) ? user as ApiChat : undefined}
peer={peer}
/>
))}
</div>

View File

@ -49,7 +49,7 @@ const Contact: FC<OwnProps & StateProps> = ({
>
<Avatar
size="large"
user={user}
peer={user}
text={firstName || lastName}
/>
<div className="contact-info">

View File

@ -12,7 +12,6 @@ import {
getMessageLocation,
buildStaticMapHash,
isGeoLiveExpired,
isUserId,
} from '../../../global/helpers';
import { formatCountdownShort, formatLastUpdated } from '../../../util/dateFormat';
import {
@ -87,10 +86,6 @@ const Location: FC<OwnProps> = ({
const prevMediaBlobUrl = usePrevious(mediaBlobUrl);
const mapBlobUrl = mediaBlobUrl || prevMediaBlobUrl;
const isPeerUser = peer && isUserId(peer.id);
const avatarUser = (peer && isPeerUser) ? peer as ApiUser : undefined;
const avatarChat = (peer && !isPeerUser) ? peer as ApiChat : undefined;
const accuracyRadiusPx = useMemo(() => {
if (type !== 'geoLive' || !point.accuracyRadius) {
return 0;
@ -213,7 +208,7 @@ const Location: FC<OwnProps> = ({
if (type === 'geoLive') {
return (
<div className={pinClassName} dangerouslySetInnerHTML={SVG_PIN}>
<Avatar chat={avatarChat} user={avatarUser} className="location-avatar" />
<Avatar peer={peer} className="location-avatar" />
{location.heading !== undefined && (
<div className="direction" style={`--direction: ${location.heading}deg`} />
)}

View File

@ -774,18 +774,14 @@ const Message: FC<OwnProps & StateProps> = ({
}
function renderAvatar() {
const isAvatarPeerUser = avatarPeer && isUserId(avatarPeer.id);
const avatarUser = (avatarPeer && isAvatarPeerUser) ? avatarPeer as ApiUser : undefined;
const avatarChat = (avatarPeer && !isAvatarPeerUser) ? avatarPeer as ApiChat : undefined;
const hiddenName = (!avatarPeer && forwardInfo) ? forwardInfo.hiddenUserName : undefined;
return (
<Avatar
size={isMobile ? 'small-mobile' : 'small'}
user={avatarUser}
chat={avatarChat}
peer={avatarPeer}
text={hiddenName}
onClick={(avatarUser || avatarChat) ? handleAvatarClick : undefined}
onClick={avatarPeer ? handleAvatarClick : undefined}
/>
);
}

View File

@ -415,7 +415,7 @@ const MessageContextMenu: FC<OwnProps> = ({
{seenByRecentUsers?.map((user) => (
<Avatar
size="micro"
user={user}
peer={user}
/>
))}
</div>

View File

@ -226,7 +226,7 @@ const Poll: FC<OwnProps & StateProps> = ({
<Avatar
key={user.id}
size="micro"
user={user}
peer={user}
/>
))}
</div>

View File

@ -84,7 +84,7 @@ const ReactionButton: FC<{
{recentReactors.map((user) => (
<Avatar
key={user.id}
user={user}
peer={user}
size="micro"
/>
))}

View File

@ -13,7 +13,6 @@ import {
selectChat,
selectCurrentTextSearch,
} from '../../global/selectors';
import { isChatChannel } from '../../global/helpers';
import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager';
import { renderMessageSummary } from '../common/helpers/renderMessageText';
import useLang from '../../hooks/useLang';
@ -37,7 +36,6 @@ export type OwnProps = {
};
type StateProps = {
chat?: ApiChat;
messagesById?: Record<number, ApiMessage>;
query?: string;
totalCount?: number;
@ -48,7 +46,6 @@ const RightSearch: FC<OwnProps & StateProps> = ({
chatId,
threadId,
isActive,
chat,
messagesById,
query,
totalCount,
@ -96,26 +93,29 @@ const RightSearch: FC<OwnProps & StateProps> = ({
return undefined;
}
const senderUser = message.senderId ? selectUser(getGlobal(), message.senderId) : undefined;
const global = getGlobal();
let senderChat;
if (chat && isChatChannel(chat)) {
senderChat = chat;
} else if (message.forwardInfo) {
let senderPeer = message.senderId
? selectUser(global, message.senderId) || selectChat(global, message.senderId)
: undefined;
if (!senderPeer && message.forwardInfo) {
const { isChannelPost, fromChatId } = message.forwardInfo;
senderChat = isChannelPost && fromChatId ? selectChat(getGlobal(), fromChatId) : undefined;
} else {
senderChat = message.senderId ? selectChat(getGlobal(), message.senderId) : undefined;
const originalSender = isChannelPost && fromChatId ? selectChat(global, fromChatId) : undefined;
if (originalSender) senderPeer = originalSender;
}
if (!senderPeer) {
return undefined;
}
return {
message,
senderUser,
senderChat,
senderPeer: senderPeer!,
onClick: () => focusMessage({ chatId, threadId, messageId: id }),
};
}).filter(Boolean);
}, [query, viewportIds, messagesById, chat, focusMessage, chatId, threadId]);
}, [query, viewportIds, messagesById, focusMessage, chatId, threadId]);
const handleKeyDown = useKeyboardListNavigation(containerRef, true, (index) => {
const foundResult = viewportResults?.[index === -1 ? 0 : index];
@ -125,11 +125,10 @@ const RightSearch: FC<OwnProps & StateProps> = ({
}, '.ListItem-button', true);
const renderSearchResult = ({
message, senderUser, senderChat, onClick,
message, senderPeer, onClick,
}: {
message: ApiMessage;
senderUser?: ApiUser;
senderChat?: ApiChat;
senderPeer: ApiUser | ApiChat;
onClick: NoneToVoidFunction;
}) => {
const text = renderMessageSummary(lang, message, undefined, query);
@ -142,12 +141,11 @@ const RightSearch: FC<OwnProps & StateProps> = ({
onClick={onClick}
>
<Avatar
chat={senderChat}
user={senderUser}
peer={senderPeer}
/>
<div className="info">
<div className="search-result-message-top">
<FullNameTitle peer={(senderUser || senderChat)!} withEmojiStatus />
<FullNameTitle peer={senderPeer} withEmojiStatus />
<LastMessageMeta message={message} />
</div>
<div className="subtitle" dir="auto">
@ -189,9 +187,8 @@ const RightSearch: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global, { chatId }): StateProps => {
const chat = selectChat(global, chatId);
const messagesById = chat && selectChatMessages(global, chat.id);
if (!chat || !messagesById) {
const messagesById = selectChatMessages(global, chatId);
if (!messagesById) {
return {};
}
@ -199,7 +196,6 @@ export default memo(withGlobal<OwnProps>(
const { totalCount, foundIds } = results || {};
return {
chat,
messagesById,
query,
totalCount,

View File

@ -67,7 +67,7 @@ const JoinRequest: FC<OwnProps & StateProps> = ({
<Avatar
key={userId}
size="medium"
user={user}
peer={user}
/>
<div className={buildClassName('user-info')}>
<div className={buildClassName('user-name')}>{fullName}</div>

View File

@ -118,7 +118,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
<div className="modal-header">
<Avatar
size="tiny"
chat={linkedChat}
peer={linkedChat}
/>
<div className="modal-title">
{lang(isChannel ? 'DiscussionUnlinkGroup' : 'DiscussionUnlinkChannel')}
@ -136,7 +136,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
<div className="modal-header">
<Avatar
size="tiny"
chat={linkedGroup}
peer={linkedGroup}
/>
<div className="modal-title">
{lang('Channel.DiscussionGroup.LinkGroup')}

View File

@ -230,7 +230,7 @@ const ManageUser: FC<OwnProps & StateProps> = ({
<Avatar
photo={notPersonalPhoto}
noPersonalPhoto
user={user}
peer={user}
size="mini"
className="personal-photo"
/>

View File

@ -27,7 +27,7 @@ const StatisticsPublicForward: FC<OwnProps> = ({ data }) => {
return (
<div className="StatisticsPublicForward" onClick={handleClick}>
<Avatar size="medium" chat={data.chat} />
<Avatar size="medium" peer={data.chat} />
<div className="StatisticsPublicForward__info">
<div className="StatisticsPublicForward__title">

View File

@ -171,6 +171,10 @@ export function isUserOnline(user: ApiUser, userStatus?: ApiUserStatus) {
return false;
}
if (user.isSelf) {
return false;
}
return userStatus.type === 'userStatusOnline' && type !== 'userTypeBot';
}

View File

@ -178,7 +178,7 @@ class TelegramClient {
return topic.id < offsetTopicId;
}
return true;
}).filter((_, i) => i < limit),
}).slice(0, limit),
users: [],
chats: [],
messages: [],