Settings: Performance mode (#3045)

Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
This commit is contained in:
Alexander Zinchuk 2023-04-25 17:24:26 +04:00
parent 8535fcff66
commit 4f42b676ce
149 changed files with 1190 additions and 610 deletions

1
package-lock.json generated
View File

@ -107,6 +107,7 @@
} }
}, },
"dev/eslint-multitab": { "dev/eslint-multitab": {
"name": "eslint-plugin-eslint-multitab-tt",
"version": "0.0.0", "version": "0.0.0",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",

View File

@ -5,7 +5,6 @@ import React, {
import { getActions, getGlobal, withGlobal } from '../../../global'; import { getActions, getGlobal, withGlobal } from '../../../global';
import type { ApiGroupCall } from '../../../api/types'; import type { ApiGroupCall } 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,7 +25,6 @@ type OwnProps = {
type StateProps = { type StateProps = {
groupCall?: ApiGroupCall; groupCall?: ApiGroupCall;
isActive: boolean; isActive: boolean;
animationLevel: AnimationLevel;
}; };
const GroupCallTopPane: FC<OwnProps & StateProps> = ({ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
@ -35,7 +33,6 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
className, className,
groupCall, groupCall,
hasPinnedOffset, hasPinnedOffset,
animationLevel,
}) => { }) => {
const { const {
requestMasterAndJoinGroupCall, requestMasterAndJoinGroupCall,
@ -112,12 +109,12 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
<div className="avatars"> <div className="avatars">
{fetchedParticipants.map((p) => { {fetchedParticipants.map((p) => {
if (!p) return undefined; if (!p) return undefined;
return ( return (
<Avatar <Avatar
key={p.user ? p.user.id : p.chat.id} key={p.user ? p.user.id : p.chat.id}
chat={p.chat} chat={p.chat}
user={p.user} user={p.user}
animationLevel={animationLevel}
/> />
); );
})} })}
@ -142,7 +139,6 @@ export default memo(withGlobal<OwnProps>(
? groupCall.participantsCount > 0 && groupCall.isLoaded ? groupCall.participantsCount > 0 && groupCall.isLoaded
: chat && chat.isCallNotEmpty && chat.isCallActive, : chat && chat.isCallNotEmpty && chat.isCallActive,
), ),
animationLevel: global.settings.byKey.animationLevel,
}; };
}, },
)(GroupCallTopPane)); )(GroupCallTopPane));

View File

@ -6,7 +6,6 @@ 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,
@ -41,7 +40,6 @@ type StateProps = {
phoneCall?: ApiPhoneCall; phoneCall?: ApiPhoneCall;
isOutgoing: boolean; isOutgoing: boolean;
isCallPanelVisible?: boolean; isCallPanelVisible?: boolean;
animationLevel: AnimationLevel;
}; };
const PhoneCall: FC<StateProps> = ({ const PhoneCall: FC<StateProps> = ({
@ -49,7 +47,6 @@ const PhoneCall: FC<StateProps> = ({
isOutgoing, isOutgoing,
phoneCall, phoneCall,
isCallPanelVisible, isCallPanelVisible,
animationLevel,
}) => { }) => {
const lang = useLang(); const lang = useLang();
const { const {
@ -240,9 +237,6 @@ 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'}
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} />}
@ -379,7 +373,6 @@ export default memo(withGlobal(
user, user,
isOutgoing: phoneCall?.adminId === currentUserId, isOutgoing: phoneCall?.adminId === currentUserId,
phoneCall: isMasterTab ? phoneCall : undefined, phoneCall: isMasterTab ? phoneCall : undefined,
animationLevel: global.settings.byKey.animationLevel,
}; };
}, },
)(PhoneCall)); )(PhoneCall));

View File

@ -4,8 +4,8 @@ import React, {
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getGlobal } from '../../global'; import { getGlobal } from '../../global';
import { ANIMATION_LEVEL_MAX } from '../../config';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { selectCanAnimateInterface } from '../../global/selectors';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
@ -25,7 +25,7 @@ const AnimatedCounter: FC<OwnProps> = ({
const prevTextRef = useRef<string>(); const prevTextRef = useRef<string>();
const [isAnimating, markAnimating, unmarkAnimating] = useFlag(false); const [isAnimating, markAnimating, unmarkAnimating] = useFlag(false);
const shouldAnimate = getGlobal().settings.byKey.animationLevel === ANIMATION_LEVEL_MAX; const shouldAnimate = selectCanAnimateInterface(getGlobal());
const textElement = useMemo(() => { const textElement = useMemo(() => {
if (!shouldAnimate) { if (!shouldAnimate) {

View File

@ -8,11 +8,9 @@ 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 { ANIMATION_LEVEL_MAX, IS_TEST } from '../../config'; import { IS_TEST } from '../../config';
import { VIDEO_AVATARS_DISABLED } from '../../util/windowEnvironment';
import { import {
getChatAvatarHash, getChatAvatarHash,
getChatTitle, getChatTitle,
@ -30,7 +28,6 @@ import renderText from './helpers/renderText';
import useMedia from '../../hooks/useMedia'; import useMedia from '../../hooks/useMedia';
import useMediaTransition from '../../hooks/useMediaTransition'; import useMediaTransition from '../../hooks/useMediaTransition';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
import OptimizedVideo from '../ui/OptimizedVideo'; import OptimizedVideo from '../ui/OptimizedVideo';
@ -52,12 +49,9 @@ type OwnProps = {
text?: string; text?: string;
isSavedMessages?: boolean; isSavedMessages?: boolean;
withVideo?: boolean; withVideo?: boolean;
noLoop?: boolean;
loopIndefinitely?: boolean; loopIndefinitely?: boolean;
animationLevel?: AnimationLevel;
noPersonalPhoto?: boolean; noPersonalPhoto?: boolean;
lastSyncTime?: number; lastSyncTime?: number;
forceVideo?: boolean;
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void; onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void;
}; };
@ -72,13 +66,9 @@ const Avatar: FC<OwnProps> = ({
text, text,
isSavedMessages, isSavedMessages,
withVideo, withVideo,
noLoop,
loopIndefinitely, loopIndefinitely,
lastSyncTime, lastSyncTime,
forceVideo,
animationLevel,
noPersonalPhoto, noPersonalPhoto,
observeIntersection,
onClick, onClick,
}) => { }) => {
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -90,14 +80,7 @@ const Avatar: FC<OwnProps> = ({
let imageHash: string | undefined; let imageHash: string | undefined;
let videoHash: string | undefined; let videoHash: string | undefined;
const canShowVideo = ( const shouldLoadVideo = withVideo && photo?.isVideo;
withVideo && !VIDEO_AVATARS_DISABLED && animationLevel === ANIMATION_LEVEL_MAX
&& user?.isPremium && user?.hasVideoAvatar
);
const isIntersectingForVideo = useIsIntersecting(
ref, canShowVideo ? observeIntersection : undefined,
);
const shouldLoadVideo = isIntersectingForVideo && (canShowVideo || (forceVideo && photo?.isVideo));
const shouldFetchBig = size === 'jumbo'; const shouldFetchBig = size === 'jumbo';
if (!isSavedMessages && !isDeleted) { if (!isSavedMessages && !isDeleted) {
@ -117,7 +100,7 @@ const Avatar: FC<OwnProps> = ({
const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl, lastSyncTime); const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl, lastSyncTime);
const hasBlobUrl = Boolean(imgBlobUrl || videoBlobUrl); const hasBlobUrl = Boolean(imgBlobUrl || videoBlobUrl);
// `videoBlobUrl` can be taken from memory cache, so we need to check `shouldLoadVideo` again // `videoBlobUrl` can be taken from memory cache, so we need to check `shouldLoadVideo` again
const shouldPlayVideo = Boolean(isIntersectingForVideo && videoBlobUrl && shouldLoadVideo); const shouldPlayVideo = Boolean(videoBlobUrl && shouldLoadVideo);
const transitionClassNames = useMediaTransition(hasBlobUrl); const transitionClassNames = useMediaTransition(hasBlobUrl);
@ -134,10 +117,10 @@ const Avatar: FC<OwnProps> = ({
if (loopIndefinitely) return; if (loopIndefinitely) return;
videoLoopCountRef.current += 1; videoLoopCountRef.current += 1;
if (videoLoopCountRef.current >= LOOP_COUNT || noLoop) { if (videoLoopCountRef.current >= LOOP_COUNT) {
video.style.display = 'none'; video.style.display = 'none';
} }
}, [loopIndefinitely, noLoop, videoBlobUrl]); }, [loopIndefinitely, videoBlobUrl]);
const lang = useLang(); const lang = useLang();

View File

@ -71,7 +71,7 @@ const CustomEmoji: FC<OwnProps> = ({
} }
// An alternative to `withGlobal` to avoid adding numerous global containers // An alternative to `withGlobal` to avoid adding numerous global containers
const customEmoji = useCustomEmoji(documentId); const { customEmoji, canPlay } = useCustomEmoji(documentId);
const loopCountRef = useRef(0); const loopCountRef = useRef(0);
const [shouldLoop, setShouldLoop] = useState(true); const [shouldLoop, setShouldLoop] = useState(true);
@ -131,13 +131,13 @@ const CustomEmoji: FC<OwnProps> = ({
sticker={customEmoji} sticker={customEmoji}
isSmall={!isBig} isSmall={!isBig}
size={size} size={size}
noPlay={noPlay} noPlay={noPlay || !canPlay}
customColor={customColor} customColor={customColor}
thumbClassName={styles.thumb} thumbClassName={styles.thumb}
fullMediaClassName={styles.media} fullMediaClassName={styles.media}
shouldLoop={shouldLoop} shouldLoop={shouldLoop}
loopLimit={loopLimit} loopLimit={loopLimit}
shouldPreloadPreview={shouldPreloadPreview} shouldPreloadPreview={shouldPreloadPreview || noPlay || !canPlay}
forceOnHeavyAnimation={forceOnHeavyAnimation} forceOnHeavyAnimation={forceOnHeavyAnimation}
observeIntersectionForLoading={observeIntersectionForLoading} observeIntersectionForLoading={observeIntersectionForLoading}
observeIntersectionForPlaying={observeIntersectionForPlaying} observeIntersectionForPlaying={observeIntersectionForPlaying}

View File

@ -28,6 +28,7 @@ import animateHorizontalScroll from '../../util/animateHorizontalScroll';
import { pickTruthy, unique } from '../../util/iteratees'; import { pickTruthy, unique } from '../../util/iteratees';
import { isSameReaction } from '../../global/helpers'; import { isSameReaction } from '../../global/helpers';
import { import {
selectCanPlayAnimatedEmojis,
selectIsAlwaysHighPriorityEmoji, selectIsAlwaysHighPriorityEmoji,
selectIsChatWithSelf, selectIsChatWithSelf,
selectIsCurrentUserPremium, selectIsCurrentUserPremium,
@ -316,7 +317,7 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
) : ( ) : (
<StickerSetCover <StickerSetCover
stickerSet={stickerSet as ApiStickerSet} stickerSet={stickerSet as ApiStickerSet}
noAnimate={!canAnimate || !loadAndPlay} noPlay={!canAnimate || !loadAndPlay}
observeIntersection={observeIntersectionForCovers} observeIntersection={observeIntersectionForCovers}
sharedCanvasRef={withSharedCanvas ? (isHq ? sharedCanvasHqRef : sharedCanvasRef) : undefined} sharedCanvasRef={withSharedCanvas ? (isHq ? sharedCanvasHqRef : sharedCanvasRef) : undefined}
/> />
@ -332,7 +333,7 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
size={STICKER_SIZE_PICKER_HEADER} size={STICKER_SIZE_PICKER_HEADER}
title={stickerSet.title} title={stickerSet.title}
className={buttonClassName} className={buttonClassName}
noAnimate={!canAnimate || !loadAndPlay} noPlay={!canAnimate || !loadAndPlay}
observeIntersection={observeIntersectionForCovers} observeIntersection={observeIntersectionForCovers}
noContextMenu noContextMenu
isCurrentUserPremium isCurrentUserPremium
@ -451,7 +452,7 @@ export default memo(withGlobal<OwnProps>(
recentStatusEmojis: isStatusPicker ? recentStatusEmojis : undefined, recentStatusEmojis: isStatusPicker ? recentStatusEmojis : undefined,
stickerSetsById, stickerSetsById,
addedCustomEmojiIds: global.customEmojis.added.setIds, addedCustomEmojiIds: global.customEmojis.added.setIds,
canAnimate: global.settings.byKey.shouldLoopStickers, canAnimate: selectCanPlayAnimatedEmojis(global),
isSavedMessages, isSavedMessages,
isCurrentUserPremium: selectIsCurrentUserPremium(global), isCurrentUserPremium: selectIsCurrentUserPremium(global),
customEmojiFeaturedIds, customEmojiFeaturedIds,

View File

@ -7,7 +7,7 @@ import type { FC } from '../../lib/teact/teact';
import type { ApiSticker, ApiStickerSet } from '../../api/types'; import type { ApiSticker, ApiStickerSet } from '../../api/types';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { selectCanPlayAnimatedEmojis } from '../../global/selectors';
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
import usePrevious from '../../hooks/usePrevious'; import usePrevious from '../../hooks/usePrevious';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -24,10 +24,12 @@ export type OwnProps = {
type StateProps = { type StateProps = {
customEmojiSets?: ApiStickerSet[]; customEmojiSets?: ApiStickerSet[];
canPlayAnimatedEmojis?: boolean;
}; };
const CustomEmojiSetsModal: FC<OwnProps & StateProps> = ({ const CustomEmojiSetsModal: FC<OwnProps & StateProps> = ({
customEmojiSets, customEmojiSets,
canPlayAnimatedEmojis,
onClose, onClose,
}) => { }) => {
const { openStickerSet } = getActions(); const { openStickerSet } = getActions();
@ -64,6 +66,7 @@ const CustomEmojiSetsModal: FC<OwnProps & StateProps> = ({
stickerSet={customEmojiSet} stickerSet={customEmojiSet}
onClick={handleSetClick} onClick={handleSetClick}
observeIntersection={observeIntersectionForCovers} observeIntersection={observeIntersectionForCovers}
noPlay={!canPlayAnimatedEmojis}
/> />
))} ))}
</div> </div>
@ -77,6 +80,7 @@ export default memo(withGlobal<OwnProps>(
return { return {
customEmojiSets, customEmojiSets,
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
}; };
}, },
)(CustomEmojiSetsModal)); )(CustomEmojiSetsModal));

View File

@ -3,7 +3,6 @@ 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 {
@ -42,7 +41,6 @@ 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> = ({
@ -57,7 +55,6 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
currentUserId, currentUserId,
canDeleteForAll, canDeleteForAll,
contactName, contactName,
animationLevel,
onClose, onClose,
onCloseAnimationEnd, onCloseAnimationEnd,
}) => { }) => {
@ -128,8 +125,6 @@ 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>
@ -243,7 +238,6 @@ export default memo(withGlobal<OwnProps>(
currentUserId: global.currentUserId, currentUserId: global.currentUserId,
canDeleteForAll, canDeleteForAll,
contactName, contactName,
animationLevel: global.settings.byKey.animationLevel,
}; };
}, },
)(DeleteChatModal)); )(DeleteChatModal));

View File

@ -17,7 +17,7 @@
} }
} }
body.animation-level-1 & { body.no-page-transitions & {
.ripple-container { .ripple-container {
display: none; display: none;
} }

View File

@ -74,6 +74,8 @@
position: absolute; position: absolute;
.bubble { .bubble {
--offset-y: 0;
width: auto; width: auto;
} }
} }

View File

@ -152,6 +152,7 @@ const GifButton: FC<OwnProps> = ({
className="gif-unsave-button" className="gif-unsave-button"
color="dark" color="dark"
pill pill
noFastClick
onClick={handleUnsaveClick} onClick={handleUnsaveClick}
> >
<i className="icon icon-close gif-unsave-button-icon" /> <i className="icon icon-close gif-unsave-button-icon" />

View File

@ -9,7 +9,6 @@ import type {
ApiChat, ApiTopic, ApiThreadInfo, ApiTypingStatus, ApiChat, ApiTopic, ApiThreadInfo, ApiTypingStatus,
} from '../../api/types'; } from '../../api/types';
import type { GlobalState } from '../../global/types'; import type { GlobalState } from '../../global/types';
import type { AnimationLevel } from '../../types';
import type { LangFn } from '../../hooks/useLang'; import type { LangFn } from '../../hooks/useLang';
import { MediaViewerOrigin } from '../../types'; import { MediaViewerOrigin } from '../../types';
@ -20,7 +19,11 @@ import {
isChatSuperGroup, isChatSuperGroup,
} from '../../global/helpers'; } from '../../global/helpers';
import { import {
selectChat, selectChatMessages, selectChatOnlineCount, selectThreadInfo, selectThreadMessagesCount, selectChat,
selectChatMessages,
selectChatOnlineCount,
selectThreadInfo,
selectThreadMessagesCount,
} from '../../global/selectors'; } from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import renderText from './helpers/renderText'; import renderText from './helpers/renderText';
@ -47,7 +50,6 @@ type OwnProps = {
withFullInfo?: boolean; withFullInfo?: boolean;
withUpdatingStatus?: boolean; withUpdatingStatus?: boolean;
withChatType?: boolean; withChatType?: boolean;
withVideoAvatar?: boolean;
noRtl?: boolean; noRtl?: boolean;
noAvatar?: boolean; noAvatar?: boolean;
onClick?: VoidFunction; onClick?: VoidFunction;
@ -60,7 +62,6 @@ type StateProps =
topic?: ApiTopic; topic?: ApiTopic;
onlineCount?: number; onlineCount?: number;
areMessagesLoaded: boolean; areMessagesLoaded: boolean;
animationLevel: AnimationLevel;
messagesCount?: number; messagesCount?: number;
} }
& Pick<GlobalState, 'lastSyncTime'>; & Pick<GlobalState, 'lastSyncTime'>;
@ -77,13 +78,11 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
withFullInfo, withFullInfo,
withUpdatingStatus, withUpdatingStatus,
withChatType, withChatType,
withVideoAvatar,
threadInfo, threadInfo,
noRtl, noRtl,
chat, chat,
onlineCount, onlineCount,
areMessagesLoaded, areMessagesLoaded,
animationLevel,
lastSyncTime, lastSyncTime,
topic, topic,
messagesCount, messagesCount,
@ -187,12 +186,14 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
size={avatarSize} size={avatarSize}
chat={chat} chat={chat}
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined} onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
withVideo={withVideoAvatar}
animationLevel={animationLevel}
/> />
)} )}
{isTopic && ( {isTopic && (
<TopicIcon topic={topic!} className="topic-header-icon" size={TOPIC_ICON_SIZE} /> <TopicIcon
topic={topic!}
className="topic-header-icon"
size={TOPIC_ICON_SIZE}
/>
)} )}
<div className="info"> <div className="info">
{topic {topic
@ -238,7 +239,6 @@ export default memo(withGlobal<OwnProps>(
onlineCount, onlineCount,
topic, topic,
areMessagesLoaded, areMessagesLoaded,
animationLevel: global.settings.byKey.animationLevel,
messagesCount, messagesCount,
}; };
}, },

View File

