From 4f42b676ce60f08a8390afaa3e59f3100ebcde23 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Tue, 25 Apr 2023 17:24:26 +0400 Subject: [PATCH] Settings: Performance mode (#3045) Co-authored-by: Alexander Zinchuk --- package-lock.json | 1 + .../calls/group/GroupCallTopPane.tsx | 6 +- src/components/calls/phone/PhoneCall.tsx | 7 - src/components/common/AnimatedCounter.tsx | 4 +- src/components/common/Avatar.tsx | 27 +-- src/components/common/CustomEmoji.tsx | 6 +- src/components/common/CustomEmojiPicker.tsx | 7 +- .../common/CustomEmojiSetsModal.tsx | 6 +- src/components/common/DeleteChatModal.tsx | 6 - src/components/common/EmbeddedMessage.scss | 2 +- src/components/common/GifButton.scss | 2 + src/components/common/GifButton.tsx | 1 + src/components/common/GroupChatInfo.tsx | 20 +- .../common/MediaSpoiler.module.scss | 4 +- src/components/common/MessageText.tsx | 10 +- src/components/common/PrivateChatInfo.tsx | 14 +- src/components/common/ProfileInfo.tsx | 7 +- src/components/common/StickerButton.tsx | 7 +- src/components/common/StickerSet.tsx | 2 +- src/components/common/StickerSetCard.tsx | 8 +- src/components/common/StickerView.tsx | 17 +- .../helpers/renderActionMessageText.tsx | 4 +- .../common/helpers/renderMessageText.ts | 8 +- .../common/helpers/renderTextWithEntities.tsx | 55 +++-- src/components/common/hooks/useCustomEmoji.ts | 17 +- src/components/left/LeftColumn.tsx | 1 + src/components/left/NewChatButton.scss | 2 +- src/components/left/main/Archive.module.scss | 4 +- src/components/left/main/Chat.scss | 12 +- src/components/left/main/Chat.tsx | 15 +- src/components/left/main/ForumPanel.tsx | 17 +- src/components/left/main/LeftMainHeader.tsx | 43 ++-- src/components/left/main/StatusButton.tsx | 2 +- .../left/main/StatusPickerMenu.module.scss | 8 +- src/components/left/main/StatusPickerMenu.tsx | 6 +- src/components/left/main/Topic.tsx | 18 +- .../left/main/hooks/useChatListEntry.tsx | 18 +- src/components/left/search/ChatMessage.tsx | 6 - .../left/search/LeftSearchResultChat.tsx | 4 +- .../left/search/LeftSearchResultTopic.tsx | 4 +- src/components/left/search/RecentContacts.tsx | 9 +- src/components/left/settings/Settings.scss | 15 ++ src/components/left/settings/Settings.tsx | 9 + .../left/settings/SettingsActiveWebsite.tsx | 6 - .../left/settings/SettingsActiveWebsites.tsx | 6 +- .../left/settings/SettingsCustomEmoji.tsx | 7 +- .../left/settings/SettingsDataStorage.tsx | 29 --- .../left/settings/SettingsGeneral.tsx | 32 +-- .../left/settings/SettingsHeader.tsx | 3 + src/components/left/settings/SettingsMain.tsx | 7 + .../left/settings/SettingsPerformance.tsx | 221 ++++++++++++++++++ .../left/settings/SettingsStickers.tsx | 19 +- src/components/main/Dialogs.tsx | 7 +- src/components/main/Main.scss | 23 +- src/components/main/Main.tsx | 28 ++- .../main/premium/GiftPremiumModal.tsx | 6 - .../main/premium/PremiumMainModal.tsx | 5 +- src/components/mediaViewer/MediaViewer.scss | 2 +- src/components/mediaViewer/MediaViewer.tsx | 30 ++- .../mediaViewer/MediaViewerContent.tsx | 9 +- .../mediaViewer/MediaViewerSlides.tsx | 16 +- src/components/mediaViewer/SenderInfo.tsx | 12 +- src/components/mediaViewer/VideoPlayer.scss | 2 +- src/components/middle/ActionMessage.tsx | 14 +- .../middle/ActionMessageSuggestedAvatar.tsx | 1 - src/components/middle/AudioPlayer.scss | 2 +- src/components/middle/ChatReportPanel.scss | 2 +- .../middle/FloatingActionButtons.module.scss | 2 +- src/components/middle/HeaderActions.tsx | 4 +- .../middle/HeaderPinnedMessage.module.scss | 10 +- src/components/middle/MessageList.scss | 13 +- src/components/middle/MessageList.tsx | 14 +- .../middle/MessageSelectToolbar.scss | 2 +- .../middle/MiddleColumn.module.scss | 8 +- src/components/middle/MiddleColumn.scss | 17 +- src/components/middle/MiddleColumn.tsx | 15 +- src/components/middle/MiddleHeader.scss | 11 +- src/components/middle/MiddleHeader.tsx | 2 - src/components/middle/NoMessages.tsx | 6 +- src/components/middle/ReactorListModal.tsx | 11 +- .../middle/ScrollDownButton.module.scss | 7 +- .../composer/AttachmentModalItem.module.scss | 5 + src/components/middle/composer/Composer.scss | 7 +- .../composer/ComposerEmbeddedMessage.scss | 2 +- .../composer/ComposerEmbeddedMessage.tsx | 3 +- .../middle/composer/CustomEmojiTooltip.tsx | 3 + src/components/middle/composer/GifPicker.scss | 7 +- .../middle/composer/MessageInput.tsx | 6 +- .../middle/composer/StickerPicker.tsx | 8 +- .../middle/composer/StickerSetCover.tsx | 9 +- .../middle/composer/SymbolMenu.scss | 9 +- src/components/middle/composer/SymbolMenu.tsx | 9 +- .../middle/composer/WebPagePreview.scss | 8 +- .../composer/hooks/useInputCustomEmojis.ts | 16 +- .../middle/message/AnimatedCustomEmoji.tsx | 8 +- .../middle/message/AnimatedEmoji.tsx | 2 +- .../middle/message/CommentButton.scss | 8 - src/components/middle/message/Contact.tsx | 7 +- .../middle/message/ContextMenuContainer.tsx | 5 + src/components/middle/message/Message.scss | 2 +- src/components/middle/message/Message.tsx | 24 +- .../middle/message/MessageContextMenu.scss | 9 +- .../middle/message/MessageContextMenu.tsx | 3 + src/components/middle/message/Poll.tsx | 2 +- .../middle/message/ReactionAnimatedEmoji.tsx | 6 +- .../middle/message/ReactionButton.tsx | 3 + .../middle/message/ReactionPicker.module.scss | 8 +- .../middle/message/ReactionPicker.tsx | 7 +- .../middle/message/ReactionSelector.scss | 9 +- .../middle/message/ReactionSelector.tsx | 3 + .../message/ReactionSelectorReaction.scss | 12 +- .../message/ReactionSelectorReaction.tsx | 21 +- src/components/middle/message/Reactions.tsx | 3 + .../middle/message/SponsoredMessage.tsx | 5 +- src/components/middle/message/Sticker.tsx | 15 +- src/components/right/EditTopic.tsx | 3 +- src/components/right/RightColumn.scss | 8 +- src/components/right/RightSearch.tsx | 6 - src/components/right/StickerSetResult.tsx | 2 +- .../right/management/JoinRequest.tsx | 8 +- src/components/ui/Button.scss | 4 +- src/components/ui/Checkbox.tsx | 4 +- src/components/ui/FloatingActionButton.scss | 2 +- src/components/ui/ListItem.scss | 2 +- src/components/ui/Menu.scss | 7 +- src/components/ui/Modal.scss | 2 +- src/components/ui/RippleEffect.scss | 2 +- src/components/ui/Switcher.scss | 2 +- src/components/ui/Tab.scss | 2 +- src/components/ui/Toggle.module.scss | 94 ++++++++ src/components/ui/Toggle.tsx | 20 ++ src/components/ui/Transition.tsx | 36 +-- src/config.ts | 1 + src/global/actions/api/reactions.ts | 13 +- src/global/actions/ui/initial.ts | 16 +- src/global/actions/ui/misc.ts | 11 +- src/global/actions/ui/settings.ts | 31 ++- src/global/cache.ts | 32 ++- src/global/initialState.ts | 61 ++++- src/global/selectors/messages.ts | 23 +- src/global/selectors/ui.ts | 45 ++++ src/global/types.ts | 3 + src/styles/_common.scss | 2 +- src/types/index.ts | 12 +- src/util/animateHorizontalScroll.ts | 4 +- src/util/animateScroll.ts | 4 +- src/util/customEmojiManager.ts | 6 +- src/util/perfomanceSettings.ts | 23 ++ src/util/windowEnvironment.ts | 3 - 149 files changed, 1190 insertions(+), 610 deletions(-) create mode 100644 src/components/left/settings/SettingsPerformance.tsx create mode 100644 src/components/ui/Toggle.module.scss create mode 100644 src/components/ui/Toggle.tsx create mode 100644 src/util/perfomanceSettings.ts diff --git a/package-lock.json b/package-lock.json index d183bf7fd..b398b953e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,6 +107,7 @@ } }, "dev/eslint-multitab": { + "name": "eslint-plugin-eslint-multitab-tt", "version": "0.0.0", "dev": true, "license": "ISC", diff --git a/src/components/calls/group/GroupCallTopPane.tsx b/src/components/calls/group/GroupCallTopPane.tsx index 6e4240db0..4c5c6a037 100644 --- a/src/components/calls/group/GroupCallTopPane.tsx +++ b/src/components/calls/group/GroupCallTopPane.tsx @@ -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 = ({ @@ -35,7 +33,6 @@ const GroupCallTopPane: FC = ({ className, groupCall, hasPinnedOffset, - animationLevel, }) => { const { requestMasterAndJoinGroupCall, @@ -112,12 +109,12 @@ const GroupCallTopPane: FC = ({
{fetchedParticipants.map((p) => { if (!p) return undefined; + return ( ); })} @@ -142,7 +139,6 @@ export default memo(withGlobal( ? groupCall.participantsCount > 0 && groupCall.isLoaded : chat && chat.isCallNotEmpty && chat.isCallActive, ), - animationLevel: global.settings.byKey.animationLevel, }; }, )(GroupCallTopPane)); diff --git a/src/components/calls/phone/PhoneCall.tsx b/src/components/calls/phone/PhoneCall.tsx index 184a3d629..3fbebe9dc 100644 --- a/src/components/calls/phone/PhoneCall.tsx +++ b/src/components/calls/phone/PhoneCall.tsx @@ -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 = ({ @@ -49,7 +47,6 @@ const PhoneCall: FC = ({ isOutgoing, phoneCall, isCallPanelVisible, - animationLevel, }) => { const lang = useLang(); const { @@ -240,9 +237,6 @@ const PhoneCall: FC = ({ user={user} size="jumbo" className={hasVideo || hasPresentation ? styles.blurred : ''} - withVideo - noLoop={phoneCall?.state !== 'requesting'} - animationLevel={animationLevel} /> {phoneCall?.screencastState === 'active' && streams?.presentation &&
@@ -77,6 +80,7 @@ export default memo(withGlobal( return { customEmojiSets, + canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), }; }, )(CustomEmojiSetsModal)); diff --git a/src/components/common/DeleteChatModal.tsx b/src/components/common/DeleteChatModal.tsx index 15f0cf3f9..65dbb3b5b 100644 --- a/src/components/common/DeleteChatModal.tsx +++ b/src/components/common/DeleteChatModal.tsx @@ -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 = ({ @@ -57,7 +55,6 @@ const DeleteChatModal: FC = ({ currentUserId, canDeleteForAll, contactName, - animationLevel, onClose, onCloseAnimationEnd, }) => { @@ -128,8 +125,6 @@ const DeleteChatModal: FC = ({ size="tiny" chat={chat} isSavedMessages={isChatWithSelf} - animationLevel={animationLevel} - withVideo />

{lang(renderTitle())}

@@ -243,7 +238,6 @@ export default memo(withGlobal( currentUserId: global.currentUserId, canDeleteForAll, contactName, - animationLevel: global.settings.byKey.animationLevel, }; }, )(DeleteChatModal)); diff --git a/src/components/common/EmbeddedMessage.scss b/src/components/common/EmbeddedMessage.scss index 325db66a0..41d3c4c44 100644 --- a/src/components/common/EmbeddedMessage.scss +++ b/src/components/common/EmbeddedMessage.scss @@ -17,7 +17,7 @@ } } - body.animation-level-1 & { + body.no-page-transitions & { .ripple-container { display: none; } diff --git a/src/components/common/GifButton.scss b/src/components/common/GifButton.scss index eab1e105a..74758a764 100644 --- a/src/components/common/GifButton.scss +++ b/src/components/common/GifButton.scss @@ -74,6 +74,8 @@ position: absolute; .bubble { + --offset-y: 0; + width: auto; } } diff --git a/src/components/common/GifButton.tsx b/src/components/common/GifButton.tsx index 6d520ce80..b2989c733 100644 --- a/src/components/common/GifButton.tsx +++ b/src/components/common/GifButton.tsx @@ -152,6 +152,7 @@ const GifButton: FC = ({ className="gif-unsave-button" color="dark" pill + noFastClick onClick={handleUnsaveClick} > diff --git a/src/components/common/GroupChatInfo.tsx b/src/components/common/GroupChatInfo.tsx index 906f4d545..075458241 100644 --- a/src/components/common/GroupChatInfo.tsx +++ b/src/components/common/GroupChatInfo.tsx @@ -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; @@ -77,13 +78,11 @@ const GroupChatInfo: FC = ({ withFullInfo, withUpdatingStatus, withChatType, - withVideoAvatar, threadInfo, noRtl, chat, onlineCount, areMessagesLoaded, - animationLevel, lastSyncTime, topic, messagesCount, @@ -187,12 +186,14 @@ const GroupChatInfo: FC = ({ size={avatarSize} chat={chat} onClick={withMediaViewer ? handleAvatarViewerOpen : undefined} - withVideo={withVideoAvatar} - animationLevel={animationLevel} /> )} {isTopic && ( - + )}
{topic @@ -238,7 +239,6 @@ export default memo(withGlobal( onlineCount, topic, areMessagesLoaded, - animationLevel: global.settings.byKey.animationLevel, messagesCount, }; }, diff --git a/src/components/common/MediaSpoiler.module.scss b/src/components/common/MediaSpoiler.module.scss index 5ed69854c..6c195cea7 100644 --- a/src/components/common/MediaSpoiler.module.scss +++ b/src/components/common/MediaSpoiler.module.scss @@ -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 { diff --git a/src/components/common/MessageText.tsx b/src/components/common/MessageText.tsx index b357452d4..6ac680ee5 100644 --- a/src/components/common/MessageText.tsx +++ b/src/components/common/MessageText.tsx @@ -79,13 +79,13 @@ function MessageText({ {[ withSharedCanvas && , withSharedCanvas && , - 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)} ); diff --git a/src/components/common/PrivateChatInfo.tsx b/src/components/common/PrivateChatInfo.tsx index 3cfd727a1..3fd13f473 100644 --- a/src/components/common/PrivateChatInfo.tsx +++ b/src/components/common/PrivateChatInfo.tsx @@ -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; @@ -60,7 +61,6 @@ const PrivateChatInfo: FC = ({ withUsername, withFullInfo, withUpdatingStatus, - withVideoAvatar, emojiStatusSize, noStatusOrTyping, noRtl, @@ -68,7 +68,6 @@ const PrivateChatInfo: FC = ({ userStatus, isSavedMessages, areMessagesLoaded, - animationLevel, lastSyncTime, adminMember, }) => { @@ -173,8 +172,6 @@ const PrivateChatInfo: FC = ({ user={user} isSavedMessages={isSavedMessages} onClick={withMediaViewer ? handleAvatarViewerOpen : undefined} - withVideo={withVideoAvatar} - animationLevel={animationLevel} />
{renderNameTitle()} @@ -198,7 +195,6 @@ export default memo(withGlobal( userStatus, isSavedMessages, areMessagesLoaded, - animationLevel: global.settings.byKey.animationLevel, }; }, )(PrivateChatInfo)); diff --git a/src/components/common/ProfileInfo.tsx b/src/components/common/ProfileInfo.tsx index 7323912b3..3ed78984e 100644 --- a/src/components/common/ProfileInfo.tsx +++ b/src/components/common/ProfileInfo.tsx @@ -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 = ({ chat, isSavedMessages, connectionState, - animationLevel, mediaId, avatarOwnerId, topic, @@ -103,7 +100,7 @@ const ProfileInfo: FC = ({ 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( 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( userFallbackPhoto: userFullInfo?.fallbackPhoto, chatProfilePhoto: chatFullInfo?.profilePhoto, isSavedMessages, - animationLevel, mediaId, avatarOwnerId, ...(topic && { diff --git a/src/components/common/StickerButton.tsx b/src/components/common/StickerButton.tsx index 2fac60ae1..24a944aed 100644 --- a/src/components/common/StickerButton.tsx +++ b/src/components/common/StickerButton.tsx @@ -28,7 +28,7 @@ import './StickerButton.scss'; type OwnProps = { sticker: ApiSticker; size: number; - noAnimate?: boolean; + noPlay?: boolean; title?: string; className?: string; noContextMenu?: boolean; @@ -63,7 +63,7 @@ const contentForStatusMenuContext = [ const StickerButton = ({ sticker, size, - noAnimate, + noPlay, title, className, noContextMenu, @@ -102,7 +102,7 @@ const StickerButton = diff --git a/src/components/common/StickerSet.tsx b/src/components/common/StickerSet.tsx index 025ddc735..cd7f1e077 100644 --- a/src/components/common/StickerSet.tsx +++ b/src/components/common/StickerSet.tsx @@ -339,7 +339,7 @@ const StickerSet: FC = ({ size={itemSize} observeIntersection={observeIntersectionForPlayingItems} observeIntersectionForShowing={observeIntersectionForShowingItems} - noAnimate={!loadAndPlay} + noPlay={!loadAndPlay} isSavedMessages={isSavedMessages} isStatusPicker={isStatusPicker} canViewSet diff --git a/src/components/common/StickerSetCard.tsx b/src/components/common/StickerSetCard.tsx index bd53fc96b..550f4d1c7 100644 --- a/src/components/common/StickerSetCard.tsx +++ b/src/components/common/StickerSetCard.tsx @@ -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 = ({ stickerSet, - noAnimate, + noPlay, className, observeIntersection, onClick, @@ -55,7 +55,7 @@ const StickerSetCard: FC = ({ @@ -66,7 +66,7 @@ const StickerSetCard: FC = ({ sticker={firstSticker} size={STICKER_SIZE_GENERAL_SETTINGS} title={stickerSet.title} - noAnimate={noAnimate} + noPlay={noPlay} observeIntersection={observeIntersection} noContextMenu isCurrentUserPremium diff --git a/src/components/common/StickerView.tsx b/src/components/common/StickerView.tsx index 11a183552..30a8c33d2 100644 --- a/src/components/common/StickerView.tsx +++ b/src/components/common/StickerView.tsx @@ -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 = ({ 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 = ({ 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); diff --git a/src/components/common/helpers/renderActionMessageText.tsx b/src/components/common/helpers/renderActionMessageText.tsx index b23c012d5..218103724 100644 --- a/src/components/common/helpers/renderActionMessageText.tsx +++ b/src/components/common/helpers/renderActionMessageText.tsx @@ -97,7 +97,9 @@ export function renderActionMessageText( content.push(...processed); if (unprocessed.includes('%action_topic%')) { - const topicEmoji = topic?.iconEmojiId ? : ''; + const topicEmoji = topic?.iconEmojiId + ? + : ''; const topicString = topic ? `${topic.title}` : 'a topic'; processed = processPlaceholder( unprocessed, diff --git a/src/components/common/helpers/renderMessageText.ts b/src/components/common/helpers/renderMessageText.ts index 20190f2e5..a6da21d8c 100644 --- a/src/components/common/helpers/renderMessageText.ts +++ b/src/components/common/helpers/renderMessageText.ts @@ -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 diff --git a/src/components/common/helpers/renderTextWithEntities.tsx b/src/components/common/helpers/renderTextWithEntities.tsx index c205d85ef..69a446186 100644 --- a/src/components/common/helpers/renderTextWithEntities.tsx +++ b/src/components/common/helpers/renderTextWithEntities.tsx @@ -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, - sharedCanvasHqRef?: React.RefObject, - 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; + sharedCanvasHqRef?: React.RefObject; + 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(''); diff --git a/src/components/common/hooks/useCustomEmoji.ts b/src/components/common/hooks/useCustomEmoji.ts index de1cedbe9..e05ae23b3 100644 --- a/src/components/common/hooks/useCustomEmoji.ts +++ b/src/components/common/hooks/useCustomEmoji.ts @@ -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( - 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 }; } diff --git a/src/components/left/LeftColumn.tsx b/src/components/left/LeftColumn.tsx index 99295373e..bb7138373 100644 --- a/src/components/left/LeftColumn.tsx +++ b/src/components/left/LeftColumn.tsx @@ -157,6 +157,7 @@ const LeftColumn: FC = ({ case SettingsScreens.Notifications: case SettingsScreens.DataStorage: case SettingsScreens.Privacy: + case SettingsScreens.Performance: case SettingsScreens.ActiveSessions: case SettingsScreens.Language: case SettingsScreens.Stickers: diff --git a/src/components/left/NewChatButton.scss b/src/components/left/NewChatButton.scss index 3e1212dc0..dd7585301 100644 --- a/src/components/left/NewChatButton.scss +++ b/src/components/left/NewChatButton.scss @@ -11,7 +11,7 @@ left: 1rem; } - body.animation-level-0 & { + body.no-page-transitions & { transform: none !important; opacity: 0; diff --git a/src/components/left/main/Archive.module.scss b/src/components/left/main/Archive.module.scss index a88a64e79..5b2c60fe0 100644 --- a/src/components/left/main/Archive.module.scss +++ b/src/components/left/main/Archive.module.scss @@ -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; } } diff --git a/src/components/left/main/Chat.scss b/src/components/left/main/Chat.scss index d6212f3a2..b3b78d11d 100644 --- a/src/components/left/main/Chat.scss +++ b/src/components/left/main/Chat.scss @@ -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 { diff --git a/src/components/left/main/Chat.tsx b/src/components/left/main/Chat.tsx index cdeed1528..5815c9fa9 100644 --- a/src/components/left/main/Chat.tsx +++ b/src/components/left/main/Chat.tsx @@ -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 = ({ @@ -110,7 +110,7 @@ const Chat: FC = ({ actionTargetChatId, offsetTop, draft, - animationLevel, + withInterfaceAnimations, isSelected, isSelectedForum, canScrollDown, @@ -150,7 +150,7 @@ const Chat: FC = ({ lastMessageSender, observeIntersection, animationType, - animationLevel, + withInterfaceAnimations, orderDiff, }); @@ -239,13 +239,10 @@ const Chat: FC = ({ userStatus={userStatus} isSavedMessages={user?.isSelf} lastSyncTime={lastSyncTime} - animationLevel={animationLevel} - withVideo - observeIntersection={observeIntersection} /> {chat.isCallActive && chat.isCallNotEmpty && ( - + )}
@@ -337,7 +334,6 @@ export default memo(withGlobal( 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( userStatus, lastMessageTopic, typingStatus, + withInterfaceAnimations: selectCanAnimateInterface(global), }; }, )(Chat)); diff --git a/src/components/left/main/ForumPanel.tsx b/src/components/left/main/ForumPanel.tsx index 913a339b2..d685d9b5b 100644 --- a/src/components/left/main/ForumPanel.tsx +++ b/src/components/left/main/ForumPanel.tsx @@ -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 = ({ onTopicSearch, onCloseAnimationEnd, onOpenAnimationStart, - animationLevel, + withInterfaceAnimations, }) => { const { closeForumPanel, openChatWithInfo, loadTopics, @@ -97,10 +96,10 @@ const ForumPanel: FC = ({ }, [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 = ({ 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( chat, lastSyncTime: global.lastSyncTime, currentTopicId: chatId === currentChatId ? currentThreadId : undefined, - animationLevel: global.settings.byKey.animationLevel, + withInterfaceAnimations: selectCanAnimateInterface(global), }; }, )(ForumPanel)); diff --git a/src/components/left/main/LeftMainHeader.tsx b/src/components/left/main/LeftMainHeader.tsx index 002a2e21c..9c1dff9f6 100644 --- a/src/components/left/main/LeftMainHeader.tsx +++ b/src/components/left/main/LeftMainHeader.tsx @@ -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 & Pick; -const ANIMATION_LEVEL_OPTIONS = [0, 1, 2]; const WEBK_VERSION_URL = 'https://web.telegram.org/k/'; const LeftMainHeader: FC = ({ @@ -121,6 +128,7 @@ const LeftMainHeader: FC = ({ requestNextSettingsScreen, skipLockOnUnload, openUrl, + updatePerformanceSettings, } = getActions(); const lang = useLang(); @@ -206,12 +214,16 @@ const LeftMainHeader: FC = ({ const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent) => { 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 = ({ : 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 = ({ onClick={handleAnimationLevelChange} > {lang('Appearance.Animations').toLowerCase()} - 0} - /> + = ({ )} ), [ - animationLevel, archivedUnreadChatsCount, canInstall, handleAnimationLevelChange, handleBugReportClick, lang, + animationLevelValue, archivedUnreadChatsCount, canInstall, handleAnimationLevelChange, handleBugReportClick, lang, handleChangelogClick, handleDarkModeToggle, handleOpenTipsChat, handleSelectSaved, handleSwitchToWebK, onSelectArchived, onSelectContacts, onSelectSettings, theme, withOtherVersions, archiveSettings, ]); diff --git a/src/components/left/main/StatusButton.tsx b/src/components/left/main/StatusButton.tsx index 75b00bd04..1f49421ad 100644 --- a/src/components/left/main/StatusButton.tsx +++ b/src/components/left/main/StatusButton.tsx @@ -93,7 +93,7 @@ const StatusButton: FC = ({ emojiStatus }) => { ); }; -export default memo(withGlobal((global) => { +export default memo(withGlobal((global): StateProps => { const { currentUserId } = global; const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined; diff --git a/src/components/left/main/StatusPickerMenu.module.scss b/src/components/left/main/StatusPickerMenu.module.scss index 784c8ac05..e005f4fc4 100644 --- a/src/components/left/main/StatusPickerMenu.module.scss +++ b/src/components/left/main/StatusPickerMenu.module.scss @@ -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); diff --git a/src/components/left/main/StatusPickerMenu.tsx b/src/components/left/main/StatusPickerMenu.tsx index a84570789..39361d4ea 100644 --- a/src/components/left/main/StatusPickerMenu.tsx +++ b/src/components/left/main/StatusPickerMenu.tsx @@ -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 = ({ isOpen, statusButtonRef, areFeaturedStickersLoaded, + isTranslucent, onEmojiStatusSelect, onClose, }) => { @@ -68,7 +71,7 @@ const StatusPickerMenu: FC = ({ loadAndPlay={isOpen} isHidden={!isOpen} isStatusPicker - isTranslucent + isTranslucent={isTranslucent} onContextMenuOpen={markContextMenuShown} onContextMenuClose={unmarkContextMenuShown} onCustomEmojiSelect={handleEmojiSelect} @@ -82,5 +85,6 @@ const StatusPickerMenu: FC = ({ export default memo(withGlobal((global): StateProps => { return { areFeaturedStickersLoaded: Boolean(global.customEmojis.featuredIds?.length), + isTranslucent: selectIsContextMenuTranslucent(global), }; })(StatusPickerMenu)); diff --git a/src/components/left/main/Topic.tsx b/src/components/left/main/Topic.tsx index 47b935425..2a6930486 100644 --- a/src/components/left/main/Topic.tsx +++ b/src/components/left/main/Topic.tsx @@ -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 = ({ @@ -80,7 +84,7 @@ const Topic: FC = ({ actionTargetChatId, lastMessageSender, animationType, - animationLevel, + withInterfaceAnimations, orderDiff, typingStatus, draft, @@ -122,7 +126,7 @@ const Topic: FC = ({ typingStatus, animationType, - animationLevel, + withInterfaceAnimations, orderDiff, }); @@ -229,7 +233,7 @@ export default memo(withGlobal( lastMessageSender, typingStatus, canDelete: selectCanDeleteTopic(global, chatId, topic.id), - animationLevel: global.settings.byKey.animationLevel, + withInterfaceAnimations: selectCanAnimateInterface(global), draft, ...(isOutgoing && lastMessage && { lastMessageOutgoingStatus: selectOutgoingStatus(global, lastMessage), diff --git a/src/components/left/main/hooks/useChatListEntry.tsx b/src/components/left/main/hooks/useChatListEntry.tsx index 6235d2a19..d8f113c58 100644 --- a/src/components/left/main/hooks/useChatListEntry.tsx +++ b/src/components/left/main/hooks/useChatListEntry.tsx @@ -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 (

{lang('Draft')} - {renderTextWithEntities(draft.text, draft.entities, undefined, undefined, undefined, undefined, true)} + {renderTextWithEntities({ + text: draft.text, + entities: draft.entities, + isSimple: true, + withTranslucentThumbs: true, + })}

); } @@ -137,6 +141,8 @@ export default function useChatListEntry({ actionTargetChatId, lastMessageTopic, { isEmbedded: true }, + undefined, + undefined, )}

); @@ -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, diff --git a/src/components/left/search/ChatMessage.tsx b/src/components/left/search/ChatMessage.tsx index 6c89ec84b..e753855b8 100644 --- a/src/components/left/search/ChatMessage.tsx +++ b/src/components/left/search/ChatMessage.tsx @@ -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 = ({ @@ -54,7 +52,6 @@ const ChatMessage: FC = ({ chatId, chat, privateChatUser, - animationLevel, lastSyncTime, }) => { const { focusMessage } = getActions(); @@ -88,8 +85,6 @@ const ChatMessage: FC = ({ user={privateChatUser} isSavedMessages={privateChatUser?.isSelf} lastSyncTime={lastSyncTime} - withVideo - animationLevel={animationLevel} />
@@ -152,7 +147,6 @@ export default memo(withGlobal( return { chat, lastSyncTime: global.lastSyncTime, - animationLevel: global.settings.byKey.animationLevel, ...(privateChatUserId && { privateChatUser }), }; }, diff --git a/src/components/left/search/LeftSearchResultChat.tsx b/src/components/left/search/LeftSearchResultChat.tsx index d32073a4c..63499f6d5 100644 --- a/src/components/left/search/LeftSearchResultChat.tsx +++ b/src/components/left/search/LeftSearchResultChat.tsx @@ -73,9 +73,9 @@ const LeftSearchResultChat: FC = ({ buttonRef={buttonRef} > {isUserId(chatId) ? ( - + ) : ( - + )} ; recentlyFoundChatIds?: string[]; - animationLevel: AnimationLevel; }; const SEARCH_CLOSE_TIMEOUT_MS = 250; @@ -39,7 +37,6 @@ const RecentContacts: FC = ({ topUserIds, usersById, recentlyFoundChatIds, - animationLevel, onReset, }) => { const { @@ -86,7 +83,7 @@ const RecentContacts: FC = ({ onClick={() => handleClick(userId)} dir={lang.isRtl ? 'rtl' : undefined} > - +
{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}
))} @@ -126,13 +123,11 @@ export default memo(withGlobal( const { userIds: topUserIds } = global.topPeers; const usersById = global.users.byId; const { recentlyFoundChatIds } = global; - const { animationLevel } = global.settings.byKey; return { topUserIds, usersById, recentlyFoundChatIds, - animationLevel, }; }, )(RecentContacts)); diff --git a/src/components/left/settings/Settings.scss b/src/components/left/settings/Settings.scss index 4c895ac94..03f226886 100644 --- a/src/components/left/settings/Settings.scss +++ b/src/components/left/settings/Settings.scss @@ -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; diff --git a/src/components/left/settings/Settings.tsx b/src/components/left/settings/Settings.tsx index c4b11b780..59bf05301 100644 --- a/src/components/left/settings/Settings.tsx +++ b/src/components/left/settings/Settings.tsx @@ -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 = ({ /> ); + case SettingsScreens.Performance: + return ( + + ); + default: return undefined; } diff --git a/src/components/left/settings/SettingsActiveWebsite.tsx b/src/components/left/settings/SettingsActiveWebsite.tsx index f08e8143c..9524e68ca 100644 --- a/src/components/left/settings/SettingsActiveWebsite.tsx +++ b/src/components/left/settings/SettingsActiveWebsite.tsx @@ -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 = ({ isOpen, session, bot, - animationLevel, onClose, }) => { const { terminateWebAuthorization } = getActions(); @@ -80,8 +77,6 @@ const SettingsActiveWebsite: FC = ({ className={styles.avatar} user={renderingBot} size="large" - animationLevel={animationLevel} - withVideo /> {renderingBot && }
@@ -112,6 +107,5 @@ export default memo(withGlobal((global, { hash }): StateProps => { return { session, bot, - animationLevel: global.settings.byKey.animationLevel, }; })(SettingsActiveWebsite)); diff --git a/src/components/left/settings/SettingsActiveWebsites.tsx b/src/components/left/settings/SettingsActiveWebsites.tsx index e45ac0a53..60a513f31 100644 --- a/src/components/left/settings/SettingsActiveWebsites.tsx +++ b/src/components/left/settings/SettingsActiveWebsites.tsx @@ -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; orderedHashes: string[]; - animationLevel: AnimationLevel; }; const SettingsActiveWebsites: FC = ({ isActive, byHash, orderedHashes, - animationLevel, onReset, }) => { const { @@ -113,7 +110,7 @@ const SettingsActiveWebsites: FC = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => handleOpenSessionModal(session.hash)} > - +
{formatPastTimeShort(lang, session.dateActive * 1000)} {bot && } @@ -164,7 +161,6 @@ export default memo(withGlobal( return { byHash, orderedHashes, - animationLevel: global.settings.byKey.animationLevel, }; }, )(SettingsActiveWebsites)); diff --git a/src/components/left/settings/SettingsCustomEmoji.tsx b/src/components/left/settings/SettingsCustomEmoji.tsx index 5bb81ffc3..f8d233103 100644 --- a/src/components/left/settings/SettingsCustomEmoji.tsx +++ b/src/components/left/settings/SettingsCustomEmoji.tsx @@ -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 & { customEmojiSetIds?: string[]; stickerSetsById: Record; + canPlayAnimatedEmojis: boolean; }; const SettingsCustomEmoji: FC = ({ @@ -34,6 +36,7 @@ const SettingsCustomEmoji: FC = ({ customEmojiSetIds, stickerSetsById, shouldSuggestCustomEmoji, + canPlayAnimatedEmojis, onReset, }) => { const { openStickerSet, setSettingOption } = getActions(); @@ -78,6 +81,7 @@ const SettingsCustomEmoji: FC = ({ stickerSet={stickerSet} observeIntersection={observeIntersectionForCovers} onClick={handleStickerSetClick} + noPlay={!canPlayAnimatedEmojis} /> ))}
@@ -91,13 +95,14 @@ const SettingsCustomEmoji: FC = ({ }; export default memo(withGlobal( - (global) => { + (global): StateProps => { return { ...pick(global.settings.byKey, [ 'shouldSuggestCustomEmoji', ]), customEmojiSetIds: global.customEmojis.added.setIds, stickerSetsById: global.stickers.setsById, + canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), }; }, )(SettingsCustomEmoji)); diff --git a/src/components/left/settings/SettingsDataStorage.tsx b/src/components/left/settings/SettingsDataStorage.tsx index 547c946f7..cef47b418 100644 --- a/src/components/left/settings/SettingsDataStorage.tsx +++ b/src/components/left/settings/SettingsDataStorage.tsx @@ -30,8 +30,6 @@ type StateProps = Pick; @@ -50,8 +48,6 @@ const SettingsDataStorage: FC = ({ canAutoLoadFileInPrivateChats, canAutoLoadFileInGroups, canAutoLoadFileInChannels, - canAutoPlayGifs, - canAutoPlayVideos, autoLoadFileMaxSizeMb, }) => { const { setSettingOption } = getActions(); @@ -71,14 +67,6 @@ const SettingsDataStorage: FC = ({ 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 = ({ canAutoLoadFileInGroups, canAutoLoadFileInChannels, )} - -
-

{lang('AutoplayMedia')}

- - - -
); }; @@ -199,8 +172,6 @@ export default memo(withGlobal( 'canAutoLoadFileInPrivateChats', 'canAutoLoadFileInGroups', 'canAutoLoadFileInChannels', - 'canAutoPlayGifs', - 'canAutoPlayVideos', 'autoLoadFileMaxSizeMb', ]); }, diff --git a/src/components/left/settings/SettingsGeneral.tsx b/src/components/left/settings/SettingsGeneral.tsx index 0ae62993b..52f9b7ac7 100644 --- a/src/components/left/settings/SettingsGeneral.tsx +++ b/src/components/left/settings/SettingsGeneral.tsx @@ -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 = ({ onScreenSelect, onReset, messageTextSize, - animationLevel, messageSendKeyCombo, timeFormat, theme, @@ -88,14 +81,6 @@ const SettingsGeneral: FC = ({ }, ] : 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 = ({ />
-
-

- Animation Level -

-

- Choose the desired animations amount. -

- - -
- {KEYBOARD_SEND_OPTIONS && (

{lang('VoiceOver.Keyboard')}

diff --git a/src/components/left/settings/SettingsHeader.tsx b/src/components/left/settings/SettingsHeader.tsx index 676edc5ad..be68f0818 100644 --- a/src/components/left/settings/SettingsHeader.tsx +++ b/src/components/left/settings/SettingsHeader.tsx @@ -145,6 +145,9 @@ const SettingsHeader: FC = ({ case SettingsScreens.PrivacyPhoneP2PDeniedContacts: return

{lang('NeverShareWith')}

; + case SettingsScreens.Performance: + return

{lang('Animations and Performance')}

; + case SettingsScreens.ActiveSessions: return

{lang('SessionsTitle')}

; case SettingsScreens.ActiveWebsites: diff --git a/src/components/left/settings/SettingsMain.tsx b/src/components/left/settings/SettingsMain.tsx index 0879ad46a..9e4ee4083 100644 --- a/src/components/left/settings/SettingsMain.tsx +++ b/src/components/left/settings/SettingsMain.tsx @@ -85,6 +85,13 @@ const SettingsMain: FC = ({ > {lang('Telegram.GeneralSettingsViewController')} + onScreenSelect(SettingsScreens.Performance)} + > + {lang('Animations and Performance')} + 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>({}); + + const sectionCheckedStates = useMemo(() => { + return PERFORMANCE_OPTIONS.reduce((acc, [, options], index) => { + acc[index] = options.every(({ key }) => performanceSettings[key]); + + return acc; + }, {} as Record); + }, [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) => { + 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); + + updatePerformanceSettings(newSettings); + }, []); + + const handlePropertyChange = useCallback((e: React.ChangeEvent) => { + const { name, checked } = e.target; + + updatePerformanceSettings({ [name as PerformanceTypeKey]: checked }); + }, []); + + return ( +
+
+

+ Animation Level +

+

+ Choose the desired animations amount. +

+ + +
+ +
+

Resource-Intensive Processes

+ + {PERFORMANCE_OPTIONS.map(([sectionName, options], index) => { + return ( +
+
+ +
+ {Boolean(sectionExpandedStates[index]) && ( +
+ {options.map(({ key, label, disabled }) => ( + + ))} +
+ )} +
+ ); + })} +
+
+ ); +} + +export default memo(withGlobal((global): StateProps => { + return { + performanceSettings: selectPerformanceSettings(global), + }; +})(SettingsPerformance)); diff --git a/src/components/left/settings/SettingsStickers.tsx b/src/components/left/settings/SettingsStickers.tsx index 28a73d14f..7f25e30bf 100644 --- a/src/components/left/settings/SettingsStickers.tsx +++ b/src/components/left/settings/SettingsStickers.tsx @@ -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 & { addedSetIds?: string[]; customEmojiSetIds?: string[]; stickerSetsById: Record; defaultReaction?: ApiReaction; availableReactions?: ApiAvailableReaction[]; + canPlayAnimatedEmojis: boolean; }; const SettingsStickers: FC = ({ @@ -53,8 +54,8 @@ const SettingsStickers: FC = ({ stickerSetsById, defaultReaction, shouldSuggestStickers, - shouldLoopStickers, availableReactions, + canPlayAnimatedEmojis, onReset, onScreenSelect, }) => { @@ -78,10 +79,6 @@ const SettingsStickers: FC = ({ 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 = ({ checked={shouldSuggestStickers} onCheck={handleSuggestStickersChange} /> - = ({ stickerSet={stickerSet} observeIntersection={observeIntersectionForCovers} onClick={handleStickerSetClick} + noPlay={!canPlayAnimatedEmojis} /> ))}
@@ -158,13 +151,13 @@ export default memo(withGlobal( 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)); diff --git a/src/components/main/Dialogs.tsx b/src/components/main/Dialogs.tsx index f056aa466..47c48958e 100644 --- a/src/components/main/Dialogs.tsx +++ b/src/components/main/Dialogs.tsx @@ -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 = ({ dialogs, animationLevel }) => { +const Dialogs: FC = ({ dialogs }) => { const { dismissDialog, acceptInviteConfirmation, @@ -47,7 +45,7 @@ const Dialogs: FC = ({ dialogs, animationLevel }) => { function renderInviteHeader(title: string, photo?: ApiPhoto) { return (
- {photo && } + {photo && }
{renderText(title)}
@@ -196,7 +194,6 @@ export default memo(withGlobal( (global): StateProps => { return { dialogs: selectTabState(global).dialogs, - animationLevel: global.settings.byKey.animationLevel, }; }, )(Dialogs)); diff --git a/src/components/main/Main.scss b/src/components/main/Main.scss index bcd90d15e..851ecf57c 100644 --- a/src/components/main/Main.scss +++ b/src/components/main/Main.scss @@ -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 { diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index f09fec6a1..274d85372 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -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 = ({ openedStickerSetShortName, openedCustomEmojiSetIds, isServiceChatReady, - animationLevel, + withInterfaceAnimations, language, wasTimeFormatSetManually, addedSetIds, @@ -189,6 +193,7 @@ const Main: FC = ({ isCurrentUserPremium, deleteFolderDialogId, isMasterTab, + noRightColumnAnimation, }) => { const { initMain, @@ -386,7 +391,7 @@ const Main: FC = ({ // 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 = ({ 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 = ({ return; } - if (animationLevel === 0) { + if (noRightColumnAnimation) { setIsNarrowMessageList(isRightColumnOpen); return; } @@ -434,7 +439,7 @@ const Main: FC = ({ 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( const { settings: { byKey: { - animationLevel, language, wasTimeFormatSetManually, + language, wasTimeFormatSetManually, }, }, lastSyncTime, @@ -574,6 +579,8 @@ export default memo(withGlobal( 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( 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( deleteFolderDialogId: deleteFolderDialogModal, isMasterTab, requestedDraft, + noRightColumnAnimation, }; }, )(Main)); diff --git a/src/components/main/premium/GiftPremiumModal.tsx b/src/components/main/premium/GiftPremiumModal.tsx index bf0144301..4bd47f044 100644 --- a/src/components/main/premium/GiftPremiumModal.tsx +++ b/src/components/main/premium/GiftPremiumModal.tsx @@ -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 = ({ @@ -45,7 +43,6 @@ const GiftPremiumModal: FC = ({ gifts, monthlyCurrency, monthlyAmount, - animationLevel, }) => { const { openPremiumModal, closeGiftPremiumModal, openUrl } = getActions(); @@ -131,8 +128,6 @@ const GiftPremiumModal: FC = ({ user={renderedUser} size="jumbo" className={styles.avatar} - animationLevel={animationLevel} - withVideo />

{lang('GiftTelegramPremiumTitle')} @@ -179,6 +174,5 @@ export default memo(withGlobal((global): StateProps => { gifts, monthlyCurrency, monthlyAmount: monthlyAmount ? Number(monthlyAmount) : undefined, - animationLevel: global.settings.byKey.animationLevel, }; })(GiftPremiumModal)); diff --git a/src/components/main/premium/PremiumMainModal.tsx b/src/components/main/premium/PremiumMainModal.tsx index b3870fbdc..28e4e6d97 100644 --- a/src/components/main/premium/PremiumMainModal.tsx +++ b/src/components/main/premium/PremiumMainModal.tsx @@ -219,7 +219,10 @@ const PremiumMainModal: FC = ({ return (
- {renderTextWithEntities(promo.statusText, promo.statusEntities)} + {renderTextWithEntities({ + text: promo.statusText, + entities: promo.statusEntities, + })}
); } diff --git a/src/components/mediaViewer/MediaViewer.scss b/src/components/mediaViewer/MediaViewer.scss index d6daad2c4..549090877 100644 --- a/src/components/mediaViewer/MediaViewer.scss +++ b/src/components/mediaViewer/MediaViewer.scss @@ -35,7 +35,7 @@ } } - body.animation-level-2 & { + body:not(.no-media-viewer-animations) & { transition-duration: 0.3s !important; } diff --git a/src/components/mediaViewer/MediaViewer.tsx b/src/components/mediaViewer/MediaViewer.tsx index 30e2f0a9a..4650afa57 100644 --- a/src/components/mediaViewer/MediaViewer.tsx +++ b/src/components/mediaViewer/MediaViewer.tsx @@ -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; collectionIds?: number[]; isHidden?: boolean; - animationLevel: AnimationLevel; + withAnimation?: boolean; shouldSkipHistoryAnimations?: boolean; }; @@ -87,7 +87,7 @@ const MediaViewer: FC = ({ message, chatMessages, collectionIds, - animationLevel, + withAnimation, isHidden, shouldSkipHistoryAnimations, }) => { @@ -105,8 +105,8 @@ const MediaViewer: FC = ({ /* Animation */ const animationKey = useRef(); const prevSenderId = usePrevious(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 = ({ 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 | undefined; @@ -494,7 +492,7 @@ export default memo(withGlobal( message, chatMessages, collectionIds, - animationLevel, + withAnimation, isHidden, shouldSkipHistoryAnimations, }; diff --git a/src/components/mediaViewer/MediaViewerContent.tsx b/src/components/mediaViewer/MediaViewerContent.tsx index 289589c28..c291a9a80 100644 --- a/src/components/mediaViewer/MediaViewerContent.tsx +++ b/src/components/mediaViewer/MediaViewerContent.tsx @@ -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 = (props) => { chatId, message, origin, - animationLevel, + withAnimation, isProtected, volume, playbackRate, @@ -82,8 +81,6 @@ const MediaViewerContent: FC = (props) => { const lang = useLang(); - const isGhostAnimation = animationLevel === 2; - const { isVideo, isPhoto, @@ -97,7 +94,7 @@ const MediaViewerContent: FC = (props) => { videoSize, loadProgress, } = useMediaProps({ - message, avatarOwner, mediaId, origin, delay: isGhostAnimation && ANIMATION_DURATION, + message, avatarOwner, mediaId, origin, delay: withAnimation ? ANIMATION_DURATION : false, }); const [, toggleControls] = useControlsSignal(); diff --git a/src/components/mediaViewer/MediaViewerSlides.tsx b/src/components/mediaViewer/MediaViewerSlides.tsx index 697b85dbb..6e087163f 100644 --- a/src/components/mediaViewer/MediaViewerSlides.tsx +++ b/src/components/mediaViewer/MediaViewerSlides.tsx @@ -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 = ({ isPhoto, isOpen, hasFooter, - animationLevel, + withAnimation, isHidden, ...rest }) => { @@ -196,7 +196,7 @@ const MediaViewerSlides: FC = ({ 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 = ({ selectMediaDebounced, setIsActiveDebounced, clearSwipeDirectionDebounced, - animationLevel, + withAnimation, setIsMouseDown, setIsActive, isHidden, @@ -703,7 +703,7 @@ const MediaViewerSlides: FC = ({ @@ -722,7 +722,7 @@ const MediaViewerSlides: FC = ({ /* 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 = ({ diff --git a/src/components/mediaViewer/SenderInfo.tsx b/src/components/mediaViewer/SenderInfo.tsx index b2fc17cd8..568dc9890 100644 --- a/src/components/mediaViewer/SenderInfo.tsx +++ b/src/components/mediaViewer/SenderInfo.tsx @@ -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 = ({ isFallbackAvatar, isAvatar, message, - animationLevel, }) => { const { closeMediaViewer, @@ -79,9 +76,9 @@ const SenderInfo: FC = ({ return (
{isUserId(sender.id) ? ( - + ) : ( - + )}
@@ -99,16 +96,14 @@ const SenderInfo: FC = ({ export default withGlobal( (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( return { message, sender: message && selectSender(global, message), - animationLevel, }; }, )(SenderInfo); diff --git a/src/components/mediaViewer/VideoPlayer.scss b/src/components/mediaViewer/VideoPlayer.scss index 67bebfad1..95aaec272 100644 --- a/src/components/mediaViewer/VideoPlayer.scss +++ b/src/components/mediaViewer/VideoPlayer.scss @@ -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; } diff --git a/src/components/middle/ActionMessage.tsx b/src/components/middle/ActionMessage.tsx index df9f91ee4..1333b8da1 100644 --- a/src/components/middle/ActionMessage.tsx +++ b/src/components/middle/ActionMessage.tsx @@ -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; 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 = ({ appearanceOrder = 0, isJustAdded, isLastInList, - usersById, senderUser, senderChat, targetUserIds, @@ -87,6 +87,7 @@ const ActionMessage: FC = ({ isInsideTopic, topic, memoFirstUnreadIdRef, + canPlayAnimatedEmojis, observeIntersectionForReading, observeIntersectionForLoading, observeIntersectionForPlaying, @@ -140,6 +141,8 @@ const ActionMessage: FC = ({ 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 = ({ @@ -256,7 +259,6 @@ export default memo(withGlobal( 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( const topic = selectTopicFromMessage(global, message); return { - usersById, senderUser, senderChat, targetChatId, @@ -287,6 +288,7 @@ export default memo(withGlobal( isFocused, premiumGiftSticker, topic, + canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), ...(isFocused && { focusDirection, noFocusHighlight, diff --git a/src/components/middle/ActionMessageSuggestedAvatar.tsx b/src/components/middle/ActionMessageSuggestedAvatar.tsx index 1c409b981..c26e64b53 100644 --- a/src/components/middle/ActionMessageSuggestedAvatar.tsx +++ b/src/components/middle/ActionMessageSuggestedAvatar.tsx @@ -98,7 +98,6 @@ const ActionMessageSuggestedAvatar: FC = ({ ( 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, diff --git a/src/components/middle/HeaderPinnedMessage.module.scss b/src/components/middle/HeaderPinnedMessage.module.scss index 13d301ee9..910e10c85 100644 --- a/src/components/middle/HeaderPinnedMessage.module.scss +++ b/src/components/middle/HeaderPinnedMessage.module.scss @@ -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; } } diff --git a/src/components/middle/MessageList.scss b/src/components/middle/MessageList.scss index 32d0c2495..e9c78e903 100644 --- a/src/components/middle/MessageList.scss +++ b/src/components/middle/MessageList.scss @@ -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; } diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index ada7894e8..78c447267 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -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 = ({ withBottomShift, withDefaultBg, topic, + noMessageSendingAnimation, onPinnedIntersectionChange, getForceNextPinnedInHeader, }) => { @@ -464,6 +466,9 @@ const MessageList: FC = ({ lastItemElement!, 'end', BOTTOM_FOCUS_MARGIN, + undefined, + undefined, + noMessageSendingAnimation ? 0 : undefined, ); }); } @@ -516,7 +521,7 @@ const MessageList: FC = ({ }; }); // 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( hasLinkedChat: Boolean(chatFullInfo?.linkedChatId), lastSyncTime: global.lastSyncTime, topic, + noMessageSendingAnimation: !selectPerformanceSettingsValue(global, 'messageSendingAnimations'), ...(withLastMessageWhenPreloading && { lastMessage }), }; }, diff --git a/src/components/middle/MessageSelectToolbar.scss b/src/components/middle/MessageSelectToolbar.scss index c921369ae..6d5b83f44 100644 --- a/src/components/middle/MessageSelectToolbar.scss +++ b/src/components/middle/MessageSelectToolbar.scss @@ -46,7 +46,7 @@ top: auto; } - body.animation-level-0 & { + body.no-page-transitions & { transition: none !important; } diff --git a/src/components/middle/MiddleColumn.module.scss b/src/components/middle/MiddleColumn.module.scss index 9d016c49f..dfa36b139 100644 --- a/src/components/middle/MiddleColumn.module.scss +++ b/src/components/middle/MiddleColumn.module.scss @@ -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); } diff --git a/src/components/middle/MiddleColumn.scss b/src/components/middle/MiddleColumn.scss index bb5c682ff..ee92f2a5e 100644 --- a/src/components/middle/MiddleColumn.scss +++ b/src/components/middle/MiddleColumn.scss @@ -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)); diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index a42c42226..67dcecafe 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -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 = ({ isReactorListModalOpen, isGiftPremiumModalOpen, isMessageLanguageModalOpen, - animationLevel, + withInterfaceAnimations, shouldSkipHistoryAnimations, currentTransitionKey, isChannel, @@ -257,7 +256,7 @@ const MiddleColumn: FC = ({ ); const { isReady, handleCssTransitionEnd, handleSlideTransitionStop } = useIsReady( - !shouldSkipHistoryAnimations && animationLevel !== ANIMATION_LEVEL_MIN, + !shouldSkipHistoryAnimations && withInterfaceAnimations, currentTransitionKey, prevTransitionKey, chatId, @@ -473,7 +472,7 @@ const MiddleColumn: FC = ({ onFocusPinnedMessage={onFocusPinnedMessage} /> ( 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, diff --git a/src/components/middle/MiddleHeader.scss b/src/components/middle/MiddleHeader.scss index 7ea52246f..2a196f26c 100644 --- a/src/components/middle/MiddleHeader.scss +++ b/src/components/middle/MiddleHeader.scss @@ -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; } } diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index 4430a3983..7fbe81719 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -361,7 +361,6 @@ const MiddleHeader: FC = ({ withFullInfo withMediaViewer withUpdatingStatus - withVideoAvatar={isReady} emojiStatusSize={EMOJI_STATUS_SIZE} noRtl /> @@ -376,7 +375,6 @@ const MiddleHeader: FC = ({ withMediaViewer={threadId === MAIN_THREAD_ID} withFullInfo={threadId === MAIN_THREAD_ID} withUpdatingStatus - withVideoAvatar={isReady} noRtl /> )} diff --git a/src/components/middle/NoMessages.tsx b/src/components/middle/NoMessages.tsx index 0b4e864c3..41dbca76b 100644 --- a/src/components/middle/NoMessages.tsx +++ b/src/components/middle/NoMessages.tsx @@ -56,7 +56,11 @@ function renderTopic(lang: LangFn, topic: ApiTopic) { return (
- +

{lang('Chat.EmptyTopicPlaceholder.Title')}

{renderText(lang('Chat.EmptyTopicPlaceholder.Text'), ['br'])}

diff --git a/src/components/middle/ReactorListModal.tsx b/src/components/middle/ReactorListModal.tsx index 9da1daab1..c1eaec065 100644 --- a/src/components/middle/ReactorListModal.tsx +++ b/src/components/middle/ReactorListModal.tsx @@ -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 = ({ @@ -50,7 +51,6 @@ const ReactorListModal: FC = ({ messageId, seenByUserIds, availableReactions, - animationLevel, }) => { const { loadReactors, @@ -194,7 +194,7 @@ const ReactorListModal: FC = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => handleClick(userId)} > - + {r.reaction && ( ( reactors: message?.reactors, seenByUserIds: message?.seenByUserIds, availableReactions: global.availableReactions, - animationLevel: global.settings.byKey.animationLevel, }; }, )(ReactorListModal)); diff --git a/src/components/middle/ScrollDownButton.module.scss b/src/components/middle/ScrollDownButton.module.scss index bd877f1b7..b395fcaa6 100644 --- a/src/components/middle/ScrollDownButton.module.scss +++ b/src/components/middle/ScrollDownButton.module.scss @@ -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))); } diff --git a/src/components/middle/composer/AttachmentModalItem.module.scss b/src/components/middle/composer/AttachmentModalItem.module.scss index d939f633b..02f777b41 100644 --- a/src/components/middle/composer/AttachmentModalItem.module.scss +++ b/src/components/middle/composer/AttachmentModalItem.module.scss @@ -58,6 +58,11 @@ overflow: hidden; backdrop-filter: blur(10px); + + :global(body.no-menu-blur) & { + background-color: #707579; + backdrop-filter: none; + } } .action-item { diff --git a/src/components/middle/composer/Composer.scss b/src/components/middle/composer/Composer.scss index 7947715f0..18ea9966d 100644 --- a/src/components/middle/composer/Composer.scss +++ b/src/components/middle/composer/Composer.scss @@ -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; } diff --git a/src/components/middle/composer/ComposerEmbeddedMessage.scss b/src/components/middle/composer/ComposerEmbeddedMessage.scss index 78e936ffe..e9687417f 100644 --- a/src/components/middle/composer/ComposerEmbeddedMessage.scss +++ b/src/components/middle/composer/ComposerEmbeddedMessage.scss @@ -11,7 +11,7 @@ height: 0 !important; } - body.animation-level-0 & { + body.no-message-composer-animations & { transition: none !important; } diff --git a/src/components/middle/composer/ComposerEmbeddedMessage.tsx b/src/components/middle/composer/ComposerEmbeddedMessage.tsx index 9614940e3..1f2a6c7e2 100644 --- a/src/components/middle/composer/ComposerEmbeddedMessage.tsx +++ b/src/components/middle/composer/ComposerEmbeddedMessage.tsx @@ -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( 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)!); diff --git a/src/components/middle/composer/CustomEmojiTooltip.tsx b/src/components/middle/composer/CustomEmojiTooltip.tsx index 3afd258af..355ef56ce 100644 --- a/src/components/middle/composer/CustomEmojiTooltip.tsx +++ b/src/components/middle/composer/CustomEmojiTooltip.tsx @@ -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 = ({ customEmoji, isSavedMessages, isCurrentUserPremium, + noPlay, }) => { const { clearCustomEmojiForEmoji } = getActions(); @@ -97,6 +99,7 @@ const CustomEmojiTooltip: FC = ({ isSavedMessages={isSavedMessages} canViewSet isCurrentUserPremium={isCurrentUserPremium} + noPlay={noPlay} /> )) ) : shouldRender ? ( diff --git a/src/components/middle/composer/GifPicker.scss b/src/components/middle/composer/GifPicker.scss index af3a14fb6..8b3f0719c 100644 --- a/src/components/middle/composer/GifPicker.scss +++ b/src/components/middle/composer/GifPicker.scss @@ -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 & { diff --git a/src/components/middle/composer/MessageInput.tsx b/src/components/middle/composer/MessageInput.tsx index dd63a17cf..11d019b7a 100644 --- a/src/components/middle/composer/MessageInput.tsx +++ b/src/components/middle/composer/MessageInput.tsx @@ -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 = ({ shouldSuppressTextFormatter, replyingToId, isSelectModeActive, + canPlayAnimatedEmojis, messageSendKeyCombo, onUpdate, onSuppressedFocus, @@ -157,6 +159,7 @@ const MessageInput: FC = ({ sharedCanvasHqRef, absoluteContainerRef, isAttachmentModalInput ? 'attachment' : 'composer', + canPlayAnimatedEmojis, isActive, ); @@ -590,6 +593,7 @@ export default memo(withGlobal( messageSendKeyCombo, replyingToId: chatId && threadId ? selectReplyingToId(global, chatId, threadId) : undefined, isSelectModeActive: selectIsInSelectMode(global), + canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), }; }, )(MessageInput)); diff --git a/src/components/middle/composer/StickerPicker.tsx b/src/components/middle/composer/StickerPicker.tsx index 69a16e141..2c68e3aea 100644 --- a/src/components/middle/composer/StickerPicker.tsx +++ b/src/components/middle/composer/StickerPicker.tsx @@ -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 = ({ ) : ( @@ -300,7 +300,7 @@ const StickerPicker: FC = ({ 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( premiumStickers: premiumSet.stickers, stickerSetsById: setsById, addedSetIds: added.setIds, - canAnimate: global.settings.byKey.shouldLoopStickers, + canAnimate: selectShouldLoopStickers(global), isSavedMessages, isCurrentUserPremium: selectIsCurrentUserPremium(global), chatStickerSetId, diff --git a/src/components/middle/composer/StickerSetCover.tsx b/src/components/middle/composer/StickerSetCover.tsx index ebe200528..9e9847691 100644 --- a/src/components/middle/composer/StickerSetCover.tsx +++ b/src/components/middle/composer/StickerSetCover.tsx @@ -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; }; @@ -33,7 +33,7 @@ type OwnProps = { const StickerSetCover: FC = ({ stickerSet, size = STICKER_SIZE_PICKER_HEADER, - noAnimate, + noPlay, observeIntersection, sharedCanvasRef, }) => { @@ -44,6 +44,7 @@ const StickerSetCover: FC = ({ 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 = ({ 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 = ({ diff --git a/src/components/middle/composer/SymbolMenu.scss b/src/components/middle/composer/SymbolMenu.scss index 2534c2691..232710325 100644 --- a/src/components/middle/composer/SymbolMenu.scss +++ b/src/components/middle/composer/SymbolMenu.scss @@ -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; } diff --git a/src/components/middle/composer/SymbolMenu.tsx b/src/components/middle/composer/SymbolMenu.tsx index bebb33559..bcfdcf9a6 100644 --- a/src/components/middle/composer/SymbolMenu.tsx +++ b/src/components/middle/composer/SymbolMenu.tsx @@ -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 = ({ transformOriginX, transformOriginY, style, + isBackgroundTranslucent, }) => { const { loadPremiumSetStickers } = getActions(); const [activeTab, setActiveTab] = useState(0); @@ -219,7 +221,7 @@ const SymbolMenu: FC = ({ isHidden={!isOpen || !isActive} loadAndPlay={isOpen && (isActive || isFrom)} chatId={chatId} - isTranslucent={!isMobile} + isTranslucent={!isMobile && isBackgroundTranslucent} onCustomEmojiSelect={handleCustomEmojiSelect} /> ); @@ -232,7 +234,7 @@ const SymbolMenu: FC = ({ canSendStickers={canSendStickers} chatId={chatId} threadId={threadId} - isTranslucent={!isMobile} + isTranslucent={!isMobile && isBackgroundTranslucent} onStickerSelect={handleStickerSelect} /> ); @@ -348,6 +350,7 @@ export default memo(withGlobal( isLeftColumnShown: selectTabState(global).isLeftColumnShown, isCurrentUserPremium: selectIsCurrentUserPremium(global), lastSyncTime: global.lastSyncTime, + isBackgroundTranslucent: selectIsContextMenuTranslucent(global), }; }, )(SymbolMenu)); diff --git a/src/components/middle/composer/WebPagePreview.scss b/src/components/middle/composer/WebPagePreview.scss index 9edf5647e..3c355f4a9 100644 --- a/src/components/middle/composer/WebPagePreview.scss +++ b/src/components/middle/composer/WebPagePreview.scss @@ -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 { diff --git a/src/components/middle/composer/hooks/useInputCustomEmojis.ts b/src/components/middle/composer/hooks/useInputCustomEmojis.ts index e8c35d278..5cd2f6c34 100644 --- a/src/components/middle/composer/hooks/useInputCustomEmojis.ts +++ b/src/components/middle/composer/hooks/useInputCustomEmojis.ts @@ -41,6 +41,7 @@ export default function useInputCustomEmojis( sharedCanvasHqRef: React.RefObject, absoluteContainerRef: React.RefObject, 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); diff --git a/src/components/middle/message/AnimatedCustomEmoji.tsx b/src/components/middle/message/AnimatedCustomEmoji.tsx index 130b55487..606fd661d 100644 --- a/src/components/middle/message/AnimatedCustomEmoji.tsx +++ b/src/components/middle/message/AnimatedCustomEmoji.tsx @@ -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 = ({ @@ -46,6 +48,7 @@ const AnimatedCustomEmoji: FC = ({ sticker, effect, soundId, + noPlay, observeIntersection, }) => { const { @@ -65,6 +68,7 @@ const AnimatedCustomEmoji: FC = ({ style={style} size={size} isBig + noPlay={noPlay} withSharedAnimation forceOnHeavyAnimation observeIntersectionForLoading={observeIntersection} @@ -75,9 +79,11 @@ const AnimatedCustomEmoji: FC = ({ export default memo(withGlobal((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)); diff --git a/src/components/middle/message/AnimatedEmoji.tsx b/src/components/middle/message/AnimatedEmoji.tsx index b3e56309f..d60865cdb 100644 --- a/src/components/middle/message/AnimatedEmoji.tsx +++ b/src/components/middle/message/AnimatedEmoji.tsx @@ -22,7 +22,7 @@ import './AnimatedEmoji.scss'; type OwnProps = { emoji: string; - withEffects: boolean; + withEffects?: boolean; isOwn?: boolean; observeIntersection?: ObserveFn; lastSyncTime?: number; diff --git a/src/components/middle/message/CommentButton.scss b/src/components/middle/message/CommentButton.scss index c10cc7199..4c02f595b 100644 --- a/src/components/middle/message/CommentButton.scss +++ b/src/components/middle/message/CommentButton.scss @@ -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 & { diff --git a/src/components/middle/message/Contact.tsx b/src/components/middle/message/Contact.tsx index 7d988fa33..7ffa71655 100644 --- a/src/components/middle/message/Contact.tsx +++ b/src/components/middle/message/Contact.tsx @@ -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 = ({ - contact, user, phoneCodeList, animationLevel, + contact, user, phoneCodeList, }) => { const { openChat } = getActions(); @@ -51,8 +49,6 @@ const Contact: FC = ({ size="large" user={user} text={firstName || lastName} - animationLevel={animationLevel} - withVideo />
{firstName} {lastName}
@@ -70,7 +66,6 @@ export default withGlobal( return { user, phoneCodeList, - animationLevel: global.settings.byKey.animationLevel, }; }, )(Contact); diff --git a/src/components/middle/message/ContextMenuContainer.tsx b/src/components/middle/message/ContextMenuContainer.tsx index aea063648..b8d6cdf5d 100644 --- a/src/components/middle/message/ContextMenuContainer.tsx +++ b/src/components/middle/message/ContextMenuContainer.tsx @@ -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 = ({ @@ -147,6 +149,7 @@ const ContextMenuContainer: FC = ({ canSaveGif, canRevote, canClosePoll, + canPlayAnimatedEmojis, activeDownloads, noReplies, canShowSeenBy, @@ -495,6 +498,7 @@ const ContextMenuContainer: FC = ({ canTranslate={canTranslate} canShowOriginal={canShowOriginal} canSelectLanguage={canSelectLanguage} + canPlayAnimatedEmojis={canPlayAnimatedEmojis} hasCustomEmoji={hasCustomEmoji} customEmojiSets={customEmojiSets} isDownloading={isDownloading} @@ -665,6 +669,7 @@ export default memo(withGlobal( canTranslate, canShowOriginal: hasTranslation, canSelectLanguage: hasTranslation, + canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), }; }, )(ContextMenuContainer)); diff --git a/src/components/middle/message/Message.scss b/src/components/middle/message/Message.scss index 7173ea941..f51fa5a25 100644 --- a/src/components/middle/message/Message.scss +++ b/src/components/middle/message/Message.scss @@ -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; } } diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index c96db8cfd..338f7f650 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -25,9 +25,7 @@ import type { ApiReaction, ApiStickerSet, } from '../../../api/types'; -import type { - AnimationLevel, FocusDirection, IAlbum, ISettings, -} from '../../../types'; +import type { FocusDirection, IAlbum, ISettings } from '../../../types'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { PinnedIntersectionChangedCallback } from '../hooks/usePinnedMessage'; import { AudioOrigin } from '../../../types'; @@ -69,6 +67,7 @@ import { selectChatTranslations, selectRequestedTranslationLanguage, selectChatFullInfo, + selectPerformanceSettingsValue, } from '../../../global/selectors'; import { getMessageContent, @@ -248,13 +247,14 @@ type StateProps = { transcribedText?: string; isTranscriptionError?: boolean; isPremium: boolean; - animationLevel: AnimationLevel; senderAdminMember?: ApiChatMember; messageTopic?: ApiTopic; hasTopicChip?: boolean; chatTranslations?: ChatTranslatedMessages; areTranslationsEnabled?: boolean; requestedTranslationLanguage?: string; + withReactionEffects?: boolean; + withStickerEffects?: boolean; }; type MetaPosition = @@ -354,13 +354,14 @@ const Message: FC = ({ repliesThreadInfo, hasUnreadReaction, memoFirstUnreadIdRef, - animationLevel, senderAdminMember, messageTopic, hasTopicChip, chatTranslations, areTranslationsEnabled, requestedTranslationLanguage, + withReactionEffects, + withStickerEffects, onPinnedIntersectionChange, }) => { const { @@ -785,9 +786,6 @@ const Message: FC = ({ text={hiddenName} lastSyncTime={lastSyncTime} onClick={(avatarUser || avatarChat) ? handleAvatarClick : undefined} - observeIntersection={observeIntersectionForLoading} - animationLevel={animationLevel} - withVideo /> ); } @@ -861,6 +859,7 @@ const Message: FC = ({ genericEffects={genericEffects} observeIntersection={observeIntersectionForPlaying} noRecentReactors={isChannel} + withEffects={withReactionEffects} /> ); } @@ -917,6 +916,7 @@ const Message: FC = ({ memoFirstUnreadIdRef.current && messageId >= memoFirstUnreadIdRef.current ) || isLocal) ) || undefined} + withEffect={withStickerEffects} onPlayEffect={startStickerEffect} onStopEffect={stopStickerEffect} /> @@ -924,7 +924,7 @@ const Message: FC = ({ {hasAnimatedEmoji && animatedCustomEmoji && ( = ({ {hasAnimatedEmoji && animatedEmoji && ( = ({ genericEffects={genericEffects} observeIntersection={observeIntersectionForPlaying} noRecentReactors={isChannel} + withEffects={withReactionEffects} /> )}
@@ -1473,7 +1474,6 @@ export default memo(withGlobal( isTranscribing: transcriptionId !== undefined && global.transcriptions[transcriptionId]?.isPending, transcribedText: transcriptionId !== undefined ? global.transcriptions[transcriptionId]?.text : undefined, isPremium: selectIsCurrentUserPremium(global), - animationLevel: global.settings.byKey.animationLevel, senderAdminMember, messageTopic, genericEffects: global.genericEmojiEffects, @@ -1482,6 +1482,8 @@ export default memo(withGlobal( areTranslationsEnabled: global.settings.byKey.canTranslate, requestedTranslationLanguage, hasLinkedChat: Boolean(chatFullInfo?.linkedChatId), + withReactionEffects: selectPerformanceSettingsValue(global, 'reactionEffects'), + withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'), ...((canShowSender || isLocation) && { sender }), ...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }), ...(typeof uploadProgress === 'number' && { uploadProgress }), diff --git a/src/components/middle/message/MessageContextMenu.scss b/src/components/middle/message/MessageContextMenu.scss index 6dbb08372..e2b5b044b 100644 --- a/src/components/middle/message/MessageContextMenu.scss +++ b/src/components/middle/message/MessageContextMenu.scss @@ -24,8 +24,8 @@ } &.with-reactions .bubble { - background: none; - backdrop-filter: none; + background: none !important; + backdrop-filter: none !important; box-shadow: none; padding: 3.5rem 0 0 !important; } @@ -36,6 +36,11 @@ box-shadow: 0 0.25rem 0.5rem 0.125rem var(--color-default-shadow); border-radius: var(--border-radius-default); padding: 0.25rem 0; + + body.no-menu-blur & { + background: var(--color-background); + backdrop-filter: none; + } } .backdrop { diff --git a/src/components/middle/message/MessageContextMenu.tsx b/src/components/middle/message/MessageContextMenu.tsx index fb758ece0..ee4359d90 100644 --- a/src/components/middle/message/MessageContextMenu.tsx +++ b/src/components/middle/message/MessageContextMenu.tsx @@ -78,6 +78,7 @@ type OwnProps = { noReplies?: boolean; hasCustomEmoji?: boolean; customEmojiSets?: ApiStickerSet[]; + canPlayAnimatedEmojis?: boolean; noTransition?: boolean; onReply?: NoneToVoidFunction; onOpenThread?: VoidFunction; @@ -159,6 +160,7 @@ const MessageContextMenu: FC = ({ seenByRecentUsers, hasCustomEmoji, customEmojiSets, + canPlayAnimatedEmojis, noTransition, onReply, onOpenThread, @@ -326,6 +328,7 @@ const MessageContextMenu: FC = ({ isReady={isReady} canBuyPremium={canBuyPremium} isCurrentUserPremium={isCurrentUserPremium} + canPlayAnimatedEmojis={canPlayAnimatedEmojis} onShowMore={handleOpenReactionPicker} /> )} diff --git a/src/components/middle/message/Poll.tsx b/src/components/middle/message/Poll.tsx index 64ef4fb78..03a11b24c 100644 --- a/src/components/middle/message/Poll.tsx +++ b/src/components/middle/message/Poll.tsx @@ -245,7 +245,7 @@ const Poll: FC = ({ return ( isSolutionShown && poll.results.solution && ( = ({ activeReactions, availableReactions, observeIntersection, + withEffects, }) => { const { stopActiveReaction } = getActions(); @@ -55,7 +57,7 @@ const ReactionAnimatedEmoji: FC = ({ ), [availableReactions, reaction]); const centerIconId = availableReaction?.centerIcon?.id; - const customEmoji = useCustomEmoji(isCustom ? reaction.documentId : undefined); + const { customEmoji } = useCustomEmoji(isCustom ? reaction.documentId : undefined); const assignedEffectId = useMemo(() => { if (!isCustom) return availableReaction?.aroundAnimation?.id; @@ -93,7 +95,7 @@ const ReactionAnimatedEmoji: FC = ({ activeReactions?.find((active) => isSameReaction(active.reaction, reaction)) ), [activeReactions, reaction]); - const shouldPlay = Boolean(activeReaction && (isCustom || mediaDataCenterIcon) && mediaDataEffect); + const shouldPlay = Boolean(withEffects && activeReaction && (isCustom || mediaDataCenterIcon) && mediaDataEffect); const { shouldRender: shouldRenderAnimation, transitionClassNames: animationClassNames, diff --git a/src/components/middle/message/ReactionButton.tsx b/src/components/middle/message/ReactionButton.tsx index a3555adbc..4e7935b6f 100644 --- a/src/components/middle/message/ReactionButton.tsx +++ b/src/components/middle/message/ReactionButton.tsx @@ -25,6 +25,7 @@ const ReactionButton: FC<{ activeReactions?: ActiveReaction[]; availableReactions?: ApiAvailableReaction[]; withRecentReactors?: boolean; + withEffects?: boolean; genericEffects?: ApiStickerSet; observeIntersection?: ObserveFn; }> = ({ @@ -33,6 +34,7 @@ const ReactionButton: FC<{ activeReactions, availableReactions, withRecentReactors, + withEffects, genericEffects, observeIntersection, }) => { @@ -73,6 +75,7 @@ const ReactionButton: FC<{ availableReactions={availableReactions} genericEffects={genericEffects} observeIntersection={observeIntersection} + withEffects={withEffects} /> {recentReactors?.length ? (
diff --git a/src/components/middle/message/ReactionPicker.module.scss b/src/components/middle/message/ReactionPicker.module.scss index 9f3c551b8..a5fad8b78 100644 --- a/src/components/middle/message/ReactionPicker.module.scss +++ b/src/components/middle/message/ReactionPicker.module.scss @@ -10,14 +10,18 @@ } .menuContent { - --color-background: var(--color-background-compact-menu); --border-radius-default: 1.25rem; + :global(body:not(.no-menu-blur)) & { + --color-background: var(--color-background-compact-menu); + + backdrop-filter: blur(10px); + } + width: calc(var(--symbol-menu-width) + 0.25rem); // Reserve width for scrollbar height: var(--symbol-menu-height); padding: 0 !important; transform-origin: 9rem 4.625rem !important; - backdrop-filter: blur(10px); @supports (overflow: overlay) { width: var(--symbol-menu-width); diff --git a/src/components/middle/message/ReactionPicker.tsx b/src/components/middle/message/ReactionPicker.tsx index 01b58de86..cf30e644f 100644 --- a/src/components/middle/message/ReactionPicker.tsx +++ b/src/components/middle/message/ReactionPicker.tsx @@ -12,7 +12,7 @@ import type { IAnchorPosition } from '../../../types'; import buildClassName from '../../../util/buildClassName'; import { isUserId } from '../../../global/helpers'; import { - selectChat, selectChatFullInfo, selectChatMessage, selectTabState, + selectChat, selectChatFullInfo, selectChatMessage, selectIsContextMenuTranslucent, selectTabState, } from '../../../global/selectors'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; import useMenuPosition from '../../../hooks/useMenuPosition'; @@ -31,6 +31,7 @@ interface StateProps { withCustomReactions?: boolean; message?: ApiMessage; position?: IAnchorPosition; + isTranslucent?: boolean; } const FULL_PICKER_SHIFT_DELTA = { x: -23, y: -64 }; @@ -40,6 +41,7 @@ const ReactionPicker: FC = ({ isOpen, message, position, + isTranslucent, withCustomReactions, }) => { const { toggleReaction, closeReactionPicker } = getActions(); @@ -124,7 +126,7 @@ const ReactionPicker: FC = ({ isReactionPicker className={!withCustomReactions ? styles.hidden : undefined} selectedReactionIds={selectedReactionIds} - isTranslucent + isTranslucent={isTranslucent} onCustomEmojiSelect={handleToggleCustomReaction} onReactionSelect={handleToggleReaction} /> @@ -157,6 +159,7 @@ export default memo(withGlobal((global): StateProps => { withCustomReactions: chat?.isForbidden || areSomeReactionsAllowed ? false : areCustomReactionsAllowed || isPrivateChat, + isTranslucent: selectIsContextMenuTranslucent(global), }; })(ReactionPicker)); diff --git a/src/components/middle/message/ReactionSelector.scss b/src/components/middle/message/ReactionSelector.scss index b96f061d8..a725ba7a0 100644 --- a/src/components/middle/message/ReactionSelector.scss +++ b/src/components/middle/message/ReactionSelector.scss @@ -23,11 +23,14 @@ &__bubble-big, &__bubble-small, &__items-wrapper { - background: var(--color-background-compact-menu); - backdrop-filter: blur(10px); - + background: var(--color-background); filter: drop-shadow(0 0.25rem 0.125rem var(--color-default-shadow)); + body:not(.no-menu-blur) & { + background: var(--color-background-compact-menu); + backdrop-filter: blur(10px); + } + body.is-safari & { filter: none; box-shadow: 0 0.25rem 0.125rem var(--color-default-shadow); diff --git a/src/components/middle/message/ReactionSelector.tsx b/src/components/middle/message/ReactionSelector.tsx index bc15fcafa..5873d9c8b 100644 --- a/src/components/middle/message/ReactionSelector.tsx +++ b/src/components/middle/message/ReactionSelector.tsx @@ -30,6 +30,7 @@ type OwnProps = { isReady?: boolean; canBuyPremium?: boolean; isCurrentUserPremium?: boolean; + canPlayAnimatedEmojis?: boolean; onShowMore: (position: IAnchorPosition) => void; }; @@ -44,6 +45,7 @@ const ReactionSelector: FC = ({ maxUniqueReactions, isPrivate, isReady, + canPlayAnimatedEmojis, onToggleReaction, onShowMore, }) => { @@ -103,6 +105,7 @@ const ReactionSelector: FC = ({ isReady={isReady} onToggleReaction={onToggleReaction} reaction={reaction} + noAppearAnimation={!canPlayAnimatedEmojis} chosen={userReactionIndexes.has(i)} /> ))} diff --git a/src/components/middle/message/ReactionSelectorReaction.scss b/src/components/middle/message/ReactionSelectorReaction.scss index 6e7a6ab24..6ffe2fcf3 100644 --- a/src/components/middle/message/ReactionSelectorReaction.scss +++ b/src/components/middle/message/ReactionSelectorReaction.scss @@ -8,6 +8,14 @@ margin-inline-start: 0; } + &__static-icon { + position: absolute; + top: 5%; + left: 5%; + width: 90%; + height: 90%; + } + .AnimatedSticker { position: absolute; top: 0; @@ -25,8 +33,8 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - width: 2.375rem; - height: 2.375rem; + width: 2.25rem; + height: 2.25rem; border-radius: 50%; background-color: var(--color-background-compact-menu-hover); } diff --git a/src/components/middle/message/ReactionSelectorReaction.tsx b/src/components/middle/message/ReactionSelectorReaction.tsx index ea6f0a59b..d5b94c27b 100644 --- a/src/components/middle/message/ReactionSelectorReaction.tsx +++ b/src/components/middle/message/ReactionSelectorReaction.tsx @@ -3,6 +3,7 @@ import React, { memo } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact'; import type { ApiAvailableReaction, ApiReaction } from '../../../api/types'; +import { REM } from '../../common/helpers/mediaDimensions'; import { createClassNameBuilder } from '../../../util/buildClassName'; import useMedia from '../../../hooks/useMedia'; import useFlag from '../../../hooks/useFlag'; @@ -11,12 +12,13 @@ import AnimatedSticker from '../../common/AnimatedSticker'; import './ReactionSelectorReaction.scss'; -const REACTION_SIZE = 32; +const REACTION_SIZE = 2 * REM; type OwnProps = { reaction: ApiAvailableReaction; isReady?: boolean; chosen?: boolean; + noAppearAnimation?: boolean; onToggleReaction: (reaction: ApiReaction) => void; }; @@ -25,11 +27,13 @@ const cn = createClassNameBuilder('ReactionSelectorReaction'); const ReactionSelectorReaction: FC = ({ reaction, isReady, + noAppearAnimation, chosen, onToggleReaction, }) => { - const mediaAppearData = useMedia(`sticker${reaction.appearAnimation?.id}`, !isReady); - const mediaData = useMedia(`document${reaction.selectAnimation?.id}`, !isReady); + const mediaAppearData = useMedia(`sticker${reaction.appearAnimation?.id}`, !isReady || noAppearAnimation); + const mediaData = useMedia(`document${reaction.selectAnimation?.id}`, !isReady || noAppearAnimation); + const staticIconData = useMedia(`document${reaction.staticIcon?.id}`, !noAppearAnimation); const [isAnimationLoaded, markAnimationLoaded] = useFlag(); const [isFirstPlay, , unmarkIsFirstPlay] = useFlag(true); @@ -45,7 +49,14 @@ const ReactionSelectorReaction: FC = ({ onClick={handleClick} onMouseEnter={isReady && !isFirstPlay ? activate : undefined} > - {!isAnimationLoaded && ( + {noAppearAnimation && ( + + )} + {!isAnimationLoaded && !noAppearAnimation && ( = ({ onEnded={unmarkIsFirstPlay} /> )} - {!isFirstPlay && ( + {!isFirstPlay && !noAppearAnimation && ( = ({ genericEffects, observeIntersection, noRecentReactors, + withEffects, }) => { const lang = useLang(); @@ -60,6 +62,7 @@ const Reactions: FC = ({ withRecentReactors={totalCount <= MAX_RECENT_AVATARS && !noRecentReactors} genericEffects={genericEffects} observeIntersection={observeIntersection} + withEffects={withEffects} /> ))} {metaChildren} diff --git a/src/components/middle/message/SponsoredMessage.tsx b/src/components/middle/message/SponsoredMessage.tsx index 9ca419bf9..61d035b07 100644 --- a/src/components/middle/message/SponsoredMessage.tsx +++ b/src/components/middle/message/SponsoredMessage.tsx @@ -123,7 +123,10 @@ const SponsoredMessage: FC = ({
- {renderTextWithEntities(message.text.text, message.text.entities)} + {renderTextWithEntities({ + text: message.text.text, + entities: message.text.entities, + })} diff --git a/src/components/middle/message/Sticker.tsx b/src/components/middle/message/Sticker.tsx index 3fe632770..db232c500 100644 --- a/src/components/middle/message/Sticker.tsx +++ b/src/components/middle/message/Sticker.tsx @@ -33,13 +33,14 @@ type OwnProps = { shouldLoop?: boolean; lastSyncTime?: number; shouldPlayEffect?: boolean; + withEffect?: boolean; onPlayEffect?: VoidFunction; onStopEffect?: VoidFunction; }; const Sticker: FC = ({ message, observeIntersection, observeIntersectionForPlaying, shouldLoop, lastSyncTime, - shouldPlayEffect, onPlayEffect, onStopEffect, + shouldPlayEffect, withEffect, onPlayEffect, onStopEffect, }) => { const { showNotification, openStickerSet } = getActions(); @@ -75,11 +76,11 @@ const Sticker: FC = ({ const previousShouldPlayEffect = usePrevious(shouldPlayEffect); useEffect(() => { - if (hasEffect && canPlay && (shouldPlayEffect || previousShouldPlayEffect)) { + if (hasEffect && withEffect && canPlay && (shouldPlayEffect || previousShouldPlayEffect)) { startPlayingEffect(); onPlayEffect?.(); } - }, [hasEffect, canPlay, onPlayEffect, shouldPlayEffect, previousShouldPlayEffect, startPlayingEffect]); + }, [hasEffect, canPlay, onPlayEffect, shouldPlayEffect, previousShouldPlayEffect, startPlayingEffect, withEffect]); const openModal = useCallback(() => { openStickerSet({ @@ -89,7 +90,7 @@ const Sticker: FC = ({ const handleClick = useCallback(() => { if (hasEffect) { - if (isPlayingEffect) { + if (isPlayingEffect || !withEffect) { showNotification({ message: lang('PremiumStickerTooltip'), action: { @@ -101,7 +102,7 @@ const Sticker: FC = ({ actionText: lang('ViewAction'), }); return; - } else { + } else if (withEffect) { startPlayingEffect(); onPlayEffect?.(); return; @@ -110,7 +111,7 @@ const Sticker: FC = ({ openModal(); }, [ hasEffect, isPlayingEffect, lang, onPlayEffect, openModal, showNotification, startPlayingEffect, - sticker.stickerSetInfo, + sticker.stickerSetInfo, withEffect, ]); const isMemojiSticker = 'isMissing' in stickerSetInfo; @@ -140,7 +141,7 @@ const Sticker: FC = ({ withSharedAnimation cacheBuster={lastSyncTime} /> - {hasEffect && canLoad && isPlayingEffect && ( + {hasEffect && withEffect && canLoad && isPlayingEffect && ( = ({ @@ -55,7 +53,6 @@ const RightSearch: FC = ({ query, totalCount, foundIds, - animationLevel, onClose, }) => { const { @@ -147,8 +144,6 @@ const RightSearch: FC = ({
@@ -209,7 +204,6 @@ export default memo(withGlobal( query, totalCount, foundIds, - animationLevel: global.settings.byKey.animationLevel, }; }, )(RightSearch)); diff --git a/src/components/right/StickerSetResult.tsx b/src/components/right/StickerSetResult.tsx index d98785583..2f6872657 100644 --- a/src/components/right/StickerSetResult.tsx +++ b/src/components/right/StickerSetResult.tsx @@ -107,7 +107,7 @@ const StickerSetResult: FC = ({ sticker={sticker} size={STICKER_SIZE_SEARCH} observeIntersection={observeIntersection} - noAnimate={!shouldPlay || isModalOpen} + noPlay={!shouldPlay || isModalOpen} clickArg={sticker} onClick={handleStickerClick} noContextMenu diff --git a/src/components/right/management/JoinRequest.tsx b/src/components/right/management/JoinRequest.tsx index 844e69eb8..bb1ec1768 100644 --- a/src/components/right/management/JoinRequest.tsx +++ b/src/components/right/management/JoinRequest.tsx @@ -2,15 +2,14 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useCallback } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import type { AnimationLevel } from '../../../types'; import type { ApiUser } from '../../../api/types'; -import useLang from '../../../hooks/useLang'; import { getUserFullName } from '../../../global/helpers'; import { selectUser } from '../../../global/selectors'; import { formatHumanDate, formatTime, isToday } from '../../../util/dateFormat'; import { getServerTime } from '../../../util/serverTime'; import { createClassNameBuilder } from '../../../util/buildClassName'; +import useLang from '../../../hooks/useLang'; import Avatar from '../../common/Avatar'; import Button from '../../ui/Button'; @@ -28,7 +27,6 @@ type OwnProps = { type StateProps = { user?: ApiUser; isSavedMessages?: boolean; - animationLevel: AnimationLevel; }; const JoinRequest: FC = ({ @@ -38,7 +36,6 @@ const JoinRequest: FC = ({ date, isChannel, user, - animationLevel, }) => { const { openChat, hideChatJoinRequest } = getActions(); @@ -71,8 +68,6 @@ const JoinRequest: FC = ({ key={userId} size="medium" user={user} - animationLevel={animationLevel} - withVideo />
{fullName}
@@ -99,7 +94,6 @@ export default memo(withGlobal( return { user, - animationLevel: global.settings.byKey.animationLevel, }; }, )(JoinRequest)); diff --git a/src/components/ui/Button.scss b/src/components/ui/Button.scss index 98c977a13..2021a6a71 100644 --- a/src/components/ui/Button.scss +++ b/src/components/ui/Button.scss @@ -14,7 +14,7 @@ } @mixin no-ripple-styles() { - body.animation-level-0 & { + body.no-page-transitions & { &:not(.disabled):not(:disabled) { &:active { @content; @@ -50,7 +50,7 @@ // @optimization &:active, &.clicked, - body.animation-level-0 & { + body.no-page-transitions & { transition: none !important; } diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx index d9a16901e..b57791eed 100644 --- a/src/components/ui/Checkbox.tsx +++ b/src/components/ui/Checkbox.tsx @@ -27,7 +27,7 @@ type OwnProps = { className?: string; onChange?: (e: ChangeEvent) => void; onCheck?: (isChecked: boolean) => void; - onClickLabel?: (e: React.MouseEvent) => void; + onClickLabel?: (e: React.MouseEvent, value?: string) => void; }; const Checkbox: FC = ({ @@ -65,7 +65,7 @@ const Checkbox: FC = ({ function handleClick(event: React.MouseEvent) { if (event.target !== labelRef.current) { - onClickLabel?.(event); + onClickLabel?.(event, value); } } diff --git a/src/components/ui/FloatingActionButton.scss b/src/components/ui/FloatingActionButton.scss index e4ac39549..e3b1b7847 100644 --- a/src/components/ui/FloatingActionButton.scss +++ b/src/components/ui/FloatingActionButton.scss @@ -6,7 +6,7 @@ transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); z-index: 2; - body.animation-level-0 & { + body.no-page-transitions & { transition: none !important; } diff --git a/src/components/ui/ListItem.scss b/src/components/ui/ListItem.scss index e2b89ef0a..3a21fd5e8 100644 --- a/src/components/ui/ListItem.scss +++ b/src/components/ui/ListItem.scss @@ -128,7 +128,7 @@ @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-item-active) !important; } diff --git a/src/components/ui/Menu.scss b/src/components/ui/Menu.scss index b1b8b4eb6..64f501f32 100644 --- a/src/components/ui/Menu.scss +++ b/src/components/ui/Menu.scss @@ -38,7 +38,7 @@ transition: opacity 0.2s ease-in, transform 0.2s ease-in !important; } - body.animation-level-0 & { + body.no-context-menu-animations & { transform: none !important; transition: opacity 0.15s !important; } @@ -84,6 +84,11 @@ background: var(--color-background-compact-menu); backdrop-filter: blur(10px); padding: 0.25rem 0; + + body.no-menu-blur & { + background: var(--color-background); + backdrop-filter: none; + } } .footer { diff --git a/src/components/ui/Modal.scss b/src/components/ui/Modal.scss index 8957725a0..959533498 100644 --- a/src/components/ui/Modal.scss +++ b/src/components/ui/Modal.scss @@ -78,7 +78,7 @@ transition: transform 0.2s ease, opacity 0.2s ease; - body.animation-level-0 & { + body.no-page-transitions & { transition: none; transform: none !important; } diff --git a/src/components/ui/RippleEffect.scss b/src/components/ui/RippleEffect.scss index fbc0f623c..02739f66e 100644 --- a/src/components/ui/RippleEffect.scss +++ b/src/components/ui/RippleEffect.scss @@ -33,7 +33,7 @@ bottom: 0; right: 0; - body.animation-level-0 & { + body.no-page-transitions & { display: none; } diff --git a/src/components/ui/Switcher.scss b/src/components/ui/Switcher.scss index 4aae57c1f..694342d59 100644 --- a/src/components/ui/Switcher.scss +++ b/src/components/ui/Switcher.scss @@ -13,7 +13,7 @@ pointer-events: none; } - body.animation-level-0 &, + body.no-page-transitions &, &.no-animation { .widget, .widget::after { diff --git a/src/components/ui/Tab.scss b/src/components/ui/Tab.scss index 1665bb226..adb247861 100644 --- a/src/components/ui/Tab.scss +++ b/src/components/ui/Tab.scss @@ -96,7 +96,7 @@ &.animate { transition: transform var(--slide-transition); - body.animation-level-0 & { + body.no-page-transitions & { transition: none !important; } } diff --git a/src/components/ui/Toggle.module.scss b/src/components/ui/Toggle.module.scss new file mode 100644 index 000000000..436604450 --- /dev/null +++ b/src/components/ui/Toggle.module.scss @@ -0,0 +1,94 @@ +.root { + --widget-width: 2.375rem; + --widget-height: 0.3125rem; + --thumb-size: 0.625rem; + + display: inline-flex; + position: relative; +} + +.alignToEnd { + margin-inline-start: auto; + margin-inline-end: 0.25rem; +} + +.widget { + cursor: var(--custom-cursor, pointer); + position: relative; + width: var(--widget-width); + height: 0; + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: var(--thumb-size); + height: var(--thumb-size); + background-color: var(--color-background); + border-radius: calc(var(--thumb-size) / 2); + transition: transform 200ms; + + :global(body.no-page-transitions) & { + transition: none !important; + } + } + + &.min::after { + transform: translate(0, -50%); + /* stylelint-disable-next-line plugin/whole-pixel */ + box-shadow: 0 0 0 0.109375rem var(--color-gray); + } + + &.mid::after { + transform: translate(calc(var(--widget-width) / 2 - calc(var(--thumb-size) / 2)), -50%); + /* stylelint-disable-next-line plugin/whole-pixel */ + box-shadow: 0 0 0 0.109375rem var(--color-primary); + } + + &.max::after { + transform: translate(calc(var(--widget-width) - var(--thumb-size)), -50%); + /* stylelint-disable-next-line plugin/whole-pixel */ + box-shadow: 0 0 0 0.109375rem var(--color-primary); + } +} + +.filler { + position: absolute; + top: 0; + left: 0; + width: var(--widget-width); + height: var(--widget-height); + border-radius: 0.25rem; + overflow: hidden; + background-color: var(--color-gray); + transform: translateY(-50%); + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: var(--widget-width); + height: var(--widget-height); + border-radius: 0.25rem; + background-color: var(--color-primary); + transition: transform 200ms; + + :global(body.no-page-transitions) & { + transition: none !important; + } + } + + &.min::after { + transform: translateX(-100%); + } + + &.mid::after { + transform: translateX(-50%); + } + + &.max::after { + transform: translateX(0); + } +} diff --git a/src/components/ui/Toggle.tsx b/src/components/ui/Toggle.tsx new file mode 100644 index 000000000..b75d38075 --- /dev/null +++ b/src/components/ui/Toggle.tsx @@ -0,0 +1,20 @@ +import React, { memo } from '../../lib/teact/teact'; + +import buildClassName from '../../util/buildClassName'; + +import styles from './Toggle.module.scss'; + +interface OwnProps { + value: 'min' | 'mid' | 'max'; +} + +function Toggle({ value }: OwnProps) { + return ( +
+ + +
+ ); +} + +export default memo(Toggle); diff --git a/src/components/ui/Transition.tsx b/src/components/ui/Transition.tsx index d7df073c4..0197e1275 100644 --- a/src/components/ui/Transition.tsx +++ b/src/components/ui/Transition.tsx @@ -5,28 +5,27 @@ import { requestMutation, requestForcedReflow } from '../../lib/fasterdom/faster import { getGlobal } from '../../global'; -import type { GlobalState } from '../../global/types'; - -import { ANIMATION_LEVEL_MIN } from '../../config'; import buildClassName from '../../util/buildClassName'; import forceReflow from '../../util/forceReflow'; import { waitForAnimationEnd, waitForTransitionEnd } from '../../util/cssAnimationEndListeners'; +import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; +import { selectCanAnimateInterface } from '../../global/selectors'; import useForceUpdate from '../../hooks/useForceUpdate'; import usePrevious from '../../hooks/usePrevious'; -import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import './Transition.scss'; +type AnimationName = ( + 'none' | 'slide' | 'slideRtl' | 'slideFade' | 'zoomFade' | 'slideLayers' + | 'fade' | 'pushSlide' | 'reveal' | 'slideOptimized' | 'slideOptimizedRtl' | 'semiFade' + | 'slideVertical' | 'slideVerticalFade' +); export type ChildrenFn = (isActive: boolean, isFrom: boolean, currentKey: number) => React.ReactNode; export type TransitionProps = { ref?: RefObject; activeKey: number; nextKey?: number; - name: ( - 'none' | 'slide' | 'slideRtl' | 'slideFade' | 'zoomFade' | 'slideLayers' - | 'fade' | 'pushSlide' | 'reveal' | 'slideOptimized' | 'slideOptimizedRtl' | 'semiFade' - | 'slideVertical' | 'slideVerticalFade' - ); + name: AnimationName; direction?: 'auto' | 'inverse' | 1 | -1; renderCount?: number; shouldRestoreHeight?: boolean; @@ -53,6 +52,10 @@ const CLASSES = { inactive: 'Transition_slide-inactive', afterSlides: 'Transition_afterSlides', }; +const DISABLEABLE_ANIMATIONS = new Set([ + 'slide', 'slideRtl', 'slideFade', 'zoomFade', 'slideLayers', 'pushSlide', 'reveal', + 'slideOptimized', 'slideOptimizedRtl', 'slideVertical', 'slideVerticalFade', +]); function Transition({ ref, @@ -74,9 +77,10 @@ function Transition({ children, afterChildren, }: TransitionProps) { - // No need for a container to update on change - const { animationLevel } = getGlobal().settings.byKey; const currentKeyRef = useRef(); + // No need for a container to update on change + const shouldDisableAnimation = DISABLEABLE_ANIMATIONS.has(name) + && !selectCanAnimateInterface(getGlobal()); // eslint-disable-next-line no-null/no-null let containerRef = useRef(null); @@ -164,7 +168,7 @@ function Transition({ if (isSlideOptimized) { performSlideOptimized( - animationLevel, + shouldDisableAnimation, name, isBackwards, cleanup, @@ -181,7 +185,7 @@ function Transition({ return; } - if (name === 'none' || animationLevel === ANIMATION_LEVEL_MIN) { + if (name === 'none' || shouldDisableAnimation) { childNodes.forEach((node, i) => { if (node instanceof HTMLElement) { removeExtraClass(node, CLASSES.from); @@ -269,7 +273,7 @@ function Transition({ shouldCleanup, slideClassName, cleanupExceptionKey, - animationLevel, + shouldDisableAnimation, forceUpdate, ]); @@ -336,7 +340,7 @@ function Transition({ export default Transition; function performSlideOptimized( - animationLevel: GlobalState['settings']['byKey']['animationLevel'], + shouldDisableAnimation: boolean, name: 'slideOptimized' | 'slideOptimizedRtl', isBackwards: boolean, cleanup: NoneToVoidFunction, @@ -353,7 +357,7 @@ function performSlideOptimized( return; } - if (animationLevel === ANIMATION_LEVEL_MIN) { + if (shouldDisableAnimation) { toggleExtraClass(container, `Transition-${name}`, !isBackwards); toggleExtraClass(container, `Transition-${name}Backwards`, isBackwards); diff --git a/src/config.ts b/src/config.ts index 071b6d274..ca2a54e73 100644 --- a/src/config.ts +++ b/src/config.ts @@ -102,6 +102,7 @@ export const DEFAULT_VOLUME = 1; export const DEFAULT_PLAYBACK_RATE = 1; export const PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION = 20 * 60; // 20 min +export const ANIMATION_LEVEL_CUSTOM = -1; export const ANIMATION_LEVEL_MIN = 0; export const ANIMATION_LEVEL_MED = 1; export const ANIMATION_LEVEL_MAX = 2; diff --git a/src/global/actions/api/reactions.ts b/src/global/actions/api/reactions.ts index 6fd719bf2..1f315a1e0 100644 --- a/src/global/actions/api/reactions.ts +++ b/src/global/actions/api/reactions.ts @@ -4,13 +4,15 @@ import { callApi } from '../../../api/gramjs'; import type { ActionReturnType } from '../../types'; import { ApiMediaFormat } from '../../../api/types'; -import { ANIMATION_LEVEL_MAX } from '../../../config'; import { selectChat, - selectChatMessage, selectCurrentChat, selectTabState, + selectChatMessage, + selectCurrentChat, selectDefaultReaction, selectMaxUserReactions, selectMessageIdsByGroupId, + selectPerformanceSettingsValue, + selectTabState, } from '../../selectors'; import { addMessageReaction, subtractXForEmojiInteraction, updateUnreadReactions } from '../../reducers/reactions'; import { @@ -147,10 +149,9 @@ addActionHandler('toggleReaction', async (global, actions, payload): Promise { const { messageIds, tabId = getCurrentTabId() } = payload; - const { animationLevel } = global.settings.byKey; - const chat = selectCurrentChat(global, tabId); if (!chat) return undefined; @@ -372,7 +371,7 @@ addActionHandler('animateUnreadReaction', (global, actions, payload): ActionRetu actions.markMessagesRead({ messageIds, tabId }); - if (animationLevel !== ANIMATION_LEVEL_MAX) return undefined; + if (selectPerformanceSettingsValue(global, 'reactionEffects')) return undefined; global = getGlobal(); diff --git a/src/global/actions/ui/initial.ts b/src/global/actions/ui/initial.ts index f4df994e1..cde157898 100644 --- a/src/global/actions/ui/initial.ts +++ b/src/global/actions/ui/initial.ts @@ -2,13 +2,14 @@ import { addCallback } from '../../../lib/teact/teactn'; import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import { addActionHandler, getGlobal, setGlobal } from '../../index'; -import { ANIMATION_LEVEL_MAX } from '../../../config'; import { IS_ANDROID, IS_IOS, IS_MAC_OS, IS_SAFARI, IS_TOUCH_ENV, } from '../../../util/windowEnvironment'; import { setLanguage } from '../../../util/langProvider'; import switchTheme from '../../../util/switchTheme'; -import { selectTabState, selectNotifySettings, selectTheme } from '../../selectors'; +import { + selectTabState, selectNotifySettings, selectTheme, selectPerformanceSettings, selectCanAnimateInterface, +} from '../../selectors'; import { startWebsync, stopWebsync } from '../../../util/websync'; import { subscribe, unsubscribe } from '../../../util/notifications'; import { clearCaching, setupCaching } from '../../cache'; @@ -18,6 +19,7 @@ import { callApi } from '../../../api/gramjs'; import type { ActionReturnType, GlobalState } from '../../types'; import { updateTabState } from '../../reducers/tabs'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; +import { applyPerformanceSettings } from '../../../util/perfomanceSettings'; const HISTORY_ANIMATION_DURATION = 450; @@ -99,8 +101,9 @@ addCallback((global: GlobalState) => { shouldInit: false, }, tabState.id); - const { animationLevel, messageTextSize, language } = global.settings.byKey; + const { messageTextSize, language } = global.settings.byKey; const theme = selectTheme(global); + const performanceType = selectPerformanceSettings(global); void setLanguage(language, undefined, true); @@ -112,8 +115,8 @@ addCallback((global: GlobalState) => { document.documentElement.style.setProperty('--message-text-size', `${messageTextSize}px`); document.documentElement.setAttribute('data-message-text-size', messageTextSize.toString()); document.body.classList.add('initial'); - document.body.classList.add(`animation-level-${animationLevel}`); document.body.classList.add(IS_TOUCH_ENV ? 'is-touch-env' : 'is-pointer-env'); + applyPerformanceSettings(performanceType); if (IS_IOS) { document.body.classList.add('is-ios'); @@ -127,7 +130,7 @@ addCallback((global: GlobalState) => { } }); - switchTheme(theme, animationLevel === ANIMATION_LEVEL_MAX); + switchTheme(theme, selectCanAnimateInterface(global)); startWebsync(); @@ -208,10 +211,9 @@ function subscribeToSystemThemeChange() { // eslint-disable-next-line eslint-multitab-tt/no-immediate-global let global = getGlobal(); const nextTheme = selectTheme(global); - const { animationLevel } = global.settings.byKey; if (nextTheme !== currentTheme) { - switchTheme(nextTheme, animationLevel === ANIMATION_LEVEL_MAX); + switchTheme(nextTheme, selectCanAnimateInterface(global)); // Force-update component containers global = { ...global }; setGlobal(global); diff --git a/src/global/actions/ui/misc.ts b/src/global/actions/ui/misc.ts index f16d3dfc9..19fdcbed7 100644 --- a/src/global/actions/ui/misc.ts +++ b/src/global/actions/ui/misc.ts @@ -11,7 +11,13 @@ import { } from '../../../config'; import getReadableErrorText from '../../../util/getReadableErrorText'; import { - selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectTabState, selectIsTrustedBot, selectChat, + selectCanAnimateInterface, + selectChat, + selectChatMessage, + selectCurrentChat, + selectCurrentMessageList, + selectIsTrustedBot, + selectTabState, } from '../../selectors'; import generateIdFor from '../../../util/generateIdFor'; import { compact, unique } from '../../../util/iteratees'; @@ -436,8 +442,7 @@ addActionHandler('requestConfetti', (global, actions, payload): ActionReturnType const { top, left, width, height, tabId = getCurrentTabId(), } = payload || {}; - const { animationLevel } = global.settings.byKey; - if (animationLevel === 0) return undefined; + if (!selectCanAnimateInterface(global)) return undefined; return updateTabState(global, { confetti: { diff --git a/src/global/actions/ui/settings.ts b/src/global/actions/ui/settings.ts index d79c546ad..c9c61acd6 100644 --- a/src/global/actions/ui/settings.ts +++ b/src/global/actions/ui/settings.ts @@ -1,13 +1,15 @@ import { addActionHandler, getActions } from '../../index'; import { replaceSettings, replaceThemeSettings } from '../../reducers'; import switchTheme from '../../../util/switchTheme'; -import { ANIMATION_LEVEL_MAX, ANIMATION_LEVEL_MED, ANIMATION_LEVEL_MIN } from '../../../config'; import { setLanguage, setTimeFormat } from '../../../util/langProvider'; import { IS_IOS } from '../../../util/windowEnvironment'; import type { ActionReturnType, GlobalState } from '../../types'; import { updateTabState } from '../../reducers/tabs'; import { addCallback } from '../../../lib/teact/teactn'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; +import { requestMutation } from '../../../lib/fasterdom/fasterdom'; +import { applyPerformanceSettings } from '../../../util/perfomanceSettings'; +import { selectCanAnimateInterface } from '../../selectors'; let prevGlobal: GlobalState | undefined; @@ -17,21 +19,23 @@ addCallback((global: GlobalState) => { const settings = global.settings.byKey; const prevSettings = prevGlobal?.settings.byKey; + const performance = global.settings.performance; + const prevPerformance = prevGlobal?.settings.performance; prevGlobal = global; if (!prevSettings) { return; } - if (settings.animationLevel !== prevSettings.animationLevel) { - [ANIMATION_LEVEL_MIN, ANIMATION_LEVEL_MED, ANIMATION_LEVEL_MAX].forEach((i) => { - document.body.classList.toggle(`animation-level-${i}`, settings.animationLevel === i); + if (performance !== prevPerformance) { + requestMutation(() => { + applyPerformanceSettings(performance); }); } if (settings.theme !== prevSettings.theme) { - const animationLevel = document.hasFocus() ? global.settings.byKey.animationLevel : ANIMATION_LEVEL_MIN; - switchTheme(settings.theme, animationLevel === ANIMATION_LEVEL_MAX); + const withAnimation = document.hasFocus() ? selectCanAnimateInterface(global) : false; + switchTheme(settings.theme, withAnimation); } if (settings.language !== prevSettings.language) { @@ -61,6 +65,21 @@ addActionHandler('setSettingOption', (global, actions, payload): ActionReturnTyp return replaceSettings(global, payload); }); +addActionHandler('updatePerformanceSettings', (global, actions, payload): ActionReturnType => { + global = { + ...global, + settings: { + ...global.settings, + performance: { + ...global.settings.performance, + ...payload, + }, + }, + }; + + return global; +}); + addActionHandler('setThemeSettings', (global, actions, payload): ActionReturnType => { const { theme, ...settings } = payload; diff --git a/src/global/cache.ts b/src/global/cache.ts index 7d13c1c25..3a750d2d5 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -22,6 +22,8 @@ import { ARCHIVED_FOLDER_ID, DEFAULT_PATTERN_COLOR, DEFAULT_LIMITS, + ANIMATION_LEVEL_MIN, + ANIMATION_LEVEL_MED, } from '../config'; import { isHeavyAnimating } from '../hooks/useHeavyAnimationCheck'; import { @@ -33,7 +35,7 @@ import { selectVisibleUsers, } from './selectors'; import { hasStoredSession } from '../util/sessions'; -import { INITIAL_GLOBAL_STATE } from './initialState'; +import { INITIAL_GLOBAL_STATE, INITIAL_PERFORMANCE_STATE_MID, INITIAL_PERFORMANCE_STATE_MIN } from './initialState'; import { isUserId } from './helpers'; import { getOrderedIds } from '../util/folderManager'; import { clearGlobalForLockScreen } from './reducers'; @@ -158,6 +160,31 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) { ...cached.chatFolders, }; + if (!cached.settings.performance) { + if (cached.settings.byKey.animationLevel === ANIMATION_LEVEL_MIN) { + cached.settings.performance = INITIAL_PERFORMANCE_STATE_MIN; + } else if (cached.settings.byKey.animationLevel === ANIMATION_LEVEL_MED) { + cached.settings.performance = INITIAL_PERFORMANCE_STATE_MID; + } else { + cached.settings.performance = initialState.settings.performance; + } + } + + if ('canAutoPlayVideos' in cached.settings.byKey) { + cached.settings.performance.autoplayVideos = cached.settings.byKey.canAutoPlayVideos; + delete cached.settings.byKey.canAutoPlayVideos; + } + + if ('canAutoPlayGifs' in cached.settings.byKey) { + cached.settings.performance.autoplayGifs = cached.settings.byKey.canAutoPlayGifs; + delete cached.settings.byKey.canAutoPlayGifs; + } + + cached.settings.performance = { + ...initialState.settings.performance, + ...cached.settings.performance, + }; + if (!cached.stickers.premium) { cached.stickers.premium = initialState.stickers.premium; } @@ -555,11 +582,12 @@ function reduceMessages(global: T): GlobalState['messages } function reduceSettings(global: T): GlobalState['settings'] { - const { byKey, themes } = global.settings; + const { byKey, themes, performance } = global.settings; return { byKey, themes, + performance, privacy: {}, notifyExceptions: {}, }; diff --git a/src/global/initialState.ts b/src/global/initialState.ts index 80227d984..89cc494d8 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -1,14 +1,67 @@ import type { TabState, GlobalState } from './types'; +import type { PerformanceType } from '../types'; import { NewChatMembersProgress } from '../types'; import { - ANIMATION_LEVEL_DEFAULT, DARK_THEME_PATTERN_COLOR, DEFAULT_MESSAGE_TEXT_SIZE_PX, DEFAULT_PATTERN_COLOR, + ANIMATION_LEVEL_DEFAULT, + DARK_THEME_PATTERN_COLOR, + DEFAULT_MESSAGE_TEXT_SIZE_PX, + DEFAULT_PATTERN_COLOR, DEFAULT_PLAYBACK_RATE, DEFAULT_VOLUME, - IOS_DEFAULT_MESSAGE_TEXT_SIZE_PX, MACOS_DEFAULT_MESSAGE_TEXT_SIZE_PX, + IOS_DEFAULT_MESSAGE_TEXT_SIZE_PX, + MACOS_DEFAULT_MESSAGE_TEXT_SIZE_PX, } from '../config'; import { IS_IOS, IS_MAC_OS } from '../util/windowEnvironment'; +export const INITIAL_PERFORMANCE_STATE_MAX: PerformanceType = { + animatedEmoji: true, + autoplayGifs: true, + autoplayVideos: true, + contextMenuAnimations: true, + contextMenuBlur: true, + loopAnimatedStickers: true, + mediaViewerAnimations: true, + messageComposerAnimations: true, + messageSendingAnimations: true, + pageTransitions: true, + reactionEffects: true, + rightColumnAnimations: true, + stickerEffects: true, +}; + +export const INITIAL_PERFORMANCE_STATE_MID: PerformanceType = { + animatedEmoji: true, + autoplayGifs: true, + autoplayVideos: true, + contextMenuAnimations: true, + contextMenuBlur: true, + loopAnimatedStickers: true, + mediaViewerAnimations: true, + messageComposerAnimations: true, + messageSendingAnimations: true, + pageTransitions: true, + reactionEffects: true, + rightColumnAnimations: false, + stickerEffects: false, +}; + +export const INITIAL_PERFORMANCE_STATE_MIN: PerformanceType = { + animatedEmoji: false, + autoplayGifs: false, + autoplayVideos: false, + contextMenuAnimations: false, + contextMenuBlur: false, + loopAnimatedStickers: false, + mediaViewerAnimations: false, + messageComposerAnimations: false, + messageSendingAnimations: false, + pageTransitions: false, + reactionEffects: false, + rightColumnAnimations: false, + stickerEffects: false, +}; + export const INITIAL_GLOBAL_STATE: GlobalState = { attachMenu: { bots: {} }, passcode: {}, @@ -158,11 +211,8 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { hasWebNotifications: true, hasPushNotifications: true, notificationSoundVolume: 5, - canAutoPlayGifs: true, - canAutoPlayVideos: true, shouldSuggestStickers: true, shouldSuggestCustomEmoji: true, - shouldLoopStickers: true, language: 'en', timeFormat: '24h', wasTimeFormatSetManually: false, @@ -183,6 +233,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { patternColor: DARK_THEME_PATTERN_COLOR, }, }, + performance: INITIAL_PERFORMANCE_STATE_MAX, privacy: {}, notifyExceptions: {}, }, diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index bbdffe4b1..2d04c8635 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -44,7 +44,8 @@ import { isServiceNotificationMessage, isUserId, isUserRightBanned, - canSendReaction, getAllowedAttachmentOptions, + canSendReaction, + getAllowedAttachmentOptions, } from '../helpers'; import { findLast } from '../../util/iteratees'; import { selectIsStickerFavorite } from './symbols'; @@ -1115,26 +1116,6 @@ function canAutoLoadMedia({ ); } -export function selectCanAutoPlayMedia(global: T, message: ApiMessage) { - const video = getMessageVideo(message) || getMessageWebPageVideo(message); - if (!video) { - return undefined; - } - - const { - canAutoPlayVideos, - canAutoPlayGifs, - } = global.settings.byKey; - - const asGif = video.isGif || video.isRound; - - return (canAutoPlayVideos && !asGif) || (canAutoPlayGifs && asGif); -} - -export function selectShouldLoopStickers(global: T) { - return global.settings.byKey.shouldLoopStickers; -} - export function selectLastServiceNotification(global: T) { const { serviceNotifications } = global; const maxId = Math.max(...serviceNotifications.map(({ id }) => id)); diff --git a/src/global/selectors/ui.ts b/src/global/selectors/ui.ts index 40e6b74bb..76301b94b 100644 --- a/src/global/selectors/ui.ts +++ b/src/global/selectors/ui.ts @@ -1,4 +1,6 @@ import type { GlobalState, TabArgs } from '../types'; +import type { PerformanceTypeKey } from '../../types'; +import type { ApiMessage } from '../../api/types'; import { NewChatMembersProgress, RightColumnContent } from '../../types'; import { getSystemTheme } from '../../util/windowEnvironment'; @@ -11,6 +13,7 @@ import { selectIsStatisticsShown, selectIsMessageStatisticsShown } from './stati import { selectCurrentManagement } from './management'; import { selectTabState } from './tabs'; import { getCurrentTabId } from '../../util/establishMultitabRole'; +import { getMessageVideo, getMessageWebPageVideo } from '../helpers'; export function selectIsMediaViewerOpen( global: T, @@ -74,6 +77,7 @@ export function selectIsForumPanelOpen( tabState.globalSearch.query === undefined || tabState.globalSearch.isClosing ); } + export function selectIsReactionPickerOpen( global: T, ...[tabId = getCurrentTabId()]: TabArgs @@ -81,3 +85,44 @@ export function selectIsReactionPickerOpen( const { reactionPicker } = selectTabState(global, tabId); return Boolean(reactionPicker?.position); } + +export function selectPerformanceSettings(global: T) { + return global.settings.performance; +} + +export function selectPerformanceSettingsValue( + global: T, + key: PerformanceTypeKey, +) { + return global.settings.performance[key]; +} + +export function selectCanAutoPlayMedia(global: T, message: ApiMessage) { + const video = getMessageVideo(message) || getMessageWebPageVideo(message); + if (!video) { + return undefined; + } + + const canAutoPlayVideos = selectPerformanceSettingsValue(global, 'autoplayVideos'); + const canAutoPlayGifs = selectPerformanceSettingsValue(global, 'autoplayGifs'); + + const asGif = video.isGif || video.isRound; + + return (canAutoPlayVideos && !asGif) || (canAutoPlayGifs && asGif); +} + +export function selectShouldLoopStickers(global: T) { + return selectPerformanceSettingsValue(global, 'loopAnimatedStickers'); +} + +export function selectCanPlayAnimatedEmojis(global: T) { + return selectPerformanceSettingsValue(global, 'animatedEmoji'); +} + +export function selectCanAnimateInterface(global: T) { + return selectPerformanceSettingsValue(global, 'pageTransitions'); +} + +export function selectIsContextMenuTranslucent(global: T) { + return selectPerformanceSettingsValue(global, 'contextMenuBlur'); +} diff --git a/src/global/types.ts b/src/global/types.ts index f844945dc..73d1d9f06 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -83,6 +83,7 @@ import type { NewChatMembersProgress, NotifyException, PaymentStep, + PerformanceType, PrivacyVisibility, ProfileEditProgress, SettingsScreens, @@ -800,6 +801,7 @@ export type GlobalState = { settings: { byKey: ISettings; + performance: PerformanceType; loadedWallpapers?: ApiWallpaper[]; themes: Partial>; privacy: Partial>; @@ -918,6 +920,7 @@ export interface ActionPayloads { // settings setSettingOption: Partial | undefined; + updatePerformanceSettings: Partial; loadPasswordInfo: undefined; clearTwoFaError: undefined; updatePassword: { diff --git a/src/styles/_common.scss b/src/styles/_common.scss index f5a78acc2..bcae9d4db 100644 --- a/src/styles/_common.scss +++ b/src/styles/_common.scss @@ -140,7 +140,7 @@ .Avatar { transition: transform var(--layer-transition); - body.animation-level-0 & { + body.no-page-transitions & { transition: none; } } diff --git a/src/types/index.ts b/src/types/index.ts index 40d87e15c..1fbddfecc 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -29,6 +29,14 @@ export interface IAlbum { export type ThemeKey = 'light' | 'dark'; export type AnimationLevel = 0 | 1 | 2; +export type PerformanceTypeKey = ( + 'pageTransitions' | 'messageSendingAnimations' | 'mediaViewerAnimations' + | 'messageComposerAnimations' | 'contextMenuAnimations' | 'contextMenuBlur' | 'rightColumnAnimations' + | 'animatedEmoji' | 'loopAnimatedStickers' | 'reactionEffects' | 'stickerEffects' | 'autoplayGifs' | 'autoplayVideos' +); +export type PerformanceType = { + [key in PerformanceTypeKey]: boolean; +}; export interface IThemeSettings { background?: string; @@ -76,11 +84,8 @@ export interface ISettings extends NotifySettings, Record { canAutoLoadFileInGroups: boolean; canAutoLoadFileInChannels: boolean; autoLoadFileMaxSizeMb: number; - canAutoPlayGifs: boolean; - canAutoPlayVideos: boolean; shouldSuggestStickers: boolean; shouldSuggestCustomEmoji: boolean; - shouldLoopStickers: boolean; hasPassword?: boolean; languages?: ApiLanguage[]; language: LangCode; @@ -183,6 +188,7 @@ export enum SettingsScreens { PrivacyGroupChatsAllowedContacts, PrivacyGroupChatsDeniedContacts, PrivacyBlockedUsers, + Performance, Folders, FoldersCreateFolder, FoldersEditFolder, diff --git a/src/util/animateHorizontalScroll.ts b/src/util/animateHorizontalScroll.ts index 1f8b0d5c7..3cb4b1947 100644 --- a/src/util/animateHorizontalScroll.ts +++ b/src/util/animateHorizontalScroll.ts @@ -1,15 +1,15 @@ import { getGlobal } from '../global'; -import { ANIMATION_LEVEL_MIN } from '../config'; import { animate } from './animation'; import { requestMutation } from '../lib/fasterdom/fasterdom'; +import { selectCanAnimateInterface } from '../global/selectors'; const DEFAULT_DURATION = 300; const stopById: Map = new Map(); export default function animateHorizontalScroll(container: HTMLElement, left: number, duration = DEFAULT_DURATION) { - if (getGlobal().settings.byKey.animationLevel === ANIMATION_LEVEL_MIN) { + if (!selectCanAnimateInterface(getGlobal())) { duration = 0; } diff --git a/src/util/animateScroll.ts b/src/util/animateScroll.ts index 61dd2fe59..ef65375ae 100644 --- a/src/util/animateScroll.ts +++ b/src/util/animateScroll.ts @@ -4,7 +4,6 @@ import { getGlobal } from '../global'; import { FocusDirection } from '../types'; import { - ANIMATION_LEVEL_MIN, FAST_SMOOTH_MIN_DURATION, FAST_SMOOTH_MAX_DURATION, FAST_SMOOTH_MAX_DISTANCE, @@ -13,6 +12,7 @@ import { import { IS_ANDROID } from './windowEnvironment'; import { dispatchHeavyAnimationEvent } from '../hooks/useHeavyAnimationCheck'; import { animateSingle, cancelSingleAnimation } from './animation'; +import { selectCanAnimateInterface } from '../global/selectors'; type Params = Parameters; @@ -57,7 +57,7 @@ function createMutateFunction( ) { if ( forceDirection === FocusDirection.Static - || getGlobal().settings.byKey.animationLevel === ANIMATION_LEVEL_MIN + || !selectCanAnimateInterface(getGlobal()) ) { forceDuration = 0; } diff --git a/src/util/customEmojiManager.ts b/src/util/customEmojiManager.ts index de40422bb..2da7eec75 100644 --- a/src/util/customEmojiManager.ts +++ b/src/util/customEmojiManager.ts @@ -5,6 +5,7 @@ import { ApiMediaFormat } from '../api/types'; import type { ApiSticker } from '../api/types'; import type { GlobalState } from '../global/types'; +import { selectCanPlayAnimatedEmojis } from '../global/selectors'; import { getStickerPreviewHash } from '../global/helpers'; import * as mediaLoader from './mediaLoader'; import { throttle } from './schedulers'; @@ -28,7 +29,10 @@ const renderHandlers = new Set(); let prevGlobal: GlobalState | undefined; addCallback((global: GlobalState) => { - if (global.customEmojis.byId !== prevGlobal?.customEmojis.byId) { + if ( + global.customEmojis.byId !== prevGlobal?.customEmojis.byId + || selectCanPlayAnimatedEmojis(global) !== selectCanPlayAnimatedEmojis(prevGlobal) + ) { for (const entry of handlers) { const [handler, id] = entry; if (global.customEmojis.byId[id]) { diff --git a/src/util/perfomanceSettings.ts b/src/util/perfomanceSettings.ts new file mode 100644 index 000000000..55010059c --- /dev/null +++ b/src/util/perfomanceSettings.ts @@ -0,0 +1,23 @@ +import type { PerformanceType } from '../types'; + +export function applyPerformanceSettings(performanceType: PerformanceType) { + const { + pageTransitions, + messageSendingAnimations, + mediaViewerAnimations, + messageComposerAnimations, + contextMenuAnimations, + contextMenuBlur, + rightColumnAnimations, + } = performanceType; + + const root = document.body; + + root.classList.toggle('no-page-transitions', !pageTransitions); + root.classList.toggle('no-message-sending-animations', !messageSendingAnimations); + root.classList.toggle('no-media-viewer-animations', !mediaViewerAnimations); + root.classList.toggle('no-message-composer-animations', !messageComposerAnimations); + root.classList.toggle('no-context-menu-animations', !contextMenuAnimations); + root.classList.toggle('no-menu-blur', !contextMenuBlur); + root.classList.toggle('no-right-column-animations', !rightColumnAnimations); +} diff --git a/src/util/windowEnvironment.ts b/src/util/windowEnvironment.ts index cb22e81af..7dcde38ff 100644 --- a/src/util/windowEnvironment.ts +++ b/src/util/windowEnvironment.ts @@ -120,9 +120,6 @@ export const MESSAGE_LIST_SENSITIVE_AREA = 750; export const MAX_BUFFER_SIZE = (IS_MOBILE ? 512 : 2000) * 1024 ** 2; // 512 OR 2000 MB -// TODO Turn on later as `!IS_IOS && !IS_ANDROID` -export const VIDEO_AVATARS_DISABLED = true; - function isLastEmojiVersionSupported() { const ALLOWABLE_CALCULATION_ERROR_SIZE = 5; const inlineEl = document.createElement('span');