[Refactoring] Animated icons (#1897)
This commit is contained in:
parent
7997dff459
commit
05ca268f6b
@ -17,7 +17,6 @@ import * as cacheApi from '../../../util/cacheApi';
|
|||||||
import { getEntityTypeById } from '../gramjsBuilders';
|
import { getEntityTypeById } from '../gramjsBuilders';
|
||||||
|
|
||||||
const MEDIA_ENTITY_TYPES = new Set(['msg', 'sticker', 'gif', 'wallpaper', 'photo', 'webDocument', 'document']);
|
const MEDIA_ENTITY_TYPES = new Set(['msg', 'sticker', 'gif', 'wallpaper', 'photo', 'webDocument', 'document']);
|
||||||
const TGS_MIME_TYPE = 'application/x-tgsticker';
|
|
||||||
|
|
||||||
export default async function downloadMedia(
|
export default async function downloadMedia(
|
||||||
{
|
{
|
||||||
@ -63,8 +62,8 @@ export default async function downloadMedia(
|
|||||||
|
|
||||||
export type EntityType = (
|
export type EntityType = (
|
||||||
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'channel' | 'chat' | 'user' | 'photo' | 'stickerSet' | 'webDocument' |
|
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'channel' | 'chat' | 'user' | 'photo' | 'stickerSet' | 'webDocument' |
|
||||||
'document'
|
'document' | 'staticMap'
|
||||||
);
|
);
|
||||||
|
|
||||||
async function download(
|
async function download(
|
||||||
url: string,
|
url: string,
|
||||||
@ -84,12 +83,6 @@ async function download(
|
|||||||
entityType, entityId, sizeType, params, mediaMatchType,
|
entityType, entityId, sizeType, params, mediaMatchType,
|
||||||
} = parsed;
|
} = parsed;
|
||||||
|
|
||||||
if (entityType === 'file') {
|
|
||||||
const response = await fetch(entityId);
|
|
||||||
const data = await response.arrayBuffer();
|
|
||||||
return { data };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
return Promise.reject(new Error('ERROR: Client is not connected'));
|
return Promise.reject(new Error('ERROR: Client is not connected'));
|
||||||
}
|
}
|
||||||
@ -192,7 +185,7 @@ async function download(
|
|||||||
return { mimeType, data, fullSize };
|
return { mimeType, data, fullSize };
|
||||||
} else if (entityType === 'stickerSet') {
|
} else if (entityType === 'stickerSet') {
|
||||||
const data = await client.downloadStickerSetThumb(entity);
|
const data = await client.downloadStickerSetThumb(entity);
|
||||||
const mimeType = mediaFormat === ApiMediaFormat.Lottie ? TGS_MIME_TYPE : getMimeType(data);
|
const mimeType = getMimeType(data);
|
||||||
|
|
||||||
return { mimeType, data };
|
return { mimeType, data };
|
||||||
} else {
|
} else {
|
||||||
@ -247,15 +240,11 @@ async function parseMedia(
|
|||||||
): Promise<ApiParsedMedia | undefined> {
|
): Promise<ApiParsedMedia | undefined> {
|
||||||
switch (mediaFormat) {
|
switch (mediaFormat) {
|
||||||
case ApiMediaFormat.BlobUrl:
|
case ApiMediaFormat.BlobUrl:
|
||||||
case ApiMediaFormat.Lottie: {
|
|
||||||
return new Blob([data], { type: mimeType });
|
return new Blob([data], { type: mimeType });
|
||||||
}
|
case ApiMediaFormat.Text:
|
||||||
case ApiMediaFormat.Text: {
|
|
||||||
return data.toString();
|
return data.toString();
|
||||||
}
|
case ApiMediaFormat.Progressive:
|
||||||
case ApiMediaFormat.Progressive: {
|
|
||||||
return data.buffer;
|
return data.buffer;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -307,7 +296,7 @@ export function parseMediaUrl(url: string) {
|
|||||||
: url.startsWith('webDocument')
|
: url.startsWith('webDocument')
|
||||||
? url.match(/(webDocument):(.+)/)
|
? url.match(/(webDocument):(.+)/)
|
||||||
: url.match(
|
: url.match(
|
||||||
/(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|file|document)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/,
|
/(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|document)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/,
|
||||||
);
|
);
|
||||||
if (!mediaMatch) {
|
if (!mediaMatch) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -316,14 +305,6 @@ export function parseMediaUrl(url: string) {
|
|||||||
const mediaMatchType = mediaMatch[1];
|
const mediaMatchType = mediaMatch[1];
|
||||||
const entityId: string | number = mediaMatch[2];
|
const entityId: string | number = mediaMatch[2];
|
||||||
|
|
||||||
if (mediaMatchType === 'file') {
|
|
||||||
return {
|
|
||||||
mediaMatchType,
|
|
||||||
entityType: 'file',
|
|
||||||
entityId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let entityType: EntityType;
|
let entityType: EntityType;
|
||||||
const params = mediaMatch[3];
|
const params = mediaMatch[3];
|
||||||
const sizeType = params?.replace('?size=', '') || undefined;
|
const sizeType = params?.replace('?size=', '') || undefined;
|
||||||
@ -331,9 +312,7 @@ export function parseMediaUrl(url: string) {
|
|||||||
if (mediaMatch[1] === 'avatar' || mediaMatch[1] === 'profile') {
|
if (mediaMatch[1] === 'avatar' || mediaMatch[1] === 'profile') {
|
||||||
entityType = getEntityTypeById(entityId);
|
entityType = getEntityTypeById(entityId);
|
||||||
} else {
|
} else {
|
||||||
entityType = mediaMatch[1] as (
|
entityType = mediaMatch[1] as EntityType;
|
||||||
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'stickerSet' | 'photo' | 'webDocument' | 'document'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
export enum ApiMediaFormat {
|
export enum ApiMediaFormat {
|
||||||
BlobUrl,
|
BlobUrl,
|
||||||
Lottie,
|
|
||||||
Progressive,
|
Progressive,
|
||||||
Stream,
|
Stream,
|
||||||
Text,
|
Text,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import QrCodeStyling from 'qr-code-styling';
|
import QrCodeStyling from 'qr-code-styling';
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useEffect, useRef, memo, useCallback, useState,
|
useEffect, useRef, memo, useCallback,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -9,11 +9,11 @@ import type { GlobalState } from '../../global/types';
|
|||||||
import type { LangCode } from '../../types';
|
import type { LangCode } from '../../types';
|
||||||
|
|
||||||
import { DEFAULT_LANG_CODE } from '../../config';
|
import { DEFAULT_LANG_CODE } from '../../config';
|
||||||
|
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
|
||||||
import { setLanguage } from '../../util/langProvider';
|
import { setLanguage } from '../../util/langProvider';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
|
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
|
||||||
import getAnimationData from '../common/helpers/animatedAssets';
|
|
||||||
|
|
||||||
import useLangString from '../../hooks/useLangString';
|
import useLangString from '../../hooks/useLangString';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -22,8 +22,8 @@ import useMediaTransition from '../../hooks/useMediaTransition';
|
|||||||
|
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import AnimatedSticker from '../common/AnimatedSticker';
|
|
||||||
import blankUrl from '../../assets/blank.png';
|
import blankUrl from '../../assets/blank.png';
|
||||||
|
import AnimatedIcon from '../common/AnimatedIcon';
|
||||||
|
|
||||||
type StateProps =
|
type StateProps =
|
||||||
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
|
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
|
||||||
@ -73,17 +73,7 @@ const AuthCode: FC<StateProps> = ({
|
|||||||
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
|
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
|
||||||
const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag();
|
const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag();
|
||||||
|
|
||||||
const [animationData, setAnimationData] = useState<string>();
|
|
||||||
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
|
|
||||||
|
|
||||||
const transitionClassNames = useMediaTransition(isQrMounted);
|
const transitionClassNames = useMediaTransition(isQrMounted);
|
||||||
const airplaneTransitionClassNames = useMediaTransition(isAnimationLoaded);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!animationData) {
|
|
||||||
getAnimationData('QrPlane').then(setAnimationData);
|
|
||||||
}
|
|
||||||
}, [animationData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authQrCode) {
|
if (!authQrCode) {
|
||||||
@ -142,17 +132,13 @@ const AuthCode: FC<StateProps> = ({
|
|||||||
ref={qrCodeRef}
|
ref={qrCodeRef}
|
||||||
style={`width: ${QR_SIZE}px; height: ${QR_SIZE}px`}
|
style={`width: ${QR_SIZE}px; height: ${QR_SIZE}px`}
|
||||||
/>
|
/>
|
||||||
{animationData && (
|
<AnimatedIcon
|
||||||
<AnimatedSticker
|
tgsUrl={LOCAL_TGS_URLS.QrPlane}
|
||||||
id="qrPlane"
|
size={QR_PLANE_SIZE}
|
||||||
className={buildClassName('qr-plane', airplaneTransitionClassNames)}
|
className="qr-plane"
|
||||||
size={QR_PLANE_SIZE}
|
nonInteractive
|
||||||
animationData={animationData}
|
noLoop={false}
|
||||||
play={isAnimationLoaded}
|
/>
|
||||||
onLoad={markAnimationLoaded}
|
|
||||||
key="qrPlane"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{!isQrMounted && <div className="qr-loading"><Loading /></div>}
|
{!isQrMounted && <div className="qr-loading"><Loading /></div>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
IS_REQUEST_FULLSCREEN_SUPPORTED,
|
IS_REQUEST_FULLSCREEN_SUPPORTED,
|
||||||
IS_SINGLE_COLUMN_LAYOUT,
|
IS_SINGLE_COLUMN_LAYOUT,
|
||||||
} from '../../../util/environment';
|
} from '../../../util/environment';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import {
|
import {
|
||||||
selectGroupCall,
|
selectGroupCall,
|
||||||
@ -338,7 +339,11 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="video-buttons">
|
<div className="video-buttons">
|
||||||
{hasVideo && (IS_ANDROID || IS_IOS) && (
|
{hasVideo && (IS_ANDROID || IS_IOS) && (
|
||||||
<button className="smaller-button" onClick={switchCameraInput}>
|
<button className="smaller-button" onClick={switchCameraInput}>
|
||||||
<AnimatedIcon name="CameraFlip" playSegment={CAMERA_FLIP_PLAY_SEGMENT} size={24} />
|
<AnimatedIcon
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.CameraFlip}
|
||||||
|
playSegment={CAMERA_FLIP_PLAY_SEGMENT}
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -7,13 +7,14 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import type { IAnchorPosition } from '../../../types';
|
import type { IAnchorPosition } from '../../../types';
|
||||||
|
|
||||||
|
import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import buildStyle from '../../../util/buildStyle';
|
import buildStyle from '../../../util/buildStyle';
|
||||||
import useRunThrottled from '../../../hooks/useRunThrottled';
|
import useRunThrottled from '../../../hooks/useRunThrottled';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import { selectIsAdminInActiveGroupCall } from '../../../global/selectors/calls';
|
import { selectIsAdminInActiveGroupCall } from '../../../global/selectors/calls';
|
||||||
import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config';
|
|
||||||
|
|
||||||
import Menu from '../../ui/Menu';
|
import Menu from '../../ui/Menu';
|
||||||
import MenuItem from '../../ui/MenuItem';
|
import MenuItem from '../../ui/MenuItem';
|
||||||
@ -171,7 +172,7 @@ const GroupCallParticipantMenu: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<AnimatedIcon
|
<AnimatedIcon
|
||||||
name="Speaker"
|
tgsUrl={LOCAL_TGS_URLS.Speaker}
|
||||||
playSegment={speakerIconPlaySegment}
|
playSegment={speakerIconPlaySegment}
|
||||||
size={SPEAKER_ICON_SIZE}
|
size={SPEAKER_ICON_SIZE}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import React, {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { vibrateShort } from '../../../util/vibrate';
|
import { vibrateShort } from '../../../util/vibrate';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
@ -147,7 +148,7 @@ const MicrophoneButton: FC<StateProps> = ({
|
|||||||
onMouseUp={handleMouseUpMute}
|
onMouseUp={handleMouseUpMute}
|
||||||
>
|
>
|
||||||
<AnimatedIcon
|
<AnimatedIcon
|
||||||
name={animatedIconName}
|
tgsUrl={LOCAL_TGS_URLS[animatedIconName]}
|
||||||
size={ICON_SIZE}
|
size={ICON_SIZE}
|
||||||
playSegment={playSegment}
|
playSegment={playSegment}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,9 +2,12 @@ import type { GroupCallParticipant } from '../../../lib/secret-sauce';
|
|||||||
import { THRESHOLD } from '../../../lib/secret-sauce';
|
import { THRESHOLD } from '../../../lib/secret-sauce';
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
import AnimatedIcon from '../../common/AnimatedIcon';
|
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
|
|
||||||
|
import AnimatedIcon from '../../common/AnimatedIcon';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
participant: GroupCallParticipant;
|
participant: GroupCallParticipant;
|
||||||
noColor?: boolean;
|
noColor?: boolean;
|
||||||
@ -59,7 +62,7 @@ const OutlinedMicrophoneIcon: FC<OwnProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedIcon
|
<AnimatedIcon
|
||||||
name="VoiceOutlined"
|
tgsUrl={LOCAL_TGS_URLS.VoiceOutlined}
|
||||||
playSegment={playSegment}
|
playSegment={playSegment}
|
||||||
size={28}
|
size={28}
|
||||||
color={microphoneColor}
|
color={microphoneColor}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
IS_REQUEST_FULLSCREEN_SUPPORTED,
|
IS_REQUEST_FULLSCREEN_SUPPORTED,
|
||||||
IS_SINGLE_COLUMN_LAYOUT,
|
IS_SINGLE_COLUMN_LAYOUT,
|
||||||
} from '../../../util/environment';
|
} from '../../../util/environment';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { selectPhoneCallUser } from '../../../global/selectors/calls';
|
import { selectPhoneCallUser } from '../../../global/selectors/calls';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -311,9 +312,13 @@ const PhoneCall: FC<StateProps> = ({
|
|||||||
{hasOwnVideo && (IS_ANDROID || IS_IOS) && (
|
{hasOwnVideo && (IS_ANDROID || IS_IOS) && (
|
||||||
<PhoneCallButton
|
<PhoneCallButton
|
||||||
onClick={handleFlipCamera}
|
onClick={handleFlipCamera}
|
||||||
customIcon={
|
customIcon={(
|
||||||
<AnimatedIcon name="CameraFlip" playSegment={!isFlipping ? [0, 1] : [0, 10]} size={32} />
|
<AnimatedIcon
|
||||||
}
|
tgsUrl={LOCAL_TGS_URLS.CameraFlip}
|
||||||
|
playSegment={!isFlipping ? [0, 1] : [0, 10]}
|
||||||
|
size={32}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
isDisabled={!isActive}
|
isDisabled={!isActive}
|
||||||
label={lang('VoipFlip')}
|
label={lang('VoipFlip')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,105 +0,0 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
|
||||||
import React, { memo } from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type { ApiSticker } from '../../api/types';
|
|
||||||
import { ApiMediaFormat } from '../../api/types';
|
|
||||||
import type { ActiveEmojiInteraction } from '../../global/types';
|
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
|
||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
|
||||||
import useMedia from '../../hooks/useMedia';
|
|
||||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
|
||||||
import useAnimatedEmoji from './hooks/useAnimatedEmoji';
|
|
||||||
import { LIKE_STICKER_ID } from './helpers/mediaDimensions';
|
|
||||||
|
|
||||||
import AnimatedSticker from './AnimatedSticker';
|
|
||||||
|
|
||||||
import './AnimatedEmoji.scss';
|
|
||||||
|
|
||||||
type OwnProps = {
|
|
||||||
sticker?: ApiSticker;
|
|
||||||
effect?: ApiSticker;
|
|
||||||
isOwn?: boolean;
|
|
||||||
soundId?: string;
|
|
||||||
observeIntersection?: ObserveFn;
|
|
||||||
size?: 'large' | 'medium' | 'small';
|
|
||||||
lastSyncTime?: number;
|
|
||||||
forceLoadPreview?: boolean;
|
|
||||||
messageId?: number;
|
|
||||||
chatId?: string;
|
|
||||||
activeEmojiInteractions?: ActiveEmojiInteraction[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const QUALITY = 1;
|
|
||||||
|
|
||||||
const AnimatedEmoji: FC<OwnProps> = ({
|
|
||||||
sticker,
|
|
||||||
effect,
|
|
||||||
isOwn,
|
|
||||||
soundId,
|
|
||||||
size = 'medium',
|
|
||||||
observeIntersection,
|
|
||||||
lastSyncTime,
|
|
||||||
forceLoadPreview,
|
|
||||||
messageId,
|
|
||||||
chatId,
|
|
||||||
activeEmojiInteractions,
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
markAnimationLoaded,
|
|
||||||
isAnimationLoaded,
|
|
||||||
ref,
|
|
||||||
width,
|
|
||||||
style,
|
|
||||||
handleClick,
|
|
||||||
playKey,
|
|
||||||
} = useAnimatedEmoji(size, chatId, messageId, soundId, activeEmojiInteractions, isOwn, undefined, effect?.emoji);
|
|
||||||
|
|
||||||
const localMediaHash = `sticker${sticker?.id}`;
|
|
||||||
|
|
||||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
|
||||||
|
|
||||||
const thumbDataUri = sticker?.thumbnail?.dataUri;
|
|
||||||
const previewBlobUrl = useMedia(
|
|
||||||
sticker ? `${localMediaHash}?size=m` : undefined,
|
|
||||||
!isIntersecting && !forceLoadPreview,
|
|
||||||
ApiMediaFormat.BlobUrl,
|
|
||||||
lastSyncTime,
|
|
||||||
);
|
|
||||||
const transitionClassNames = useMediaTransition(previewBlobUrl);
|
|
||||||
|
|
||||||
const mediaData = useMedia(localMediaHash, !isIntersecting, ApiMediaFormat.Lottie, lastSyncTime);
|
|
||||||
const isMediaLoaded = Boolean(mediaData);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={ref}
|
|
||||||
className={buildClassName('AnimatedEmoji media-inner', sticker?.id === LIKE_STICKER_ID && 'like-sticker-thumb')}
|
|
||||||
style={style}
|
|
||||||
onClick={handleClick}
|
|
||||||
>
|
|
||||||
{!isAnimationLoaded && thumbDataUri && (
|
|
||||||
<img src={thumbDataUri} alt="" />
|
|
||||||
)}
|
|
||||||
{!isAnimationLoaded && previewBlobUrl && (
|
|
||||||
<img src={previewBlobUrl} className={transitionClassNames} alt="" />
|
|
||||||
)}
|
|
||||||
{isMediaLoaded && localMediaHash && (
|
|
||||||
<AnimatedSticker
|
|
||||||
key={localMediaHash}
|
|
||||||
id={localMediaHash}
|
|
||||||
animationData={mediaData!}
|
|
||||||
size={width}
|
|
||||||
quality={QUALITY}
|
|
||||||
play={isIntersecting && playKey}
|
|
||||||
forceOnHeavyAnimation
|
|
||||||
noLoop
|
|
||||||
onLoad={markAnimationLoaded}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(AnimatedEmoji);
|
|
||||||
@ -1,42 +1,70 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import React, {
|
||||||
import React, { memo, useEffect, useState } from '../../lib/teact/teact';
|
memo, useCallback, useState,
|
||||||
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ANIMATED_STICKERS_PATHS } from './helpers/animatedAssets';
|
import type { OwnProps as AnimatedStickerProps } from './AnimatedSticker';
|
||||||
import getAnimationData from './helpers/animatedAssets';
|
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||||
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
|
||||||
import AnimatedSticker from './AnimatedSticker';
|
import AnimatedSticker from './AnimatedSticker';
|
||||||
|
|
||||||
type OwnProps = {
|
const DEFAULT_SIZE = 150;
|
||||||
name: keyof typeof ANIMATED_STICKERS_PATHS;
|
|
||||||
size: number;
|
|
||||||
playSegment?: [number, number];
|
|
||||||
color?: [number, number, number];
|
|
||||||
};
|
|
||||||
|
|
||||||
const AnimatedIcon: FC<OwnProps> = ({
|
export type OwnProps =
|
||||||
size,
|
Partial<AnimatedStickerProps>
|
||||||
name,
|
& { noTransition?: boolean; nonInteractive?: boolean };
|
||||||
playSegment,
|
|
||||||
color,
|
|
||||||
}) => {
|
|
||||||
const [iconData, setIconData] = useState<string>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
const loadedAnimationUrls = new Set();
|
||||||
getAnimationData(name).then(setIconData);
|
|
||||||
}, [name]);
|
function AnimatedIcon(props: OwnProps) {
|
||||||
|
const {
|
||||||
|
size = DEFAULT_SIZE,
|
||||||
|
play = true,
|
||||||
|
noLoop = true,
|
||||||
|
className,
|
||||||
|
noTransition,
|
||||||
|
nonInteractive,
|
||||||
|
onLoad,
|
||||||
|
onClick,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
const { tgsUrl } = props;
|
||||||
|
|
||||||
|
const key = `${tgsUrl}_${size}`;
|
||||||
|
const [isAnimationLoaded, markAnimationLoaded] = useFlag(loadedAnimationUrls.has(key));
|
||||||
|
const transitionClassNames = useMediaTransition(noTransition || isAnimationLoaded);
|
||||||
|
|
||||||
|
const handleLoad = useCallback(() => {
|
||||||
|
markAnimationLoaded();
|
||||||
|
loadedAnimationUrls.add(key);
|
||||||
|
|
||||||
|
onLoad?.();
|
||||||
|
}, [key, markAnimationLoaded, onLoad]);
|
||||||
|
|
||||||
|
const [playKey, setPlayKey] = useState(String(Math.random()));
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
if (play === true) {
|
||||||
|
setPlayKey(String(Math.random()));
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick?.();
|
||||||
|
}, [onClick, play]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
id={name}
|
className={buildClassName(className, transitionClassNames, 'shown')}
|
||||||
play
|
|
||||||
noLoop
|
|
||||||
playSegment={playSegment}
|
|
||||||
size={size}
|
size={size}
|
||||||
speed={1}
|
play={play === true ? playKey : play}
|
||||||
animationData={iconData}
|
noLoop={noLoop}
|
||||||
color={color}
|
onClick={!nonInteractive ? handleClick : undefined}
|
||||||
|
onLoad={handleLoad}
|
||||||
|
/* eslint-disable-next-line react/jsx-props-no-spreading */
|
||||||
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default memo(AnimatedIcon);
|
export default memo(AnimatedIcon);
|
||||||
|
|||||||
41
src/components/common/AnimatedIconFromSticker.tsx
Normal file
41
src/components/common/AnimatedIconFromSticker.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps as AnimatedIconProps } from './AnimatedIcon';
|
||||||
|
import type { ApiSticker } from '../../api/types';
|
||||||
|
import { ApiMediaFormat } from '../../api/types';
|
||||||
|
|
||||||
|
import useMedia from '../../hooks/useMedia';
|
||||||
|
|
||||||
|
import AnimatedIconWithPreview from './AnimatedIconWithPreview';
|
||||||
|
|
||||||
|
type OwnProps =
|
||||||
|
Partial<AnimatedIconProps>
|
||||||
|
& { sticker?: ApiSticker; noLoad?: boolean; forcePreview?: boolean; lastSyncTime?: number };
|
||||||
|
|
||||||
|
function AnimatedIconFromSticker(props: OwnProps) {
|
||||||
|
const {
|
||||||
|
sticker, noLoad, forcePreview, lastSyncTime, ...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const thumbDataUri = sticker?.thumbnail?.dataUri;
|
||||||
|
const localMediaHash = `sticker${sticker?.id}`;
|
||||||
|
const previewBlobUrl = useMedia(
|
||||||
|
sticker ? `${localMediaHash}?size=m` : undefined,
|
||||||
|
noLoad && !forcePreview,
|
||||||
|
ApiMediaFormat.BlobUrl,
|
||||||
|
lastSyncTime,
|
||||||
|
);
|
||||||
|
const tgsUrl = useMedia(localMediaHash, noLoad, undefined, lastSyncTime);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedIconWithPreview
|
||||||
|
tgsUrl={tgsUrl}
|
||||||
|
previewUrl={previewBlobUrl}
|
||||||
|
thumbDataUri={thumbDataUri}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(AnimatedIconFromSticker);
|
||||||
11
src/components/common/AnimatedIconWithPreview.module.scss
Normal file
11
src/components/common/AnimatedIconWithPreview.module.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
45
src/components/common/AnimatedIconWithPreview.tsx
Normal file
45
src/components/common/AnimatedIconWithPreview.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps as AnimatedIconProps } from './AnimatedIcon';
|
||||||
|
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||||
|
import AnimatedIcon from './AnimatedIcon';
|
||||||
|
import styles from './AnimatedIconWithPreview.module.scss';
|
||||||
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
import buildStyle from '../../util/buildStyle';
|
||||||
|
|
||||||
|
type OwnProps =
|
||||||
|
Partial<AnimatedIconProps>
|
||||||
|
& { previewUrl?: string; thumbDataUri?: string };
|
||||||
|
|
||||||
|
function AnimatedIconWithPreview(props: OwnProps) {
|
||||||
|
const {
|
||||||
|
previewUrl, thumbDataUri, className, ...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const transitionClassNames = useMediaTransition(previewUrl);
|
||||||
|
const [isAnimationReady, markAnimationReady] = useFlag(false);
|
||||||
|
const { size } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName(className, styles.root)}
|
||||||
|
style={buildStyle(size !== undefined && `width: ${size}px; height: ${size}px;`)}
|
||||||
|
>
|
||||||
|
{!isAnimationReady && thumbDataUri && (
|
||||||
|
// eslint-disable-next-line jsx-a11y/alt-text
|
||||||
|
<img src={thumbDataUri} className={buildClassName(styles.preview)} />
|
||||||
|
)}
|
||||||
|
{!isAnimationReady && (
|
||||||
|
// eslint-disable-next-line jsx-a11y/alt-text
|
||||||
|
<img src={previewUrl} className={buildClassName(styles.preview, transitionClassNames)} />
|
||||||
|
)}
|
||||||
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
|
<AnimatedIcon {...otherProps} onLoad={markAnimationReady} noTransition />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(AnimatedIconWithPreview);
|
||||||
@ -1,17 +1,21 @@
|
|||||||
|
import type { RefObject } from 'react';
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
useEffect, useRef, memo, useCallback, useState,
|
useEffect, useRef, memo, useCallback, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import { fastRaf } from '../../util/schedulers';
|
import { fastRaf } from '../../util/schedulers';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
import buildStyle from '../../util/buildStyle';
|
||||||
import useHeavyAnimationCheck from '../../hooks/useHeavyAnimationCheck';
|
import useHeavyAnimationCheck from '../../hooks/useHeavyAnimationCheck';
|
||||||
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
||||||
|
|
||||||
type OwnProps = {
|
export type OwnProps = {
|
||||||
|
ref?: RefObject<HTMLDivElement>;
|
||||||
className?: string;
|
className?: string;
|
||||||
id: string;
|
style?: string;
|
||||||
animationData?: string;
|
tgsUrl?: string;
|
||||||
play?: boolean | string;
|
play?: boolean | string;
|
||||||
playSegment?: [number, number];
|
playSegment?: [number, number];
|
||||||
speed?: number;
|
speed?: number;
|
||||||
@ -19,9 +23,10 @@ type OwnProps = {
|
|||||||
size: number;
|
size: number;
|
||||||
quality?: number;
|
quality?: number;
|
||||||
isLowPriority?: boolean;
|
isLowPriority?: boolean;
|
||||||
onLoad?: NoneToVoidFunction;
|
|
||||||
forceOnHeavyAnimation?: boolean;
|
forceOnHeavyAnimation?: boolean;
|
||||||
color?: [number, number, number];
|
color?: [number, number, number];
|
||||||
|
onClick?: NoneToVoidFunction;
|
||||||
|
onLoad?: NoneToVoidFunction;
|
||||||
onEnded?: NoneToVoidFunction;
|
onEnded?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,9 +50,10 @@ async function ensureLottie() {
|
|||||||
setTimeout(ensureLottie, LOTTIE_LOAD_DELAY);
|
setTimeout(ensureLottie, LOTTIE_LOAD_DELAY);
|
||||||
|
|
||||||
const AnimatedSticker: FC<OwnProps> = ({
|
const AnimatedSticker: FC<OwnProps> = ({
|
||||||
|
ref,
|
||||||
className,
|
className,
|
||||||
id,
|
style,
|
||||||
animationData,
|
tgsUrl,
|
||||||
play,
|
play,
|
||||||
playSegment,
|
playSegment,
|
||||||
speed,
|
speed,
|
||||||
@ -57,12 +63,17 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
isLowPriority,
|
isLowPriority,
|
||||||
color,
|
color,
|
||||||
forceOnHeavyAnimation,
|
forceOnHeavyAnimation,
|
||||||
|
onClick,
|
||||||
onLoad,
|
onLoad,
|
||||||
onEnded,
|
onEnded,
|
||||||
}) => {
|
}) => {
|
||||||
const [animation, setAnimation] = useState<RLottieInstance>();
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const container = useRef<HTMLDivElement>(null);
|
let containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
if (ref) {
|
||||||
|
containerRef = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [animation, setAnimation] = useState<RLottieInstance>();
|
||||||
const wasPlaying = useRef(false);
|
const wasPlaying = useRef(false);
|
||||||
const isFrozen = useRef(false);
|
const isFrozen = useRef(false);
|
||||||
const isFirstRender = useRef(true);
|
const isFirstRender = useRef(true);
|
||||||
@ -73,19 +84,18 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
playSegmentRef.current = playSegment;
|
playSegmentRef.current = playSegment;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (animation || !animationData) {
|
if (animation || !tgsUrl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exec = () => {
|
const exec = () => {
|
||||||
if (!container.current) {
|
if (!containerRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newAnimation = new RLottie(
|
const newAnimation = new RLottie(
|
||||||
id,
|
containerRef.current,
|
||||||
container.current,
|
tgsUrl,
|
||||||
animationData,
|
|
||||||
{
|
{
|
||||||
noLoop,
|
noLoop,
|
||||||
size,
|
size,
|
||||||
@ -109,13 +119,13 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
ensureLottie().then(() => {
|
ensureLottie().then(() => {
|
||||||
fastRaf(() => {
|
fastRaf(() => {
|
||||||
if (container.current) {
|
if (containerRef.current) {
|
||||||
exec();
|
exec();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [color, animation, animationData, id, isLowPriority, noLoop, onLoad, quality, size, speed, onEnded]);
|
}, [color, animation, tgsUrl, isLowPriority, noLoop, onLoad, quality, size, speed, onEnded]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!animation) return;
|
if (!animation) return;
|
||||||
@ -135,10 +145,8 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
if (animation && (playRef.current || playSegmentRef.current)) {
|
if (animation && (playRef.current || playSegmentRef.current)) {
|
||||||
if (playSegmentRef.current) {
|
if (playSegmentRef.current) {
|
||||||
animation.playSegment(playSegmentRef.current);
|
animation.playSegment(playSegmentRef.current);
|
||||||
} else if (shouldRestart) {
|
|
||||||
animation.goToAndPlay(0);
|
|
||||||
} else {
|
} else {
|
||||||
animation.play();
|
animation.play(shouldRestart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [animation]);
|
}, [animation]);
|
||||||
@ -167,12 +175,12 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const unfreezeAnimation = useCallback(() => {
|
const unfreezeAnimation = useCallback(() => {
|
||||||
if (wasPlaying.current) {
|
if (wasPlaying.current) {
|
||||||
playAnimation();
|
playAnimation(noLoop);
|
||||||
}
|
}
|
||||||
|
|
||||||
wasPlaying.current = false;
|
wasPlaying.current = false;
|
||||||
isFrozen.current = false;
|
isFrozen.current = false;
|
||||||
}, [playAnimation]);
|
}, [noLoop, playAnimation]);
|
||||||
|
|
||||||
const unfreezeAnimationOnRaf = useCallback(() => {
|
const unfreezeAnimationOnRaf = useCallback(() => {
|
||||||
fastRaf(unfreezeAnimation);
|
fastRaf(unfreezeAnimation);
|
||||||
@ -203,12 +211,12 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
if (animation) {
|
if (animation) {
|
||||||
if (isFirstRender.current) {
|
if (isFirstRender.current) {
|
||||||
isFirstRender.current = false;
|
isFirstRender.current = false;
|
||||||
} else if (animationData) {
|
} else if (tgsUrl) {
|
||||||
animation.changeData(animationData);
|
animation.changeData(tgsUrl);
|
||||||
playAnimation();
|
playAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [playAnimation, animation, animationData]);
|
}, [playAnimation, animation, tgsUrl]);
|
||||||
|
|
||||||
useHeavyAnimationCheck(freezeAnimation, unfreezeAnimation, forceOnHeavyAnimation);
|
useHeavyAnimationCheck(freezeAnimation, unfreezeAnimation, forceOnHeavyAnimation);
|
||||||
// Pausing frame may not happen in background
|
// Pausing frame may not happen in background
|
||||||
@ -216,15 +224,16 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
// then we can play again.
|
// then we can play again.
|
||||||
useBackgroundMode(freezeAnimation, unfreezeAnimationOnRaf);
|
useBackgroundMode(freezeAnimation, unfreezeAnimationOnRaf);
|
||||||
|
|
||||||
const fullClassName = buildClassName('AnimatedSticker', className);
|
|
||||||
|
|
||||||
const style = size ? `width: ${size}px; height: ${size}px;` : undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={container}
|
ref={containerRef}
|
||||||
className={fullClassName}
|
className={buildClassName('AnimatedSticker', className)}
|
||||||
style={style}
|
style={buildStyle(
|
||||||
|
size !== undefined && `width: ${size}px; height: ${size}px;`,
|
||||||
|
onClick && 'cursor: pointer',
|
||||||
|
style,
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
|
||||||
import React, { memo, useEffect, useState } from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type { ActiveEmojiInteraction } from '../../global/types';
|
|
||||||
|
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
|
||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
|
||||||
import type { ANIMATED_STICKERS_PATHS } from './helpers/animatedAssets';
|
|
||||||
import getAnimationData from './helpers/animatedAssets';
|
|
||||||
import useAnimatedEmoji from './hooks/useAnimatedEmoji';
|
|
||||||
|
|
||||||
import AnimatedSticker from './AnimatedSticker';
|
|
||||||
|
|
||||||
const QUALITY = 1;
|
|
||||||
|
|
||||||
type OwnProps = {
|
|
||||||
localSticker?: string;
|
|
||||||
localEffect?: string;
|
|
||||||
isOwn?: boolean;
|
|
||||||
soundId?: string;
|
|
||||||
observeIntersection?: ObserveFn;
|
|
||||||
size?: 'large' | 'medium' | 'small';
|
|
||||||
lastSyncTime?: number;
|
|
||||||
forceLoadPreview?: boolean;
|
|
||||||
messageId?: number;
|
|
||||||
chatId?: string;
|
|
||||||
activeEmojiInteractions?: ActiveEmojiInteraction[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const LocalAnimatedEmoji: FC<OwnProps> = ({
|
|
||||||
localSticker,
|
|
||||||
localEffect,
|
|
||||||
isOwn,
|
|
||||||
soundId,
|
|
||||||
size = 'medium',
|
|
||||||
observeIntersection,
|
|
||||||
messageId,
|
|
||||||
chatId,
|
|
||||||
activeEmojiInteractions,
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
playKey,
|
|
||||||
ref,
|
|
||||||
style,
|
|
||||||
width,
|
|
||||||
handleClick,
|
|
||||||
markAnimationLoaded,
|
|
||||||
} = useAnimatedEmoji(size, chatId, messageId, soundId, activeEmojiInteractions, isOwn, localEffect);
|
|
||||||
const id = `local_emoji_${localSticker}`;
|
|
||||||
|
|
||||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
|
||||||
|
|
||||||
const [localStickerAnimationData, setLocalStickerAnimationData] = useState<string>();
|
|
||||||
useEffect(() => {
|
|
||||||
if (localSticker) {
|
|
||||||
getAnimationData(localSticker as keyof typeof ANIMATED_STICKERS_PATHS).then((data) => {
|
|
||||||
setLocalStickerAnimationData(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [localSticker]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={ref}
|
|
||||||
className="AnimatedEmoji media-inner"
|
|
||||||
style={style}
|
|
||||||
onClick={handleClick}
|
|
||||||
>
|
|
||||||
{localStickerAnimationData && (
|
|
||||||
<AnimatedSticker
|
|
||||||
key={id}
|
|
||||||
id={id}
|
|
||||||
animationData={localStickerAnimationData}
|
|
||||||
size={width}
|
|
||||||
quality={QUALITY}
|
|
||||||
play={isIntersecting && playKey}
|
|
||||||
forceOnHeavyAnimation
|
|
||||||
noLoop
|
|
||||||
onLoad={markAnimationLoaded}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(LocalAnimatedEmoji);
|
|
||||||
@ -1,14 +1,15 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, { useCallback, memo } from '../../lib/teact/teact';
|
||||||
useState, useEffect, useCallback, memo,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import { STICKER_SIZE_AUTH, STICKER_SIZE_AUTH_MOBILE, STICKER_SIZE_TWO_FA } from '../../config';
|
import { STICKER_SIZE_AUTH, STICKER_SIZE_AUTH_MOBILE, STICKER_SIZE_TWO_FA } from '../../config';
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||||
import getAnimationData from './helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from './helpers/animatedAssets';
|
||||||
|
|
||||||
import AnimatedSticker from './AnimatedSticker';
|
import AnimatedSticker from './AnimatedSticker';
|
||||||
|
|
||||||
|
import useTimeout from '../../hooks/useTimeout';
|
||||||
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
|
||||||
import './PasswordMonkey.scss';
|
import './PasswordMonkey.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@ -23,53 +24,32 @@ const SEGMENT_COVER_EYE: [number, number] = [20, 0];
|
|||||||
const STICKER_SIZE = IS_SINGLE_COLUMN_LAYOUT ? STICKER_SIZE_AUTH_MOBILE : STICKER_SIZE_AUTH;
|
const STICKER_SIZE = IS_SINGLE_COLUMN_LAYOUT ? STICKER_SIZE_AUTH_MOBILE : STICKER_SIZE_AUTH;
|
||||||
|
|
||||||
const PasswordMonkey: FC<OwnProps> = ({ isPasswordVisible, isBig }) => {
|
const PasswordMonkey: FC<OwnProps> = ({ isPasswordVisible, isBig }) => {
|
||||||
const [closeMonkeyData, setCloseMonkeyData] = useState<string>();
|
const [isFirstMonkeyLoaded, markFirstMonkeyLoaded] = useFlag(false);
|
||||||
const [peekMonkeyData, setPeekMonkeyData] = useState<string>();
|
const [isPeekShown, markPeekShown] = useFlag(false);
|
||||||
const [isFirstMonkeyLoaded, setIsFirstMonkeyLoaded] = useState(false);
|
|
||||||
const [isPeekShown, setIsPeekShown] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useTimeout(markPeekShown, PEEK_MONKEY_SHOW_DELAY);
|
||||||
if (!closeMonkeyData) {
|
const handleFirstMonkeyLoad = useCallback(markFirstMonkeyLoaded, [markFirstMonkeyLoaded]);
|
||||||
getAnimationData('MonkeyClose').then(setCloseMonkeyData);
|
|
||||||
} else {
|
|
||||||
setTimeout(() => setIsPeekShown(true), PEEK_MONKEY_SHOW_DELAY);
|
|
||||||
}
|
|
||||||
}, [closeMonkeyData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!peekMonkeyData) {
|
|
||||||
getAnimationData('MonkeyPeek').then(setPeekMonkeyData);
|
|
||||||
}
|
|
||||||
}, [peekMonkeyData]);
|
|
||||||
|
|
||||||
const handleFirstMonkeyLoad = useCallback(() => setIsFirstMonkeyLoaded(true), []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="monkey" className={isBig ? 'big' : ''}>
|
<div id="monkey" className={isBig ? 'big' : ''}>
|
||||||
{!isFirstMonkeyLoaded && (
|
{!isFirstMonkeyLoaded && (
|
||||||
<div className="monkey-preview" />
|
<div className="monkey-preview" />
|
||||||
)}
|
)}
|
||||||
{closeMonkeyData && (
|
<AnimatedSticker
|
||||||
<AnimatedSticker
|
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
||||||
id="closeMonkey"
|
className={isPeekShown ? 'hidden' : 'shown'}
|
||||||
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
tgsUrl={LOCAL_TGS_URLS.MonkeyClose}
|
||||||
className={isPeekShown ? 'hidden' : 'shown'}
|
playSegment={SEGMENT_COVER_EYES}
|
||||||
animationData={closeMonkeyData}
|
noLoop
|
||||||
playSegment={SEGMENT_COVER_EYES}
|
onLoad={handleFirstMonkeyLoad}
|
||||||
noLoop
|
/>
|
||||||
onLoad={handleFirstMonkeyLoad}
|
<AnimatedSticker
|
||||||
/>
|
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
||||||
)}
|
className={isPeekShown ? 'shown' : 'hidden'}
|
||||||
{peekMonkeyData && (
|
tgsUrl={LOCAL_TGS_URLS.MonkeyPeek}
|
||||||
<AnimatedSticker
|
playSegment={isPasswordVisible ? SEGMENT_UNCOVER_EYE : SEGMENT_COVER_EYE}
|
||||||
id="peekMonkey"
|
noLoop
|
||||||
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
/>
|
||||||
className={isPeekShown ? 'shown' : 'hidden'}
|
|
||||||
animationData={peekMonkeyData}
|
|
||||||
playSegment={isPasswordVisible ? SEGMENT_UNCOVER_EYE : SEGMENT_COVER_EYE}
|
|
||||||
noLoop
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -75,7 +75,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
|||||||
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`, !isIntersecting, ApiMediaFormat.BlobUrl);
|
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`, !isIntersecting, ApiMediaFormat.BlobUrl);
|
||||||
|
|
||||||
const shouldPlay = isIntersecting && !noAnimate;
|
const shouldPlay = isIntersecting && !noAnimate;
|
||||||
const lottieData = useMedia(sticker.isLottie && localMediaHash, !shouldPlay, ApiMediaFormat.Lottie);
|
const lottieData = useMedia(sticker.isLottie && localMediaHash, !shouldPlay);
|
||||||
const [isLottieLoaded, markLoaded, unmarkLoaded] = useFlag(Boolean(lottieData));
|
const [isLottieLoaded, markLoaded, unmarkLoaded] = useFlag(Boolean(lottieData));
|
||||||
const canLottiePlay = isLottieLoaded && shouldPlay;
|
const canLottiePlay = isLottieLoaded && shouldPlay;
|
||||||
const isVideo = sticker.isVideo && IS_WEBM_SUPPORTED;
|
const isVideo = sticker.isVideo && IS_WEBM_SUPPORTED;
|
||||||
@ -217,8 +217,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
|||||||
)}
|
)}
|
||||||
{shouldPlay && lottieData && (
|
{shouldPlay && lottieData && (
|
||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
id={localMediaHash}
|
tgsUrl={lottieData}
|
||||||
animationData={lottieData}
|
|
||||||
play
|
play
|
||||||
size={size}
|
size={size}
|
||||||
isLowPriority
|
isLowPriority
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, { useState, useCallback, memo } from '../../lib/teact/teact';
|
||||||
useState, useEffect, useCallback, memo,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import { STICKER_SIZE_AUTH, STICKER_SIZE_AUTH_MOBILE, STICKER_SIZE_TWO_FA } from '../../config';
|
import { STICKER_SIZE_AUTH, STICKER_SIZE_AUTH_MOBILE, STICKER_SIZE_TWO_FA } from '../../config';
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||||
import getAnimationData from './helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from './helpers/animatedAssets';
|
||||||
|
|
||||||
import AnimatedSticker from './AnimatedSticker';
|
import AnimatedSticker from './AnimatedSticker';
|
||||||
|
|
||||||
@ -30,23 +28,9 @@ const TrackingMonkey: FC<OwnProps> = ({
|
|||||||
isTracking,
|
isTracking,
|
||||||
isBig,
|
isBig,
|
||||||
}) => {
|
}) => {
|
||||||
const [idleMonkeyData, setIdleMonkeyData] = useState<string>();
|
|
||||||
const [trackingMonkeyData, setTrackingMonkeyData] = useState<string>();
|
|
||||||
const [isFirstMonkeyLoaded, setIsFirstMonkeyLoaded] = useState(false);
|
const [isFirstMonkeyLoaded, setIsFirstMonkeyLoaded] = useState(false);
|
||||||
const TRACKING_FRAMES_PER_SYMBOL = (TRACKING_END_FRAME - TRACKING_START_FRAME) / codeLength;
|
const TRACKING_FRAMES_PER_SYMBOL = (TRACKING_END_FRAME - TRACKING_START_FRAME) / codeLength;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!idleMonkeyData) {
|
|
||||||
getAnimationData('MonkeyIdle').then(setIdleMonkeyData);
|
|
||||||
}
|
|
||||||
}, [idleMonkeyData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!trackingMonkeyData) {
|
|
||||||
getAnimationData('MonkeyTracking').then(setTrackingMonkeyData);
|
|
||||||
}
|
|
||||||
}, [trackingMonkeyData]);
|
|
||||||
|
|
||||||
const handleFirstMonkeyLoad = useCallback(() => setIsFirstMonkeyLoaded(true), []);
|
const handleFirstMonkeyLoad = useCallback(() => setIsFirstMonkeyLoaded(true), []);
|
||||||
|
|
||||||
function getTrackingFrames(): [number, number] {
|
function getTrackingFrames(): [number, number] {
|
||||||
@ -75,27 +59,21 @@ const TrackingMonkey: FC<OwnProps> = ({
|
|||||||
{!isFirstMonkeyLoaded && (
|
{!isFirstMonkeyLoaded && (
|
||||||
<div className="monkey-preview" />
|
<div className="monkey-preview" />
|
||||||
)}
|
)}
|
||||||
{idleMonkeyData && (
|
<AnimatedSticker
|
||||||
<AnimatedSticker
|
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
||||||
id="idleMonkey"
|
className={isTracking ? 'hidden' : undefined}
|
||||||
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
tgsUrl={LOCAL_TGS_URLS.MonkeyIdle}
|
||||||
className={isTracking ? 'hidden' : undefined}
|
play={!isTracking}
|
||||||
animationData={idleMonkeyData}
|
onLoad={handleFirstMonkeyLoad}
|
||||||
play={!isTracking}
|
/>
|
||||||
onLoad={handleFirstMonkeyLoad}
|
<AnimatedSticker
|
||||||
/>
|
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
||||||
)}
|
className={!isTracking ? 'hidden' : 'shown'}
|
||||||
{trackingMonkeyData && (
|
tgsUrl={LOCAL_TGS_URLS.MonkeyTracking}
|
||||||
<AnimatedSticker
|
playSegment={isTracking ? getTrackingFrames() : undefined}
|
||||||
id="trackingMonkey"
|
speed={2}
|
||||||
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
noLoop
|
||||||
className={!isTracking ? 'hidden' : 'shown'}
|
/>
|
||||||
animationData={trackingMonkeyData}
|
|
||||||
playSegment={isTracking ? getTrackingFrames() : undefined}
|
|
||||||
speed={2}
|
|
||||||
noLoop
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
import { ApiMediaFormat } from '../../../api/types';
|
|
||||||
|
|
||||||
import * as mediaLoader from '../../../util/mediaLoader';
|
|
||||||
|
|
||||||
import MonkeyIdle from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyIdle.tgs';
|
import MonkeyIdle from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyIdle.tgs';
|
||||||
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
|
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
|
||||||
import MonkeyClose from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyClose.tgs';
|
import MonkeyClose from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyClose.tgs';
|
||||||
@ -31,7 +27,7 @@ import Invite from '../../../assets/tgs/invites/Invite.tgs';
|
|||||||
|
|
||||||
import QrPlane from '../../../assets/tgs/auth/QrPlane.tgs';
|
import QrPlane from '../../../assets/tgs/auth/QrPlane.tgs';
|
||||||
|
|
||||||
export const ANIMATED_STICKERS_PATHS = {
|
export const LOCAL_TGS_URLS = {
|
||||||
MonkeyIdle,
|
MonkeyIdle,
|
||||||
MonkeyTracking,
|
MonkeyTracking,
|
||||||
MonkeyClose,
|
MonkeyClose,
|
||||||
@ -56,9 +52,3 @@ export const ANIMATED_STICKERS_PATHS = {
|
|||||||
QrPlane,
|
QrPlane,
|
||||||
Congratulations,
|
Congratulations,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function getAnimationData(name: keyof typeof ANIMATED_STICKERS_PATHS) {
|
|
||||||
const path = ANIMATED_STICKERS_PATHS[name].replace(window.location.origin, '');
|
|
||||||
|
|
||||||
return mediaLoader.fetch(`file${path}`, ApiMediaFormat.Lottie);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,24 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
useCallback, useEffect, useRef, useState,
|
useCallback, useEffect, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import safePlay from '../../../util/safePlay';
|
import safePlay from '../../../util/safePlay';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
import type { ActiveEmojiInteraction } from '../../../global/types';
|
import type { ActiveEmojiInteraction } from '../../../global/types';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
|
||||||
import { selectLocalAnimatedEmojiEffectByName } from '../../../global/selectors';
|
import { selectLocalAnimatedEmojiEffectByName } from '../../../global/selectors';
|
||||||
|
|
||||||
const WIDTH = {
|
const SIZE = 104;
|
||||||
large: 160,
|
|
||||||
medium: 128,
|
|
||||||
small: 104,
|
|
||||||
};
|
|
||||||
const INTERACTION_BUNCH_TIME = 1000;
|
const INTERACTION_BUNCH_TIME = 1000;
|
||||||
const MS_DIVIDER = 1000;
|
const MS_DIVIDER = 1000;
|
||||||
const TIME_DEFAULT = 0;
|
const TIME_DEFAULT = 0;
|
||||||
|
|
||||||
export default function useAnimatedEmoji(
|
export default function useAnimatedEmoji(
|
||||||
size: 'large' | 'medium' | 'small',
|
|
||||||
chatId?: string,
|
chatId?: string,
|
||||||
messageId?: number,
|
messageId?: number,
|
||||||
soundId?: string,
|
soundId?: string,
|
||||||
@ -32,7 +26,6 @@ export default function useAnimatedEmoji(
|
|||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const hasEffect = localEffect || emoji;
|
const hasEffect = localEffect || emoji;
|
||||||
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@ -42,10 +35,8 @@ export default function useAnimatedEmoji(
|
|||||||
|
|
||||||
const soundMediaData = useMedia(soundId ? `document${soundId}` : undefined, !soundId);
|
const soundMediaData = useMedia(soundId ? `document${soundId}` : undefined, !soundId);
|
||||||
|
|
||||||
const width = WIDTH[size];
|
const style = `width: ${SIZE}px; height: ${SIZE}px;`;
|
||||||
const style = `width: ${width}px; height: ${width}px;`;
|
|
||||||
|
|
||||||
const [playKey, setPlayKey] = useState(String(Math.random()));
|
|
||||||
const interactions = useRef<number[] | undefined>(undefined);
|
const interactions = useRef<number[] | undefined>(undefined);
|
||||||
const startedInteractions = useRef<number | undefined>(undefined);
|
const startedInteractions = useRef<number | undefined>(undefined);
|
||||||
const sendInteractionBunch = useCallback(() => {
|
const sendInteractionBunch = useCallback(() => {
|
||||||
@ -65,8 +56,6 @@ export default function useAnimatedEmoji(
|
|||||||
}, [sendEmojiInteraction, chatId, messageId, localEffect, emoji]);
|
}, [sendEmojiInteraction, chatId, messageId, localEffect, emoji]);
|
||||||
|
|
||||||
const play = useCallback(() => {
|
const play = useCallback(() => {
|
||||||
setPlayKey(String(Math.random()));
|
|
||||||
|
|
||||||
const audio = audioRef.current;
|
const audio = audioRef.current;
|
||||||
if (soundMediaData) {
|
if (soundMediaData) {
|
||||||
if (audio) {
|
if (audio) {
|
||||||
@ -98,7 +87,7 @@ export default function useAnimatedEmoji(
|
|||||||
emoji,
|
emoji,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
startSize: width,
|
startSize: SIZE,
|
||||||
isReversed: !isOwn,
|
isReversed: !isOwn,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -113,7 +102,7 @@ export default function useAnimatedEmoji(
|
|||||||
: TIME_DEFAULT);
|
: TIME_DEFAULT);
|
||||||
}, [
|
}, [
|
||||||
chatId, emoji, hasEffect, interactWithAnimatedEmoji, isOwn,
|
chatId, emoji, hasEffect, interactWithAnimatedEmoji, isOwn,
|
||||||
localEffect, messageId, play, sendInteractionBunch, width,
|
localEffect, messageId, play, sendInteractionBunch,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Set an end anchor for remote activated interaction
|
// Set an end anchor for remote activated interaction
|
||||||
@ -137,7 +126,7 @@ export default function useAnimatedEmoji(
|
|||||||
id,
|
id,
|
||||||
chatId,
|
chatId,
|
||||||
emoticon: localEffect ? selectLocalAnimatedEmojiEffectByName(localEffect) : emoji,
|
emoticon: localEffect ? selectLocalAnimatedEmojiEffectByName(localEffect) : emoji,
|
||||||
startSize: width,
|
startSize: SIZE,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
isReversed: !isOwn,
|
isReversed: !isOwn,
|
||||||
@ -145,16 +134,13 @@ export default function useAnimatedEmoji(
|
|||||||
play();
|
play();
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
activeEmojiInteractions, chatId, emoji, isOwn, localEffect, messageId, play, sendWatchingEmojiInteraction, width,
|
activeEmojiInteractions, chatId, emoji, isOwn, localEffect, messageId, play, sendWatchingEmojiInteraction,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
playKey,
|
|
||||||
ref,
|
ref,
|
||||||
|
size: SIZE,
|
||||||
style,
|
style,
|
||||||
width,
|
|
||||||
handleClick,
|
handleClick,
|
||||||
markAnimationLoaded,
|
|
||||||
isAnimationLoaded,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { selectAnimatedEmoji, selectChatFolder } from '../../../global/selectors
|
|||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import AnimatedEmoji from '../../common/AnimatedEmoji';
|
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
|
||||||
|
|
||||||
import './EmptyFolder.scss';
|
import './EmptyFolder.scss';
|
||||||
|
|
||||||
@ -27,6 +27,8 @@ type StateProps = {
|
|||||||
animatedEmoji?: ApiSticker;
|
animatedEmoji?: ApiSticker;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ICON_SIZE = 128;
|
||||||
|
|
||||||
const EmptyFolder: FC<OwnProps & StateProps> = ({
|
const EmptyFolder: FC<OwnProps & StateProps> = ({
|
||||||
chatFolder, animatedEmoji, foldersDispatch, onScreenSelect,
|
chatFolder, animatedEmoji, foldersDispatch, onScreenSelect,
|
||||||
}) => {
|
}) => {
|
||||||
@ -39,7 +41,9 @@ const EmptyFolder: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="EmptyFolder">
|
<div className="EmptyFolder">
|
||||||
<div className="sticker">{animatedEmoji && <AnimatedEmoji sticker={animatedEmoji} />}</div>
|
<div className="sticker">
|
||||||
|
{animatedEmoji && <AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} />}
|
||||||
|
</div>
|
||||||
<h3 className="title" dir="auto">{lang('FilterNoChatsToDisplay')}</h3>
|
<h3 className="title" dir="auto">{lang('FilterNoChatsToDisplay')}</h3>
|
||||||
<p className="description" dir="auto">
|
<p className="description" dir="auto">
|
||||||
{lang(chatFolder ? 'ChatList.EmptyChatListFilterText' : 'Chat.EmptyChat')}
|
{lang(chatFolder ? 'ChatList.EmptyChatListFilterText' : 'Chat.EmptyChat')}
|
||||||
|
|||||||
@ -90,22 +90,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.settings-content-icon {
|
.settings-content-icon {
|
||||||
width: 5rem;
|
|
||||||
height: 5rem;
|
|
||||||
margin-bottom: 2.5rem;
|
margin-bottom: 2.5rem;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.AnimatedSticker {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-main-menu {
|
.settings-main-menu {
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
import { findIntersectionWithSet } from '../../../../util/iteratees';
|
import { findIntersectionWithSet } from '../../../../util/iteratees';
|
||||||
import { isUserId } from '../../../../global/helpers';
|
import { isUserId } from '../../../../global/helpers';
|
||||||
import getAnimationData from '../../../common/helpers/animatedAssets';
|
|
||||||
import type {
|
import type {
|
||||||
FolderEditDispatch,
|
FolderEditDispatch,
|
||||||
FoldersState,
|
FoldersState,
|
||||||
@ -21,13 +21,13 @@ import useLang from '../../../../hooks/useLang';
|
|||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
import ListItem from '../../../ui/ListItem';
|
import ListItem from '../../../ui/ListItem';
|
||||||
import AnimatedSticker from '../../../common/AnimatedSticker';
|
|
||||||
import InputText from '../../../ui/InputText';
|
import InputText from '../../../ui/InputText';
|
||||||
import PrivateChatInfo from '../../../common/PrivateChatInfo';
|
import PrivateChatInfo from '../../../common/PrivateChatInfo';
|
||||||
import GroupChatInfo from '../../../common/GroupChatInfo';
|
import GroupChatInfo from '../../../common/GroupChatInfo';
|
||||||
import FloatingActionButton from '../../../ui/FloatingActionButton';
|
import FloatingActionButton from '../../../ui/FloatingActionButton';
|
||||||
import Spinner from '../../../ui/Spinner';
|
import Spinner from '../../../ui/Spinner';
|
||||||
import ShowMoreButton from '../../../ui/ShowMoreButton';
|
import ShowMoreButton from '../../../ui/ShowMoreButton';
|
||||||
|
import AnimatedIcon from '../../../common/AnimatedIcon';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
state: FoldersState;
|
state: FoldersState;
|
||||||
@ -67,10 +67,6 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
addChatFolder,
|
addChatFolder,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const [animationData, setAnimationData] = useState<string>();
|
|
||||||
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
|
|
||||||
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
|
|
||||||
|
|
||||||
const [isIncludedChatsListExpanded, setIsIncludedChatsListExpanded] = useState(false);
|
const [isIncludedChatsListExpanded, setIsIncludedChatsListExpanded] = useState(false);
|
||||||
const [isExcludedChatsListExpanded, setIsExcludedChatsListExpanded] = useState(false);
|
const [isExcludedChatsListExpanded, setIsExcludedChatsListExpanded] = useState(false);
|
||||||
|
|
||||||
@ -83,12 +79,6 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
selectedChatTypes: excludedChatTypes,
|
selectedChatTypes: excludedChatTypes,
|
||||||
} = selectChatFilters(state, 'excluded');
|
} = selectChatFilters(state, 'excluded');
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!animationData) {
|
|
||||||
getAnimationData('FoldersNew').then(setAnimationData);
|
|
||||||
}
|
|
||||||
}, [animationData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsIncludedChatsListExpanded(false);
|
setIsIncludedChatsListExpanded(false);
|
||||||
setIsExcludedChatsListExpanded(false);
|
setIsExcludedChatsListExpanded(false);
|
||||||
@ -219,18 +209,12 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="settings-fab-wrapper">
|
<div className="settings-fab-wrapper">
|
||||||
<div className="settings-content no-border custom-scroll">
|
<div className="settings-content no-border custom-scroll">
|
||||||
<div className="settings-content-header">
|
<div className="settings-content-header">
|
||||||
<div className="settings-content-icon">
|
<AnimatedIcon
|
||||||
{animationData && (
|
size={STICKER_SIZE_FOLDER_SETTINGS}
|
||||||
<AnimatedSticker
|
tgsUrl={LOCAL_TGS_URLS.FoldersNew}
|
||||||
id="settingsFoldersEdit"
|
play={String(state.folderId)}
|
||||||
size={STICKER_SIZE_FOLDER_SETTINGS}
|
className="settings-content-icon"
|
||||||
animationData={animationData}
|
/>
|
||||||
play={isAnimationLoaded && String(state.folderId)}
|
|
||||||
noLoop
|
|
||||||
onLoad={handleAnimationLoad}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{state.mode === 'create' && (
|
{state.mode === 'create' && (
|
||||||
<p className="settings-item-description mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
|
<p className="settings-item-description mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import type { FC } from '../../../../lib/teact/teact';
|
import type { FC } from '../../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useMemo, useCallback, useState, useEffect,
|
memo, useMemo, useCallback, useEffect,
|
||||||
} from '../../../../lib/teact/teact';
|
} from '../../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
import type { ApiChatFolder } from '../../../../api/types';
|
import type { ApiChatFolder } from '../../../../api/types';
|
||||||
|
|
||||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
import { throttle } from '../../../../util/schedulers';
|
import { throttle } from '../../../../util/schedulers';
|
||||||
import getAnimationData from '../../../common/helpers/animatedAssets';
|
|
||||||
import { getFolderDescriptionText } from '../../../../global/helpers';
|
import { getFolderDescriptionText } from '../../../../global/helpers';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
@ -17,7 +17,7 @@ import { useFolderManagerForChatsCount } from '../../../../hooks/useFolderManage
|
|||||||
import ListItem from '../../../ui/ListItem';
|
import ListItem from '../../../ui/ListItem';
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../../ui/Button';
|
||||||
import Loading from '../../../ui/Loading';
|
import Loading from '../../../ui/Loading';
|
||||||
import AnimatedSticker from '../../../common/AnimatedSticker';
|
import AnimatedIcon from '../../../common/AnimatedIcon';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
@ -51,16 +51,6 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
showDialog,
|
showDialog,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const [animationData, setAnimationData] = useState<string>();
|
|
||||||
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
|
|
||||||
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!animationData) {
|
|
||||||
getAnimationData('FoldersAll').then(setAnimationData);
|
|
||||||
}
|
|
||||||
}, [animationData]);
|
|
||||||
|
|
||||||
// Due to the parent Transition, this component never gets unmounted,
|
// Due to the parent Transition, this component never gets unmounted,
|
||||||
// that's why we use throttled API call on every update.
|
// that's why we use throttled API call on every update.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -126,18 +116,11 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content no-border custom-scroll">
|
<div className="settings-content no-border custom-scroll">
|
||||||
<div className="settings-content-header">
|
<div className="settings-content-header">
|
||||||
<div className="settings-content-icon">
|
<AnimatedIcon
|
||||||
{animationData && (
|
size={STICKER_SIZE_FOLDER_SETTINGS}
|
||||||
<AnimatedSticker
|
tgsUrl={LOCAL_TGS_URLS.FoldersAll}
|
||||||
id="settingsFoldersMain"
|
className="settings-content-icon"
|
||||||
size={STICKER_SIZE_FOLDER_SETTINGS}
|
/>
|
||||||
animationData={animationData}
|
|
||||||
play={isAnimationLoaded}
|
|
||||||
noLoop
|
|
||||||
onLoad={handleAnimationLoad}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="settings-item-description mb-3" dir="auto">
|
<p className="settings-item-description mb-3" dir="auto">
|
||||||
{lang('CreateNewFilterInfo')}
|
{lang('CreateNewFilterInfo')}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { FC } from '../../../../lib/teact/teact';
|
|||||||
import React, { memo, useCallback } from '../../../../lib/teact/teact';
|
import React, { memo, useCallback } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
import { STICKER_SIZE_PASSCODE } from '../../../../config';
|
import { STICKER_SIZE_PASSCODE } from '../../../../config';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
@ -27,7 +28,11 @@ const SettingsPasscodeCongratulations: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content local-passcode custom-scroll">
|
<div className="settings-content local-passcode custom-scroll">
|
||||||
<div className="settings-content-header no-border">
|
<div className="settings-content-header no-border">
|
||||||
<AnimatedIcon size={STICKER_SIZE_PASSCODE} name="Congratulations" />
|
<AnimatedIcon
|
||||||
|
size={STICKER_SIZE_PASSCODE}
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.Congratulations}
|
||||||
|
className="settings-content-icon"
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="settings-item-description mb-3" dir="auto">
|
<p className="settings-item-description mb-3" dir="auto">
|
||||||
Congratulations!
|
Congratulations!
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import type { FC } from '../../../../lib/teact/teact';
|
import type { FC } from '../../../../lib/teact/teact';
|
||||||
import React, { memo } from '../../../../lib/teact/teact';
|
import React, { memo } from '../../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../../global';
|
|
||||||
|
|
||||||
import type { ApiSticker } from '../../../../api/types';
|
|
||||||
import { SettingsScreens } from '../../../../types';
|
import { SettingsScreens } from '../../../../types';
|
||||||
|
|
||||||
import { selectAnimatedEmoji } from '../../../../global/selectors';
|
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
import ListItem from '../../../ui/ListItem';
|
import ListItem from '../../../ui/ListItem';
|
||||||
import AnimatedEmoji from '../../../common/AnimatedEmoji';
|
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
|
||||||
|
|
||||||
|
import lockPreviewUrl from '../../../../assets/lock.png';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
@ -18,12 +18,8 @@ type OwnProps = {
|
|||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
const SettingsPasscodeEnabled: FC<OwnProps> = ({
|
||||||
animatedEmoji: ApiSticker;
|
isActive, onReset, onScreenSelect,
|
||||||
};
|
|
||||||
|
|
||||||
const SettingsPasscodeEnabled: FC<OwnProps & StateProps> = ({
|
|
||||||
isActive, onReset, animatedEmoji, onScreenSelect,
|
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -32,7 +28,12 @@ const SettingsPasscodeEnabled: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content local-passcode custom-scroll">
|
<div className="settings-content local-passcode custom-scroll">
|
||||||
<div className="settings-content-header no-border">
|
<div className="settings-content-header no-border">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
<AnimatedIconWithPreview
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.Lock}
|
||||||
|
previewUrl={lockPreviewUrl}
|
||||||
|
size={160}
|
||||||
|
className="settings-content-icon"
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="settings-item-description mb-3" dir="auto">
|
<p className="settings-item-description mb-3" dir="auto">
|
||||||
Local passcode is enabled.
|
Local passcode is enabled.
|
||||||
@ -59,8 +60,4 @@ const SettingsPasscodeEnabled: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>((global) => {
|
export default memo(SettingsPasscodeEnabled);
|
||||||
return {
|
|
||||||
animatedEmoji: selectAnimatedEmoji(global, '🔐'),
|
|
||||||
};
|
|
||||||
})(SettingsPasscodeEnabled));
|
|
||||||
|
|||||||
@ -2,11 +2,14 @@ import type { FC } from '../../../../lib/teact/teact';
|
|||||||
import React, { memo } from '../../../../lib/teact/teact';
|
import React, { memo } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
import { STICKER_SIZE_PASSCODE } from '../../../../config';
|
import { STICKER_SIZE_PASSCODE } from '../../../../config';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../../ui/Button';
|
||||||
import AnimatedIcon from '../../../common/AnimatedIcon';
|
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
|
||||||
|
|
||||||
|
import lockPreviewUrl from '../../../../assets/lock.png';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onStart: NoneToVoidFunction;
|
onStart: NoneToVoidFunction;
|
||||||
@ -24,7 +27,12 @@ const SettingsPasscodeStart: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content local-passcode custom-scroll">
|
<div className="settings-content local-passcode custom-scroll">
|
||||||
<div className="settings-content-header no-border">
|
<div className="settings-content-header no-border">
|
||||||
<AnimatedIcon size={STICKER_SIZE_PASSCODE} name="Lock" />
|
<AnimatedIconWithPreview
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.Lock}
|
||||||
|
previewUrl={lockPreviewUrl}
|
||||||
|
size={STICKER_SIZE_PASSCODE}
|
||||||
|
className="settings-content-icon"
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="settings-item-description" dir="auto">
|
<p className="settings-item-description" dir="auto">
|
||||||
When you set up an additional passcode, a lock icon will appear on the chats page.
|
When you set up an additional passcode, a lock icon will appear on the chats page.
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import React, { memo, useCallback } from '../../../../lib/teact/teact';
|
|||||||
import { SettingsScreens } from '../../../../types';
|
import { SettingsScreens } from '../../../../types';
|
||||||
|
|
||||||
import { STICKER_SIZE_TWO_FA } from '../../../../config';
|
import { STICKER_SIZE_TWO_FA } from '../../../../config';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
@ -33,7 +34,11 @@ const SettingsTwoFaCongratulations: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header no-border">
|
<div className="settings-content-header no-border">
|
||||||
<AnimatedIcon size={STICKER_SIZE_TWO_FA} name="Congratulations" />
|
<AnimatedIcon
|
||||||
|
size={STICKER_SIZE_TWO_FA}
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.Congratulations}
|
||||||
|
className="settings-content-icon"
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="settings-item-description mb-3" dir="auto">
|
<p className="settings-item-description mb-3" dir="auto">
|
||||||
{lang('TwoStepVerificationPasswordSetInfo')}
|
{lang('TwoStepVerificationPasswordSetInfo')}
|
||||||
|
|||||||
@ -11,9 +11,9 @@ import { selectAnimatedEmoji } from '../../../../global/selectors';
|
|||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
import AnimatedEmoji from '../../../common/AnimatedEmoji';
|
|
||||||
import InputText from '../../../ui/InputText';
|
import InputText from '../../../ui/InputText';
|
||||||
import Loading from '../../../ui/Loading';
|
import Loading from '../../../ui/Loading';
|
||||||
|
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
@ -30,6 +30,7 @@ type StateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const FOCUS_DELAY_TIMEOUT_MS = IS_SINGLE_COLUMN_LAYOUT ? 550 : 400;
|
const FOCUS_DELAY_TIMEOUT_MS = IS_SINGLE_COLUMN_LAYOUT ? 550 : 400;
|
||||||
|
const ICON_SIZE = 160;
|
||||||
|
|
||||||
const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
||||||
animatedEmoji,
|
animatedEmoji,
|
||||||
@ -79,7 +80,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header no-border">
|
<div className="settings-content-header no-border">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
<AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} className="settings-content-icon" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="settings-item pt-0">
|
<div className="settings-item pt-0">
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import type { FC } from '../../../../lib/teact/teact';
|
import type { FC } from '../../../../lib/teact/teact';
|
||||||
import React, { memo } from '../../../../lib/teact/teact';
|
import React, { memo } from '../../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../../global';
|
|
||||||
|
|
||||||
import type { ApiSticker } from '../../../../api/types';
|
|
||||||
import { SettingsScreens } from '../../../../types';
|
import { SettingsScreens } from '../../../../types';
|
||||||
|
|
||||||
import { selectAnimatedEmoji } from '../../../../global/selectors';
|
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
import ListItem from '../../../ui/ListItem';
|
import ListItem from '../../../ui/ListItem';
|
||||||
import AnimatedEmoji from '../../../common/AnimatedEmoji';
|
|
||||||
import renderText from '../../../common/helpers/renderText';
|
import renderText from '../../../common/helpers/renderText';
|
||||||
|
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
|
||||||
|
|
||||||
|
import lockPreviewUrl from '../../../../assets/lock.png';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
@ -19,12 +19,8 @@ type OwnProps = {
|
|||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
const SettingsTwoFaEnabled: FC<OwnProps> = ({
|
||||||
animatedEmoji: ApiSticker;
|
isActive, onReset, onScreenSelect,
|
||||||
};
|
|
||||||
|
|
||||||
const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
|
|
||||||
isActive, onReset, animatedEmoji, onScreenSelect,
|
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -36,7 +32,12 @@ const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header no-border">
|
<div className="settings-content-header no-border">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
<AnimatedIconWithPreview
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.Lock}
|
||||||
|
previewUrl={lockPreviewUrl}
|
||||||
|
size={160}
|
||||||
|
className="settings-content-icon"
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="settings-item-description mb-3" dir="auto">
|
<p className="settings-item-description mb-3" dir="auto">
|
||||||
{renderText(lang('EnabledPasswordText'), ['br'])}
|
{renderText(lang('EnabledPasswordText'), ['br'])}
|
||||||
@ -70,8 +71,4 @@ const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>((global) => {
|
export default memo(SettingsTwoFaEnabled);
|
||||||
return {
|
|
||||||
animatedEmoji: selectAnimatedEmoji(global, '🔐'),
|
|
||||||
};
|
|
||||||
})(SettingsTwoFaEnabled));
|
|
||||||
|
|||||||
@ -8,15 +8,15 @@ import type { ApiSticker } from '../../../../api/types';
|
|||||||
|
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../../util/environment';
|
||||||
import { selectAnimatedEmoji } from '../../../../global/selectors';
|
import { selectAnimatedEmoji } from '../../../../global/selectors';
|
||||||
|
import renderText from '../../../common/helpers/renderText';
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../../ui/Button';
|
||||||
import Modal from '../../../ui/Modal';
|
import Modal from '../../../ui/Modal';
|
||||||
import AnimatedEmoji from '../../../common/AnimatedEmoji';
|
|
||||||
import InputText from '../../../ui/InputText';
|
import InputText from '../../../ui/InputText';
|
||||||
import renderText from '../../../common/helpers/renderText';
|
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
icon: 'hint' | 'email';
|
icon: 'hint' | 'email';
|
||||||
@ -36,6 +36,7 @@ type StateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const FOCUS_DELAY_TIMEOUT_MS = IS_SINGLE_COLUMN_LAYOUT ? 550 : 400;
|
const FOCUS_DELAY_TIMEOUT_MS = IS_SINGLE_COLUMN_LAYOUT ? 550 : 400;
|
||||||
|
const ICON_SIZE = 160;
|
||||||
|
|
||||||
const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
||||||
animatedEmoji,
|
animatedEmoji,
|
||||||
@ -100,7 +101,7 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header no-border">
|
<div className="settings-content-header no-border">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
<AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} className="settings-content-icon" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="settings-item pt-0">
|
<div className="settings-item pt-0">
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import type { FC } from '../../../../lib/teact/teact';
|
import type { FC } from '../../../../lib/teact/teact';
|
||||||
import React, { memo } from '../../../../lib/teact/teact';
|
import React, { memo } from '../../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../../global';
|
|
||||||
|
|
||||||
import type { ApiSticker } from '../../../../api/types';
|
|
||||||
|
|
||||||
import { selectAnimatedEmoji } from '../../../../global/selectors';
|
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../../ui/Button';
|
||||||
import AnimatedEmoji from '../../../common/AnimatedEmoji';
|
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
|
||||||
|
|
||||||
|
import lockPreviewUrl from '../../../../assets/lock.png';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onStart: NoneToVoidFunction;
|
onStart: NoneToVoidFunction;
|
||||||
@ -17,12 +16,8 @@ type OwnProps = {
|
|||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
const SettingsTwoFaStart: FC<OwnProps> = ({
|
||||||
animatedEmoji: ApiSticker;
|
isActive, onReset, onStart,
|
||||||
};
|
|
||||||
|
|
||||||
const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({
|
|
||||||
isActive, onReset, animatedEmoji, onStart,
|
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -34,7 +29,12 @@ const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="settings-content two-fa custom-scroll">
|
<div className="settings-content two-fa custom-scroll">
|
||||||
<div className="settings-content-header no-border">
|
<div className="settings-content-header no-border">
|
||||||
<AnimatedEmoji sticker={animatedEmoji} size="large" />
|
<AnimatedIconWithPreview
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.Lock}
|
||||||
|
previewUrl={lockPreviewUrl}
|
||||||
|
size={160}
|
||||||
|
className="settings-content-icon"
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="settings-item-description mb-3" dir="auto">
|
<p className="settings-item-description mb-3" dir="auto">
|
||||||
{lang('SetAdditionalPasswordInfo')}
|
{lang('SetAdditionalPasswordInfo')}
|
||||||
@ -48,8 +48,4 @@ const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>((global) => {
|
export default memo(SettingsTwoFaStart);
|
||||||
return {
|
|
||||||
animatedEmoji: selectAnimatedEmoji(global, '🔐'),
|
|
||||||
};
|
|
||||||
})(SettingsTwoFaStart));
|
|
||||||
|
|||||||
@ -26,26 +26,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
position: relative;
|
|
||||||
width: 10rem;
|
width: 10rem;
|
||||||
height: 10rem;
|
height: 10rem;
|
||||||
margin: 0 auto 1rem;
|
margin: 0 auto 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconAnimated,
|
|
||||||
.iconStatic {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 10rem;
|
|
||||||
height: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconStatic {
|
|
||||||
background: url("../../assets/lock.png") no-repeat center;
|
|
||||||
background-size: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help {
|
.help {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,21 +6,22 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
|
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { decryptSession } from '../../util/passcode';
|
import { decryptSession } from '../../util/passcode';
|
||||||
import getAnimationData from '../common/helpers/animatedAssets';
|
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
import useTimeout from '../../hooks/useTimeout';
|
import useTimeout from '../../hooks/useTimeout';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
|
||||||
import AnimatedSticker from '../common/AnimatedSticker';
|
import AnimatedIconWithPreview from '../common/AnimatedIconWithPreview';
|
||||||
import PasswordForm from '../common/PasswordForm';
|
import PasswordForm from '../common/PasswordForm';
|
||||||
import ConfirmDialog from '../ui/ConfirmDialog';
|
import ConfirmDialog from '../ui/ConfirmDialog';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import Link from '../ui/Link';
|
import Link from '../ui/Link';
|
||||||
|
|
||||||
import styles from './LockScreen.module.scss';
|
import styles from './LockScreen.module.scss';
|
||||||
|
import lockPreviewUrl from '../../assets/lock.png';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isLocked?: boolean;
|
isLocked?: boolean;
|
||||||
@ -55,18 +56,6 @@ const LockScreen: FC<OwnProps & StateProps> = ({
|
|||||||
const [shouldShowPasscode, setShouldShowPasscode] = useState(false);
|
const [shouldShowPasscode, setShouldShowPasscode] = useState(false);
|
||||||
const [isSignOutDialogOpen, openSignOutConfirmation, closeSignOutConfirmation] = useFlag(false);
|
const [isSignOutDialogOpen, openSignOutConfirmation, closeSignOutConfirmation] = useFlag(false);
|
||||||
const { transitionClassNames, shouldRender } = useShowTransition(isLocked);
|
const { transitionClassNames, shouldRender } = useShowTransition(isLocked);
|
||||||
const [animationData, setAnimationData] = useState<string>();
|
|
||||||
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
|
|
||||||
const shouldRenderAnimated = Boolean(animationData);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getAnimationData('Lock').then(setAnimationData);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { transitionClassNames: animatedClassNames } = useShowTransition(shouldRenderAnimated);
|
|
||||||
const { shouldRender: shouldRenderStatic, transitionClassNames: staticClassNames } = useShowTransition(
|
|
||||||
!isAnimationLoaded, undefined, true,
|
|
||||||
);
|
|
||||||
|
|
||||||
useTimeout(
|
useTimeout(
|
||||||
resetInvalidUnlockAttempts,
|
resetInvalidUnlockAttempts,
|
||||||
@ -128,22 +117,12 @@ const LockScreen: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.container, transitionClassNames)}>
|
<div className={buildClassName(styles.container, transitionClassNames)}>
|
||||||
<div className={styles.wrapper} dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className={styles.wrapper} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
<div className={styles.icon}>
|
<AnimatedIconWithPreview
|
||||||
{shouldRenderStatic && (
|
tgsUrl={LOCAL_TGS_URLS.Lock}
|
||||||
<div className={buildClassName(styles.iconStatic, staticClassNames)} />
|
previewUrl={lockPreviewUrl}
|
||||||
)}
|
size={ICON_SIZE}
|
||||||
{shouldRenderAnimated && (
|
className={styles.icon}
|
||||||
<AnimatedSticker
|
/>
|
||||||
id="lock_screen_icon"
|
|
||||||
animationData={animationData}
|
|
||||||
className={buildClassName(styles.iconAnimated, animatedClassNames)}
|
|
||||||
play
|
|
||||||
noLoop
|
|
||||||
size={ICON_SIZE}
|
|
||||||
onLoad={markAnimationLoaded}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<PasswordForm
|
<PasswordForm
|
||||||
key="password-form"
|
key="password-form"
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
|
memo, useCallback, useEffect, useLayoutEffect, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ActiveEmojiInteraction } from '../../global/types';
|
import type { ActiveEmojiInteraction } from '../../global/types';
|
||||||
import { ApiMediaFormat } from '../../api/types';
|
|
||||||
|
|
||||||
import { IS_ANDROID } from '../../util/environment';
|
import { IS_ANDROID } from '../../util/environment';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -14,7 +13,7 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import {
|
import {
|
||||||
selectAnimatedEmojiEffect,
|
selectAnimatedEmojiEffect,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import getAnimationData, { ANIMATED_STICKERS_PATHS } from '../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
|
||||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||||
|
|
||||||
import AnimatedSticker from '../common/AnimatedSticker';
|
import AnimatedSticker from '../common/AnimatedSticker';
|
||||||
@ -84,22 +83,16 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
|
|||||||
}, PLAYING_DURATION);
|
}, PLAYING_DURATION);
|
||||||
}, [stop]);
|
}, [stop]);
|
||||||
|
|
||||||
const effectAnimationData = useMedia(`sticker${effectAnimationId}`, !effectAnimationId, ApiMediaFormat.Lottie);
|
const effectTgsUrl = useMedia(`sticker${effectAnimationId}`, !effectAnimationId);
|
||||||
|
|
||||||
const [localEffectAnimationData, setLocalEffectAnimationData] = useState<string | undefined>();
|
|
||||||
useEffect(() => {
|
|
||||||
if (localEffectAnimation) {
|
|
||||||
getAnimationData(localEffectAnimation as keyof typeof ANIMATED_STICKERS_PATHS).then((data) => {
|
|
||||||
setLocalEffectAnimationData(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [localEffectAnimation]);
|
|
||||||
|
|
||||||
if (!activeEmojiInteraction.startSize) {
|
if (!activeEmojiInteraction.startSize) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scale = (activeEmojiInteraction.startSize || 0) / EFFECT_SIZE;
|
const scale = (activeEmojiInteraction.startSize || 0) / EFFECT_SIZE;
|
||||||
|
const tgsUrl = localEffectAnimation && (localEffectAnimation in LOCAL_TGS_URLS)
|
||||||
|
? LOCAL_TGS_URLS[localEffectAnimation as keyof typeof LOCAL_TGS_URLS]
|
||||||
|
: effectTgsUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -113,9 +106,8 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
key={`effect_${effectAnimationId}`}
|
key={`effect_${effectAnimationId}`}
|
||||||
id={`effect_${effectAnimationId}`}
|
|
||||||
size={EFFECT_SIZE}
|
size={EFFECT_SIZE}
|
||||||
animationData={localEffectAnimationData || effectAnimationData}
|
tgsUrl={tgsUrl}
|
||||||
play={isPlaying}
|
play={isPlaying}
|
||||||
quality={IS_ANDROID ? 0.5 : undefined}
|
quality={IS_ANDROID ? 0.5 : undefined}
|
||||||
forceOnHeavyAnimation
|
forceOnHeavyAnimation
|
||||||
@ -133,7 +125,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {
|
return {
|
||||||
effectAnimationId: animatedEffect ? animatedEffect.id : undefined,
|
effectAnimationId: animatedEffect ? animatedEffect.id : undefined,
|
||||||
localEffectAnimation: !animatedEffect && activeEmojiInteraction.animatedEffect
|
localEffectAnimation: !animatedEffect && activeEmojiInteraction.animatedEffect
|
||||||
&& Object.keys(ANIMATED_STICKERS_PATHS).includes(activeEmojiInteraction.animatedEffect)
|
&& Object.keys(LOCAL_TGS_URLS).includes(activeEmojiInteraction.animatedEffect)
|
||||||
? activeEmojiInteraction.animatedEffect : undefined,
|
? activeEmojiInteraction.animatedEffect : undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import type { FC } from '../../../lib/teact/teact';
|
|||||||
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiStickerSet } from '../../../api/types';
|
import type { ApiStickerSet } from '../../../api/types';
|
||||||
import { ApiMediaFormat } from '../../../api/types';
|
|
||||||
|
|
||||||
import { STICKER_SIZE_PICKER_HEADER } from '../../../config';
|
import { STICKER_SIZE_PICKER_HEADER } from '../../../config';
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
@ -30,7 +29,7 @@ const StickerSetCoverAnimated: FC<OwnProps> = ({
|
|||||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||||
|
|
||||||
const mediaHash = `stickerSet${stickerSet.id}`;
|
const mediaHash = `stickerSet${stickerSet.id}`;
|
||||||
const lottieData = useMedia(mediaHash, !isIntersecting, ApiMediaFormat.Lottie);
|
const lottieData = useMedia(mediaHash, !isIntersecting);
|
||||||
const transitionClassNames = useMediaTransition(lottieData);
|
const transitionClassNames = useMediaTransition(lottieData);
|
||||||
|
|
||||||
const firstLetters = useMemo(() => {
|
const firstLetters = useMemo(() => {
|
||||||
@ -44,9 +43,8 @@ const StickerSetCoverAnimated: FC<OwnProps> = ({
|
|||||||
{firstLetters}
|
{firstLetters}
|
||||||
{lottieData && (
|
{lottieData && (
|
||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
id={mediaHash}
|
|
||||||
size={size}
|
size={size}
|
||||||
animationData={lottieData}
|
tgsUrl={lottieData}
|
||||||
className={transitionClassNames}
|
className={transitionClassNames}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,13 +1,6 @@
|
|||||||
.AnimatedEmoji {
|
.AnimatedEmoji {
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
|
|
||||||
img {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.like-sticker-thumb img {
|
&.like-sticker-thumb img {
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
}
|
}
|
||||||
112
src/components/middle/message/AnimatedEmoji.tsx
Normal file
112
src/components/middle/message/AnimatedEmoji.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
import { withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiSticker } from '../../../api/types';
|
||||||
|
import type { ActiveEmojiInteraction } from '../../../global/types';
|
||||||
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
|
import { LIKE_STICKER_ID } from '../../common/helpers/mediaDimensions';
|
||||||
|
import {
|
||||||
|
selectAnimatedEmoji,
|
||||||
|
selectAnimatedEmojiEffect,
|
||||||
|
selectAnimatedEmojiSound,
|
||||||
|
selectLocalAnimatedEmoji,
|
||||||
|
selectLocalAnimatedEmojiEffect,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import useAnimatedEmoji from '../../common/hooks/useAnimatedEmoji';
|
||||||
|
|
||||||
|
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
|
||||||
|
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||||
|
|
||||||
|
import './AnimatedEmoji.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
emoji: string;
|
||||||
|
withEffects: boolean;
|
||||||
|
isOwn?: boolean;
|
||||||
|
observeIntersection?: ObserveFn;
|
||||||
|
size?: 'large' | 'medium' | 'small';
|
||||||
|
lastSyncTime?: number;
|
||||||
|
forceLoadPreview?: boolean;
|
||||||
|
messageId?: number;
|
||||||
|
chatId?: string;
|
||||||
|
activeEmojiInteractions?: ActiveEmojiInteraction[];
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
sticker?: ApiSticker;
|
||||||
|
effect?: ApiSticker;
|
||||||
|
localSticker?: keyof typeof LOCAL_TGS_URLS;
|
||||||
|
localEffect?: string;
|
||||||
|
soundId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QUALITY = 1;
|
||||||
|
|
||||||
|
const AnimatedEmoji: FC<OwnProps & StateProps> = ({
|
||||||
|
isOwn,
|
||||||
|
observeIntersection,
|
||||||
|
lastSyncTime,
|
||||||
|
forceLoadPreview,
|
||||||
|
messageId,
|
||||||
|
chatId,
|
||||||
|
activeEmojiInteractions,
|
||||||
|
sticker,
|
||||||
|
effect,
|
||||||
|
localSticker,
|
||||||
|
localEffect,
|
||||||
|
soundId,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
ref,
|
||||||
|
size,
|
||||||
|
style,
|
||||||
|
handleClick,
|
||||||
|
} = useAnimatedEmoji(chatId, messageId, soundId, activeEmojiInteractions, isOwn, localEffect, effect?.emoji);
|
||||||
|
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||||
|
|
||||||
|
return localSticker ? (
|
||||||
|
<AnimatedIconWithPreview
|
||||||
|
tgsUrl={LOCAL_TGS_URLS[localSticker]}
|
||||||
|
size={size}
|
||||||
|
quality={QUALITY}
|
||||||
|
play={isIntersecting}
|
||||||
|
forceOnHeavyAnimation
|
||||||
|
ref={ref}
|
||||||
|
className="AnimatedEmoji media-inner"
|
||||||
|
style={style}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<AnimatedIconFromSticker
|
||||||
|
sticker={sticker}
|
||||||
|
size={size}
|
||||||
|
quality={QUALITY}
|
||||||
|
noLoad={!isIntersecting}
|
||||||
|
forcePreview={forceLoadPreview}
|
||||||
|
lastSyncTime={lastSyncTime}
|
||||||
|
play={isIntersecting}
|
||||||
|
forceOnHeavyAnimation
|
||||||
|
ref={ref}
|
||||||
|
className={buildClassName('AnimatedEmoji media-inner', sticker?.id === LIKE_STICKER_ID && 'like-sticker-thumb')}
|
||||||
|
style={style}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>((global, { emoji, withEffects }) => {
|
||||||
|
const localSticker = selectLocalAnimatedEmoji(global, emoji);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sticker: selectAnimatedEmoji(global, emoji),
|
||||||
|
effect: withEffects ? selectAnimatedEmojiEffect(global, emoji) : undefined,
|
||||||
|
soundId: selectAnimatedEmojiSound(global, emoji),
|
||||||
|
localSticker,
|
||||||
|
localEffect: localSticker && withEffects ? selectLocalAnimatedEmojiEffect(localSticker) : undefined,
|
||||||
|
};
|
||||||
|
})(AnimatedEmoji));
|
||||||
@ -14,7 +14,6 @@ import type {
|
|||||||
ApiMessageOutgoingStatus,
|
ApiMessageOutgoingStatus,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiSticker,
|
|
||||||
ApiThreadInfo,
|
ApiThreadInfo,
|
||||||
ApiAvailableReaction,
|
ApiAvailableReaction,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
@ -33,7 +32,6 @@ import {
|
|||||||
selectUser,
|
selectUser,
|
||||||
selectIsMessageFocused,
|
selectIsMessageFocused,
|
||||||
selectCurrentTextSearch,
|
selectCurrentTextSearch,
|
||||||
selectAnimatedEmoji,
|
|
||||||
selectIsInSelectMode,
|
selectIsInSelectMode,
|
||||||
selectIsMessageSelected,
|
selectIsMessageSelected,
|
||||||
selectIsDocumentGroupSelected,
|
selectIsDocumentGroupSelected,
|
||||||
@ -47,14 +45,12 @@ import {
|
|||||||
selectAllowedMessageActions,
|
selectAllowedMessageActions,
|
||||||
selectIsDownloading,
|
selectIsDownloading,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectAnimatedEmojiEffect,
|
|
||||||
selectAnimatedEmojiSound,
|
|
||||||
selectMessageIdsByGroupId,
|
selectMessageIdsByGroupId,
|
||||||
selectLocalAnimatedEmoji,
|
|
||||||
selectIsMessageProtected,
|
selectIsMessageProtected,
|
||||||
selectLocalAnimatedEmojiEffect,
|
|
||||||
selectDefaultReaction,
|
selectDefaultReaction,
|
||||||
selectReplySender,
|
selectReplySender,
|
||||||
|
selectAnimatedEmoji,
|
||||||
|
selectLocalAnimatedEmoji,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import {
|
import {
|
||||||
getMessageContent,
|
getMessageContent,
|
||||||
@ -102,7 +98,7 @@ import Audio from '../../common/Audio';
|
|||||||
import MessageMeta from './MessageMeta';
|
import MessageMeta from './MessageMeta';
|
||||||
import ContextMenuContainer from './ContextMenuContainer.async';
|
import ContextMenuContainer from './ContextMenuContainer.async';
|
||||||
import Sticker from './Sticker';
|
import Sticker from './Sticker';
|
||||||
import AnimatedEmoji from '../../common/AnimatedEmoji';
|
import AnimatedEmoji from './AnimatedEmoji';
|
||||||
import Photo from './Photo';
|
import Photo from './Photo';
|
||||||
import Video from './Video';
|
import Video from './Video';
|
||||||
import Contact from './Contact';
|
import Contact from './Contact';
|
||||||
@ -117,7 +113,6 @@ import InlineButtons from './InlineButtons';
|
|||||||
import CommentButton from './CommentButton';
|
import CommentButton from './CommentButton';
|
||||||
import Reactions from './Reactions';
|
import Reactions from './Reactions';
|
||||||
import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
|
import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
|
||||||
import LocalAnimatedEmoji from '../../common/LocalAnimatedEmoji';
|
|
||||||
import MessagePhoneCall from './MessagePhoneCall';
|
import MessagePhoneCall from './MessagePhoneCall';
|
||||||
|
|
||||||
import './Message.scss';
|
import './Message.scss';
|
||||||
@ -176,12 +171,7 @@ type StateProps = {
|
|||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
serverTimeOffset: number;
|
serverTimeOffset: number;
|
||||||
highlight?: string;
|
highlight?: string;
|
||||||
isSingleEmoji?: boolean;
|
animatedEmoji?: string;
|
||||||
animatedEmoji?: ApiSticker;
|
|
||||||
localSticker?: string;
|
|
||||||
localEffect?: string;
|
|
||||||
animatedEmojiEffect?: ApiSticker;
|
|
||||||
animatedEmojiSoundId?: string;
|
|
||||||
isInSelectMode?: boolean;
|
isInSelectMode?: boolean;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
isGroupSelected?: boolean;
|
isGroupSelected?: boolean;
|
||||||
@ -264,10 +254,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
serverTimeOffset,
|
serverTimeOffset,
|
||||||
highlight,
|
highlight,
|
||||||
animatedEmoji,
|
animatedEmoji,
|
||||||
localSticker,
|
|
||||||
localEffect,
|
|
||||||
animatedEmojiEffect,
|
|
||||||
animatedEmojiSoundId,
|
|
||||||
isInSelectMode,
|
isInSelectMode,
|
||||||
isSelected,
|
isSelected,
|
||||||
isGroupSelected,
|
isGroupSelected,
|
||||||
@ -338,7 +324,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const hasReply = isReplyMessage(message) && !shouldHideReply;
|
const hasReply = isReplyMessage(message) && !shouldHideReply;
|
||||||
const hasThread = Boolean(threadInfo) && messageListType === 'thread';
|
const hasThread = Boolean(threadInfo) && messageListType === 'thread';
|
||||||
const customShape = getMessageCustomShape(message);
|
const customShape = getMessageCustomShape(message);
|
||||||
const hasAnimatedEmoji = localSticker || animatedEmoji;
|
const hasAnimatedEmoji = animatedEmoji;
|
||||||
const hasReactions = reactionMessage?.reactions && !areReactionsEmpty(reactionMessage.reactions);
|
const hasReactions = reactionMessage?.reactions && !areReactionsEmpty(reactionMessage.reactions);
|
||||||
const asForwarded = (
|
const asForwarded = (
|
||||||
forwardInfo
|
forwardInfo
|
||||||
@ -662,26 +648,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{animatedEmoji && (
|
{animatedEmoji && (
|
||||||
<AnimatedEmoji
|
<AnimatedEmoji
|
||||||
size="small"
|
emoji={animatedEmoji}
|
||||||
|
withEffects={isUserId(chatId)}
|
||||||
isOwn={isOwn}
|
isOwn={isOwn}
|
||||||
sticker={animatedEmoji}
|
|
||||||
effect={animatedEmojiEffect}
|
|
||||||
soundId={animatedEmojiSoundId}
|
|
||||||
observeIntersection={observeIntersectionForMedia}
|
|
||||||
lastSyncTime={lastSyncTime}
|
|
||||||
forceLoadPreview={isLocal}
|
|
||||||
messageId={messageId}
|
|
||||||
chatId={chatId}
|
|
||||||
activeEmojiInteractions={activeEmojiInteractions}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{localSticker && (
|
|
||||||
<LocalAnimatedEmoji
|
|
||||||
size="small"
|
|
||||||
isOwn={isOwn}
|
|
||||||
localSticker={localSticker}
|
|
||||||
localEffect={localEffect}
|
|
||||||
soundId={animatedEmojiSoundId}
|
|
||||||
observeIntersection={observeIntersectionForMedia}
|
observeIntersection={observeIntersectionForMedia}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
forceLoadPreview={isLocal}
|
forceLoadPreview={isLocal}
|
||||||
@ -1057,8 +1026,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { query: highlight } = selectCurrentTextSearch(global) || {};
|
const { query: highlight } = selectCurrentTextSearch(global) || {};
|
||||||
|
|
||||||
const singleEmoji = getMessageSingleEmoji(message);
|
const singleEmoji = getMessageSingleEmoji(message);
|
||||||
let isSelected: boolean;
|
const animatedEmoji = singleEmoji && (
|
||||||
|
selectAnimatedEmoji(global, singleEmoji) || selectLocalAnimatedEmoji(global, singleEmoji)
|
||||||
|
) ? singleEmoji : undefined;
|
||||||
|
|
||||||
|
let isSelected: boolean;
|
||||||
if (album?.messages) {
|
if (album?.messages) {
|
||||||
isSelected = album.messages.every(({ id: messageId }) => selectIsMessageSelected(global, messageId));
|
isSelected = album.messages.every(({ id: messageId }) => selectIsMessageSelected(global, messageId));
|
||||||
} else {
|
} else {
|
||||||
@ -1079,8 +1051,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isLastInDocumentGroup ? selectChatMessage(global, chatId, documentGroupFirstMessageId!) : undefined
|
isLastInDocumentGroup ? selectChatMessage(global, chatId, documentGroupFirstMessageId!) : undefined
|
||||||
) : message;
|
) : message;
|
||||||
|
|
||||||
const localSticker = singleEmoji ? selectLocalAnimatedEmoji(global, singleEmoji) : undefined;
|
|
||||||
|
|
||||||
const hasUnreadReaction = chat?.unreadReactions?.includes(message.id);
|
const hasUnreadReaction = chat?.unreadReactions?.includes(message.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1107,12 +1077,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
serverTimeOffset,
|
serverTimeOffset,
|
||||||
highlight,
|
highlight,
|
||||||
isSingleEmoji: Boolean(singleEmoji),
|
animatedEmoji,
|
||||||
animatedEmoji: singleEmoji ? selectAnimatedEmoji(global, singleEmoji) : undefined,
|
|
||||||
animatedEmojiEffect: singleEmoji && isUserId(chatId) ? selectAnimatedEmojiEffect(global, singleEmoji) : undefined,
|
|
||||||
animatedEmojiSoundId: singleEmoji ? selectAnimatedEmojiSound(global, singleEmoji) : undefined,
|
|
||||||
localSticker,
|
|
||||||
localEffect: localSticker && isUserId(chatId) ? selectLocalAnimatedEmojiEffect(localSticker) : undefined,
|
|
||||||
isInSelectMode: selectIsInSelectMode(global),
|
isInSelectMode: selectIsInSelectMode(global),
|
||||||
isSelected,
|
isSelected,
|
||||||
isGroupSelected: (
|
isGroupSelected: (
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { getActions } from '../../../global';
|
|||||||
|
|
||||||
import type { ActiveReaction } from '../../../global/types';
|
import type { ActiveReaction } from '../../../global/types';
|
||||||
import type { ApiAvailableReaction } from '../../../api/types';
|
import type { ApiAvailableReaction } from '../../../api/types';
|
||||||
import { ApiMediaFormat } from '../../../api/types';
|
|
||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
@ -37,8 +36,8 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
|
|||||||
const availableReaction = availableReactions?.find((r) => r.reaction === reaction);
|
const availableReaction = availableReactions?.find((r) => r.reaction === reaction);
|
||||||
const centerIconId = availableReaction?.centerIcon?.id;
|
const centerIconId = availableReaction?.centerIcon?.id;
|
||||||
const effectId = availableReaction?.aroundAnimation?.id;
|
const effectId = availableReaction?.aroundAnimation?.id;
|
||||||
const mediaDataCenterIcon = useMedia(`sticker${centerIconId}`, !centerIconId, ApiMediaFormat.Lottie);
|
const mediaDataCenterIcon = useMedia(`sticker${centerIconId}`, !centerIconId);
|
||||||
const mediaDataEffect = useMedia(`sticker${effectId}`, !effectId, ApiMediaFormat.Lottie);
|
const mediaDataEffect = useMedia(`sticker${effectId}`, !effectId);
|
||||||
|
|
||||||
const shouldPlay = Boolean(activeReaction?.reaction === reaction && mediaDataCenterIcon && mediaDataEffect);
|
const shouldPlay = Boolean(activeReaction?.reaction === reaction && mediaDataCenterIcon && mediaDataEffect);
|
||||||
const {
|
const {
|
||||||
@ -66,10 +65,9 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
|
|||||||
<>
|
<>
|
||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
key={centerIconId}
|
key={centerIconId}
|
||||||
id={`reaction_emoji_${centerIconId}`}
|
|
||||||
className={animationClassNames}
|
className={animationClassNames}
|
||||||
size={CENTER_ICON_SIZE}
|
size={CENTER_ICON_SIZE}
|
||||||
animationData={mediaDataCenterIcon}
|
tgsUrl={mediaDataCenterIcon}
|
||||||
play
|
play
|
||||||
noLoop
|
noLoop
|
||||||
forceOnHeavyAnimation
|
forceOnHeavyAnimation
|
||||||
@ -78,10 +76,9 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
key={effectId}
|
key={effectId}
|
||||||
id={`reaction_effect_${effectId}`}
|
|
||||||
className={buildClassName('effect', animationClassNames)}
|
className={buildClassName('effect', animationClassNames)}
|
||||||
size={EFFECT_SIZE}
|
size={EFFECT_SIZE}
|
||||||
animationData={mediaDataEffect}
|
tgsUrl={mediaDataEffect}
|
||||||
play
|
play
|
||||||
noLoop
|
noLoop
|
||||||
forceOnHeavyAnimation
|
forceOnHeavyAnimation
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import type { FC } from '../../../lib/teact/teact';
|
|||||||
import React, { memo, useRef } from '../../../lib/teact/teact';
|
import React, { memo, useRef } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiAvailableReaction } from '../../../api/types';
|
import type { ApiAvailableReaction } from '../../../api/types';
|
||||||
import { ApiMediaFormat } from '../../../api/types';
|
|
||||||
|
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
@ -31,7 +30,7 @@ const ReactionSelectorReaction: FC<OwnProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const mediaData = useMedia(`document${reaction.selectAnimation?.id}`, !isReady, ApiMediaFormat.Lottie);
|
const mediaData = useMedia(`document${reaction.selectAnimation?.id}`, !isReady);
|
||||||
|
|
||||||
const [isActivated, activate, deactivate] = useFlag();
|
const [isActivated, activate, deactivate] = useFlag();
|
||||||
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
|
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
|
||||||
@ -67,9 +66,8 @@ const ReactionSelectorReaction: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
{shouldRenderAnimated && (
|
{shouldRenderAnimated && (
|
||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
id={`select_${reaction.reaction}`}
|
|
||||||
className={cn('animated', [animatedClassNames])}
|
className={cn('animated', [animatedClassNames])}
|
||||||
animationData={mediaData}
|
tgsUrl={mediaData}
|
||||||
play={isActivated}
|
play={isActivated}
|
||||||
noLoop
|
noLoop
|
||||||
size={REACTION_SIZE}
|
size={REACTION_SIZE}
|
||||||
|
|||||||
@ -118,8 +118,7 @@ const Sticker: FC<OwnProps> = ({
|
|||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
key={mediaHash}
|
key={mediaHash}
|
||||||
className={buildClassName('full-media', transitionClassNames)}
|
className={buildClassName('full-media', transitionClassNames)}
|
||||||
id={mediaHash}
|
tgsUrl={mediaData}
|
||||||
animationData={mediaData}
|
|
||||||
size={width}
|
size={width}
|
||||||
play={shouldPlay}
|
play={shouldPlay}
|
||||||
noLoop={!shouldLoop}
|
noLoop={!shouldLoop}
|
||||||
|
|||||||
@ -8,20 +8,20 @@ import type { ApiChat } from '../../../api/types';
|
|||||||
import { ManagementScreens } from '../../../types';
|
import { ManagementScreens } from '../../../types';
|
||||||
|
|
||||||
import { STICKER_SIZE_DISCUSSION_GROUPS } from '../../../config';
|
import { STICKER_SIZE_DISCUSSION_GROUPS } from '../../../config';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
import { selectChat } from '../../../global/selectors';
|
import { selectChat } from '../../../global/selectors';
|
||||||
import getAnimationData from '../../common/helpers/animatedAssets';
|
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import NothingFound from '../../common/NothingFound';
|
import NothingFound from '../../common/NothingFound';
|
||||||
import GroupChatInfo from '../../common/GroupChatInfo';
|
import GroupChatInfo from '../../common/GroupChatInfo';
|
||||||
import AnimatedSticker from '../../common/AnimatedSticker';
|
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
import Avatar from '../../common/Avatar';
|
import Avatar from '../../common/Avatar';
|
||||||
import { isChatChannel } from '../../../global/helpers';
|
import { isChatChannel } from '../../../global/helpers';
|
||||||
|
import AnimatedIcon from '../../common/AnimatedIcon';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -56,9 +56,6 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
|
|||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const [linkedGroupId, setLinkedGroupId] = useState<string>();
|
const [linkedGroupId, setLinkedGroupId] = useState<string>();
|
||||||
const [animationData, setAnimationData] = useState<string>();
|
|
||||||
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
|
|
||||||
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
|
|
||||||
const [isConfirmUnlinkGroupDialogOpen, openConfirmUnlinkGroupDialog, closeConfirmUnlinkGroupDialog] = useFlag();
|
const [isConfirmUnlinkGroupDialogOpen, openConfirmUnlinkGroupDialog, closeConfirmUnlinkGroupDialog] = useFlag();
|
||||||
const [isConfirmLinkGroupDialogOpen, openConfirmLinkGroupDialog, closeConfirmLinkGroupDialog] = useFlag();
|
const [isConfirmLinkGroupDialogOpen, openConfirmLinkGroupDialog, closeConfirmLinkGroupDialog] = useFlag();
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -73,12 +70,6 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
|
|||||||
loadGroupsForDiscussion();
|
loadGroupsForDiscussion();
|
||||||
}, [loadGroupsForDiscussion]);
|
}, [loadGroupsForDiscussion]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!animationData) {
|
|
||||||
getAnimationData('DiscussionGroups').then(setAnimationData);
|
|
||||||
}
|
|
||||||
}, [animationData]);
|
|
||||||
|
|
||||||
const handleUnlinkGroupSessions = useCallback(() => {
|
const handleUnlinkGroupSessions = useCallback(() => {
|
||||||
closeConfirmUnlinkGroupDialog();
|
closeConfirmUnlinkGroupDialog();
|
||||||
unlinkDiscussionGroup({ channelId: isChannel ? chatId : linkedChatId });
|
unlinkDiscussionGroup({ channelId: isChannel ? chatId : linkedChatId });
|
||||||
@ -143,10 +134,6 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
|
|||||||
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?`,
|
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?`,
|
||||||
['br', 'simple_markdown'],
|
['br', 'simple_markdown'],
|
||||||
);
|
);
|
||||||
// return renderText(
|
|
||||||
// lang('DiscussionLinkGroupPublicAlert', linkedChat.title, chat!.title),
|
|
||||||
// ['br', 'simple_markdown'],
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderText(
|
return renderText(
|
||||||
@ -154,10 +141,6 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
|
|||||||
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?\n\nAnyone from the channel will be able to see messages in this group.`,
|
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?\n\nAnyone from the channel will be able to see messages in this group.`,
|
||||||
['br', 'simple_markdown'],
|
['br', 'simple_markdown'],
|
||||||
);
|
);
|
||||||
// return renderText(
|
|
||||||
// lang('DiscussionLinkGroupPrivateAlert', linkedChat.title, chat!.title),
|
|
||||||
// ['br', 'simple_markdown'],
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLinkedGroup() {
|
function renderLinkedGroup() {
|
||||||
@ -244,18 +227,11 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="Management">
|
<div className="Management">
|
||||||
<div className="custom-scroll">
|
<div className="custom-scroll">
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<div className="section-icon">
|
<AnimatedIcon
|
||||||
{animationData && (
|
tgsUrl={LOCAL_TGS_URLS.DiscussionGroups}
|
||||||
<AnimatedSticker
|
size={STICKER_SIZE_DISCUSSION_GROUPS}
|
||||||
id="discussionGroupsDucks"
|
className="section-icon"
|
||||||
size={STICKER_SIZE_DISCUSSION_GROUPS}
|
/>
|
||||||
animationData={animationData}
|
|
||||||
play={isAnimationLoaded}
|
|
||||||
noLoop
|
|
||||||
onLoad={handleAnimationLoad}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{linkedChat && renderLinkedGroup()}
|
{linkedChat && renderLinkedGroup()}
|
||||||
{!linkedChat && renderDiscussionGroups()}
|
{!linkedChat && renderDiscussionGroups()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useState,
|
memo, useCallback, useMemo, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ import type { ApiChat, ApiExportedInvite } from '../../../api/types';
|
|||||||
import { ManagementScreens } from '../../../types';
|
import { ManagementScreens } from '../../../types';
|
||||||
|
|
||||||
import { STICKER_SIZE_INVITES } from '../../../config';
|
import { STICKER_SIZE_INVITES } from '../../../config';
|
||||||
import getAnimationData from '../../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import { formatCountdown, MILLISECONDS_IN_DAY } from '../../../util/dateFormat';
|
import { formatCountdown, MILLISECONDS_IN_DAY } from '../../../util/dateFormat';
|
||||||
@ -27,7 +27,7 @@ import Button from '../../ui/Button';
|
|||||||
import DropdownMenu from '../../ui/DropdownMenu';
|
import DropdownMenu from '../../ui/DropdownMenu';
|
||||||
import MenuItem from '../../ui/MenuItem';
|
import MenuItem from '../../ui/MenuItem';
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
import AnimatedSticker from '../../common/AnimatedSticker';
|
import AnimatedIcon from '../../common/AnimatedIcon';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -82,16 +82,6 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
||||||
const [deletingInvite, setDeletingInvite] = useState<ApiExportedInvite | undefined>();
|
const [deletingInvite, setDeletingInvite] = useState<ApiExportedInvite | undefined>();
|
||||||
|
|
||||||
const [animationData, setAnimationData] = useState<string>();
|
|
||||||
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
|
|
||||||
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!animationData) {
|
|
||||||
getAnimationData('Invite').then(setAnimationData);
|
|
||||||
}
|
|
||||||
}, [animationData]);
|
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive,
|
isActive,
|
||||||
onBack: onClose,
|
onBack: onClose,
|
||||||
@ -288,17 +278,11 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="Management ManageInvites">
|
<div className="Management ManageInvites">
|
||||||
<div className="custom-scroll">
|
<div className="custom-scroll">
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<div className="section-icon">
|
<AnimatedIcon
|
||||||
{animationData && (
|
tgsUrl={LOCAL_TGS_URLS.Invite}
|
||||||
<AnimatedSticker
|
size={STICKER_SIZE_INVITES}
|
||||||
id="inviteDuck"
|
className="section-icon"
|
||||||
size={STICKER_SIZE_INVITES}
|
/>
|
||||||
animationData={animationData}
|
|
||||||
play={isAnimationLoaded}
|
|
||||||
onLoad={handleAnimationLoad}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<p className="text-muted">{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
|
<p className="text-muted">{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
|
||||||
</div>
|
</div>
|
||||||
{primaryInviteLink && (
|
{primaryInviteLink && (
|
||||||
|
|||||||
@ -1,24 +1,22 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
|
||||||
memo, useCallback, useEffect, useState,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiChat } from '../../../api/types';
|
import type { ApiChat } from '../../../api/types';
|
||||||
|
|
||||||
import { STICKER_SIZE_JOIN_REQUESTS } from '../../../config';
|
import { STICKER_SIZE_JOIN_REQUESTS } from '../../../config';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
import { selectChat } from '../../../global/selectors';
|
import { selectChat } from '../../../global/selectors';
|
||||||
import { isChatChannel, isUserId } from '../../../global/helpers';
|
import { isChatChannel, isUserId } from '../../../global/helpers';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import getAnimationData from '../../common/helpers/animatedAssets';
|
|
||||||
|
|
||||||
import JoinRequest from './JoinRequest';
|
import JoinRequest from './JoinRequest';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
import AnimatedSticker from '../../common/AnimatedSticker';
|
|
||||||
import Spinner from '../../ui/Spinner';
|
import Spinner from '../../ui/Spinner';
|
||||||
|
import AnimatedIcon from '../../common/AnimatedIcon';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -45,16 +43,6 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const [animationData, setAnimationData] = useState<string>();
|
|
||||||
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
|
|
||||||
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!animationData) {
|
|
||||||
getAnimationData('JoinRequest').then(setAnimationData);
|
|
||||||
}
|
|
||||||
}, [animationData]);
|
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive,
|
isActive,
|
||||||
onBack: onClose,
|
onBack: onClose,
|
||||||
@ -80,17 +68,11 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="Management ManageJoinRequests">
|
<div className="Management ManageJoinRequests">
|
||||||
<div className="custom-scroll">
|
<div className="custom-scroll">
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<div className="section-icon">
|
<AnimatedIcon
|
||||||
{animationData && (
|
tgsUrl={LOCAL_TGS_URLS.JoinRequest}
|
||||||
<AnimatedSticker
|
size={STICKER_SIZE_JOIN_REQUESTS}
|
||||||
id="joinRequestDucks"
|
className="section-icon"
|
||||||
size={STICKER_SIZE_JOIN_REQUESTS}
|
/>
|
||||||
animationData={animationData}
|
|
||||||
play={isAnimationLoaded}
|
|
||||||
onLoad={handleAnimationLoad}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{Boolean(chat?.joinRequests?.length) && (
|
{Boolean(chat?.joinRequests?.length) && (
|
||||||
<div className="bulk-actions">
|
<div className="bulk-actions">
|
||||||
<Button className="bulk-action-button" onClick={openAcceptAllDialog}>Accept all</Button>
|
<Button className="bulk-action-button" onClick={openAcceptAllDialog}>Accept all</Button>
|
||||||
|
|||||||
@ -36,22 +36,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-icon {
|
.section-icon {
|
||||||
width: 8.75rem;
|
|
||||||
height: 8.75rem;
|
|
||||||
margin: 0 auto 2rem;
|
margin: 0 auto 2rem;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.AnimatedSticker {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ListItem {
|
.ListItem {
|
||||||
|
|||||||
@ -31,10 +31,10 @@ addActionHandler('loadAvailableReactions', async () => {
|
|||||||
// Preload animations
|
// Preload animations
|
||||||
result.forEach((availableReaction) => {
|
result.forEach((availableReaction) => {
|
||||||
if (availableReaction.aroundAnimation) {
|
if (availableReaction.aroundAnimation) {
|
||||||
mediaLoader.fetch(`sticker${availableReaction.aroundAnimation.id}`, ApiMediaFormat.Lottie);
|
mediaLoader.fetch(`sticker${availableReaction.aroundAnimation.id}`, ApiMediaFormat.BlobUrl);
|
||||||
}
|
}
|
||||||
if (availableReaction.centerIcon) {
|
if (availableReaction.centerIcon) {
|
||||||
mediaLoader.fetch(`sticker${availableReaction.centerIcon.id}`, ApiMediaFormat.Lottie);
|
mediaLoader.fetch(`sticker${availableReaction.centerIcon.id}`, ApiMediaFormat.BlobUrl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -302,15 +302,10 @@ export function getAudioHasCover(media: ApiAudio) {
|
|||||||
export function getMessageMediaFormat(
|
export function getMessageMediaFormat(
|
||||||
message: ApiMessage, target: Target,
|
message: ApiMessage, target: Target,
|
||||||
): ApiMediaFormat {
|
): ApiMediaFormat {
|
||||||
const {
|
const { video, audio, voice } = message.content;
|
||||||
sticker, video, audio, voice,
|
|
||||||
} = message.content;
|
|
||||||
|
|
||||||
const fullVideo = video || getMessageWebPageVideo(message);
|
const fullVideo = video || getMessageWebPageVideo(message);
|
||||||
|
|
||||||
if (sticker && target === 'inline' && sticker.isLottie) {
|
if (fullVideo && IS_PROGRESSIVE_SUPPORTED && (
|
||||||
return ApiMediaFormat.Lottie;
|
|
||||||
} else if (fullVideo && IS_PROGRESSIVE_SUPPORTED && (
|
|
||||||
target === 'viewerFull' || target === 'inline'
|
target === 'viewerFull' || target === 'inline'
|
||||||
)) {
|
)) {
|
||||||
return ApiMediaFormat.Progressive;
|
return ApiMediaFormat.Progressive;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
import WorkerConnector from '../../util/WorkerConnector';
|
import WorkerConnector from '../../util/WorkerConnector';
|
||||||
import { animate } from '../../util/animation';
|
import { animate } from '../../util/animation';
|
||||||
import cycleRestrict from '../../util/cycleRestrict';
|
import cycleRestrict from '../../util/cycleRestrict';
|
||||||
|
import generateIdFor from '../../util/generateIdFor';
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
noLoop?: boolean;
|
noLoop?: boolean;
|
||||||
@ -25,6 +26,7 @@ const HIGH_PRIORITY_QUALITY = IS_SINGLE_COLUMN_LAYOUT ? 0.75 : 1;
|
|||||||
const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75;
|
const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75;
|
||||||
const HIGH_PRIORITY_CACHE_MODULO = IS_SAFARI ? 2 : 4;
|
const HIGH_PRIORITY_CACHE_MODULO = IS_SAFARI ? 2 : 4;
|
||||||
const LOW_PRIORITY_CACHE_MODULO = 0;
|
const LOW_PRIORITY_CACHE_MODULO = 0;
|
||||||
|
const KEY_STORE = {};
|
||||||
|
|
||||||
const workers = new Array(MAX_WORKERS).fill(undefined).map(
|
const workers = new Array(MAX_WORKERS).fill(undefined).map(
|
||||||
() => new WorkerConnector(new Worker(new URL('./rlottie.worker.ts', import.meta.url))),
|
() => new WorkerConnector(new Worker(new URL('./rlottie.worker.ts', import.meta.url))),
|
||||||
@ -36,7 +38,7 @@ class RLottie {
|
|||||||
|
|
||||||
private imgSize!: number;
|
private imgSize!: number;
|
||||||
|
|
||||||
private key!: string;
|
private key = generateIdFor(KEY_STORE);
|
||||||
|
|
||||||
private msPerFrame = 1000 / 60;
|
private msPerFrame = 1000 / 60;
|
||||||
|
|
||||||
@ -66,6 +68,8 @@ class RLottie {
|
|||||||
|
|
||||||
private isWaiting = true;
|
private isWaiting = true;
|
||||||
|
|
||||||
|
private isEnded = false;
|
||||||
|
|
||||||
private isOnLoadFired = false;
|
private isOnLoadFired = false;
|
||||||
|
|
||||||
private isDestroyed = false;
|
private isDestroyed = false;
|
||||||
@ -83,9 +87,8 @@ class RLottie {
|
|||||||
private lastRenderAt?: number;
|
private lastRenderAt?: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private id: string,
|
|
||||||
private container: HTMLDivElement,
|
private container: HTMLDivElement,
|
||||||
private animationData: string,
|
private tgsUrl: string,
|
||||||
private params: Params = {},
|
private params: Params = {},
|
||||||
private onLoad?: () => void,
|
private onLoad?: () => void,
|
||||||
private customColor?: [number, number, number],
|
private customColor?: [number, number, number],
|
||||||
@ -100,7 +103,11 @@ class RLottie {
|
|||||||
return this.isAnimating || this.isWaiting;
|
return this.isAnimating || this.isWaiting;
|
||||||
}
|
}
|
||||||
|
|
||||||
play() {
|
play(forceRestart = false) {
|
||||||
|
if (this.isEnded && forceRestart) {
|
||||||
|
this.approxFrameIndex = Math.floor(0);
|
||||||
|
}
|
||||||
|
|
||||||
this.stopFrameIndex = undefined;
|
this.stopFrameIndex = undefined;
|
||||||
this.direction = 1;
|
this.direction = 1;
|
||||||
this.doPlay();
|
this.doPlay();
|
||||||
@ -117,20 +124,6 @@ class RLottie {
|
|||||||
this.chunks = this.chunks.map((chunk, i) => (i === currentChunkIndex ? chunk : undefined));
|
this.chunks = this.chunks.map((chunk, i) => (i === currentChunkIndex ? chunk : undefined));
|
||||||
}
|
}
|
||||||
|
|
||||||
goToAndPlay(frameIndex: number) {
|
|
||||||
this.approxFrameIndex = Math.floor(frameIndex / this.reduceFactor);
|
|
||||||
this.stopFrameIndex = undefined;
|
|
||||||
this.direction = 1;
|
|
||||||
this.doPlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
goToAndStop(frameIndex: number) {
|
|
||||||
this.approxFrameIndex = Math.floor(frameIndex / this.reduceFactor);
|
|
||||||
this.stopFrameIndex = Math.floor(frameIndex / this.reduceFactor);
|
|
||||||
this.direction = 1;
|
|
||||||
this.doPlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
playSegment([startFrameIndex, stopFrameIndex]: [number, number]) {
|
playSegment([startFrameIndex, stopFrameIndex]: [number, number]) {
|
||||||
this.approxFrameIndex = Math.floor(startFrameIndex / this.reduceFactor);
|
this.approxFrameIndex = Math.floor(startFrameIndex / this.reduceFactor);
|
||||||
this.stopFrameIndex = Math.floor(stopFrameIndex / this.reduceFactor);
|
this.stopFrameIndex = Math.floor(stopFrameIndex / this.reduceFactor);
|
||||||
@ -184,8 +177,6 @@ class RLottie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initConfig() {
|
private initConfig() {
|
||||||
this.key = `${this.id}_${this.imgSize}`;
|
|
||||||
|
|
||||||
const { isLowPriority } = this.params;
|
const { isLowPriority } = this.params;
|
||||||
|
|
||||||
this.cacheModulo = isLowPriority ? LOW_PRIORITY_CACHE_MODULO : HIGH_PRIORITY_CACHE_MODULO;
|
this.cacheModulo = isLowPriority ? LOW_PRIORITY_CACHE_MODULO : HIGH_PRIORITY_CACHE_MODULO;
|
||||||
@ -221,7 +212,7 @@ class RLottie {
|
|||||||
name: 'init',
|
name: 'init',
|
||||||
args: [
|
args: [
|
||||||
this.key,
|
this.key,
|
||||||
this.animationData,
|
this.tgsUrl,
|
||||||
this.imgSize,
|
this.imgSize,
|
||||||
this.params.isLowPriority,
|
this.params.isLowPriority,
|
||||||
this.onRendererInit.bind(this),
|
this.onRendererInit.bind(this),
|
||||||
@ -247,16 +238,16 @@ class RLottie {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeData(animationData: string) {
|
changeData(tgsUrl: string) {
|
||||||
this.pause();
|
this.pause();
|
||||||
this.animationData = animationData;
|
this.tgsUrl = tgsUrl;
|
||||||
this.initConfig();
|
this.initConfig();
|
||||||
|
|
||||||
workers[this.workerIndex].request({
|
workers[this.workerIndex].request({
|
||||||
name: 'changeData',
|
name: 'changeData',
|
||||||
args: [
|
args: [
|
||||||
this.key,
|
this.key,
|
||||||
this.animationData,
|
this.tgsUrl,
|
||||||
this.params.isLowPriority,
|
this.params.isLowPriority,
|
||||||
this.onChangeData.bind(this),
|
this.onChangeData.bind(this),
|
||||||
],
|
],
|
||||||
@ -291,6 +282,7 @@ class RLottie {
|
|||||||
this.lastRenderAt = undefined;
|
this.lastRenderAt = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isEnded = false;
|
||||||
this.isAnimating = true;
|
this.isAnimating = true;
|
||||||
this.isWaiting = false;
|
this.isWaiting = false;
|
||||||
|
|
||||||
@ -359,6 +351,7 @@ class RLottie {
|
|||||||
if (delta > 0 && (frameIndex === this.framesCount! - 1 || expectedNextFrameIndex > this.framesCount! - 1)) {
|
if (delta > 0 && (frameIndex === this.framesCount! - 1 || expectedNextFrameIndex > this.framesCount! - 1)) {
|
||||||
if (this.params.noLoop) {
|
if (this.params.noLoop) {
|
||||||
this.isAnimating = false;
|
this.isAnimating = false;
|
||||||
|
this.isEnded = true;
|
||||||
this.onEnded?.();
|
this.onEnded?.();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -369,6 +362,7 @@ class RLottie {
|
|||||||
} else if (delta < 0 && (frameIndex === 0 || expectedNextFrameIndex < 0)) {
|
} else if (delta < 0 && (frameIndex === 0 || expectedNextFrameIndex < 0)) {
|
||||||
if (this.params.noLoop) {
|
if (this.params.noLoop) {
|
||||||
this.isAnimating = false;
|
this.isAnimating = false;
|
||||||
|
this.isEnded = true;
|
||||||
this.onEnded?.();
|
this.onEnded?.();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -379,10 +373,10 @@ class RLottie {
|
|||||||
} else if (
|
} else if (
|
||||||
this.stopFrameIndex !== undefined
|
this.stopFrameIndex !== undefined
|
||||||
&& (frameIndex === this.stopFrameIndex
|
&& (frameIndex === this.stopFrameIndex
|
||||||
|| (
|
|| (
|
||||||
(delta > 0 && expectedNextFrameIndex > this.stopFrameIndex)
|
(delta > 0 && expectedNextFrameIndex > this.stopFrameIndex)
|
||||||
|| (delta < 0 && expectedNextFrameIndex < this.stopFrameIndex)
|
|| (delta < 0 && expectedNextFrameIndex < this.stopFrameIndex)
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
this.stopFrameIndex = undefined;
|
this.stopFrameIndex = undefined;
|
||||||
this.isAnimating = false;
|
this.isAnimating = false;
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const renderers = new Map<string, {
|
|||||||
|
|
||||||
async function init(
|
async function init(
|
||||||
key: string,
|
key: string,
|
||||||
animationData: string,
|
tgsUrl: string,
|
||||||
imgSize: number,
|
imgSize: number,
|
||||||
isLowPriority: boolean,
|
isLowPriority: boolean,
|
||||||
onInit: CancellableCallback,
|
onInit: CancellableCallback,
|
||||||
@ -48,7 +48,7 @@ async function init(
|
|||||||
await rLottieApiPromise;
|
await rLottieApiPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = await extractJson(animationData);
|
const json = await extractJson(tgsUrl);
|
||||||
const stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0);
|
const stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0);
|
||||||
const handle = rLottieApi.init();
|
const handle = rLottieApi.init();
|
||||||
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
|
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
|
||||||
@ -62,7 +62,7 @@ async function init(
|
|||||||
|
|
||||||
async function changeData(
|
async function changeData(
|
||||||
key: string,
|
key: string,
|
||||||
animationData: string,
|
tgsUrl: string,
|
||||||
isLowPriority: boolean,
|
isLowPriority: boolean,
|
||||||
onInit: CancellableCallback,
|
onInit: CancellableCallback,
|
||||||
) {
|
) {
|
||||||
@ -70,7 +70,7 @@ async function changeData(
|
|||||||
await rLottieApiPromise;
|
await rLottieApiPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = await extractJson(animationData);
|
const json = await extractJson(tgsUrl);
|
||||||
const stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0);
|
const stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0);
|
||||||
const { handle } = renderers.get(key)!;
|
const { handle } = renderers.get(key)!;
|
||||||
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
|
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
|
||||||
@ -79,8 +79,8 @@ async function changeData(
|
|||||||
onInit(reduceFactor, msPerFrame, reducedFramesCount);
|
onInit(reduceFactor, msPerFrame, reducedFramesCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractJson(animationData: string) {
|
async function extractJson(tgsUrl: string) {
|
||||||
const response = await fetch(animationData);
|
const response = await fetch(tgsUrl);
|
||||||
const contentType = response.headers.get('Content-Type');
|
const contentType = response.headers.get('Content-Type');
|
||||||
|
|
||||||
// Support deprecated JSON format cached locally
|
// Support deprecated JSON format cached locally
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { pause } from './util/schedulers';
|
|||||||
|
|
||||||
declare const self: ServiceWorkerGlobalScope;
|
declare const self: ServiceWorkerGlobalScope;
|
||||||
|
|
||||||
const ASSET_CACHE_PATTERN = /[0-9a-f]{20}.*\.(js|css|woff2?|svg|png|jpg|jpeg|json|wasm)$/;
|
const ASSET_CACHE_PATTERN = /[\da-f]{20}.*\.(js|css|woff2?|svg|png|jpg|jpeg|tgs|json|wasm)$/;
|
||||||
const ACTIVATE_TIMEOUT = 3000;
|
const ACTIVATE_TIMEOUT = 3000;
|
||||||
|
|
||||||
self.addEventListener('install', (e) => {
|
self.addEventListener('install', (e) => {
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import { webpToPng } from './webpToPng';
|
|||||||
|
|
||||||
const asCacheApiType = {
|
const asCacheApiType = {
|
||||||
[ApiMediaFormat.BlobUrl]: cacheApi.Type.Blob,
|
[ApiMediaFormat.BlobUrl]: cacheApi.Type.Blob,
|
||||||
[ApiMediaFormat.Lottie]: cacheApi.Type.Blob,
|
|
||||||
[ApiMediaFormat.Text]: cacheApi.Type.Text,
|
[ApiMediaFormat.Text]: cacheApi.Type.Text,
|
||||||
[ApiMediaFormat.Progressive]: undefined,
|
[ApiMediaFormat.Progressive]: undefined,
|
||||||
[ApiMediaFormat.Stream]: undefined,
|
[ApiMediaFormat.Stream]: undefined,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user