@ -16,7 +16,7 @@
mask-repeat: no-repeat; mask-repeat: no-repeat;
mask-size: 0%; mask-size: 0%;
:global(body.animation-level-2) & { :global(body:not(.no-page-transitions)) & {
animation: 500ms ease-in circle-cut forwards; animation: 500ms ease-in circle-cut forwards;
} }
@ -90,7 +90,7 @@
} }
} }
:global(body.animation-level-2) .dots { :global(body:not(.no-page-transitions)) .dots {
animation: 20s linear infinite dots; animation: 20s linear infinite dots;
&::before { &::before {

View File

@ -79,13 +79,13 @@ function MessageText({
{[ {[
withSharedCanvas && <canvas ref={sharedCanvasRef} className="shared-canvas" />, withSharedCanvas && <canvas ref={sharedCanvasRef} className="shared-canvas" />,
withSharedCanvas && <canvas ref={sharedCanvasHqRef} className="shared-canvas" />, withSharedCanvas && <canvas ref={sharedCanvasHqRef} className="shared-canvas" />,
renderTextWithEntities( renderTextWithEntities({
trimText(text!, truncateLength), text: trimText(text!, truncateLength),
entities, entities,
highlight, highlight,
emojiSize, emojiSize,
shouldRenderAsHtml, shouldRenderAsHtml,
message.id, messageId: message.id,
isSimple, isSimple,
isProtected, isProtected,
observeIntersectionForLoading, observeIntersectionForLoading,
@ -93,8 +93,8 @@ function MessageText({
withTranslucentThumbs, withTranslucentThumbs,
sharedCanvasRef, sharedCanvasRef,
sharedCanvasHqRef, sharedCanvasHqRef,
textCacheBusterRef.current.toString(), cacheBuster: textCacheBusterRef.current.toString(),
), }),
].flat().filter(Boolean)} ].flat().filter(Boolean)}
</> </>
); );

View File

@ -8,10 +8,13 @@ import type {
ApiUser, ApiTypingStatus, ApiUserStatus, ApiChatMember, ApiUser, ApiTypingStatus, ApiUserStatus, ApiChatMember,
} from '../../api/types'; } 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';
import { getMainUsername, getUserStatus, isUserOnline } from '../../global/helpers'; import { getMainUsername, getUserStatus, isUserOnline } from '../../global/helpers';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import renderText from './helpers/renderText'; import renderText from './helpers/renderText';
@ -34,7 +37,6 @@ type OwnProps = {
withUsername?: boolean; withUsername?: boolean;
withFullInfo?: boolean; withFullInfo?: boolean;
withUpdatingStatus?: boolean; withUpdatingStatus?: boolean;
withVideoAvatar?: boolean;
emojiStatusSize?: number; emojiStatusSize?: number;
noStatusOrTyping?: boolean; noStatusOrTyping?: boolean;
noRtl?: boolean; noRtl?: boolean;
@ -46,7 +48,6 @@ type StateProps =
user?: ApiUser; user?: ApiUser;
userStatus?: ApiUserStatus; userStatus?: ApiUserStatus;
isSavedMessages?: boolean; isSavedMessages?: boolean;
animationLevel: AnimationLevel;
areMessagesLoaded: boolean; areMessagesLoaded: boolean;
} }
& Pick<GlobalState, 'lastSyncTime'>; & Pick<GlobalState, 'lastSyncTime'>;
@ -60,7 +61,6 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
withUsername, withUsername,
withFullInfo, withFullInfo,
withUpdatingStatus, withUpdatingStatus,
withVideoAvatar,
emojiStatusSize, emojiStatusSize,
noStatusOrTyping, noStatusOrTyping,
noRtl, noRtl,
@ -68,7 +68,6 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
userStatus, userStatus,
isSavedMessages, isSavedMessages,
areMessagesLoaded, areMessagesLoaded,
animationLevel,
lastSyncTime, lastSyncTime,
adminMember, adminMember,
}) => { }) => {
@ -173,8 +172,6 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
user={user} user={user}
isSavedMessages={isSavedMessages} isSavedMessages={isSavedMessages}
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined} onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
withVideo={withVideoAvatar}
animationLevel={animationLevel}
/> />
<div className="info"> <div className="info">
{renderNameTitle()} {renderNameTitle()}
@ -198,7 +195,6 @@ export default memo(withGlobal<OwnProps>(
userStatus, userStatus,
isSavedMessages, isSavedMessages,
areMessagesLoaded, areMessagesLoaded,
animationLevel: global.settings.byKey.animationLevel,
}; };
}, },
)(PrivateChatInfo)); )(PrivateChatInfo));

View File

