Sticker Set Cover: Fix pausing when hidden

This commit is contained in:
Alexander Zinchuk 2022-11-01 18:53:26 +01:00
parent b8573ebf35
commit 3bffde60cb
8 changed files with 88 additions and 125 deletions

View File

@ -29,13 +29,13 @@ type OwnProps<T> = {
noAnimate?: boolean;
title?: string;
className?: string;
clickArg: T;
noContextMenu?: boolean;
isSavedMessages?: boolean;
canViewSet?: boolean;
isCurrentUserPremium?: boolean;
observeIntersection: ObserveFn;
onClick?: (arg: OwnProps<T>['clickArg'], isSilent?: boolean, shouldSchedule?: boolean) => void;
clickArg: T;
onFaveClick?: (sticker: ApiSticker) => void;
onUnfaveClick?: (sticker: ApiSticker) => void;
onRemoveRecentClick?: (sticker: ApiSticker) => void;

View File

@ -1,4 +1,4 @@
import React, { memo, useCallback, useMemo } from '../../lib/teact/teact';
import React, { memo, useCallback } from '../../lib/teact/teact';
import type { ApiSticker, ApiStickerSet } from '../../api/types';
import type { FC } from '../../lib/teact/teact';
@ -11,7 +11,6 @@ import useLang from '../../hooks/useLang';
import ListItem from '../ui/ListItem';
import Button from '../ui/Button';
import StickerSetCoverAnimated from '../middle/composer/StickerSetCoverAnimated';
import StickerSetCover from '../middle/composer/StickerSetCover';
import StickerButton from './StickerButton';
@ -19,6 +18,7 @@ import './StickerSetCard.scss';
type OwnProps = {
stickerSet?: ApiStickerSet;
noAnimate?: boolean;
className?: string;
observeIntersection: ObserveFn;
onClick: (value: ApiSticker) => void;
@ -26,6 +26,7 @@ type OwnProps = {
const StickerSetCard: FC<OwnProps> = ({
stickerSet,
noAnimate,
className,
observeIntersection,
onClick,
@ -38,7 +39,11 @@ const StickerSetCard: FC<OwnProps> = ({
if (firstSticker) onClick(firstSticker);
}, [firstSticker, onClick]);
const preview = useMemo(() => {
if (!stickerSet || !stickerSet.stickers) {
return undefined;
}
function renderPreview() {
if (!stickerSet) return undefined;
if (stickerSet.hasThumbnail || !firstSticker) {
return (
@ -47,18 +52,12 @@ const StickerSetCard: FC<OwnProps> = ({
color="translucent"
isRtl={lang.isRtl}
>
{stickerSet.isLottie ? (
<StickerSetCoverAnimated
size={STICKER_SIZE_GENERAL_SETTINGS}
stickerSet={stickerSet}
observeIntersection={observeIntersection}
/>
) : (
<StickerSetCover
stickerSet={stickerSet}
observeIntersection={observeIntersection}
/>
)}
<StickerSetCover
stickerSet={stickerSet}
size={STICKER_SIZE_GENERAL_SETTINGS}
noAnimate={noAnimate}
observeIntersection={observeIntersection}
/>
</Button>
);
} else {
@ -67,17 +66,14 @@ const StickerSetCard: FC<OwnProps> = ({
sticker={firstSticker}
size={STICKER_SIZE_GENERAL_SETTINGS}
title={stickerSet.title}
noAnimate={noAnimate}
observeIntersection={observeIntersection}
clickArg={undefined}
noContextMenu
isCurrentUserPremium
clickArg={undefined}
/>
);
}
}, [firstSticker, lang.isRtl, observeIntersection, stickerSet]);
if (!stickerSet || !stickerSet.stickers) {
return undefined;
}
return (
@ -87,7 +83,7 @@ const StickerSetCard: FC<OwnProps> = ({
inactive={!firstSticker}
onClick={handleCardClick}
>
{preview}
{renderPreview()}
<div className="multiline-menu-item">
<div className="title">{stickerSet.title}</div>
<div className="subtitle">{lang('StickerPack.StickerCount', stickerSet.count, 'i')}</div>

View File

@ -33,7 +33,6 @@ import Button from '../../ui/Button';
import StickerButton from '../../common/StickerButton';
import StickerSet from './StickerSet';
import StickerSetCover from './StickerSetCover';
import StickerSetCoverAnimated from './StickerSetCoverAnimated';
import './StickerPicker.scss';
@ -50,7 +49,7 @@ type StateProps = {
addedCustomEmojiIds?: string[];
recentCustomEmoji: ApiSticker[];
featuredCustomEmojiIds?: string[];
shouldPlay?: boolean;
canAnimate?: boolean;
isSavedMessages?: boolean;
isCurrentUserPremium?: boolean;
};
@ -68,7 +67,7 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
recentCustomEmoji,
stickerSetsById,
featuredCustomEmojiIds,
shouldPlay,
canAnimate,
isSavedMessages,
isCurrentUserPremium,
onCustomEmojiSelect,
@ -199,14 +198,10 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
>
{stickerSet.id === RECENT_SYMBOL_SET_ID ? (
<i className="icon-recent" />
) : stickerSet.isLottie ? (
<StickerSetCoverAnimated
stickerSet={stickerSet as ApiStickerSet}
observeIntersection={observeIntersectionForCovers}
/>
) : (
<StickerSetCover
stickerSet={stickerSet as ApiStickerSet}
noAnimate={!canAnimate || !loadAndPlay}
observeIntersection={observeIntersectionForCovers}
/>
)}
@ -220,11 +215,12 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
size={STICKER_SIZE_PICKER_HEADER}
title={stickerSet.title}
className={buttonClassName}
noAnimate={!canAnimate || !loadAndPlay}
observeIntersection={observeIntersectionForCovers}
onClick={selectStickerSet}
clickArg={index}
noContextMenu
isCurrentUserPremium
onClick={selectStickerSet}
clickArg={index}
/>
);
}
@ -260,14 +256,14 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
<StickerSet
key={stickerSet.id}
stickerSet={stickerSet}
loadAndPlay={Boolean(shouldPlay && loadAndPlay)}
loadAndPlay={Boolean(canAnimate && loadAndPlay)}
index={i}
observeIntersection={observeIntersection}
shouldRender={activeSetIndex >= i - 1 && activeSetIndex <= i + 1}
onStickerSelect={handleEmojiSelect}
isSavedMessages={isSavedMessages}
isCustomEmojiPicker
isCurrentUserPremium={isCurrentUserPremium}
onStickerSelect={handleEmojiSelect}
/>
))}
</div>
@ -288,7 +284,7 @@ export default memo(withGlobal<OwnProps>(
return {
stickerSetsById: setsById,
addedCustomEmojiIds: global.customEmojis.added.setIds,
shouldPlay: global.settings.byKey.shouldLoopStickers,
canAnimate: global.settings.byKey.shouldLoopStickers,
isSavedMessages,
isCurrentUserPremium: selectIsCurrentUserPremium(global),
recentCustomEmoji,

View File

@ -35,7 +35,6 @@ import Button from '../../ui/Button';
import StickerButton from '../../common/StickerButton';
import StickerSet from './StickerSet';
import StickerSetCover from './StickerSetCover';
import StickerSetCoverAnimated from './StickerSetCoverAnimated';
import PremiumIcon from '../../common/PremiumIcon';
import './StickerPicker.scss';
@ -56,7 +55,7 @@ type StateProps = {
premiumStickers: ApiSticker[];
stickerSetsById: Record<string, ApiStickerSet>;
addedSetIds?: string[];
shouldPlay?: boolean;
canAnimate?: boolean;
isSavedMessages?: boolean;
isCurrentUserPremium?: boolean;
};
@ -78,7 +77,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
premiumStickers,
addedSetIds,
stickerSetsById,
shouldPlay,
canAnimate,
isSavedMessages,
isCurrentUserPremium,
onStickerSelect,
@ -286,14 +285,10 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
<i className="icon-favorite" />
) : stickerSet.id === CHAT_STICKER_SET_ID ? (
<Avatar chat={chat} size="small" />
) : stickerSet.isLottie ? (
<StickerSetCoverAnimated
stickerSet={stickerSet as ApiStickerSet}
observeIntersection={observeIntersectionForCovers}
/>
) : (
<StickerSetCover
stickerSet={stickerSet as ApiStickerSet}
noAnimate={!canAnimate || !loadAndPlay}
observeIntersection={observeIntersectionForCovers}
/>
)}
@ -307,11 +302,12 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
size={STICKER_SIZE_PICKER_HEADER}
title={stickerSet.title}
className={buttonClassName}
noAnimate={!canAnimate || !loadAndPlay}
observeIntersection={observeIntersectionForCovers}
onClick={selectStickerSet}
clickArg={index}
noContextMenu
isCurrentUserPremium
onClick={selectStickerSet}
clickArg={index}
/>
);
}
@ -350,17 +346,17 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
<StickerSet
key={stickerSet.id}
stickerSet={stickerSet}
loadAndPlay={Boolean(shouldPlay && loadAndPlay)}
loadAndPlay={Boolean(canAnimate && loadAndPlay)}
index={i}
observeIntersection={observeIntersection}
shouldRender={activeSetIndex >= i - 1 && activeSetIndex <= i + 1}
favoriteStickers={favoriteStickers}
isSavedMessages={isSavedMessages}
isCurrentUserPremium={isCurrentUserPremium}
onStickerSelect={handleStickerSelect}
onStickerUnfave={handleStickerUnfave}
onStickerFave={handleStickerFave}
onStickerRemoveRecent={handleRemoveRecentSticker}
favoriteStickers={favoriteStickers}
isSavedMessages={isSavedMessages}
isCurrentUserPremium={isCurrentUserPremium}
/>
))}
</div>
@ -388,7 +384,7 @@ export default memo(withGlobal<OwnProps>(
premiumStickers: premiumSet.stickers,
stickerSetsById: setsById,
addedSetIds: added.setIds,
shouldPlay: global.settings.byKey.shouldLoopStickers,
canAnimate: global.settings.byKey.shouldLoopStickers,
isSavedMessages,
isCurrentUserPremium: selectIsCurrentUserPremium(global),
};

View File

@ -166,15 +166,15 @@ const StickerSet: FC<OwnProps> = ({
size={itemSize}
observeIntersection={observeIntersection}
noAnimate={!loadAndPlay}
isSavedMessages={isSavedMessages}
canViewSet
isCurrentUserPremium={isCurrentUserPremium}
onClick={onStickerSelect}
clickArg={sticker}
onUnfaveClick={stickerSet.id === FAVORITE_SYMBOL_SET_ID && favoriteStickerIdsSet?.has(sticker.id)
? onStickerUnfave : undefined}
onFaveClick={!favoriteStickerIdsSet?.has(sticker.id) ? onStickerFave : undefined}
onRemoveRecentClick={isRecent ? onStickerRemoveRecent : undefined}
isSavedMessages={isSavedMessages}
canViewSet
isCurrentUserPremium={isCurrentUserPremium}
/>
))}
{!isExpanded && stickerSet.count > itemsBeforeCutout && (

View File

@ -0,0 +1,3 @@
.video {
width: 100%;
}

View File

@ -1,44 +1,73 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
import React, { memo, useRef } from '../../../lib/teact/teact';
import type { ApiStickerSet } from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { STICKER_SIZE_PICKER_HEADER } from '../../../config';
import { IS_WEBM_SUPPORTED } from '../../../util/environment';
import { getFirstLetters } from '../../../util/textFormat';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import buildClassName from '../../../util/buildClassName';
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
import useMedia from '../../../hooks/useMedia';
import useMediaTransition from '../../../hooks/useMediaTransition';
import AnimatedSticker from '../../common/AnimatedSticker';
import OptimizedVideo from '../../ui/OptimizedVideo';
import styles from './StickerSetCover.module.scss';
type OwnProps = {
stickerSet: ApiStickerSet;
size?: number;
noAnimate?: boolean;
observeIntersection: ObserveFn;
};
const StickerSetCover: FC<OwnProps> = ({ stickerSet, observeIntersection }) => {
const StickerSetCover: FC<OwnProps> = ({
stickerSet,
size = STICKER_SIZE_PICKER_HEADER,
noAnimate,
observeIntersection,
}) => {
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
const { hasThumbnail, isLottie, isVideos: isVideo } = stickerSet;
const isIntersecting = useIsIntersecting(ref, observeIntersection);
const mediaData = useMedia(stickerSet.hasThumbnail && `stickerSet${stickerSet.id}`, !isIntersecting);
const transitionClassNames = useMediaTransition(mediaData);
const isVideo = stickerSet.isVideos;
const firstLetters = useMemo(() => {
if ((isVideo && !IS_WEBM_SUPPORTED) || !mediaData) return getFirstLetters(stickerSet.title, 2);
return undefined;
}, [isVideo, mediaData, stickerSet.title]);
const mediaData = useMedia((hasThumbnail || isLottie) && `stickerSet${stickerSet.id}`, !isIntersecting);
const isReady = mediaData && (!isVideo || IS_WEBM_SUPPORTED);
const transitionClassNames = useMediaTransition(isReady);
return (
<div ref={ref} className="sticker-set-cover">
{firstLetters}
{isVideo ? (
<OptimizedVideo canPlay src={mediaData} className={transitionClassNames} loop disablePictureInPicture />
{isReady ? (
isLottie ? (
<AnimatedSticker
className={transitionClassNames}
tgsUrl={mediaData}
size={size}
play={isIntersecting && !noAnimate}
/>
) : isVideo ? (
<OptimizedVideo
className={buildClassName(styles.video, transitionClassNames)}
src={mediaData}
canPlay={isIntersecting && !noAnimate}
loop
disablePictureInPicture
/>
) : (
<img
src={mediaData}
className={transitionClassNames}
alt=""
/>
)
) : (
<img src={mediaData} className={transitionClassNames} alt="" />
getFirstLetters(stickerSet.title, 2)
)}
</div>
);

View File

@ -1,57 +0,0 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
import type { ApiStickerSet } from '../../../api/types';
import { STICKER_SIZE_PICKER_HEADER } from '../../../config';
import { getFirstLetters } from '../../../util/textFormat';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
import useMedia from '../../../hooks/useMedia';
import useMediaTransition from '../../../hooks/useMediaTransition';
import AnimatedSticker from '../../common/AnimatedSticker';
type OwnProps = {
size?: number;
stickerSet: ApiStickerSet;
observeIntersection: ObserveFn;
};
const StickerSetCoverAnimated: FC<OwnProps> = ({
size = STICKER_SIZE_PICKER_HEADER,
stickerSet,
observeIntersection,
}) => {
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
const isIntersecting = useIsIntersecting(ref, observeIntersection);
const mediaHash = `stickerSet${stickerSet.id}`;
const lottieData = useMedia(mediaHash, !isIntersecting);
const transitionClassNames = useMediaTransition(lottieData);
const firstLetters = useMemo(() => {
if (lottieData) return undefined;
return getFirstLetters(stickerSet.title, 2);
}, [lottieData, stickerSet.title]);
return (
<div ref={ref} className="sticker-set-cover">
{firstLetters}
{lottieData && (
<AnimatedSticker
size={size}
tgsUrl={lottieData}
className={transitionClassNames}
play={isIntersecting}
/>
)}
</div>
);
};
export default memo(StickerSetCoverAnimated);