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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ import {
} from '../../../config';
import {
INITIAL_PERFORMANCE_STATE_MAX,
INITIAL_PERFORMANCE_STATE_MID,
INITIAL_PERFORMANCE_STATE_MED,
INITIAL_PERFORMANCE_STATE_MIN,
} from '../../../global/initialState';
import { selectTabState, selectTheme, selectUser } from '../../../global/selectors';
@ -119,7 +119,7 @@ const LeftSideMenuItems = ({
}
const performanceSettings = newLevel === ANIMATION_LEVEL_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 });
updatePerformanceSettings(performanceSettings);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,9 +6,6 @@
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
&::before {
pointer-events: none;
@ -25,6 +22,11 @@
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 */
> div:last-child {
box-shadow: 0 2px 2px var(--color-light-shadow);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -144,6 +144,14 @@ export function selectCanAnimateInterface<T extends GlobalState>(global: T) {
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) {
return selectPerformanceSettingsValue(global, 'contextMenuBlur');
}
@ -152,10 +160,6 @@ export function selectIsSynced<T extends GlobalState>(global: T) {
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>(
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 ARE_CALLS_SUPPORTED = true;
export const LAYERS_ANIMATION_NAME = IS_ANDROID ? 'slideFade' : IS_IOS ? 'slideLayers' : 'pushSlide';
export const IS_WAVE_TRANSFORM_SUPPORTED = !IS_MOBILE
&& !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'
);
}