Mini Apps: Allow suggesting user emoji status (#5236)
This commit is contained in:
parent
ba9956f921
commit
0933603cb0
@ -712,20 +712,20 @@ export function buildInputChatReactions(chatReactions?: ApiChatReactions) {
|
|||||||
return new GramJs.ChatReactionsNone();
|
return new GramJs.ChatReactionsNone();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildInputEmojiStatus(emojiStatus: ApiSticker, expires?: number) {
|
export function buildInputEmojiStatus(emojiStatusId: string, expires?: number) {
|
||||||
if (emojiStatus.id === DEFAULT_STATUS_ICON_ID) {
|
if (emojiStatusId === DEFAULT_STATUS_ICON_ID) {
|
||||||
return new GramJs.EmojiStatusEmpty();
|
return new GramJs.EmojiStatusEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expires) {
|
if (expires) {
|
||||||
return new GramJs.EmojiStatusUntil({
|
return new GramJs.EmojiStatusUntil({
|
||||||
documentId: BigInt(emojiStatus.id),
|
documentId: BigInt(emojiStatusId),
|
||||||
until: expires,
|
until: expires,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GramJs.EmojiStatus({
|
return new GramJs.EmojiStatus({
|
||||||
documentId: BigInt(emojiStatus.id),
|
documentId: BigInt(emojiStatusId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import BigInt from 'big-integer';
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiPeer, ApiSticker, ApiUser,
|
ApiChat, ApiPeer, ApiUser,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
@ -304,9 +304,9 @@ export function reportSpam(userOrChat: ApiPeer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateEmojiStatus(emojiStatus: ApiSticker, expires?: number) {
|
export function updateEmojiStatus(emojiStatusId: string, expires?: number) {
|
||||||
return invokeRequest(new GramJs.account.UpdateEmojiStatus({
|
return invokeRequest(new GramJs.account.UpdateEmojiStatus({
|
||||||
emojiStatus: buildInputEmojiStatus(emojiStatus, expires),
|
emojiStatus: buildInputEmojiStatus(emojiStatusId, expires),
|
||||||
}), {
|
}), {
|
||||||
shouldReturnTrue: true,
|
shouldReturnTrue: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -125,6 +125,7 @@ export type ApiNotification = {
|
|||||||
disableClickDismiss?: boolean;
|
disableClickDismiss?: boolean;
|
||||||
shouldShowTimer?: boolean;
|
shouldShowTimer?: boolean;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
|
customEmojiIconId?: string;
|
||||||
dismissAction?: CallbackAction;
|
dismissAction?: CallbackAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1111,6 +1111,7 @@
|
|||||||
"LiveLocationUpdatedMinutesAgo_one" = "updated 1 minute ago";
|
"LiveLocationUpdatedMinutesAgo_one" = "updated 1 minute ago";
|
||||||
"LiveLocationUpdatedMinutesAgo_other" = "updated {count} minutes ago";
|
"LiveLocationUpdatedMinutesAgo_other" = "updated {count} minutes ago";
|
||||||
"LiveLocationUpdatedTodayAt" = "updated at {time}";
|
"LiveLocationUpdatedTodayAt" = "updated at {time}";
|
||||||
|
"RightNow" = "Just now";
|
||||||
"Seconds_one" = "{count} second";
|
"Seconds_one" = "{count} second";
|
||||||
"Seconds_other" = "{count} seconds";
|
"Seconds_other" = "{count} seconds";
|
||||||
"Minutes_one" = "{count} minute";
|
"Minutes_one" = "{count} minute";
|
||||||
@ -1383,3 +1384,7 @@
|
|||||||
"CloseMiniApps" = "Close Mini Apps";
|
"CloseMiniApps" = "Close Mini Apps";
|
||||||
"DoNotAskAgain" = "Don't ask again";
|
"DoNotAskAgain" = "Don't ask again";
|
||||||
"PaymentInfoDone" = "Proceed to checkout";
|
"PaymentInfoDone" = "Proceed to checkout";
|
||||||
|
"BotSuggestedStatusFor" = "Do you want to set this emoji status suggested by **{bot}** for **{duration}**?";
|
||||||
|
"BotSuggestedStatus" = "Do you want to set this emoji status suggested by **{bot}**?";
|
||||||
|
"BotSuggestedStatusTitle" = "Set Emoji Status";
|
||||||
|
"BotSuggestedStatusUpdated" = "Your emoji status is updated.";
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export { default as PremiumMainModal } from '../components/main/premium/PremiumM
|
|||||||
export { default as GiveawayModal } from '../components/main/premium/GiveawayModal';
|
export { default as GiveawayModal } from '../components/main/premium/GiveawayModal';
|
||||||
export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal';
|
export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal';
|
||||||
export { default as StatusPickerMenu } from '../components/left/main/StatusPickerMenu';
|
export { default as StatusPickerMenu } from '../components/left/main/StatusPickerMenu';
|
||||||
|
export { default as SuggestedStatusModal } from '../components/modals/suggestedStatus/SuggestedStatusModal';
|
||||||
export { default as BoostModal } from '../components/modals/boost/BoostModal';
|
export { default as BoostModal } from '../components/modals/boost/BoostModal';
|
||||||
export { default as GiftCodeModal } from '../components/modals/giftcode/GiftCodeModal';
|
export { default as GiftCodeModal } from '../components/modals/giftcode/GiftCodeModal';
|
||||||
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
|
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import type { ApiBotInlineMediaResult, ApiSticker } from '../../api/types';
|
|||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { getServerTimeOffset } from '../../util/serverTime';
|
import { getServerTime } from '../../util/serverTime';
|
||||||
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
||||||
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
|
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
|
||||||
|
|
||||||
@ -198,8 +198,8 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
|||||||
handleContextMenuClose();
|
handleContextMenuClose();
|
||||||
onContextMenuClick?.();
|
onContextMenuClick?.();
|
||||||
setEmojiStatus({
|
setEmojiStatus({
|
||||||
emojiStatus: sticker,
|
emojiStatusId: sticker.id,
|
||||||
expires: Date.now() / 1000 + duration + getServerTimeOffset(),
|
expires: getServerTime() + duration,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,19 +2,19 @@ import type { TeactNode } from '../../../lib/teact/teact';
|
|||||||
import React, { memo } from '../../../lib/teact/teact';
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../global';
|
import { withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiUser } from '../../../api/types';
|
import type { ApiPeer } from '../../../api/types';
|
||||||
import type { CustomPeer } from '../../../types';
|
import type { CustomPeer } from '../../../types';
|
||||||
import type { IconName } from '../../../types/icons';
|
import type { IconName } from '../../../types/icons';
|
||||||
|
|
||||||
import { getChatTitle, getUserFirstOrLastName } from '../../../global/helpers';
|
import { isApiPeerChat } from '../../../global/helpers/peers';
|
||||||
import { selectChat, selectUser } from '../../../global/selectors';
|
import { selectPeer, selectUser } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { getPeerColorClass } from '../helpers/peerColor';
|
import { getPeerColorClass } from '../helpers/peerColor';
|
||||||
import renderText from '../helpers/renderText';
|
|
||||||
|
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
|
import FullNameTitle from '../FullNameTitle';
|
||||||
import Icon from '../icons/Icon';
|
import Icon from '../icons/Icon';
|
||||||
|
|
||||||
import './PickerSelectedItem.scss';
|
import './PickerSelectedItem.scss';
|
||||||
@ -25,6 +25,7 @@ type OwnProps<T = undefined> = {
|
|||||||
// eslint-disable-next-line react/no-unused-prop-types
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
customPeer?: CustomPeer;
|
customPeer?: CustomPeer;
|
||||||
|
mockPeer?: ApiPeer;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
title?: string;
|
title?: string;
|
||||||
isMinimized?: boolean;
|
isMinimized?: boolean;
|
||||||
@ -32,13 +33,12 @@ type OwnProps<T = undefined> = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
fluid?: boolean;
|
fluid?: boolean;
|
||||||
withPeerColors?: boolean;
|
withPeerColors?: boolean;
|
||||||
clickArg: T;
|
clickArg?: T;
|
||||||
onClick: (arg: T) => void;
|
onClick?: (arg: T) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
peer?: ApiPeer;
|
||||||
user?: ApiUser;
|
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,8 +49,8 @@ const PickerSelectedItem = <T,>({
|
|||||||
isMinimized,
|
isMinimized,
|
||||||
canClose,
|
canClose,
|
||||||
clickArg,
|
clickArg,
|
||||||
chat,
|
peer,
|
||||||
user,
|
mockPeer,
|
||||||
customPeer,
|
customPeer,
|
||||||
className,
|
className,
|
||||||
fluid,
|
fluid,
|
||||||
@ -60,6 +60,11 @@ const PickerSelectedItem = <T,>({
|
|||||||
}: OwnProps<T> & StateProps) => {
|
}: OwnProps<T> & StateProps) => {
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
|
const apiPeer = mockPeer || peer;
|
||||||
|
const anyPeer = customPeer || apiPeer;
|
||||||
|
|
||||||
|
const chat = apiPeer && isApiPeerChat(apiPeer) ? apiPeer : undefined;
|
||||||
|
|
||||||
let iconElement: TeactNode | undefined;
|
let iconElement: TeactNode | undefined;
|
||||||
let titleText: any;
|
let titleText: any;
|
||||||
|
|
||||||
@ -71,21 +76,16 @@ const PickerSelectedItem = <T,>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
titleText = title;
|
titleText = title;
|
||||||
} else if (customPeer || user || chat) {
|
} else if (anyPeer) {
|
||||||
iconElement = (
|
iconElement = (
|
||||||
<Avatar
|
<Avatar
|
||||||
peer={customPeer || user || chat}
|
peer={anyPeer}
|
||||||
size="small"
|
size="small"
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const name = (customPeer && (customPeer.title || lang(customPeer.titleKey!)))
|
titleText = title || <FullNameTitle peer={anyPeer} isSavedMessages={isSavedMessages} withEmojiStatus />;
|
||||||
|| (!chat || (user && !isSavedMessages)
|
|
||||||
? getUserFirstOrLastName(user)
|
|
||||||
: getChatTitle(lang, chat, isSavedMessages));
|
|
||||||
|
|
||||||
titleText = title || (name ? renderText(name) : undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
@ -95,13 +95,13 @@ const PickerSelectedItem = <T,>({
|
|||||||
isMinimized && 'minimized',
|
isMinimized && 'minimized',
|
||||||
canClose && 'closeable',
|
canClose && 'closeable',
|
||||||
fluid && 'fluid',
|
fluid && 'fluid',
|
||||||
withPeerColors && getPeerColorClass(customPeer || chat || user),
|
withPeerColors && getPeerColorClass(customPeer || peer),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={fullClassName}
|
className={fullClassName}
|
||||||
onClick={() => onClick(clickArg)}
|
onClick={() => onClick?.(clickArg!)}
|
||||||
title={isMinimized ? titleText : undefined}
|
title={isMinimized ? titleText : undefined}
|
||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
>
|
>
|
||||||
@ -126,13 +126,12 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const chat = selectChat(global, peerId);
|
const peer = selectPeer(global, peerId);
|
||||||
const user = selectUser(global, peerId);
|
const user = selectUser(global, peerId);
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
peer,
|
||||||
user,
|
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type { ApiEmojiStatus, ApiSticker } from '../../../api/types';
|
|||||||
|
|
||||||
import { EMOJI_STATUS_LOOP_LIMIT } from '../../../config';
|
import { EMOJI_STATUS_LOOP_LIMIT } from '../../../config';
|
||||||
import { selectUser } from '../../../global/selectors';
|
import { selectUser } from '../../../global/selectors';
|
||||||
import { getServerTimeOffset } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
|
||||||
import useTimeout from '../../../hooks/schedulers/useTimeout';
|
import useTimeout from '../../../hooks/schedulers/useTimeout';
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
@ -36,7 +36,7 @@ const StatusButton: FC<StateProps> = ({ emojiStatus }) => {
|
|||||||
const [isStatusPickerOpen, openStatusPicker, closeStatusPicker] = useFlag(false);
|
const [isStatusPickerOpen, openStatusPicker, closeStatusPicker] = useFlag(false);
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
|
|
||||||
const delay = emojiStatus?.until ? emojiStatus.until * 1000 - Date.now() + getServerTimeOffset() * 1000 : undefined;
|
const delay = emojiStatus?.until ? (emojiStatus.until - getServerTime()) * 1000 : undefined;
|
||||||
useTimeout(loadCurrentUser, delay);
|
useTimeout(loadCurrentUser, delay);
|
||||||
|
|
||||||
useEffectWithPrevDeps(([prevEmojiStatus]) => {
|
useEffectWithPrevDeps(([prevEmojiStatus]) => {
|
||||||
@ -48,7 +48,7 @@ const StatusButton: FC<StateProps> = ({ emojiStatus }) => {
|
|||||||
|
|
||||||
const handleEmojiStatusSet = useCallback((sticker: ApiSticker) => {
|
const handleEmojiStatusSet = useCallback((sticker: ApiSticker) => {
|
||||||
markShouldShowEffect();
|
markShouldShowEffect();
|
||||||
setEmojiStatus({ emojiStatus: sticker });
|
setEmojiStatus({ emojiStatusId: sticker.id });
|
||||||
}, [markShouldShowEffect, setEmojiStatus]);
|
}, [markShouldShowEffect, setEmojiStatus]);
|
||||||
|
|
||||||
useTimeout(hideEffect, isEffectShown ? EFFECT_DURATION_MS : undefined);
|
useTimeout(hideEffect, isEffectShown ? EFFECT_DURATION_MS : undefined);
|
||||||
|
|||||||
@ -138,6 +138,7 @@
|
|||||||
|
|
||||||
&.has-inline-buttons {
|
&.has-inline-buttons {
|
||||||
.message-content {
|
.message-content {
|
||||||
|
--border-bottom-left-radius: var(--border-radius-messages-small);
|
||||||
--border-bottom-right-radius: var(--border-radius-messages-small);
|
--border-bottom-right-radius: var(--border-radius-messages-small);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import StarsBalanceModal from './stars/StarsBalanceModal.async';
|
|||||||
import StarsPaymentModal from './stars/StarsPaymentModal.async';
|
import StarsPaymentModal from './stars/StarsPaymentModal.async';
|
||||||
import StarsSubscriptionModal from './stars/subscription/StarsSubscriptionModal.async';
|
import StarsSubscriptionModal from './stars/subscription/StarsSubscriptionModal.async';
|
||||||
import StarsTransactionInfoModal from './stars/transaction/StarsTransactionModal.async';
|
import StarsTransactionInfoModal from './stars/transaction/StarsTransactionModal.async';
|
||||||
|
import SuggestedStatusModal from './suggestedStatus/SuggestedStatusModal.async';
|
||||||
import UrlAuthModal from './urlAuth/UrlAuthModal.async';
|
import UrlAuthModal from './urlAuth/UrlAuthModal.async';
|
||||||
import WebAppModal from './webApp/WebAppModal.async';
|
import WebAppModal from './webApp/WebAppModal.async';
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'isGiftRecipientPickerOpen' |
|
'isGiftRecipientPickerOpen' |
|
||||||
'isWebAppsCloseConfirmationModalOpen' |
|
'isWebAppsCloseConfirmationModalOpen' |
|
||||||
'giftInfoModal' |
|
'giftInfoModal' |
|
||||||
|
'suggestedStatusModal' |
|
||||||
'aboutAdsModal'
|
'aboutAdsModal'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@ -96,6 +98,7 @@ const MODALS: ModalRegistry = {
|
|||||||
isGiftRecipientPickerOpen: GiftRecipientPicker,
|
isGiftRecipientPickerOpen: GiftRecipientPicker,
|
||||||
isWebAppsCloseConfirmationModalOpen: WebAppsCloseConfirmationModal,
|
isWebAppsCloseConfirmationModalOpen: WebAppsCloseConfirmationModal,
|
||||||
giftInfoModal: GiftInfoModal,
|
giftInfoModal: GiftInfoModal,
|
||||||
|
suggestedStatusModal: SuggestedStatusModal,
|
||||||
aboutAdsModal: AboutAdsModal,
|
aboutAdsModal: AboutAdsModal,
|
||||||
};
|
};
|
||||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||||
|
|||||||
@ -7,12 +7,13 @@ import type { TabState } from '../../../global/types';
|
|||||||
import { getChatTitle, isChatAdmin, isChatChannel } from '../../../global/helpers';
|
import { getChatTitle, isChatAdmin, isChatChannel } from '../../../global/helpers';
|
||||||
import { selectChat, selectChatFullInfo, selectIsCurrentUserPremium } from '../../../global/selectors';
|
import { selectChat, selectChatFullInfo, selectIsCurrentUserPremium } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatDateInFuture } from '../../../util/dates/dateFormat';
|
import { formatShortDuration } from '../../../util/dates/dateFormat';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
|
import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
@ -79,7 +80,8 @@ const BoostModal = ({
|
|||||||
|
|
||||||
const isOpen = Boolean(modal);
|
const isOpen = Boolean(modal);
|
||||||
|
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chat && !chatFullInfo) {
|
if (chat && !chatFullInfo) {
|
||||||
@ -92,16 +94,16 @@ const BoostModal = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getChatTitle(lang, chat);
|
return getChatTitle(oldLang, chat);
|
||||||
}, [chat, lang]);
|
}, [chat, oldLang]);
|
||||||
|
|
||||||
const boostedChatTitle = useMemo(() => {
|
const boostedChatTitle = useMemo(() => {
|
||||||
if (!prevBoostedChat) {
|
if (!prevBoostedChat) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getChatTitle(lang, prevBoostedChat);
|
return getChatTitle(oldLang, prevBoostedChat);
|
||||||
}, [prevBoostedChat, lang]);
|
}, [prevBoostedChat, oldLang]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isStatusLoaded,
|
isStatusLoaded,
|
||||||
@ -118,7 +120,7 @@ const BoostModal = ({
|
|||||||
if (!modal?.boostStatus || !chat) {
|
if (!modal?.boostStatus || !chat) {
|
||||||
return {
|
return {
|
||||||
isStatusLoaded: false,
|
isStatusLoaded: false,
|
||||||
title: lang('Loading'),
|
title: oldLang('Loading'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,23 +142,23 @@ const BoostModal = ({
|
|||||||
|
|
||||||
const hasBoost = hasMyBoost;
|
const hasBoost = hasMyBoost;
|
||||||
|
|
||||||
const left = lang('BoostsLevel', currentLevel);
|
const left = oldLang('BoostsLevel', currentLevel);
|
||||||
const right = hasNextLevel ? lang('BoostsLevel', currentLevel + 1) : undefined;
|
const right = hasNextLevel ? oldLang('BoostsLevel', currentLevel + 1) : undefined;
|
||||||
|
|
||||||
const moreBoosts = lang('ChannelBoost.MoreBoosts', remainingBoosts);
|
const moreBoosts = oldLang('ChannelBoost.MoreBoosts', remainingBoosts);
|
||||||
|
|
||||||
const modalTitle = isChannel ? lang('BoostChannel') : lang('BoostGroup');
|
const modalTitle = isChannel ? oldLang('BoostChannel') : oldLang('BoostGroup');
|
||||||
|
|
||||||
const boostsLeftToUnrestrict = (chatFullInfo?.boostsToUnrestrict || 0) - (chatFullInfo?.boostsApplied || 0);
|
const boostsLeftToUnrestrict = (chatFullInfo?.boostsToUnrestrict || 0) - (chatFullInfo?.boostsApplied || 0);
|
||||||
|
|
||||||
let description: string | undefined;
|
let description: string | undefined;
|
||||||
if (isMaxLevel) {
|
if (isMaxLevel) {
|
||||||
description = lang('BoostsMaxLevelReached');
|
description = oldLang('BoostsMaxLevelReached');
|
||||||
} else if (boostsLeftToUnrestrict > 0 && !isChatAdmin(chat)) {
|
} else if (boostsLeftToUnrestrict > 0 && !isChatAdmin(chat)) {
|
||||||
const boostTimes = lang('GroupBoost.BoostToUnrestrict.Times', boostsLeftToUnrestrict);
|
const boostTimes = oldLang('GroupBoost.BoostToUnrestrict.Times', boostsLeftToUnrestrict);
|
||||||
description = lang('GroupBoost.BoostToUnrestrict', [boostTimes, chatTitle]);
|
description = oldLang('GroupBoost.BoostToUnrestrict', [boostTimes, chatTitle]);
|
||||||
} else {
|
} else {
|
||||||
description = lang('ChannelBoost.MoreBoostsNeeded.Text', [chatTitle, moreBoosts]);
|
description = oldLang('ChannelBoost.MoreBoostsNeeded.Text', [chatTitle, moreBoosts]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -172,7 +174,7 @@ const BoostModal = ({
|
|||||||
isBoosted: hasBoost,
|
isBoosted: hasBoost,
|
||||||
canBoostMore: areBoostsInDifferentChannels && !isMaxLevel,
|
canBoostMore: areBoostsInDifferentChannels && !isMaxLevel,
|
||||||
};
|
};
|
||||||
}, [chat, chatTitle, modal, lang, chatFullInfo, isChannel]);
|
}, [chat, chatTitle, modal, oldLang, chatFullInfo, isChannel]);
|
||||||
|
|
||||||
const isBoostDisabled = !modal?.myBoosts?.length && isCurrentUserPremium;
|
const isBoostDisabled = !modal?.myBoosts?.length && isCurrentUserPremium;
|
||||||
const isReplacingBoost = boost?.chatId && boost.chatId !== modal?.chatId;
|
const isReplacingBoost = boost?.chatId && boost.chatId !== modal?.chatId;
|
||||||
@ -238,7 +240,7 @@ const BoostModal = ({
|
|||||||
/>
|
/>
|
||||||
{isBoosted && (
|
{isBoosted && (
|
||||||
<div className={buildClassName(styles.description, styles.bold)}>
|
<div className={buildClassName(styles.description, styles.bold)}>
|
||||||
{lang('ChannelBoost.YouBoostedChannelText', chatTitle)}
|
{oldLang('ChannelBoost.YouBoostedChannelText', chatTitle)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={styles.description}>
|
<div className={styles.description}>
|
||||||
@ -249,12 +251,12 @@ const BoostModal = ({
|
|||||||
{canBoostMore ? (
|
{canBoostMore ? (
|
||||||
<>
|
<>
|
||||||
<Icon name="boost" />
|
<Icon name="boost" />
|
||||||
{lang(isChannel ? 'ChannelBoost.BoostChannel' : 'GroupBoost.BoostGroup')}
|
{oldLang(isChannel ? 'ChannelBoost.BoostChannel' : 'GroupBoost.BoostGroup')}
|
||||||
</>
|
</>
|
||||||
) : lang('OK')}
|
) : oldLang('OK')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button isText className="confirm-dialog-button" onClick={handleCloseClick}>
|
<Button isText className="confirm-dialog-button" onClick={handleCloseClick}>
|
||||||
{lang('Cancel')}
|
{oldLang('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -286,14 +288,16 @@ const BoostModal = ({
|
|||||||
<Avatar peer={chat} size="large" />
|
<Avatar peer={chat} size="large" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{renderText(lang('ChannelBoost.ReplaceBoost', [boostedChatTitle, chatTitle]), ['simple_markdown', 'emoji'])}
|
{renderText(
|
||||||
|
oldLang('ChannelBoost.ReplaceBoost', [boostedChatTitle, chatTitle]), ['simple_markdown', 'emoji'],
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="dialog-buttons">
|
<div className="dialog-buttons">
|
||||||
<Button isText className="confirm-dialog-button" onClick={handleApplyBoost}>
|
<Button isText className="confirm-dialog-button" onClick={handleApplyBoost}>
|
||||||
{lang('Replace')}
|
{oldLang('Replace')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button isText className="confirm-dialog-button" onClick={closeReplaceModal}>
|
<Button isText className="confirm-dialog-button" onClick={closeReplaceModal}>
|
||||||
{lang('Cancel')}
|
{oldLang('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -302,15 +306,15 @@ const BoostModal = ({
|
|||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={isWaitDialogOpen}
|
isOpen={isWaitDialogOpen}
|
||||||
isOnlyConfirm
|
isOnlyConfirm
|
||||||
confirmLabel={lang('OK')}
|
confirmLabel={oldLang('OK')}
|
||||||
title={lang('ChannelBoost.Error.BoostTooOftenTitle')}
|
title={oldLang('ChannelBoost.Error.BoostTooOftenTitle')}
|
||||||
onClose={closeWaitDialog}
|
onClose={closeWaitDialog}
|
||||||
confirmHandler={closeWaitDialog}
|
confirmHandler={closeWaitDialog}
|
||||||
>
|
>
|
||||||
{renderText(
|
{renderText(
|
||||||
lang(
|
oldLang(
|
||||||
'ChannelBoost.Error.BoostTooOftenText',
|
'ChannelBoost.Error.BoostTooOftenText',
|
||||||
formatDateInFuture(lang, getServerTime(), boost!.cooldownUntil),
|
formatShortDuration(lang, boost!.cooldownUntil - getServerTime()),
|
||||||
),
|
),
|
||||||
['simple_markdown', 'emoji'],
|
['simple_markdown', 'emoji'],
|
||||||
)}
|
)}
|
||||||
@ -319,12 +323,12 @@ const BoostModal = ({
|
|||||||
{!isCurrentUserPremium && (
|
{!isCurrentUserPremium && (
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={isPremiumDialogOpen}
|
isOpen={isPremiumDialogOpen}
|
||||||
confirmLabel={lang('Common.Yes')}
|
confirmLabel={oldLang('Common.Yes')}
|
||||||
title={lang('PremiumNeeded')}
|
title={oldLang('PremiumNeeded')}
|
||||||
onClose={closePremiumDialog}
|
onClose={closePremiumDialog}
|
||||||
confirmHandler={handleProceedPremium}
|
confirmHandler={handleProceedPremium}
|
||||||
>
|
>
|
||||||
{renderText(lang('PremiumNeededForBoosting'), ['simple_markdown', 'emoji'])}
|
{renderText(oldLang('PremiumNeededForBoosting'), ['simple_markdown', 'emoji'])}
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import buildClassName from '../../../../util/buildClassName';
|
|||||||
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||||
import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format';
|
import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format';
|
||||||
import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer';
|
import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer';
|
||||||
|
import { getServerTime } from '../../../../util/serverTime';
|
||||||
import { formatInteger } from '../../../../util/textFormat';
|
import { formatInteger } from '../../../../util/textFormat';
|
||||||
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ const GiftInfoModal = ({
|
|||||||
const canUpdate = Boolean(userGift?.fromId && userGift.messageId);
|
const canUpdate = Boolean(userGift?.fromId && userGift.messageId);
|
||||||
const isSender = userGift?.fromId === currentUserId;
|
const isSender = userGift?.fromId === currentUserId;
|
||||||
const canConvertDifference = (userGift && starGiftMaxConvertPeriod && (
|
const canConvertDifference = (userGift && starGiftMaxConvertPeriod && (
|
||||||
userGift.date + starGiftMaxConvertPeriod - Date.now() / 1000
|
userGift.date + starGiftMaxConvertPeriod - getServerTime()
|
||||||
)) || 0;
|
)) || 0;
|
||||||
const conversionLeft = Math.ceil(canConvertDifference / 60 / 60 / 24);
|
const conversionLeft = Math.ceil(canConvertDifference / 60 / 60 / 24);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps } from './SuggestedStatusModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const SuggestedStatusModalAsync: FC<OwnProps> = (props) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const SuggestedStatusModal = useModuleLoader(Bundles.Extra, 'SuggestedStatusModal', !modal);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return SuggestedStatusModal ? <SuggestedStatusModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SuggestedStatusModalAsync;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topEmoji {
|
||||||
|
--custom-emoji-size: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
145
src/components/modals/suggestedStatus/SuggestedStatusModal.tsx
Normal file
145
src/components/modals/suggestedStatus/SuggestedStatusModal.tsx
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiUser } from '../../../api/types';
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
|
import { getUserFullName } from '../../../global/helpers';
|
||||||
|
import { selectUser } from '../../../global/selectors';
|
||||||
|
import { formatShortDuration } from '../../../util/dates/dateFormat';
|
||||||
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import CustomEmoji from '../../common/CustomEmoji';
|
||||||
|
import PickerSelectedItem from '../../common/pickers/PickerSelectedItem';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
|
||||||
|
import styles from './SuggestedStatusModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
modal: TabState['suggestedStatusModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
bot?: ApiUser;
|
||||||
|
currentUser?: ApiUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CUSTOM_EMOJI_SIZE = 6 * REM;
|
||||||
|
|
||||||
|
const SuggestedStatusModal = ({ modal, currentUser, bot }: OwnProps & StateProps) => {
|
||||||
|
const { setEmojiStatus, closeSuggestedStatusModal, sendWebAppEvent } = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const isOpen = Boolean(modal);
|
||||||
|
const renderingModal = useCurrentOrPrev(modal);
|
||||||
|
|
||||||
|
const mockPeerWithStatus = useMemo(() => {
|
||||||
|
if (!currentUser || !renderingModal) return undefined;
|
||||||
|
return {
|
||||||
|
...currentUser,
|
||||||
|
emojiStatus: {
|
||||||
|
documentId: renderingModal.customEmojiId,
|
||||||
|
},
|
||||||
|
} satisfies ApiUser;
|
||||||
|
}, [currentUser, renderingModal]);
|
||||||
|
|
||||||
|
const description = useMemo(() => {
|
||||||
|
if (!renderingModal || !bot) return undefined;
|
||||||
|
|
||||||
|
const botName = getUserFullName(bot);
|
||||||
|
|
||||||
|
if (renderingModal.duration) {
|
||||||
|
return lang('BotSuggestedStatusFor', {
|
||||||
|
bot: botName,
|
||||||
|
duration: formatShortDuration(lang, renderingModal.duration),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return lang('BotSuggestedStatus', { bot: botName }, { withNodes: true, withMarkdown: true });
|
||||||
|
}, [bot, lang, renderingModal]);
|
||||||
|
|
||||||
|
const handleClose = useLastCallback(() => {
|
||||||
|
const webAppKey = renderingModal?.webAppKey;
|
||||||
|
|
||||||
|
if (webAppKey) {
|
||||||
|
sendWebAppEvent({
|
||||||
|
webAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'emoji_status_failed',
|
||||||
|
eventData: {
|
||||||
|
error: 'USER_DECLINED',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSuggestedStatusModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSetStatus = useLastCallback(() => {
|
||||||
|
if (!renderingModal) return;
|
||||||
|
|
||||||
|
const expires = renderingModal.duration ? getServerTime() + renderingModal.duration : undefined;
|
||||||
|
|
||||||
|
setEmojiStatus({
|
||||||
|
referrerWebAppKey: renderingModal.webAppKey,
|
||||||
|
emojiStatusId: renderingModal.customEmojiId,
|
||||||
|
expires,
|
||||||
|
});
|
||||||
|
closeSuggestedStatusModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
contentClassName={styles.content}
|
||||||
|
hasAbsoluteCloseButton
|
||||||
|
isSlim
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
{renderingModal && (
|
||||||
|
<CustomEmoji
|
||||||
|
className={styles.topEmoji}
|
||||||
|
documentId={renderingModal.customEmojiId}
|
||||||
|
size={CUSTOM_EMOJI_SIZE}
|
||||||
|
loopLimit={1}
|
||||||
|
forceAlways
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<h3 className={styles.title}>{lang('BotSuggestedStatusTitle')}</h3>
|
||||||
|
<p className={styles.description}>{description}</p>
|
||||||
|
</div>
|
||||||
|
{mockPeerWithStatus && (
|
||||||
|
<PickerSelectedItem
|
||||||
|
mockPeer={mockPeerWithStatus}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button size="smaller" onClick={handleSetStatus}>
|
||||||
|
{lang('GeneralConfirm')}
|
||||||
|
</Button>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { modal }): StateProps => {
|
||||||
|
const currentUser = selectUser(global, global.currentUserId!);
|
||||||
|
const bot = modal?.botId ? selectUser(global, modal.botId) : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentUser,
|
||||||
|
bot,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(SuggestedStatusModal));
|
||||||
@ -248,9 +248,10 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleToggleClick = useLastCallback(() => {
|
const handleToggleClick = useLastCallback(() => {
|
||||||
if (attachBot) {
|
if (attachBot) {
|
||||||
|
const key = getWebAppKey(activeWebApp!);
|
||||||
updateWebApp({
|
updateWebApp({
|
||||||
webApp: {
|
key,
|
||||||
...activeWebApp!,
|
update: {
|
||||||
isRemoveModalOpen: true,
|
isRemoveModalOpen: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -140,13 +140,8 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
const isActive = (activeWebApp && webApp) && activeWebAppKey === webAppKey;
|
const isActive = (activeWebApp && webApp) && activeWebAppKey === webAppKey;
|
||||||
|
|
||||||
const updateCurrentWebApp = useLastCallback((updatedPartialWebApp: Partial<WebApp>) => {
|
const updateCurrentWebApp = useLastCallback((updatedPartialWebApp: Partial<WebApp>) => {
|
||||||
if (!webApp) return;
|
if (!webAppKey) return;
|
||||||
const updatedWebApp = {
|
updateWebApp({ key: webAppKey, update: updatedPartialWebApp });
|
||||||
...webApp,
|
|
||||||
...updatedPartialWebApp,
|
|
||||||
};
|
|
||||||
webApp = updatedWebApp;
|
|
||||||
updateWebApp({ webApp: updatedWebApp });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { useCallback, useEffect, useRef } from '../../../../lib/teact/teact';
|
import { useCallback, useEffect, useRef } from '../../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../../global';
|
import { getActions } from '../../../../global';
|
||||||
|
|
||||||
import type { WebApp } from '../../../../global/types';
|
import type { WebApp } from '../../../../global/types';
|
||||||
import type { WebAppInboundEvent, WebAppOutboundEvent } from '../../../../types/webapp';
|
import type { WebAppInboundEvent, WebAppOutboundEvent } from '../../../../types/webapp';
|
||||||
|
|
||||||
|
import { getWebAppKey } from '../../../../global/helpers';
|
||||||
import { extractCurrentThemeParams } from '../../../../util/themeStyle';
|
import { extractCurrentThemeParams } from '../../../../util/themeStyle';
|
||||||
|
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
@ -44,6 +46,8 @@ const useWebAppFrame = (
|
|||||||
setWebAppPaymentSlug,
|
setWebAppPaymentSlug,
|
||||||
openInvoice,
|
openInvoice,
|
||||||
closeWebApp,
|
closeWebApp,
|
||||||
|
openSuggestedStatusModal,
|
||||||
|
updateWebApp,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const isReloadSupported = useRef<boolean>(false);
|
const isReloadSupported = useRef<boolean>(false);
|
||||||
@ -146,7 +150,10 @@ const useWebAppFrame = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'web_app_close') {
|
if (eventType === 'web_app_close') {
|
||||||
if (webApp) closeWebApp({ webApp, skipClosingConfirmation: true });
|
if (webApp) {
|
||||||
|
const key = getWebAppKey(webApp);
|
||||||
|
closeWebApp({ key, skipClosingConfirmation: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'web_app_request_viewport') {
|
if (eventType === 'web_app_request_viewport') {
|
||||||
@ -214,6 +221,51 @@ const useWebAppFrame = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType === 'web_app_set_emoji_status') {
|
||||||
|
const { custom_emoji_id, duration } = eventData;
|
||||||
|
|
||||||
|
if (!custom_emoji_id || typeof custom_emoji_id !== 'string') {
|
||||||
|
sendEvent({
|
||||||
|
eventType: 'emoji_status_failed',
|
||||||
|
eventData: {
|
||||||
|
error: 'SUGGESTED_EMOJI_INVALID',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duration) {
|
||||||
|
try {
|
||||||
|
BigInt(duration);
|
||||||
|
} catch (e) {
|
||||||
|
sendEvent({
|
||||||
|
eventType: 'emoji_status_failed',
|
||||||
|
eventData: {
|
||||||
|
error: 'DURATION_INVALID',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!webApp) {
|
||||||
|
sendEvent({
|
||||||
|
eventType: 'emoji_status_failed',
|
||||||
|
eventData: {
|
||||||
|
error: 'UNKNOWN_ERROR',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openSuggestedStatusModal({
|
||||||
|
webAppKey: getWebAppKey(webApp),
|
||||||
|
customEmojiId: custom_emoji_id,
|
||||||
|
duration: Number(duration),
|
||||||
|
botId: webApp.botId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onEvent(data);
|
onEvent(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ignore other messages
|
// Ignore other messages
|
||||||
@ -231,6 +283,21 @@ const useWebAppFrame = (
|
|||||||
sendViewport(isResizing);
|
sendViewport(isResizing);
|
||||||
}, [sendViewport, windowSize]);
|
}, [sendViewport, windowSize]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!webApp?.plannedEvents?.length) return;
|
||||||
|
const events = webApp.plannedEvents;
|
||||||
|
events.forEach((event) => {
|
||||||
|
sendEvent(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateWebApp({
|
||||||
|
key: getWebAppKey(webApp),
|
||||||
|
update: {
|
||||||
|
plannedEvents: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [sendEvent, webApp]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('message', handleMessage);
|
window.addEventListener('message', handleMessage);
|
||||||
return () => window.removeEventListener('message', handleMessage);
|
return () => window.removeEventListener('message', handleMessage);
|
||||||
|
|||||||
@ -165,7 +165,6 @@
|
|||||||
.modal-content,
|
.modal-content,
|
||||||
.modal-content > p {
|
.modal-content > p {
|
||||||
unicode-bidi: plaintext;
|
unicode-bidi: plaintext;
|
||||||
text-align: initial;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-about {
|
.modal-about {
|
||||||
|
|||||||
@ -46,6 +46,13 @@
|
|||||||
|
|
||||||
.notification-icon {
|
.notification-icon {
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-emoji-icon {
|
||||||
|
--custom-emoji-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon, .notification-emoji-icon {
|
||||||
margin-inline-end: 0.75rem;
|
margin-inline-end: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,12 +13,14 @@ import { isLangFnParam } from '../../util/localization/types';
|
|||||||
import { ANIMATION_END_DELAY } from '../../config';
|
import { ANIMATION_END_DELAY } from '../../config';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
|
import { REM } from '../common/helpers/mediaDimensions';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
||||||
|
|
||||||
|
import CustomEmoji from '../common/CustomEmoji';
|
||||||
import Icon from '../common/icons/Icon';
|
import Icon from '../common/icons/Icon';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import Portal from './Portal';
|
import Portal from './Portal';
|
||||||
@ -32,6 +34,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
const DEFAULT_DURATION = 3000;
|
const DEFAULT_DURATION = 3000;
|
||||||
const ANIMATION_DURATION = 150;
|
const ANIMATION_DURATION = 150;
|
||||||
|
const CUSTOM_EMOJI_SIZE = 1.75 * REM;
|
||||||
|
|
||||||
const Notification: FC<OwnProps> = ({
|
const Notification: FC<OwnProps> = ({
|
||||||
notification,
|
notification,
|
||||||
@ -51,6 +54,7 @@ const Notification: FC<OwnProps> = ({
|
|||||||
dismissAction,
|
dismissAction,
|
||||||
duration = DEFAULT_DURATION,
|
duration = DEFAULT_DURATION,
|
||||||
icon,
|
icon,
|
||||||
|
customEmojiIconId,
|
||||||
shouldShowTimer,
|
shouldShowTimer,
|
||||||
title,
|
title,
|
||||||
containerSelector,
|
containerSelector,
|
||||||
@ -155,7 +159,16 @@ const Notification: FC<OwnProps> = ({
|
|||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
<Icon name={icon || 'info-filled'} className="notification-icon" />
|
{customEmojiIconId ? (
|
||||||
|
<CustomEmoji
|
||||||
|
className="notification-emoji-icon"
|
||||||
|
forceAlways
|
||||||
|
size={CUSTOM_EMOJI_SIZE}
|
||||||
|
documentId={customEmojiIconId}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon name={icon || 'info-filled'} className="notification-icon" />
|
||||||
|
)}
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{renderedTitle && (
|
{renderedTitle && (
|
||||||
<div className="notification-title">{renderedTitle}</div>
|
<div className="notification-title">{renderedTitle}</div>
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import './api/statistics';
|
|||||||
import './api/stories';
|
import './api/stories';
|
||||||
import './ui/initial';
|
import './ui/initial';
|
||||||
import './ui/chats';
|
import './ui/chats';
|
||||||
|
import './ui/bots';
|
||||||
import './ui/messages';
|
import './ui/messages';
|
||||||
import './ui/globalSearch';
|
import './ui/globalSearch';
|
||||||
import './ui/middleSearch';
|
import './ui/middleSearch';
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type {
|
|||||||
ActionReturnType, GlobalState, TabArgs, WebApp,
|
ActionReturnType, GlobalState, TabArgs, WebApp,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import {
|
import {
|
||||||
type ApiChat, type ApiChatType, type ApiContact, type ApiInputMessageReplyInfo, type ApiPeer, type ApiUrlAuthResult,
|
type ApiChat, type ApiContact, type ApiInputMessageReplyInfo, type ApiPeer, type ApiUrlAuthResult,
|
||||||
MAIN_THREAD_ID,
|
MAIN_THREAD_ID,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { ManagementProgress } from '../../../types';
|
import { ManagementProgress } from '../../../types';
|
||||||
@ -30,10 +30,9 @@ import {
|
|||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import {
|
import {
|
||||||
activateWebAppIfOpen,
|
activateWebAppIfOpen,
|
||||||
addWebAppToOpenList, clearOpenedWebApps, hasOpenedMoreThanOneWebApps,
|
addWebAppToOpenList,
|
||||||
hasOpenedWebApps, removeActiveWebAppFromOpenList, removeWebAppFromOpenList,
|
replaceInlineBotSettings,
|
||||||
replaceInlineBotSettings, replaceInlineBotsIsLoading,
|
replaceInlineBotsIsLoading,
|
||||||
replaceIsWebAppModalOpen, replaceWebAppModalState, updateWebApp,
|
|
||||||
} from '../../reducers/bots';
|
} from '../../reducers/bots';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
@ -692,18 +691,6 @@ addActionHandler('loadPreviewMedias', async (global, actions, payload): Promise<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openWebAppTab', (global, actions, payload): ActionReturnType => {
|
|
||||||
const {
|
|
||||||
webApp, tabId = getCurrentTabId(),
|
|
||||||
} = payload;
|
|
||||||
|
|
||||||
if (webApp) {
|
|
||||||
global = getGlobal();
|
|
||||||
global = addWebAppToOpenList(global, webApp, true, true, tabId);
|
|
||||||
setGlobal(global);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('openWebAppsCloseConfirmationModal', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openWebAppsCloseConfirmationModal', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
@ -872,143 +859,6 @@ addActionHandler('sendWebViewData', (global, actions, payload): ActionReturnType
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('updateWebApp', (global, actions, payload): ActionReturnType => {
|
|
||||||
const {
|
|
||||||
webApp, tabId = getCurrentTabId(),
|
|
||||||
} = payload;
|
|
||||||
return updateWebApp(global, webApp, tabId);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('closeActiveWebApp', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
|
||||||
|
|
||||||
global = removeActiveWebAppFromOpenList(global, tabId);
|
|
||||||
if (!hasOpenedWebApps(global, tabId)) return replaceIsWebAppModalOpen(global, false, tabId);
|
|
||||||
|
|
||||||
return global;
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('openMoreAppsTab', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
|
||||||
|
|
||||||
const tabState = selectTabState(global, tabId);
|
|
||||||
global = updateTabState(global, {
|
|
||||||
webApps: {
|
|
||||||
...tabState.webApps,
|
|
||||||
activeWebApp: undefined,
|
|
||||||
isMoreAppsTabActive: true,
|
|
||||||
},
|
|
||||||
}, tabId);
|
|
||||||
|
|
||||||
return global;
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('closeMoreAppsTab', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
|
||||||
|
|
||||||
const tabState = selectTabState(global, tabId);
|
|
||||||
|
|
||||||
const openedWebApps = tabState.webApps.openedWebApps;
|
|
||||||
|
|
||||||
const openedWebAppsValues = Object.values(openedWebApps);
|
|
||||||
const openedWebAppsCount = openedWebAppsValues.length;
|
|
||||||
|
|
||||||
global = updateTabState(global, {
|
|
||||||
webApps: {
|
|
||||||
...tabState.webApps,
|
|
||||||
isMoreAppsTabActive: false,
|
|
||||||
activeWebApp: openedWebAppsCount ? openedWebAppsValues[openedWebAppsCount - 1] : undefined,
|
|
||||||
isModalOpen: openedWebAppsCount > 0,
|
|
||||||
},
|
|
||||||
}, tabId);
|
|
||||||
|
|
||||||
return global;
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('closeWebApp', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { webApp, skipClosingConfirmation, tabId = getCurrentTabId() } = payload || {};
|
|
||||||
|
|
||||||
global = removeWebAppFromOpenList(global, webApp, skipClosingConfirmation, tabId);
|
|
||||||
if (!hasOpenedWebApps(global, tabId)) return replaceIsWebAppModalOpen(global, false, tabId);
|
|
||||||
|
|
||||||
return global;
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('closeWebAppModal', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { shouldSkipConfirmation, tabId = getCurrentTabId() } = payload || {};
|
|
||||||
|
|
||||||
const shouldShowConfirmation = !shouldSkipConfirmation
|
|
||||||
&& !global.settings.byKey.shouldSkipWebAppCloseConfirmation && hasOpenedMoreThanOneWebApps(global, tabId);
|
|
||||||
|
|
||||||
if (shouldShowConfirmation) {
|
|
||||||
actions.openWebAppsCloseConfirmationModal({ tabId });
|
|
||||||
return global;
|
|
||||||
}
|
|
||||||
|
|
||||||
global = clearOpenedWebApps(global, tabId);
|
|
||||||
if (!hasOpenedWebApps(global, tabId)) return replaceIsWebAppModalOpen(global, false, tabId);
|
|
||||||
|
|
||||||
return global;
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('changeWebAppModalState', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
|
||||||
const tabState = selectTabState(global, tabId);
|
|
||||||
|
|
||||||
const newModalState = tabState.webApps.modalState === 'maximized' ? 'minimized' : 'maximized';
|
|
||||||
return replaceWebAppModalState(global, newModalState, tabId);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('setWebAppPaymentSlug', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { tabId = getCurrentTabId() } = payload;
|
|
||||||
const tabState = selectTabState(global, tabId);
|
|
||||||
const activeWebApp = tabState.webApps.activeWebApp;
|
|
||||||
if (!activeWebApp?.url) return undefined;
|
|
||||||
|
|
||||||
const updatedApp = {
|
|
||||||
...activeWebApp,
|
|
||||||
slug: payload.slug,
|
|
||||||
};
|
|
||||||
|
|
||||||
return updateWebApp(global, updatedApp, tabId);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('cancelBotTrustRequest', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
|
||||||
return updateTabState(global, {
|
|
||||||
botTrustRequest: undefined,
|
|
||||||
}, tabId);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('markBotTrusted', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { botId, isWriteAllowed, tabId = getCurrentTabId() } = payload;
|
|
||||||
const { trustedBotIds } = global;
|
|
||||||
|
|
||||||
const newTrustedBotIds = new Set(trustedBotIds);
|
|
||||||
newTrustedBotIds.add(botId);
|
|
||||||
|
|
||||||
global = {
|
|
||||||
...global,
|
|
||||||
trustedBotIds: Array.from(newTrustedBotIds),
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabState = selectTabState(global, tabId);
|
|
||||||
if (tabState.botTrustRequest?.onConfirm) {
|
|
||||||
const { action, payload: callbackPayload } = tabState.botTrustRequest.onConfirm;
|
|
||||||
// @ts-ignore
|
|
||||||
actions[action]({
|
|
||||||
...(callbackPayload as {}),
|
|
||||||
isWriteAllowed,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
global = updateTabState(global, {
|
|
||||||
botTrustRequest: undefined,
|
|
||||||
}, tabId);
|
|
||||||
|
|
||||||
setGlobal(global);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('loadAttachBots', async (global): Promise<void> => {
|
addActionHandler('loadAttachBots', async (global): Promise<void> => {
|
||||||
await loadAttachBots(global);
|
await loadAttachBots(global);
|
||||||
|
|
||||||
@ -1150,50 +1000,6 @@ addActionHandler('confirmAttachBotInstall', async (global, actions, payload): Pr
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('cancelAttachBotInstall', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
|
||||||
return updateTabState(global, {
|
|
||||||
requestedAttachBotInstall: undefined,
|
|
||||||
}, tabId);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('requestAttachBotInChat', (global, actions, payload): ActionReturnType => {
|
|
||||||
const {
|
|
||||||
bot, filter, startParam, tabId = getCurrentTabId(),
|
|
||||||
} = payload;
|
|
||||||
const currentChatId = selectCurrentMessageList(global, tabId)?.chatId;
|
|
||||||
|
|
||||||
const supportedFilters = bot.attachMenuPeerTypes?.filter((type): type is ApiChatType => (
|
|
||||||
type !== 'self' && filter.includes(type)
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!supportedFilters?.length) {
|
|
||||||
actions.callAttachBot({
|
|
||||||
chatId: currentChatId || bot.id,
|
|
||||||
bot,
|
|
||||||
startParam,
|
|
||||||
tabId,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
global = updateTabState(global, {
|
|
||||||
requestedAttachBotInChat: {
|
|
||||||
bot,
|
|
||||||
filter: supportedFilters,
|
|
||||||
startParam,
|
|
||||||
},
|
|
||||||
}, tabId);
|
|
||||||
setGlobal(global);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('cancelAttachBotInChat', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
|
||||||
return updateTabState(global, {
|
|
||||||
requestedAttachBotInChat: undefined,
|
|
||||||
}, tabId);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('requestBotUrlAuth', async (global, actions, payload): Promise<void> => {
|
addActionHandler('requestBotUrlAuth', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
chatId, buttonId, messageId, url, tabId = getCurrentTabId(),
|
chatId, buttonId, messageId, url, tabId = getCurrentTabId(),
|
||||||
|
|||||||
@ -29,9 +29,11 @@ import {
|
|||||||
updateUserSearch,
|
updateUserSearch,
|
||||||
updateUserSearchFetchingStatus,
|
updateUserSearchFetchingStatus,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatFullInfo,
|
selectChatFullInfo,
|
||||||
|
selectIsCurrentUserPremium,
|
||||||
selectPeer,
|
selectPeer,
|
||||||
selectPeerPhotos,
|
selectPeerPhotos,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
@ -388,10 +390,62 @@ addActionHandler('reportSpam', (global, actions, payload): ActionReturnType => {
|
|||||||
void callApi('reportSpam', peer);
|
void callApi('reportSpam', peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('setEmojiStatus', (global, actions, payload): ActionReturnType => {
|
addActionHandler('setEmojiStatus', async (global, actions, payload): Promise<void> => {
|
||||||
const { emojiStatus, expires } = payload;
|
const {
|
||||||
|
emojiStatusId, referrerWebAppKey, expires, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
void callApi('updateEmojiStatus', emojiStatus, expires);
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
if (!isCurrentUserPremium) {
|
||||||
|
if (referrerWebAppKey) {
|
||||||
|
actions.sendWebAppEvent({
|
||||||
|
webAppKey: referrerWebAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'emoji_status_failed',
|
||||||
|
eventData: {
|
||||||
|
error: 'USER_DECLINED',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.openPremiumModal({ initialSection: 'emoji_status', tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await callApi('updateEmojiStatus', emojiStatusId, expires);
|
||||||
|
|
||||||
|
if (referrerWebAppKey) {
|
||||||
|
if (!result) {
|
||||||
|
actions.sendWebAppEvent({
|
||||||
|
webAppKey: referrerWebAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'emoji_status_failed',
|
||||||
|
eventData: {
|
||||||
|
error: 'SERVER_ERROR',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.sendWebAppEvent({
|
||||||
|
webAppKey: referrerWebAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'emoji_status_set',
|
||||||
|
},
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
actions.showNotification({
|
||||||
|
message: {
|
||||||
|
key: 'BotSuggestedStatusUpdated',
|
||||||
|
},
|
||||||
|
customEmojiIconId: emojiStatusId,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('saveCloseFriends', async (global, actions, payload): Promise<void> => {
|
addActionHandler('saveCloseFriends', async (global, actions, payload): Promise<void> => {
|
||||||
@ -418,3 +472,39 @@ addActionHandler('saveCloseFriends', async (global, actions, payload): Promise<v
|
|||||||
});
|
});
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openSuggestedStatusModal', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
customEmojiId, duration, botId, webAppKey, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const customEmoji = await callApi('fetchCustomEmoji', {
|
||||||
|
documentId: [customEmojiId],
|
||||||
|
});
|
||||||
|
if (!customEmoji?.[0]) {
|
||||||
|
if (webAppKey) {
|
||||||
|
actions.sendWebAppEvent({
|
||||||
|
webAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'emoji_status_failed',
|
||||||
|
eventData: {
|
||||||
|
error: 'SUGGESTED_EMOJI_INVALID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
suggestedStatusModal: {
|
||||||
|
customEmojiId,
|
||||||
|
duration,
|
||||||
|
webAppKey,
|
||||||
|
botId,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|||||||
@ -1,8 +1,22 @@
|
|||||||
|
import type { ApiChatType } from '../../../api/types';
|
||||||
import type { ActionReturnType } from '../../types';
|
import type { ActionReturnType } from '../../types';
|
||||||
|
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
|
import { getWebAppKey } from '../../helpers';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import { addWebAppToOpenList } from '../../reducers/bots';
|
import {
|
||||||
|
addWebAppToOpenList,
|
||||||
|
clearOpenedWebApps,
|
||||||
|
hasOpenedMoreThanOneWebApps,
|
||||||
|
hasOpenedWebApps,
|
||||||
|
removeActiveWebAppFromOpenList,
|
||||||
|
removeWebAppFromOpenList,
|
||||||
|
replaceIsWebAppModalOpen,
|
||||||
|
replaceWebAppModalState,
|
||||||
|
updateWebApp,
|
||||||
|
} from '../../reducers/bots';
|
||||||
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
|
import { selectCurrentMessageList, selectTabState, selectWebApp } from '../../selectors';
|
||||||
|
|
||||||
addActionHandler('openWebAppTab', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openWebAppTab', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
@ -15,3 +29,199 @@ addActionHandler('openWebAppTab', (global, actions, payload): ActionReturnType =
|
|||||||
global = addWebAppToOpenList(global, webApp, true, true, tabId);
|
global = addWebAppToOpenList(global, webApp, true, true, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('updateWebApp', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
key, update, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
return updateWebApp(global, key, update, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeActiveWebApp', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
global = removeActiveWebAppFromOpenList(global, tabId);
|
||||||
|
if (!hasOpenedWebApps(global, tabId)) return replaceIsWebAppModalOpen(global, false, tabId);
|
||||||
|
|
||||||
|
return global;
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('openMoreAppsTab', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
global = updateTabState(global, {
|
||||||
|
webApps: {
|
||||||
|
...tabState.webApps,
|
||||||
|
activeWebApp: undefined,
|
||||||
|
isMoreAppsTabActive: true,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
|
||||||
|
return global;
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeMoreAppsTab', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
|
||||||
|
const openedWebApps = tabState.webApps.openedWebApps;
|
||||||
|
|
||||||
|
const openedWebAppsValues = Object.values(openedWebApps);
|
||||||
|
const openedWebAppsCount = openedWebAppsValues.length;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
webApps: {
|
||||||
|
...tabState.webApps,
|
||||||
|
isMoreAppsTabActive: false,
|
||||||
|
activeWebApp: openedWebAppsCount ? openedWebAppsValues[openedWebAppsCount - 1] : undefined,
|
||||||
|
isModalOpen: openedWebAppsCount > 0,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
|
||||||
|
return global;
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeWebApp', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { key, skipClosingConfirmation, tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
global = removeWebAppFromOpenList(global, key, skipClosingConfirmation, tabId);
|
||||||
|
if (!hasOpenedWebApps(global, tabId)) return replaceIsWebAppModalOpen(global, false, tabId);
|
||||||
|
|
||||||
|
return global;
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeWebAppModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { shouldSkipConfirmation, tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
const shouldShowConfirmation = !shouldSkipConfirmation
|
||||||
|
&& !global.settings.byKey.shouldSkipWebAppCloseConfirmation && hasOpenedMoreThanOneWebApps(global, tabId);
|
||||||
|
|
||||||
|
if (shouldShowConfirmation) {
|
||||||
|
actions.openWebAppsCloseConfirmationModal({ tabId });
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = clearOpenedWebApps(global, tabId);
|
||||||
|
if (!hasOpenedWebApps(global, tabId)) return replaceIsWebAppModalOpen(global, false, tabId);
|
||||||
|
|
||||||
|
return global;
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('changeWebAppModalState', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
|
||||||
|
const newModalState = tabState.webApps.modalState === 'maximized' ? 'minimized' : 'maximized';
|
||||||
|
return replaceWebAppModalState(global, newModalState, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('setWebAppPaymentSlug', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload;
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
const activeWebApp = tabState.webApps.activeWebApp;
|
||||||
|
if (!activeWebApp?.url) return undefined;
|
||||||
|
|
||||||
|
const key = getWebAppKey(activeWebApp);
|
||||||
|
|
||||||
|
return updateWebApp(global, key, { slug: payload.slug }, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('cancelBotTrustRequest', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
return updateTabState(global, {
|
||||||
|
botTrustRequest: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('markBotTrusted', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { botId, isWriteAllowed, tabId = getCurrentTabId() } = payload;
|
||||||
|
const { trustedBotIds } = global;
|
||||||
|
|
||||||
|
const newTrustedBotIds = new Set(trustedBotIds);
|
||||||
|
newTrustedBotIds.add(botId);
|
||||||
|
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
trustedBotIds: Array.from(newTrustedBotIds),
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
if (tabState.botTrustRequest?.onConfirm) {
|
||||||
|
const { action, payload: callbackPayload } = tabState.botTrustRequest.onConfirm;
|
||||||
|
// @ts-ignore
|
||||||
|
actions[action]({
|
||||||
|
...(callbackPayload as {}),
|
||||||
|
isWriteAllowed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
botTrustRequest: undefined,
|
||||||
|
}, tabId);
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('sendWebAppEvent', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { event, webAppKey, tabId = getCurrentTabId() } = payload;
|
||||||
|
const webApp = selectWebApp(global, webAppKey, tabId);
|
||||||
|
if (!webApp) return global;
|
||||||
|
|
||||||
|
const newPlannedEvents = webApp.plannedEvents ? [...webApp.plannedEvents, event] : [event];
|
||||||
|
|
||||||
|
actions.updateWebApp({
|
||||||
|
key: webAppKey,
|
||||||
|
update: {
|
||||||
|
plannedEvents: newPlannedEvents,
|
||||||
|
},
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return global;
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('cancelAttachBotInstall', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
return updateTabState(global, {
|
||||||
|
requestedAttachBotInstall: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('requestAttachBotInChat', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
bot, filter, startParam, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
const currentChatId = selectCurrentMessageList(global, tabId)?.chatId;
|
||||||
|
|
||||||
|
const supportedFilters = bot.attachMenuPeerTypes?.filter((type): type is ApiChatType => (
|
||||||
|
type !== 'self' && filter.includes(type)
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!supportedFilters?.length) {
|
||||||
|
actions.callAttachBot({
|
||||||
|
chatId: currentChatId || bot.id,
|
||||||
|
bot,
|
||||||
|
startParam,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
requestedAttachBotInChat: {
|
||||||
|
bot,
|
||||||
|
filter: supportedFilters,
|
||||||
|
startParam,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('cancelAttachBotInChat', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
return updateTabState(global, {
|
||||||
|
requestedAttachBotInChat: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|||||||
@ -9,7 +9,7 @@ addActionHandler('setUserSearchQuery', (global, actions, payload): ActionReturnT
|
|||||||
const {
|
const {
|
||||||
query,
|
query,
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
} = payload!;
|
} = payload;
|
||||||
|
|
||||||
return updateUserSearch(global, {
|
return updateUserSearch(global, {
|
||||||
globalUserIds: undefined,
|
globalUserIds: undefined,
|
||||||
@ -20,7 +20,7 @@ addActionHandler('setUserSearchQuery', (global, actions, payload): ActionReturnT
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openAddContactDialog', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openAddContactDialog', (global, actions, payload): ActionReturnType => {
|
||||||
const { userId, tabId = getCurrentTabId() } = payload!;
|
const { userId, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
newContact: { userId },
|
newContact: { userId },
|
||||||
@ -42,3 +42,11 @@ addActionHandler('closeNewContactDialog', (global, actions, payload): ActionRetu
|
|||||||
|
|
||||||
return closeNewContactDialog(global, tabId);
|
return closeNewContactDialog(global, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeSuggestedStatusModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
suggestedStatusModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export function convertToApiChatType(type: string): ApiChatType | undefined {
|
|||||||
export function getWebAppKey(webApp: Partial<WebApp>) {
|
export function getWebAppKey(webApp: Partial<WebApp>) {
|
||||||
if (webApp.requestUrl) return webApp.requestUrl;
|
if (webApp.requestUrl) return webApp.requestUrl;
|
||||||
if (webApp.appName) return `${webApp.botId}?appName=${webApp.appName}`;
|
if (webApp.appName) return `${webApp.botId}?appName=${webApp.appName}`;
|
||||||
return webApp.botId;
|
return webApp.botId!;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSystemBot(botId: string) {
|
export function isSystemBot(botId: string) {
|
||||||
|
|||||||
@ -37,20 +37,19 @@ export function replaceInlineBotsIsLoading<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateWebApp <T extends GlobalState>(
|
export function updateWebApp <T extends GlobalState>(
|
||||||
global: T, webApp: Partial<WebApp>,
|
global: T, key: string, webAppUpdate: Partial<WebApp>,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
const currentTabState = selectTabState(global, tabId);
|
const currentTabState = selectTabState(global, tabId);
|
||||||
const openedWebApps = currentTabState.webApps.openedWebApps;
|
const openedWebApps = currentTabState.webApps.openedWebApps;
|
||||||
|
|
||||||
const key = webApp && getWebAppKey(webApp);
|
const originalWebApp = openedWebApps[key];
|
||||||
const originalWebApp = key ? openedWebApps[key] : undefined;
|
|
||||||
|
|
||||||
if (!originalWebApp) return global;
|
if (!originalWebApp) return global;
|
||||||
|
|
||||||
const updatedValue = {
|
const updatedValue = {
|
||||||
...originalWebApp,
|
...originalWebApp,
|
||||||
...webApp,
|
...webAppUpdate,
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedWebAppKey = getWebAppKey(updatedValue);
|
const updatedWebAppKey = getWebAppKey(updatedValue);
|
||||||
@ -143,18 +142,22 @@ export function removeActiveWebAppFromOpenList<T extends GlobalState>(
|
|||||||
|
|
||||||
if (!currentTabState.webApps.activeWebApp) return global;
|
if (!currentTabState.webApps.activeWebApp) return global;
|
||||||
|
|
||||||
return removeWebAppFromOpenList(global, currentTabState.webApps.activeWebApp, false, tabId);
|
const key = getWebAppKey(currentTabState.webApps.activeWebApp);
|
||||||
|
|
||||||
|
return removeWebAppFromOpenList(global, key, false, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeWebAppFromOpenList<T extends GlobalState>(
|
export function removeWebAppFromOpenList<T extends GlobalState>(
|
||||||
global: T, webApp: WebApp, skipClosingConfirmation?: boolean,
|
global: T, key: string, skipClosingConfirmation?: boolean,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
const currentTabState = selectTabState(global, tabId);
|
const currentTabState = selectTabState(global, tabId);
|
||||||
const openedWebApps = currentTabState.webApps.openedWebApps;
|
const openedWebApps = currentTabState.webApps.openedWebApps;
|
||||||
|
const webApp = openedWebApps[key];
|
||||||
|
if (!webApp) return global;
|
||||||
|
|
||||||
if (!skipClosingConfirmation && webApp.shouldConfirmClosing) {
|
if (!skipClosingConfirmation && webApp.shouldConfirmClosing) {
|
||||||
return updateWebApp(global, { ...webApp, isCloseModalOpen: true }, tabId);
|
return updateWebApp(global, key, { isCloseModalOpen: true }, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedOpenedWebApps = { ...openedWebApps };
|
const updatedOpenedWebApps = { ...openedWebApps };
|
||||||
@ -164,7 +167,7 @@ export function removeWebAppFromOpenList<T extends GlobalState>(
|
|||||||
|
|
||||||
if (removingWebAppKey) {
|
if (removingWebAppKey) {
|
||||||
delete updatedOpenedWebApps[removingWebAppKey];
|
delete updatedOpenedWebApps[removingWebAppKey];
|
||||||
newOpenedKeys = currentTabState.webApps.openedOrderedKeys.filter((key) => key !== removingWebAppKey);
|
newOpenedKeys = currentTabState.webApps.openedOrderedKeys.filter((k) => k !== removingWebAppKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeWebApp = currentTabState.webApps.activeWebApp;
|
const activeWebApp = currentTabState.webApps.activeWebApp;
|
||||||
|
|||||||
@ -150,3 +150,9 @@ export function selectIsSynced<T extends GlobalState>(global: T) {
|
|||||||
export function selectCanAnimateSnapEffect<T extends GlobalState>(global: T) {
|
export function selectCanAnimateSnapEffect<T extends GlobalState>(global: T) {
|
||||||
return IS_SNAP_EFFECT_SUPPORTED && selectPerformanceSettingsValue(global, 'snapEffect');
|
return IS_SNAP_EFFECT_SUPPORTED && selectPerformanceSettingsValue(global, 'snapEffect');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectWebApp<T extends GlobalState>(
|
||||||
|
global: T, key: string, ...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
) {
|
||||||
|
return selectTabState(global, tabId).webApps.openedWebApps[key];
|
||||||
|
}
|
||||||
|
|||||||
@ -911,6 +911,13 @@ export type TabState = {
|
|||||||
userId?: string;
|
userId?: string;
|
||||||
gift: ApiUserStarGift | ApiStarGift;
|
gift: ApiUserStarGift | ApiStarGift;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
suggestedStatusModal?: {
|
||||||
|
botId: string;
|
||||||
|
webAppKey?: string;
|
||||||
|
customEmojiId: string;
|
||||||
|
duration?: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GlobalState = {
|
export type GlobalState = {
|
||||||
@ -1333,6 +1340,7 @@ export type WebApp = {
|
|||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
isBackButtonVisible?: boolean;
|
isBackButtonVisible?: boolean;
|
||||||
isSettingsButtonVisible?: boolean;
|
isSettingsButtonVisible?: boolean;
|
||||||
|
plannedEvents?: WebAppOutboundEvent[];
|
||||||
sendEvent?: (event: WebAppOutboundEvent) => void;
|
sendEvent?: (event: WebAppOutboundEvent) => void;
|
||||||
reloadFrame?: (url: string) => void;
|
reloadFrame?: (url: string) => void;
|
||||||
};
|
};
|
||||||
@ -3115,7 +3123,8 @@ export interface ActionPayloads {
|
|||||||
startParam?: string;
|
startParam?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
updateWebApp: {
|
updateWebApp: {
|
||||||
webApp: Partial<WebApp>;
|
key: string;
|
||||||
|
update: Partial<WebApp>;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
requestMainWebView: {
|
requestMainWebView: {
|
||||||
botId: string;
|
botId: string;
|
||||||
@ -3260,9 +3269,13 @@ export interface ActionPayloads {
|
|||||||
openMoreAppsTab: WithTabId | undefined;
|
openMoreAppsTab: WithTabId | undefined;
|
||||||
closeMoreAppsTab: WithTabId | undefined;
|
closeMoreAppsTab: WithTabId | undefined;
|
||||||
closeWebApp: {
|
closeWebApp: {
|
||||||
webApp: WebApp;
|
key: string;
|
||||||
skipClosingConfirmation?: boolean;
|
skipClosingConfirmation?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
sendWebAppEvent: {
|
||||||
|
webAppKey: string;
|
||||||
|
event: WebAppOutboundEvent;
|
||||||
|
} & WithTabId;
|
||||||
closeWebAppModal: ({
|
closeWebAppModal: ({
|
||||||
shouldSkipConfirmation?: boolean;
|
shouldSkipConfirmation?: boolean;
|
||||||
} & WithTabId) | undefined;
|
} & WithTabId) | undefined;
|
||||||
@ -3546,9 +3559,17 @@ export interface ActionPayloads {
|
|||||||
closeStarsGiftModal: WithTabId | undefined;
|
closeStarsGiftModal: WithTabId | undefined;
|
||||||
|
|
||||||
setEmojiStatus: {
|
setEmojiStatus: {
|
||||||
emojiStatus: ApiSticker;
|
emojiStatusId: string;
|
||||||
expires?: number;
|
expires?: number;
|
||||||
};
|
referrerWebAppKey?: string;
|
||||||
|
} & WithTabId;
|
||||||
|
openSuggestedStatusModal: {
|
||||||
|
botId: string;
|
||||||
|
webAppKey?: string;
|
||||||
|
customEmojiId: string;
|
||||||
|
duration?: number;
|
||||||
|
} & WithTabId;
|
||||||
|
closeSuggestedStatusModal: WithTabId | undefined;
|
||||||
|
|
||||||
// Invoice
|
// Invoice
|
||||||
openInvoice: Exclude<ApiInputInvoice, ApiInputInvoiceStarGift> & WithTabId;
|
openInvoice: Exclude<ApiInputInvoice, ApiInputInvoiceStarGift> & WithTabId;
|
||||||
|
|||||||
@ -509,6 +509,7 @@ export type CustomPeer = {
|
|||||||
peerColorId?: number;
|
peerColorId?: number;
|
||||||
isVerified?: boolean;
|
isVerified?: boolean;
|
||||||
fakeType?: ApiFakeType;
|
fakeType?: ApiFakeType;
|
||||||
|
emojiStatusId?: string;
|
||||||
customPeerAvatarColor?: string;
|
customPeerAvatarColor?: string;
|
||||||
withPremiumGradient?: boolean;
|
withPremiumGradient?: boolean;
|
||||||
} & ({
|
} & ({
|
||||||
|
|||||||
10
src/types/language.d.ts
vendored
10
src/types/language.d.ts
vendored
@ -943,6 +943,7 @@ export interface LangPair {
|
|||||||
'ScheduleSendWhenOnline': undefined;
|
'ScheduleSendWhenOnline': undefined;
|
||||||
'VoipIncoming': undefined;
|
'VoipIncoming': undefined;
|
||||||
'LiveLocationUpdatedJustNow': undefined;
|
'LiveLocationUpdatedJustNow': undefined;
|
||||||
|
'RightNow': undefined;
|
||||||
'AudioPause': undefined;
|
'AudioPause': undefined;
|
||||||
'AudioPlay': undefined;
|
'AudioPlay': undefined;
|
||||||
'ToggleUserNotifications': undefined;
|
'ToggleUserNotifications': undefined;
|
||||||
@ -1152,6 +1153,8 @@ export interface LangPair {
|
|||||||
'CloseMiniApps': undefined;
|
'CloseMiniApps': undefined;
|
||||||
'DoNotAskAgain': undefined;
|
'DoNotAskAgain': undefined;
|
||||||
'PaymentInfoDone': undefined;
|
'PaymentInfoDone': undefined;
|
||||||
|
'BotSuggestedStatusTitle': undefined;
|
||||||
|
'BotSuggestedStatusUpdated': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||||
@ -1541,6 +1544,13 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
|||||||
'StarsPerMonth': {
|
'StarsPerMonth': {
|
||||||
'amount': V;
|
'amount': V;
|
||||||
};
|
};
|
||||||
|
'BotSuggestedStatusFor': {
|
||||||
|
'bot': V;
|
||||||
|
'duration': V;
|
||||||
|
};
|
||||||
|
'BotSuggestedStatus': {
|
||||||
|
'bot': V;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairPlural {
|
export interface LangPairPlural {
|
||||||
|
|||||||
@ -95,6 +95,10 @@ export type WebAppInboundEvent =
|
|||||||
WebAppEvent<'web_app_biometry_update_token', {
|
WebAppEvent<'web_app_biometry_update_token', {
|
||||||
token: string;
|
token: string;
|
||||||
}> |
|
}> |
|
||||||
|
WebAppEvent<'web_app_set_emoji_status', {
|
||||||
|
custom_emoji_id: string;
|
||||||
|
duration?: number;
|
||||||
|
}> |
|
||||||
WebAppEvent<'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand'
|
WebAppEvent<'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand'
|
||||||
| 'web_app_request_phone' | 'web_app_close' | 'web_app_close_scan_qr_popup'
|
| 'web_app_request_phone' | 'web_app_close' | 'web_app_close_scan_qr_popup'
|
||||||
| 'web_app_request_write_access' | 'web_app_request_phone' | 'iframe_will_reload'
|
| 'web_app_request_write_access' | 'web_app_request_phone' | 'iframe_will_reload'
|
||||||
@ -168,6 +172,10 @@ export type WebAppOutboundEvent =
|
|||||||
WebAppEvent<'biometry_token_updated', {
|
WebAppEvent<'biometry_token_updated', {
|
||||||
status: 'updated' | 'removed' | 'failed';
|
status: 'updated' | 'removed' | 'failed';
|
||||||
}> |
|
}> |
|
||||||
|
WebAppEvent<'emoji_status_failed', {
|
||||||
|
error: 'UNSUPPORTED' | 'USER_DECLINED' | 'SUGGESTED_EMOJI_INVALID'
|
||||||
|
| 'DURATION_INVALID' | 'SERVER_ERROR' | 'UNKNOWN_ERROR';
|
||||||
|
}> |
|
||||||
WebAppEvent<'main_button_pressed' |
|
WebAppEvent<'main_button_pressed' |
|
||||||
'secondary_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed'
|
'secondary_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed'
|
||||||
| 'reload_iframe', null>;
|
| 'reload_iframe' | 'emoji_status_set', null>;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { OldLangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
import type { TimeFormat } from '../../types';
|
import type { TimeFormat } from '../../types';
|
||||||
|
import type { LangFn } from '../localization';
|
||||||
|
|
||||||
import withCache from '../withCache';
|
import withCache from '../withCache';
|
||||||
|
|
||||||
@ -374,29 +375,28 @@ export function formatDateAtTime(
|
|||||||
return lang('formatDateAtTime', [formattedDate, time]);
|
return lang('formatDateAtTime', [formattedDate, time]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatDateInFuture(
|
export function formatShortDuration(lang: LangFn, duration: number) {
|
||||||
lang: OldLangFn,
|
if (duration < 0) {
|
||||||
currentTime: number,
|
|
||||||
datetime: number,
|
|
||||||
) {
|
|
||||||
const diff = Math.ceil(datetime - currentTime);
|
|
||||||
if (diff < 0) {
|
|
||||||
return lang('RightNow');
|
return lang('RightNow');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diff < 60) {
|
if (duration < 60) {
|
||||||
return lang('Seconds', diff);
|
const count = Math.ceil(duration);
|
||||||
|
return lang('Seconds', { count }, { pluralValue: duration });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diff < 60 * 60) {
|
if (duration < 60 * 60) {
|
||||||
return lang('Minutes', Math.ceil(diff / 60));
|
const count = Math.ceil(duration / 60);
|
||||||
|
return lang('Minutes', { count }, { pluralValue: count });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diff < 60 * 60 * 24) {
|
if (duration < 60 * 60 * 24) {
|
||||||
return lang('Hours', Math.ceil(diff / (60 * 60)));
|
const count = Math.ceil(duration / (60 * 60));
|
||||||
|
return lang('Hours', { count }, { pluralValue: count });
|
||||||
}
|
}
|
||||||
|
|
||||||
return lang('Days', Math.ceil(diff / (60 * 60 * 24)));
|
const count = Math.ceil(duration / (60 * 60 * 24));
|
||||||
|
return lang('Days', { count }, { pluralValue: count });
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidDate(day: number, month: number, year = 2021): boolean {
|
function isValidDate(day: number, month: number, year = 2021): boolean {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user