@ -8,7 +8,6 @@ import type {
ApiUser, ApiChat, ApiUserStatus, ApiTopic, ApiPhoto, ApiUser, ApiChat, ApiUserStatus, ApiTopic, ApiPhoto,
} from '../../api/types'; } 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/windowEnvironment'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
@ -55,7 +54,6 @@ type StateProps =
userStatus?: ApiUserStatus; userStatus?: ApiUserStatus;
chat?: ApiChat; chat?: ApiChat;
isSavedMessages?: boolean; isSavedMessages?: boolean;
animationLevel: AnimationLevel;
mediaId?: number; mediaId?: number;
avatarOwnerId?: string; avatarOwnerId?: string;
topic?: ApiTopic; topic?: ApiTopic;
@ -78,7 +76,6 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
chat, chat,
isSavedMessages, isSavedMessages,
connectionState, connectionState,
animationLevel,
mediaId, mediaId,
avatarOwnerId, avatarOwnerId,
topic, topic,
@ -103,7 +100,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
const prevAvatarOwnerId = usePrevious(avatarOwnerId); const prevAvatarOwnerId = usePrevious(avatarOwnerId);
const [hasSlideAnimation, setHasSlideAnimation] = useState(true); const [hasSlideAnimation, setHasSlideAnimation] = useState(true);
const slideAnimation = hasSlideAnimation const slideAnimation = hasSlideAnimation
? animationLevel >= 1 ? (lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized') : 'none' ? (lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized')
: 'none'; : 'none';
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0); const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
@ -355,7 +352,6 @@ export default memo(withGlobal<OwnProps>(
const userStatus = selectUserStatus(global, userId); const userStatus = selectUserStatus(global, userId);
const chat = selectChat(global, userId); const chat = selectChat(global, userId);
const isSavedMessages = !forceShowSelf && user && user.isSelf; const isSavedMessages = !forceShowSelf && user && user.isSelf;
const { animationLevel } = global.settings.byKey;
const { mediaId, avatarOwnerId } = selectTabState(global).mediaViewer; const { mediaId, avatarOwnerId } = selectTabState(global).mediaViewer;
const isForum = chat?.isForum; const isForum = chat?.isForum;
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {}; const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
@ -373,7 +369,6 @@ export default memo(withGlobal<OwnProps>(
userFallbackPhoto: userFullInfo?.fallbackPhoto, userFallbackPhoto: userFullInfo?.fallbackPhoto,
chatProfilePhoto: chatFullInfo?.profilePhoto, chatProfilePhoto: chatFullInfo?.profilePhoto,
isSavedMessages, isSavedMessages,
animationLevel,
mediaId, mediaId,
avatarOwnerId, avatarOwnerId,
...(topic && { ...(topic && {

View File

@ -28,7 +28,7 @@ import './StickerButton.scss';
type OwnProps<T> = { type OwnProps<T> = {
sticker: ApiSticker; sticker: ApiSticker;
size: number; size: number;
noAnimate?: boolean; noPlay?: boolean;
title?: string; title?: string;
className?: string; className?: string;
noContextMenu?: boolean; noContextMenu?: boolean;
@ -63,7 +63,7 @@ const contentForStatusMenuContext = [
const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult | undefined = undefined>({ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult | undefined = undefined>({
sticker, sticker,
size, size,
noAnimate, noPlay,
title, title,
className, className,
noContextMenu, noContextMenu,
@ -102,7 +102,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
const isIntersecting = useIsIntersecting(ref, observeIntersection); const isIntersecting = useIsIntersecting(ref, observeIntersection);
const shouldLoad = isIntersecting; const shouldLoad = isIntersecting;
const shouldPlay = isIntersecting && !noAnimate; const shouldPlay = isIntersecting && !noPlay;
const isIntesectingForShowing = useIsIntersecting(ref, observeIntersectionForShowing); const isIntesectingForShowing = useIsIntersecting(ref, observeIntersectionForShowing);
@ -326,6 +326,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
className="sticker-remove-button" className="sticker-remove-button"
color="dark" color="dark"
round round
noFastClick
onClick={handleRemoveClick} onClick={handleRemoveClick}
> >
<i className="icon icon-close" /> <i className="icon icon-close" />

View File

@ -339,7 +339,7 @@ const StickerSet: FC<OwnProps> = ({
size={itemSize} size={itemSize}
observeIntersection={observeIntersectionForPlayingItems} observeIntersection={observeIntersectionForPlayingItems}
observeIntersectionForShowing={observeIntersectionForShowingItems} observeIntersectionForShowing={observeIntersectionForShowingItems}
noAnimate={!loadAndPlay} noPlay={!loadAndPlay}
isSavedMessages={isSavedMessages} isSavedMessages={isSavedMessages}
isStatusPicker={isStatusPicker} isStatusPicker={isStatusPicker}
canViewSet canViewSet

View File

@ -18,7 +18,7 @@ import './StickerSetCard.scss';
type OwnProps = { type OwnProps = {
stickerSet?: ApiStickerSet; stickerSet?: ApiStickerSet;
noAnimate?: boolean; noPlay?: boolean;
className?: string; className?: string;
observeIntersection: ObserveFn; observeIntersection: ObserveFn;
onClick: (value: ApiSticker) => void; onClick: (value: ApiSticker) => void;
@ -26,7 +26,7 @@ type OwnProps = {
const StickerSetCard: FC<OwnProps> = ({ const StickerSetCard: FC<OwnProps> = ({
stickerSet, stickerSet,
noAnimate, noPlay,
className, className,
observeIntersection, observeIntersection,
onClick, onClick,
@ -55,7 +55,7 @@ const StickerSetCard: FC<OwnProps> = ({
<StickerSetCover <StickerSetCover
stickerSet={stickerSet} stickerSet={stickerSet}
size={STICKER_SIZE_GENERAL_SETTINGS} size={STICKER_SIZE_GENERAL_SETTINGS}
noAnimate={noAnimate} noPlay={noPlay}
observeIntersection={observeIntersection} observeIntersection={observeIntersection}
/> />
</Button> </Button>
@ -66,7 +66,7 @@ const StickerSetCard: FC<OwnProps> = ({
sticker={firstSticker} sticker={firstSticker}
size={STICKER_SIZE_GENERAL_SETTINGS} size={STICKER_SIZE_GENERAL_SETTINGS}
title={stickerSet.title} title={stickerSet.title}
noAnimate={noAnimate} noPlay={noPlay}
observeIntersection={observeIntersection} observeIntersection={observeIntersection}
noContextMenu noContextMenu
isCurrentUserPremium isCurrentUserPremium

View File

@ -1,4 +1,4 @@
import React, { memo, useMemo, useState } from '../../lib/teact/teact'; import React, { memo, useMemo } from '../../lib/teact/teact';
import { getGlobal } from '../../global'; import { getGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
@ -95,19 +95,22 @@ const StickerView: FC<OwnProps> = ({
const shouldPlay = isIntersectingForPlaying && !noPlay; const shouldPlay = isIntersectingForPlaying && !noPlay;
const thumbDataUri = useThumbnail(sticker); const thumbDataUri = useThumbnail(sticker);
// Use preview instead of thumb but only if it's already loaded // Use preview instead of thumb but only if it's already loaded or when playing an animation is disabled
const [preloadedPreviewData] = useState(mediaLoader.getFromMemory(previewMediaHash)); const previewMediaDataFromCache: string | undefined = mediaLoader.getFromMemory(previewMediaHash);
const thumbData = customColor ? thumbDataUri : (preloadedPreviewData || thumbDataUri); const previewMediaData = useMedia(
previewMediaHash, Boolean(previewMediaDataFromCache || !noPlay), undefined, cacheBuster,
);
const thumbData = customColor ? thumbDataUri : (previewMediaData || thumbDataUri);
const shouldForcePreview = isUnsupportedVideo || (isStatic && isSmall); const shouldForcePreview = isUnsupportedVideo || (isStatic && isSmall);
fullMediaHash ||= shouldForcePreview ? previewMediaHash : `sticker${id}`; fullMediaHash ||= shouldForcePreview ? previewMediaHash : `sticker${id}`;
// If preloaded preview is forced, it will render as thumb, so no need to load it again // If preloaded preview is forced, it will render as thumb, so no need to load it again
const shouldSkipFullMedia = Boolean(fullMediaHash === previewMediaHash && preloadedPreviewData); const shouldSkipFullMedia = Boolean(fullMediaHash === previewMediaHash && previewMediaData);
const fullMediaData = useMedia(fullMediaHash, !shouldLoad || shouldSkipFullMedia, undefined, cacheBuster); const fullMediaData = useMedia(fullMediaHash, !shouldLoad || shouldSkipFullMedia, undefined, cacheBuster);
// If Lottie data is loaded we will only render thumb if it's good enough (from preview) // If Lottie data is loaded we will only render thumb if it's good enough (from preview)
const [isPlayerReady, markPlayerReady] = useFlag(Boolean(isLottie && fullMediaData && !preloadedPreviewData)); const [isPlayerReady, markPlayerReady] = useFlag(Boolean(isLottie && fullMediaData && !previewMediaData));
// Delay mounting on Android until heavy animation ends // Delay mounting on Android until heavy animation ends
const [isReadyToMount, markReadyToMount, unmarkReadyToMount] = useFlag(!IS_ANDROID || !isHeavyAnimating()); const [isReadyToMount, markReadyToMount, unmarkReadyToMount] = useFlag(!IS_ANDROID || !isHeavyAnimating());
useHeavyAnimationCheck(unmarkReadyToMount, markReadyToMount, isReadyToMount); useHeavyAnimationCheck(unmarkReadyToMount, markReadyToMount, isReadyToMount);
@ -116,7 +119,7 @@ const StickerView: FC<OwnProps> = ({
const isThumbOpaque = sharedCanvasRef && !withTranslucentThumb; const isThumbOpaque = sharedCanvasRef && !withTranslucentThumb;
const thumbClassNames = useMediaTransition(thumbData && !isFullMediaReady); const thumbClassNames = useMediaTransition(thumbData && !isFullMediaReady);
const fullMediaClassNames = useMediaTransition(isFullMediaReady); const fullMediaClassNames = useMediaTransition(isFullMediaReady);
const noTransition = isLottie && preloadedPreviewData; const noTransition = isLottie && previewMediaData;
const coords = useCoordsInSharedCanvas(containerRef, sharedCanvasRef); const coords = useCoordsInSharedCanvas(containerRef, sharedCanvasRef);

View File

@ -97,7 +97,9 @@ export function renderActionMessageText(
content.push(...processed); content.push(...processed);
if (unprocessed.includes('%action_topic%')) { if (unprocessed.includes('%action_topic%')) {
const topicEmoji = topic?.iconEmojiId ? <CustomEmoji documentId={topic.iconEmojiId} /> : ''; const topicEmoji = topic?.iconEmojiId
? <CustomEmoji documentId={topic.iconEmojiId} />
: '';
const topicString = topic ? `${topic.title}` : 'a topic'; const topicString = topic ? `${topic.title}` : 'a topic';
processed = processPlaceholder( processed = processPlaceholder(
unprocessed, unprocessed,

View File

@ -30,16 +30,16 @@ export function renderMessageText(
return contentNotSupportedText ? [trimText(contentNotSupportedText, truncateLength)] : undefined; return contentNotSupportedText ? [trimText(contentNotSupportedText, truncateLength)] : undefined;
} }
return renderTextWithEntities( return renderTextWithEntities({
trimText(text, truncateLength), text: trimText(text, truncateLength),
entities, entities,
highlight, highlight,
emojiSize, emojiSize,
shouldRenderAsHtml, shouldRenderAsHtml,
message.id, messageId: message.id,
isSimple, isSimple,
isProtected, isProtected,
); });
} }
// TODO Use Message Summary component instead // TODO Use Message Summary component instead

View File

@ -27,22 +27,37 @@ interface IOrganizedEntity {
const HQ_EMOJI_THRESHOLD = 64; const HQ_EMOJI_THRESHOLD = 64;
export function renderTextWithEntities( export function renderTextWithEntities({
text: string, text,
entities?: ApiMessageEntity[], entities,
highlight?: string, highlight,
emojiSize?: number, emojiSize,
shouldRenderAsHtml?: boolean, shouldRenderAsHtml,
messageId?: number, messageId,
isSimple?: boolean, isSimple,
isProtected?: boolean, isProtected,
observeIntersectionForLoading?: ObserveFn, observeIntersectionForLoading,
observeIntersectionForPlaying?: ObserveFn, observeIntersectionForPlaying,
withTranslucentThumbs?: boolean, withTranslucentThumbs,
sharedCanvasRef?: React.RefObject<HTMLCanvasElement>, sharedCanvasRef,
sharedCanvasHqRef?: React.RefObject<HTMLCanvasElement>, sharedCanvasHqRef,
cacheBuster?: string, cacheBuster,
) { }: {
text: string;
entities?: ApiMessageEntity[];
highlight?: string;
emojiSize?: number;
shouldRenderAsHtml?: boolean;
messageId?: number;
isSimple?: boolean;
isProtected?: boolean;
observeIntersectionForLoading?: ObserveFn;
observeIntersectionForPlaying?: ObserveFn;
withTranslucentThumbs?: boolean;
sharedCanvasRef?: React.RefObject<HTMLCanvasElement>;
sharedCanvasHqRef?: React.RefObject<HTMLCanvasElement>;
cacheBuster?: string;
}) {
if (!entities || !entities.length) { if (!entities || !entities.length) {
return renderMessagePart(text, highlight, emojiSize, shouldRenderAsHtml, isSimple); return renderMessagePart(text, highlight, emojiSize, shouldRenderAsHtml, isSimple);
} }
@ -183,13 +198,11 @@ export function getTextWithEntitiesAsHtml(formattedText?: ApiFormattedText) {
return ''; return '';
} }
const result = renderTextWithEntities( const result = renderTextWithEntities({
text, text,
entities, entities,
undefined, shouldRenderAsHtml: true,
undefined, });
true,
);
if (Array.isArray(result)) { if (Array.isArray(result)) {
return result.join(''); return result.join('');

View File

@ -1,28 +1,35 @@
import { useCallback, useEffect, useState } from '../../../lib/teact/teact'; import { useCallback, useEffect, useState } from '../../../lib/teact/teact';
import { getGlobal } from '../../../global'; import { getGlobal } from '../../../global';
import type { GlobalState } from '../../../global/types';
import type { ApiSticker } from '../../../api/types'; import type { ApiSticker } from '../../../api/types';
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
import { addCustomEmojiCallback, removeCustomEmojiCallback } from '../../../util/customEmojiManager'; import { addCustomEmojiCallback, removeCustomEmojiCallback } from '../../../util/customEmojiManager';
import useEnsureCustomEmoji from '../../../hooks/useEnsureCustomEmoji'; import useEnsureCustomEmoji from '../../../hooks/useEnsureCustomEmoji';
export default function useCustomEmoji(documentId?: string) { export default function useCustomEmoji(documentId?: string) {
const global = getGlobal();
const [customEmoji, setCustomEmoji] = useState<ApiSticker | undefined>( const [customEmoji, setCustomEmoji] = useState<ApiSticker | undefined>(
documentId ? getGlobal().customEmojis.byId[documentId] : undefined, documentId ? global.customEmojis.byId[documentId] : undefined,
); );
const [canPlay, setCanPlay] = useState(selectCanPlayAnimatedEmojis(global));
useEnsureCustomEmoji(documentId); useEnsureCustomEmoji(documentId);
const handleGlobalChange = useCallback(() => { const handleGlobalChange = useCallback((customEmojis?: GlobalState['customEmojis']) => {
if (!documentId) return; if (!documentId) return;
setCustomEmoji(getGlobal().customEmojis.byId[documentId]);
const newGlobal = getGlobal();
setCustomEmoji((customEmojis ?? newGlobal.customEmojis).byId[documentId]);
setCanPlay(selectCanPlayAnimatedEmojis(newGlobal));
}, [documentId]); }, [documentId]);
useEffect(handleGlobalChange, [documentId, handleGlobalChange]); useEffect(handleGlobalChange, [documentId, handleGlobalChange]);
useEffect(() => { useEffect(() => {
if (customEmoji || !documentId) return undefined; if (!documentId) return undefined;
addCustomEmojiCallback(handleGlobalChange, documentId); addCustomEmojiCallback(handleGlobalChange, documentId);
@ -31,5 +38,5 @@ export default function useCustomEmoji(documentId?: string) {
}; };
}, [customEmoji, documentId, handleGlobalChange]); }, [customEmoji, documentId, handleGlobalChange]);
return customEmoji; return { customEmoji, canPlay };
} }

View File

@ -157,6 +157,7 @@ const LeftColumn: FC<StateProps> = ({
case SettingsScreens.Notifications: case SettingsScreens.Notifications:
case SettingsScreens.DataStorage: case SettingsScreens.DataStorage:
case SettingsScreens.Privacy: case SettingsScreens.Privacy:
case SettingsScreens.Performance:
case SettingsScreens.ActiveSessions: case SettingsScreens.ActiveSessions:
case SettingsScreens.Language: case SettingsScreens.Language:
case SettingsScreens.Stickers: case SettingsScreens.Stickers:

View File

@ -11,7 +11,7 @@
left: 1rem; left: 1rem;
} }
body.animation-level-0 & { body.no-page-transitions & {
transform: none !important; transform: none !important;
opacity: 0; opacity: 0;

View File

@ -53,8 +53,8 @@
.info { .info {
transition: opacity 0.3s ease, transform var(--layer-transition); transition: opacity 0.3s ease, transform var(--layer-transition);
:global(body.animation-level-0) & { :global(body.no-page-transitions) & {
transition: none; transition: opacity 0.3s ease;
} }
} }

View File

@ -43,7 +43,7 @@
// Super specific selector to override the same in `ListItem` // Super specific selector to override the same in `ListItem`
@media (min-width: 600px) { @media (min-width: 600px) {
&:not(.has-ripple):not(.is-static), &:not(.has-ripple):not(.is-static),
body.animation-level-0 & { body.no-page-transitions & {
.ListItem-button:active { .ListItem-button:active {
--background-color: var(--color-chat-hover) !important; --background-color: var(--color-chat-hover) !important;
} }
@ -156,7 +156,7 @@
transform: translateX(-0.375rem) scaleY(0.5); transform: translateX(-0.375rem) scaleY(0.5);
transition: transform var(--layer-transition); transition: transform var(--layer-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none; transition: none;
} }
@ -211,8 +211,8 @@
.Badge-transition { .Badge-transition {
transition: opacity var(--layer-transition), transform var(--layer-transition); transition: opacity var(--layer-transition), transform var(--layer-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none; transition: opacity var(--layer-transition);
} }
} }
} }
@ -220,8 +220,8 @@
.info { .info {
transition: opacity 300ms ease, transform var(--layer-transition); transition: opacity 300ms ease, transform var(--layer-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none; transition: opacity 300ms ease;
} }
.title .custom-emoji { .title .custom-emoji {

View File

@ -13,7 +13,6 @@ import type {
ApiUser, ApiUser,
ApiUserStatus, ApiUserStatus,
} from '../../../api/types'; } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import type { ChatAnimationTypes } from './hooks'; import type { ChatAnimationTypes } from './hooks';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
@ -25,6 +24,7 @@ import {
selectIsChatMuted, selectIsChatMuted,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { import {
selectCanAnimateInterface,
selectChat, selectChat,
selectChatMessage, selectChatMessage,
selectCurrentMessageList, selectCurrentMessageList,
@ -82,7 +82,6 @@ type StateProps = {
lastMessageSender?: ApiUser | ApiChat; lastMessageSender?: ApiUser | ApiChat;
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus; lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
draft?: ApiFormattedText; draft?: ApiFormattedText;
animationLevel?: AnimationLevel;
isSelected?: boolean; isSelected?: boolean;
isSelectedForum?: boolean; isSelectedForum?: boolean;
canScrollDown?: boolean; canScrollDown?: boolean;
@ -90,6 +89,7 @@ type StateProps = {
lastSyncTime?: number; lastSyncTime?: number;
lastMessageTopic?: ApiTopic; lastMessageTopic?: ApiTopic;
typingStatus?: ApiTypingStatus; typingStatus?: ApiTypingStatus;
withInterfaceAnimations?: boolean;
}; };
const Chat: FC<OwnProps & StateProps> = ({ const Chat: FC<OwnProps & StateProps> = ({
@ -110,7 +110,7 @@ const Chat: FC<OwnProps & StateProps> = ({
actionTargetChatId, actionTargetChatId,
offsetTop, offsetTop,
draft, draft,
animationLevel, withInterfaceAnimations,
isSelected, isSelected,
isSelectedForum, isSelectedForum,
canScrollDown, canScrollDown,
@ -150,7 +150,7 @@ const Chat: FC<OwnProps & StateProps> = ({
lastMessageSender, lastMessageSender,
observeIntersection, observeIntersection,
animationType, animationType,
animationLevel, withInterfaceAnimations,
orderDiff, orderDiff,
}); });
@ -239,13 +239,10 @@ const Chat: FC<OwnProps & StateProps> = ({
userStatus={userStatus} userStatus={userStatus}
isSavedMessages={user?.isSelf} isSavedMessages={user?.isSelf}
lastSyncTime={lastSyncTime} lastSyncTime={lastSyncTime}
animationLevel={animationLevel}
withVideo
observeIntersection={observeIntersection}
/> />
<AvatarBadge chatId={chatId} /> <AvatarBadge chatId={chatId} />
{chat.isCallActive && chat.isCallNotEmpty && ( {chat.isCallActive && chat.isCallNotEmpty && (
<ChatCallStatus isMobile={isMobile} isSelected={isSelected} isActive={animationLevel !== 0} /> <ChatCallStatus isMobile={isMobile} isSelected={isSelected} isActive={withInterfaceAnimations} />
)} )}
</div> </div>
<div className="info"> <div className="info">
@ -337,7 +334,6 @@ export default memo(withGlobal<OwnProps>(
actionTargetChatId, actionTargetChatId,
actionTargetMessage, actionTargetMessage,
draft: selectDraft(global, chatId, MAIN_THREAD_ID), draft: selectDraft(global, chatId, MAIN_THREAD_ID),
animationLevel: global.settings.byKey.animationLevel,
isSelected, isSelected,
isSelectedForum, isSelectedForum,
canScrollDown: isSelected && messageListType === 'thread', canScrollDown: isSelected && messageListType === 'thread',
@ -350,6 +346,7 @@ export default memo(withGlobal<OwnProps>(
userStatus, userStatus,
lastMessageTopic, lastMessageTopic,
typingStatus, typingStatus,
withInterfaceAnimations: selectCanAnimateInterface(global),
}; };
}, },
)(Chat)); )(Chat));

View File

@ -9,12 +9,11 @@ import type { ApiChat } from '../../../api/types';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
import { import {
GENERAL_TOPIC_ID, GENERAL_TOPIC_ID, TOPICS_SLICE, TOPIC_HEIGHT_PX, TOPIC_LIST_SENSITIVE_AREA,
TOPICS_SLICE, TOPIC_HEIGHT_PX, TOPIC_LIST_SENSITIVE_AREA, ANIMATION_LEVEL_MIN,
} from '../../../config'; } from '../../../config';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { import {
selectChat, selectCurrentMessageList, selectIsForumPanelOpen, selectTabState, selectCanAnimateInterface, selectChat, selectCurrentMessageList, selectIsForumPanelOpen, selectTabState,
} from '../../../global/selectors'; } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { getOrderedTopics } from '../../../global/helpers'; import { getOrderedTopics } from '../../../global/helpers';
@ -54,7 +53,7 @@ type StateProps = {
chat?: ApiChat; chat?: ApiChat;
currentTopicId?: number; currentTopicId?: number;
lastSyncTime?: number; lastSyncTime?: number;
animationLevel?: number; withInterfaceAnimations?: boolean;
}; };
const INTERSECTION_THROTTLE = 200; const INTERSECTION_THROTTLE = 200;
@ -68,7 +67,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
onTopicSearch, onTopicSearch,
onCloseAnimationEnd, onCloseAnimationEnd,
onOpenAnimationStart, onOpenAnimationStart,
animationLevel, withInterfaceAnimations,
}) => { }) => {
const { const {
closeForumPanel, openChatWithInfo, loadTopics, closeForumPanel, openChatWithInfo, loadTopics,
@ -97,10 +96,10 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
}, [closeForumPanel]); }, [closeForumPanel]);
useEffect(() => { useEffect(() => {
if (animationLevel === ANIMATION_LEVEL_MIN && !isOpen) { if (!withInterfaceAnimations && !isOpen) {
onCloseAnimationEnd?.(); onCloseAnimationEnd?.();
} }
}, [animationLevel, isOpen, onCloseAnimationEnd]); }, [withInterfaceAnimations, isOpen, onCloseAnimationEnd]);
const handleToggleChatInfo = useCallback(() => { const handleToggleChatInfo = useCallback(() => {
if (!chat) return; if (!chat) return;
@ -212,7 +211,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
styles.root, styles.root,
isScrolled && styles.scrolled, isScrolled && styles.scrolled,
lang.isRtl && styles.rtl, lang.isRtl && styles.rtl,
animationLevel === ANIMATION_LEVEL_MIN && styles.noAnimation, !withInterfaceAnimations && styles.noAnimation,
)} )}
onTransitionEnd={!isOpen ? onCloseAnimationEnd : undefined} onTransitionEnd={!isOpen ? onCloseAnimationEnd : undefined}
> >
@ -294,7 +293,7 @@ export default memo(withGlobal<OwnProps>(
chat, chat,
lastSyncTime: global.lastSyncTime, lastSyncTime: global.lastSyncTime,
currentTopicId: chatId === currentChatId ? currentThreadId : undefined, currentTopicId: chatId === currentChatId ? currentThreadId : undefined,
animationLevel: global.settings.byKey.animationLevel, withInterfaceAnimations: selectCanAnimateInterface(global),
}; };
}, },
)(ForumPanel)); )(ForumPanel));

View File

@ -4,13 +4,15 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { GlobalState, TabState } from '../../../global/types';
import type { AnimationLevel, ISettings } from '../../../types'; import type { AnimationLevel, ISettings } from '../../../types';
import type { TabState, GlobalState } from '../../../global/types';
import { LeftColumnContent, SettingsScreens } from '../../../types'; import { LeftColumnContent, SettingsScreens } from '../../../types';
import { import {
APP_NAME, APP_VERSION, ARCHIVED_FOLDER_ID, ANIMATION_LEVEL_MAX,
ANIMATION_LEVEL_MIN,
APP_NAME, APP_VERSION,
ARCHIVED_FOLDER_ID,
BETA_CHANGELOG_URL, BETA_CHANGELOG_URL,
DEBUG, DEBUG,
FEEDBACK_URL, FEEDBACK_URL,
@ -18,6 +20,11 @@ import {
IS_TEST, IS_TEST,
PRODUCTION_HOSTNAME, PRODUCTION_HOSTNAME,
} from '../../../config'; } from '../../../config';
import {
INITIAL_PERFORMANCE_STATE_MAX,
INITIAL_PERFORMANCE_STATE_MID,
INITIAL_PERFORMANCE_STATE_MIN,
} from '../../../global/initialState';
import { IS_PWA } from '../../../util/windowEnvironment'; import { IS_PWA } from '../../../util/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatDateToString } from '../../../util/dateFormat'; import { formatDateToString } from '../../../util/dateFormat';
@ -32,6 +39,7 @@ import { useHotkeys } from '../../../hooks/useHotkeys';
import { getPromptInstall } from '../../../util/installPrompt'; import { getPromptInstall } from '../../../util/installPrompt';
import captureEscKeyListener from '../../../util/captureEscKeyListener'; import captureEscKeyListener from '../../../util/captureEscKeyListener';
import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition'; import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition';
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
import DropdownMenu from '../../ui/DropdownMenu'; import DropdownMenu from '../../ui/DropdownMenu';
@ -43,9 +51,9 @@ import Switcher from '../../ui/Switcher';
import ShowTransition from '../../ui/ShowTransition'; import ShowTransition from '../../ui/ShowTransition';
import ConnectionStatusOverlay from '../ConnectionStatusOverlay'; import ConnectionStatusOverlay from '../ConnectionStatusOverlay';
import StatusButton from './StatusButton'; import StatusButton from './StatusButton';
import Toggle from '../../ui/Toggle';
import './LeftMainHeader.scss'; import './LeftMainHeader.scss';
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
type OwnProps = { type OwnProps = {
shouldHideSearch?: boolean; shouldHideSearch?: boolean;
@ -79,7 +87,6 @@ type StateProps =
& Pick<GlobalState, 'connectionState' | 'isSyncing' | 'archiveSettings'> & Pick<GlobalState, 'connectionState' | 'isSyncing' | 'archiveSettings'>
& Pick<TabState, 'canInstall'>; & Pick<TabState, 'canInstall'>;
const ANIMATION_LEVEL_OPTIONS = [0, 1, 2];
const WEBK_VERSION_URL = 'https://web.telegram.org/k/'; const WEBK_VERSION_URL = 'https://web.telegram.org/k/';
const LeftMainHeader: FC<OwnProps & StateProps> = ({ const LeftMainHeader: FC<OwnProps & StateProps> = ({
@ -121,6 +128,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
requestNextSettingsScreen, requestNextSettingsScreen,
skipLockOnUnload, skipLockOnUnload,
openUrl, openUrl,
updatePerformanceSettings,
} = getActions(); } = getActions();
const lang = useLang(); const lang = useLang();
@ -206,12 +214,16 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent<HTMLElement>) => { const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
e.stopPropagation(); e.stopPropagation();
const newLevel = animationLevel === 0 ? 2 : 0; let newLevel = animationLevel + 1;
ANIMATION_LEVEL_OPTIONS.forEach((_, i) => { if (newLevel > ANIMATION_LEVEL_MAX) {
document.body.classList.toggle(`animation-level-${i}`, newLevel === i); newLevel = ANIMATION_LEVEL_MIN;
}); }
const performanceSettings = newLevel === ANIMATION_LEVEL_MIN
? INITIAL_PERFORMANCE_STATE_MIN
: (newLevel === ANIMATION_LEVEL_MAX ? INITIAL_PERFORMANCE_STATE_MAX : INITIAL_PERFORMANCE_STATE_MID);
setSettingOption({ animationLevel: newLevel }); setSettingOption({ animationLevel: newLevel as AnimationLevel });
updatePerformanceSettings(performanceSettings);
}, [animationLevel, setSettingOption]); }, [animationLevel, setSettingOption]);
const handleChangelogClick = useCallback(() => { const handleChangelogClick = useCallback(() => {
@ -249,6 +261,9 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
: lang('Search'); : lang('Search');
const versionString = IS_BETA ? `${APP_VERSION} Beta (${APP_REVISION})` : (DEBUG ? APP_REVISION : APP_VERSION); const versionString = IS_BETA ? `${APP_VERSION} Beta (${APP_REVISION})` : (DEBUG ? APP_REVISION : APP_VERSION);
const animationLevelValue = animationLevel !== ANIMATION_LEVEL_MIN
? (animationLevel === ANIMATION_LEVEL_MAX ? 'max' : 'mid')
: 'min';
// Disable dropdown menu RTL animation for resize // Disable dropdown menu RTL animation for resize
const { const {
@ -304,11 +319,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
onClick={handleAnimationLevelChange} onClick={handleAnimationLevelChange}
> >
<span className="menu-item-name capitalize">{lang('Appearance.Animations').toLowerCase()}</span> <span className="menu-item-name capitalize">{lang('Appearance.Animations').toLowerCase()}</span>
<Switcher <Toggle value={animationLevelValue} />
id="animations"
label="Toggle Animations"
checked={animationLevel > 0}
/>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
icon="help" icon="help"
@ -350,7 +361,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
)} )}
</> </>
), [ ), [
animationLevel, archivedUnreadChatsCount, canInstall, handleAnimationLevelChange, handleBugReportClick, lang, animationLevelValue, archivedUnreadChatsCount, canInstall, handleAnimationLevelChange, handleBugReportClick, lang,
handleChangelogClick, handleDarkModeToggle, handleOpenTipsChat, handleSelectSaved, handleSwitchToWebK, handleChangelogClick, handleDarkModeToggle, handleOpenTipsChat, handleSelectSaved, handleSwitchToWebK,
onSelectArchived, onSelectContacts, onSelectSettings, theme, withOtherVersions, archiveSettings, onSelectArchived, onSelectContacts, onSelectSettings, theme, withOtherVersions, archiveSettings,
]); ]);

View File

@ -93,7 +93,7 @@ const StatusButton: FC<StateProps> = ({ emojiStatus }) => {
); );
}; };
export default memo(withGlobal((global) => { export default memo(withGlobal((global): StateProps => {
const { currentUserId } = global; const { currentUserId } = global;
const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined; const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined;

View File

@ -2,7 +2,6 @@
--offset-y: 3.25rem !important; --offset-y: 3.25rem !important;
--offset-x: auto !important; --offset-x: auto !important;
--color-text: var(--color-primary); --color-text: var(--color-primary);
--color-background: var(--color-background-compact-menu);
--border-radius-default: 1.25rem; --border-radius-default: 1.25rem;
left: 0.5rem; left: 0.5rem;
@ -10,7 +9,12 @@
max-width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar max-width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar
height: var(--symbol-menu-height); height: var(--symbol-menu-height);
padding: 0 !important; padding: 0 !important;
backdrop-filter: blur(10px);
:global(body:not(.no-menu-blur)) & {
--color-background: var(--color-background-compact-menu);
backdrop-filter: blur(10px);
}
@supports (overflow: overlay) { @supports (overflow: overlay) {
width: var(--symbol-menu-width); width: var(--symbol-menu-width);

View File

@ -7,6 +7,7 @@ import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import type { ApiSticker } from '../../../api/types'; import type { ApiSticker } from '../../../api/types';
import { selectIsContextMenuTranslucent } from '../../../global/selectors';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import Menu from '../../ui/Menu'; import Menu from '../../ui/Menu';
@ -24,12 +25,14 @@ export type OwnProps = {
interface StateProps { interface StateProps {
areFeaturedStickersLoaded?: boolean; areFeaturedStickersLoaded?: boolean;
isTranslucent?: boolean;
} }
const StatusPickerMenu: FC<OwnProps & StateProps> = ({ const StatusPickerMenu: FC<OwnProps & StateProps> = ({
isOpen, isOpen,
statusButtonRef, statusButtonRef,
areFeaturedStickersLoaded, areFeaturedStickersLoaded,
isTranslucent,
onEmojiStatusSelect, onEmojiStatusSelect,
onClose, onClose,
}) => { }) => {
@ -68,7 +71,7 @@ const StatusPickerMenu: FC<OwnProps & StateProps> = ({
loadAndPlay={isOpen} loadAndPlay={isOpen}
isHidden={!isOpen} isHidden={!isOpen}
isStatusPicker isStatusPicker
isTranslucent isTranslucent={isTranslucent}
onContextMenuOpen={markContextMenuShown} onContextMenuOpen={markContextMenuShown}
onContextMenuClose={unmarkContextMenuShown} onContextMenuClose={unmarkContextMenuShown}
onCustomEmojiSelect={handleEmojiSelect} onCustomEmojiSelect={handleEmojiSelect}
@ -82,5 +85,6 @@ const StatusPickerMenu: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>((global): StateProps => { export default memo(withGlobal<OwnProps>((global): StateProps => {
return { return {
areFeaturedStickersLoaded: Boolean(global.customEmojis.featuredIds?.length), areFeaturedStickersLoaded: Boolean(global.customEmojis.featuredIds?.length),
isTranslucent: selectIsContextMenuTranslucent(global),
}; };
})(StatusPickerMenu)); })(StatusPickerMenu));

View File

@ -9,15 +9,19 @@ import type {
} from '../../../api/types'; } from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { ChatAnimationTypes } from './hooks'; import type { ChatAnimationTypes } from './hooks';
import type { AnimationLevel } from '../../../types';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment'; import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment';
import { import {
selectCanAnimateInterface,
selectCanDeleteTopic, selectCanDeleteTopic,
selectChat, selectChat,
selectChatMessage, selectCurrentMessageList, selectChatMessage,
selectCurrentMessageList,
selectDraft, selectDraft,
selectOutgoingStatus, selectThreadInfo, selectThreadParam, selectUser, selectOutgoingStatus,
selectThreadInfo,
selectThreadParam,
selectUser,
} from '../../../global/selectors'; } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { createLocationHash } from '../../../util/routing'; import { createLocationHash } from '../../../util/routing';
@ -57,11 +61,11 @@ type StateProps = {
actionTargetUserIds?: string[]; actionTargetUserIds?: string[];
lastMessageSender?: ApiUser | ApiChat; lastMessageSender?: ApiUser | ApiChat;
actionTargetChatId?: string; actionTargetChatId?: string;
animationLevel?: AnimationLevel;
typingStatus?: ApiTypingStatus; typingStatus?: ApiTypingStatus;
draft?: ApiFormattedText; draft?: ApiFormattedText;
canScrollDown?: boolean; canScrollDown?: boolean;
wasTopicOpened?: boolean; wasTopicOpened?: boolean;
withInterfaceAnimations?: boolean;
}; };
const Topic: FC<OwnProps & StateProps> = ({ const Topic: FC<OwnProps & StateProps> = ({
@ -80,7 +84,7 @@ const Topic: FC<OwnProps & StateProps> = ({
actionTargetChatId, actionTargetChatId,
lastMessageSender, lastMessageSender,
animationType, animationType,
animationLevel, withInterfaceAnimations,
orderDiff, orderDiff,
typingStatus, typingStatus,
draft, draft,
@ -122,7 +126,7 @@ const Topic: FC<OwnProps & StateProps> = ({
typingStatus, typingStatus,
animationType, animationType,
animationLevel, withInterfaceAnimations,
orderDiff, orderDiff,
}); });
@ -229,7 +233,7 @@ export default memo(withGlobal<OwnProps>(
lastMessageSender, lastMessageSender,
typingStatus, typingStatus,
canDelete: selectCanDeleteTopic(global, chatId, topic.id), canDelete: selectCanDeleteTopic(global, chatId, topic.id),
animationLevel: global.settings.byKey.animationLevel, withInterfaceAnimations: selectCanAnimateInterface(global),
draft, draft,
...(isOutgoing && lastMessage && { ...(isOutgoing && lastMessage && {
lastMessageOutgoingStatus: selectOutgoingStatus(global, lastMessage), lastMessageOutgoingStatus: selectOutgoingStatus(global, lastMessage),

View File

@ -2,7 +2,6 @@ import React, { useLayoutEffect, useMemo, useRef } from '../../../../lib/teact/t
import { requestMutation } from '../../../../lib/fasterdom/fasterdom'; import { requestMutation } from '../../../../lib/fasterdom/fasterdom';
import { getGlobal } from '../../../../global'; import { getGlobal } from '../../../../global';
import type { AnimationLevel } from '../../../../types';
import type { LangFn } from '../../../../hooks/useLang'; import type { LangFn } from '../../../../hooks/useLang';
import type { import type {
ApiChat, ApiTopic, ApiMessage, ApiTypingStatus, ApiUser, ApiChat, ApiTopic, ApiMessage, ApiTypingStatus, ApiUser,
@ -46,7 +45,7 @@ export default function useChatListEntry({
observeIntersection, observeIntersection,
animationType, animationType,
orderDiff, orderDiff,
animationLevel, withInterfaceAnimations,
isTopic, isTopic,
}: { }: {
chat?: ApiChat; chat?: ApiChat;
@ -64,7 +63,7 @@ export default function useChatListEntry({
animationType: ChatAnimationTypes; animationType: ChatAnimationTypes;
orderDiff: number; orderDiff: number;
animationLevel?: AnimationLevel; withInterfaceAnimations?: boolean;
}) { }) {
const lang = useLang(); const lang = useLang();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -113,7 +112,12 @@ export default function useChatListEntry({
return ( return (
<p className="last-message" dir={lang.isRtl ? 'auto' : 'ltr'}> <p className="last-message" dir={lang.isRtl ? 'auto' : 'ltr'}>
<span className="draft">{lang('Draft')}</span> <span className="draft">{lang('Draft')}</span>
{renderTextWithEntities(draft.text, draft.entities, undefined, undefined, undefined, undefined, true)} {renderTextWithEntities({
text: draft.text,
entities: draft.entities,
isSimple: true,
withTranslucentThumbs: true,
})}
</p> </p>
); );
} }
@ -137,6 +141,8 @@ export default function useChatListEntry({
actionTargetChatId, actionTargetChatId,
lastMessageTopic, lastMessageTopic,
{ isEmbedded: true }, { isEmbedded: true },
undefined,
undefined,
)} )}
</p> </p>
); );
@ -161,7 +167,7 @@ export default function useChatListEntry({
useLayoutEffect(() => { useLayoutEffect(() => {
const element = ref.current; const element = ref.current;
if (animationLevel === 0 || !element) { if (!withInterfaceAnimations || !element) {
return; return;
} }
@ -191,7 +197,7 @@ export default function useChatListEntry({
element.style.transform = ''; element.style.transform = '';
}); });
}, ANIMATION_DURATION + ANIMATION_END_DELAY); }, ANIMATION_DURATION + ANIMATION_END_DELAY);
}, [animationLevel, orderDiff, animationType]); }, [withInterfaceAnimations, orderDiff, animationType]);
return { return {
renderSubtitle, renderSubtitle,

View File

@ -5,7 +5,6 @@ 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 type { LangFn } from '../../../hooks/useLang'; import type { LangFn } from '../../../hooks/useLang';
import { import {
@ -45,7 +44,6 @@ 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> = ({
@ -54,7 +52,6 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
chatId, chatId,
chat, chat,
privateChatUser, privateChatUser,
animationLevel,
lastSyncTime, lastSyncTime,
}) => { }) => {
const { focusMessage } = getActions(); const { focusMessage } = getActions();
@ -88,8 +85,6 @@ 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">
@ -152,7 +147,6 @@ export default memo(withGlobal<OwnProps>(
return { return {
chat, chat,
lastSyncTime: global.lastSyncTime, lastSyncTime: global.lastSyncTime,
animationLevel: global.settings.byKey.animationLevel,
...(privateChatUserId && { privateChatUser }), ...(privateChatUserId && { privateChatUser }),
}; };
}, },

View File

@ -73,9 +73,9 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
buttonRef={buttonRef} buttonRef={buttonRef}
> >
{isUserId(chatId) ? ( {isUserId(chatId) ? (
<PrivateChatInfo userId={chatId} withUsername={withUsername} avatarSize="large" withVideoAvatar /> <PrivateChatInfo userId={chatId} withUsername={withUsername} avatarSize="large" />
) : ( ) : (
<GroupChatInfo chatId={chatId} withUsername={withUsername} avatarSize="large" withVideoAvatar /> <GroupChatInfo chatId={chatId} withUsername={withUsername} avatarSize="large" />
)} )}
<DeleteChatModal <DeleteChatModal
isOpen={isDeleteModalOpen} isOpen={isDeleteModalOpen}

View File

@ -4,10 +4,8 @@ import { withGlobal } from '../../../global';
import type { ApiTopic } from '../../../api/types'; import type { ApiTopic } from '../../../api/types';
import {
selectChat,
} from '../../../global/selectors';
import { REM } from '../../common/helpers/mediaDimensions'; import { REM } from '../../common/helpers/mediaDimensions';
import { selectChat } from '../../../global/selectors';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import useSelectWithEnter from '../../../hooks/useSelectWithEnter'; import useSelectWithEnter from '../../../hooks/useSelectWithEnter';

View File

@ -5,7 +5,6 @@ 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';
@ -14,8 +13,8 @@ import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import LeftSearchResultChat from './LeftSearchResultChat';
import Avatar from '../../common/Avatar'; import Avatar from '../../common/Avatar';
import LeftSearchResultChat from './LeftSearchResultChat';
import './RecentContacts.scss'; import './RecentContacts.scss';
@ -27,7 +26,6 @@ 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;
@ -39,7 +37,6 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
topUserIds, topUserIds,
usersById, usersById,
recentlyFoundChatIds, recentlyFoundChatIds,
animationLevel,
onReset, onReset,
}) => { }) => {
const { const {
@ -86,7 +83,7 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
onClick={() => handleClick(userId)} onClick={() => handleClick(userId)}
dir={lang.isRtl ? 'rtl' : undefined} dir={lang.isRtl ? 'rtl' : undefined}
> >
<Avatar user={usersById[userId]} animationLevel={animationLevel} withVideo /> <Avatar user={usersById[userId]} />
<div className="top-peer-name">{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}</div> <div className="top-peer-name">{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}</div>
</div> </div>
))} ))}
@ -126,13 +123,11 @@ 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; const { recentlyFoundChatIds } = global;
const { animationLevel } = global.settings.byKey;
return { return {
topUserIds, topUserIds,
usersById, usersById,
recentlyFoundChatIds, recentlyFoundChatIds,
animationLevel,
}; };
}, },
)(RecentContacts)); )(RecentContacts));

View File

@ -124,12 +124,15 @@
} }
} }
.settings-item-simple,
.settings-item { .settings-item {
background-color: var(--color-background); background-color: var(--color-background);
padding: 1.5rem 1.5rem 1rem; padding: 1.5rem 1.5rem 1rem;
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent); box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
margin-bottom: 0.625rem; margin-bottom: 0.625rem;
}
.settings-item {
&.no-border { &.no-border {
margin-bottom: 0; margin-bottom: 0;
box-shadow: none; box-shadow: none;
@ -360,6 +363,18 @@
} }
} }
.settings-dropdown-section {
margin: 0 -0.75rem 1rem -1rem;
.DropdownList {
position: relative;
&--open {
transform: translate(0, 0);
}
}
}
.SettingsDefaultReaction { .SettingsDefaultReaction {
.current-default-reaction { .current-default-reaction {
margin-inline-end: 2rem; margin-inline-end: 2rem;

View File

@ -31,6 +31,7 @@ import SettingsStickers from './SettingsStickers';
import SettingsCustomEmoji from './SettingsCustomEmoji'; import SettingsCustomEmoji from './SettingsCustomEmoji';
import SettingsDoNotTranslate from './SettingsDoNotTranslate'; import SettingsDoNotTranslate from './SettingsDoNotTranslate';
import SettingsExperimental from './SettingsExperimental'; import SettingsExperimental from './SettingsExperimental';
import SettingsPerformance from './SettingsPerformance';
import './Settings.scss'; import './Settings.scss';
@ -425,6 +426,14 @@ const Settings: FC<OwnProps> = ({
/> />
); );
case SettingsScreens.Performance:
return (
<SettingsPerformance
isActive={isScreenActive}
onReset={handleReset}
/>
);
default: default:
return undefined; return undefined;
} }

View File

@ -2,7 +2,6 @@ import React, { memo, useCallback } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import type { AnimationLevel } from '../../../types';
import type { ApiUser, ApiWebSession } from '../../../api/types'; import type { ApiUser, ApiWebSession } from '../../../api/types';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -26,14 +25,12 @@ 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, isOpen,
session, session,
bot, bot,
animationLevel,
onClose, onClose,
}) => { }) => {
const { terminateWebAuthorization } = getActions(); const { terminateWebAuthorization } = getActions();
@ -80,8 +77,6 @@ const SettingsActiveWebsite: FC<OwnProps & StateProps> = ({
className={styles.avatar} className={styles.avatar}
user={renderingBot} user={renderingBot}
size="large" size="large"
animationLevel={animationLevel}
withVideo
/> />
{renderingBot && <FullNameTitle className={styles.title} peer={renderingBot} />} {renderingBot && <FullNameTitle className={styles.title} peer={renderingBot} />}
<div className={styles.note}> <div className={styles.note}>
@ -112,6 +107,5 @@ export default memo(withGlobal<OwnProps>((global, { hash }): StateProps => {
return { return {
session, session,
bot, bot,
animationLevel: global.settings.byKey.animationLevel,
}; };
})(SettingsActiveWebsite)); })(SettingsActiveWebsite));

View File

@ -5,7 +5,6 @@ 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 buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -30,14 +29,12 @@ 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 {
@ -113,7 +110,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" withVideo animationLevel={animationLevel} /> <Avatar className={styles.avatar} user={bot} size="tiny" />
<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>
{bot && <FullNameTitle className={styles.title} peer={bot} />} {bot && <FullNameTitle className={styles.title} peer={bot} />}
@ -164,7 +161,6 @@ export default memo(withGlobal<OwnProps>(
return { return {
byHash, byHash,
orderedHashes, orderedHashes,
animationLevel: global.settings.byKey.animationLevel,
}; };
}, },
)(SettingsActiveWebsites)); )(SettingsActiveWebsites));

View File

@ -10,6 +10,7 @@ import type { ISettings } from '../../../types';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -27,6 +28,7 @@ type StateProps = Pick<ISettings, (
)> & { )> & {
customEmojiSetIds?: string[]; customEmojiSetIds?: string[];
stickerSetsById: Record<string, ApiStickerSet>; stickerSetsById: Record<string, ApiStickerSet>;
canPlayAnimatedEmojis: boolean;
}; };
const SettingsCustomEmoji: FC<OwnProps & StateProps> = ({ const SettingsCustomEmoji: FC<OwnProps & StateProps> = ({
@ -34,6 +36,7 @@ const SettingsCustomEmoji: FC<OwnProps & StateProps> = ({
customEmojiSetIds, customEmojiSetIds,
stickerSetsById, stickerSetsById,
shouldSuggestCustomEmoji, shouldSuggestCustomEmoji,
canPlayAnimatedEmojis,
onReset, onReset,
}) => { }) => {
const { openStickerSet, setSettingOption } = getActions(); const { openStickerSet, setSettingOption } = getActions();
@ -78,6 +81,7 @@ const SettingsCustomEmoji: FC<OwnProps & StateProps> = ({
stickerSet={stickerSet} stickerSet={stickerSet}
observeIntersection={observeIntersectionForCovers} observeIntersection={observeIntersectionForCovers}
onClick={handleStickerSetClick} onClick={handleStickerSetClick}
noPlay={!canPlayAnimatedEmojis}
/> />
))} ))}
</div> </div>
@ -91,13 +95,14 @@ const SettingsCustomEmoji: FC<OwnProps & StateProps> = ({
}; };
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global) => { (global): StateProps => {
return { return {
...pick(global.settings.byKey, [ ...pick(global.settings.byKey, [
'shouldSuggestCustomEmoji', 'shouldSuggestCustomEmoji',
]), ]),
customEmojiSetIds: global.customEmojis.added.setIds, customEmojiSetIds: global.customEmojis.added.setIds,
stickerSetsById: global.stickers.setsById, stickerSetsById: global.stickers.setsById,
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
}; };
}, },
)(SettingsCustomEmoji)); )(SettingsCustomEmoji));

View File

@ -30,8 +30,6 @@ type StateProps = Pick<ISettings, (
'canAutoLoadFileInPrivateChats' | 'canAutoLoadFileInPrivateChats' |
'canAutoLoadFileInGroups' | 'canAutoLoadFileInGroups' |
'canAutoLoadFileInChannels' | 'canAutoLoadFileInChannels' |
'canAutoPlayGifs' |
'canAutoPlayVideos' |
'autoLoadFileMaxSizeMb' 'autoLoadFileMaxSizeMb'
)>; )>;
@ -50,8 +48,6 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
canAutoLoadFileInPrivateChats, canAutoLoadFileInPrivateChats,
canAutoLoadFileInGroups, canAutoLoadFileInGroups,
canAutoLoadFileInChannels, canAutoLoadFileInChannels,
canAutoPlayGifs,
canAutoPlayVideos,
autoLoadFileMaxSizeMb, autoLoadFileMaxSizeMb,
}) => { }) => {
const { setSettingOption } = getActions(); const { setSettingOption } = getActions();
@ -71,14 +67,6 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
setSettingOption({ autoLoadFileMaxSizeMb: AUTODOWNLOAD_FILESIZE_MB_LIMITS[value] }); setSettingOption({ autoLoadFileMaxSizeMb: AUTODOWNLOAD_FILESIZE_MB_LIMITS[value] });
}, [setSettingOption]); }, [setSettingOption]);
const handleCanAutoPlayGifsChange = useCallback((value: boolean) => {
setSettingOption({ canAutoPlayGifs: value });
}, [setSettingOption]);
const handleCanAutoPlayVideosChange = useCallback((value: boolean) => {
setSettingOption({ canAutoPlayVideos: value });
}, [setSettingOption]);
function renderContentSizeSlider() { function renderContentSizeSlider() {
const value = AUTODOWNLOAD_FILESIZE_MB_LIMITS.indexOf(autoLoadFileMaxSizeMb); const value = AUTODOWNLOAD_FILESIZE_MB_LIMITS.indexOf(autoLoadFileMaxSizeMb);
@ -165,21 +153,6 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
canAutoLoadFileInGroups, canAutoLoadFileInGroups,
canAutoLoadFileInChannels, canAutoLoadFileInChannels,
)} )}
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('AutoplayMedia')}</h4>
<Checkbox
label={lang('GifsTab2')}
checked={canAutoPlayGifs}
onCheck={handleCanAutoPlayGifsChange}
/>
<Checkbox
label={lang('DataAndStorage.Autoplay.Videos')}
checked={canAutoPlayVideos}
onCheck={handleCanAutoPlayVideosChange}
/>
</div>
</div> </div>
); );
}; };
@ -199,8 +172,6 @@ export default memo(withGlobal<OwnProps>(
'canAutoLoadFileInPrivateChats', 'canAutoLoadFileInPrivateChats',
'canAutoLoadFileInGroups', 'canAutoLoadFileInGroups',
'canAutoLoadFileInChannels', 'canAutoLoadFileInChannels',
'canAutoPlayGifs',
'canAutoPlayVideos',
'autoLoadFileMaxSizeMb', 'autoLoadFileMaxSizeMb',
]); ]);
}, },

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 { AnimationLevel, ISettings, TimeFormat } from '../../../types'; import type { ISettings, TimeFormat } from '../../../types';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { import {
@ -37,12 +37,6 @@ type StateProps =
shouldUseSystemTheme: boolean; shouldUseSystemTheme: boolean;
}; };
const ANIMATION_LEVEL_OPTIONS = [
'Solid and Steady',
'Nice and Fast',
'Lots of Stuff',
];
const TIME_FORMAT_OPTIONS: IRadioOption[] = [{ const TIME_FORMAT_OPTIONS: IRadioOption[] = [{
label: '12-hour', label: '12-hour',
value: '12h', value: '12h',
@ -56,7 +50,6 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
onScreenSelect, onScreenSelect,
onReset, onReset,
messageTextSize, messageTextSize,
animationLevel,
messageSendKeyCombo, messageSendKeyCombo,
timeFormat, timeFormat,
theme, theme,
@ -88,14 +81,6 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
}, },
] : undefined; ] : undefined;
const handleAnimationLevelChange = useCallback((newLevel: number) => {
ANIMATION_LEVEL_OPTIONS.forEach((_, i) => {
document.body.classList.toggle(`animation-level-${i}`, newLevel === i);
});
setSettingOption({ animationLevel: newLevel as AnimationLevel });
}, [setSettingOption]);
const handleMessageTextSizeChange = useCallback((newSize: number) => { const handleMessageTextSizeChange = useCallback((newSize: number) => {
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
'--composer-text-size', `${Math.max(newSize, IS_IOS ? 16 : 15)}px`, '--composer-text-size', `${Math.max(newSize, IS_IOS ? 16 : 15)}px`,
@ -176,21 +161,6 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
/> />
</div> </div>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
Animation Level
</h4>
<p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
Choose the desired animations amount.
</p>
<RangeSlider
options={ANIMATION_LEVEL_OPTIONS}
value={animationLevel}
onChange={handleAnimationLevelChange}
/>
</div>
{KEYBOARD_SEND_OPTIONS && ( {KEYBOARD_SEND_OPTIONS && (
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('VoiceOver.Keyboard')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('VoiceOver.Keyboard')}</h4>

View File

@ -145,6 +145,9 @@ const SettingsHeader: FC<OwnProps> = ({
case SettingsScreens.PrivacyPhoneP2PDeniedContacts: case SettingsScreens.PrivacyPhoneP2PDeniedContacts:
return <h3>{lang('NeverShareWith')}</h3>; return <h3>{lang('NeverShareWith')}</h3>;
case SettingsScreens.Performance:
return <h3>{lang('Animations and Performance')}</h3>;
case SettingsScreens.ActiveSessions: case SettingsScreens.ActiveSessions:
return <h3>{lang('SessionsTitle')}</h3>; return <h3>{lang('SessionsTitle')}</h3>;
case SettingsScreens.ActiveWebsites: case SettingsScreens.ActiveWebsites:

View File

@ -85,6 +85,13 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
> >
{lang('Telegram.GeneralSettingsViewController')} {lang('Telegram.GeneralSettingsViewController')}
</ListItem> </ListItem>
<ListItem
icon="animations"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Performance)}
>
{lang('Animations and Performance')}
</ListItem>
<ListItem <ListItem
icon="unmute" icon="unmute"
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind

View File

@ -0,0 +1,221 @@
import React, {
memo, useCallback, useMemo, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { AnimationLevel, PerformanceType, PerformanceTypeKey } from '../../../types';
import { IS_BACKDROP_BLUR_SUPPORTED } from '../../../util/windowEnvironment';
import {
ANIMATION_LEVEL_CUSTOM, ANIMATION_LEVEL_MAX, ANIMATION_LEVEL_MED, ANIMATION_LEVEL_MIN,
} from '../../../config';
import {
INITIAL_PERFORMANCE_STATE_MAX,
INITIAL_PERFORMANCE_STATE_MID,
INITIAL_PERFORMANCE_STATE_MIN,
} from '../../../global/initialState';
import { selectPerformanceSettings } from '../../../global/selectors';
import { areDeepEqual } from '../../../util/areDeepEqual';
import useLang from '../../../hooks/useLang';
import useHistoryBack from '../../../hooks/useHistoryBack';
import RangeSlider from '../../ui/RangeSlider';
import Checkbox from '../../ui/Checkbox';
type PerformanceSection = [string, PerformanceOption[]];
type PerformanceOption = {
key: PerformanceTypeKey;
label: string;
disabled?: boolean;
};
type OwnProps = {
isActive?: boolean;
onReset: () => void;
};
type StateProps = {
performanceSettings: PerformanceType;
};
const ANIMATION_LEVEL_OPTIONS = [
'Power Saving',
'Nice and Fast',
'Lots of Stuff',
];
const ANIMATION_LEVEL_CUSTOM_OPTIONS = [
'Power Saving',
'Custom',
'Lots of Stuff',
];
const PERFORMANCE_OPTIONS: PerformanceSection[] = [
['LiteMode.Key.animations.Title', [
{ key: 'pageTransitions', label: 'Page Transitions' },
{ key: 'messageSendingAnimations', label: 'Message Sending Animation' },
{ key: 'mediaViewerAnimations', label: 'Media Viewer Animations' },
{ key: 'messageComposerAnimations', label: 'Message Composer Animations' },
{ key: 'contextMenuAnimations', label: 'Context Menu Animation' },
{ key: 'contextMenuBlur', label: 'Context Menu Blur', disabled: !IS_BACKDROP_BLUR_SUPPORTED },
{ key: 'rightColumnAnimations', label: 'Right Column Animation' },
]],
['Stickers and Emoji', [
{ key: 'animatedEmoji', label: 'Allow Animated Emoji' },
{ key: 'loopAnimatedStickers', label: 'Loop Animated Stickers' },
{ key: 'reactionEffects', label: 'Reaction Effects' },
{ key: 'stickerEffects', label: 'Full-Screen Sticker and Emoji Effects' },
]],
['AutoplayMedia', [
{ key: 'autoplayGifs', label: 'AutoplayGIF' },
{ key: 'autoplayVideos', label: 'AutoplayVideo' },
]],
];
function SettingsPerformance({
isActive,
performanceSettings,
onReset,
}: OwnProps & StateProps) {
const {
setSettingOption,
updatePerformanceSettings,
} = getActions();
useHistoryBack({
isActive,
onBack: onReset,
});
const lang = useLang();
const [sectionExpandedStates, setSectionExpandedStates] = useState<Record<number, boolean>>({});
const sectionCheckedStates = useMemo(() => {
return PERFORMANCE_OPTIONS.reduce((acc, [, options], index) => {
acc[index] = options.every(({ key }) => performanceSettings[key]);
return acc;
}, {} as Record<number, boolean>);
}, [performanceSettings]);
const animationLevelState = useMemo(() => {
if (areDeepEqual(performanceSettings, INITIAL_PERFORMANCE_STATE_MAX)) {
return ANIMATION_LEVEL_MAX;
}
if (areDeepEqual(performanceSettings, INITIAL_PERFORMANCE_STATE_MIN)) {
return ANIMATION_LEVEL_MIN;
}
if (areDeepEqual(performanceSettings, INITIAL_PERFORMANCE_STATE_MID)) {
return ANIMATION_LEVEL_MED;
}
return ANIMATION_LEVEL_CUSTOM;
}, [performanceSettings]);
const animationLevelOptions = animationLevelState === ANIMATION_LEVEL_CUSTOM
? ANIMATION_LEVEL_CUSTOM_OPTIONS
: ANIMATION_LEVEL_OPTIONS;
const handleToggleSection = useCallback((e: React.MouseEvent, index?: string) => {
e.preventDefault();
const sectionIndex = Number(index);
setSectionExpandedStates((prev) => ({
...prev,
[sectionIndex]: !prev[sectionIndex],
}));
}, []);
const handleAnimationLevelChange = useCallback((newLevel: number) => {
const performance = newLevel === ANIMATION_LEVEL_MIN
? INITIAL_PERFORMANCE_STATE_MIN
: (newLevel === ANIMATION_LEVEL_MED ? INITIAL_PERFORMANCE_STATE_MID : INITIAL_PERFORMANCE_STATE_MAX);
setSettingOption({ animationLevel: newLevel as AnimationLevel });
updatePerformanceSettings(performance);
}, [setSettingOption]);
const handlePropertyGroupChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { name, checked } = e.target;
const perfomanceSection = PERFORMANCE_OPTIONS.find(([sectionName]) => sectionName === name);
if (!perfomanceSection) {
return;
}
const newSettings = perfomanceSection[1].reduce((acc, { key }) => {
acc[key] = checked;
return acc;
}, {} as Partial<PerformanceType>);
updatePerformanceSettings(newSettings);
}, []);
const handlePropertyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { name, checked } = e.target;
updatePerformanceSettings({ [name as PerformanceTypeKey]: checked });
}, []);
return (
<div className="settings-content custom-scroll">
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
Animation Level
</h4>
<p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
Choose the desired animations amount.
</p>
<RangeSlider
options={animationLevelOptions}
value={animationLevelState === ANIMATION_LEVEL_CUSTOM ? ANIMATION_LEVEL_MED : animationLevelState}
onChange={handleAnimationLevelChange}
/>
</div>
<div className="settings-item-simple settings-item__with-shifted-dropdown">
<h3 className="settings-item-header" dir="auto">Resource-Intensive Processes</h3>
{PERFORMANCE_OPTIONS.map(([sectionName, options], index) => {
return (
<div
key={sectionName}
className="settings-dropdown-section"
>
<div className="ListItem no-selection with-checkbox">
<Checkbox
name={sectionName}
value={index.toString()}
checked={sectionCheckedStates[index]}
label={lang(sectionName)}
rightIcon={sectionExpandedStates[index] ? 'up' : 'down'}
onChange={handlePropertyGroupChange}
onClickLabel={handleToggleSection}
/>
</div>
{Boolean(sectionExpandedStates[index]) && (
<div className="DropdownList DropdownList--open">
{options.map(({ key, label, disabled }) => (
<Checkbox
key={key}
name={key}
checked={performanceSettings[key]}
label={lang(label)}
disabled={disabled}
onChange={handlePropertyChange}
/>
))}
</div>
)}
</div>
);
})}
</div>
</div>
);
}
export default memo(withGlobal<OwnProps>((global): StateProps => {
return {
performanceSettings: selectPerformanceSettings(global),
};
})(SettingsPerformance));

View File

@ -17,6 +17,7 @@ import renderText from '../../common/helpers/renderText';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import { REM } from '../../common/helpers/mediaDimensions'; import { REM } from '../../common/helpers/mediaDimensions';
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -36,14 +37,14 @@ type OwnProps = {
type StateProps = type StateProps =
Pick<ISettings, ( Pick<ISettings, (
'shouldSuggestStickers' | 'shouldSuggestStickers'
'shouldLoopStickers'
)> & { )> & {
addedSetIds?: string[]; addedSetIds?: string[];
customEmojiSetIds?: string[]; customEmojiSetIds?: string[];
stickerSetsById: Record<string, ApiStickerSet>; stickerSetsById: Record<string, ApiStickerSet>;
defaultReaction?: ApiReaction; defaultReaction?: ApiReaction;
availableReactions?: ApiAvailableReaction[]; availableReactions?: ApiAvailableReaction[];
canPlayAnimatedEmojis: boolean;
}; };
const SettingsStickers: FC<OwnProps & StateProps> = ({ const SettingsStickers: FC<OwnProps & StateProps> = ({
@ -53,8 +54,8 @@ const SettingsStickers: FC<OwnProps & StateProps> = ({
stickerSetsById, stickerSetsById,
defaultReaction, defaultReaction,
shouldSuggestStickers, shouldSuggestStickers,
shouldLoopStickers,
availableReactions, availableReactions,
canPlayAnimatedEmojis,
onReset, onReset,
onScreenSelect, onScreenSelect,
}) => { }) => {
@ -78,10 +79,6 @@ const SettingsStickers: FC<OwnProps & StateProps> = ({
setSettingOption({ shouldSuggestStickers: newValue }); setSettingOption({ shouldSuggestStickers: newValue });
}, [setSettingOption]); }, [setSettingOption]);
const handleShouldLoopStickersChange = useCallback((newValue: boolean) => {
setSettingOption({ shouldLoopStickers: newValue });
}, [setSettingOption]);
const stickerSets = useMemo(() => ( const stickerSets = useMemo(() => (
addedSetIds && Object.values(pick(stickerSetsById, addedSetIds)) addedSetIds && Object.values(pick(stickerSetsById, addedSetIds))
), [addedSetIds, stickerSetsById]); ), [addedSetIds, stickerSetsById]);
@ -99,11 +96,6 @@ const SettingsStickers: FC<OwnProps & StateProps> = ({
checked={shouldSuggestStickers} checked={shouldSuggestStickers}
onCheck={handleSuggestStickersChange} onCheck={handleSuggestStickersChange}
/> />
<Checkbox
label={lang('LoopAnimatedStickers')}
checked={shouldLoopStickers}
onCheck={handleShouldLoopStickersChange}
/>
<ListItem <ListItem
className="mt-4" className="mt-4"
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
@ -141,6 +133,7 @@ const SettingsStickers: FC<OwnProps & StateProps> = ({
stickerSet={stickerSet} stickerSet={stickerSet}
observeIntersection={observeIntersectionForCovers} observeIntersection={observeIntersectionForCovers}
onClick={handleStickerSetClick} onClick={handleStickerSetClick}
noPlay={!canPlayAnimatedEmojis}
/> />
))} ))}
</div> </div>
@ -158,13 +151,13 @@ export default memo(withGlobal<OwnProps>(
return { return {
...pick(global.settings.byKey, [ ...pick(global.settings.byKey, [
'shouldSuggestStickers', 'shouldSuggestStickers',
'shouldLoopStickers',
]), ]),
addedSetIds: global.stickers.added.setIds, addedSetIds: global.stickers.added.setIds,
customEmojiSetIds: global.customEmojis.added.setIds, customEmojiSetIds: global.customEmojis.added.setIds,
stickerSetsById: global.stickers.setsById, stickerSetsById: global.stickers.setsById,
defaultReaction: global.config?.defaultReaction, defaultReaction: global.config?.defaultReaction,
availableReactions: global.availableReactions, availableReactions: global.availableReactions,
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
}; };
}, },
)(SettingsStickers)); )(SettingsStickers));

View File

@ -5,7 +5,6 @@ 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 { selectTabState } from '../../global/selectors'; import { selectTabState } from '../../global/selectors';
import getReadableErrorText from '../../util/getReadableErrorText'; import getReadableErrorText from '../../util/getReadableErrorText';
@ -20,10 +19,9 @@ import Avatar from '../common/Avatar';
type StateProps = { type StateProps = {
dialogs: (ApiError | ApiInviteInfo | ApiContact)[]; dialogs: (ApiError | ApiInviteInfo | ApiContact)[];
animationLevel: AnimationLevel;
}; };
const Dialogs: FC<StateProps> = ({ dialogs, animationLevel }) => { const Dialogs: FC<StateProps> = ({ dialogs }) => {
const { const {
dismissDialog, dismissDialog,
acceptInviteConfirmation, acceptInviteConfirmation,
@ -47,7 +45,7 @@ const Dialogs: FC<StateProps> = ({ dialogs, animationLevel }) => {
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} animationLevel={animationLevel} withVideo />} {photo && <Avatar size="small" photo={photo} withVideo />}
<div className="modal-title"> <div className="modal-title">
{renderText(title)} {renderText(title)}
</div> </div>
@ -196,7 +194,6 @@ export default memo(withGlobal(
(global): StateProps => { (global): StateProps => {
return { return {
dialogs: selectTabState(global).dialogs, dialogs: selectTabState(global).dialogs,
animationLevel: global.settings.byKey.animationLevel,
}; };
}, },
)(Dialogs)); )(Dialogs));

View File

@ -62,7 +62,7 @@
transform: translate3d(-5rem, 0, 0); transform: translate3d(-5rem, 0, 0);
transition: transform var(--layer-transition); transition: transform var(--layer-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none; transition: none;
} }
@ -80,10 +80,6 @@
z-index: 1; z-index: 1;
pointer-events: none; pointer-events: none;
body.animation-level-0 & {
transition: none;
}
// @optimization // @optimization
body.is-android & { body.is-android & {
display: none; display: none;
@ -159,13 +155,18 @@
transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
transition: transform var(--layer-transition); transition: transform var(--layer-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none; transition: none;
} }
#Main.left-column-open & { #Main.left-column-open & {
transform: translate3d(26.5rem, 0, 0); transform: translate3d(26.5rem, 0, 0);
} }
body.no-right-column-animations #Main.right-column-open &,
body.no-right-column-animations #Main.right-column-shown & {
transition: none;
}
} }
@media (max-width: 600px) { @media (max-width: 600px) {
@ -189,7 +190,15 @@
} }
} }
body.is-android.animation-level-1 { body.is-android:not(.no-right-column-animations) {
--layer-transition: 250ms ease-in-out;
#RightColumn {
transition: transform var(--layer-transition), opacity var(--layer-transition);
}
}
body.is-android.no-page-transitions {
--layer-transition: 250ms ease-in-out; --layer-transition: 250ms ease-in-out;
#LeftColumn, #MiddleColumn, #RightColumn { #LeftColumn, #MiddleColumn, #RightColumn {

View File

@ -6,7 +6,7 @@ import { addExtraClass } from '../../lib/teact/teact-dom';
import { requestNextMutation } from '../../lib/fasterdom/fasterdom'; import { requestNextMutation } from '../../lib/fasterdom/fasterdom';
import { getActions, getGlobal, withGlobal } from '../../global'; import { getActions, getGlobal, withGlobal } from '../../global';
import type { AnimationLevel, LangCode } from '../../types'; import type { LangCode } from '../../types';
import type { import type {
ApiAttachBot, ApiAttachBot,
ApiChat, ApiMessage, ApiUser, ApiChat, ApiMessage, ApiUser,
@ -27,7 +27,10 @@ import {
selectIsMediaViewerOpen, selectIsMediaViewerOpen,
selectIsRightColumnShown, selectIsRightColumnShown,
selectIsServiceChatReady, selectIsServiceChatReady,
selectUser, selectIsReactionPickerOpen, selectUser,
selectIsReactionPickerOpen,
selectPerformanceSettingsValue,
selectCanAnimateInterface,
} from '../../global/selectors'; } from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners'; import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
@ -109,7 +112,6 @@ type StateProps = {
openedCustomEmojiSetIds?: string[]; openedCustomEmojiSetIds?: string[];
activeGroupCallId?: string; activeGroupCallId?: string;
isServiceChatReady?: boolean; isServiceChatReady?: boolean;
animationLevel: AnimationLevel;
language?: LangCode; language?: LangCode;
wasTimeFormatSetManually?: boolean; wasTimeFormatSetManually?: boolean;
isPhoneCallActive?: boolean; isPhoneCallActive?: boolean;
@ -135,6 +137,8 @@ type StateProps = {
isReceiptModalOpen?: boolean; isReceiptModalOpen?: boolean;
isReactionPickerOpen: boolean; isReactionPickerOpen: boolean;
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
noRightColumnAnimation?: boolean;
withInterfaceAnimations?: boolean;
}; };
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
@ -163,7 +167,7 @@ const Main: FC<OwnProps & StateProps> = ({
openedStickerSetShortName, openedStickerSetShortName,
openedCustomEmojiSetIds, openedCustomEmojiSetIds,
isServiceChatReady, isServiceChatReady,
animationLevel, withInterfaceAnimations,
language, language,
wasTimeFormatSetManually, wasTimeFormatSetManually,
addedSetIds, addedSetIds,
@ -189,6 +193,7 @@ const Main: FC<OwnProps & StateProps> = ({
isCurrentUserPremium, isCurrentUserPremium,
deleteFolderDialogId, deleteFolderDialogId,
isMasterTab, isMasterTab,
noRightColumnAnimation,
}) => { }) => {
const { const {
initMain, initMain,
@ -386,7 +391,7 @@ const Main: FC<OwnProps & StateProps> = ({
// Handle opening middle column // Handle opening middle column
useSyncEffect(([prevIsLeftColumnOpen]) => { useSyncEffect(([prevIsLeftColumnOpen]) => {
if (prevIsLeftColumnOpen === undefined || isLeftColumnOpen === prevIsLeftColumnOpen || animationLevel === 0) { if (prevIsLeftColumnOpen === undefined || isLeftColumnOpen === prevIsLeftColumnOpen || !withInterfaceAnimations) {
return; return;
} }
@ -405,7 +410,7 @@ const Main: FC<OwnProps & StateProps> = ({
willAnimateLeftColumnRef.current = false; willAnimateLeftColumnRef.current = false;
forceUpdate(); forceUpdate();
}); });
}, [isLeftColumnOpen, animationLevel, forceUpdate]); }, [isLeftColumnOpen, withInterfaceAnimations, forceUpdate]);
const rightColumnTransition = useShowTransition( const rightColumnTransition = useShowTransition(
isRightColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations, undefined, true, isRightColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations, undefined, true,
@ -419,7 +424,7 @@ const Main: FC<OwnProps & StateProps> = ({
return; return;
} }
if (animationLevel === 0) { if (noRightColumnAnimation) {
setIsNarrowMessageList(isRightColumnOpen); setIsNarrowMessageList(isRightColumnOpen);
return; return;
} }
@ -434,7 +439,7 @@ const Main: FC<OwnProps & StateProps> = ({
forceUpdate(); forceUpdate();
setIsNarrowMessageList(isRightColumnOpen); setIsNarrowMessageList(isRightColumnOpen);
}); });
}, [isRightColumnOpen, animationLevel, forceUpdate]); }, [isRightColumnOpen, noRightColumnAnimation, forceUpdate]);
const className = buildClassName( const className = buildClassName(
leftColumnTransition.hasShownClass && 'left-column-shown', leftColumnTransition.hasShownClass && 'left-column-shown',
@ -534,7 +539,7 @@ export default memo(withGlobal<OwnProps>(
const { const {
settings: { settings: {
byKey: { byKey: {
animationLevel, language, wasTimeFormatSetManually, language, wasTimeFormatSetManually,
}, },
}, },
lastSyncTime, lastSyncTime,
@ -574,6 +579,8 @@ export default memo(withGlobal<OwnProps>(
const gameTitle = gameMessage?.content.game?.title; const gameTitle = gameMessage?.content.game?.title;
const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined; const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
const { chatId } = selectCurrentMessageList(global) || {}; const { chatId } = selectCurrentMessageList(global) || {};
const noRightColumnAnimation = !selectPerformanceSettingsValue(global, 'rightColumnAnimations')
|| !selectCanAnimateInterface(global);
return { return {
lastSyncTime, lastSyncTime,
@ -593,7 +600,7 @@ export default memo(withGlobal<OwnProps>(
openedCustomEmojiSetIds, openedCustomEmojiSetIds,
isServiceChatReady: selectIsServiceChatReady(global), isServiceChatReady: selectIsServiceChatReady(global),
activeGroupCallId: isMasterTab ? global.groupCalls.activeGroupCallId : undefined, activeGroupCallId: isMasterTab ? global.groupCalls.activeGroupCallId : undefined,
animationLevel, withInterfaceAnimations: selectCanAnimateInterface(global),
language, language,
wasTimeFormatSetManually, wasTimeFormatSetManually,
isPhoneCallActive: isMasterTab ? Boolean(global.phoneCall) : undefined, isPhoneCallActive: isMasterTab ? Boolean(global.phoneCall) : undefined,
@ -619,6 +626,7 @@ export default memo(withGlobal<OwnProps>(
deleteFolderDialogId: deleteFolderDialogModal, deleteFolderDialogId: deleteFolderDialogModal,
isMasterTab, isMasterTab,
requestedDraft, requestedDraft,
noRightColumnAnimation,
}; };
}, },
)(Main)); )(Main));

View File

@ -5,7 +5,6 @@ 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';
@ -36,7 +35,6 @@ 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> = ({
@ -45,7 +43,6 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
gifts, gifts,
monthlyCurrency, monthlyCurrency,
monthlyAmount, monthlyAmount,
animationLevel,
}) => { }) => {
const { openPremiumModal, closeGiftPremiumModal, openUrl } = getActions(); const { openPremiumModal, closeGiftPremiumModal, openUrl } = getActions();
@ -131,8 +128,6 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
user={renderedUser} user={renderedUser}
size="jumbo" size="jumbo"
className={styles.avatar} className={styles.avatar}
animationLevel={animationLevel}
withVideo
/> />
<h2 className={styles.headerText}> <h2 className={styles.headerText}>
{lang('GiftTelegramPremiumTitle')} {lang('GiftTelegramPremiumTitle')}
@ -179,6 +174,5 @@ 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

@ -219,7 +219,10 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
return ( return (
<div className={styles.footerText} dir={lang.isRtl ? 'rtl' : undefined}> <div className={styles.footerText} dir={lang.isRtl ? 'rtl' : undefined}>
{renderTextWithEntities(promo.statusText, promo.statusEntities)} {renderTextWithEntities({
text: promo.statusText,
entities: promo.statusEntities,
})}
</div> </div>
); );
} }

View File

@ -35,7 +35,7 @@
} }
} }
body.animation-level-2 & { body:not(.no-media-viewer-animations) & {
transition-duration: 0.3s !important; transition-duration: 0.3s !important;
} }

View File

@ -6,7 +6,6 @@ import React, {
import type { import type {
ApiChat, ApiMessage, ApiPhoto, ApiUser, ApiChat, ApiMessage, ApiPhoto, 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';
@ -23,6 +22,7 @@ import {
selectUser, selectUser,
selectOutlyingListByMessageId, selectOutlyingListByMessageId,
selectUserFullInfo, selectUserFullInfo,
selectPerformanceSettingsValue,
} from '../../global/selectors'; } from '../../global/selectors';
import { stopCurrentAudio } from '../../util/audioPlayer'; import { stopCurrentAudio } from '../../util/audioPlayer';
import captureEscKeyListener from '../../util/captureEscKeyListener'; import captureEscKeyListener from '../../util/captureEscKeyListener';
@ -68,7 +68,7 @@ type StateProps = {
chatMessages?: Record<number, ApiMessage>; chatMessages?: Record<number, ApiMessage>;
collectionIds?: number[]; collectionIds?: number[];
isHidden?: boolean; isHidden?: boolean;
animationLevel: AnimationLevel; withAnimation?: boolean;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
}; };
@ -87,7 +87,7 @@ const MediaViewer: FC<StateProps> = ({
message, message,
chatMessages, chatMessages,
collectionIds, collectionIds,
animationLevel, withAnimation,
isHidden, isHidden,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
}) => { }) => {
@ -105,8 +105,8 @@ const MediaViewer: FC<StateProps> = ({
/* Animation */ /* Animation */
const animationKey = useRef<number>(); const animationKey = useRef<number>();
const prevSenderId = usePrevious<string | undefined>(senderId); const prevSenderId = usePrevious<string | undefined>(senderId);
const headerAnimation = animationLevel === 2 ? 'slideFade' : 'none'; const headerAnimation = withAnimation ? 'slideFade' : 'none';
const isGhostAnimation = animationLevel === 2 && !shouldSkipHistoryAnimations; const isGhostAnimation = Boolean(withAnimation && !shouldSkipHistoryAnimations);
/* Controls */ /* Controls */
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag(); const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
@ -373,7 +373,7 @@ const MediaViewer: FC<StateProps> = ({
isOpen={isOpen} isOpen={isOpen}
hasFooter={hasFooter} hasFooter={hasFooter}
isVideo={isVideo} isVideo={isVideo}
animationLevel={animationLevel} withAnimation={withAnimation}
onClose={handleClose} onClose={handleClose}
selectMedia={selectMedia} selectMedia={selectMedia}
isHidden={isHidden} isHidden={isHidden}
@ -394,21 +394,19 @@ export default memo(withGlobal(
origin, origin,
isHidden, isHidden,
} = mediaViewer; } = mediaViewer;
const { const withAnimation = selectPerformanceSettingsValue(global, 'mediaViewerAnimations');
animationLevel,
} = global.settings.byKey;
const { currentUserId } = global; const { currentUserId } = global;
let isChatWithSelf = !!chatId && selectIsChatWithSelf(global, chatId); let isChatWithSelf = !!chatId && selectIsChatWithSelf(global, chatId);
if (origin === MediaViewerOrigin.SearchResult) { if (origin === MediaViewerOrigin.SearchResult) {
if (!(chatId && mediaId)) { if (!(chatId && mediaId)) {
return { animationLevel, shouldSkipHistoryAnimations }; return { withAnimation, shouldSkipHistoryAnimations };
} }
const message = selectChatMessage(global, chatId, mediaId); const message = selectChatMessage(global, chatId, mediaId);
if (!message) { if (!message) {
return { animationLevel, shouldSkipHistoryAnimations }; return { withAnimation, shouldSkipHistoryAnimations };
} }
return { return {
@ -418,7 +416,7 @@ export default memo(withGlobal(
isChatWithSelf, isChatWithSelf,
origin, origin,
message, message,
animationLevel, withAnimation,
isHidden, isHidden,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
}; };
@ -443,7 +441,7 @@ export default memo(withGlobal(
avatarOwnerFallbackPhoto: user ? selectUserFullInfo(global, avatarOwnerId)?.fallbackPhoto : undefined, avatarOwnerFallbackPhoto: user ? selectUserFullInfo(global, avatarOwnerId)?.fallbackPhoto : undefined,
isChatWithSelf, isChatWithSelf,
canUpdateMedia, canUpdateMedia,
animationLevel, withAnimation,
origin, origin,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
isHidden, isHidden,
@ -451,7 +449,7 @@ export default memo(withGlobal(
} }
if (!(chatId && threadId && mediaId)) { if (!(chatId && threadId && mediaId)) {
return { animationLevel, shouldSkipHistoryAnimations }; return { withAnimation, shouldSkipHistoryAnimations };
} }
let message: ApiMessage | undefined; let message: ApiMessage | undefined;
@ -462,7 +460,7 @@ export default memo(withGlobal(
} }
if (!message) { if (!message) {
return { animationLevel, shouldSkipHistoryAnimations }; return { withAnimation, shouldSkipHistoryAnimations };
} }
let chatMessages: Record<number, ApiMessage> | undefined; let chatMessages: Record<number, ApiMessage> | undefined;
@ -494,7 +492,7 @@ export default memo(withGlobal(
message, message,
chatMessages, chatMessages,
collectionIds, collectionIds,
animationLevel, withAnimation,
isHidden, isHidden,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
}; };

View File

@ -5,7 +5,6 @@ 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 { import {
@ -36,7 +35,7 @@ type OwnProps = {
avatarOwnerId?: string; avatarOwnerId?: string;
origin?: MediaViewerOrigin; origin?: MediaViewerOrigin;
isActive?: boolean; isActive?: boolean;
animationLevel: AnimationLevel; withAnimation?: boolean;
onClose: () => void; onClose: () => void;
onFooterClick: () => void; onFooterClick: () => void;
isMoving?: boolean; isMoving?: boolean;
@ -69,7 +68,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
chatId, chatId,
message, message,
origin, origin,
animationLevel, withAnimation,
isProtected, isProtected,
volume, volume,
playbackRate, playbackRate,
@ -82,8 +81,6 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
const lang = useLang(); const lang = useLang();
const isGhostAnimation = animationLevel === 2;
const { const {
isVideo, isVideo,
isPhoto, isPhoto,
@ -97,7 +94,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
videoSize, videoSize,
loadProgress, loadProgress,
} = useMediaProps({ } = useMediaProps({
message, avatarOwner, mediaId, origin, delay: isGhostAnimation && ANIMATION_DURATION, message, avatarOwner, mediaId, origin, delay: withAnimation ? ANIMATION_DURATION : false,
}); });
const [, toggleControls] = useControlsSignal(); const [, toggleControls] = useControlsSignal();

View File

@ -3,7 +3,7 @@ import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef, useState, memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import type { AnimationLevel, MediaViewerOrigin } from '../../types'; import type { 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';
@ -43,7 +43,7 @@ type OwnProps = {
threadId?: number; threadId?: number;
avatarOwnerId?: string; avatarOwnerId?: string;
origin?: MediaViewerOrigin; origin?: MediaViewerOrigin;
animationLevel: AnimationLevel; withAnimation?: boolean;
onClose: () => void; onClose: () => void;
isHidden?: boolean; isHidden?: boolean;
hasFooter?: boolean; hasFooter?: boolean;
@ -85,7 +85,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
isPhoto, isPhoto,
isOpen, isOpen,
hasFooter, hasFooter,
animationLevel, withAnimation,
isHidden, isHidden,
...rest ...rest
}) => { }) => {
@ -196,7 +196,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
selectMediaDebounced(mId); selectMediaDebounced(mId);
setIsActiveDebounced(true); setIsActiveDebounced(true);
lastTransform = { x: 0, y: 0, scale: 1 }; lastTransform = { x: 0, y: 0, scale: 1 };
if (animationLevel === 0) { if (!withAnimation) {
setTransform(lastTransform); setTransform(lastTransform);
return true; return true;
} }
@ -643,7 +643,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
selectMediaDebounced, selectMediaDebounced,
setIsActiveDebounced, setIsActiveDebounced,
clearSwipeDirectionDebounced, clearSwipeDirectionDebounced,
animationLevel, withAnimation,
setIsMouseDown, setIsMouseDown,
setIsActive, setIsActive,
isHidden, isHidden,
@ -703,7 +703,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
<MediaViewerContent <MediaViewerContent
/* eslint-disable-next-line react/jsx-props-no-spreading */ /* eslint-disable-next-line react/jsx-props-no-spreading */
{...rest} {...rest}
animationLevel={animationLevel} withAnimation={withAnimation}
isMoving={isMoving} isMoving={isMoving}
mediaId={prevMediaId} mediaId={prevMediaId}
/> />
@ -722,7 +722,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
/* eslint-disable-next-line react/jsx-props-no-spreading */ /* eslint-disable-next-line react/jsx-props-no-spreading */
{...rest} {...rest}
mediaId={activeMediaId} mediaId={activeMediaId}
animationLevel={animationLevel} withAnimation={withAnimation}
isActive={isActive} isActive={isActive}
isMoving={isMoving} isMoving={isMoving}
/> />
@ -732,7 +732,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
<MediaViewerContent <MediaViewerContent
/* eslint-disable-next-line react/jsx-props-no-spreading */ /* eslint-disable-next-line react/jsx-props-no-spreading */
{...rest} {...rest}
animationLevel={animationLevel} withAnimation={withAnimation}
isMoving={isMoving} isMoving={isMoving}
mediaId={nextMediaId} mediaId={nextMediaId}
/> />

View File

@ -3,7 +3,6 @@ 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 { getSenderTitle, isUserId } from '../../global/helpers'; import { getSenderTitle, isUserId } from '../../global/helpers';
import { formatMediaDateTime } from '../../util/dateFormat'; import { formatMediaDateTime } from '../../util/dateFormat';
@ -31,7 +30,6 @@ 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;
@ -43,7 +41,6 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
isFallbackAvatar, isFallbackAvatar,
isAvatar, isAvatar,
message, message,
animationLevel,
}) => { }) => {
const { const {
closeMediaViewer, closeMediaViewer,
@ -79,9 +76,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} animationLevel={animationLevel} withVideo /> <Avatar key={sender.id} size="medium" user={sender as ApiUser} />
) : ( ) : (
<Avatar key={sender.id} size="medium" chat={sender as ApiChat} animationLevel={animationLevel} withVideo /> <Avatar key={sender.id} size="medium" chat={sender as ApiChat} />
)} )}
<div className="meta"> <div className="meta">
<div className="title" dir="auto"> <div className="title" dir="auto">
@ -99,16 +96,14 @@ 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 { animationLevel }; return {};
} }
const message = selectChatMessage(global, chatId, messageId); const message = selectChatMessage(global, chatId, messageId);
@ -116,7 +111,6 @@ export default withGlobal<OwnProps>(
return { return {
message, message,
sender: message && selectSender(global, message), sender: message && selectSender(global, message),
animationLevel,
}; };
}, },
)(SenderInfo); )(SenderInfo);

View File

@ -45,7 +45,7 @@
height: 3.25rem; height: 3.25rem;
background-color: rgba(0, 0, 0, 0.5) !important; background-color: rgba(0, 0, 0, 0.5) !important;
z-index: 3; z-index: 3;
body:not(.animation-level-0) & { body:not(.no-page-transitions) & {
transition: opacity 0.3s ease !important; transition: opacity 0.3s ease !important;
} }

View File

@ -2,7 +2,7 @@ import type { FC } from '../../lib/teact/teact';
import React, { import React, {
memo, useEffect, useMemo, useRef, memo, useEffect, useMemo, useRef,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, getGlobal, withGlobal } from '../../global';
import type { import type {
ApiUser, ApiMessage, ApiChat, ApiSticker, ApiTopic, ApiUser, ApiMessage, ApiChat, ApiSticker, ApiTopic,
@ -18,6 +18,7 @@ import {
selectChat, selectChat,
selectTopicFromMessage, selectTopicFromMessage,
selectTabState, selectTabState,
selectCanPlayAnimatedEmojis,
} from '../../global/selectors'; } from '../../global/selectors';
import { getMessageHtmlId, isChatChannel } from '../../global/helpers'; import { getMessageHtmlId, isChatChannel } from '../../global/helpers';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
@ -53,7 +54,6 @@ type OwnProps = {
}; };
type StateProps = { type StateProps = {
usersById: Record<string, ApiUser>;
senderUser?: ApiUser; senderUser?: ApiUser;
senderChat?: ApiChat; senderChat?: ApiChat;
targetUserIds?: string[]; targetUserIds?: string[];
@ -64,6 +64,7 @@ type StateProps = {
focusDirection?: FocusDirection; focusDirection?: FocusDirection;
noFocusHighlight?: boolean; noFocusHighlight?: boolean;
premiumGiftSticker?: ApiSticker; premiumGiftSticker?: ApiSticker;
canPlayAnimatedEmojis?: boolean;
}; };
const APPEARANCE_DELAY = 10; const APPEARANCE_DELAY = 10;
@ -74,7 +75,6 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
appearanceOrder = 0, appearanceOrder = 0,
isJustAdded, isJustAdded,
isLastInList, isLastInList,
usersById,
senderUser, senderUser,
senderChat, senderChat,
targetUserIds, targetUserIds,
@ -87,6 +87,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
isInsideTopic, isInsideTopic,
topic, topic,
memoFirstUnreadIdRef, memoFirstUnreadIdRef,
canPlayAnimatedEmojis,
observeIntersectionForReading, observeIntersectionForReading,
observeIntersectionForLoading, observeIntersectionForLoading,
observeIntersectionForPlaying, observeIntersectionForPlaying,
@ -140,6 +141,8 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
const { transitionClassNames } = useShowTransition(isShown, undefined, noAppearanceAnimation, false); const { transitionClassNames } = useShowTransition(isShown, undefined, noAppearanceAnimation, false);
// No need for expensive global updates on users and chats, so we avoid them
const usersById = getGlobal().users.byId;
const targetUsers = useMemo(() => { const targetUsers = useMemo(() => {
return targetUserIds return targetUserIds
? targetUserIds.map((userId) => usersById?.[userId]).filter(Boolean) ? targetUserIds.map((userId) => usersById?.[userId]).filter(Boolean)
@ -196,7 +199,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
<AnimatedIconFromSticker <AnimatedIconFromSticker
key={message.id} key={message.id}
sticker={premiumGiftSticker} sticker={premiumGiftSticker}
play play={canPlayAnimatedEmojis}
noLoop noLoop
nonInteractive nonInteractive
/> />
@ -256,7 +259,6 @@ export default memo(withGlobal<OwnProps>(
chatId, senderId, replyToMessageId, content, chatId, senderId, replyToMessageId, content,
} = message; } = message;
const { byId: usersById } = global.users;
const userId = senderId; const userId = senderId;
const { targetUserIds, targetChatId } = content.action || {}; const { targetUserIds, targetChatId } = content.action || {};
const targetMessageId = replyToMessageId; const targetMessageId = replyToMessageId;
@ -278,7 +280,6 @@ export default memo(withGlobal<OwnProps>(
const topic = selectTopicFromMessage(global, message); const topic = selectTopicFromMessage(global, message);
return { return {
usersById,
senderUser, senderUser,
senderChat, senderChat,
targetChatId, targetChatId,
@ -287,6 +288,7 @@ export default memo(withGlobal<OwnProps>(
isFocused, isFocused,
premiumGiftSticker, premiumGiftSticker,
topic, topic,
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
...(isFocused && { ...(isFocused && {
focusDirection, focusDirection,
noFocusHighlight, noFocusHighlight,

View File

@ -98,7 +98,6 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
<span className="action-message-suggested-avatar" tabIndex={0} role="button" onClick={handleViewSuggestedAvatar}> <span className="action-message-suggested-avatar" tabIndex={0} role="button" onClick={handleViewSuggestedAvatar}>
<Avatar <Avatar
photo={message.content.action!.photo} photo={message.content.action!.photo}
forceVideo
loopIndefinitely loopIndefinitely
withVideo={isVideo} withVideo={isVideo}
size="jumbo" size="jumbo"

View File

@ -4,7 +4,7 @@
margin-top: -0.25rem; margin-top: -0.25rem;
margin-bottom: -0.25rem; margin-bottom: -0.25rem;
body.animation-level-0 & { body.no-page-transitions & {
transition: none !important; transition: none !important;
} }

View File

@ -13,7 +13,7 @@
transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
transition: opacity 0.15s ease, transform var(--layer-transition); transition: opacity 0.15s ease, transform var(--layer-transition);
body.animation-level-1 & { body.no-page-transitions & {
.ripple-container { .ripple-container {
display: none; display: none;
} }

View File

@ -27,7 +27,7 @@
pointer-events: none; pointer-events: none;
} }
:global(body.animation-level-0) & { :global(body.no-page-transitions) & {
transform: none !important; transform: none !important;
transition: opacity 0.15s; transition: opacity 0.15s;

View File

@ -13,12 +13,12 @@ import { MAIN_THREAD_ID } from '../../api/types';
import type { IAnchorPosition } from '../../types'; import type { IAnchorPosition } from '../../types';
import { ManagementScreens } from '../../types'; import { ManagementScreens } from '../../types';
import { ANIMATION_LEVEL_MIN } from '../../config';
import { ARE_CALLS_SUPPORTED, IS_PWA } from '../../util/windowEnvironment'; import { ARE_CALLS_SUPPORTED, IS_PWA } from '../../util/windowEnvironment';
import { import {
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId, isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
} from '../../global/helpers'; } from '../../global/helpers';
import { import {
selectCanAnimateInterface,
selectChat, selectChat,
selectChatBot, selectChatBot,
selectChatFullInfo, selectChatFullInfo,
@ -351,7 +351,7 @@ export default memo(withGlobal<OwnProps>(
const pendingJoinRequests = isMainThread ? chatFullInfo?.requestsPending : undefined; const pendingJoinRequests = isMainThread ? chatFullInfo?.requestsPending : undefined;
const shouldJoinToSend = Boolean(chat?.isNotJoined && chat.isJoinToSend); const shouldJoinToSend = Boolean(chat?.isNotJoined && chat.isJoinToSend);
const shouldSendJoinRequest = Boolean(chat?.isNotJoined && chat.isJoinRequest); const shouldSendJoinRequest = Boolean(chat?.isNotJoined && chat.isJoinRequest);
const noAnimation = global.settings.byKey.animationLevel === ANIMATION_LEVEL_MIN; const noAnimation = !selectCanAnimateInterface(global);
return { return {
noMenu: false, noMenu: false,

View File

@ -18,13 +18,11 @@
} }
} }
:global(body.animation-level-1) & { :global(body.no-page-transitions) & {
:global(.ripple-container) { :global(.ripple-container) {
display: none; display: none;
} }
}
:global(body.animation-level-0) & {
transition: none !important; transition: none !important;
} }
@ -32,6 +30,10 @@
transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
transition: opacity 0.15s ease, transform var(--layer-transition); transition: opacity 0.15s ease, transform var(--layer-transition);
:global(body.no-right-column-animations) & {
transition: opacity 0.15s ease;
}
:global(#Main.right-column-open) & { :global(#Main.right-column-open) & {
transform: translate3d(calc(var(--right-column-width) * -1), 0, 0); transform: translate3d(calc(var(--right-column-width) * -1), 0, 0);
} }
@ -264,7 +266,7 @@
:global(.tools-stacked.animated) .root { :global(.tools-stacked.animated) .root {
animation: fade-in var(--layer-transition) forwards; animation: fade-in var(--layer-transition) forwards;
:global(body.animation-level-0) & { :global(body.no-page-transitions) & {
animation: none; animation: none;
} }
} }

View File

@ -12,7 +12,7 @@
transition: transform var(--layer-transition); transition: transform var(--layer-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none !important; transition: none !important;
} }
@ -42,7 +42,7 @@
bottom: 0; bottom: 0;
} }
body.keyboard-visible.animation-level-0 & { body.keyboard-visible.no-page-transitions & {
transition: none !important; transition: none !important;
} }
} }
@ -125,10 +125,9 @@
opacity: 0; opacity: 0;
} }
body.animation-level-0 & { body.no-message-sending-animations & {
opacity: 1; opacity: 1;
transform: none; transform: none;
display: flex !important;
transition: none !important; transition: none !important;
} }
@ -155,7 +154,7 @@
opacity: 0; opacity: 0;
transition: opacity var(--select-transition); transition: opacity var(--select-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none !important; transition: none !important;
} }
} }
@ -362,7 +361,7 @@
} }
} }
body.animation-level-0 & { body.no-page-transitions & {
transition: none; transition: none;
} }
@ -434,7 +433,7 @@
width: calc(100% - var(--right-column-width)); width: calc(100% - var(--right-column-width));
} }
body.animation-level-0 & { body.no-right-column-animations & {
transition: none; transition: none;
} }

View File

@ -16,7 +16,6 @@ import type {
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 type { Signal } from '../../util/signals'; import type { Signal } from '../../util/signals';
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage'; import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
import { LoadMoreDirection } from '../../types'; import { LoadMoreDirection } from '../../types';
@ -41,7 +40,9 @@ import {
selectLastScrollOffset, selectLastScrollOffset,
selectThreadInfo, selectThreadInfo,
selectTabState, selectTabState,
selectUserFullInfo, selectChatFullInfo, selectUserFullInfo,
selectChatFullInfo,
selectPerformanceSettingsValue,
} from '../../global/selectors'; } from '../../global/selectors';
import { import {
isChatChannel, isChatChannel,
@ -117,7 +118,6 @@ type StateProps = {
restrictionReason?: ApiRestrictionReason; restrictionReason?: ApiRestrictionReason;
focusingId?: number; focusingId?: number;
isSelectModeActive?: boolean; isSelectModeActive?: boolean;
animationLevel?: AnimationLevel;
lastMessage?: ApiMessage; lastMessage?: ApiMessage;
isLoadingBotInfo?: boolean; isLoadingBotInfo?: boolean;
botInfo?: ApiBotInfo; botInfo?: ApiBotInfo;
@ -126,6 +126,7 @@ type StateProps = {
hasLinkedChat?: boolean; hasLinkedChat?: boolean;
lastSyncTime?: number; lastSyncTime?: number;
topic?: ApiTopic; topic?: ApiTopic;
noMessageSendingAnimation?: boolean;
}; };
const MESSAGE_REACTIONS_POLLING_INTERVAL = 15 * 1000; const MESSAGE_REACTIONS_POLLING_INTERVAL = 15 * 1000;
@ -177,6 +178,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
withBottomShift, withBottomShift,
withDefaultBg, withDefaultBg,
topic, topic,
noMessageSendingAnimation,
onPinnedIntersectionChange, onPinnedIntersectionChange,
getForceNextPinnedInHeader, getForceNextPinnedInHeader,
}) => { }) => {
@ -464,6 +466,9 @@ const MessageList: FC<OwnProps & StateProps> = ({
lastItemElement!, lastItemElement!,
'end', 'end',
BOTTOM_FOCUS_MARGIN, BOTTOM_FOCUS_MARGIN,
undefined,
undefined,
noMessageSendingAnimation ? 0 : undefined,
); );
}); });
} }
@ -516,7 +521,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
}; };
}); });
// This should match deps for `useSyncEffect` above // This should match deps for `useSyncEffect` above
}, [messageIds, isViewportNewest, hasTools, getContainerHeight, prevContainerHeightRef]); }, [messageIds, isViewportNewest, hasTools, getContainerHeight, prevContainerHeightRef, noMessageSendingAnimation]);
useEffectWithPrevDeps(([prevIsSelectModeActive]) => { useEffectWithPrevDeps(([prevIsSelectModeActive]) => {
if (prevIsSelectModeActive !== undefined) { if (prevIsSelectModeActive !== undefined) {
@ -730,6 +735,7 @@ export default memo(withGlobal<OwnProps>(
hasLinkedChat: Boolean(chatFullInfo?.linkedChatId), hasLinkedChat: Boolean(chatFullInfo?.linkedChatId),
lastSyncTime: global.lastSyncTime, lastSyncTime: global.lastSyncTime,
topic, topic,
noMessageSendingAnimation: !selectPerformanceSettingsValue(global, 'messageSendingAnimations'),
...(withLastMessageWhenPreloading && { lastMessage }), ...(withLastMessageWhenPreloading && { lastMessage }),
}; };
}, },

View File

@ -46,7 +46,7 @@
top: auto; top: auto;
} }
body.animation-level-0 & { body.no-page-transitions & {
transition: none !important; transition: none !important;
} }

View File

@ -33,7 +33,7 @@
transform: scale(1.1); transform: scale(1.1);
} }
:global(body:not(.animation-level-0)) &.withTransition { :global(body:not(.no-page-transitions)) &.withTransition {
transition: background-color 0.2s; transition: background-color 0.2s;
&.customBgImage::before { &.customBgImage::before {
@ -46,14 +46,14 @@
} }
@media screen and (min-width: 1276px) { @media screen and (min-width: 1276px) {
:global(body.animation-level-2) &:not(.customBgImage)::before { :global(body:not(.no-page-transitions)) &:not(.customBgImage)::before {
overflow: hidden; overflow: hidden;
transform: scale(1); transform: scale(1);
transform-origin: left center; transform-origin: left center;
} }
} }
:global(html.theme-light body.animation-level-2) &:not(.customBgImage).withRightColumn::before { :global(html.theme-light body:not(.no-page-transitions)) &:not(.customBgImage).withRightColumn::before {
@media screen and (min-width: 1276px) { @media screen and (min-width: 1276px) {
transform: scaleX(0.73) !important; transform: scaleX(0.73) !important;
} }
@ -65,7 +65,7 @@
} }
} }
:global(html.theme-light body.animation-level-2) &:not(.customBgImage).withRightColumn.withTransition::before { :global(html.theme-light body:not(.no-page-transitions)) &:not(.customBgImage).withRightColumn.withTransition::before {
transition: transform var(--layer-transition); transition: transform var(--layer-transition);
} }

