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": {
"name": "eslint-plugin-eslint-multitab-tt",
"version": "0.0.0",
"dev": true,
"license": "ISC",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@
mask-repeat: no-repeat;
mask-size: 0%;
:global(body.animation-level-2) & {
:global(body:not(.no-page-transitions)) & {
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;
&::before {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ import './StickerSetCard.scss';
type OwnProps = {
stickerSet?: ApiStickerSet;
noAnimate?: boolean;
noPlay?: boolean;
className?: string;
observeIntersection: ObserveFn;
onClick: (value: ApiSticker) => void;
@ -26,7 +26,7 @@ type OwnProps = {
const StickerSetCard: FC<OwnProps> = ({
stickerSet,
noAnimate,
noPlay,
className,
observeIntersection,
onClick,
@ -55,7 +55,7 @@ const StickerSetCard: FC<OwnProps> = ({
<StickerSetCover
stickerSet={stickerSet}
size={STICKER_SIZE_GENERAL_SETTINGS}
noAnimate={noAnimate}
noPlay={noPlay}
observeIntersection={observeIntersection}
/>
</Button>
@ -66,7 +66,7 @@ const StickerSetCard: FC<OwnProps> = ({
sticker={firstSticker}
size={STICKER_SIZE_GENERAL_SETTINGS}
title={stickerSet.title}
noAnimate={noAnimate}
noPlay={noPlay}
observeIntersection={observeIntersection}
noContextMenu
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 type { FC } from '../../lib/teact/teact';
@ -95,19 +95,22 @@ const StickerView: FC<OwnProps> = ({
const shouldPlay = isIntersectingForPlaying && !noPlay;
const thumbDataUri = useThumbnail(sticker);
// Use preview instead of thumb but only if it's already loaded
const [preloadedPreviewData] = useState(mediaLoader.getFromMemory(previewMediaHash));
const thumbData = customColor ? thumbDataUri : (preloadedPreviewData || thumbDataUri);
// Use preview instead of thumb but only if it's already loaded or when playing an animation is disabled
const previewMediaDataFromCache: string | undefined = mediaLoader.getFromMemory(previewMediaHash);
const previewMediaData = useMedia(
previewMediaHash, Boolean(previewMediaDataFromCache || !noPlay), undefined, cacheBuster,
);
const thumbData = customColor ? thumbDataUri : (previewMediaData || thumbDataUri);
const shouldForcePreview = isUnsupportedVideo || (isStatic && isSmall);
fullMediaHash ||= shouldForcePreview ? previewMediaHash : `sticker${id}`;
// 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);
// 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
const [isReadyToMount, markReadyToMount, unmarkReadyToMount] = useFlag(!IS_ANDROID || !isHeavyAnimating());
useHeavyAnimationCheck(unmarkReadyToMount, markReadyToMount, isReadyToMount);
@ -116,7 +119,7 @@ const StickerView: FC<OwnProps> = ({
const isThumbOpaque = sharedCanvasRef && !withTranslucentThumb;
const thumbClassNames = useMediaTransition(thumbData && !isFullMediaReady);
const fullMediaClassNames = useMediaTransition(isFullMediaReady);
const noTransition = isLottie && preloadedPreviewData;
const noTransition = isLottie && previewMediaData;
const coords = useCoordsInSharedCanvas(containerRef, sharedCanvasRef);

View File

@ -97,7 +97,9 @@ export function renderActionMessageText(
content.push(...processed);
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';
processed = processPlaceholder(
unprocessed,

View File

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

View File

@ -27,22 +27,37 @@ interface IOrganizedEntity {
const HQ_EMOJI_THRESHOLD = 64;
export function renderTextWithEntities(
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,
) {
export function renderTextWithEntities({
text,
entities,
highlight,
emojiSize,
shouldRenderAsHtml,
messageId,
isSimple,
isProtected,
observeIntersectionForLoading,
observeIntersectionForPlaying,
withTranslucentThumbs,
sharedCanvasRef,
sharedCanvasHqRef,
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) {
return renderMessagePart(text, highlight, emojiSize, shouldRenderAsHtml, isSimple);
}
@ -183,13 +198,11 @@ export function getTextWithEntitiesAsHtml(formattedText?: ApiFormattedText) {
return '';
}
const result = renderTextWithEntities(
const result = renderTextWithEntities({
text,
entities,
undefined,
undefined,
true,
);
shouldRenderAsHtml: true,
});
if (Array.isArray(result)) {
return result.join('');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,13 +4,15 @@ import React, {
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { GlobalState, TabState } from '../../../global/types';
import type { AnimationLevel, ISettings } from '../../../types';
import type { TabState, GlobalState } from '../../../global/types';
import { LeftColumnContent, SettingsScreens } from '../../../types';
import {
APP_NAME, APP_VERSION, ARCHIVED_FOLDER_ID,
ANIMATION_LEVEL_MAX,
ANIMATION_LEVEL_MIN,
APP_NAME, APP_VERSION,
ARCHIVED_FOLDER_ID,
BETA_CHANGELOG_URL,
DEBUG,
FEEDBACK_URL,
@ -18,6 +20,11 @@ import {
IS_TEST,
PRODUCTION_HOSTNAME,
} 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 buildClassName from '../../../util/buildClassName';
import { formatDateToString } from '../../../util/dateFormat';
@ -32,6 +39,7 @@ import { useHotkeys } from '../../../hooks/useHotkeys';
import { getPromptInstall } from '../../../util/installPrompt';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition';
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
import useAppLayout from '../../../hooks/useAppLayout';
import DropdownMenu from '../../ui/DropdownMenu';
@ -43,9 +51,9 @@ import Switcher from '../../ui/Switcher';
import ShowTransition from '../../ui/ShowTransition';
import ConnectionStatusOverlay from '../ConnectionStatusOverlay';
import StatusButton from './StatusButton';
import Toggle from '../../ui/Toggle';
import './LeftMainHeader.scss';
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
type OwnProps = {
shouldHideSearch?: boolean;
@ -79,7 +87,6 @@ type StateProps =
& Pick<GlobalState, 'connectionState' | 'isSyncing' | 'archiveSettings'>
& Pick<TabState, 'canInstall'>;
const ANIMATION_LEVEL_OPTIONS = [0, 1, 2];
const WEBK_VERSION_URL = 'https://web.telegram.org/k/';
const LeftMainHeader: FC<OwnProps & StateProps> = ({
@ -121,6 +128,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
requestNextSettingsScreen,
skipLockOnUnload,
openUrl,
updatePerformanceSettings,
} = getActions();
const lang = useLang();
@ -206,12 +214,16 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
e.stopPropagation();
const newLevel = animationLevel === 0 ? 2 : 0;
ANIMATION_LEVEL_OPTIONS.forEach((_, i) => {
document.body.classList.toggle(`animation-level-${i}`, newLevel === i);
});
let newLevel = animationLevel + 1;
if (newLevel > ANIMATION_LEVEL_MAX) {
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]);
const handleChangelogClick = useCallback(() => {
@ -249,6 +261,9 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
: lang('Search');
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
const {
@ -304,11 +319,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
onClick={handleAnimationLevelChange}
>
<span className="menu-item-name capitalize">{lang('Appearance.Animations').toLowerCase()}</span>
<Switcher
id="animations"
label="Toggle Animations"
checked={animationLevel > 0}
/>
<Toggle value={animationLevelValue} />
</MenuItem>
<MenuItem
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,
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 currentUser = currentUserId ? selectUser(global, currentUserId) : undefined;

View File

@ -2,7 +2,6 @@
--offset-y: 3.25rem !important;
--offset-x: auto !important;
--color-text: var(--color-primary);
--color-background: var(--color-background-compact-menu);
--border-radius-default: 1.25rem;
left: 0.5rem;
@ -10,7 +9,12 @@
max-width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar
height: var(--symbol-menu-height);
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) {
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 { ApiSticker } from '../../../api/types';
import { selectIsContextMenuTranslucent } from '../../../global/selectors';
import useFlag from '../../../hooks/useFlag';
import Menu from '../../ui/Menu';
@ -24,12 +25,14 @@ export type OwnProps = {
interface StateProps {
areFeaturedStickersLoaded?: boolean;
isTranslucent?: boolean;
}
const StatusPickerMenu: FC<OwnProps & StateProps> = ({
isOpen,
statusButtonRef,
areFeaturedStickersLoaded,
isTranslucent,
onEmojiStatusSelect,
onClose,
}) => {
@ -68,7 +71,7 @@ const StatusPickerMenu: FC<OwnProps & StateProps> = ({
loadAndPlay={isOpen}
isHidden={!isOpen}
isStatusPicker
isTranslucent
isTranslucent={isTranslucent}
onContextMenuOpen={markContextMenuShown}
onContextMenuClose={unmarkContextMenuShown}
onCustomEmojiSelect={handleEmojiSelect}
@ -82,5 +85,6 @@ const StatusPickerMenu: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>((global): StateProps => {
return {
areFeaturedStickersLoaded: Boolean(global.customEmojis.featuredIds?.length),
isTranslucent: selectIsContextMenuTranslucent(global),
};
})(StatusPickerMenu));

View File

@ -9,15 +9,19 @@ import type {
} from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { ChatAnimationTypes } from './hooks';
import type { AnimationLevel } from '../../../types';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment';
import {
selectCanAnimateInterface,
selectCanDeleteTopic,
selectChat,
selectChatMessage, selectCurrentMessageList,
selectChatMessage,
selectCurrentMessageList,
selectDraft,
selectOutgoingStatus, selectThreadInfo, selectThreadParam, selectUser,
selectOutgoingStatus,
selectThreadInfo,
selectThreadParam,
selectUser,
} from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { createLocationHash } from '../../../util/routing';
@ -57,11 +61,11 @@ type StateProps = {
actionTargetUserIds?: string[];
lastMessageSender?: ApiUser | ApiChat;
actionTargetChatId?: string;
animationLevel?: AnimationLevel;
typingStatus?: ApiTypingStatus;
draft?: ApiFormattedText;
canScrollDown?: boolean;
wasTopicOpened?: boolean;
withInterfaceAnimations?: boolean;
};
const Topic: FC<OwnProps & StateProps> = ({
@ -80,7 +84,7 @@ const Topic: FC<OwnProps & StateProps> = ({
actionTargetChatId,
lastMessageSender,
animationType,
animationLevel,
withInterfaceAnimations,
orderDiff,
typingStatus,
draft,
@ -122,7 +126,7 @@ const Topic: FC<OwnProps & StateProps> = ({
typingStatus,
animationType,
animationLevel,
withInterfaceAnimations,
orderDiff,
});
@ -229,7 +233,7 @@ export default memo(withGlobal<OwnProps>(
lastMessageSender,
typingStatus,
canDelete: selectCanDeleteTopic(global, chatId, topic.id),
animationLevel: global.settings.byKey.animationLevel,
withInterfaceAnimations: selectCanAnimateInterface(global),
draft,
...(isOutgoing && 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 { getGlobal } from '../../../../global';
import type { AnimationLevel } from '../../../../types';
import type { LangFn } from '../../../../hooks/useLang';
import type {
ApiChat, ApiTopic, ApiMessage, ApiTypingStatus, ApiUser,
@ -46,7 +45,7 @@ export default function useChatListEntry({
observeIntersection,
animationType,
orderDiff,
animationLevel,
withInterfaceAnimations,
isTopic,
}: {
chat?: ApiChat;
@ -64,7 +63,7 @@ export default function useChatListEntry({
animationType: ChatAnimationTypes;
orderDiff: number;
animationLevel?: AnimationLevel;
withInterfaceAnimations?: boolean;
}) {
const lang = useLang();
// eslint-disable-next-line no-null/no-null
@ -113,7 +112,12 @@ export default function useChatListEntry({
return (
<p className="last-message" dir={lang.isRtl ? 'auto' : 'ltr'}>
<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>
);
}
@ -137,6 +141,8 @@ export default function useChatListEntry({
actionTargetChatId,
lastMessageTopic,
{ isEmbedded: true },
undefined,
undefined,
)}
</p>
);
@ -161,7 +167,7 @@ export default function useChatListEntry({
useLayoutEffect(() => {
const element = ref.current;
if (animationLevel === 0 || !element) {
if (!withInterfaceAnimations || !element) {
return;
}
@ -191,7 +197,7 @@ export default function useChatListEntry({
element.style.transform = '';
});
}, ANIMATION_DURATION + ANIMATION_END_DELAY);
}, [animationLevel, orderDiff, animationType]);
}, [withInterfaceAnimations, orderDiff, animationType]);
return {
renderSubtitle,

View File

@ -5,7 +5,6 @@ import { getActions, withGlobal } from '../../../global';
import type {
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus,
} from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import type { LangFn } from '../../../hooks/useLang';
import {
@ -45,7 +44,6 @@ type StateProps = {
privateChatUser?: ApiUser;
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
lastSyncTime?: number;
animationLevel?: AnimationLevel;
};
const ChatMessage: FC<OwnProps & StateProps> = ({
@ -54,7 +52,6 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
chatId,
chat,
privateChatUser,
animationLevel,
lastSyncTime,
}) => {
const { focusMessage } = getActions();
@ -88,8 +85,6 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
user={privateChatUser}
isSavedMessages={privateChatUser?.isSelf}
lastSyncTime={lastSyncTime}
withVideo
animationLevel={animationLevel}
/>
<div className="info">
<div className="info-row">
@ -152,7 +147,6 @@ export default memo(withGlobal<OwnProps>(
return {
chat,
lastSyncTime: global.lastSyncTime,
animationLevel: global.settings.byKey.animationLevel,
...(privateChatUserId && { privateChatUser }),
};
},

View File

@ -73,9 +73,9 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
buttonRef={buttonRef}
>
{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
isOpen={isDeleteModalOpen}

View File

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

View File

@ -5,7 +5,6 @@ import React, {
import { getActions, withGlobal } from '../../../global';
import type { ApiUser } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { getUserFirstOrLastName } from '../../../global/helpers';
import renderText from '../../common/helpers/renderText';
@ -14,8 +13,8 @@ import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
import useLang from '../../../hooks/useLang';
import Button from '../../ui/Button';
import LeftSearchResultChat from './LeftSearchResultChat';
import Avatar from '../../common/Avatar';
import LeftSearchResultChat from './LeftSearchResultChat';
import './RecentContacts.scss';
@ -27,7 +26,6 @@ type StateProps = {
topUserIds?: string[];
usersById: Record<string, ApiUser>;
recentlyFoundChatIds?: string[];
animationLevel: AnimationLevel;
};
const SEARCH_CLOSE_TIMEOUT_MS = 250;
@ -39,7 +37,6 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
topUserIds,
usersById,
recentlyFoundChatIds,
animationLevel,
onReset,
}) => {
const {
@ -86,7 +83,7 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
onClick={() => handleClick(userId)}
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>
))}
@ -126,13 +123,11 @@ export default memo(withGlobal<OwnProps>(
const { userIds: topUserIds } = global.topPeers;
const usersById = global.users.byId;
const { recentlyFoundChatIds } = global;
const { animationLevel } = global.settings.byKey;
return {
topUserIds,
usersById,
recentlyFoundChatIds,
animationLevel,
};
},
)(RecentContacts));

View File

@ -124,12 +124,15 @@
}
}
.settings-item-simple,
.settings-item {
background-color: var(--color-background);
padding: 1.5rem 1.5rem 1rem;
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
margin-bottom: 0.625rem;
}
.settings-item {
&.no-border {
margin-bottom: 0;
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 {
.current-default-reaction {
margin-inline-end: 2rem;

View File

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

View File

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

View File

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

View File

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

View File

@ -30,8 +30,6 @@ type StateProps = Pick<ISettings, (
'canAutoLoadFileInPrivateChats' |
'canAutoLoadFileInGroups' |
'canAutoLoadFileInChannels' |
'canAutoPlayGifs' |
'canAutoPlayVideos' |
'autoLoadFileMaxSizeMb'
)>;
@ -50,8 +48,6 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
canAutoLoadFileInPrivateChats,
canAutoLoadFileInGroups,
canAutoLoadFileInChannels,
canAutoPlayGifs,
canAutoPlayVideos,
autoLoadFileMaxSizeMb,
}) => {
const { setSettingOption } = getActions();
@ -71,14 +67,6 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
setSettingOption({ autoLoadFileMaxSizeMb: AUTODOWNLOAD_FILESIZE_MB_LIMITS[value] });
}, [setSettingOption]);
const handleCanAutoPlayGifsChange = useCallback((value: boolean) => {
setSettingOption({ canAutoPlayGifs: value });
}, [setSettingOption]);
const handleCanAutoPlayVideosChange = useCallback((value: boolean) => {
setSettingOption({ canAutoPlayVideos: value });
}, [setSettingOption]);
function renderContentSizeSlider() {
const value = AUTODOWNLOAD_FILESIZE_MB_LIMITS.indexOf(autoLoadFileMaxSizeMb);
@ -165,21 +153,6 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
canAutoLoadFileInGroups,
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>
);
};
@ -199,8 +172,6 @@ export default memo(withGlobal<OwnProps>(
'canAutoLoadFileInPrivateChats',
'canAutoLoadFileInGroups',
'canAutoLoadFileInChannels',
'canAutoPlayGifs',
'canAutoPlayVideos',
'autoLoadFileMaxSizeMb',
]);
},

View File

@ -4,7 +4,7 @@ import React, {
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { AnimationLevel, ISettings, TimeFormat } from '../../../types';
import type { ISettings, TimeFormat } from '../../../types';
import { SettingsScreens } from '../../../types';
import {
@ -37,12 +37,6 @@ type StateProps =
shouldUseSystemTheme: boolean;
};
const ANIMATION_LEVEL_OPTIONS = [
'Solid and Steady',
'Nice and Fast',
'Lots of Stuff',
];
const TIME_FORMAT_OPTIONS: IRadioOption[] = [{
label: '12-hour',
value: '12h',
@ -56,7 +50,6 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
onScreenSelect,
onReset,
messageTextSize,
animationLevel,
messageSendKeyCombo,
timeFormat,
theme,
@ -88,14 +81,6 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
},
] : 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) => {
document.documentElement.style.setProperty(
'--composer-text-size', `${Math.max(newSize, IS_IOS ? 16 : 15)}px`,
@ -176,21 +161,6 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
/>
</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 && (
<div className="settings-item">
<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:
return <h3>{lang('NeverShareWith')}</h3>;
case SettingsScreens.Performance:
return <h3>{lang('Animations and Performance')}</h3>;
case SettingsScreens.ActiveSessions:
return <h3>{lang('SessionsTitle')}</h3>;
case SettingsScreens.ActiveWebsites:

View File

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

View File

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

View File

@ -62,7 +62,7 @@
transform: translate3d(-5rem, 0, 0);
transition: transform var(--layer-transition);
body.animation-level-0 & {
body.no-page-transitions & {
transition: none;
}
@ -80,10 +80,6 @@
z-index: 1;
pointer-events: none;
body.animation-level-0 & {
transition: none;
}
// @optimization
body.is-android & {
display: none;
@ -159,13 +155,18 @@
transform: translate3d(0, 0, 0);
transition: transform var(--layer-transition);
body.animation-level-0 & {
body.no-page-transitions & {
transition: none;
}
#Main.left-column-open & {
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) {
@ -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;
#LeftColumn, #MiddleColumn, #RightColumn {

View File

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

View File

@ -5,7 +5,6 @@ import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiPremiumGiftOption, ApiUser } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { formatCurrency } from '../../../util/formatCurrency';
import renderText from '../../common/helpers/renderText';
@ -36,7 +35,6 @@ type StateProps = {
gifts?: ApiPremiumGiftOption[];
monthlyCurrency?: string;
monthlyAmount?: number;
animationLevel: AnimationLevel;
};
const GiftPremiumModal: FC<OwnProps & StateProps> = ({
@ -45,7 +43,6 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
gifts,
monthlyCurrency,
monthlyAmount,
animationLevel,
}) => {
const { openPremiumModal, closeGiftPremiumModal, openUrl } = getActions();
@ -131,8 +128,6 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
user={renderedUser}
size="jumbo"
className={styles.avatar}
animationLevel={animationLevel}
withVideo
/>
<h2 className={styles.headerText}>
{lang('GiftTelegramPremiumTitle')}
@ -179,6 +174,5 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
gifts,
monthlyCurrency,
monthlyAmount: monthlyAmount ? Number(monthlyAmount) : undefined,
animationLevel: global.settings.byKey.animationLevel,
};
})(GiftPremiumModal));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ import React, { useCallback } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { ApiChat, ApiMessage, ApiUser } from '../../api/types';
import type { AnimationLevel } from '../../types';
import { getSenderTitle, isUserId } from '../../global/helpers';
import { formatMediaDateTime } from '../../util/dateFormat';
@ -31,7 +30,6 @@ type OwnProps = {
type StateProps = {
sender?: ApiUser | ApiChat;
message?: ApiMessage;
animationLevel: AnimationLevel;
};
const ANIMATION_DURATION = 350;
@ -43,7 +41,6 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
isFallbackAvatar,
isAvatar,
message,
animationLevel,
}) => {
const {
closeMediaViewer,
@ -79,9 +76,9 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
return (
<div className="SenderInfo" onClick={handleFocusMessage}>
{isUserId(sender.id) ? (
<Avatar key={sender.id} size="medium" user={sender as ApiUser} 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="title" dir="auto">
@ -99,16 +96,14 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
export default withGlobal<OwnProps>(
(global, { chatId, messageId, isAvatar }): StateProps => {
const { animationLevel } = global.settings.byKey;
if (isAvatar && chatId) {
return {
sender: isUserId(chatId) ? selectUser(global, chatId) : selectChat(global, chatId),
animationLevel,
};
}
if (!messageId || !chatId) {
return { animationLevel };
return {};
}
const message = selectChatMessage(global, chatId, messageId);
@ -116,7 +111,6 @@ export default withGlobal<OwnProps>(
return {
message,
sender: message && selectSender(global, message),
animationLevel,
};
},
)(SenderInfo);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,13 +18,11 @@
}
}
:global(body.animation-level-1) & {
:global(body.no-page-transitions) & {
:global(.ripple-container) {
display: none;
}
}
:global(body.animation-level-0) & {
transition: none !important;
}
@ -32,6 +30,10 @@
transform: translate3d(0, 0, 0);
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) & {
transform: translate3d(calc(var(--right-column-width) * -1), 0, 0);
}
@ -264,7 +266,7 @@
:global(.tools-stacked.animated) .root {
animation: fade-in var(--layer-transition) forwards;
:global(body.animation-level-0) & {
:global(body.no-page-transitions) & {
animation: none;
}
}

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@
transform: scale(1.1);
}
:global(body:not(.animation-level-0)) &.withTransition {
:global(body:not(.no-page-transitions)) &.withTransition {
transition: background-color 0.2s;
&.customBgImage::before {
@ -46,14 +46,14 @@
}
@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;
transform: scale(1);
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) {
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);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -56,7 +56,11 @@ function renderTopic(lang: LangFn, topic: ApiTopic) {
return (
<div className="NoMessages">
<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>
<p className="description topic-description">{renderText(lang('Chat.EmptyTopicPlaceholder.Text'), ['br'])}</p>
</div>

View File

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

View File

@ -21,10 +21,15 @@
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;
}
: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) & {
transform: translateX(calc(-1 * var(--right-column-width)));
}

View File

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

View File

@ -156,8 +156,7 @@
animation-duration: 0ms !important;
}
body.animation-level-0 &,
body.animation-level-1 & {
body.no-message-composer-animations & {
.icon-send,
.icon-microphone-alt,
.icon-check,
@ -171,7 +170,7 @@
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;
transform-origin: right;
}
@ -447,7 +446,7 @@
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: height 100ms ease;
body.animation-level-0 & {
body.no-message-composer-animations & {
transition: none !important;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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