Support Dice (#6572)
This commit is contained in:
parent
049db12d92
commit
1c602dddfa
@ -38,6 +38,10 @@ export interface GramJsAppConfig extends LimitsConfig {
|
||||
file_reference_base64: string;
|
||||
}>;
|
||||
emojies_send_dice: string[];
|
||||
emojies_send_dice_success: Record<string, {
|
||||
value: number;
|
||||
frame_start: number;
|
||||
}>;
|
||||
groupcall_video_participants_max: number;
|
||||
reactions_uniq_max: number;
|
||||
chat_read_mark_size_threshold: number;
|
||||
@ -144,6 +148,17 @@ function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||
}, {}) : {};
|
||||
}
|
||||
|
||||
function buildDiceEmojiesSuccess(appConfig: GramJsAppConfig) {
|
||||
const { emojies_send_dice_success } = appConfig;
|
||||
return emojies_send_dice_success ? Object.entries(emojies_send_dice_success).reduce((acc, [key, value]) => {
|
||||
acc[key] = {
|
||||
value: value.value,
|
||||
frameStart: value.frame_start,
|
||||
};
|
||||
return acc;
|
||||
}, {} as ApiAppConfig['diceEmojiesSuccess']) : {};
|
||||
}
|
||||
|
||||
function getLimit(appConfig: GramJsAppConfig, key: Limit, fallbackKey: ApiLimitType) {
|
||||
const defaultLimit = appConfig[`${key}_default`] || DEFAULT_LIMITS[fallbackKey][0];
|
||||
const premiumLimit = appConfig[`${key}_premium`] || DEFAULT_LIMITS[fallbackKey][1];
|
||||
@ -251,6 +266,8 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
||||
whitelistedBotIds: appConfig.whitelisted_bots,
|
||||
arePasskeysAvailable: appConfig.settings_display_passkeys,
|
||||
passkeysMaxCount: appConfig.passkeys_account_passkeys_max,
|
||||
diceEmojies: appConfig.emojies_send_dice,
|
||||
diceEmojiesSuccess: buildDiceEmojiesSuccess(appConfig),
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -3,6 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
||||
import type {
|
||||
ApiAudio,
|
||||
ApiContact,
|
||||
ApiDice,
|
||||
ApiDocument,
|
||||
ApiFormattedText,
|
||||
ApiGame,
|
||||
@ -179,13 +180,16 @@ export function buildMessageMediaContent(
|
||||
const game = buildGameFromMedia(media);
|
||||
if (game) return { game };
|
||||
|
||||
const dice = buildDiceFromMedia(media);
|
||||
if (dice) return { dice };
|
||||
|
||||
const storyData = buildMessageStoryData(media);
|
||||
if (storyData) return { storyData };
|
||||
|
||||
const giveaway = buildGiweawayFromMedia(media);
|
||||
const giveaway = buildGiveawayFromMedia(media);
|
||||
if (giveaway) return { giveaway };
|
||||
|
||||
const giveawayResults = buildGiweawayResultsFromMedia(media);
|
||||
const giveawayResults = buildGiveawayResultsFromMedia(media);
|
||||
if (giveawayResults) return { giveawayResults };
|
||||
|
||||
const paidMedia = buildPaidMedia(media);
|
||||
@ -668,7 +672,24 @@ function buildGame(media: GramJs.MessageMediaGame): ApiGame | undefined {
|
||||
};
|
||||
}
|
||||
|
||||
function buildGiweawayFromMedia(media: GramJs.TypeMessageMedia): ApiGiveaway | undefined {
|
||||
function buildDiceFromMedia(media: GramJs.TypeMessageMedia): ApiDice | undefined {
|
||||
if (!(media instanceof GramJs.MessageMediaDice)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildDice(media);
|
||||
}
|
||||
|
||||
function buildDice(media: GramJs.MessageMediaDice): ApiDice | undefined {
|
||||
const { value, emoticon } = media;
|
||||
return {
|
||||
mediaType: 'dice',
|
||||
value,
|
||||
emoticon,
|
||||
};
|
||||
}
|
||||
|
||||
function buildGiveawayFromMedia(media: GramJs.TypeMessageMedia): ApiGiveaway | undefined {
|
||||
if (!(media instanceof GramJs.MessageMediaGiveaway)) {
|
||||
return undefined;
|
||||
}
|
||||
@ -696,7 +717,7 @@ function buildGiveaway(media: GramJs.MessageMediaGiveaway): ApiGiveaway | undefi
|
||||
};
|
||||
}
|
||||
|
||||
function buildGiweawayResultsFromMedia(media: GramJs.TypeMessageMedia): ApiGiveawayResults | undefined {
|
||||
function buildGiveawayResultsFromMedia(media: GramJs.TypeMessageMedia): ApiGiveawayResults | undefined {
|
||||
if (!(media instanceof GramJs.MessageMediaGiveawayResults)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import type {
|
||||
ApiAttachment,
|
||||
ApiChat,
|
||||
ApiContact,
|
||||
ApiDice,
|
||||
ApiDraft,
|
||||
ApiFactCheck,
|
||||
ApiInputMessageReplyInfo,
|
||||
@ -428,29 +429,53 @@ function buildNewTodo(todo: ApiNewMediaTodo): ApiMediaTodo {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildLocalMessage(
|
||||
chat: ApiChat,
|
||||
lastMessageId?: number,
|
||||
text?: string,
|
||||
entities?: ApiMessageEntity[],
|
||||
replyInfo?: ApiInputReplyInfo,
|
||||
suggestedPostInfo?: ApiInputSuggestedPostInfo,
|
||||
attachment?: ApiAttachment,
|
||||
sticker?: ApiSticker,
|
||||
gif?: ApiVideo,
|
||||
poll?: ApiNewPoll,
|
||||
todo?: ApiNewMediaTodo,
|
||||
contact?: ApiContact,
|
||||
groupedId?: string,
|
||||
scheduledAt?: number,
|
||||
scheduleRepeatPeriod?: number,
|
||||
sendAs?: ApiPeer,
|
||||
story?: ApiStory | ApiStorySkipped,
|
||||
isInvertedMedia?: true,
|
||||
effectId?: string,
|
||||
isPending?: true,
|
||||
messagePriceInStars?: number,
|
||||
) {
|
||||
export function buildLocalMessage({
|
||||
chat,
|
||||
lastMessageId,
|
||||
text,
|
||||
entities,
|
||||
replyInfo,
|
||||
suggestedPostInfo,
|
||||
attachment,
|
||||
sticker,
|
||||
gif,
|
||||
poll,
|
||||
todo,
|
||||
contact,
|
||||
groupedId,
|
||||
scheduledAt,
|
||||
scheduleRepeatPeriod,
|
||||
sendAs,
|
||||
story,
|
||||
isInvertedMedia,
|
||||
effectId,
|
||||
isPending,
|
||||
messagePriceInStars,
|
||||
dice,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
lastMessageId?: number;
|
||||
text?: string;
|
||||
entities?: ApiMessageEntity[];
|
||||
replyInfo?: ApiInputReplyInfo;
|
||||
suggestedPostInfo?: ApiInputSuggestedPostInfo;
|
||||
attachment?: ApiAttachment;
|
||||
sticker?: ApiSticker;
|
||||
gif?: ApiVideo;
|
||||
poll?: ApiNewPoll;
|
||||
todo?: ApiNewMediaTodo;
|
||||
contact?: ApiContact;
|
||||
groupedId?: string;
|
||||
scheduledAt?: number;
|
||||
scheduleRepeatPeriod?: number;
|
||||
sendAs?: ApiPeer;
|
||||
story?: ApiStory | ApiStorySkipped;
|
||||
isInvertedMedia?: true;
|
||||
effectId?: string;
|
||||
isPending?: true;
|
||||
messagePriceInStars?: number;
|
||||
dice?: string;
|
||||
}) {
|
||||
const localId = getNextLocalMessageId(lastMessageId);
|
||||
const media = attachment && buildUploadingMedia(attachment);
|
||||
const isChannel = chat.type === 'chatTypeChannel';
|
||||
@ -460,7 +485,13 @@ export function buildLocalMessage(
|
||||
const localPoll = poll && buildNewPoll(poll, localId);
|
||||
const localTodo = todo && buildNewTodo(todo);
|
||||
|
||||
const formattedText = text ? addTimestampEntities(
|
||||
const localDice = dice ? {
|
||||
mediaType: 'dice',
|
||||
value: -1,
|
||||
emoticon: dice,
|
||||
} satisfies ApiDice : undefined;
|
||||
|
||||
const formattedText = text && !dice ? addTimestampEntities(
|
||||
{ text, entities, emojiOnlyCount: undefined },
|
||||
) : undefined;
|
||||
|
||||
@ -476,6 +507,7 @@ export function buildLocalMessage(
|
||||
storyData: story && { mediaType: 'storyData', ...story },
|
||||
pollId: localPoll?.id,
|
||||
todo: localTodo,
|
||||
dice: localDice,
|
||||
}),
|
||||
date: scheduledAt || getServerTime(),
|
||||
isOutgoing: !isChannel,
|
||||
|
||||
@ -301,7 +301,7 @@ export function sendMessageLocal(
|
||||
const {
|
||||
chat, lastMessageId, text, entities, replyInfo, suggestedPostInfo, attachment, sticker, story, gif, poll, todo,
|
||||
contact, scheduledAt, scheduleRepeatPeriod, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending,
|
||||
messagePriceInStars,
|
||||
messagePriceInStars, dice,
|
||||
} = params;
|
||||
|
||||
if (!chat) return undefined;
|
||||
@ -309,7 +309,7 @@ export function sendMessageLocal(
|
||||
const {
|
||||
message: localMessage,
|
||||
poll: localPoll,
|
||||
} = buildLocalMessage(
|
||||
} = buildLocalMessage({
|
||||
chat,
|
||||
lastMessageId,
|
||||
text,
|
||||
@ -331,7 +331,8 @@ export function sendMessageLocal(
|
||||
effectId,
|
||||
isPending,
|
||||
messagePriceInStars,
|
||||
);
|
||||
dice,
|
||||
});
|
||||
|
||||
sendApiUpdate({
|
||||
'@type': localMessage.isScheduled ? 'newScheduledMessage' : 'newMessage',
|
||||
@ -352,7 +353,7 @@ export function sendApiMessage(
|
||||
) {
|
||||
const {
|
||||
chat, text, entities, replyInfo, suggestedPostInfo, suggestedMedia,
|
||||
attachment, sticker, story, gif, poll, todo, contact,
|
||||
attachment, sticker, story, gif, poll, todo, contact, dice,
|
||||
|
||||
isSilent, scheduledAt, scheduleRepeatPeriod, groupedId, noWebPage, sendAs, shouldUpdateStickerSetOrder,
|
||||
isInvertedMedia, effectId, webPageMediaSize, webPageUrl, messagePriceInStars,
|
||||
@ -465,6 +466,10 @@ export function sendApiMessage(
|
||||
lastName: contact.lastName,
|
||||
vcard: DEFAULT_PRIMITIVES.STRING,
|
||||
});
|
||||
} else if (dice) {
|
||||
media = new GramJs.InputMediaDice({
|
||||
emoticon: dice,
|
||||
});
|
||||
}
|
||||
|
||||
type SharedKeys<T, U> = {
|
||||
|
||||
@ -277,6 +277,27 @@ export async function fetchPremiumGifts() {
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchDiceStickers({ emoji }: { emoji: string }) {
|
||||
const result = await invokeRequest(new GramJs.messages.GetStickerSet({
|
||||
stickerset: new GramJs.InputStickerSetDice({
|
||||
emoticon: emoji,
|
||||
}),
|
||||
hash: DEFAULT_PRIMITIVES.INT,
|
||||
}));
|
||||
|
||||
if (!(result instanceof GramJs.messages.StickerSet)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
localDb.stickerSets[String(result.set.id)] = result.set;
|
||||
|
||||
return {
|
||||
set: buildStickerSet(result.set),
|
||||
stickers: processStickerResult(result.documents),
|
||||
packs: processStickerPackResult(result.packs),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchTonGifts() {
|
||||
const result = await invokeRequest(new GramJs.messages.GetStickerSet({
|
||||
stickerset: new GramJs.InputStickerSetTonGifts(),
|
||||
|
||||
@ -304,6 +304,12 @@ export type ApiGame = {
|
||||
document?: ApiDocument;
|
||||
};
|
||||
|
||||
export type ApiDice = {
|
||||
mediaType: 'dice';
|
||||
value: number;
|
||||
emoticon: string;
|
||||
};
|
||||
|
||||
export type ApiGiveaway = {
|
||||
mediaType: 'giveaway';
|
||||
quantity: number;
|
||||
@ -608,6 +614,7 @@ export type MediaContent = {
|
||||
giveaway?: ApiGiveaway;
|
||||
giveawayResults?: ApiGiveawayResults;
|
||||
paidMedia?: ApiPaidMedia;
|
||||
dice?: ApiDice;
|
||||
ttlSeconds?: number;
|
||||
};
|
||||
export type MediaContainer = {
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import type { TeactNode } from '../../lib/teact/teact';
|
||||
|
||||
import type { CallbackAction } from '../../global/types';
|
||||
import type { IconName } from '../../types/icons';
|
||||
import type { RegularLangFnParameters } from '../../util/localization';
|
||||
import type { ApiDocument, ApiFormattedText, ApiPhoto, ApiReaction } from './messages';
|
||||
import type { LangFnParameters, RegularLangFnParameters } from '../../util/localization';
|
||||
import type { ApiDocument, ApiFormattedText, ApiMessageEntity, ApiPhoto, ApiReaction } from './messages';
|
||||
import type { ApiPremiumSection } from './payments';
|
||||
import type { ApiBotVerification } from './peers';
|
||||
import type { ApiStarsSubscriptionPricing } from './stars';
|
||||
@ -123,8 +121,7 @@ export type ApiNotification = {
|
||||
localId: string;
|
||||
containerSelector?: string;
|
||||
type?: 'paidMessage' | undefined;
|
||||
title?: string | RegularLangFnParameters;
|
||||
message: TeactNode | RegularLangFnParameters;
|
||||
title?: string | LangFnParameters;
|
||||
cacheBreaker?: string;
|
||||
actionText?: string | RegularLangFnParameters;
|
||||
action?: CallbackAction | CallbackAction[];
|
||||
@ -136,7 +133,13 @@ export type ApiNotification = {
|
||||
customEmojiIconId?: string;
|
||||
shouldUseCustomIcon?: boolean;
|
||||
dismissAction?: CallbackAction;
|
||||
};
|
||||
} & ({
|
||||
message: string;
|
||||
messageEntities?: ApiMessageEntity[];
|
||||
} | {
|
||||
message: LangFnParameters;
|
||||
messageEntities?: undefined;
|
||||
});
|
||||
|
||||
export type ApiError = {
|
||||
message: string;
|
||||
@ -289,6 +292,11 @@ export interface ApiAppConfig {
|
||||
whitelistedBotIds?: string[];
|
||||
arePasskeysAvailable: boolean;
|
||||
passkeysMaxCount: number;
|
||||
diceEmojies: string[];
|
||||
diceEmojiesSuccess: Record<string, {
|
||||
value: number;
|
||||
frameStart: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ApiConfig {
|
||||
|
||||
@ -2557,6 +2557,8 @@
|
||||
"BotReadTextFromClipboardTitle" = "Clipboard Access";
|
||||
"BotReadTextFromClipboardDescription" = "{bot} wants to read the contents of your clipboard. Do you want to continue?";
|
||||
"BotReadTextFromClipboardConfirm" = "Allow";
|
||||
"DiceToast" = "Send a {emoji} emoji to try your luck.";
|
||||
"DiceToastSend" = "Send";
|
||||
"ChatTypePrivate" = "Private Chat";
|
||||
"ChatTypeGroup" = "Group";
|
||||
"ChatTypeChannel" = "Channel";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ElementRef, FC } from '../../lib/teact/teact';
|
||||
import type { ElementRef } from '../../lib/teact/teact';
|
||||
import {
|
||||
getIsHeavyAnimating,
|
||||
memo,
|
||||
@ -39,6 +39,7 @@ export type OwnProps = {
|
||||
tgsUrl?: string;
|
||||
play?: boolean | string;
|
||||
playSegment?: [number, number];
|
||||
seekToEnd?: boolean;
|
||||
speed?: number;
|
||||
noLoop?: boolean;
|
||||
size: number;
|
||||
@ -55,11 +56,12 @@ export type OwnProps = {
|
||||
onLoad?: NoneToVoidFunction;
|
||||
onEnded?: NoneToVoidFunction;
|
||||
onLoop?: NoneToVoidFunction;
|
||||
onFrame?: (index: number) => void;
|
||||
};
|
||||
|
||||
const THROTTLE_MS = 150;
|
||||
|
||||
const AnimatedSticker: FC<OwnProps> = ({
|
||||
const AnimatedSticker = ({
|
||||
ref,
|
||||
renderId,
|
||||
className,
|
||||
@ -68,6 +70,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
play,
|
||||
playSegment,
|
||||
speed,
|
||||
seekToEnd,
|
||||
noLoop,
|
||||
size,
|
||||
quality,
|
||||
@ -83,7 +86,8 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
onLoad,
|
||||
onEnded,
|
||||
onLoop,
|
||||
}) => {
|
||||
onFrame,
|
||||
}: OwnProps) => {
|
||||
let containerRef = useRef<HTMLDivElement>();
|
||||
if (ref) {
|
||||
containerRef = ref;
|
||||
@ -160,12 +164,17 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
onLoad,
|
||||
onEnded,
|
||||
onLoop,
|
||||
onFrame,
|
||||
);
|
||||
|
||||
if (speed) {
|
||||
newAnimation.setSpeed(speed);
|
||||
}
|
||||
|
||||
if (seekToEnd) {
|
||||
newAnimation.seekToEnd();
|
||||
}
|
||||
|
||||
setAnimation(newAnimation);
|
||||
animationRef.current = newAnimation;
|
||||
});
|
||||
@ -207,6 +216,8 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
|
||||
if (playSegmentRef.current) {
|
||||
animation.playSegment(playSegmentRef.current, shouldRestart, viewId);
|
||||
} else if (seekToEnd) {
|
||||
animation.seekToEnd();
|
||||
} else {
|
||||
animation.play(shouldRestart, viewId);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ElementRef, FC } from '../../lib/teact/teact';
|
||||
import type { ElementRef } from '../../lib/teact/teact';
|
||||
import { memo, useMemo, useRef } from '../../lib/teact/teact';
|
||||
import { getGlobal } from '../../global';
|
||||
|
||||
@ -16,6 +16,7 @@ import useColorFilter from '../../hooks/stickers/useColorFilter';
|
||||
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||
import useMountAfterHeavyAnimation from '../../hooks/useMountAfterHeavyAnimation';
|
||||
@ -39,24 +40,28 @@ type OwnProps = {
|
||||
loopLimit?: number;
|
||||
shouldLoop?: boolean;
|
||||
shouldPreloadPreview?: boolean;
|
||||
skipPreview?: boolean;
|
||||
forceAlways?: boolean;
|
||||
forceOnHeavyAnimation?: boolean;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
noLoad?: boolean;
|
||||
noPlay?: boolean;
|
||||
forceAnimatedStickerOnEnd?: boolean;
|
||||
noVideoOnMobile?: boolean;
|
||||
withSharedAnimation?: boolean;
|
||||
sharedCanvasRef?: ElementRef<HTMLCanvasElement>;
|
||||
withTranslucentThumb?: boolean; // With shared canvas thumbs are opaque by default to provide better transition effect
|
||||
onVideoEnded?: AnyToVoidFunction;
|
||||
onAnimatedStickerLoop?: AnyToVoidFunction;
|
||||
onAnimatedStickerFrame?: (frame: number) => void;
|
||||
onAnimatedStickerLoad?: AnyToVoidFunction;
|
||||
};
|
||||
|
||||
const SHARED_PREFIX = 'shared';
|
||||
const STICKER_SIZE = 24;
|
||||
|
||||
const StickerView: FC<OwnProps> = ({
|
||||
const StickerView = ({
|
||||
containerRef,
|
||||
sticker,
|
||||
thumbClassName,
|
||||
@ -68,6 +73,7 @@ const StickerView: FC<OwnProps> = ({
|
||||
loopLimit,
|
||||
shouldLoop = false,
|
||||
shouldPreloadPreview,
|
||||
skipPreview,
|
||||
forceAlways,
|
||||
forceOnHeavyAnimation,
|
||||
observeIntersectionForLoading,
|
||||
@ -75,12 +81,15 @@ const StickerView: FC<OwnProps> = ({
|
||||
noLoad,
|
||||
noPlay,
|
||||
noVideoOnMobile,
|
||||
forceAnimatedStickerOnEnd,
|
||||
withSharedAnimation,
|
||||
withTranslucentThumb,
|
||||
sharedCanvasRef,
|
||||
onVideoEnded,
|
||||
onAnimatedStickerLoop,
|
||||
}) => {
|
||||
onAnimatedStickerFrame,
|
||||
onAnimatedStickerLoad,
|
||||
}: OwnProps) => {
|
||||
const {
|
||||
id, isLottie, stickerSetInfo, emoji,
|
||||
} = sticker;
|
||||
@ -111,10 +120,11 @@ const StickerView: FC<OwnProps> = ({
|
||||
|
||||
const cachedPreview = mediaLoader.getFromMemory(previewMediaHash);
|
||||
const isReadyToMountFullMedia = useMountAfterHeavyAnimation(hasIntersectedForPlayingRef.current);
|
||||
const shouldForcePreview = isUnsupportedVideo || (isStatic ? isSmall : noPlay);
|
||||
const shouldLoadPreview = !customColor && !cachedPreview && (!isReadyToMountFullMedia || shouldForcePreview);
|
||||
const shouldForcePreview = !skipPreview && (isUnsupportedVideo || (isStatic ? isSmall : noPlay));
|
||||
const shouldLoadPreview = !skipPreview && !customColor && !cachedPreview
|
||||
&& (!isReadyToMountFullMedia || shouldForcePreview);
|
||||
const previewMediaData = useMedia(previewMediaHash, !shouldLoadPreview);
|
||||
const withPreview = shouldLoadPreview || cachedPreview;
|
||||
const withPreview = !skipPreview && (shouldLoadPreview || cachedPreview);
|
||||
|
||||
const shouldSkipLoadingFullMedia = Boolean(shouldForcePreview || (
|
||||
fullMediaHash === previewMediaHash && (cachedPreview || previewMediaData)
|
||||
@ -125,7 +135,7 @@ const StickerView: FC<OwnProps> = ({
|
||||
const isFullMediaReady = shouldRenderFullMedia && (isStatic || isPlayerReady);
|
||||
|
||||
const thumbDataUri = useThumbnail(sticker.thumbnail);
|
||||
const thumbData = cachedPreview || previewMediaData || thumbDataUri;
|
||||
const thumbData = !skipPreview ? (cachedPreview || previewMediaData || thumbDataUri) : undefined;
|
||||
const isThumbOpaque = sharedCanvasRef && !withTranslucentThumb;
|
||||
|
||||
const noCrossTransition = Boolean(isLottie && withPreview);
|
||||
@ -153,6 +163,11 @@ const StickerView: FC<OwnProps> = ({
|
||||
].filter(Boolean).join('_')
|
||||
), [id, size, customColor, dpr, withSharedAnimation, randomIdPrefix]);
|
||||
|
||||
const handleAnimatedStickerLoad = useLastCallback(() => {
|
||||
onAnimatedStickerLoad?.();
|
||||
markPlayerReady();
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
@ -182,15 +197,17 @@ const StickerView: FC<OwnProps> = ({
|
||||
)}
|
||||
tgsUrl={fullMediaData}
|
||||
play={shouldPlay}
|
||||
seekToEnd={forceAnimatedStickerOnEnd}
|
||||
noLoop={!shouldLoop}
|
||||
forceOnHeavyAnimation={forceAlways || forceOnHeavyAnimation}
|
||||
forceAlways={forceAlways}
|
||||
isLowPriority={isSmall && !selectIsAlwaysHighPriorityEmoji(getGlobal(), stickerSetInfo)}
|
||||
sharedCanvas={sharedCanvasRef?.current || undefined}
|
||||
sharedCanvasCoords={coords}
|
||||
onLoad={markPlayerReady}
|
||||
onLoad={handleAnimatedStickerLoad}
|
||||
onLoop={onAnimatedStickerLoop}
|
||||
onEnded={onAnimatedStickerLoop}
|
||||
onFrame={onAnimatedStickerFrame}
|
||||
color={customColor}
|
||||
/>
|
||||
) : isVideo ? (
|
||||
|
||||
@ -146,6 +146,7 @@ type StateProps = {
|
||||
isAccountFrozen?: boolean;
|
||||
isAppConfigLoaded?: boolean;
|
||||
isFoldersSidebarShown: boolean;
|
||||
diceEmojies?: string[];
|
||||
selectedGiftAuction?: ApiStarGiftAuctionState;
|
||||
};
|
||||
|
||||
@ -200,6 +201,7 @@ const Main = ({
|
||||
isAccountFrozen,
|
||||
isAppConfigLoaded,
|
||||
isFoldersSidebarShown,
|
||||
diceEmojies,
|
||||
selectedGiftAuction,
|
||||
}: OwnProps & StateProps) => {
|
||||
const {
|
||||
@ -216,6 +218,7 @@ const Main = ({
|
||||
loadCountryList,
|
||||
loadAvailableReactions,
|
||||
loadStickerSets,
|
||||
loadDiceStickers,
|
||||
loadPremiumGifts,
|
||||
loadTonGifts,
|
||||
loadStarGifts,
|
||||
@ -390,6 +393,12 @@ const Main = ({
|
||||
}
|
||||
}, [addedSetIds, addedCustomEmojiIds, isMasterTab, isSynced, isAppConfigLoaded, isAccountFrozen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMasterTab && isSynced && isAppConfigLoaded && !isAccountFrozen && diceEmojies) {
|
||||
loadDiceStickers();
|
||||
}
|
||||
}, [isMasterTab, isSynced, isAppConfigLoaded, isAccountFrozen, diceEmojies]);
|
||||
|
||||
useEffect(() => {
|
||||
loadBotFreezeAppeal();
|
||||
}, [isAppConfigLoaded]);
|
||||
@ -708,6 +717,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
isAccountFrozen,
|
||||
isAppConfigLoaded: global.isAppConfigLoaded,
|
||||
isFoldersSidebarShown: foldersPosition === FOLDERS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global),
|
||||
diceEmojies: global.appConfig?.diceEmojies,
|
||||
selectedGiftAuction,
|
||||
};
|
||||
},
|
||||
|
||||
@ -181,6 +181,7 @@ import AnimatedEmoji from './AnimatedEmoji';
|
||||
import CommentButton from './CommentButton';
|
||||
import Contact from './Contact';
|
||||
import ContextMenuContainer from './ContextMenuContainer.async';
|
||||
import DiceWrapper from './dice/DiceWrapper';
|
||||
import FactCheck from './FactCheck';
|
||||
import Game from './Game';
|
||||
import Giveaway from './Giveaway';
|
||||
@ -477,6 +478,7 @@ const Message = ({
|
||||
const [isPlayingSnapAnimation, setIsPlayingSnapAnimation] = useState(false);
|
||||
const [isPlayingDeleteAnimation, setIsPlayingDeleteAnimation] = useState(false);
|
||||
const [shouldPlayEffect, requestEffect, hideEffect] = useFlag();
|
||||
const [shouldPlayDiceEffect, requestDiceEffect, hideDiceEffect] = useFlag();
|
||||
const [isDeclineDialogOpen, openDeclineDialog, closeDeclineDialog] = useFlag();
|
||||
const [declineReason, setDeclineReason] = useState('');
|
||||
const { isMobile, isTouchScreen } = useAppLayout();
|
||||
@ -547,7 +549,7 @@ const Message = ({
|
||||
voice, document, sticker, contact,
|
||||
invoice, location,
|
||||
action, game, storyData, giveaway,
|
||||
giveawayResults, todo,
|
||||
giveawayResults, todo, dice,
|
||||
} = getMessageContent(message);
|
||||
|
||||
const messageReplyInfo = getMessageReplyInfo(message);
|
||||
@ -780,6 +782,14 @@ const Message = ({
|
||||
}
|
||||
}, [effect, isLocal, memoFirstUnreadIdRef, messageId, sticker?.hasEffect]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dice && ((
|
||||
memoFirstUnreadIdRef?.current && messageId >= memoFirstUnreadIdRef.current
|
||||
) || isLocal)) {
|
||||
requestDiceEffect();
|
||||
}
|
||||
}, [dice, memoFirstUnreadIdRef, messageId, isLocal]);
|
||||
|
||||
const detectedLanguage = useTextLanguage(
|
||||
text?.text,
|
||||
!(areTranslationsEnabled && shouldDetectChatLanguage) || isTypingDraft,
|
||||
@ -1299,6 +1309,15 @@ const Message = ({
|
||||
canAutoLoadMedia={canAutoLoadMedia}
|
||||
/>
|
||||
)}
|
||||
{dice && (
|
||||
<DiceWrapper
|
||||
isLocal={isLocal}
|
||||
dice={dice}
|
||||
isOutgoing={isOwn}
|
||||
canPlayWinEffect={shouldPlayDiceEffect}
|
||||
onEffectPlayed={hideDiceEffect}
|
||||
/>
|
||||
)}
|
||||
{invoice?.extendedMedia && (
|
||||
<InvoiceMediaPreview
|
||||
message={message}
|
||||
|
||||
@ -179,7 +179,8 @@ const Poll: FC<OwnProps> = ({
|
||||
const showSolution = useLastCallback(() => {
|
||||
showNotification({
|
||||
localId: getMessageKey(message),
|
||||
message: renderTextWithEntities({ text: poll.results.solution!, entities: poll.results.solutionEntities }),
|
||||
message: poll.results.solution!,
|
||||
messageEntities: poll.results.solutionEntities,
|
||||
duration: SOLUTION_DURATION,
|
||||
containerSelector: SOLUTION_CONTAINER_ID,
|
||||
});
|
||||
|
||||
19
src/components/middle/message/dice/Dice.module.scss
Normal file
19
src/components/middle/message/dice/Dice.module.scss
Normal file
@ -0,0 +1,19 @@
|
||||
.root {
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
position: relative;
|
||||
|
||||
display: block !important;
|
||||
|
||||
width: var(--_size);
|
||||
height: var(--_size);
|
||||
}
|
||||
|
||||
.sticker {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
141
src/components/middle/message/dice/Dice.tsx
Normal file
141
src/components/middle/message/dice/Dice.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
import { memo, useRef, useState } from '@teact';
|
||||
import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiSticker } from '../../../../api/types';
|
||||
import type { OwnProps } from './DiceWrapper';
|
||||
|
||||
import { selectDiceSticker, selectIdleDiceSticker } from '../../../../global/selectors/symbols';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
import { getStickerDimensions, REM } from '../../../common/helpers/mediaDimensions';
|
||||
|
||||
import useAppLayout from '../../../../hooks/useAppLayout';
|
||||
import useFlag from '../../../../hooks/useFlag';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
|
||||
import StickerView from '../../../common/StickerView';
|
||||
|
||||
import styles from './Dice.module.scss';
|
||||
|
||||
type StateProps = {
|
||||
idleSticker?: ApiSticker;
|
||||
valueSticker?: ApiSticker;
|
||||
winEffect?: {
|
||||
value: number;
|
||||
frameStart: number;
|
||||
};
|
||||
};
|
||||
|
||||
const FALLBACK_SIZE = 13 * REM;
|
||||
|
||||
const Dice = ({
|
||||
dice,
|
||||
idleSticker,
|
||||
valueSticker,
|
||||
winEffect,
|
||||
canPlayWinEffect,
|
||||
isLocal,
|
||||
isOutgoing,
|
||||
onEffectPlayed,
|
||||
observeIntersectionForLoading,
|
||||
observeIntersectionForPlaying,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { requestConfetti, showNotification } = getActions();
|
||||
const { isMobile } = useAppLayout();
|
||||
const { width } = idleSticker ? getStickerDimensions(idleSticker, isMobile) : { width: FALLBACK_SIZE };
|
||||
|
||||
const shouldSkipToEnd = !canPlayWinEffect && !isLocal;
|
||||
const [isShowingResult, setIsShowingResult] = useState<boolean>(shouldSkipToEnd);
|
||||
const [isValueStickerLoaded, markValueStickerLoaded] = useFlag();
|
||||
|
||||
const idleContainerRef = useRef<HTMLDivElement>();
|
||||
const valueContainerRef = useRef<HTMLDivElement>();
|
||||
|
||||
const onIdleLoop = useLastCallback(() => {
|
||||
setIsShowingResult(isValueStickerLoaded);
|
||||
});
|
||||
|
||||
const onValueFrame = useLastCallback((frame: number) => {
|
||||
if (canPlayWinEffect && isOutgoing && dice.value === winEffect?.value && frame === winEffect?.frameStart) {
|
||||
requestConfetti({});
|
||||
onEffectPlayed?.();
|
||||
}
|
||||
});
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
showNotification({
|
||||
message: {
|
||||
key: 'DiceToast',
|
||||
variables: {
|
||||
emoji: dice.emoticon,
|
||||
},
|
||||
options: {
|
||||
withNodes: true,
|
||||
},
|
||||
},
|
||||
action: {
|
||||
action: 'sendDiceInCurrentChat',
|
||||
payload: {
|
||||
emoji: dice.emoticon,
|
||||
},
|
||||
},
|
||||
actionText: {
|
||||
key: 'DiceToastSend',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.root} style={`--_size: ${width}px`} onClick={handleClick}>
|
||||
{idleSticker && (
|
||||
<div
|
||||
ref={idleContainerRef}
|
||||
className={buildClassName(styles.sticker, isShowingResult && styles.hidden)}
|
||||
>
|
||||
<StickerView
|
||||
containerRef={idleContainerRef}
|
||||
sticker={idleSticker}
|
||||
size={width}
|
||||
noPlay={isShowingResult}
|
||||
shouldLoop
|
||||
forceAlways
|
||||
skipPreview={isShowingResult}
|
||||
onAnimatedStickerLoop={onIdleLoop}
|
||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{valueSticker && (
|
||||
<div ref={valueContainerRef} className={buildClassName(styles.sticker, !isShowingResult && styles.hidden)}>
|
||||
<StickerView
|
||||
containerRef={valueContainerRef}
|
||||
sticker={valueSticker}
|
||||
size={width}
|
||||
noPlay={!isShowingResult}
|
||||
skipPreview
|
||||
forceAlways
|
||||
forceAnimatedStickerOnEnd={shouldSkipToEnd}
|
||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||
onAnimatedStickerLoad={markValueStickerLoaded}
|
||||
onAnimatedStickerFrame={onValueFrame}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { dice }): Complete<StateProps> => {
|
||||
const idleSticker = selectIdleDiceSticker(global, dice.emoticon);
|
||||
const valueSticker = selectDiceSticker(global, dice.emoticon, dice.value);
|
||||
|
||||
const winEffect = global.appConfig.diceEmojiesSuccess[dice.emoticon];
|
||||
return {
|
||||
idleSticker,
|
||||
valueSticker,
|
||||
winEffect,
|
||||
};
|
||||
},
|
||||
)(Dice));
|
||||
29
src/components/middle/message/dice/DiceWrapper.tsx
Normal file
29
src/components/middle/message/dice/DiceWrapper.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { ApiDice } from '../../../../api/types';
|
||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||
|
||||
import { SLOT_MACHINE_EMOJI } from '../../../../config';
|
||||
|
||||
import Dice from './Dice';
|
||||
import SlotMachine from './SlotMachine';
|
||||
|
||||
export type OwnProps = {
|
||||
dice: ApiDice;
|
||||
canPlayWinEffect?: boolean;
|
||||
isLocal?: boolean;
|
||||
isOutgoing?: boolean;
|
||||
onEffectPlayed?: NoneToVoidFunction;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
};
|
||||
|
||||
const DiceWrapper = (props: OwnProps) => {
|
||||
if (props.dice.emoticon === SLOT_MACHINE_EMOJI) {
|
||||
return <SlotMachine {...props} />;
|
||||
}
|
||||
|
||||
return <Dice {...props} />;
|
||||
};
|
||||
|
||||
export default memo(DiceWrapper);
|
||||
202
src/components/middle/message/dice/SlotMachine.tsx
Normal file
202
src/components/middle/message/dice/SlotMachine.tsx
Normal file
@ -0,0 +1,202 @@
|
||||
import { memo, useRef, useState } from '../../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiStickerSet } from '../../../../api/types';
|
||||
import type { OwnProps } from './DiceWrapper';
|
||||
|
||||
import { SLOT_MACHINE_EMOJI } from '../../../../config';
|
||||
import { getStickerMediaHash } from '../../../../global/helpers';
|
||||
import { selectStickerSet } from '../../../../global/selectors';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
import { getStickerDimensions, REM } from '../../../common/helpers/mediaDimensions';
|
||||
import { prepareSlotMachine } from '../helpers/prepareSlotMachine';
|
||||
|
||||
import useAppLayout from '../../../../hooks/useAppLayout';
|
||||
import useFlag from '../../../../hooks/useFlag';
|
||||
import { useIsIntersecting } from '../../../../hooks/useIntersectionObserver';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
import useMedia from '../../../../hooks/useMedia';
|
||||
import useMediaTransition from '../../../../hooks/useMediaTransition';
|
||||
|
||||
import AnimatedSticker from '../../../common/AnimatedSticker';
|
||||
|
||||
import styles from './Dice.module.scss';
|
||||
|
||||
type StateProps = {
|
||||
slotsStickerSet?: ApiStickerSet;
|
||||
winEffect?: {
|
||||
value: number;
|
||||
frameStart: number;
|
||||
};
|
||||
};
|
||||
|
||||
const FALLBACK_SIZE = 13 * REM;
|
||||
const STICKER_RENDER_DELAY = 100;
|
||||
const WIN_BACKGROUND_DELAY = 700;
|
||||
|
||||
const SlotMachine = ({
|
||||
dice,
|
||||
canPlayWinEffect,
|
||||
isLocal,
|
||||
isOutgoing,
|
||||
slotsStickerSet,
|
||||
winEffect,
|
||||
onEffectPlayed,
|
||||
observeIntersectionForLoading,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { requestConfetti, showNotification } = getActions();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const isWin = dice.value === winEffect?.value;
|
||||
const shouldSkipToEnd = !canPlayWinEffect && !isLocal;
|
||||
|
||||
const loadedCountRef = useRef(0);
|
||||
const [isReady, markReady] = useFlag(!shouldSkipToEnd);
|
||||
|
||||
const [backgroundState, setBackgroundState] = useState<'base' | 'win'>(shouldSkipToEnd && isWin ? 'win' : 'base');
|
||||
const [spinState, setSpinState] = useState<'base' | 'result'>(shouldSkipToEnd ? 'result' : 'base');
|
||||
|
||||
const { ref } = useMediaTransition({
|
||||
hasMediaData: isReady,
|
||||
});
|
||||
|
||||
const canLoad = useIsIntersecting(ref, observeIntersectionForLoading);
|
||||
|
||||
const preparedStickers = slotsStickerSet?.stickers && prepareSlotMachine(slotsStickerSet?.stickers, dice.value);
|
||||
const backgroundHash = preparedStickers?.background
|
||||
? getStickerMediaHash(preparedStickers.background, 'full') : undefined;
|
||||
const backgroundData = useMedia(backgroundHash, !canLoad);
|
||||
const frameWinHash = preparedStickers?.frameWin && isWin
|
||||
? getStickerMediaHash(preparedStickers.frameWin, 'full') : undefined;
|
||||
const frameWinData = useMedia(frameWinHash, !canLoad);
|
||||
const frameStartHash = preparedStickers?.frameStart
|
||||
? getStickerMediaHash(preparedStickers.frameStart, 'full') : undefined;
|
||||
const frameStartData = useMedia(frameStartHash, !canLoad);
|
||||
|
||||
const leftSpinHash = preparedStickers?.leftSpin
|
||||
? getStickerMediaHash(preparedStickers.leftSpin, 'full') : undefined;
|
||||
const leftSpinData = useMedia(leftSpinHash, !canLoad);
|
||||
const middleSpinHash = preparedStickers?.middleSpin
|
||||
? getStickerMediaHash(preparedStickers.middleSpin, 'full') : undefined;
|
||||
const middleSpinData = useMedia(middleSpinHash, !canLoad);
|
||||
const rightSpinHash = preparedStickers?.rightSpin
|
||||
? getStickerMediaHash(preparedStickers.rightSpin, 'full') : undefined;
|
||||
const rightSpinData = useMedia(rightSpinHash, !canLoad);
|
||||
|
||||
const leftResultHash = preparedStickers?.leftResult
|
||||
? getStickerMediaHash(preparedStickers.leftResult, 'full') : undefined;
|
||||
const leftResultData = useMedia(leftResultHash, !canLoad);
|
||||
const middleResultHash = preparedStickers?.middleResult
|
||||
? getStickerMediaHash(preparedStickers.middleResult, 'full') : undefined;
|
||||
const middleResultData = useMedia(middleResultHash, !canLoad);
|
||||
const rightResultHash = preparedStickers?.rightResult
|
||||
? getStickerMediaHash(preparedStickers.rightResult, 'full') : undefined;
|
||||
const rightResultData = useMedia(rightResultHash, !canLoad);
|
||||
|
||||
const { width } = preparedStickers ? getStickerDimensions(preparedStickers.background, isMobile)
|
||||
: { width: FALLBACK_SIZE };
|
||||
|
||||
const isWaitingForResults = !leftResultData || !middleResultData || !rightResultData;
|
||||
|
||||
const handleLoaded = useLastCallback(() => {
|
||||
loadedCountRef.current += 1;
|
||||
if (loadedCountRef.current >= 3) {
|
||||
setTimeout(() => {
|
||||
markReady();
|
||||
}, STICKER_RENDER_DELAY);
|
||||
}
|
||||
});
|
||||
|
||||
const handleSpinEnded = useLastCallback(() => {
|
||||
if (isWaitingForResults) return;
|
||||
setSpinState('result');
|
||||
// Result spin start - too early. Result spin end - too late.
|
||||
if (isWin) setTimeout(() => setBackgroundState('win'), WIN_BACKGROUND_DELAY);
|
||||
});
|
||||
|
||||
const onWinBackgroundFrame = useLastCallback((frame: number) => {
|
||||
if (canPlayWinEffect && isOutgoing && isWin && frame === winEffect?.frameStart) {
|
||||
requestConfetti({});
|
||||
onEffectPlayed?.();
|
||||
}
|
||||
});
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
showNotification({
|
||||
message: {
|
||||
key: 'DiceToast',
|
||||
variables: {
|
||||
emoji: dice.emoticon,
|
||||
},
|
||||
options: {
|
||||
withNodes: true,
|
||||
},
|
||||
},
|
||||
action: {
|
||||
action: 'sendDiceInCurrentChat',
|
||||
payload: {
|
||||
emoji: dice.emoticon,
|
||||
},
|
||||
},
|
||||
actionText: {
|
||||
key: 'DiceToastSend',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
function renderSticker(
|
||||
tgsUrl: string | undefined,
|
||||
isHidden: boolean,
|
||||
shouldLoop?: boolean,
|
||||
noRenderOnHidden?: boolean,
|
||||
onEnded?: NoneToVoidFunction,
|
||||
onFrame?: (frame: number) => void,
|
||||
) {
|
||||
if (noRenderOnHidden && isHidden) return undefined;
|
||||
return (
|
||||
<div className={buildClassName(styles.sticker, isHidden && styles.hidden)}>
|
||||
<AnimatedSticker
|
||||
tgsUrl={tgsUrl}
|
||||
size={width}
|
||||
play={!isHidden}
|
||||
noLoop={!shouldLoop}
|
||||
forceAlways
|
||||
onEnded={onEnded}
|
||||
onFrame={onFrame}
|
||||
onLoad={!isHidden ? handleLoaded : undefined}
|
||||
seekToEnd={shouldSkipToEnd}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref} className={styles.root} style={`--_size: ${width}px`} onClick={handleClick}>
|
||||
{renderSticker(backgroundData, backgroundState !== 'base', false, true)}
|
||||
{renderSticker(frameWinData, backgroundState !== 'win', false, false, undefined, onWinBackgroundFrame)}
|
||||
|
||||
{renderSticker(leftSpinData, spinState !== 'base', isWaitingForResults, true)}
|
||||
{renderSticker(middleSpinData, spinState !== 'base', isWaitingForResults, true)}
|
||||
{renderSticker(rightSpinData, spinState !== 'base', isWaitingForResults, true, handleSpinEnded)}
|
||||
|
||||
{renderSticker(leftResultData, spinState !== 'result')}
|
||||
{renderSticker(middleResultData, spinState !== 'result')}
|
||||
{renderSticker(rightResultData, spinState !== 'result')}
|
||||
|
||||
{renderSticker(frameStartData, false)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal(
|
||||
(global): Complete<StateProps> => {
|
||||
const stickerSetId = global.stickers.diceSetIdByEmoji?.[SLOT_MACHINE_EMOJI];
|
||||
const slotsStickerSet = stickerSetId ? selectStickerSet(global, stickerSetId) : undefined;
|
||||
const winEffect = global.appConfig.diceEmojiesSuccess[SLOT_MACHINE_EMOJI];
|
||||
|
||||
return {
|
||||
slotsStickerSet,
|
||||
winEffect,
|
||||
};
|
||||
},
|
||||
)(SlotMachine));
|
||||
@ -58,7 +58,7 @@ export function translateWithYou<K extends LangKey>(
|
||||
export function getPinnedMediaValue(lang: LangFn, message: ApiMessage) {
|
||||
const {
|
||||
audio, contact, document, game, giveaway, giveawayResults, paidMedia, storyData,
|
||||
invoice, location, photo, pollId, sticker, video, voice,
|
||||
invoice, location, photo, pollId, sticker, video, voice, dice,
|
||||
} = getMessageContent(message);
|
||||
|
||||
if (message.groupedId || paidMedia) return lang('ActionPinnedMediaAlbum');
|
||||
@ -78,6 +78,7 @@ export function getPinnedMediaValue(lang: LangFn, message: ApiMessage) {
|
||||
if (pollId) return lang('ActionPinnedMediaPoll');
|
||||
if (giveaway) return lang('ActionPinnedMediaGiveaway');
|
||||
if (giveawayResults) return lang('ActionPinnedMediaGiveawayResults');
|
||||
if (dice) return dice.emoticon;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
41
src/components/middle/message/helpers/prepareSlotMachine.ts
Normal file
41
src/components/middle/message/helpers/prepareSlotMachine.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { ApiSticker } from '../../../../api/types';
|
||||
|
||||
const SLOT_MAP = [1, 2, 3, 0];
|
||||
export function prepareSlotMachine(stickers: ApiSticker[], value: number) {
|
||||
const isLocal = value === -1;
|
||||
|
||||
const leftSlot = (value - 1) & 0b11;
|
||||
const middleSlot = ((value - 1) >> 2) & 0b11;
|
||||
const rightSlot = ((value - 1) >> 4) & 0b11;
|
||||
|
||||
const bg = stickers[0];
|
||||
const frameWin = stickers[1];
|
||||
const frameStart = stickers[2];
|
||||
|
||||
const leftWin = stickers[3];
|
||||
const leftResult = !isLocal ? stickers[4 + SLOT_MAP[leftSlot]] : undefined;
|
||||
const leftSpin = stickers[8];
|
||||
|
||||
const middleWin = stickers[9];
|
||||
const middleResult = !isLocal ? stickers[10 + SLOT_MAP[middleSlot]] : undefined;
|
||||
const middleSpin = stickers[14];
|
||||
|
||||
const rightWin = stickers[15];
|
||||
const rightResult = !isLocal ? stickers[16 + SLOT_MAP[rightSlot]] : undefined;
|
||||
const rightSpin = stickers[20];
|
||||
|
||||
return {
|
||||
background: bg,
|
||||
frameWin,
|
||||
frameStart,
|
||||
leftWin,
|
||||
leftResult,
|
||||
leftSpin,
|
||||
middleWin,
|
||||
middleResult,
|
||||
middleSpin,
|
||||
rightWin,
|
||||
rightResult,
|
||||
rightSpin,
|
||||
};
|
||||
}
|
||||
@ -99,13 +99,17 @@ function GiftItemStar({
|
||||
|
||||
if (isUserLimitReached) {
|
||||
showNotification({
|
||||
message: lang('NotificationGiftsLimit2', {
|
||||
count: perUserTotal,
|
||||
}, {
|
||||
pluralValue: perUserTotal!,
|
||||
withMarkdown: true,
|
||||
withNodes: true,
|
||||
}),
|
||||
message: {
|
||||
key: 'NotificationGiftsLimit2',
|
||||
variables: {
|
||||
count: perUserTotal,
|
||||
},
|
||||
options: {
|
||||
pluralValue: perUserTotal!,
|
||||
withMarkdown: true,
|
||||
withNodes: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
@ -15,6 +14,7 @@ import buildClassName from '../../util/buildClassName';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
import { REM } from '../common/helpers/mediaDimensions';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
import { renderTextWithEntities } from '../common/helpers/renderTextWithEntities';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
@ -37,9 +37,9 @@ const DEFAULT_DURATION = 3000;
|
||||
const ANIMATION_DURATION = 150;
|
||||
const CUSTOM_EMOJI_SIZE = 1.75 * REM;
|
||||
|
||||
const Notification: FC<OwnProps> = ({
|
||||
const Notification = ({
|
||||
notification,
|
||||
}) => {
|
||||
}: OwnProps) => {
|
||||
const actions = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
@ -62,7 +62,10 @@ const Notification: FC<OwnProps> = ({
|
||||
containerSelector,
|
||||
} = notification;
|
||||
|
||||
const isMessageLangFnParam = isLangFnParam(message);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const actionActivationRef = useRef<boolean>(false);
|
||||
const timerRef = useRef<number | undefined>();
|
||||
const { transitionClassNames } = useShowTransitionDeprecated(isOpen);
|
||||
|
||||
@ -80,8 +83,9 @@ const Notification: FC<OwnProps> = ({
|
||||
}
|
||||
});
|
||||
|
||||
const handleActionClick = useLastCallback(() => {
|
||||
if (action) {
|
||||
const handleActionClick = useLastCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (action && !actionActivationRef.current) {
|
||||
actionActivationRef.current = true;
|
||||
if (Array.isArray(action)) {
|
||||
// @ts-ignore
|
||||
action.forEach((cb) => actions[cb.action](cb.payload));
|
||||
@ -98,7 +102,8 @@ const Notification: FC<OwnProps> = ({
|
||||
});
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
if (action) {
|
||||
if (action && !actionActivationRef.current) {
|
||||
actionActivationRef.current = true;
|
||||
if (Array.isArray(action)) {
|
||||
// @ts-ignore
|
||||
action.forEach((cb) => actions[cb.action](cb.payload));
|
||||
@ -146,19 +151,27 @@ const Notification: FC<OwnProps> = ({
|
||||
}
|
||||
|
||||
return renderText(title, ['simple_markdown', 'emoji', 'br', 'links']);
|
||||
// @ts-expect-error -- Lang Parameters are too complex
|
||||
}, [lang, title]);
|
||||
|
||||
const renderedMessage = useMemo(() => {
|
||||
if (isLangFnParam(message)) {
|
||||
if (isMessageLangFnParam) {
|
||||
return lang.with(message);
|
||||
}
|
||||
|
||||
if (typeof message === 'string') {
|
||||
if (notification.messageEntities) {
|
||||
return renderTextWithEntities({
|
||||
text: message,
|
||||
entities: notification.messageEntities,
|
||||
});
|
||||
}
|
||||
return renderText(message, ['simple_markdown', 'emoji', 'br', 'links']);
|
||||
}
|
||||
|
||||
return message;
|
||||
}, [lang, message]);
|
||||
// @ts-expect-error -- Lang Parameters are too complex
|
||||
}, [isMessageLangFnParam, lang, message, notification.messageEntities]);
|
||||
|
||||
const renderedActionText = useMemo(() => {
|
||||
if (!actionText) return undefined;
|
||||
|
||||
@ -459,3 +459,5 @@ export const DEFAULT_RESALE_GIFTS_FILTER_OPTIONS: ResaleGiftsFilterOptions = {
|
||||
};
|
||||
|
||||
export const ACCOUNT_TTL_OPTIONS = [1, 3, 6, 12, 18, 24];
|
||||
|
||||
export const SLOT_MACHINE_EMOJI = '🎰';
|
||||
|
||||
@ -434,6 +434,12 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
|
||||
});
|
||||
}
|
||||
|
||||
const diceEmojies = global.appConfig.diceEmojies;
|
||||
let dice = payload.dice;
|
||||
if (payload.text && !payload.entities?.length && diceEmojies.includes(payload.text)) {
|
||||
dice = payload.text;
|
||||
}
|
||||
|
||||
const params: SendMessageParams = {
|
||||
...payload,
|
||||
chat,
|
||||
@ -445,6 +451,8 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
|
||||
lastMessageId,
|
||||
messagePriceInStars,
|
||||
isStoryReply,
|
||||
dice,
|
||||
text: !dice ? payload.text : undefined,
|
||||
isPending: messagePriceInStars ? true : undefined,
|
||||
...suggestedMessage && { isInvertedMedia: suggestedMessage?.isInvertedMedia },
|
||||
};
|
||||
@ -610,6 +618,20 @@ addActionHandler('sendInviteMessages', async (global, actions, payload): Promise
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('sendDiceInCurrentChat', (global, actions, payload): ActionReturnType => {
|
||||
const { emoji, tabId = getCurrentTabId() } = payload;
|
||||
const messageList = selectCurrentMessageList(global, tabId);
|
||||
if (!messageList) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
actions.sendMessage({
|
||||
messageList,
|
||||
dice: emoji,
|
||||
tabId,
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('editMessage', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
messageList, text, entities, attachments, tabId = getCurrentTabId(),
|
||||
|
||||
@ -199,6 +199,34 @@ addActionHandler('loadFeaturedStickers', async (global): Promise<void> => {
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadDiceStickers', async (global): Promise<void> => {
|
||||
const emojis = global.appConfig.diceEmojies;
|
||||
const promises = emojis.map((emoji) => callApi('fetchDiceStickers', { emoji }));
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
global = getGlobal();
|
||||
results.forEach((result, index) => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
const emoji = emojis[index];
|
||||
const { set, stickers, packs } = result;
|
||||
global = updateStickerSet(global, set.id, { ...set, stickers, packs });
|
||||
global = {
|
||||
...global,
|
||||
stickers: {
|
||||
...global.stickers,
|
||||
diceSetIdByEmoji: {
|
||||
...global.stickers.diceSetIdByEmoji,
|
||||
[emoji]: set.id,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadPremiumGifts', async (global): Promise<void> => {
|
||||
const stickerSet = await callApi('fetchPremiumGifts');
|
||||
if (!stickerSet) {
|
||||
|
||||
@ -443,6 +443,7 @@ function reduceGlobal<T extends GlobalState>(global: T) {
|
||||
'availableEffectById',
|
||||
]),
|
||||
lastIsChatInfoShown: !getIsMobile() ? global.lastIsChatInfoShown : undefined,
|
||||
stickers: reduceStickers(global),
|
||||
customEmojis: reduceCustomEmojis(global),
|
||||
users: reduceUsers(global),
|
||||
chats: reduceChats(global),
|
||||
@ -486,6 +487,15 @@ export function serializeGlobal<T extends GlobalState>(global: T) {
|
||||
return JSON.stringify(reduceGlobal(global));
|
||||
}
|
||||
|
||||
function reduceStickers<T extends GlobalState>(global: T): GlobalState['stickers'] {
|
||||
const { diceSetIdByEmoji, setsById } = global.stickers;
|
||||
return {
|
||||
...INITIAL_GLOBAL_STATE.stickers,
|
||||
diceSetIdByEmoji,
|
||||
setsById: pickTruthy(setsById, Object.values(diceSetIdByEmoji || {})),
|
||||
};
|
||||
}
|
||||
|
||||
function reduceCustomEmojis<T extends GlobalState>(global: T): GlobalState['customEmojis'] {
|
||||
const { lastRendered, byId } = global.customEmojis;
|
||||
const folderEmojiIds = Object.values(global.chatFolders.byId)
|
||||
|
||||
@ -152,6 +152,7 @@ function getSummaryDescription(
|
||||
giveawayResults,
|
||||
paidMedia,
|
||||
todo,
|
||||
dice,
|
||||
} = mediaContent;
|
||||
const { poll } = statefulContent || {};
|
||||
|
||||
@ -256,6 +257,10 @@ function getSummaryDescription(
|
||||
});
|
||||
}
|
||||
|
||||
if (dice) {
|
||||
summary = dice.emoticon;
|
||||
}
|
||||
|
||||
return summary || lang('MessageUnsupported');
|
||||
}
|
||||
|
||||
|
||||
@ -63,13 +63,13 @@ export function getMessageTranscription(message: ApiMessage) {
|
||||
|
||||
export function hasMessageText(message: MediaContainer) {
|
||||
const {
|
||||
action, text, sticker, photo, video, audio, voice, document, pollId, todo,
|
||||
action, text, sticker, photo, video, audio, voice, document, pollId, todo, dice,
|
||||
webPage, contact, invoice, location, game, storyData, giveaway, giveawayResults, paidMedia,
|
||||
} = message.content;
|
||||
|
||||
return Boolean(text) || !(
|
||||
sticker || photo || video || audio || voice || document || contact || pollId || todo || webPage
|
||||
|| invoice || location || game || storyData || giveaway || giveawayResults
|
||||
|| invoice || location || game || storyData || giveaway || giveawayResults || dice
|
||||
|| paidMedia || action?.type === 'phoneCall'
|
||||
);
|
||||
}
|
||||
@ -112,10 +112,10 @@ export function getMessageCustomShape(message: ApiMessage): boolean {
|
||||
const {
|
||||
text, sticker, photo, video, audio, voice,
|
||||
document, pollId, webPage, contact, action,
|
||||
game, invoice, location, storyData,
|
||||
game, invoice, location, storyData, dice,
|
||||
} = message.content;
|
||||
|
||||
if (sticker || (video?.isRound)) {
|
||||
if (sticker || (video?.isRound) || dice) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -726,7 +726,7 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
||||
) && !(
|
||||
content.sticker || content.contact || content.pollId || content.action
|
||||
|| (content.video?.isRound) || content.location || content.invoice || content.giveaway || content.giveawayResults
|
||||
|| isDocumentSticker
|
||||
|| isDocumentSticker || content.dice
|
||||
)
|
||||
&& !isForwarded
|
||||
&& !message.viaBotId
|
||||
@ -763,7 +763,7 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
||||
const canReport = !isPrivate && !isOwn;
|
||||
|
||||
const canDeleteForAll = canDelete && !chat.isForbidden && (
|
||||
(isPrivate && !isChatWithSelf && !isBotChat)
|
||||
(isPrivate && !isChatWithSelf && !isBotChat && !content.dice)
|
||||
|| (isBasicGroup && (
|
||||
isOwn || getHasAdminRight(chat, 'deleteMessages') || chat.isCreator
|
||||
))
|
||||
|
||||
@ -250,3 +250,28 @@ export function selectGiftStickerForTon<T extends GlobalState>(global: T, amount
|
||||
export function selectCustomEmoji<T extends GlobalState>(global: T, documentId: string) {
|
||||
return global.customEmojis.byId[documentId];
|
||||
}
|
||||
|
||||
export function selectIdleDiceSticker<T extends GlobalState>(global: T, diceEmoji: string) {
|
||||
const { diceSetIdByEmoji } = global.stickers;
|
||||
if (!diceSetIdByEmoji) return undefined;
|
||||
const diceSetId = diceSetIdByEmoji[diceEmoji];
|
||||
if (!diceSetId) return undefined;
|
||||
|
||||
const diceSet = global.stickers.setsById[diceSetId];
|
||||
if (!diceSet) return undefined;
|
||||
|
||||
return diceSet.stickers?.find((sticker) => sticker.emoji === '#\u20E3');
|
||||
}
|
||||
|
||||
export function selectDiceSticker<T extends GlobalState>(global: T, diceEmoji: string, value: number) {
|
||||
const { diceSetIdByEmoji } = global.stickers;
|
||||
if (!diceSetIdByEmoji) return undefined;
|
||||
const diceSetId = diceSetIdByEmoji[diceEmoji];
|
||||
if (!diceSetId) return undefined;
|
||||
|
||||
const diceSet = global.stickers.setsById[diceSetId];
|
||||
if (!diceSet) return undefined;
|
||||
|
||||
const numberEmoji = `${value}\u20E3`;
|
||||
return diceSet.stickers?.find((sticker) => sticker.emoji === numberEmoji);
|
||||
}
|
||||
|
||||
@ -495,6 +495,9 @@ export interface ActionPayloads {
|
||||
sendMessages: {
|
||||
sendParams: SendMessageParams[];
|
||||
};
|
||||
sendDiceInCurrentChat: {
|
||||
emoji: string;
|
||||
} & WithTabId;
|
||||
sendInviteMessages: {
|
||||
chatId: string;
|
||||
userIds: string[];
|
||||
@ -2006,6 +2009,7 @@ export interface ActionPayloads {
|
||||
loadRecentStickers: undefined;
|
||||
loadFavoriteStickers: undefined;
|
||||
loadFeaturedStickers: undefined;
|
||||
loadDiceStickers: undefined;
|
||||
|
||||
reorderStickerSets: {
|
||||
isCustomEmoji?: boolean;
|
||||
|
||||
@ -366,6 +366,7 @@ export type GlobalState = {
|
||||
stickers: ApiSticker[];
|
||||
emojis: ApiSticker[];
|
||||
};
|
||||
diceSetIdByEmoji?: Record<string, string>;
|
||||
};
|
||||
|
||||
customEmojis: {
|
||||
|
||||
@ -22,6 +22,8 @@ type Frame =
|
||||
| typeof WAITING
|
||||
| ImageBitmap;
|
||||
|
||||
type FrameCallback = (index: number) => void;
|
||||
|
||||
const HIGH_PRIORITY_QUALITY = (IS_ANDROID || IS_IOS) ? 0.75 : 1;
|
||||
const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75;
|
||||
const LOW_PRIORITY_QUALITY_SIZE_THRESHOLD = 24;
|
||||
@ -47,6 +49,7 @@ class RLottie {
|
||||
isSharedCanvas?: boolean;
|
||||
coords?: Params['coords'];
|
||||
onLoad?: NoneToVoidFunction;
|
||||
onFrame?: FrameCallback;
|
||||
}>();
|
||||
|
||||
private imgSize!: number;
|
||||
@ -89,13 +92,19 @@ class RLottie {
|
||||
|
||||
private lastRenderAt?: number;
|
||||
|
||||
private requestedSeekToEnd = false;
|
||||
|
||||
static init(...args: ConstructorParameters<typeof RLottie>) {
|
||||
const [
|
||||
, canvas,
|
||||
renderId,
|
||||
params,
|
||||
viewId = generateUniqueId(), ,
|
||||
viewId = generateUniqueId(),
|
||||
,
|
||||
onLoad,
|
||||
,
|
||||
,
|
||||
onFrame,
|
||||
] = args;
|
||||
let instance = instancesByRenderId.get(renderId);
|
||||
|
||||
@ -103,7 +112,7 @@ class RLottie {
|
||||
instance = new RLottie(...args);
|
||||
instancesByRenderId.set(renderId, instance);
|
||||
} else {
|
||||
instance.addView(viewId, canvas, onLoad, params?.coords);
|
||||
instance.addView(viewId, canvas, onLoad, onFrame, params?.coords);
|
||||
}
|
||||
|
||||
return instance;
|
||||
@ -111,16 +120,17 @@ class RLottie {
|
||||
|
||||
constructor(
|
||||
private tgsUrl: string,
|
||||
private container: HTMLDivElement | HTMLCanvasElement,
|
||||
container: HTMLDivElement | HTMLCanvasElement,
|
||||
private renderId: string,
|
||||
private params: Params,
|
||||
viewId: string = generateUniqueId(),
|
||||
private customColor?: [number, number, number],
|
||||
private onLoad?: NoneToVoidFunction | undefined,
|
||||
onLoad?: NoneToVoidFunction | undefined,
|
||||
private onEnded?: (isDestroyed?: boolean) => void,
|
||||
private onLoop?: () => void,
|
||||
onFrame?: FrameCallback,
|
||||
) {
|
||||
this.addView(viewId, container, onLoad, params.coords);
|
||||
this.addView(viewId, container, onLoad, onFrame, params.coords);
|
||||
this.initConfig();
|
||||
this.initRenderer();
|
||||
}
|
||||
@ -209,6 +219,11 @@ class RLottie {
|
||||
this.doPlay();
|
||||
}
|
||||
|
||||
seekToEnd() {
|
||||
this.requestedSeekToEnd = true;
|
||||
this.doPlay();
|
||||
}
|
||||
|
||||
setSpeed(speed: number) {
|
||||
this.speed = speed;
|
||||
}
|
||||
@ -257,6 +272,7 @@ class RLottie {
|
||||
viewId: string,
|
||||
container: HTMLDivElement | HTMLCanvasElement,
|
||||
onLoad?: NoneToVoidFunction,
|
||||
onFrame?: FrameCallback,
|
||||
coords?: Params['coords'],
|
||||
) {
|
||||
const sizeFactor = this.calcSizeFactor();
|
||||
@ -292,7 +308,7 @@ class RLottie {
|
||||
container.appendChild(canvas);
|
||||
|
||||
this.views.set(viewId, {
|
||||
canvas, ctx, onLoad,
|
||||
canvas, ctx, onLoad, onFrame,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@ -442,6 +458,12 @@ class RLottie {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.requestedSeekToEnd) {
|
||||
this.approxFrameIndex = this.framesCount - 1;
|
||||
this.stopFrameIndex = undefined;
|
||||
this.requestedSeekToEnd = false;
|
||||
}
|
||||
|
||||
if (this.isAnimating) {
|
||||
return;
|
||||
}
|
||||
@ -486,12 +508,13 @@ class RLottie {
|
||||
if (frameIndex !== this.prevFrameIndex) {
|
||||
this.views.forEach((containerData) => {
|
||||
const {
|
||||
ctx, isLoaded, isPaused, coords: { x, y } = {}, onLoad,
|
||||
ctx, isLoaded, isPaused, coords: { x, y } = {}, onLoad, onFrame,
|
||||
} = containerData;
|
||||
|
||||
if (!isLoaded || !isPaused) {
|
||||
ctx.clearRect(x || 0, y || 0, this.imgSize, this.imgSize);
|
||||
ctx.drawImage(frame, x || 0, y || 0);
|
||||
onFrame?.(frameIndex);
|
||||
}
|
||||
|
||||
if (!isLoaded) {
|
||||
|
||||
@ -160,4 +160,6 @@ export const DEFAULT_APP_CONFIG: ApiAppConfig = {
|
||||
typingDraftTtl: 10,
|
||||
arePasskeysAvailable: true,
|
||||
passkeysMaxCount: 5,
|
||||
diceEmojies: [],
|
||||
diceEmojiesSuccess: {},
|
||||
};
|
||||
|
||||
@ -723,6 +723,7 @@ export type SendMessageParams = {
|
||||
gif?: ApiVideo;
|
||||
poll?: ApiNewPoll;
|
||||
todo?: ApiNewMediaTodo;
|
||||
dice?: string;
|
||||
contact?: ApiContact;
|
||||
isSilent?: boolean;
|
||||
scheduledAt?: number;
|
||||
|
||||
4
src/types/language.d.ts
vendored
4
src/types/language.d.ts
vendored
@ -1885,6 +1885,7 @@ export interface LangPair {
|
||||
'SettingsBirthday': undefined;
|
||||
'BotReadTextFromClipboardTitle': undefined;
|
||||
'BotReadTextFromClipboardConfirm': undefined;
|
||||
'DiceToastSend': undefined;
|
||||
'ChatTypePrivate': undefined;
|
||||
'ChatTypeGroup': undefined;
|
||||
'ChatTypeChannel': undefined;
|
||||
@ -3329,6 +3330,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'BotReadTextFromClipboardDescription': {
|
||||
'bot': V;
|
||||
};
|
||||
'DiceToast': {
|
||||
'emoji': V;
|
||||
};
|
||||
'GroupStatusWithOnline': {
|
||||
'status': V;
|
||||
'onlineCount': V;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user