[Perf] Simpler transitions for medium animation level

This commit is contained in:
Alexander Zinchuk 2025-08-21 12:04:50 +02:00
parent 0a01bad35d
commit c78ea0e276
21 changed files with 203 additions and 101 deletions

View File

@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../global';
import type { import type {
ApiChat, ApiPeerPhotos, ApiSticker, ApiTopic, ApiUser, ApiUserStatus, ApiChat, ApiPeerPhotos, ApiSticker, ApiTopic, ApiUser, ApiUserStatus,
} from '../../api/types'; } from '../../api/types';
import type { AnimationLevel } from '../../types';
import { MediaViewerOrigin } from '../../types'; import { MediaViewerOrigin } from '../../types';
import { import {
@ -20,10 +21,12 @@ import {
selectUser, selectUser,
selectUserStatus, selectUserStatus,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState.ts';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment'; import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { captureEvents, SwipeDirection } from '../../util/captureEvents'; import { captureEvents, SwipeDirection } from '../../util/captureEvents';
import { MEMO_EMPTY_ARRAY } from '../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import { resolveTransitionName } from '../../util/resolveTransitionName.ts';
import renderText from './helpers/renderText'; import renderText from './helpers/renderText';
import useIntervalForceUpdate from '../../hooks/schedulers/useIntervalForceUpdate'; import useIntervalForceUpdate from '../../hooks/schedulers/useIntervalForceUpdate';
@ -58,6 +61,7 @@ type StateProps =
avatarOwnerId?: string; avatarOwnerId?: string;
topic?: ApiTopic; topic?: ApiTopic;
messagesCount?: number; messagesCount?: number;
animationLevel: AnimationLevel;
emojiStatusSticker?: ApiSticker; emojiStatusSticker?: ApiSticker;
emojiStatusSlug?: string; emojiStatusSlug?: string;
profilePhotos?: ApiPeerPhotos; profilePhotos?: ApiPeerPhotos;
@ -79,6 +83,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
avatarOwnerId, avatarOwnerId,
topic, topic,
messagesCount, messagesCount,
animationLevel,
emojiStatusSticker, emojiStatusSticker,
emojiStatusSlug, emojiStatusSlug,
profilePhotos, profilePhotos,
@ -103,8 +108,6 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
const prevMediaIndex = usePreviousDeprecated(mediaIndex); const prevMediaIndex = usePreviousDeprecated(mediaIndex);
const prevAvatarOwnerId = usePreviousDeprecated(avatarOwnerId); const prevAvatarOwnerId = usePreviousDeprecated(avatarOwnerId);
const [hasSlideAnimation, setHasSlideAnimation] = useState(true); const [hasSlideAnimation, setHasSlideAnimation] = useState(true);
// slideOptimized doesn't work well when animation is dynamically disabled
const slideAnimation = hasSlideAnimation ? (oldLang.isRtl ? 'slideRtl' : 'slide') : 'none';
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0); const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
const isFirst = photos.length <= 1 || currentPhotoIndex === 0; const isFirst = photos.length <= 1 || currentPhotoIndex === 0;
@ -349,7 +352,10 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
</div> </div>
</div> </div>
)} )}
<Transition activeKey={currentPhotoIndex} name={slideAnimation}> <Transition
activeKey={currentPhotoIndex}
name={resolveTransitionName('slide', animationLevel, !hasSlideAnimation, oldLang.isRtl)}
>
{renderPhoto} {renderPhoto}
</Transition> </Transition>
@ -400,6 +406,7 @@ export default memo(withGlobal<OwnProps>(
const isForum = chat?.isForum; const isForum = chat?.isForum;
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {}; const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
const topic = isForum && currentTopicId ? selectTopic(global, peerId, currentTopicId) : undefined; const topic = isForum && currentTopicId ? selectTopic(global, peerId, currentTopicId) : undefined;
const { animationLevel } = selectSharedSettings(global);
const emojiStatus = (user || chat)?.emojiStatus; const emojiStatus = (user || chat)?.emojiStatus;
const emojiStatusSticker = emojiStatus ? global.customEmojis.byId[emojiStatus.documentId] : undefined; const emojiStatusSticker = emojiStatus ? global.customEmojis.byId[emojiStatus.documentId] : undefined;
@ -411,6 +418,7 @@ export default memo(withGlobal<OwnProps>(
chat, chat,
mediaIndex, mediaIndex,
avatarOwnerId, avatarOwnerId,
animationLevel,
emojiStatusSticker, emojiStatusSticker,
emojiStatusSlug, emojiStatusSlug,
profilePhotos, profilePhotos,

View File

@ -1,22 +1,24 @@
import type { import type {
ElementRef } from '../../lib/teact/teact'; ElementRef } from '@teact';
import { import {
memo, useEffect, useMemo, useState, memo, useEffect, useMemo, useState,
} from '../../lib/teact/teact'; } from '@teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { GlobalState } from '../../global/types'; import type { GlobalState } from '../../global/types';
import type { FoldersActions } from '../../hooks/reducers/useFoldersReducer'; import type { FoldersActions } from '../../hooks/reducers/useFoldersReducer';
import type { ReducerAction } from '../../hooks/useReducer'; import type { ReducerAction } from '../../hooks/useReducer';
import { LeftColumnContent, SettingsScreens } from '../../types'; import { type AnimationLevel, LeftColumnContent, SettingsScreens } from '../../types';
import { import {
selectCurrentChat, selectIsCurrentUserFrozen, selectIsForumPanelOpen, selectTabState, selectCurrentChat, selectIsCurrentUserFrozen, selectIsForumPanelOpen, selectTabState,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState.ts';
import { import {
IS_APP, IS_FIREFOX, IS_MAC_OS, IS_TOUCH_ENV, LAYERS_ANIMATION_NAME, IS_APP, IS_FIREFOX, IS_MAC_OS, IS_TOUCH_ENV,
} from '../../util/browser/windowEnvironment'; } from '../../util/browser/windowEnvironment';
import captureEscKeyListener from '../../util/captureEscKeyListener'; import captureEscKeyListener from '../../util/captureEscKeyListener';
import { resolveTransitionName } from '../../util/resolveTransitionName.ts';
import { debounce } from '../../util/schedulers'; import { debounce } from '../../util/schedulers';
import { captureControlledSwipe } from '../../util/swipeController'; import { captureControlledSwipe } from '../../util/swipeController';
@ -45,6 +47,7 @@ type StateProps = {
searchQuery?: string; searchQuery?: string;
searchDate?: number; searchDate?: number;
isFirstChatFolderActive: boolean; isFirstChatFolderActive: boolean;
animationLevel: AnimationLevel;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
currentUserId?: string; currentUserId?: string;
hasPasscode?: boolean; hasPasscode?: boolean;
@ -81,6 +84,7 @@ function LeftColumn({
searchQuery, searchQuery,
searchDate, searchDate,
isFirstChatFolderActive, isFirstChatFolderActive,
animationLevel,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
currentUserId, currentUserId,
hasPasscode, hasPasscode,
@ -501,6 +505,7 @@ function LeftColumn({
currentScreen={settingsScreen} currentScreen={settingsScreen}
foldersState={foldersState} foldersState={foldersState}
foldersDispatch={foldersDispatch} foldersDispatch={foldersDispatch}
animationLevel={animationLevel}
shouldSkipTransition={shouldSkipHistoryAnimations} shouldSkipTransition={shouldSkipHistoryAnimations}
onReset={handleReset} onReset={handleReset}
/> />
@ -512,6 +517,7 @@ function LeftColumn({
isActive={isActive} isActive={isActive}
isChannel isChannel
content={contentKey} content={contentKey}
animationLevel={animationLevel}
onReset={handleReset} onReset={handleReset}
/> />
); );
@ -521,6 +527,7 @@ function LeftColumn({
key={lastResetTime} key={lastResetTime}
isActive={isActive} isActive={isActive}
content={contentKey} content={contentKey}
animationLevel={animationLevel}
onReset={handleReset} onReset={handleReset}
/> />
); );
@ -549,7 +556,7 @@ function LeftColumn({
return ( return (
<Transition <Transition
ref={ref} ref={ref}
name={shouldSkipHistoryAnimations ? 'none' : LAYERS_ANIMATION_NAME} name={resolveTransitionName('layers', animationLevel, shouldSkipHistoryAnimations)}
renderCount={RENDER_COUNT} renderCount={RENDER_COUNT}
activeKey={contentType} activeKey={contentType}
shouldCleanup shouldCleanup
@ -590,6 +597,7 @@ export default memo(withGlobal<OwnProps>(
archiveSettings, archiveSettings,
} = global; } = global;
const { animationLevel } = selectSharedSettings(global);
const currentChat = selectCurrentChat(global); const currentChat = selectCurrentChat(global);
const isChatOpen = Boolean(currentChat?.id); const isChatOpen = Boolean(currentChat?.id);
const isForumPanelOpen = selectIsForumPanelOpen(global); const isForumPanelOpen = selectIsForumPanelOpen(global);
@ -600,6 +608,7 @@ export default memo(withGlobal<OwnProps>(
searchQuery: query, searchQuery: query,
searchDate: minDate, searchDate: minDate,
isFirstChatFolderActive: activeChatFolder === 0, isFirstChatFolderActive: activeChatFolder === 0,
animationLevel,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
currentUserId, currentUserId,
hasPasscode, hasPasscode,

View File

@ -1,12 +1,11 @@
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '@teact';
import { import { memo, useEffect, useMemo, useRef } from '@teact';
memo, useEffect, useMemo, useRef,
} from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global'; import { getActions, getGlobal, withGlobal } from '../../../global';
import type { ApiChatFolder, ApiChatlistExportedInvite, ApiSession } from '../../../api/types'; import type { ApiChatFolder, ApiChatlistExportedInvite, ApiSession } from '../../../api/types';
import type { GlobalState } from '../../../global/types'; import type { GlobalState } from '../../../global/types';
import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
import type { AnimationLevel } from '../../../types';
import type { MenuItemContextAction } from '../../ui/ListItem'; import type { MenuItemContextAction } from '../../ui/ListItem';
import type { TabWithProperties } from '../../ui/TabList'; import type { TabWithProperties } from '../../ui/TabList';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
@ -14,11 +13,13 @@ import { SettingsScreens } from '../../../types';
import { ALL_FOLDER_ID } from '../../../config'; import { ALL_FOLDER_ID } from '../../../config';
import { selectCanShareFolder, selectIsCurrentUserFrozen, selectTabState } from '../../../global/selectors'; import { selectCanShareFolder, selectIsCurrentUserFrozen, selectTabState } from '../../../global/selectors';
import { selectCurrentLimit } from '../../../global/selectors/limits'; import { selectCurrentLimit } from '../../../global/selectors/limits';
import { selectSharedSettings } from '../../../global/selectors/sharedState.ts';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener'; import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { captureEvents, SwipeDirection } from '../../../util/captureEvents'; import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { resolveTransitionName } from '../../../util/resolveTransitionName.ts';
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities'; import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
import useDerivedState from '../../../hooks/useDerivedState'; import useDerivedState from '../../../hooks/useDerivedState';
@ -48,6 +49,7 @@ type StateProps = {
orderedFolderIds?: number[]; orderedFolderIds?: number[];
activeChatFolder: number; activeChatFolder: number;
currentUserId?: string; currentUserId?: string;
animationLevel: AnimationLevel;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
maxFolders: number; maxFolders: number;
maxChatLists: number; maxChatLists: number;
@ -70,6 +72,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
activeChatFolder, activeChatFolder,
currentUserId, currentUserId,
isForumPanelOpen, isForumPanelOpen,
animationLevel,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
maxFolders, maxFolders,
maxChatLists, maxChatLists,
@ -393,7 +396,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
) : undefined} ) : undefined}
<Transition <Transition
ref={transitionRef} ref={transitionRef}
name={shouldSkipHistoryAnimations ? 'none' : lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'} name={resolveTransitionName('slideOptimized', animationLevel, shouldSkipHistoryAnimations, lang.isRtl)}
activeKey={activeChatFolder} activeKey={activeChatFolder}
renderCount={shouldRenderFolders ? folderTabs.length : undefined} renderCount={shouldRenderFolders ? folderTabs.length : undefined}
> >
@ -427,6 +430,7 @@ export default memo(withGlobal<OwnProps>(
currentUserId, currentUserId,
archiveSettings, archiveSettings,
} = global; } = global;
const { animationLevel } = selectSharedSettings(global);
const { shouldSkipHistoryAnimations, activeChatFolder } = selectTabState(global); const { shouldSkipHistoryAnimations, activeChatFolder } = selectTabState(global);
const { storyViewer: { isRibbonShown: isStoryRibbonShown } } = selectTabState(global); const { storyViewer: { isRibbonShown: isStoryRibbonShown } } = selectTabState(global);
const isAccountFrozen = selectIsCurrentUserFrozen(global); const isAccountFrozen = selectIsCurrentUserFrozen(global);
@ -437,6 +441,7 @@ export default memo(withGlobal<OwnProps>(
orderedFolderIds, orderedFolderIds,
activeChatFolder, activeChatFolder,
currentUserId, currentUserId,
animationLevel,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
hasArchivedChats: Boolean(archived?.length), hasArchivedChats: Boolean(archived?.length),
hasArchivedStories: Boolean(archivedStories?.length), hasArchivedStories: Boolean(archivedStories?.length),

View File

@ -19,7 +19,7 @@ import {
} from '../../../config'; } from '../../../config';
import { import {
INITIAL_PERFORMANCE_STATE_MAX, INITIAL_PERFORMANCE_STATE_MAX,
INITIAL_PERFORMANCE_STATE_MID, INITIAL_PERFORMANCE_STATE_MED,
INITIAL_PERFORMANCE_STATE_MIN, INITIAL_PERFORMANCE_STATE_MIN,
} from '../../../global/initialState'; } from '../../../global/initialState';
import { selectTabState, selectTheme, selectUser } from '../../../global/selectors'; import { selectTabState, selectTheme, selectUser } from '../../../global/selectors';
@ -119,7 +119,7 @@ const LeftSideMenuItems = ({
} }
const performanceSettings = newLevel === ANIMATION_LEVEL_MIN const performanceSettings = newLevel === ANIMATION_LEVEL_MIN
? INITIAL_PERFORMANCE_STATE_MIN ? INITIAL_PERFORMANCE_STATE_MIN
: (newLevel === ANIMATION_LEVEL_MAX ? INITIAL_PERFORMANCE_STATE_MAX : INITIAL_PERFORMANCE_STATE_MID); : (newLevel === ANIMATION_LEVEL_MAX ? INITIAL_PERFORMANCE_STATE_MAX : INITIAL_PERFORMANCE_STATE_MED);
setSharedSettingOption({ animationLevel: newLevel as AnimationLevel }); setSharedSettingOption({ animationLevel: newLevel as AnimationLevel });
updatePerformanceSettings(performanceSettings); updatePerformanceSettings(performanceSettings);

View File

@ -1,10 +1,10 @@
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '@teact';
import { memo, useCallback, useState } from '../../../lib/teact/teact'; import { memo, useCallback, useState } from '@teact';
import { getActions } from '../../../global'; import { getActions } from '../../../global';
import { LeftColumnContent } from '../../../types'; import { type AnimationLevel, LeftColumnContent } from '../../../types';
import { LAYERS_ANIMATION_NAME } from '../../../util/browser/windowEnvironment'; import { resolveTransitionName } from '../../../util/resolveTransitionName.ts';
import useLastCallback from '../../../hooks/useLastCallback.ts'; import useLastCallback from '../../../hooks/useLastCallback.ts';
@ -18,6 +18,7 @@ export type OwnProps = {
isActive: boolean; isActive: boolean;
isChannel?: boolean; isChannel?: boolean;
content: LeftColumnContent; content: LeftColumnContent;
animationLevel: AnimationLevel;
onReset: () => void; onReset: () => void;
}; };
@ -27,6 +28,7 @@ const NewChat: FC<OwnProps> = ({
isActive, isActive,
isChannel = false, isChannel = false,
content, content,
animationLevel,
onReset, onReset,
}) => { }) => {
const { openLeftColumnContent, setGlobalSearchQuery } = getActions(); const { openLeftColumnContent, setGlobalSearchQuery } = getActions();
@ -50,7 +52,7 @@ const NewChat: FC<OwnProps> = ({
return ( return (
<Transition <Transition
id="NewChat" id="NewChat"
name={LAYERS_ANIMATION_NAME} name={resolveTransitionName('layers', animationLevel)}
renderCount={RENDER_COUNT} renderCount={RENDER_COUNT}
activeKey={content} activeKey={content}
> >

View File

@ -9,10 +9,12 @@ import {
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { RegularLangKey } from '../../../types/language'; import type { RegularLangKey } from '../../../types/language';
import { GlobalSearchContent } from '../../../types'; import { type AnimationLevel, GlobalSearchContent } from '../../../types';
import { selectTabState } from '../../../global/selectors'; import { selectTabState } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState.ts';
import { parseDateString } from '../../../util/dates/dateFormat'; import { parseDateString } from '../../../util/dates/dateFormat';
import { resolveTransitionName } from '../../../util/resolveTransitionName.ts';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation'; import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation';
@ -42,6 +44,7 @@ export type OwnProps = {
type StateProps = { type StateProps = {
currentContent?: GlobalSearchContent; currentContent?: GlobalSearchContent;
chatId?: string; chatId?: string;
animationLevel: AnimationLevel;
}; };
type TabInfo = { type TabInfo = {
@ -72,6 +75,7 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
isActive, isActive,
currentContent = GlobalSearchContent.ChatList, currentContent = GlobalSearchContent.ChatList,
chatId, chatId,
animationLevel,
onReset, onReset,
}) => { }) => {
const { const {
@ -120,7 +124,7 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
<div className="LeftSearch" ref={containerRef} onKeyDown={handleKeyDown}> <div className="LeftSearch" ref={containerRef} onKeyDown={handleKeyDown}>
<TabList activeTab={activeTab} tabs={tabs} onSwitchTab={handleSwitchTab} /> <TabList activeTab={activeTab} tabs={tabs} onSwitchTab={handleSwitchTab} />
<Transition <Transition
name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'} name={resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
renderCount={tabs.length} renderCount={tabs.length}
activeKey={currentContent} activeKey={currentContent}
> >
@ -195,7 +199,8 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { currentContent, chatId } = selectTabState(global).globalSearch; const { currentContent, chatId } = selectTabState(global).globalSearch;
const { animationLevel } = selectSharedSettings(global);
return { currentContent, chatId }; return { currentContent, chatId, animationLevel };
}, },
)(LeftSearch)); )(LeftSearch));

View File

@ -3,11 +3,14 @@ import { getActions, withGlobal } from '../../../global';
import { getGlobal } from '../../../global'; import { getGlobal } from '../../../global';
import type { ApiMessage, ApiSearchPostsFlood } from '../../../api/types'; import type { ApiMessage, ApiSearchPostsFlood } from '../../../api/types';
import type { AnimationLevel } from '../../../types';
import { LoadMoreDirection } from '../../../types'; import { LoadMoreDirection } from '../../../types';
import { selectTabState } from '../../../global/selectors'; import { selectTabState } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState.ts';
import { parseSearchResultKey, type SearchResultKey } from '../../../util/keys/searchResultKey'; import { parseSearchResultKey, type SearchResultKey } from '../../../util/keys/searchResultKey';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { resolveTransitionName } from '../../../util/resolveTransitionName.ts';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import { renderMessageSummary } from '../../common/helpers/renderMessageText'; import { renderMessageSummary } from '../../common/helpers/renderMessageText';
@ -32,6 +35,7 @@ type StateProps = {
shouldShowSearchLauncher?: boolean; shouldShowSearchLauncher?: boolean;
isNothingFound?: boolean; isNothingFound?: boolean;
isLoading?: boolean; isLoading?: boolean;
animationLevel: AnimationLevel;
}; };
const runThrottled = throttle((cb) => cb(), 500, true); const runThrottled = throttle((cb) => cb(), 500, true);
@ -44,6 +48,7 @@ const PublicPostsResults = ({
shouldShowSearchLauncher, shouldShowSearchLauncher,
isNothingFound, isNothingFound,
isLoading, isLoading,
animationLevel,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { searchMessagesGlobal } = getActions(); const { searchMessagesGlobal } = getActions();
@ -104,7 +109,7 @@ const PublicPostsResults = ({
return ( return (
<Transition <Transition
name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'} name={resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
activeKey={shouldShowSearchLauncher || isLoading ? 0 : 1} activeKey={shouldShowSearchLauncher || isLoading ? 0 : 1}
> >
{shouldShowSearchLauncher || isLoading ? ( {shouldShowSearchLauncher || isLoading ? (
@ -149,12 +154,12 @@ export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { messages: { byChatId: globalMessagesByChatId } } = global; const { messages: { byChatId: globalMessagesByChatId } } = global;
const { resultsByType, searchFlood, fetchingStatus } = selectTabState(global).globalSearch; const { resultsByType, searchFlood, fetchingStatus } = selectTabState(global).globalSearch;
const publicPostsResult = resultsByType?.publicPosts; const publicPostsResult = resultsByType?.publicPosts;
const { foundIds } = publicPostsResult || {}; const { foundIds } = publicPostsResult || {};
const isLoading = Boolean(fetchingStatus?.publicPosts && !publicPostsResult); const isLoading = Boolean(fetchingStatus?.publicPosts && !publicPostsResult);
const shouldShowSearchLauncher = !publicPostsResult && !isLoading; const shouldShowSearchLauncher = !publicPostsResult && !isLoading;
const isNothingFound = publicPostsResult && !foundIds?.length; const isNothingFound = publicPostsResult && !foundIds?.length;
const { animationLevel } = selectSharedSettings(global);
return { return {
foundIds, foundIds,
@ -163,6 +168,7 @@ export default memo(withGlobal<OwnProps>(
shouldShowSearchLauncher, shouldShowSearchLauncher,
isNothingFound, isNothingFound,
isLoading, isLoading,
animationLevel,
}; };
}, },
)(PublicPostsResults)); )(PublicPostsResults));

View File

@ -1,12 +1,13 @@
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '@teact';
import { memo, useRef, useState } from '../../../lib/teact/teact'; import { memo, useRef, useState } from '@teact';
import { getActions, getGlobal } from '../../../global'; import { getActions, getGlobal } from '../../../global';
import type { FolderEditDispatch, FoldersState } from '../../../hooks/reducers/useFoldersReducer'; import type { FolderEditDispatch, FoldersState } from '../../../hooks/reducers/useFoldersReducer';
import type { AnimationLevel } from '../../../types';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { selectTabState } from '../../../global/selectors'; import { selectTabState } from '../../../global/selectors';
import { LAYERS_ANIMATION_NAME } from '../../../util/browser/windowEnvironment'; import { resolveTransitionName } from '../../../util/resolveTransitionName.ts';
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer'; import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
@ -150,6 +151,7 @@ export type OwnProps = {
currentScreen: SettingsScreens; currentScreen: SettingsScreens;
foldersState: FoldersState; foldersState: FoldersState;
foldersDispatch: FolderEditDispatch; foldersDispatch: FolderEditDispatch;
animationLevel: AnimationLevel;
shouldSkipTransition?: boolean; shouldSkipTransition?: boolean;
onReset: (forceReturnToChatList?: true | Event) => void; onReset: (forceReturnToChatList?: true | Event) => void;
}; };
@ -160,6 +162,7 @@ const Settings: FC<OwnProps> = ({
foldersState, foldersState,
foldersDispatch, foldersDispatch,
onReset, onReset,
animationLevel,
shouldSkipTransition, shouldSkipTransition,
}) => { }) => {
const { closeShareChatFolderModal, openSettingsScreen } = getActions(); const { closeShareChatFolderModal, openSettingsScreen } = getActions();
@ -510,7 +513,7 @@ const Settings: FC<OwnProps> = ({
<Transition <Transition
ref={containerRef} ref={containerRef}
id="Settings" id="Settings"
name={shouldSkipTransition ? 'none' : LAYERS_ANIMATION_NAME} name={resolveTransitionName('layers', animationLevel, shouldSkipTransition)}
activeKey={currentScreen} activeKey={currentScreen}
renderCount={TRANSITION_RENDER_COUNT} renderCount={TRANSITION_RENDER_COUNT}
shouldWrap shouldWrap

View File

@ -12,7 +12,7 @@ import {
} from '../../../config'; } from '../../../config';
import { import {
INITIAL_PERFORMANCE_STATE_MAX, INITIAL_PERFORMANCE_STATE_MAX,
INITIAL_PERFORMANCE_STATE_MID, INITIAL_PERFORMANCE_STATE_MED,
INITIAL_PERFORMANCE_STATE_MIN, INITIAL_PERFORMANCE_STATE_MIN,
} from '../../../global/initialState'; } from '../../../global/initialState';
import { selectPerformanceSettings } from '../../../global/selectors'; import { selectPerformanceSettings } from '../../../global/selectors';
@ -109,7 +109,7 @@ function SettingsPerformance({
if (areDeepEqual(performanceSettings, INITIAL_PERFORMANCE_STATE_MIN)) { if (areDeepEqual(performanceSettings, INITIAL_PERFORMANCE_STATE_MIN)) {
return ANIMATION_LEVEL_MIN; return ANIMATION_LEVEL_MIN;
} }
if (areDeepEqual(performanceSettings, INITIAL_PERFORMANCE_STATE_MID)) { if (areDeepEqual(performanceSettings, INITIAL_PERFORMANCE_STATE_MED)) {
return ANIMATION_LEVEL_MED; return ANIMATION_LEVEL_MED;
} }
@ -137,7 +137,7 @@ function SettingsPerformance({
const handleAnimationLevelChange = useCallback((newLevel: number) => { const handleAnimationLevelChange = useCallback((newLevel: number) => {
const performance = newLevel === ANIMATION_LEVEL_MIN const performance = newLevel === ANIMATION_LEVEL_MIN
? INITIAL_PERFORMANCE_STATE_MIN ? INITIAL_PERFORMANCE_STATE_MIN
: (newLevel === ANIMATION_LEVEL_MED ? INITIAL_PERFORMANCE_STATE_MID : INITIAL_PERFORMANCE_STATE_MAX); : (newLevel === ANIMATION_LEVEL_MED ? INITIAL_PERFORMANCE_STATE_MED : INITIAL_PERFORMANCE_STATE_MAX);
setSharedSettingOption({ animationLevel: newLevel as AnimationLevel }); setSharedSettingOption({ animationLevel: newLevel as AnimationLevel });
updatePerformanceSettings(performance); updatePerformanceSettings(performance);

View File

@ -1,21 +1,10 @@
import type { import type React from '@teact';
ElementRef } from '../../lib/teact/teact'; import type { ElementRef } from '@teact';
import type React from '../../lib/teact/teact'; import { memo, useEffect, useMemo, useState } from '@teact';
import {
memo, useEffect, useMemo,
useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { import type { ApiChat, ApiChatBannedRights, ApiInputMessageReplyInfo, ApiTopic } from '../../api/types';
ApiChat, ApiChatBannedRights, ApiInputMessageReplyInfo, ApiTopic, import type { ActiveEmojiInteraction, AnimationLevel, MessageListType, ThemeKey, ThreadId } from '../../types';
} from '../../api/types';
import type {
ActiveEmojiInteraction,
MessageListType,
ThemeKey,
ThreadId,
} from '../../types';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
import { import {
@ -42,7 +31,7 @@ import {
} from '../../global/helpers'; } from '../../global/helpers';
import { import {
selectBot, selectBot,
selectCanAnimateInterface, selectCanAnimateInterface, selectCanAnimateRightColumn,
selectChat, selectChat,
selectChatFullInfo, selectChatFullInfo,
selectCurrentMessageList, selectCurrentMessageList,
@ -65,13 +54,20 @@ import {
selectTopics, selectTopics,
selectUserFullInfo, selectUserFullInfo,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState.ts';
import { import {
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_SAFARI, IS_TRANSLATION_SUPPORTED, MASK_IMAGE_DISABLED, IS_ANDROID,
IS_ELECTRON,
IS_IOS,
IS_SAFARI,
IS_TRANSLATION_SUPPORTED,
MASK_IMAGE_DISABLED,
} from '../../util/browser/windowEnvironment'; } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle'; import buildStyle from '../../util/buildStyle';
import captureEscKeyListener from '../../util/captureEscKeyListener'; import captureEscKeyListener from '../../util/captureEscKeyListener';
import { isUserId } from '../../util/entities/ids'; import { isUserId } from '../../util/entities/ids';
import { resolveTransitionName } from '../../util/resolveTransitionName.ts';
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms'; import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';
@ -144,7 +140,9 @@ type StateProps = {
isPrivacySettingsNoticeModalOpen: boolean; isPrivacySettingsNoticeModalOpen: boolean;
isReactorListModalOpen: boolean; isReactorListModalOpen: boolean;
isChatLanguageModalOpen?: boolean; isChatLanguageModalOpen?: boolean;
animationLevel: AnimationLevel;
withInterfaceAnimations?: boolean; withInterfaceAnimations?: boolean;
withRightColumnAnimation?: boolean;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
currentTransitionKey: number; currentTransitionKey: number;
isChannel?: boolean; isChannel?: boolean;
@ -208,7 +206,9 @@ function MiddleColumn({
isPrivacySettingsNoticeModalOpen, isPrivacySettingsNoticeModalOpen,
isReactorListModalOpen, isReactorListModalOpen,
isChatLanguageModalOpen, isChatLanguageModalOpen,
animationLevel,
withInterfaceAnimations, withInterfaceAnimations,
withRightColumnAnimation,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
currentTransitionKey, currentTransitionKey,
isChannel, isChannel,
@ -444,7 +444,7 @@ function MiddleColumn({
const bgClassName = buildClassName( const bgClassName = buildClassName(
styles.background, styles.background,
styles.withTransition, withRightColumnAnimation && styles.withTransition,
customBackground && styles.customBgImage, customBackground && styles.customBgImage,
backgroundColor && styles.customBgColor, backgroundColor && styles.customBgColor,
customBackground && isBackgroundBlurred && styles.blurred, customBackground && isBackgroundBlurred && styles.blurred,
@ -557,7 +557,11 @@ function MiddleColumn({
onFocusPinnedMessage={handleFocusPinnedMessage} onFocusPinnedMessage={handleFocusPinnedMessage}
/> />
<Transition <Transition
name={shouldSkipHistoryAnimations ? 'none' : withInterfaceAnimations ? 'slide' : 'fade'} name={resolveTransitionName(
'slide',
animationLevel,
shouldSkipHistoryAnimations || !withInterfaceAnimations,
)}
activeKey={currentTransitionKey} activeKey={currentTransitionKey}
shouldCleanup shouldCleanup
cleanupExceptionKey={cleanupExceptionKey} cleanupExceptionKey={cleanupExceptionKey}
@ -771,7 +775,9 @@ export default memo(withGlobal<OwnProps>(
isPrivacySettingsNoticeModalOpen: Boolean(privacySettingsNoticeModal), isPrivacySettingsNoticeModalOpen: Boolean(privacySettingsNoticeModal),
isReactorListModalOpen: Boolean(reactorModal), isReactorListModalOpen: Boolean(reactorModal),
isChatLanguageModalOpen: Boolean(chatLanguageModal), isChatLanguageModalOpen: Boolean(chatLanguageModal),
animationLevel: selectSharedSettings(global).animationLevel,
withInterfaceAnimations: selectCanAnimateInterface(global), withInterfaceAnimations: selectCanAnimateInterface(global),
withRightColumnAnimation: selectCanAnimateRightColumn(global),
currentTransitionKey: Math.max(0, messageLists.length - 1), currentTransitionKey: Math.max(0, messageLists.length - 1),
activeEmojiInteractions, activeEmojiInteractions,
leftColumnWidth, leftColumnWidth,

View File

@ -6,9 +6,6 @@
width: 100%; width: 100%;
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: width var(--layer-transition);
// In case if there are no children, we need to have a shadow // In case if there are no children, we need to have a shadow
&::before { &::before {
pointer-events: none; pointer-events: none;
@ -25,6 +22,11 @@
box-shadow: 0 2px 2px var(--color-light-shadow); box-shadow: 0 2px 2px var(--color-light-shadow);
} }
&_withRightColumnAnimation {
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: width var(--layer-transition);
}
/* stylelint-disable-next-line plugin/selector-tag-no-without-class */ /* stylelint-disable-next-line plugin/selector-tag-no-without-class */
> div:last-child { > div:last-child {
box-shadow: 0 2px 2px var(--color-light-shadow); box-shadow: 0 2px 2px var(--color-light-shadow);

View File

@ -1,7 +1,5 @@
import { import { memo, useRef, useSignal } from '@teact';
memo, useRef, useSignal, import { setExtraStyles } from '@teact/teact-dom';
} from '../../lib/teact/teact';
import { setExtraStyles } from '../../lib/teact/teact-dom';
import { withGlobal } from '../../global'; import { withGlobal } from '../../global';
import type { ApiChat, ApiUserFullInfo } from '../../api/types'; import type { ApiChat, ApiUserFullInfo } from '../../api/types';
@ -10,7 +8,12 @@ import type { Signal } from '../../util/signals';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
import { import {
selectChat, selectChatMessage, selectCurrentMiddleSearch, selectTabState, selectUserFullInfo, selectCanAnimateRightColumn,
selectChat,
selectChatMessage,
selectCurrentMiddleSearch,
selectTabState,
selectUserFullInfo,
} from '../../global/selectors'; } from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
@ -45,6 +48,7 @@ type StateProps = {
userFullInfo?: ApiUserFullInfo; userFullInfo?: ApiUserFullInfo;
isAudioPlayerRendered?: boolean; isAudioPlayerRendered?: boolean;
isMiddleSearchOpen?: boolean; isMiddleSearchOpen?: boolean;
withRightColumnAnimation?: boolean;
}; };
const FALLBACK_PANE_STATE = { height: 0 }; const FALLBACK_PANE_STATE = { height: 0 };
@ -60,6 +64,7 @@ const MiddleHeaderPanes = ({
getLoadingPinnedId, getLoadingPinnedId,
isAudioPlayerRendered, isAudioPlayerRendered,
isMiddleSearchOpen, isMiddleSearchOpen,
withRightColumnAnimation,
onFocusPinnedMessage, onFocusPinnedMessage,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { settings } = userFullInfo || {}; const { settings } = userFullInfo || {};
@ -119,7 +124,16 @@ const MiddleHeaderPanes = ({
if (!shouldRender) return undefined; if (!shouldRender) return undefined;
return ( return (
<div ref={ref} className={buildClassName(styles.root, className)}> <div
ref={ref}
className={
buildClassName(
styles.root,
withRightColumnAnimation && styles.root_withRightColumnAnimation,
className,
)
}
>
<AudioPlayer <AudioPlayer
isFullWidth isFullWidth
onPaneStateChange={setAudioPlayerState} onPaneStateChange={setAudioPlayerState}
@ -187,6 +201,7 @@ export default memo(withGlobal<OwnProps>(
userFullInfo, userFullInfo,
isAudioPlayerRendered: Boolean(audioMessage), isAudioPlayerRendered: Boolean(audioMessage),
isMiddleSearchOpen, isMiddleSearchOpen,
withRightColumnAnimation: selectCanAnimateRightColumn(global),
}; };
}, },
)(MiddleHeaderPanes)); )(MiddleHeaderPanes));

View File

@ -1,18 +1,20 @@
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '@teact';
import { import {
memo, useEffect, useLayoutEffect, useRef, useState, memo, useEffect, useLayoutEffect, useRef, useState,
} from '../../../lib/teact/teact'; } from '@teact';
import { withGlobal } from '../../../global'; import { withGlobal } from '../../../global';
import type { ApiSticker, ApiVideo } from '../../../api/types'; import type { ApiSticker, ApiVideo } from '../../../api/types';
import type { GlobalActions } from '../../../global'; import type { GlobalActions } from '../../../global';
import type { ThreadId } from '../../../types'; import type { AnimationLevel, ThreadId } from '../../../types';
import type { MenuPositionOptions } from '../../ui/Menu'; import type { MenuPositionOptions } from '../../ui/Menu';
import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { selectIsContextMenuTranslucent, selectTabState } from '../../../global/selectors'; import { selectIsContextMenuTranslucent, selectTabState } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState.ts';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { resolveTransitionName } from '../../../util/resolveTransitionName.ts';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
@ -69,6 +71,7 @@ export type OwnProps = {
type StateProps = { type StateProps = {
isLeftColumnShown: boolean; isLeftColumnShown: boolean;
isBackgroundTranslucent?: boolean; isBackgroundTranslucent?: boolean;
animationLevel: AnimationLevel;
}; };
let isActivated = false; let isActivated = false;
@ -80,12 +83,10 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
canSendStickers, canSendStickers,
canSendGifs, canSendGifs,
isMessageComposer, isMessageComposer,
isLeftColumnShown,
idPrefix, idPrefix,
isAttachmentModal, isAttachmentModal,
canSendPlainText, canSendPlainText,
className, className,
isBackgroundTranslucent,
onLoad, onLoad,
onClose, onClose,
onEmojiSelect, onEmojiSelect,
@ -96,6 +97,9 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
onSearchOpen, onSearchOpen,
addRecentEmoji, addRecentEmoji,
addRecentCustomEmoji, addRecentCustomEmoji,
isLeftColumnShown,
isBackgroundTranslucent,
animationLevel,
...menuPositionOptions ...menuPositionOptions
}) => { }) => {
const [activeTab, setActiveTab] = useState<SymbolMenuTabs>(SymbolMenuTabs.Emoji); const [activeTab, setActiveTab] = useState<SymbolMenuTabs>(SymbolMenuTabs.Emoji);
@ -253,7 +257,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
<div className="SymbolMenu-main" onClick={stopPropagation}> <div className="SymbolMenu-main" onClick={stopPropagation}>
{isActivated && ( {isActivated && (
<Transition <Transition
name="slide" name={resolveTransitionName('slide', animationLevel)}
activeKey={activeTab} activeKey={activeTab}
renderCount={Object.values(SYMBOL_MENU_TAB_TITLES).length} renderCount={Object.values(SYMBOL_MENU_TAB_TITLES).length}
> >
@ -343,6 +347,7 @@ export default memo(withGlobal<OwnProps>(
return { return {
isLeftColumnShown: selectTabState(global).isLeftColumnShown, isLeftColumnShown: selectTabState(global).isLeftColumnShown,
isBackgroundTranslucent: selectIsContextMenuTranslucent(global), isBackgroundTranslucent: selectIsContextMenuTranslucent(global),
animationLevel: selectSharedSettings(global).animationLevel,
}; };
}, },
)(SymbolMenu)); )(SymbolMenu));

View File

@ -3,6 +3,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
import type { ApiStarTopupOption } from '../../../api/types'; import type { ApiStarTopupOption } from '../../../api/types';
import type { GlobalState, TabState } from '../../../global/types'; import type { GlobalState, TabState } from '../../../global/types';
import type { AnimationLevel } from '../../../types';
import type { RegularLangKey } from '../../../types/language'; import type { RegularLangKey } from '../../../types/language';
import { import {
@ -15,8 +16,10 @@ import {
import { getChatTitle, getUserFullName } from '../../../global/helpers'; import { getChatTitle, getUserFullName } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers'; import { getPeerTitle } from '../../../global/helpers/peers';
import { selectChat, selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors'; import { selectChat, selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState.ts';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { convertCurrencyFromBaseUnit, convertTonToUsd, formatCurrencyAsString } from '../../../util/formatCurrency'; import { convertCurrencyFromBaseUnit, convertTonToUsd, formatCurrencyAsString } from '../../../util/formatCurrency';
import { resolveTransitionName } from '../../../util/resolveTransitionName.ts';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
@ -59,10 +62,11 @@ type StateProps = {
shouldForceHeight?: boolean; shouldForceHeight?: boolean;
tonUsdRate?: number; tonUsdRate?: number;
tonTopupUrl: string; tonTopupUrl: string;
animationLevel: AnimationLevel;
}; };
const StarsBalanceModal = ({ const StarsBalanceModal = ({
modal, starsBalanceState, tonBalanceState, canBuyPremium, shouldForceHeight, tonUsdRate, tonTopupUrl, modal, starsBalanceState, tonBalanceState, canBuyPremium, shouldForceHeight, tonUsdRate, tonTopupUrl, animationLevel,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { const {
closeStarsBalanceModal, loadStarsTransactions, loadStarsSubscriptions, openStarsGiftingPickerModal, openInvoice, closeStarsBalanceModal, loadStarsTransactions, loadStarsSubscriptions, openStarsGiftingPickerModal, openInvoice,
@ -365,7 +369,7 @@ const StarsBalanceModal = ({
<div className={styles.container}> <div className={styles.container}>
<div className={styles.lastSection}> <div className={styles.lastSection}>
<Transition <Transition
name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'} name={resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
activeKey={selectedTabIndex} activeKey={selectedTabIndex}
renderCount={TRANSACTION_TABS_KEYS.length} renderCount={TRANSACTION_TABS_KEYS.length}
shouldRestoreHeight shouldRestoreHeight
@ -417,6 +421,7 @@ export default memo(withGlobal<OwnProps>(
canBuyPremium: !selectIsPremiumPurchaseBlocked(global), canBuyPremium: !selectIsPremiumPurchaseBlocked(global),
tonUsdRate: global.appConfig?.tonUsdRate || TON_USD_RATE_DEFAULT, tonUsdRate: global.appConfig?.tonUsdRate || TON_USD_RATE_DEFAULT,
tonTopupUrl: global.appConfig?.tonTopupUrl || TON_TOPUP_URL_DEFAULT, tonTopupUrl: global.appConfig?.tonTopupUrl || TON_TOPUP_URL_DEFAULT,
animationLevel: selectSharedSettings(global).animationLevel,
}; };
}, },
)(StarsBalanceModal)); )(StarsBalanceModal));

View File

@ -17,6 +17,7 @@ import type {
} from '../../api/types'; } from '../../api/types';
import type { TabState } from '../../global/types'; import type { TabState } from '../../global/types';
import type { import type {
AnimationLevel,
ProfileState, ProfileTabType, SharedMediaType, ThemeKey, ThreadId, ProfileState, ProfileTabType, SharedMediaType, ThemeKey, ThreadId,
} from '../../types'; } from '../../types';
import type { RegularLangKey } from '../../types/language'; import type { RegularLangKey } from '../../types/language';
@ -67,6 +68,7 @@ import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { captureEvents, SwipeDirection } from '../../util/captureEvents'; import { captureEvents, SwipeDirection } from '../../util/captureEvents';
import { isUserId } from '../../util/entities/ids'; import { isUserId } from '../../util/entities/ids';
import { resolveTransitionName } from '../../util/resolveTransitionName.ts';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets'; import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import renderText from '../common/helpers/renderText'; import renderText from '../common/helpers/renderText';
import { getSenderName } from '../left/search/helpers/getSenderName'; import { getSenderName } from '../left/search/helpers/getSenderName';
@ -154,6 +156,7 @@ type StateProps = {
activeDownloads: TabState['activeDownloads']; activeDownloads: TabState['activeDownloads'];
isChatProtected?: boolean; isChatProtected?: boolean;
nextProfileTab?: ProfileTabType; nextProfileTab?: ProfileTabType;
animationLevel: AnimationLevel;
shouldWarnAboutSvg?: boolean; shouldWarnAboutSvg?: boolean;
similarChannels?: string[]; similarChannels?: string[];
similarBots?: string[]; similarBots?: string[];
@ -219,6 +222,7 @@ const Profile: FC<OwnProps & StateProps> = ({
activeDownloads, activeDownloads,
isChatProtected, isChatProtected,
nextProfileTab, nextProfileTab,
animationLevel,
shouldWarnAboutSvg, shouldWarnAboutSvg,
similarChannels, similarChannels,
similarBots, similarBots,
@ -892,7 +896,7 @@ const Profile: FC<OwnProps & StateProps> = ({
> >
<Transition <Transition
ref={transitionRef} ref={transitionRef}
name={oldLang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'} name={resolveTransitionName('slideOptimized', animationLevel, undefined, oldLang.isRtl)}
activeKey={activeKey} activeKey={activeKey}
renderCount={tabs.length} renderCount={tabs.length}
shouldRestoreHeight shouldRestoreHeight
@ -946,7 +950,7 @@ export default memo(withGlobal<OwnProps>(
const userFullInfo = selectUserFullInfo(global, chatId); const userFullInfo = selectUserFullInfo(global, chatId);
const messagesById = selectChatMessages(global, chatId); const messagesById = selectChatMessages(global, chatId);
const { shouldWarnAboutSvg } = selectSharedSettings(global); const { animationLevel, shouldWarnAboutSvg } = selectSharedSettings(global);
const { currentType: mediaSearchType, resultsByType } = selectCurrentSharedMediaSearch(global) || {}; const { currentType: mediaSearchType, resultsByType } = selectCurrentSharedMediaSearch(global) || {};
const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {}; const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {};
@ -1030,6 +1034,7 @@ export default memo(withGlobal<OwnProps>(
isChatProtected: chat?.isProtected, isChatProtected: chat?.isProtected,
nextProfileTab: selectTabState(global).nextProfileTab, nextProfileTab: selectTabState(global).nextProfileTab,
forceScrollProfileTab: selectTabState(global).forceScrollProfileTab, forceScrollProfileTab: selectTabState(global).forceScrollProfileTab,
animationLevel,
shouldWarnAboutSvg, shouldWarnAboutSvg,
similarChannels: similarChannelIds, similarChannels: similarChannelIds,
similarBots: similarBotsIds, similarBots: similarBotsIds,

View File

@ -1,13 +1,9 @@
import type { FC } from '../../lib/teact/teact'; import type { FC } from '@teact';
import { import { memo, useEffect, useRef, useState } from '@teact';
memo, useEffect, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { ProfileTabType, ThreadId } from '../../types'; import type { AnimationLevel, ProfileTabType, ThreadId } from '../../types';
import { import { ManagementScreens, NewChatMembersProgress, ProfileState, RightColumnContent } from '../../types';
ManagementScreens, NewChatMembersProgress, ProfileState, RightColumnContent,
} from '../../types';
import { ANIMATION_END_DELAY, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config'; import { ANIMATION_END_DELAY, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config';
import { getIsSavedDialog } from '../../global/helpers'; import { getIsSavedDialog } from '../../global/helpers';
@ -18,7 +14,9 @@ import {
selectRightColumnContentKey, selectRightColumnContentKey,
selectTabState, selectTabState,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState.ts';
import captureEscKeyListener from '../../util/captureEscKeyListener'; import captureEscKeyListener from '../../util/captureEscKeyListener';
import { resolveTransitionName } from '../../util/resolveTransitionName.ts';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import useHistoryBack from '../../hooks/useHistoryBack'; import useHistoryBack from '../../hooks/useHistoryBack';
@ -55,6 +53,7 @@ type StateProps = {
threadId?: ThreadId; threadId?: ThreadId;
isInsideTopic?: boolean; isInsideTopic?: boolean;
isChatSelected: boolean; isChatSelected: boolean;
animationLevel: AnimationLevel;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
nextManagementScreen?: ManagementScreens; nextManagementScreen?: ManagementScreens;
nextProfileTab?: ProfileTabType; nextProfileTab?: ProfileTabType;
@ -80,6 +79,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
threadId, threadId,
isMobile, isMobile,
isChatSelected, isChatSelected,
animationLevel,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
nextManagementScreen, nextManagementScreen,
nextProfileTab, nextProfileTab,
@ -403,7 +403,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
/> />
<Transition <Transition
ref={containerRef} ref={containerRef}
name={(shouldSkipTransition || shouldSkipHistoryAnimations) ? 'none' : 'zoomFade'} name={resolveTransitionName('layers', animationLevel, shouldSkipTransition || shouldSkipHistoryAnimations)}
renderCount={MAIN_SCREENS_COUNT + MANAGEMENT_SCREENS_COUNT} renderCount={MAIN_SCREENS_COUNT + MANAGEMENT_SCREENS_COUNT}
activeKey={isManagement ? MAIN_SCREENS_COUNT + managementScreen : renderingContentKey} activeKey={isManagement ? MAIN_SCREENS_COUNT + managementScreen : renderingContentKey}
shouldCleanup shouldCleanup
@ -425,6 +425,7 @@ export default memo(withGlobal<OwnProps>(
const { chatId, threadId } = selectCurrentMessageList(global) || {}; const { chatId, threadId } = selectCurrentMessageList(global) || {};
const areActiveChatsLoaded = selectAreActiveChatsLoaded(global); const areActiveChatsLoaded = selectAreActiveChatsLoaded(global);
const { animationLevel } = selectSharedSettings(global);
const { const {
management, shouldSkipHistoryAnimations, nextProfileTab, shouldCloseRightColumn, management, shouldSkipHistoryAnimations, nextProfileTab, shouldCloseRightColumn,
} = selectTabState(global); } = selectTabState(global);
@ -438,6 +439,7 @@ export default memo(withGlobal<OwnProps>(
chatId, chatId,
threadId, threadId,
isChatSelected: Boolean(chatId && areActiveChatsLoaded), isChatSelected: Boolean(chatId && areActiveChatsLoaded),
animationLevel,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
nextManagementScreen, nextManagementScreen,
nextProfileTab, nextProfileTab,

View File

@ -1,23 +1,17 @@
import { import { memo, useMemo, useRef, useState } from '../../../lib/teact/teact';
memo, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiBoost, ApiBoostStatistics, ApiTypePrepaidGiveaway } from '../../../api/types'; import type { ApiBoost, ApiBoostStatistics, ApiTypePrepaidGiveaway } from '../../../api/types';
import type { TabState } from '../../../global/types'; import type { TabState } from '../../../global/types';
import type { CustomPeer } from '../../../types'; import type { AnimationLevel, CustomPeer } from '../../../types';
import { import { GIVEAWAY_BOOST_PER_PREMIUM } from '../../../config';
GIVEAWAY_BOOST_PER_PREMIUM,
} from '../../../config';
import { isChatChannel } from '../../../global/helpers'; import { isChatChannel } from '../../../global/helpers';
import { import { selectChat, selectIsGiveawayGiftsPurchaseAvailable, selectTabState } from '../../../global/selectors';
selectChat, import { selectSharedSettings } from '../../../global/selectors/sharedState.ts';
selectIsGiveawayGiftsPurchaseAvailable,
selectTabState,
} from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatDateAtTime } from '../../../util/dates/dateFormat'; import { formatDateAtTime } from '../../../util/dates/dateFormat';
import { resolveTransitionName } from '../../../util/resolveTransitionName.ts';
import { formatInteger } from '../../../util/textFormat'; import { formatInteger } from '../../../util/textFormat';
import { getBoostProgressInfo } from '../../common/helpers/boostInfo'; import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
@ -48,6 +42,7 @@ type StateProps = {
chatId: string; chatId: string;
giveawayBoostsPerPremium?: number; giveawayBoostsPerPremium?: number;
isChannel?: boolean; isChannel?: boolean;
animationLevel: AnimationLevel;
}; };
const GIVEAWAY_IMG_LIST: Partial<Record<number, string>> = { const GIVEAWAY_IMG_LIST: Partial<Record<number, string>> = {
@ -75,6 +70,7 @@ const BoostStatistics = ({
chatId, chatId,
giveawayBoostsPerPremium, giveawayBoostsPerPremium,
isChannel, isChannel,
animationLevel,
}: StateProps) => { }: StateProps) => {
const { const {
openChat, loadMoreBoosters, closeBoostStatistics, openGiveawayModal, showNotification, openChat, loadMoreBoosters, closeBoostStatistics, openGiveawayModal, showNotification,
@ -363,7 +359,7 @@ const BoostStatistics = ({
> >
<Transition <Transition
ref={transitionRef} ref={transitionRef}
name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'} name={resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
activeKey={activeKey} activeKey={activeKey}
renderCount={tabs.length} renderCount={tabs.length}
shouldRestoreHeight shouldRestoreHeight
@ -434,6 +430,7 @@ export default memo(withGlobal(
const chat = chatId ? selectChat(global, chatId) : undefined; const chat = chatId ? selectChat(global, chatId) : undefined;
const isChannel = chat && isChatChannel(chat); const isChannel = chat && isChatChannel(chat);
const giveawayBoostsPerPremium = global.appConfig?.giveawayBoostsPerPremium; const giveawayBoostsPerPremium = global.appConfig?.giveawayBoostsPerPremium;
const { animationLevel } = selectSharedSettings(global);
return { return {
boostStatistics, boostStatistics,
@ -441,6 +438,7 @@ export default memo(withGlobal(
chatId: chatId!, chatId: chatId!,
giveawayBoostsPerPremium, giveawayBoostsPerPremium,
isChannel, isChannel,
animationLevel,
}; };
}, },
)(BoostStatistics)); )(BoostStatistics));

View File

@ -34,7 +34,7 @@ export const INITIAL_PERFORMANCE_STATE_MAX: PerformanceType = {
snapEffect: true, snapEffect: true,
}; };
export const INITIAL_PERFORMANCE_STATE_MID: PerformanceType = { export const INITIAL_PERFORMANCE_STATE_MED: PerformanceType = {
animatedEmoji: true, animatedEmoji: true,
autoplayGifs: true, autoplayGifs: true,
autoplayVideos: true, autoplayVideos: true,
@ -47,8 +47,8 @@ export const INITIAL_PERFORMANCE_STATE_MID: PerformanceType = {
pageTransitions: true, pageTransitions: true,
reactionEffects: true, reactionEffects: true,
rightColumnAnimations: false, rightColumnAnimations: false,
stickerEffects: false, stickerEffects: true,
storyRibbonAnimations: false, storyRibbonAnimations: true,
snapEffect: false, snapEffect: false,
}; };

View File

@ -144,6 +144,14 @@ export function selectCanAnimateInterface<T extends GlobalState>(global: T) {
return selectPerformanceSettingsValue(global, 'pageTransitions'); return selectPerformanceSettingsValue(global, 'pageTransitions');
} }
export function selectCanAnimateRightColumn<T extends GlobalState>(global: T) {
return selectPerformanceSettingsValue(global, 'rightColumnAnimations');
}
export function selectCanAnimateSnapEffect<T extends GlobalState>(global: T) {
return IS_SNAP_EFFECT_SUPPORTED && selectPerformanceSettingsValue(global, 'snapEffect');
}
export function selectIsContextMenuTranslucent<T extends GlobalState>(global: T) { export function selectIsContextMenuTranslucent<T extends GlobalState>(global: T) {
return selectPerformanceSettingsValue(global, 'contextMenuBlur'); return selectPerformanceSettingsValue(global, 'contextMenuBlur');
} }
@ -152,10 +160,6 @@ export function selectIsSynced<T extends GlobalState>(global: T) {
return global.isSynced; return global.isSynced;
} }
export function selectCanAnimateSnapEffect<T extends GlobalState>(global: T) {
return IS_SNAP_EFFECT_SUPPORTED && selectPerformanceSettingsValue(global, 'snapEffect');
}
export function selectWebApp<T extends GlobalState>( export function selectWebApp<T extends GlobalState>(
global: T, key: string, ...[tabId = getCurrentTabId()]: TabArgs<T> global: T, key: string, ...[tabId = getCurrentTabId()]: TabArgs<T>
) { ) {

View File

@ -76,7 +76,6 @@ export const IS_CANVAS_FILTER_SUPPORTED = (
); );
export const IS_REQUEST_FULLSCREEN_SUPPORTED = 'requestFullscreen' in document.createElement('div'); export const IS_REQUEST_FULLSCREEN_SUPPORTED = 'requestFullscreen' in document.createElement('div');
export const ARE_CALLS_SUPPORTED = true; export const ARE_CALLS_SUPPORTED = true;
export const LAYERS_ANIMATION_NAME = IS_ANDROID ? 'slideFade' : IS_IOS ? 'slideLayers' : 'pushSlide';
export const IS_WAVE_TRANSFORM_SUPPORTED = !IS_MOBILE export const IS_WAVE_TRANSFORM_SUPPORTED = !IS_MOBILE
&& !IS_FIREFOX // https://bugzilla.mozilla.org/show_bug.cgi?id=1961378 && !IS_FIREFOX // https://bugzilla.mozilla.org/show_bug.cgi?id=1961378

View File

@ -0,0 +1,23 @@
import type { AnimationLevel } from '../types';
import { ANIMATION_LEVEL_MED, ANIMATION_LEVEL_MIN } from '../config.ts';
import { IS_ANDROID, IS_IOS } from './browser/windowEnvironment.ts';
export function resolveTransitionName(
name: 'slideOptimized' | 'slide' | 'layers',
animationLevel: AnimationLevel,
isDisabled = false,
isRtl = false,
) {
if (isDisabled || animationLevel === ANIMATION_LEVEL_MIN) return 'none';
if (animationLevel === ANIMATION_LEVEL_MED) return 'slideFade';
return name === 'slideOptimized' ? (
isRtl ? 'slideOptimizedRtl' : 'slideOptimized'
) : name === 'slide' ? (
isRtl ? 'slideRtl' : 'slide'
) : (
IS_ANDROID ? 'slideFade' : IS_IOS ? 'slideLayers' : 'pushSlide'
);
}