Message, Chat Folders, Story Toggler: Leverage the new useShowTransition

This commit is contained in:
Alexander Zinchuk 2024-09-06 15:43:17 +02:00
parent b0239f1d43
commit 13f43be77d
6 changed files with 68 additions and 44 deletions

View File

@ -20,11 +20,12 @@ import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useDerivedState from '../../../hooks/useDerivedState';
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager'; import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated'; import useShowTransition from '../../../hooks/useShowTransition';
import StoryRibbon from '../../story/StoryRibbon'; import StoryRibbon from '../../story/StoryRibbon';
import TabList from '../../ui/TabList'; import TabList from '../../ui/TabList';
@ -100,10 +101,15 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
}, []); }, []);
const { const {
ref,
shouldRender: shouldRenderStoryRibbon, shouldRender: shouldRenderStoryRibbon,
transitionClassNames: storyRibbonClassNames, getIsClosing: getIsStoryRibbonClosing,
isClosing: isStoryRibbonClosing, } = useShowTransition({
} = useShowTransitionDeprecated(isStoryRibbonShown, undefined, undefined, ''); isOpen: isStoryRibbonShown,
className: false,
withShouldRender: true,
});
const isStoryRibbonClosing = useDerivedState(getIsStoryRibbonClosing);
const allChatsFolder: ApiChatFolder = useMemo(() => { const allChatsFolder: ApiChatFolder = useMemo(() => {
return { return {
@ -282,8 +288,13 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
}, [currentUserId, folderTabs, openChat, setActiveChatFolder]); }, [currentUserId, folderTabs, openChat, setActiveChatFolder]);
const { const {
shouldRender: shouldRenderPlaceholder, transitionClassNames, ref: placeholderRef,
} = useShowTransitionDeprecated(!orderedFolderIds, undefined, true); shouldRender: shouldRenderPlaceholder,
} = useShowTransition({
isOpen: !orderedFolderIds,
noMountTransition: true,
withShouldRender: true,
});
function renderCurrentTab(isActive: boolean) { function renderCurrentTab(isActive: boolean) {
const activeFolder = Object.values(chatFoldersById) const activeFolder = Object.values(chatFoldersById)
@ -310,11 +321,11 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
return ( return (
<div <div
ref={ref}
className={buildClassName( className={buildClassName(
'ChatFolders', 'ChatFolders',
shouldRenderFolders && shouldHideFolderTabs && 'ChatFolders--tabs-hidden', shouldRenderFolders && shouldHideFolderTabs && 'ChatFolders--tabs-hidden',
shouldRenderStoryRibbon && 'with-story-ribbon', shouldRenderStoryRibbon && 'with-story-ribbon',
storyRibbonClassNames,
)} )}
> >
{shouldRenderStoryRibbon && <StoryRibbon isClosing={isStoryRibbonClosing} />} {shouldRenderStoryRibbon && <StoryRibbon isClosing={isStoryRibbonClosing} />}
@ -327,7 +338,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
areFolders areFolders
/> />
) : shouldRenderPlaceholder ? ( ) : shouldRenderPlaceholder ? (
<div className={buildClassName('tabs-placeholder', transitionClassNames)} /> <div ref={placeholderRef} className="tabs-placeholder" />
) : undefined} ) : undefined}
<Transition <Transition
ref={transitionRef} ref={transitionRef}

View File

@ -9,7 +9,8 @@ import type {
ApiChatReactions, ApiChatReactions,
ApiMessage, ApiMessage,
ApiReaction, ApiReaction,
ApiStickerSet, ApiStickerSetInfo, ApiStickerSet,
ApiStickerSetInfo,
ApiThreadInfo, ApiThreadInfo,
} from '../../../api/types'; } from '../../../api/types';
import type { ActiveDownloads, MessageListType } from '../../../global/types'; import type { ActiveDownloads, MessageListType } from '../../../global/types';
@ -46,7 +47,8 @@ import {
selectIsMessageProtected, selectIsMessageProtected,
selectIsMessageUnread, selectIsMessageUnread,
selectIsPremiumPurchaseBlocked, selectIsPremiumPurchaseBlocked,
selectIsReactionPickerOpen, selectMessageCustomEmojiSets, selectIsReactionPickerOpen,
selectMessageCustomEmojiSets,
selectMessageTranslations, selectMessageTranslations,
selectRequestedChatTranslationLanguage, selectRequestedChatTranslationLanguage,
selectRequestedMessageTranslationLanguage, selectRequestedMessageTranslationLanguage,
@ -54,7 +56,6 @@ import {
selectThreadInfo, selectThreadInfo,
selectUserStatus, selectUserStatus,
} from '../../../global/selectors'; } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { copyTextToClipboard } from '../../../util/clipboard'; import { copyTextToClipboard } from '../../../util/clipboard';
import { getSelectionAsFormattedText } from './helpers/getSelectionAsFormattedText'; import { getSelectionAsFormattedText } from './helpers/getSelectionAsFormattedText';
import { isSelectionRangeInsideMessage } from './helpers/isSelectionRangeInsideMessage'; import { isSelectionRangeInsideMessage } from './helpers/isSelectionRangeInsideMessage';
@ -63,7 +64,7 @@ import useFlag from '../../../hooks/useFlag';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import useSchedule from '../../../hooks/useSchedule'; import useSchedule from '../../../hooks/useSchedule';
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated'; import useShowTransition from '../../../hooks/useShowTransition';
import PinMessageModal from '../../common/PinMessageModal.async'; import PinMessageModal from '../../common/PinMessageModal.async';
import ReportModal from '../../common/ReportModal'; import ReportModal from '../../common/ReportModal';
@ -230,7 +231,11 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useOldLang();
const { transitionClassNames } = useShowTransitionDeprecated(isOpen, onCloseAnimationEnd, undefined, false); const { ref: containerRef } = useShowTransition({
isOpen,
onCloseAnimationEnd,
className: false,
});
const [isMenuOpen, setIsMenuOpen] = useState(true); const [isMenuOpen, setIsMenuOpen] = useState(true);
const [isReportModalOpen, setIsReportModalOpen] = useState(false); const [isReportModalOpen, setIsReportModalOpen] = useState(false);
const [isPinModalOpen, setIsPinModalOpen] = useState(false); const [isPinModalOpen, setIsPinModalOpen] = useState(false);
@ -565,7 +570,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
scheduledMaxDate.setFullYear(scheduledMaxDate.getFullYear() + 1); scheduledMaxDate.setFullYear(scheduledMaxDate.getFullYear() + 1);
return ( return (
<div className={buildClassName('ContextMenuContainer', transitionClassNames)}> <div ref={containerRef} className="ContextMenuContainer">
<MessageContextMenu <MessageContextMenu
isReactionPickerOpen={isReactionPickerOpen} isReactionPickerOpen={isReactionPickerOpen}
availableReactions={availableReactions} availableReactions={availableReactions}

View File

@ -132,7 +132,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated'; import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
import useResizeObserver from '../../../hooks/useResizeObserver'; import useResizeObserver from '../../../hooks/useResizeObserver';
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated'; import useShowTransition from '../../../hooks/useShowTransition';
import useTextLanguage from '../../../hooks/useTextLanguage'; import useTextLanguage from '../../../hooks/useTextLanguage';
import useThrottledCallback from '../../../hooks/useThrottledCallback'; import useThrottledCallback from '../../../hooks/useThrottledCallback';
import useDetectChatLanguage from './hooks/useDetectChatLanguage'; import useDetectChatLanguage from './hooks/useDetectChatLanguage';
@ -473,12 +473,12 @@ const Message: FC<OwnProps & StateProps> = ({
setTimeout(markShown, appearanceOrder * APPEARANCE_DELAY); setTimeout(markShown, appearanceOrder * APPEARANCE_DELAY);
}, [appearanceOrder, markShown, noAppearanceAnimation]); }, [appearanceOrder, markShown, noAppearanceAnimation]);
const { transitionClassNames } = useShowTransitionDeprecated( useShowTransition({
isShown || isJustAdded, ref,
undefined, isOpen: isShown || isJustAdded,
noAppearanceAnimation && !isJustAdded, noMountTransition: noAppearanceAnimation && !isJustAdded,
false, className: false,
); });
const { const {
id: messageId, chatId, forwardInfo, viaBotId, isTranscriptionError, factCheck, id: messageId, chatId, forwardInfo, viaBotId, isTranscriptionError, factCheck,
@ -688,7 +688,6 @@ const Message: FC<OwnProps & StateProps> = ({
isThreadTop && !withAvatar && 'is-thread-top', isThreadTop && !withAvatar && 'is-thread-top',
Boolean(message.inlineButtons) && 'has-inline-buttons', Boolean(message.inlineButtons) && 'has-inline-buttons',
isSwiped && 'is-swiped', isSwiped && 'is-swiped',
transitionClassNames,
isJustAdded && 'is-just-added', isJustAdded && 'is-just-added',
(hasActiveReactions || shouldPlayEffect) && 'has-active-effect', (hasActiveReactions || shouldPlayEffect) && 'has-active-effect',
isStoryMention && 'is-story-mention', isStoryMention && 'is-story-mention',

View File

@ -1,10 +1,6 @@
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
useEffect, useEffect, useLayoutEffect, useRef, useSignal, useState,
useLayoutEffect,
useRef,
useSignal,
useState,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions } from '../../../global'; import { getActions } from '../../../global';
@ -13,7 +9,10 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { ApiMediaFormat } from '../../../api/types'; import { ApiMediaFormat } from '../../../api/types';
import { import {
getMediaFormat, getMessageMediaThumbDataUri, getVideoMediaHash, hasMessageTtl, getMediaFormat,
getMessageMediaThumbDataUri,
getVideoMediaHash,
hasMessageTtl,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { stopCurrentAudio } from '../../../util/audioPlayer'; import { stopCurrentAudio } from '../../../util/audioPlayer';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -25,10 +24,10 @@ import { useThrottledSignal } from '../../../hooks/useAsyncResolvers';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver'; import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useMediaTransitionDeprecated from '../../../hooks/useMediaTransitionDeprecated'; import useMediaTransition from '../../../hooks/useMediaTransition';
import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress'; import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress';
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated'; import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated'; import useShowTransition from '../../../hooks/useShowTransition';
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef'; import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import Icon from '../../common/icons/Icon'; import Icon from '../../common/icons/Icon';
@ -101,15 +100,19 @@ const RoundVideo: FC<OwnProps> = ({
const hasThumb = Boolean(getMessageMediaThumbDataUri(message)); const hasThumb = Boolean(getMessageMediaThumbDataUri(message));
const noThumb = !hasThumb || isPlayerReady || shouldRenderSpoiler; const noThumb = !hasThumb || isPlayerReady || shouldRenderSpoiler;
const thumbRef = useBlurredMediaThumbRef(video, noThumb); const thumbRef = useBlurredMediaThumbRef(video, noThumb);
const thumbClassNames = useMediaTransitionDeprecated(!noThumb); useMediaTransition(!noThumb, { ref: thumbRef });
const thumbDataUri = getMessageMediaThumbDataUri(message); const thumbDataUri = getMessageMediaThumbDataUri(message);
const isTransferring = (isLoadAllowed && !isPlayerReady) || isDownloading; const isTransferring = (isLoadAllowed && !isPlayerReady) || isDownloading;
const wasLoadDisabled = usePreviousDeprecated(isLoadAllowed) === false; const wasLoadDisabled = usePreviousDeprecated(isLoadAllowed) === false;
const { const {
shouldRender: shouldSpinnerRender, ref: spinnerRef,
transitionClassNames: spinnerClassNames, shouldRender: shouldRenderSpinner,
} = useShowTransitionDeprecated(isTransferring, undefined, wasLoadDisabled); } = useShowTransition({
isOpen: isTransferring,
noMountTransition: wasLoadDisabled,
withShouldRender: true,
});
const [isActivated, setIsActivated] = useState(false); const [isActivated, setIsActivated] = useState(false);
@ -260,7 +263,7 @@ const RoundVideo: FC<OwnProps> = ({
{!shouldRenderSpoiler && ( {!shouldRenderSpoiler && (
<canvas <canvas
ref={thumbRef} ref={thumbRef}
className={buildClassName('thumbnail', thumbClassNames)} className="thumbnail"
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`} style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
/> />
)} )}
@ -280,12 +283,12 @@ const RoundVideo: FC<OwnProps> = ({
</svg> </svg>
)} )}
</div> </div>
{shouldSpinnerRender && ( {shouldRenderSpinner && (
<div className={`media-loading ${spinnerClassNames}`}> <div ref={spinnerRef} className="media-loading">
<ProgressSpinner progress={isDownloading ? downloadProgress : loadProgress} /> <ProgressSpinner progress={isDownloading ? downloadProgress : loadProgress} />
</div> </div>
)} )}
{shouldRenderSpoiler && !shouldSpinnerRender && renderPlayWrapper()} {shouldRenderSpoiler && !shouldRenderSpinner && renderPlayWrapper()}
{!mediaData && !isLoadAllowed && ( {!mediaData && !isLoadAllowed && (
<i className="icon icon-download" /> <i className="icon icon-download" />
)} )}

View File

@ -5,12 +5,11 @@ import type { GlobalState } from '../../global/types';
import { ANIMATION_END_DELAY, PREVIEW_AVATAR_COUNT } from '../../config'; import { ANIMATION_END_DELAY, PREVIEW_AVATAR_COUNT } from '../../config';
import { selectIsForumPanelOpen, selectPerformanceSettingsValue, selectTabState } from '../../global/selectors'; import { selectIsForumPanelOpen, selectPerformanceSettingsValue, selectTabState } from '../../global/selectors';
import buildClassName from '../../util/buildClassName';
import { animateClosing, animateOpening, ANIMATION_DURATION } from './helpers/ribbonAnimation'; import { animateClosing, animateOpening, ANIMATION_DURATION } from './helpers/ribbonAnimation';
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import useOldLang from '../../hooks/useOldLang'; import useOldLang from '../../hooks/useOldLang';
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated'; import useShowTransition from '../../hooks/useShowTransition';
import useStoryPreloader from './hooks/useStoryPreloader'; import useStoryPreloader from './hooks/useStoryPreloader';
import Avatar from '../common/Avatar'; import Avatar from '../common/Avatar';
@ -89,7 +88,11 @@ function StoryToggler({
const isVisible = canShow && isShown; const isVisible = canShow && isShown;
// For some reason, setting 'slow' here also fixes scroll freezes on iOS when collapsing Story Ribbon // For some reason, setting 'slow' here also fixes scroll freezes on iOS when collapsing Story Ribbon
const { shouldRender, transitionClassNames } = useShowTransitionDeprecated(isVisible, undefined, undefined, 'slow'); const { ref, shouldRender } = useShowTransition<HTMLButtonElement>({
isOpen: isVisible,
className: 'slow',
withShouldRender: true,
});
useEffect(() => { useEffect(() => {
if (!withAnimation || isForumPanelOpen) return; if (!withAnimation || isForumPanelOpen) return;
@ -108,9 +111,10 @@ function StoryToggler({
return ( return (
<button <button
ref={ref}
type="button" type="button"
id="StoryToggler" id="StoryToggler"
className={buildClassName(styles.root, transitionClassNames)} className={styles.root}
aria-label={lang('Chat.Context.Peer.OpenStory')} aria-label={lang('Chat.Context.Peer.OpenStory')}
onClick={() => toggleStoryRibbon({ isShown: true, isArchived })} onClick={() => toggleStoryRibbon({ isShown: true, isArchived })}
dir={lang.isRtl ? 'rtl' : undefined} dir={lang.isRtl ? 'rtl' : undefined}

View File

@ -3,6 +3,7 @@ import { useLayoutEffect, useRef, useSignal } from '../lib/teact/teact';
import { addExtraClass, toggleExtraClass } from '../lib/teact/teact-dom'; import { addExtraClass, toggleExtraClass } from '../lib/teact/teact-dom';
import { requestMeasure } from '../lib/fasterdom/fasterdom'; import { requestMeasure } from '../lib/fasterdom/fasterdom';
import useDerivedSignal from './useDerivedSignal';
import useDerivedState from './useDerivedState'; import useDerivedState from './useDerivedState';
import useLastCallback from './useLastCallback'; import useLastCallback from './useLastCallback';
import { useStateRef } from './useStateRef'; import { useStateRef } from './useStateRef';
@ -82,8 +83,8 @@ export default function useShowTransition<RefType extends HTMLElement = HTMLDivE
const element = ref.current; const element = ref.current;
if (!element) return; if (!element) return;
addExtraClass(element, 'opacity-transition');
if (className !== false) { if (className !== false) {
addExtraClass(element, 'opacity-transition');
addExtraClass(element, className); addExtraClass(element, className);
} }
@ -103,6 +104,7 @@ export default function useShowTransition<RefType extends HTMLElement = HTMLDivE
() => (withShouldRender && getState() !== 'closed'), () => (withShouldRender && getState() !== 'closed'),
[withShouldRender, getState], [withShouldRender, getState],
); );
const getIsClosing = useDerivedSignal(() => getState() === 'closing', [getState]);
return { ref, shouldRender }; return { ref, shouldRender, getIsClosing };
} }