Avatar: Loop video 3 times and only with max animation level, refactoring (#2026)

This commit is contained in:
Alexander Zinchuk 2022-09-14 00:30:17 +02:00
parent f6477b2f03
commit 76d10d7212
39 changed files with 180 additions and 63 deletions

View File

@ -79,7 +79,7 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
onClick={handleOnClick}
ref={anchorRef}
>
<Avatar user={user} chat={chat} size="medium" noVideo />
<Avatar user={user} chat={chat} size="medium" />
<div className="info">
<span className="name">{name}</span>
<span className={buildClassName('about', aboutColor)}>{aboutText}</span>

View File

@ -60,7 +60,7 @@ const GroupCallParticipantVideo: FC<OwnProps & StateProps> = ({
{lang('Back')}
</button>
)}
<Avatar user={user} chat={chat} className="thumbnail-avatar" noVideo />
<Avatar user={user} chat={chat} className="thumbnail-avatar" />
{ENABLE_THUMBNAIL_VIDEO && (
<div className="thumbnail-wrapper">
<video className="thumbnail" muted autoPlay playsInline srcObject={streams?.[type]} />

View File

@ -5,6 +5,7 @@ import React, {
import { getActions, withGlobal } from '../../../global';
import type { ApiChat, ApiGroupCall, ApiUser } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { selectChatGroupCall } from '../../../global/selectors/calls';
import buildClassName from '../../../util/buildClassName';
@ -26,6 +27,7 @@ type StateProps = {
isActive: boolean;
usersById: Record<string, ApiUser>;
chatsById: Record<string, ApiChat>;
animationLevel: AnimationLevel;
};
const GroupCallTopPane: FC<OwnProps & StateProps> = ({
@ -35,6 +37,7 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
hasPinnedOffset,
usersById,
chatsById,
animationLevel,
}) => {
const {
joinGroupCall,
@ -105,9 +108,9 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
{fetchedParticipants.map((p) => {
if (!p) return undefined;
if (p.user) {
return <Avatar key={p.user.id} user={p.user} />;
return <Avatar key={p.user.id} user={p.user} animationLevel={animationLevel} />;
} else {
return <Avatar key={p.chat.id} chat={p.chat} />;
return <Avatar key={p.chat.id} chat={p.chat} animationLevel={animationLevel} />;
}
})}
</div>
@ -130,6 +133,7 @@ export default memo(withGlobal<OwnProps>(
isActive: ((!groupCall ? (chat && chat.isCallNotEmpty && chat.isCallActive)
: (groupCall.participantsCount > 0 && groupCall.isLoaded)))
&& (global.groupCalls.activeGroupCallId !== groupCall?.id),
animationLevel: global.settings.byKey.animationLevel,
};
},
)(GroupCallTopPane));

View File

@ -6,6 +6,7 @@ import { getActions, withGlobal } from '../../../global';
import '../../../global/actions/calls';
import type { ApiPhoneCall, ApiUser } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import {
IS_ANDROID,
@ -39,6 +40,7 @@ type StateProps = {
phoneCall?: ApiPhoneCall;
isOutgoing: boolean;
isCallPanelVisible?: boolean;
animationLevel: AnimationLevel;
};
const PhoneCall: FC<StateProps> = ({
@ -46,6 +48,7 @@ const PhoneCall: FC<StateProps> = ({
isOutgoing,
phoneCall,
isCallPanelVisible,
animationLevel,
}) => {
const lang = useLang();
const {
@ -235,7 +238,9 @@ const PhoneCall: FC<StateProps> = ({
user={user}
size="jumbo"
className={hasVideo || hasPresentation ? styles.blurred : ''}
withVideo
noLoop={phoneCall?.state !== 'requesting'}
animationLevel={animationLevel}
/>
{phoneCall?.screencastState === 'active' && streams?.presentation
&& <video className={styles.mainVideo} muted autoPlay playsInline srcObject={streams.presentation} />}
@ -370,6 +375,7 @@ export default memo(withGlobal(
user: selectPhoneCallUser(global),
isOutgoing: phoneCall?.adminId === currentUserId,
phoneCall,
animationLevel: global.settings.byKey.animationLevel,
};
},
)(PhoneCall));

View File

@ -9,9 +9,10 @@ import type {
ApiChat, ApiPhoto, ApiUser, ApiUserStatus,
} from '../../api/types';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import type { AnimationLevel } from '../../types';
import { ApiMediaFormat } from '../../api/types';
import { IS_TEST } from '../../config';
import { ANIMATION_LEVEL_MAX, IS_TEST } from '../../config';
import {
getChatAvatarHash,
getChatTitle,
@ -35,6 +36,8 @@ import useVideoCleanup from '../../hooks/useVideoCleanup';
import './Avatar.scss';
const LOOP_COUNT = 3;
const cn = createClassNameBuilder('Avatar');
cn.media = cn('media');
cn.icon = cn('icon');
@ -48,8 +51,9 @@ type OwnProps = {
userStatus?: ApiUserStatus;
text?: string;
isSavedMessages?: boolean;
noVideo?: boolean;
withVideo?: boolean;
noLoop?: boolean;
animationLevel?: AnimationLevel;
lastSyncTime?: number;
observeIntersection?: ObserveFn;
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void;
@ -64,9 +68,10 @@ const Avatar: FC<OwnProps> = ({
userStatus,
text,
isSavedMessages,
noVideo,
withVideo,
noLoop,
lastSyncTime,
animationLevel,
observeIntersection,
onClick,
}) => {
@ -75,15 +80,17 @@ const Avatar: FC<OwnProps> = ({
const ref = useRef<HTMLDivElement>(null);
// eslint-disable-next-line no-null/no-null
const videoRef = useRef<HTMLVideoElement>(null);
const videoLoopCountRef = useRef(0);
const isIntersecting = useIsIntersecting(ref, observeIntersection);
const isDeleted = user && isDeletedUser(user);
const isReplies = user && isChatWithRepliesBot(user.id);
let imageHash: string | undefined;
let videoHash: string | undefined;
const withVideo = isIntersecting && !noVideo && user?.isPremium && user?.hasVideoAvatar;
const shouldShowVideo = isIntersecting && animationLevel === ANIMATION_LEVEL_MAX && withVideo && user?.isPremium
&& user?.hasVideoAvatar;
const profilePhoto = user?.fullInfo?.profilePhoto;
const shouldLoadVideo = withVideo && profilePhoto?.isVideo;
const shouldLoadVideo = shouldShowVideo && profilePhoto?.isVideo;
const shouldFetchBig = size === 'jumbo';
if (!isSavedMessages && !isDeleted) {
@ -112,22 +119,27 @@ const Avatar: FC<OwnProps> = ({
useEffect(() => {
const video = videoRef.current;
if (!video || !noLoop) return undefined;
if (!video || !videoBlobUrl) return undefined;
const returnToStart = () => {
video.currentTime = 0;
videoLoopCountRef.current += 1;
if (videoLoopCountRef.current >= LOOP_COUNT || noLoop) {
video.style.display = 'none';
} else {
video.play();
}
};
video.addEventListener('ended', returnToStart);
return () => video.removeEventListener('ended', returnToStart);
}, [noLoop]);
}, [noLoop, videoBlobUrl]);
const userId = user?.id;
useEffect(() => {
if (withVideo && !profilePhoto) {
if (shouldShowVideo && !profilePhoto) {
loadFullUser({ userId });
}
}, [loadFullUser, profilePhoto, userId, withVideo]);
}, [loadFullUser, profilePhoto, userId, shouldShowVideo]);
const lang = useLang();
@ -157,7 +169,6 @@ const Avatar: FC<OwnProps> = ({
muted
autoPlay
disablePictureInPicture
loop={!noLoop}
playsInline
/>
)}

View File

@ -3,6 +3,7 @@ import React, { useCallback, memo } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { ApiChat } from '../../api/types';
import type { AnimationLevel } from '../../types';
import { selectIsChatWithSelf, selectUser } from '../../global/selectors';
import {
@ -41,6 +42,7 @@ type StateProps = {
currentUserId: string | undefined;
canDeleteForAll?: boolean;
contactName?: string;
animationLevel: AnimationLevel;
};
const DeleteChatModal: FC<OwnProps & StateProps> = ({
@ -55,6 +57,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
currentUserId,
canDeleteForAll,
contactName,
animationLevel,
onClose,
onCloseAnimationEnd,
}) => {
@ -125,6 +128,8 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
size="tiny"
chat={chat}
isSavedMessages={isChatWithSelf}
animationLevel={animationLevel}
withVideo
/>
<h3 className="modal-title">{lang(renderTitle())}</h3>
</div>
@ -236,6 +241,7 @@ export default memo(withGlobal<OwnProps>(
currentUserId: global.currentUserId,
canDeleteForAll,
contactName,
animationLevel: global.settings.byKey.animationLevel,
};
},
)(DeleteChatModal));

View File

@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../global';
import type { ApiChat, ApiTypingStatus } from '../../api/types';
import type { GlobalState } from '../../global/types';
import type { AnimationLevel } from '../../types';
import { MediaViewerOrigin } from '../../types';
import {
@ -43,6 +44,7 @@ type StateProps =
chat?: ApiChat;
onlineCount?: number;
areMessagesLoaded: boolean;
animationLevel: AnimationLevel;
}
& Pick<GlobalState, 'lastSyncTime'>;
@ -61,6 +63,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
chat,
onlineCount,
areMessagesLoaded,
animationLevel,
lastSyncTime,
}) => {
const {
@ -145,7 +148,8 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
size={avatarSize}
chat={chat}
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
noVideo={!withVideoAvatar}
withVideo={withVideoAvatar}
animationLevel={animationLevel}
/>
<div className="info">
<div className="title">
@ -184,7 +188,11 @@ export default memo(withGlobal<OwnProps>(
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId));
return {
lastSyncTime, chat, onlineCount, areMessagesLoaded,
lastSyncTime,
chat,
onlineCount,
areMessagesLoaded,
animationLevel: global.settings.byKey.animationLevel,
};
},
)(GroupChatInfo));

View File

@ -61,7 +61,6 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
user={user}
size="small"
isSavedMessages={user?.isSelf}
noVideo
/>
);

View File

@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../global';
import type { ApiUser, ApiTypingStatus, ApiUserStatus } from '../../api/types';
import type { GlobalState } from '../../global/types';
import type { AnimationLevel } from '../../types';
import { MediaViewerOrigin } from '../../types';
import { selectChatMessages, selectUser, selectUserStatus } from '../../global/selectors';
@ -40,6 +41,7 @@ type StateProps =
user?: ApiUser;
userStatus?: ApiUserStatus;
isSavedMessages?: boolean;
animationLevel: AnimationLevel;
areMessagesLoaded: boolean;
serverTimeOffset: number;
}
@ -61,6 +63,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
userStatus,
isSavedMessages,
areMessagesLoaded,
animationLevel,
lastSyncTime,
serverTimeOffset,
}) => {
@ -136,7 +139,8 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
user={user}
isSavedMessages={isSavedMessages}
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
noVideo={!withVideoAvatar}
withVideo={withVideoAvatar}
animationLevel={animationLevel}
/>
<div className="info">
{isSavedMessages ? (
@ -166,7 +170,13 @@ export default memo(withGlobal<OwnProps>(
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
return {
lastSyncTime, user, userStatus, isSavedMessages, areMessagesLoaded, serverTimeOffset,
lastSyncTime,
user,
userStatus,
isSavedMessages,
areMessagesLoaded,
serverTimeOffset,
animationLevel: global.settings.byKey.animationLevel,
};
},
)(PrivateChatInfo));

View File

@ -6,6 +6,7 @@ import { getActions, withGlobal } from '../../global';
import type { ApiUser, ApiChat, ApiUserStatus } from '../../api/types';
import type { GlobalState } from '../../global/types';
import type { AnimationLevel } from '../../types';
import { MediaViewerOrigin } from '../../types';
import { IS_TOUCH_ENV } from '../../util/environment';
@ -40,7 +41,7 @@ type StateProps =
userStatus?: ApiUserStatus;
chat?: ApiChat;
isSavedMessages?: boolean;
animationLevel: 0 | 1 | 2;
animationLevel: AnimationLevel;
serverTimeOffset: number;
mediaId?: number;
avatarOwnerId?: string;

View File

@ -9,6 +9,7 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type {
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus, ApiFormattedText, ApiUserStatus,
} from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { MAIN_THREAD_ID } from '../../../api/types';
import { ANIMATION_END_DELAY } from '../../../config';
@ -82,7 +83,7 @@ type StateProps = {
lastMessageSender?: ApiUser;
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
draft?: ApiFormattedText;
animationLevel?: number;
animationLevel?: AnimationLevel;
isSelected?: boolean;
canScrollDown?: boolean;
canChangeFolder?: boolean;
@ -315,6 +316,8 @@ const Chat: FC<OwnProps & StateProps> = ({
userStatus={userStatus}
isSavedMessages={user?.isSelf}
lastSyncTime={lastSyncTime}
animationLevel={animationLevel}
withVideo
observeIntersection={observeIntersection}
/>
{chat.isCallActive && chat.isCallNotEmpty && (

View File

@ -4,7 +4,7 @@ import React, {
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ISettings } from '../../../types';
import type { AnimationLevel, ISettings } from '../../../types';
import { LeftColumnContent, SettingsScreens } from '../../../types';
import type { ApiChat } from '../../../api/types';
import type { GlobalState } from '../../../global/types';
@ -63,7 +63,7 @@ type StateProps =
globalSearchChatId?: string;
searchDate?: number;
theme: ISettings['theme'];
animationLevel: 0 | 1 | 2;
animationLevel: AnimationLevel;
chatsById?: Record<string, ApiChat>;
isMessageListOpen: boolean;
isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized'];

View File

@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../../global';
import type {
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus,
} from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
import {
@ -47,6 +48,7 @@ type StateProps = {
privateChatUser?: ApiUser;
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
lastSyncTime?: number;
animationLevel?: AnimationLevel;
};
const ChatMessage: FC<OwnProps & StateProps> = ({
@ -55,6 +57,7 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
chatId,
chat,
privateChatUser,
animationLevel,
lastSyncTime,
}) => {
const { focusMessage } = getActions();
@ -87,6 +90,8 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
user={privateChatUser}
isSavedMessages={privateChatUser?.isSelf}
lastSyncTime={lastSyncTime}
withVideo
animationLevel={animationLevel}
/>
<div className="info">
<div className="info-row">
@ -141,6 +146,7 @@ export default memo(withGlobal<OwnProps>(
return {
chat,
lastSyncTime: global.lastSyncTime,
animationLevel: global.settings.byKey.animationLevel,
...(privateChatUserId && { privateChatUser: selectUser(global, privateChatUserId) }),
};
},

View File

@ -5,6 +5,7 @@ import React, {
import { getActions, withGlobal } from '../../../global';
import type { ApiUser } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { getUserFirstOrLastName } from '../../../global/helpers';
import renderText from '../../common/helpers/renderText';
@ -26,6 +27,7 @@ type StateProps = {
topUserIds?: string[];
usersById: Record<string, ApiUser>;
recentlyFoundChatIds?: string[];
animationLevel: AnimationLevel;
};
const SEARCH_CLOSE_TIMEOUT_MS = 250;
@ -34,7 +36,10 @@ const NBSP = '\u00A0';
const runThrottled = throttle((cb) => cb(), 60000, true);
const RecentContacts: FC<OwnProps & StateProps> = ({
topUserIds, usersById, recentlyFoundChatIds,
topUserIds,
usersById,
recentlyFoundChatIds,
animationLevel,
onReset,
}) => {
const {
@ -72,7 +77,7 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
<div ref={topUsersRef} className="top-peers no-selection">
{topUserIds.map((userId) => (
<div className="top-peer-item" onClick={() => handleClick(userId)} dir={lang.isRtl ? 'rtl' : undefined}>
<Avatar user={usersById[userId]} />
<Avatar user={usersById[userId]} animationLevel={animationLevel} withVideo />
<div className="top-peer-name">{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}</div>
</div>
))}
@ -112,11 +117,13 @@ export default memo(withGlobal<OwnProps>(
const { userIds: topUserIds } = global.topPeers;
const usersById = global.users.byId;
const { recentlyFoundChatIds } = global.globalSearch;
const { animationLevel } = global.settings.byKey;
return {
topUserIds,
usersById,
recentlyFoundChatIds,
animationLevel,
};
},
)(RecentContacts));

View File

@ -3,6 +3,7 @@ import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiUser, ApiWebSession } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { getUserFullName } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName';
@ -25,10 +26,15 @@ type OwnProps = {
type StateProps = {
session?: ApiWebSession;
bot?: ApiUser;
animationLevel: AnimationLevel;
};
const SettingsActiveWebsite: FC<OwnProps & StateProps> = ({
isOpen, session, bot, onClose,
isOpen,
session,
bot,
animationLevel,
onClose,
}) => {
const { terminateWebAuthorization } = getActions();
const lang = useLang();
@ -70,7 +76,7 @@ const SettingsActiveWebsite: FC<OwnProps & StateProps> = ({
onClose={onClose}
className={styles.root}
>
<Avatar className={styles.avatar} user={renderingBot} size="large" />
<Avatar className={styles.avatar} user={renderingBot} size="large" animationLevel={animationLevel} withVideo />
<h3 className={styles.title} dir="auto">{getUserFullName(renderingBot)}</h3>
<div className={styles.date} aria-label={lang('PrivacySettings.LastSeen')}>
{renderingSession?.domain}
@ -99,5 +105,6 @@ export default memo(withGlobal<OwnProps>((global, { hash }) => {
return {
session,
bot,
animationLevel: global.settings.byKey.animationLevel,
};
})(SettingsActiveWebsite));

View File

@ -5,6 +5,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiWebSession } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { formatPastTimeShort } from '../../../util/dateFormat';
import { getUserFullName } from '../../../global/helpers';
@ -29,12 +30,14 @@ type OwnProps = {
type StateProps = {
byHash: Record<string, ApiWebSession>;
orderedHashes: string[];
animationLevel: AnimationLevel;
};
const SettingsActiveWebsites: FC<OwnProps & StateProps> = ({
isActive,
byHash,
orderedHashes,
animationLevel,
onReset,
}) => {
const {
@ -110,7 +113,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} user={bot} size="tiny" animationLevel={animationLevel} withVideo />
<div className="multiline-menu-item full-size" dir="auto">
<span className="date">{formatPastTimeShort(lang, session.dateActive * 1000)}</span>
<span className="title">{getUserFullName(bot)}</span>
@ -161,6 +164,7 @@ export default memo(withGlobal<OwnProps>(
return {
byHash,
orderedHashes,
animationLevel: global.settings.byKey.animationLevel,
};
},
)(SettingsActiveWebsites));

View File

@ -79,7 +79,7 @@ const SettingsPrivacyBlockedUsers: FC<OwnProps & StateProps> = ({
}]}
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
>
<Avatar size="medium" user={user} chat={chat} noVideo />
<Avatar size="medium" user={user} chat={chat} />
<div className="contact-info" dir="auto">
<h3 dir="auto">{renderText((isPrivate ? getUserFullName(user) : getChatTitle(lang, chat!)) || '')}</h3>
{user?.phoneNumber && (

View File

@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../global';
import type {
ApiContact, ApiError, ApiInviteInfo, ApiPhoto,
} from '../../api/types';
import type { AnimationLevel } from '../../types';
import getReadableErrorText from '../../util/getReadableErrorText';
import { pick } from '../../util/iteratees';
@ -20,9 +21,10 @@ import './Dialogs.scss';
type StateProps = {
dialogs: (ApiError | ApiInviteInfo)[];
animationLevel: AnimationLevel;
};
const Dialogs: FC<StateProps> = ({ dialogs }) => {
const Dialogs: FC<StateProps> = ({ dialogs, animationLevel }) => {
const {
dismissDialog,
acceptInviteConfirmation,
@ -46,7 +48,7 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
function renderInviteHeader(title: string, photo?: ApiPhoto) {
return (
<div className="modal-header">
{photo && <Avatar size="small" photo={photo} />}
{photo && <Avatar size="small" photo={photo} animationLevel={animationLevel} withVideo />}
<div className="modal-title">
{renderText(title)}
</div>
@ -194,5 +196,10 @@ function getErrorHeader(error: ApiError) {
}
export default memo(withGlobal(
(global): StateProps => pick(global, ['dialogs']),
(global): StateProps => {
return {
dialogs: global.dialogs,
animationLevel: global.settings.byKey.animationLevel,
};
},
)(Dialogs));

View File

@ -4,7 +4,7 @@ import React, {
} from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global';
import type { LangCode } from '../../types';
import type { AnimationLevel, LangCode } from '../../types';
import type {
ApiChat, ApiMessage, ApiUpdateAuthorizationStateType, ApiUpdateConnectionStateType, ApiUser,
} from '../../api/types';
@ -94,7 +94,7 @@ type StateProps = {
openedCustomEmojiSetIds?: string[];
activeGroupCallId?: string;
isServiceChatReady?: boolean;
animationLevel: number;
animationLevel: AnimationLevel;
language?: LangCode;
wasTimeFormatSetManually?: boolean;
isPhoneCallActive?: boolean;

View File

@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiPremiumGiftOption, ApiUser } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { formatCurrency } from '../../../util/formatCurrency';
import renderText from '../../common/helpers/renderText';
@ -31,10 +32,16 @@ type StateProps = {
gifts?: ApiPremiumGiftOption[];
monthlyCurrency?: string;
monthlyAmount?: number;
animationLevel: AnimationLevel;
};
const GiftPremiumModal: FC<OwnProps & StateProps> = ({
isOpen, user, gifts, monthlyCurrency, monthlyAmount,
isOpen,
user,
gifts,
monthlyCurrency,
monthlyAmount,
animationLevel,
}) => {
const { openPremiumModal, closeGiftPremiumModal, openUrl } = getActions();
@ -116,7 +123,7 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
>
<i className="icon-close" />
</Button>
<Avatar user={renderedUser} size="jumbo" className={styles.avatar} />
<Avatar user={renderedUser} size="jumbo" className={styles.avatar} animationLevel={animationLevel} withVideo />
<h2 className={styles.headerText}>
{lang('GiftTelegramPremiumTitle')}
</h2>
@ -162,5 +169,6 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
gifts,
monthlyCurrency,
monthlyAmount: monthlyAmount ? Number(monthlyAmount) : undefined,
animationLevel: global.settings.byKey.animationLevel,
};
})(GiftPremiumModal));

View File

@ -6,6 +6,7 @@ import React, {
import type {
ApiChat, ApiMessage, ApiUser,
} from '../../api/types';
import type { AnimationLevel } from '../../types';
import { MediaViewerOrigin } from '../../types';
import { getActions, withGlobal } from '../../global';
@ -62,7 +63,7 @@ type StateProps = {
message?: ApiMessage;
chatMessages?: Record<number, ApiMessage>;
collectionIds?: number[];
animationLevel: 0 | 1 | 2;
animationLevel: AnimationLevel;
};
const ANIMATION_DURATION = 350;

View File

@ -5,6 +5,7 @@ import { withGlobal } from '../../global';
import type {
ApiChat, ApiDimensions, ApiMessage, ApiUser,
} from '../../api/types';
import type { AnimationLevel } from '../../types';
import { MediaViewerOrigin } from '../../types';
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
@ -30,7 +31,7 @@ type OwnProps = {
avatarOwnerId?: string;
origin?: MediaViewerOrigin;
isActive?: boolean;
animationLevel: 0 | 1 | 2;
animationLevel: AnimationLevel;
onClose: () => void;
onFooterClick: () => void;
setControlsVisible?: (isVisible: boolean) => void;

View File

@ -3,7 +3,7 @@ import React, {
memo, useCallback, useEffect, useRef, useState,
} from '../../lib/teact/teact';
import type { MediaViewerOrigin } from '../../types';
import type { AnimationLevel, MediaViewerOrigin } from '../../types';
import type { RealTouchEvent } from '../../util/captureEvents';
import { animateNumber, timingFunctions } from '../../util/animation';
@ -38,7 +38,7 @@ type OwnProps = {
threadId?: number;
avatarOwnerId?: string;
origin?: MediaViewerOrigin;
animationLevel: 0 | 1 | 2;
animationLevel: AnimationLevel;
onClose: () => void;
hasFooter?: boolean;
onFooterClick: () => void;

View File

@ -3,6 +3,7 @@ import React, { useCallback } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { ApiChat, ApiMessage, ApiUser } from '../../api/types';
import type { AnimationLevel } from '../../types';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
import { getSenderTitle, isUserId } from '../../global/helpers';
@ -29,6 +30,7 @@ type OwnProps = {
type StateProps = {
sender?: ApiUser | ApiChat;
message?: ApiMessage;
animationLevel: AnimationLevel;
};
const ANIMATION_DURATION = 350;
@ -39,6 +41,7 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
sender,
isAvatar,
message,
animationLevel,
}) => {
const {
closeMediaViewer,
@ -70,9 +73,9 @@ 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" user={sender as ApiUser} animationLevel={animationLevel} withVideo />
) : (
<Avatar key={sender.id} size="medium" chat={sender as ApiChat} />
<Avatar key={sender.id} size="medium" chat={sender as ApiChat} animationLevel={animationLevel} withVideo />
)}
<div className="meta">
<div className="title" dir="auto">
@ -90,14 +93,16 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
export default withGlobal<OwnProps>(
(global, { chatId, messageId, isAvatar }): StateProps => {
const { animationLevel } = global.settings.byKey;
if (isAvatar && chatId) {
return {
sender: isUserId(chatId) ? selectUser(global, chatId) : selectChat(global, chatId),
animationLevel,
};
}
if (!messageId || !chatId) {
return {};
return { animationLevel };
}
const message = selectChatMessage(global, chatId, messageId);
@ -105,6 +110,7 @@ export default withGlobal<OwnProps>(
return {
message,
sender: message && selectSender(global, message),
animationLevel,
};
},
)(SenderInfo);

View File

@ -7,6 +7,7 @@ import { getActions, getGlobal, withGlobal } from '../../global';
import type { ApiBotInfo, ApiMessage, ApiRestrictionReason } from '../../api/types';
import { MAIN_THREAD_ID } from '../../api/types';
import type { MessageListType } from '../../global/types';
import type { AnimationLevel } from '../../types';
import { LoadMoreDirection } from '../../types';
import { ANIMATION_END_DELAY, LOCAL_MESSAGE_MIN_ID, MESSAGE_LIST_SLICE } from '../../config';
@ -95,7 +96,7 @@ type StateProps = {
restrictionReason?: ApiRestrictionReason;
focusingId?: number;
isSelectModeActive?: boolean;
animationLevel?: number;
animationLevel?: AnimationLevel;
lastMessage?: ApiMessage;
isLoadingBotInfo?: boolean;
botInfo?: ApiBotInfo;

View File

@ -10,7 +10,7 @@ import type {
MessageListType,
ActiveEmojiInteraction,
} from '../../global/types';
import type { ThemeKey } from '../../types';
import type { AnimationLevel, ThemeKey } from '../../types';
import {
MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN,
@ -100,7 +100,7 @@ type StateProps = {
isSeenByModalOpen: boolean;
isReactorListModalOpen: boolean;
isGiftPremiumModalOpen?: boolean;
animationLevel?: number;
animationLevel: AnimationLevel;
shouldSkipHistoryAnimations?: boolean;
currentTransitionKey: number;
isChannel?: boolean;

View File

@ -5,6 +5,7 @@ import React, {
import { getActions, getGlobal, withGlobal } from '../../global';
import type { ApiMessage } from '../../api/types';
import type { AnimationLevel } from '../../types';
import { LoadMoreDirection } from '../../types';
import useLang from '../../hooks/useLang';
@ -37,6 +38,7 @@ export type OwnProps = {
export type StateProps = Pick<ApiMessage, 'reactors' | 'reactions' | 'seenByUserIds'> & {
chatId?: string;
messageId?: number;
animationLevel: AnimationLevel;
};
const ReactorListModal: FC<OwnProps & StateProps> = ({
@ -46,6 +48,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
chatId,
messageId,
seenByUserIds,
animationLevel,
}) => {
const {
loadReactors,
@ -169,7 +172,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleClick(userId)}
>
<Avatar user={user} size="small" />
<Avatar user={user} size="small" animationLevel={animationLevel} withVideo />
<div className="title">
<h3 dir="auto">{fullName && renderText(fullName)}</h3>
{user.isPremium && <PremiumIcon />}
@ -204,6 +207,7 @@ export default memo(withGlobal<OwnProps>(
reactions: message?.reactions,
reactors: message?.reactors,
seenByUserIds: message?.seenByUserIds,
animationLevel: global.settings.byKey.animationLevel,
};
},
)(ReactorListModal));

View File

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

View File

@ -1128,7 +1128,6 @@ const Composer: FC<OwnProps & StateProps> = ({
user={sendAsUser}
chat={sendAsChat}
size="tiny"
noVideo
/>
</Button>
)}

View File

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

View File

@ -62,7 +62,6 @@ const CommentButton: FC<OwnProps> = ({
size="small"
user={isUserId(user.id) ? user as ApiUser : undefined}
chat={!isUserId(user.id) ? user as ApiChat : undefined}
noVideo
/>
))}
</div>

View File

@ -3,6 +3,7 @@ import React, { useCallback } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiUser, ApiContact, ApiCountryCode } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { selectUser } from '../../../global/selectors';
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
@ -19,12 +20,13 @@ type OwnProps = {
type StateProps = {
user?: ApiUser;
phoneCodeList: ApiCountryCode[];
animationLevel: AnimationLevel;
};
const UNREGISTERED_CONTACT_ID = '0';
const Contact: FC<OwnProps & StateProps> = ({
contact, user, phoneCodeList,
contact, user, phoneCodeList, animationLevel,
}) => {
const { openChat } = getActions();
@ -45,7 +47,7 @@ const Contact: FC<OwnProps & StateProps> = ({
className={buildClassName('Contact', isRegistered && 'interactive')}
onClick={isRegistered ? handleClick : undefined}
>
<Avatar size="large" user={user} text={firstName || lastName} />
<Avatar size="large" user={user} text={firstName || lastName} animationLevel={animationLevel} withVideo />
<div className="contact-info">
<div className="contact-name">{firstName} {lastName}</div>
<div className="contact-phone">{formatPhoneNumberWithCode(phoneCodeList, phoneNumber)}</div>
@ -60,6 +62,7 @@ export default withGlobal<OwnProps>(
return {
user: selectUser(global, contact.userId),
phoneCodeList,
animationLevel: global.settings.byKey.animationLevel,
};
},
)(Contact);

View File

@ -18,7 +18,9 @@ import type {
ApiThreadInfo,
ApiAvailableReaction,
} from '../../../api/types';
import type { FocusDirection, IAlbum, ISettings } from '../../../types';
import type {
AnimationLevel, FocusDirection, IAlbum, ISettings,
} from '../../../types';
import {
AudioOrigin,
} from '../../../types';
@ -198,6 +200,7 @@ type StateProps = {
transcribedText?: string;
isTranscriptionError?: boolean;
isPremium: boolean;
animationLevel: AnimationLevel;
};
type MetaPosition =
@ -285,6 +288,7 @@ const Message: FC<OwnProps & StateProps> = ({
threadInfo,
hasUnreadReaction,
memoFirstUnreadIdRef,
animationLevel,
}) => {
const {
toggleMessageSelection,
@ -617,6 +621,8 @@ const Message: FC<OwnProps & StateProps> = ({
lastSyncTime={lastSyncTime}
onClick={(avatarUser || avatarChat) ? handleAvatarClick : undefined}
observeIntersection={observeIntersectionForMedia}
animationLevel={animationLevel}
withVideo
/>
{isAvatarPremium && <PremiumIcon className="chat-avatar-premium" />}
</>
@ -1176,6 +1182,7 @@ export default memo(withGlobal<OwnProps>(
isTranscribing: transcriptionId !== undefined && global.transcriptions[transcriptionId]?.isPending,
transcribedText: transcriptionId !== undefined ? global.transcriptions[transcriptionId]?.text : undefined,
isPremium: selectIsCurrentUserPremium(global),
animationLevel: global.settings.byKey.animationLevel,
};
},
)(Message));

View File

@ -348,7 +348,6 @@ const MessageContextMenu: FC<OwnProps> = ({
<Avatar
size="micro"
user={user}
noVideo
/>
))}
</div>

View File

@ -234,7 +234,6 @@ const Poll: FC<OwnProps & StateProps> = ({
<Avatar
size="micro"
user={user}
noVideo
/>
))}
</div>

View File

@ -68,7 +68,7 @@ const ReactionButton: FC<{
/>
{recentReactors?.length ? (
<div className="avatars">
{recentReactors.map((user) => <Avatar user={user} size="micro" noVideo />)}
{recentReactors.map((user) => <Avatar user={user} size="micro" />)}
</div>
) : formatIntegerCompact(reaction.count)}
</Button>

View File

@ -1,8 +1,9 @@
import type { FC } from '../../lib/teact/teact';
import React, { useMemo, memo, useRef } from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { ApiMessage, ApiUser, ApiChat } from '../../api/types';
import type { AnimationLevel } from '../../types';
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import {
@ -43,18 +44,20 @@ type StateProps = {
query?: string;
totalCount?: number;
foundIds?: number[];
animationLevel?: AnimationLevel;
};
const RightSearch: FC<OwnProps & StateProps> = ({
chatId,
threadId,
onClose,
isActive,
chat,
messagesById,
query,
totalCount,
foundIds,
animationLevel,
onClose,
}) => {
const {
searchTextMessagesLocal,
@ -129,7 +132,7 @@ const RightSearch: FC<OwnProps & StateProps> = ({
className="chat-item-clickable search-result-message m-0"
onClick={onClick}
>
<Avatar chat={senderChat} user={senderUser} />
<Avatar chat={senderChat} user={senderUser} animationLevel={animationLevel} withVideo />
<div className="info">
<div className="title">
<h3 dir="auto">{title && renderText(title)}</h3>
@ -189,6 +192,7 @@ export default memo(withGlobal<OwnProps>(
query,
totalCount,
foundIds,
animationLevel: global.settings.byKey.animationLevel,
};
},
)(RightSearch));

View File

@ -2,6 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { AnimationLevel } from '../../../types';
import type { ApiUser } from '../../../api/types';
import useLang from '../../../hooks/useLang';
@ -27,17 +28,19 @@ type OwnProps = {
type StateProps = {
user?: ApiUser;
isSavedMessages?: boolean;
animationLevel: AnimationLevel;
serverTimeOffset: number;
};
const JoinRequest: FC<OwnProps & StateProps> = ({
userId,
chatId,
about,
date,
isChannel,
user,
animationLevel,
serverTimeOffset,
chatId,
}) => {
const { openChat, hideChatJoinRequest } = getActions();
@ -70,6 +73,8 @@ const JoinRequest: FC<OwnProps & StateProps> = ({
key={userId}
size="medium"
user={user}
animationLevel={animationLevel}
withVideo
/>
<div className={buildClassName('user-info')}>
<div className={buildClassName('user-name')}>{fullName}</div>
@ -96,6 +101,7 @@ export default memo(withGlobal<OwnProps>(
return {
user,
animationLevel: global.settings.byKey.animationLevel,
serverTimeOffset: global.serverTimeOffset,
};
},

View File

@ -27,6 +27,7 @@ export interface IAlbum {
}
export type ThemeKey = 'light' | 'dark';
export type AnimationLevel = 0 | 1 | 2;
export interface IThemeSettings {
background?: string;
@ -59,7 +60,7 @@ export interface ISettings extends NotifySettings, Record<string, any> {
theme: ThemeKey;
shouldUseSystemTheme: boolean;
messageTextSize: number;
animationLevel: 0 | 1 | 2;
animationLevel: AnimationLevel;
messageSendKeyCombo: 'enter' | 'ctrl-enter';
canAutoLoadPhotoFromContacts: boolean;
canAutoLoadPhotoInPrivateChats: boolean;