View File

@ -44,7 +44,7 @@
transition: transform var(--select-transition); transition: transform var(--select-transition);
} }
body.animation-level-0 & { body.no-message-composer-animations & {
&, &,
&::before { &::before {
transition: none !important; transition: none !important;
@ -57,7 +57,7 @@
opacity: 1; opacity: 1;
transition: opacity var(--select-transition); transition: opacity var(--select-transition);
body.animation-level-0 & { body.no-message-composer-animations & {
transition: none !important; transition: none !important;
} }
} }
@ -68,7 +68,7 @@
transition: opacity var(--select-transition), transform var(--select-transition), background-color 0.15s, transition: opacity var(--select-transition), transform var(--select-transition), background-color 0.15s,
color 0.15s; color 0.15s;
body.animation-level-0 & { body.no-message-composer-animations & {
transition: none !important; transition: none !important;
} }
} }
@ -120,12 +120,12 @@
opacity: 1; opacity: 1;
transition: opacity var(--select-transition); transition: opacity var(--select-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none !important; transition: none !important;
} }
} }
body.animation-level-0 & { body.no-page-transitions & {
transition: none !important; transition: none !important;
} }
@ -162,10 +162,15 @@
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */ /* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: top 200ms, transform var(--layer-transition); transition: top 200ms, transform var(--layer-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none !important; transition: none !important;
} }
body.no-right-column-animations & {
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: top 200ms !important;
}
@media (min-width: 1276px) { @media (min-width: 1276px) {
width: calc(100% - var(--right-column-width)); width: calc(100% - var(--right-column-width));

View File

@ -11,7 +11,7 @@ import type {
MessageListType, MessageListType,
ActiveEmojiInteraction, ActiveEmojiInteraction,
} from '../../global/types'; } from '../../global/types';
import type { AnimationLevel, ThemeKey } from '../../types'; import type { ThemeKey } from '../../types';
import { import {
MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN, MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN,
@ -19,11 +19,9 @@ import {
MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN,
SAFE_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN, SAFE_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN,
SAFE_SCREEN_WIDTH_FOR_CHAT_INFO, SAFE_SCREEN_WIDTH_FOR_CHAT_INFO,
ANIMATION_LEVEL_MAX,
ANIMATION_END_DELAY, ANIMATION_END_DELAY,
DARK_THEME_BG_COLOR, DARK_THEME_BG_COLOR,
LIGHT_THEME_BG_COLOR, LIGHT_THEME_BG_COLOR,
ANIMATION_LEVEL_MIN,
SUPPORTED_IMAGE_CONTENT_TYPES, SUPPORTED_IMAGE_CONTENT_TYPES,
GENERAL_TOPIC_ID, GENERAL_TOPIC_ID,
TMP_CHAT_ID, TMP_CHAT_ID,
@ -31,6 +29,7 @@ import {
import { IS_ANDROID, IS_IOS, MASK_IMAGE_DISABLED } from '../../util/windowEnvironment'; import { IS_ANDROID, IS_IOS, MASK_IMAGE_DISABLED } from '../../util/windowEnvironment';
import { DropAreaState } from './composer/DropArea'; import { DropAreaState } from './composer/DropArea';
import { import {
selectCanAnimateInterface,
selectChat, selectChat,
selectChatBot, selectChatBot,
selectChatFullInfo, selectChatFullInfo,
@ -121,7 +120,7 @@ type StateProps = {
isReactorListModalOpen: boolean; isReactorListModalOpen: boolean;
isGiftPremiumModalOpen?: boolean; isGiftPremiumModalOpen?: boolean;
isMessageLanguageModalOpen?: boolean; isMessageLanguageModalOpen?: boolean;
animationLevel: AnimationLevel; withInterfaceAnimations?: boolean;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
currentTransitionKey: number; currentTransitionKey: number;
isChannel?: boolean; isChannel?: boolean;
@ -171,7 +170,7 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
isReactorListModalOpen, isReactorListModalOpen,
isGiftPremiumModalOpen, isGiftPremiumModalOpen,
isMessageLanguageModalOpen, isMessageLanguageModalOpen,
animationLevel, withInterfaceAnimations,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
currentTransitionKey, currentTransitionKey,
isChannel, isChannel,
@ -257,7 +256,7 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
); );
const { isReady, handleCssTransitionEnd, handleSlideTransitionStop } = useIsReady( const { isReady, handleCssTransitionEnd, handleSlideTransitionStop } = useIsReady(
!shouldSkipHistoryAnimations && animationLevel !== ANIMATION_LEVEL_MIN, !shouldSkipHistoryAnimations && withInterfaceAnimations,
currentTransitionKey, currentTransitionKey,
prevTransitionKey, prevTransitionKey,
chatId, chatId,
@ -473,7 +472,7 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
onFocusPinnedMessage={onFocusPinnedMessage} onFocusPinnedMessage={onFocusPinnedMessage}
/> />
<Transition <Transition
name={shouldSkipHistoryAnimations ? 'none' : animationLevel === ANIMATION_LEVEL_MAX ? 'slide' : 'fade'} name={shouldSkipHistoryAnimations ? 'none' : withInterfaceAnimations ? 'slide' : 'fade'}
activeKey={currentTransitionKey} activeKey={currentTransitionKey}
shouldCleanup shouldCleanup
cleanupExceptionKey={cleanupExceptionKey} cleanupExceptionKey={cleanupExceptionKey}
@ -655,7 +654,7 @@ export default memo(withGlobal<OwnProps>(
isReactorListModalOpen: Boolean(reactorModal), isReactorListModalOpen: Boolean(reactorModal),
isGiftPremiumModalOpen: giftPremiumModal?.isOpen, isGiftPremiumModalOpen: giftPremiumModal?.isOpen,
isMessageLanguageModalOpen: Boolean(messageLanguageModal), isMessageLanguageModalOpen: Boolean(messageLanguageModal),
animationLevel: global.settings.byKey.animationLevel, withInterfaceAnimations: selectCanAnimateInterface(global),
currentTransitionKey: Math.max(0, messageLists.length - 1), currentTransitionKey: Math.max(0, messageLists.length - 1),
activeEmojiInteractions, activeEmojiInteractions,
lastSyncTime, lastSyncTime,

View File

@ -112,7 +112,7 @@
margin-left: auto; margin-left: auto;
flex-shrink: 0; flex-shrink: 0;
body.animation-level-0 & { body.no-page-transitions & {
&, &,
.AudioPlayer, .AudioPlayer,
.HeaderActions { .HeaderActions {
@ -120,6 +120,13 @@
} }
} }
body.no-right-column-animations & {
&,
.HeaderActions {
transition: none !important;
}
}
@media (min-width: 1276px) and (max-width: 1439px) { @media (min-width: 1276px) and (max-width: 1439px) {
.HeaderActions { .HeaderActions {
transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
@ -169,7 +176,7 @@
&.tools-stacked.animated .AudioPlayer { &.tools-stacked.animated .AudioPlayer {
animation: fade-in var(--layer-transition) forwards; animation: fade-in var(--layer-transition) forwards;
body.animation-level-0 & { body.no-page-transitions & {
animation: none; animation: none;
} }
} }

View File

@ -361,7 +361,6 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
withFullInfo withFullInfo
withMediaViewer withMediaViewer
withUpdatingStatus withUpdatingStatus
withVideoAvatar={isReady}
emojiStatusSize={EMOJI_STATUS_SIZE} emojiStatusSize={EMOJI_STATUS_SIZE}
noRtl noRtl
/> />
@ -376,7 +375,6 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
withMediaViewer={threadId === MAIN_THREAD_ID} withMediaViewer={threadId === MAIN_THREAD_ID}
withFullInfo={threadId === MAIN_THREAD_ID} withFullInfo={threadId === MAIN_THREAD_ID}
withUpdatingStatus withUpdatingStatus
withVideoAvatar={isReady}
noRtl noRtl
/> />
)} )}

View File

@ -56,7 +56,11 @@ function renderTopic(lang: LangFn, topic: ApiTopic) {
return ( return (
<div className="NoMessages"> <div className="NoMessages">
<div className="wrapper"> <div className="wrapper">
<TopicIcon topic={topic} size={ICON_SIZE} className="no-messages-icon topic-icon" /> <TopicIcon
topic={topic}
size={ICON_SIZE}
className="no-messages-icon topic-icon"
/>
<h3 className="title">{lang('Chat.EmptyTopicPlaceholder.Title')}</h3> <h3 className="title">{lang('Chat.EmptyTopicPlaceholder.Title')}</h3>
<p className="description topic-description">{renderText(lang('Chat.EmptyTopicPlaceholder.Text'), ['br'])}</p> <p className="description topic-description">{renderText(lang('Chat.EmptyTopicPlaceholder.Text'), ['br'])}</p>
</div> </div>

View File

@ -5,10 +5,12 @@ import React, {
import { getActions, getGlobal, withGlobal } from '../../global'; import { getActions, getGlobal, withGlobal } from '../../global';
import type { ApiAvailableReaction, ApiMessage, ApiReaction } from '../../api/types'; import type { ApiAvailableReaction, ApiMessage, ApiReaction } from '../../api/types';
import type { AnimationLevel } from '../../types';
import { LoadMoreDirection } from '../../types'; import { LoadMoreDirection } from '../../types';
import { selectChatMessage, selectTabState } from '../../global/selectors'; import {
selectChatMessage,
selectTabState,
} from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { formatIntegerCompact } from '../../util/textFormat'; import { formatIntegerCompact } from '../../util/textFormat';
import { unique } from '../../util/iteratees'; import { unique } from '../../util/iteratees';
@ -39,7 +41,6 @@ export type StateProps = Pick<ApiMessage, 'reactors' | 'reactions' | 'seenByUser
chatId?: string; chatId?: string;
messageId?: number; messageId?: number;
availableReactions?: ApiAvailableReaction[]; availableReactions?: ApiAvailableReaction[];
animationLevel?: AnimationLevel;
}; };
const ReactorListModal: FC<OwnProps & StateProps> = ({ const ReactorListModal: FC<OwnProps & StateProps> = ({
@ -50,7 +51,6 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
messageId, messageId,
seenByUserIds, seenByUserIds,
availableReactions, availableReactions,
animationLevel,
}) => { }) => {
const { const {
loadReactors, loadReactors,
@ -194,7 +194,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" withVideo animationLevel={animationLevel} /> <Avatar user={user} size="small" />
<FullNameTitle peer={user} withEmojiStatus /> <FullNameTitle peer={user} withEmojiStatus />
{r.reaction && ( {r.reaction && (
<ReactionStaticEmoji <ReactionStaticEmoji
@ -235,7 +235,6 @@ export default memo(withGlobal<OwnProps>(
reactors: message?.reactors, reactors: message?.reactors,
seenByUserIds: message?.seenByUserIds, seenByUserIds: message?.seenByUserIds,
availableReactions: global.availableReactions, availableReactions: global.availableReactions,
animationLevel: global.settings.byKey.animationLevel,
}; };
}, },
)(ReactorListModal)); )(ReactorListModal));

View File

@ -21,10 +21,15 @@
transition: transform var(--layer-transition), opacity 0.2s ease; transition: transform var(--layer-transition), opacity 0.2s ease;
:global(body.animation-level-0) & { :global(body.no-page-transitions) &,
:global(body.no-right-column-animations) & {
transition: none !important; transition: none !important;
} }
:global(body:not(.no-right-column-animations) #Main.right-column-open) & {
transition: transform var(--layer-transition), opacity 0.2s ease;
}
:global(#Main.right-column-open) & { :global(#Main.right-column-open) & {
transform: translateX(calc(-1 * var(--right-column-width))); transform: translateX(calc(-1 * var(--right-column-width)));
} }

View File

@ -58,6 +58,11 @@
overflow: hidden; overflow: hidden;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
:global(body.no-menu-blur) & {
background-color: #707579;
backdrop-filter: none;
}
} }
.action-item { .action-item {

View File

@ -156,8 +156,7 @@
animation-duration: 0ms !important; animation-duration: 0ms !important;
} }
body.animation-level-0 &, body.no-message-composer-animations & {
body.animation-level-1 & {
.icon-send, .icon-send,
.icon-microphone-alt, .icon-microphone-alt,
.icon-check, .icon-check,
@ -171,7 +170,7 @@
z-index: 1; z-index: 1;
} }
body:not(.animation-level-0) & .send-as-button.appear-animation { body:not(.no-message-composer-animations) & .send-as-button.appear-animation {
animation: 0.25s ease-in-out forwards show-send-as-button; animation: 0.25s ease-in-out forwards show-send-as-button;
transform-origin: right; transform-origin: right;
} }
@ -447,7 +446,7 @@
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */ /* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: height 100ms ease; transition: height 100ms ease;
body.animation-level-0 & { body.no-message-composer-animations & {
transition: none !important; transition: none !important;
} }

View File

@ -11,7 +11,7 @@
height: 0 !important; height: 0 !important;
} }
body.animation-level-0 & { body.no-message-composer-animations & {
transition: none !important; transition: none !important;
} }

View File

@ -20,6 +20,7 @@ import {
selectIsChatWithSelf, selectIsChatWithSelf,
selectIsCurrentUserPremium, selectIsCurrentUserPremium,
selectTabState, selectTabState,
selectCanAnimateInterface,
} from '../../../global/selectors'; } from '../../../global/selectors';
import captureEscKeyListener from '../../../util/captureEscKeyListener'; import captureEscKeyListener from '../../../util/captureEscKeyListener';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -295,7 +296,7 @@ export default memo(withGlobal<OwnProps>(
const editingId = messageListType === 'scheduled' const editingId = messageListType === 'scheduled'
? selectEditingScheduledId(global, chatId) ? selectEditingScheduledId(global, chatId)
: selectEditingId(global, chatId, threadId); : selectEditingId(global, chatId, threadId);
const shouldAnimate = global.settings.byKey.animationLevel >= 1; const shouldAnimate = selectCanAnimateInterface(global);
const isForwarding = toChatId === chatId; const isForwarding = toChatId === chatId;
const forwardedMessages = forwardMessageIds?.map((id) => selectChatMessage(global, fromChatId!, id)!); const forwardedMessages = forwardMessageIds?.map((id) => selectChatMessage(global, fromChatId!, id)!);

View File

@ -28,6 +28,7 @@ export type OwnProps = {
addRecentCustomEmoji: GlobalActions['addRecentCustomEmoji']; addRecentCustomEmoji: GlobalActions['addRecentCustomEmoji'];
onCustomEmojiSelect: (customEmoji: ApiSticker) => void; onCustomEmojiSelect: (customEmoji: ApiSticker) => void;
onClose: NoneToVoidFunction; onClose: NoneToVoidFunction;
noPlay?: boolean;
}; };
type StateProps = { type StateProps = {
@ -46,6 +47,7 @@ const CustomEmojiTooltip: FC<OwnProps & StateProps> = ({
customEmoji, customEmoji,
isSavedMessages, isSavedMessages,
isCurrentUserPremium, isCurrentUserPremium,
noPlay,
}) => { }) => {
const { clearCustomEmojiForEmoji } = getActions(); const { clearCustomEmojiForEmoji } = getActions();
@ -97,6 +99,7 @@ const CustomEmojiTooltip: FC<OwnProps & StateProps> = ({
isSavedMessages={isSavedMessages} isSavedMessages={isSavedMessages}
canViewSet canViewSet
isCurrentUserPremium={isCurrentUserPremium} isCurrentUserPremium={isCurrentUserPremium}
noPlay={noPlay}
/> />
)) ))
) : shouldRender ? ( ) : shouldRender ? (

View File

@ -17,8 +17,11 @@
@include overflow-y-overlay(); @include overflow-y-overlay();
.Loading, .picker-disabled { .Loading, .picker-disabled {
grid-column: 1 / -1; position: absolute;
height: var(--menu-height); top: 0;
bottom: 0;
left: 0;
right: 0;
} }
.SymbolMenu.mobile-menu & { .SymbolMenu.mobile-menu & {

View File

@ -13,7 +13,7 @@ import { EDITABLE_INPUT_ID } from '../../../config';
import { import {
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_TOUCH_ENV, IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_TOUCH_ENV,
} from '../../../util/windowEnvironment'; } from '../../../util/windowEnvironment';
import { selectIsInSelectMode, selectReplyingToId } from '../../../global/selectors'; import { selectCanPlayAnimatedEmojis, selectIsInSelectMode, selectReplyingToId } from '../../../global/selectors';
import { debounce } from '../../../util/schedulers'; import { debounce } from '../../../util/schedulers';
import focusEditableElement from '../../../util/focusEditableElement'; import focusEditableElement from '../../../util/focusEditableElement';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -67,6 +67,7 @@ type StateProps = {
replyingToId?: number; replyingToId?: number;
isSelectModeActive?: boolean; isSelectModeActive?: boolean;
messageSendKeyCombo?: ISettings['messageSendKeyCombo']; messageSendKeyCombo?: ISettings['messageSendKeyCombo'];
canPlayAnimatedEmojis: boolean;
}; };
const MAX_ATTACHMENT_MODAL_INPUT_HEIGHT = 160; const MAX_ATTACHMENT_MODAL_INPUT_HEIGHT = 160;
@ -111,6 +112,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
shouldSuppressTextFormatter, shouldSuppressTextFormatter,
replyingToId, replyingToId,
isSelectModeActive, isSelectModeActive,
canPlayAnimatedEmojis,
messageSendKeyCombo, messageSendKeyCombo,
onUpdate, onUpdate,
onSuppressedFocus, onSuppressedFocus,
@ -157,6 +159,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
sharedCanvasHqRef, sharedCanvasHqRef,
absoluteContainerRef, absoluteContainerRef,
isAttachmentModalInput ? 'attachment' : 'composer', isAttachmentModalInput ? 'attachment' : 'composer',
canPlayAnimatedEmojis,
isActive, isActive,
); );
@ -590,6 +593,7 @@ export default memo(withGlobal<OwnProps>(
messageSendKeyCombo, messageSendKeyCombo,
replyingToId: chatId && threadId ? selectReplyingToId(global, chatId, threadId) : undefined, replyingToId: chatId && threadId ? selectReplyingToId(global, chatId, threadId) : undefined,
isSelectModeActive: selectIsInSelectMode(global), isSelectModeActive: selectIsInSelectMode(global),
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
}; };
}, },
)(MessageInput)); )(MessageInput));

View File

@ -24,7 +24,7 @@ import buildClassName from '../../../util/buildClassName';
import animateHorizontalScroll from '../../../util/animateHorizontalScroll'; import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
import { pickTruthy, uniqueByField } from '../../../util/iteratees'; import { pickTruthy, uniqueByField } from '../../../util/iteratees';
import { import {
selectChat, selectChatFullInfo, selectIsChatWithSelf, selectIsCurrentUserPremium, selectChat, selectChatFullInfo, selectIsChatWithSelf, selectIsCurrentUserPremium, selectShouldLoopStickers,
} from '../../../global/selectors'; } from '../../../global/selectors';
import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useAsyncRendering from '../../right/hooks/useAsyncRendering';
@ -285,7 +285,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
) : ( ) : (
<StickerSetCover <StickerSetCover
stickerSet={stickerSet as ApiStickerSet} stickerSet={stickerSet as ApiStickerSet}
noAnimate={!canAnimate || !loadAndPlay} noPlay={!canAnimate || !loadAndPlay}
observeIntersection={observeIntersectionForCovers} observeIntersection={observeIntersectionForCovers}
sharedCanvasRef={withSharedCanvas ? sharedCanvasRef : undefined} sharedCanvasRef={withSharedCanvas ? sharedCanvasRef : undefined}
/> />
@ -300,7 +300,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
size={STICKER_SIZE_PICKER_HEADER} size={STICKER_SIZE_PICKER_HEADER}
title={stickerSet.title} title={stickerSet.title}
className={buttonClassName} className={buttonClassName}
noAnimate={!canAnimate || !loadAndPlay} noPlay={!canAnimate || !loadAndPlay}
observeIntersection={observeIntersectionForCovers} observeIntersection={observeIntersectionForCovers}
noContextMenu noContextMenu
isCurrentUserPremium isCurrentUserPremium
@ -395,7 +395,7 @@ export default memo(withGlobal<OwnProps>(
premiumStickers: premiumSet.stickers, premiumStickers: premiumSet.stickers,
stickerSetsById: setsById, stickerSetsById: setsById,
addedSetIds: added.setIds, addedSetIds: added.setIds,
canAnimate: global.settings.byKey.shouldLoopStickers, canAnimate: selectShouldLoopStickers(global),
isSavedMessages, isSavedMessages,
isCurrentUserPremium: selectIsCurrentUserPremium(global), isCurrentUserPremium: selectIsCurrentUserPremium(global),
chatStickerSetId, chatStickerSetId,

View File

@ -25,7 +25,7 @@ import styles from './StickerSetCover.module.scss';
type OwnProps = { type OwnProps = {
stickerSet: ApiStickerSet; stickerSet: ApiStickerSet;
size?: number; size?: number;
noAnimate?: boolean; noPlay?: boolean;
observeIntersection: ObserveFn; observeIntersection: ObserveFn;
sharedCanvasRef?: React.RefObject<HTMLCanvasElement>; sharedCanvasRef?: React.RefObject<HTMLCanvasElement>;
}; };
@ -33,7 +33,7 @@ type OwnProps = {
const StickerSetCover: FC<OwnProps> = ({ const StickerSetCover: FC<OwnProps> = ({
stickerSet, stickerSet,
size = STICKER_SIZE_PICKER_HEADER, size = STICKER_SIZE_PICKER_HEADER,
noAnimate, noPlay,
observeIntersection, observeIntersection,
sharedCanvasRef, sharedCanvasRef,
}) => { }) => {
@ -44,6 +44,7 @@ const StickerSetCover: FC<OwnProps> = ({
const { hasThumbnail, isLottie, isVideos: isVideo } = stickerSet; const { hasThumbnail, isLottie, isVideos: isVideo } = stickerSet;
const isIntersecting = useIsIntersecting(containerRef, observeIntersection); const isIntersecting = useIsIntersecting(containerRef, observeIntersection);
const shouldPlay = isIntersecting && !noPlay;
const shouldFallbackToStatic = stickerSet.stickers && isVideo && !IS_WEBM_SUPPORTED; const shouldFallbackToStatic = stickerSet.stickers && isVideo && !IS_WEBM_SUPPORTED;
const staticHash = shouldFallbackToStatic && getStickerPreviewHash(stickerSet.stickers![0].id); const staticHash = shouldFallbackToStatic && getStickerPreviewHash(stickerSet.stickers![0].id);
@ -75,7 +76,7 @@ const StickerSetCover: FC<OwnProps> = ({
className={transitionClassNames} className={transitionClassNames}
tgsUrl={mediaData} tgsUrl={mediaData}
size={size} size={size}
play={isIntersecting && !noAnimate} play={shouldPlay}
isLowPriority={!selectIsAlwaysHighPriorityEmoji(getGlobal(), stickerSet)} isLowPriority={!selectIsAlwaysHighPriorityEmoji(getGlobal(), stickerSet)}
sharedCanvas={sharedCanvasRef?.current || undefined} sharedCanvas={sharedCanvasRef?.current || undefined}
sharedCanvasCoords={coords} sharedCanvasCoords={coords}
@ -84,7 +85,7 @@ const StickerSetCover: FC<OwnProps> = ({
<OptimizedVideo <OptimizedVideo
className={buildClassName(styles.video, transitionClassNames)} className={buildClassName(styles.video, transitionClassNames)}
src={mediaData} src={mediaData}
canPlay={isIntersecting && !noAnimate} canPlay={shouldPlay}
loop loop
disablePictureInPicture disablePictureInPicture
/> />

View File

@ -41,7 +41,7 @@
transform: translate3d(0, calc(var(--symbol-menu-height)), 0); transform: translate3d(0, calc(var(--symbol-menu-height)), 0);
} }
body.animation-level-0 & { body.no-page-transitions & {
transition: none; transition: none;
} }
@ -140,13 +140,16 @@
.bubble { .bubble {
--offset-y: 4rem; --offset-y: 4rem;
background: var(--color-background-compact-menu);
backdrop-filter: blur(10px);
border-radius: 1.25rem; border-radius: 1.25rem;
width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
body:not(.no-menu-blur) & {
background: var(--color-background-compact-menu);
backdrop-filter: blur(10px);
}
&:not(.open) { &:not(.open) {
transform: scale(0.85) !important; transform: scale(0.85) !important;
} }

View File

@ -10,7 +10,7 @@ import type { GlobalActions } from '../../../global';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { selectTabState, selectIsCurrentUserPremium } from '../../../global/selectors'; import { selectTabState, selectIsCurrentUserPremium, selectIsContextMenuTranslucent } from '../../../global/selectors';
import useShowTransition from '../../../hooks/useShowTransition'; import useShowTransition from '../../../hooks/useShowTransition';
import useMouseInside from '../../../hooks/useMouseInside'; import useMouseInside from '../../../hooks/useMouseInside';
@ -68,6 +68,7 @@ type StateProps = {
isLeftColumnShown: boolean; isLeftColumnShown: boolean;
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
lastSyncTime?: number; lastSyncTime?: number;
isBackgroundTranslucent?: boolean;
}; };
let isActivated = false; let isActivated = false;
@ -99,6 +100,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
transformOriginX, transformOriginX,
transformOriginY, transformOriginY,
style, style,
isBackgroundTranslucent,
}) => { }) => {
const { loadPremiumSetStickers } = getActions(); const { loadPremiumSetStickers } = getActions();
const [activeTab, setActiveTab] = useState<number>(0); const [activeTab, setActiveTab] = useState<number>(0);
@ -219,7 +221,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
isHidden={!isOpen || !isActive} isHidden={!isOpen || !isActive}
loadAndPlay={isOpen && (isActive || isFrom)} loadAndPlay={isOpen && (isActive || isFrom)}
chatId={chatId} chatId={chatId}
isTranslucent={!isMobile} isTranslucent={!isMobile && isBackgroundTranslucent}
onCustomEmojiSelect={handleCustomEmojiSelect} onCustomEmojiSelect={handleCustomEmojiSelect}
/> />
); );
@ -232,7 +234,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
canSendStickers={canSendStickers} canSendStickers={canSendStickers}
chatId={chatId} chatId={chatId}
threadId={threadId} threadId={threadId}
isTranslucent={!isMobile} isTranslucent={!isMobile && isBackgroundTranslucent}
onStickerSelect={handleStickerSelect} onStickerSelect={handleStickerSelect}
/> />
); );
@ -348,6 +350,7 @@ export default memo(withGlobal<OwnProps>(
isLeftColumnShown: selectTabState(global).isLeftColumnShown, isLeftColumnShown: selectTabState(global).isLeftColumnShown,
isCurrentUserPremium: selectIsCurrentUserPremium(global), isCurrentUserPremium: selectIsCurrentUserPremium(global),
lastSyncTime: global.lastSyncTime, lastSyncTime: global.lastSyncTime,
isBackgroundTranslucent: selectIsContextMenuTranslucent(global),
}; };
}, },
)(SymbolMenu)); )(SymbolMenu));

View File

@ -3,8 +3,8 @@
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */ /* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: height 150ms ease-out, opacity 150ms ease-out; transition: height 150ms ease-out, opacity 150ms ease-out;
body.animation-level-0 & { body.no-page-transitions & {
transition: none !important; transition: opacity 150ms ease-out;
} }
.select-mode-active + .middle-column-footer & { .select-mode-active + .middle-column-footer & {
@ -27,6 +27,10 @@
.ComposerEmbeddedMessage + & { .ComposerEmbeddedMessage + & {
margin-top: 0.75rem; margin-top: 0.75rem;
body.no-message-composer-animations & {
transition: opacity 150ms ease-out;
}
} }
& &-left-icon { & &-left-icon {

View File

@ -41,6 +41,7 @@ export default function useInputCustomEmojis(
sharedCanvasHqRef: React.RefObject<HTMLCanvasElement>, sharedCanvasHqRef: React.RefObject<HTMLCanvasElement>,
absoluteContainerRef: React.RefObject<HTMLElement>, absoluteContainerRef: React.RefObject<HTMLElement>,
prefixId: string, prefixId: string,
canPlayAnimatedEmojis: boolean,
isActive?: boolean, isActive?: boolean,
) { ) {
const { rgbColor: textColor } = useDynamicColorListener(inputRef); const { rgbColor: textColor } = useDynamicColorListener(inputRef);
@ -108,13 +109,18 @@ export default function useInputCustomEmojis(
position: { x, y }, position: { x, y },
textColor, textColor,
}); });
animation.play(); if (canPlayAnimatedEmojis) {
animation.play();
}
playersById.current.set(playerId, animation); playersById.current.set(playerId, animation);
}); });
clearPlayers(Array.from(playerIdsToClear)); clearPlayers(Array.from(playerIdsToClear));
}, [absoluteContainerRef, textColor, inputRef, prefixId, clearPlayers, sharedCanvasHqRef, sharedCanvasRef]); }, [
inputRef, sharedCanvasRef, sharedCanvasHqRef, clearPlayers, prefixId, textColor, absoluteContainerRef,
canPlayAnimatedEmojis,
]);
useEffect(() => { useEffect(() => {
addCustomEmojiInputRenderCallback(synchronizeElements); addCustomEmojiInputRenderCallback(synchronizeElements);
@ -157,10 +163,14 @@ export default function useInputCustomEmojis(
}, []); }, []);
const unfreezeAnimation = useCallback(() => { const unfreezeAnimation = useCallback(() => {
if (!canPlayAnimatedEmojis) {
return;
}
playersById.current?.forEach((player) => { playersById.current?.forEach((player) => {
player.play(); player.play();
}); });
}, []); }, [canPlayAnimatedEmojis]);
const unfreezeAnimationOnRaf = useCallback(() => { const unfreezeAnimationOnRaf = useCallback(() => {
requestMeasure(unfreezeAnimation); requestMeasure(unfreezeAnimation);

View File

@ -10,6 +10,7 @@ import { LIKE_STICKER_ID } from '../../common/helpers/mediaDimensions';
import { import {
selectAnimatedEmojiEffect, selectAnimatedEmojiEffect,
selectAnimatedEmojiSound, selectAnimatedEmojiSound,
selectCanPlayAnimatedEmojis,
} from '../../../global/selectors'; } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { getCustomEmojiSize } from '../composer/helpers/customEmoji'; import { getCustomEmojiSize } from '../composer/helpers/customEmoji';
@ -21,7 +22,7 @@ import './AnimatedEmoji.scss';
type OwnProps = { type OwnProps = {
customEmojiId: string; customEmojiId: string;
withEffects: boolean; withEffects?: boolean;
isOwn?: boolean; isOwn?: boolean;
lastSyncTime?: number; lastSyncTime?: number;
forceLoadPreview?: boolean; forceLoadPreview?: boolean;
@ -35,6 +36,7 @@ interface StateProps {
sticker?: ApiSticker; sticker?: ApiSticker;
effect?: ApiSticker; effect?: ApiSticker;
soundId?: string; soundId?: string;
noPlay?: boolean;
} }
const AnimatedCustomEmoji: FC<OwnProps & StateProps> = ({ const AnimatedCustomEmoji: FC<OwnProps & StateProps> = ({
@ -46,6 +48,7 @@ const AnimatedCustomEmoji: FC<OwnProps & StateProps> = ({
sticker, sticker,
effect, effect,
soundId, soundId,
noPlay,
observeIntersection, observeIntersection,
}) => { }) => {
const { const {
@ -65,6 +68,7 @@ const AnimatedCustomEmoji: FC<OwnProps & StateProps> = ({
style={style} style={style}
size={size} size={size}
isBig isBig
noPlay={noPlay}
withSharedAnimation withSharedAnimation
forceOnHeavyAnimation forceOnHeavyAnimation
observeIntersectionForLoading={observeIntersection} observeIntersectionForLoading={observeIntersection}
@ -75,9 +79,11 @@ const AnimatedCustomEmoji: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>((global, { customEmojiId, withEffects }) => { export default memo(withGlobal<OwnProps>((global, { customEmojiId, withEffects }) => {
const sticker = global.customEmojis.byId[customEmojiId]; const sticker = global.customEmojis.byId[customEmojiId];
return { return {
sticker, sticker,
effect: sticker?.emoji && withEffects ? selectAnimatedEmojiEffect(global, sticker.emoji) : undefined, effect: sticker?.emoji && withEffects ? selectAnimatedEmojiEffect(global, sticker.emoji) : undefined,
soundId: sticker?.emoji && selectAnimatedEmojiSound(global, sticker.emoji), soundId: sticker?.emoji && selectAnimatedEmojiSound(global, sticker.emoji),
noPlay: !selectCanPlayAnimatedEmojis(global),
}; };
})(AnimatedCustomEmoji)); })(AnimatedCustomEmoji));

View File

@ -22,7 +22,7 @@ import './AnimatedEmoji.scss';
type OwnProps = { type OwnProps = {
emoji: string; emoji: string;
withEffects: boolean; withEffects?: boolean;
isOwn?: boolean; isOwn?: boolean;
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
lastSyncTime?: number; lastSyncTime?: number;

View File

@ -20,10 +20,6 @@
transition: background-color 0.15s, color 0.15s; transition: background-color 0.15s, color 0.15s;
user-select: none; user-select: none;
body.animation-level-0 & {
transition: none !important;
}
.Message .has-appendix &::before { .Message .has-appendix &::before {
content: ""; content: "";
display: block; display: block;
@ -40,10 +36,6 @@
.theme-dark #root & { .theme-dark #root & {
filter: invert(0.83); filter: invert(0.83);
} }
body.animation-level-0 & {
transition: none !important;
}
} }
.custom-shape & { .custom-shape & {

View File

@ -3,7 +3,6 @@ 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';
@ -20,13 +19,12 @@ 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, animationLevel, contact, user, phoneCodeList,
}) => { }) => {
const { openChat } = getActions(); const { openChat } = getActions();
@ -51,8 +49,6 @@ const Contact: FC<OwnProps & StateProps> = ({
size="large" size="large"
user={user} user={user}
text={firstName || lastName} 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>
@ -70,7 +66,6 @@ export default withGlobal<OwnProps>(
return { return {
user, user,
phoneCodeList, phoneCodeList,
animationLevel: global.settings.byKey.animationLevel,
}; };
}, },
)(Contact); )(Contact);

View File

@ -13,6 +13,7 @@ import type { IAlbum, IAnchorPosition } from '../../../types';
import { import {
selectActiveDownloadIds, selectActiveDownloadIds,
selectAllowedMessageActions, selectAllowedMessageActions,
selectCanPlayAnimatedEmojis,
selectCanScheduleUntilOnline, selectCanScheduleUntilOnline,
selectChat, selectChat,
selectChatFullInfo, selectChatFullInfo,
@ -106,6 +107,7 @@ type StateProps = {
canScheduleUntilOnline?: boolean; canScheduleUntilOnline?: boolean;
maxUniqueReactions?: number; maxUniqueReactions?: number;
threadId?: number; threadId?: number;
canPlayAnimatedEmojis?: boolean;
}; };
const ContextMenuContainer: FC<OwnProps & StateProps> = ({ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
@ -147,6 +149,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
canSaveGif, canSaveGif,
canRevote, canRevote,
canClosePoll, canClosePoll,
canPlayAnimatedEmojis,
activeDownloads, activeDownloads,
noReplies, noReplies,
canShowSeenBy, canShowSeenBy,
@ -495,6 +498,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
canTranslate={canTranslate} canTranslate={canTranslate}
canShowOriginal={canShowOriginal} canShowOriginal={canShowOriginal}
canSelectLanguage={canSelectLanguage} canSelectLanguage={canSelectLanguage}
canPlayAnimatedEmojis={canPlayAnimatedEmojis}
hasCustomEmoji={hasCustomEmoji} hasCustomEmoji={hasCustomEmoji}
customEmojiSets={customEmojiSets} customEmojiSets={customEmojiSets}
isDownloading={isDownloading} isDownloading={isDownloading}
@ -665,6 +669,7 @@ export default memo(withGlobal<OwnProps>(
canTranslate, canTranslate,
canShowOriginal: hasTranslation, canShowOriginal: hasTranslation,
canSelectLanguage: hasTranslation, canSelectLanguage: hasTranslation,
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
}; };
}, },
)(ContextMenuContainer)); )(ContextMenuContainer));

View File

@ -53,7 +53,7 @@
transform: scale(1) translateX(0); transform: scale(1) translateX(0);
transition: transform var(--select-transition); transition: transform var(--select-transition);
body.animation-level-0 & { body.no-page-transitions & {
transition: none !important; transition: none !important;
} }
} }

Some files were not shown because too many files have changed in this diff Show More