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} onClick={handleOnClick}
ref={anchorRef} ref={anchorRef}
> >
<Avatar user={user} chat={chat} size="medium" noVideo /> <Avatar user={user} chat={chat} size="medium" />
<div className="info"> <div className="info">
<span className="name">{name}</span> <span className="name">{name}</span>
<span className={buildClassName('about', aboutColor)}>{aboutText}</span> <span className={buildClassName('about', aboutColor)}>{aboutText}</span>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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