Mini apps main app button and previews (#4866)
Co-authored-by: Ponama <anastasiiadmm@gmail.com>
This commit is contained in:
parent
8433012a88
commit
ff7c6dca5c
@ -110,7 +110,7 @@ function buildApiAttachMenuIcon(icon: GramJs.AttachMenuBotIcon): ApiAttachBotIco
|
|||||||
|
|
||||||
export function buildApiBotInfo(botInfo: GramJs.BotInfo, chatId: string): ApiBotInfo {
|
export function buildApiBotInfo(botInfo: GramJs.BotInfo, chatId: string): ApiBotInfo {
|
||||||
const {
|
const {
|
||||||
description, descriptionPhoto, descriptionDocument, userId, commands, menuButton,
|
description, descriptionPhoto, descriptionDocument, userId, commands, menuButton, hasPreviewMedias,
|
||||||
} = botInfo;
|
} = botInfo;
|
||||||
|
|
||||||
const botId = userId && buildApiPeerId(userId, 'user');
|
const botId = userId && buildApiPeerId(userId, 'user');
|
||||||
@ -126,6 +126,7 @@ export function buildApiBotInfo(botInfo: GramJs.BotInfo, chatId: string): ApiBot
|
|||||||
photo,
|
photo,
|
||||||
menuButton: buildApiBotMenuButton(menuButton),
|
menuButton: buildApiBotMenuButton(menuButton),
|
||||||
commands: commandsArray?.length ? commandsArray : undefined,
|
commands: commandsArray?.length ? commandsArray : undefined,
|
||||||
|
hasPreviewMedia: hasPreviewMedias,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -81,6 +81,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
type: userType,
|
type: userType,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
|
hasMainMiniApp: Boolean(mtpUser.botHasMainApp),
|
||||||
canEditBot: botCanEdit,
|
canEditBot: botCanEdit,
|
||||||
...(userType === 'userTypeBot' && { canBeInvitedToGroup: !mtpUser.botNochats }),
|
...(userType === 'userTypeBot' && { canBeInvitedToGroup: !mtpUser.botNochats }),
|
||||||
...(usernames && { usernames }),
|
...(usernames && { usernames }),
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiBotApp,
|
ApiBotApp,
|
||||||
|
ApiBotPreviewMedia,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiInputMessageReplyInfo,
|
ApiInputMessageReplyInfo,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
@ -23,6 +24,7 @@ import {
|
|||||||
} from '../apiBuilders/bots';
|
} from '../apiBuilders/bots';
|
||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||||
|
import { buildMessageMediaContent } from '../apiBuilders/messageContent';
|
||||||
import { buildApiUrlAuthResult } from '../apiBuilders/misc';
|
import { buildApiUrlAuthResult } from '../apiBuilders/misc';
|
||||||
import { buildApiUser } from '../apiBuilders/users';
|
import { buildApiUser } from '../apiBuilders/users';
|
||||||
import {
|
import {
|
||||||
@ -238,6 +240,35 @@ export async function requestWebView({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function requestMainWebView({
|
||||||
|
peer,
|
||||||
|
bot,
|
||||||
|
startParam,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
peer: ApiPeer;
|
||||||
|
bot: ApiUser;
|
||||||
|
startParam?: string;
|
||||||
|
theme?: ApiThemeParameters;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.RequestMainWebView({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
bot: buildInputPeer(bot.id, bot.accessHash),
|
||||||
|
startParam,
|
||||||
|
themeParams: theme ? buildInputThemeParams(theme) : undefined,
|
||||||
|
platform: WEB_APP_PLATFORM,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!(result instanceof GramJs.WebViewResultUrl)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: result.url,
|
||||||
|
queryId: result.queryId?.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function requestSimpleWebView({
|
export async function requestSimpleWebView({
|
||||||
bot,
|
bot,
|
||||||
url,
|
url,
|
||||||
@ -551,6 +582,22 @@ export async function invokeWebViewCustomMethod({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchPreviewMedias({ bot } : { bot: ApiUser }) {
|
||||||
|
const result = await invokeRequest(new GramJs.bots.GetPreviewMedias({
|
||||||
|
bot: buildInputPeer(bot.id, bot.accessHash),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
const previews: ApiBotPreviewMedia[] = result.map((preview) => {
|
||||||
|
return {
|
||||||
|
content: buildMessageMediaContent(preview.media)!,
|
||||||
|
date: preview.date,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return previews;
|
||||||
|
}
|
||||||
|
|
||||||
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
|
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
|
||||||
return results.map((result) => {
|
return results.map((result) => {
|
||||||
if (result instanceof GramJs.BotInlineMediaResult) {
|
if (result instanceof GramJs.BotInlineMediaResult) {
|
||||||
|
|||||||
@ -65,8 +65,8 @@ export {
|
|||||||
} from './twoFaSettings';
|
} from './twoFaSettings';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
answerCallbackButton, setBotInfo, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults,
|
answerCallbackButton, setBotInfo, fetchTopInlineBots, fetchPreviewMedias, fetchInlineBot, fetchInlineBotResults,
|
||||||
sendInlineBotResult, startBot, fetchPopularAppBots, fetchTopBotApps,
|
sendInlineBotResult, startBot, requestMainWebView, fetchPopularAppBots, fetchTopBotApps,
|
||||||
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot, fetchBotApp,
|
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot, fetchBotApp,
|
||||||
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot, requestAppWebView,
|
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot, requestAppWebView,
|
||||||
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
|
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiDimensions,
|
ApiDimensions,
|
||||||
ApiPhoto, ApiSticker, ApiThumbnail, ApiVideo,
|
ApiPhoto, ApiSticker, ApiThumbnail, ApiVideo, MediaContainer,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
|
|
||||||
export type ApiInlineResultType = (
|
export type ApiInlineResultType = (
|
||||||
@ -74,4 +74,9 @@ export interface ApiBotInfo {
|
|||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
gif?: ApiVideo;
|
gif?: ApiVideo;
|
||||||
menuButton: ApiBotMenuButton;
|
menuButton: ApiBotMenuButton;
|
||||||
|
hasPreviewMedia?: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiBotPreviewMedia extends MediaContainer {
|
||||||
|
date: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export interface ApiUser {
|
|||||||
maxStoryId?: number;
|
maxStoryId?: number;
|
||||||
color?: ApiPeerColor;
|
color?: ApiPeerColor;
|
||||||
canEditBot?: boolean;
|
canEditBot?: boolean;
|
||||||
|
hasMainMiniApp?: boolean;
|
||||||
botActiveUsers?: number;
|
botActiveUsers?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1271,6 +1271,9 @@
|
|||||||
"MenuInstallApp" = "Install App";
|
"MenuInstallApp" = "Install App";
|
||||||
"RemoveEffect" = "Remove effect";
|
"RemoveEffect" = "Remove effect";
|
||||||
"ReplyInPrivateMessage" = "Reply In Private Message";
|
"ReplyInPrivateMessage" = "Reply In Private Message";
|
||||||
|
"ProfileOpenAppAbout" = "By launching this mini app, you agree to the {terms}.";
|
||||||
|
"ProfileOpenAppTerms" = "Terms of Service for Mini Apps";
|
||||||
|
"ProfileBotOpenAppInfoLink" = "https://telegram.org/tos/mini-apps";
|
||||||
"MonetizationInfoTONTitle" = "What is 💎 TON?";
|
"MonetizationInfoTONTitle" = "What is 💎 TON?";
|
||||||
"ChannelEarnLearnCoinAbout" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its high speed and low commissions on transactions. {link}";
|
"ChannelEarnLearnCoinAbout" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its high speed and low commissions on transactions. {link}";
|
||||||
"MonetizationBalanceZeroInfo" = "You will be able to collect rewards using Fragment, a third-party platform used by advertisers to pay for ads. {link}";
|
"MonetizationBalanceZeroInfo" = "You will be able to collect rewards using Fragment, a third-party platform used by advertisers to pay for ads. {link}";
|
||||||
|
|||||||
82
src/components/common/PreviewMedia.tsx
Normal file
82
src/components/common/PreviewMedia.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
import React, { memo, useRef } from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { ApiBotPreviewMedia } from '../../api/types';
|
||||||
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getMessageMediaHash, getMessageMediaThumbDataUri,
|
||||||
|
} from '../../global/helpers';
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
import { formatMediaDuration } from '../../util/dates/dateFormat';
|
||||||
|
import stopEvent from '../../util/stopEvent';
|
||||||
|
|
||||||
|
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
import useMedia from '../../hooks/useMedia';
|
||||||
|
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||||
|
|
||||||
|
import './Media.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
media: ApiBotPreviewMedia;
|
||||||
|
idPrefix?: string;
|
||||||
|
isProtected?: boolean;
|
||||||
|
observeIntersection?: ObserveFn;
|
||||||
|
onClick: (index: number) => void;
|
||||||
|
index: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PreviewMedia: FC<OwnProps> = ({
|
||||||
|
media,
|
||||||
|
idPrefix = 'preview-media',
|
||||||
|
isProtected,
|
||||||
|
observeIntersection,
|
||||||
|
onClick,
|
||||||
|
index,
|
||||||
|
}) => {
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||||
|
const thumbDataUri = getMessageMediaThumbDataUri(media);
|
||||||
|
|
||||||
|
const mediaBlobUrl = useMedia(getMessageMediaHash(media, 'preview'), !isIntersecting);
|
||||||
|
const transitionClassNames = useMediaTransition(mediaBlobUrl);
|
||||||
|
|
||||||
|
const video = media.content.video;
|
||||||
|
|
||||||
|
const handleClick = useLastCallback(() => {
|
||||||
|
onClick(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
id={`${idPrefix}${index}`}
|
||||||
|
className="Media scroll-item"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={thumbDataUri}
|
||||||
|
className="media-miniature"
|
||||||
|
alt=""
|
||||||
|
draggable={!isProtected}
|
||||||
|
decoding="async"
|
||||||
|
onContextMenu={isProtected ? stopEvent : undefined}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={mediaBlobUrl}
|
||||||
|
className={buildClassName('full-media', 'media-miniature', transitionClassNames)}
|
||||||
|
alt=""
|
||||||
|
draggable={!isProtected}
|
||||||
|
decoding="async"
|
||||||
|
onContextMenu={isProtected ? stopEvent : undefined}
|
||||||
|
/>
|
||||||
|
{video && <span className="video-duration">{video.isGif ? 'GIF' : formatMediaDuration(video.duration)}</span>}
|
||||||
|
{isProtected && <span className="protector" />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(PreviewMedia);
|
||||||
@ -24,6 +24,11 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sectionInfo {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
.personalChannelSubscribers {
|
.personalChannelSubscribers {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
@ -36,3 +41,8 @@
|
|||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.openAppButton {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|||||||
@ -32,20 +32,24 @@ import { copyTextToClipboard } from '../../../util/clipboard';
|
|||||||
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
||||||
import { debounce } from '../../../util/schedulers';
|
import { debounce } from '../../../util/schedulers';
|
||||||
import stopEvent from '../../../util/stopEvent';
|
import stopEvent from '../../../util/stopEvent';
|
||||||
|
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||||
import { ChatAnimationTypes } from '../../left/main/hooks';
|
import { ChatAnimationTypes } from '../../left/main/hooks';
|
||||||
import formatUsername from '../helpers/formatUsername';
|
import formatUsername from '../helpers/formatUsername';
|
||||||
import renderText from '../helpers/renderText';
|
import renderText from '../helpers/renderText';
|
||||||
|
|
||||||
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
import useDevicePixelRatio from '../../../hooks/window/useDevicePixelRatio';
|
import useDevicePixelRatio from '../../../hooks/window/useDevicePixelRatio';
|
||||||
|
|
||||||
import Chat from '../../left/main/Chat';
|
import Chat from '../../left/main/Chat';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import Skeleton from '../../ui/placeholder/Skeleton';
|
import Skeleton from '../../ui/placeholder/Skeleton';
|
||||||
import Switcher from '../../ui/Switcher';
|
import Switcher from '../../ui/Switcher';
|
||||||
|
import SafeLink from '../SafeLink';
|
||||||
import BusinessHours from './BusinessHours';
|
import BusinessHours from './BusinessHours';
|
||||||
import UserBirthday from './UserBirthday';
|
import UserBirthday from './UserBirthday';
|
||||||
|
|
||||||
@ -70,6 +74,7 @@ type StateProps = {
|
|||||||
topicLink?: string;
|
topicLink?: string;
|
||||||
hasSavedMessages?: boolean;
|
hasSavedMessages?: boolean;
|
||||||
personalChannel?: ApiChat;
|
personalChannel?: ApiChat;
|
||||||
|
hasMainMiniApp?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_MAP_CONFIG = {
|
const DEFAULT_MAP_CONFIG = {
|
||||||
@ -95,6 +100,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
topicLink,
|
topicLink,
|
||||||
hasSavedMessages,
|
hasSavedMessages,
|
||||||
personalChannel,
|
personalChannel,
|
||||||
|
hasMainMiniApp,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
showNotification,
|
showNotification,
|
||||||
@ -104,6 +110,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
openSavedDialog,
|
openSavedDialog,
|
||||||
openMapModal,
|
openMapModal,
|
||||||
requestCollectibleInfo,
|
requestCollectibleInfo,
|
||||||
|
requestMainWebView,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -120,7 +127,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
personalChannelMessageId,
|
personalChannelMessageId,
|
||||||
birthday,
|
birthday,
|
||||||
} = userFullInfo || {};
|
} = userFullInfo || {};
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
const [areNotificationsEnabled, setAreNotificationsEnabled] = useState(!isMuted);
|
const [areNotificationsEnabled, setAreNotificationsEnabled] = useState(!isMuted);
|
||||||
|
|
||||||
@ -177,7 +185,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
const { address, geo } = businessLocation!;
|
const { address, geo } = businessLocation!;
|
||||||
if (!geo) {
|
if (!geo) {
|
||||||
copyTextToClipboard(address);
|
copyTextToClipboard(address);
|
||||||
showNotification({ message: lang('BusinessLocationCopied') });
|
showNotification({ message: oldLang('BusinessLocationCopied') });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +228,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
requestCollectibleInfo({ collectible: phoneNumber, peerId: peerId!, type: 'phone' });
|
requestCollectibleInfo({ collectible: phoneNumber, peerId: peerId!, type: 'phone' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
copy(formattedNumber!, lang('Phone'));
|
copy(formattedNumber!, oldLang('Phone'));
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleUsernameClick = useLastCallback((username: ApiUsername, isChat?: boolean) => {
|
const handleUsernameClick = useLastCallback((username: ApiUsername, isChat?: boolean) => {
|
||||||
@ -228,9 +236,35 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
requestCollectibleInfo({ collectible: username.username, peerId: peerId!, type: 'username' });
|
requestCollectibleInfo({ collectible: username.username, peerId: peerId!, type: 'username' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
copy(formatUsername(username.username, isChat), lang(isChat ? 'Link' : 'Username'));
|
copy(formatUsername(username.username, isChat), oldLang(isChat ? 'Link' : 'Username'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleOpenApp = useLastCallback(() => {
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const botId = user?.id;
|
||||||
|
if (!botId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const theme = extractCurrentThemeParams();
|
||||||
|
requestMainWebView({
|
||||||
|
botId,
|
||||||
|
peerId: botId,
|
||||||
|
theme,
|
||||||
|
shouldMarkBotTrusted: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const appTermsInfo = lang('ProfileOpenAppAbout', {
|
||||||
|
terms: (
|
||||||
|
<SafeLink
|
||||||
|
text={lang('ProfileOpenAppTerms')}
|
||||||
|
url={lang('ProfileBotOpenAppInfoLink')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}, { withNodes: true });
|
||||||
|
|
||||||
if (!chat || chat.isRestricted || (isSelf && !isInSettings)) {
|
if (!chat || chat.isRestricted || (isSelf && !isInSettings)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -239,7 +273,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
const [mainUsername, ...otherUsernames] = usernameList;
|
const [mainUsername, ...otherUsernames] = usernameList;
|
||||||
|
|
||||||
const usernameLinks = otherUsernames.length
|
const usernameLinks = otherUsernames.length
|
||||||
? (lang('UsernameAlso', '%USERNAMES%') as string)
|
? (oldLang('UsernameAlso', '%USERNAMES%') as string)
|
||||||
.split('%')
|
.split('%')
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
return (s === 'USERNAMES' ? (
|
return (s === 'USERNAMES' ? (
|
||||||
@ -282,7 +316,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
<span className="title" dir="auto">{formatUsername(mainUsername.username, isChat)}</span>
|
<span className="title" dir="auto">{formatUsername(mainUsername.username, isChat)}</span>
|
||||||
<span className="subtitle">
|
<span className="subtitle">
|
||||||
{usernameLinks && <span className="other-usernames">{usernameLinks}</span>}
|
{usernameLinks && <span className="other-usernames">{usernameLinks}</span>}
|
||||||
{lang(isChat ? 'Link' : 'Username')}
|
{oldLang(isChat ? 'Link' : 'Username')}
|
||||||
</span>
|
</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
@ -292,9 +326,9 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="ChatExtra">
|
<div className="ChatExtra">
|
||||||
{personalChannel && (
|
{personalChannel && (
|
||||||
<div className={styles.personalChannel}>
|
<div className={styles.personalChannel}>
|
||||||
<h3 className={styles.personalChannelTitle}>{lang('ProfileChannel')}</h3>
|
<h3 className={styles.personalChannelTitle}>{oldLang('ProfileChannel')}</h3>
|
||||||
<span className={styles.personalChannelSubscribers}>
|
<span className={styles.personalChannelSubscribers}>
|
||||||
{lang('Subscribers', personalChannel.membersCount, 'i')}
|
{oldLang('Subscribers', personalChannel.membersCount, 'i')}
|
||||||
</span>
|
</span>
|
||||||
<Chat
|
<Chat
|
||||||
chatId={personalChannel.id}
|
chatId={personalChannel.id}
|
||||||
@ -310,7 +344,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
<ListItem icon="phone" multiline narrow ripple onClick={handlePhoneClick}>
|
<ListItem icon="phone" multiline narrow ripple onClick={handlePhoneClick}>
|
||||||
<span className="title" dir="auto">{formattedNumber}</span>
|
<span className="title" dir="auto">{formattedNumber}</span>
|
||||||
<span className="subtitle">{lang('Phone')}</span>
|
<span className="subtitle">{oldLang('Phone')}</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
{activeUsernames && renderUsernames(activeUsernames)}
|
{activeUsernames && renderUsernames(activeUsernames)}
|
||||||
@ -331,7 +365,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
<span className="subtitle">{lang(userId ? 'UserBio' : 'Info')}</span>
|
<span className="subtitle">{oldLang(userId ? 'UserBio' : 'Info')}</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
{activeChatUsernames && !isTopicInfo && renderUsernames(activeChatUsernames, true)}
|
{activeChatUsernames && !isTopicInfo && renderUsernames(activeChatUsernames, true)}
|
||||||
@ -342,18 +376,36 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
narrow
|
narrow
|
||||||
ripple
|
ripple
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => copy(link, lang('SetUrlPlaceholder'))}
|
onClick={() => copy(link, oldLang('SetUrlPlaceholder'))}
|
||||||
>
|
>
|
||||||
<div className="title">{link}</div>
|
<div className="title">{link}</div>
|
||||||
<span className="subtitle">{lang('SetUrlPlaceholder')}</span>
|
<span className="subtitle">{oldLang('SetUrlPlaceholder')}</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
{birthday && (
|
{birthday && (
|
||||||
<UserBirthday key={peerId} birthday={birthday} user={user!} isInSettings={isInSettings} />
|
<UserBirthday key={peerId} birthday={birthday} user={user!} isInSettings={isInSettings} />
|
||||||
)}
|
)}
|
||||||
|
{ hasMainMiniApp && (
|
||||||
|
<ListItem
|
||||||
|
multiline
|
||||||
|
isStatic
|
||||||
|
narrow
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={styles.openAppButton}
|
||||||
|
size="smaller"
|
||||||
|
onClick={handleOpenApp}
|
||||||
|
>
|
||||||
|
{oldLang('ProfileBotOpenApp')}
|
||||||
|
</Button>
|
||||||
|
<div className={styles.sectionInfo}>
|
||||||
|
{appTermsInfo}
|
||||||
|
</div>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
{!isInSettings && (
|
{!isInSettings && (
|
||||||
<ListItem icon="unmute" narrow ripple onClick={handleNotificationChange}>
|
<ListItem icon="unmute" narrow ripple onClick={handleNotificationChange}>
|
||||||
<span>{lang('Notifications')}</span>
|
<span>{oldLang('Notifications')}</span>
|
||||||
<Switcher
|
<Switcher
|
||||||
id="group-notifications"
|
id="group-notifications"
|
||||||
label={userId ? 'Toggle User Notifications' : 'Toggle Chat Notifications'}
|
label={userId ? 'Toggle User Notifications' : 'Toggle Chat Notifications'}
|
||||||
@ -375,12 +427,12 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
onClick={handleClickLocation}
|
onClick={handleClickLocation}
|
||||||
>
|
>
|
||||||
<div className="title">{businessLocation.address}</div>
|
<div className="title">{businessLocation.address}</div>
|
||||||
<span className="subtitle">{lang('BusinessProfileLocation')}</span>
|
<span className="subtitle">{oldLang('BusinessProfileLocation')}</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
{hasSavedMessages && !isInSettings && (
|
{hasSavedMessages && !isInSettings && (
|
||||||
<ListItem icon="saved-messages" narrow ripple onClick={handleOpenSavedDialog}>
|
<ListItem icon="saved-messages" narrow ripple onClick={handleOpenSavedDialog}>
|
||||||
<span>{lang('SavedMessagesTab')}</span>
|
<span>{oldLang('SavedMessagesTab')}</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -418,6 +470,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
? selectChat(global, userFullInfo.personalChannelId)
|
? selectChat(global, userFullInfo.personalChannelId)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const hasMainMiniApp = user?.hasMainMiniApp;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phoneCodeList,
|
phoneCodeList,
|
||||||
chat,
|
chat,
|
||||||
@ -431,6 +485,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
topicLink,
|
topicLink,
|
||||||
hasSavedMessages,
|
hasSavedMessages,
|
||||||
personalChannel,
|
personalChannel,
|
||||||
|
hasMainMiniApp,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(ChatExtra));
|
)(ChatExtra));
|
||||||
|
|||||||
@ -299,6 +299,11 @@ function getNodes(origin: MediaViewerOrigin, message?: ApiMessage, index?: numbe
|
|||||||
mediaSelector = '.full-media';
|
mediaSelector = '.full-media';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MediaViewerOrigin.PreviewMedia:
|
||||||
|
containerSelector = `#preview-media${index}`;
|
||||||
|
mediaSelector = 'img';
|
||||||
|
break;
|
||||||
|
|
||||||
case MediaViewerOrigin.SharedMedia:
|
case MediaViewerOrigin.SharedMedia:
|
||||||
containerSelector = `#shared-media${getMessageHtmlId(message!.id, index)}`;
|
containerSelector = `#shared-media${getMessageHtmlId(message!.id, index)}`;
|
||||||
mediaSelector = 'img';
|
mediaSelector = 'img';
|
||||||
@ -358,6 +363,7 @@ function applyShape(ghost: HTMLDivElement, origin: MediaViewerOrigin) {
|
|||||||
case MediaViewerOrigin.Inline:
|
case MediaViewerOrigin.Inline:
|
||||||
case MediaViewerOrigin.ScheduledInline:
|
case MediaViewerOrigin.ScheduledInline:
|
||||||
case MediaViewerOrigin.StarsTransaction:
|
case MediaViewerOrigin.StarsTransaction:
|
||||||
|
case MediaViewerOrigin.PreviewMedia:
|
||||||
ghost.classList.add('rounded-corners');
|
ghost.classList.add('rounded-corners');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -71,7 +71,8 @@
|
|||||||
|
|
||||||
&.storiesArchive-list,
|
&.storiesArchive-list,
|
||||||
&.stories-list,
|
&.stories-list,
|
||||||
&.media-list {
|
&.media-list,
|
||||||
|
&.previewMedia-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ApiBotPreviewMedia,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiChatMember,
|
ApiChatMember,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
@ -52,6 +53,7 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectTheme,
|
selectTheme,
|
||||||
selectUser,
|
selectUser,
|
||||||
|
selectUserFullInfo,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { selectPremiumLimit } from '../../global/selectors/limits';
|
import { selectPremiumLimit } from '../../global/selectors/limits';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -77,6 +79,7 @@ import Document from '../common/Document';
|
|||||||
import GroupChatInfo from '../common/GroupChatInfo';
|
import GroupChatInfo from '../common/GroupChatInfo';
|
||||||
import Media from '../common/Media';
|
import Media from '../common/Media';
|
||||||
import NothingFound from '../common/NothingFound';
|
import NothingFound from '../common/NothingFound';
|
||||||
|
import PreviewMedia from '../common/PreviewMedia';
|
||||||
import PrivateChatInfo from '../common/PrivateChatInfo';
|
import PrivateChatInfo from '../common/PrivateChatInfo';
|
||||||
import ChatExtra from '../common/profile/ChatExtra';
|
import ChatExtra from '../common/profile/ChatExtra';
|
||||||
import ProfileInfo from '../common/ProfileInfo';
|
import ProfileInfo from '../common/ProfileInfo';
|
||||||
@ -113,6 +116,7 @@ type StateProps = {
|
|||||||
hasCommonChatsTab?: boolean;
|
hasCommonChatsTab?: boolean;
|
||||||
hasStoriesTab?: boolean;
|
hasStoriesTab?: boolean;
|
||||||
hasMembersTab?: boolean;
|
hasMembersTab?: boolean;
|
||||||
|
hasPreviewMediaTab?: boolean;
|
||||||
areMembersHidden?: boolean;
|
areMembersHidden?: boolean;
|
||||||
canAddMembers?: boolean;
|
canAddMembers?: boolean;
|
||||||
canDeleteMembers?: boolean;
|
canDeleteMembers?: boolean;
|
||||||
@ -133,6 +137,7 @@ type StateProps = {
|
|||||||
nextProfileTab?: ProfileTabType;
|
nextProfileTab?: ProfileTabType;
|
||||||
shouldWarnAboutSvg?: boolean;
|
shouldWarnAboutSvg?: boolean;
|
||||||
similarChannels?: string[];
|
similarChannels?: string[];
|
||||||
|
botPreviewMedia? : ApiBotPreviewMedia[];
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
limitSimilarChannels: number;
|
limitSimilarChannels: number;
|
||||||
isTopicInfo?: boolean;
|
isTopicInfo?: boolean;
|
||||||
@ -174,6 +179,8 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
hasCommonChatsTab,
|
hasCommonChatsTab,
|
||||||
hasStoriesTab,
|
hasStoriesTab,
|
||||||
hasMembersTab,
|
hasMembersTab,
|
||||||
|
hasPreviewMediaTab,
|
||||||
|
botPreviewMedia,
|
||||||
areMembersHidden,
|
areMembersHidden,
|
||||||
canAddMembers,
|
canAddMembers,
|
||||||
canDeleteMembers,
|
canDeleteMembers,
|
||||||
@ -210,6 +217,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
loadStoriesArchive,
|
loadStoriesArchive,
|
||||||
openPremiumModal,
|
openPremiumModal,
|
||||||
loadChannelRecommendations,
|
loadChannelRecommendations,
|
||||||
|
loadPreviewMedias,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -229,6 +237,9 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
...(hasMembersTab ? [{
|
...(hasMembersTab ? [{
|
||||||
type: 'members' as const, title: isChannel ? 'ChannelSubscribers' : 'GroupMembers',
|
type: 'members' as const, title: isChannel ? 'ChannelSubscribers' : 'GroupMembers',
|
||||||
}] : []),
|
}] : []),
|
||||||
|
...(hasPreviewMediaTab ? [{
|
||||||
|
type: 'previewMedia' as const, title: 'ProfileBotPreviewTab',
|
||||||
|
}] : []),
|
||||||
...TABS,
|
...TABS,
|
||||||
// TODO The filter for voice messages currently does not work
|
// TODO The filter for voice messages currently does not work
|
||||||
// in forum topics. Return it when it's fixed on the server side.
|
// in forum topics. Return it when it's fixed on the server side.
|
||||||
@ -240,6 +251,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
]), [
|
]), [
|
||||||
hasCommonChatsTab,
|
hasCommonChatsTab,
|
||||||
hasMembersTab,
|
hasMembersTab,
|
||||||
|
hasPreviewMediaTab,
|
||||||
hasStoriesTab,
|
hasStoriesTab,
|
||||||
isChannel,
|
isChannel,
|
||||||
isTopicInfo,
|
isTopicInfo,
|
||||||
@ -274,6 +286,12 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
setActiveTab(index);
|
setActiveTab(index);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasPreviewMediaTab && !botPreviewMedia) {
|
||||||
|
loadPreviewMedias({ botId: chatId });
|
||||||
|
}
|
||||||
|
}, [chatId, botPreviewMedia, hasPreviewMediaTab]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isChannel && !similarChannels) {
|
if (isChannel && !similarChannels) {
|
||||||
loadChannelRecommendations({ chatId });
|
loadChannelRecommendations({ chatId });
|
||||||
@ -364,6 +382,15 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSelectPreviewMedia = useLastCallback((index: number) => {
|
||||||
|
openMediaViewer({
|
||||||
|
standaloneMedia: botPreviewMedia?.flatMap((item) => item?.content.photo
|
||||||
|
|| item?.content.video).filter(Boolean),
|
||||||
|
origin: MediaViewerOrigin.PreviewMedia,
|
||||||
|
mediaIndex: index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const handlePlayAudio = useLastCallback((messageId: number) => {
|
const handlePlayAudio = useLastCallback((messageId: number) => {
|
||||||
openAudioPlayer({ chatId: profileId, messageId });
|
openAudioPlayer({ chatId: profileId, messageId });
|
||||||
});
|
});
|
||||||
@ -416,7 +443,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
if (isFirstTab) {
|
if (isFirstTab) {
|
||||||
renderingDelay = !isRightColumnShown ? HIDDEN_RENDER_DELAY : 0;
|
renderingDelay = !isRightColumnShown ? HIDDEN_RENDER_DELAY : 0;
|
||||||
// @optimization Used to delay first render of secondary tabs while animating
|
// @optimization Used to delay first render of secondary tabs while animating
|
||||||
} else if (!viewportIds) {
|
} else if (!viewportIds && !botPreviewMedia) {
|
||||||
renderingDelay = SLIDE_TRANSITION_DURATION;
|
renderingDelay = SLIDE_TRANSITION_DURATION;
|
||||||
}
|
}
|
||||||
const canRenderContent = useAsyncRendering([chatId, threadId, resultType, renderingActiveTab], renderingDelay);
|
const canRenderContent = useAsyncRendering([chatId, threadId, resultType, renderingActiveTab], renderingDelay);
|
||||||
@ -438,7 +465,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!viewportIds || !canRenderContent || !messagesById) {
|
if ((!viewportIds && !botPreviewMedia) || !canRenderContent || !messagesById) {
|
||||||
const noSpinner = isFirstTab && !canRenderContent;
|
const noSpinner = isFirstTab && !canRenderContent;
|
||||||
const forceRenderHiddenMembers = Boolean(resultType === 'members' && areMembersHidden);
|
const forceRenderHiddenMembers = Boolean(resultType === 'members' && areMembersHidden);
|
||||||
|
|
||||||
@ -450,7 +477,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!viewportIds.length) {
|
if (viewportIds && !viewportIds?.length) {
|
||||||
let text: string;
|
let text: string;
|
||||||
|
|
||||||
switch (resultType) {
|
switch (resultType) {
|
||||||
@ -595,6 +622,17 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
<GroupChatInfo chatId={id} />
|
<GroupChatInfo chatId={id} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))
|
))
|
||||||
|
) : resultType === 'previewMedia' ? (
|
||||||
|
botPreviewMedia!.map((media, i) => (
|
||||||
|
<PreviewMedia
|
||||||
|
key={media.date}
|
||||||
|
media={media}
|
||||||
|
isProtected={isChatProtected}
|
||||||
|
observeIntersection={observeIntersectionForMedia}
|
||||||
|
onClick={handleSelectPreviewMedia}
|
||||||
|
index={i}
|
||||||
|
/>
|
||||||
|
))
|
||||||
) : resultType === 'similarChannels' ? (
|
) : resultType === 'similarChannels' ? (
|
||||||
<div key={resultType}>
|
<div key={resultType}>
|
||||||
{(viewportIds as string[])!.map((channelId, i) => (
|
{(viewportIds as string[])!.map((channelId, i) => (
|
||||||
@ -739,6 +777,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const peer = user || chat;
|
const peer = user || chat;
|
||||||
const peerFullInfo = selectPeerFullInfo(global, chatId);
|
const peerFullInfo = selectPeerFullInfo(global, chatId);
|
||||||
|
|
||||||
|
const userFullInfo = selectUserFullInfo(global, chatId);
|
||||||
|
const hasPreviewMediaTab = userFullInfo?.botInfo?.hasPreviewMedia;
|
||||||
|
const botPreviewMedia = global.users.previewMediaByBotId[chatId];
|
||||||
|
|
||||||
const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories))
|
const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories))
|
||||||
&& !isSavedDialog;
|
&& !isSavedDialog;
|
||||||
const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined;
|
const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined;
|
||||||
@ -757,6 +800,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
hasCommonChatsTab,
|
hasCommonChatsTab,
|
||||||
hasStoriesTab,
|
hasStoriesTab,
|
||||||
hasMembersTab,
|
hasMembersTab,
|
||||||
|
hasPreviewMediaTab,
|
||||||
areMembersHidden,
|
areMembersHidden,
|
||||||
canAddMembers,
|
canAddMembers,
|
||||||
canDeleteMembers,
|
canDeleteMembers,
|
||||||
@ -776,6 +820,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
forceScrollProfileTab: selectTabState(global).forceScrollProfileTab,
|
forceScrollProfileTab: selectTabState(global).forceScrollProfileTab,
|
||||||
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
||||||
similarChannels: similarChannelIds,
|
similarChannels: similarChannelIds,
|
||||||
|
botPreviewMedia,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
isTopicInfo,
|
isTopicInfo,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
|
|||||||
@ -612,6 +612,88 @@ addActionHandler('requestWebView', async (global, actions, payload): Promise<voi
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('requestMainWebView', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
botId, peerId, theme, startParam, shouldMarkBotTrusted,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const bot = selectUser(global, botId);
|
||||||
|
if (!bot) return;
|
||||||
|
const peer = selectPeer(global, peerId);
|
||||||
|
if (!peer) return;
|
||||||
|
|
||||||
|
if (!selectIsTrustedBot(global, botId)) {
|
||||||
|
if (shouldMarkBotTrusted) {
|
||||||
|
actions.markBotTrusted({ botId, isWriteAllowed: true, tabId });
|
||||||
|
} else {
|
||||||
|
global = updateTabState(global, {
|
||||||
|
botTrustRequest: {
|
||||||
|
botId,
|
||||||
|
type: 'webApp',
|
||||||
|
onConfirm: {
|
||||||
|
action: 'requestMainWebView',
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await callApi('requestMainWebView', {
|
||||||
|
bot,
|
||||||
|
peer,
|
||||||
|
theme,
|
||||||
|
startParam,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { url: webViewUrl, queryId } = result;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
webApp: {
|
||||||
|
url: webViewUrl,
|
||||||
|
botId,
|
||||||
|
queryId,
|
||||||
|
buttonText: '',
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadPreviewMedias', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
botId,
|
||||||
|
} = payload;
|
||||||
|
const bot = selectUser(global, botId);
|
||||||
|
if (!bot) return;
|
||||||
|
|
||||||
|
const medias = await callApi('fetchPreviewMedias', {
|
||||||
|
bot,
|
||||||
|
});
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
if (medias) {
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
users: {
|
||||||
|
...global.users,
|
||||||
|
previewMediaByBotId: {
|
||||||
|
...global.users.previewMediaByBotId,
|
||||||
|
[botId]: medias,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('requestAppWebView', async (global, actions, payload): Promise<void> => {
|
addActionHandler('requestAppWebView', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
botId, appName, startApp, theme, isWriteAllowed, isFromConfirm,
|
botId, appName, startApp, theme, isWriteAllowed, isFromConfirm,
|
||||||
|
|||||||
@ -1485,6 +1485,20 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (startApp !== undefined && !webAppName) {
|
||||||
|
const theme = extractCurrentThemeParams();
|
||||||
|
const chatByUsername = await fetchChatByUsername(global, username);
|
||||||
|
global = getGlobal();
|
||||||
|
const user = chatByUsername && selectUser(global, chatByUsername.id);
|
||||||
|
if (!chatByUsername || !chat || !user?.hasMainMiniApp) return;
|
||||||
|
actions.requestMainWebView({
|
||||||
|
botId: chatByUsername.id,
|
||||||
|
peerId: chat.id,
|
||||||
|
theme,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!isWebApp) {
|
if (!isWebApp) {
|
||||||
await openChatByUsername(
|
await openChatByUsername(
|
||||||
global, actions, {
|
global, actions, {
|
||||||
|
|||||||
@ -253,6 +253,9 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
|
|||||||
cached.quickReplies = initialState.quickReplies;
|
cached.quickReplies = initialState.quickReplies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cached.users.previewMediaByBotId) {
|
||||||
|
cached.users.previewMediaByBotId = initialState.users.previewMediaByBotId;
|
||||||
|
}
|
||||||
if (!cached.chats.loadingParameters) {
|
if (!cached.chats.loadingParameters) {
|
||||||
cached.chats.loadingParameters = initialState.chats.loadingParameters;
|
cached.chats.loadingParameters = initialState.chats.loadingParameters;
|
||||||
}
|
}
|
||||||
@ -366,7 +369,11 @@ function reduceCustomEmojis<T extends GlobalState>(global: T): GlobalState['cust
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reduceUsers<T extends GlobalState>(global: T): GlobalState['users'] {
|
function reduceUsers<T extends GlobalState>(global: T): GlobalState['users'] {
|
||||||
const { users: { byId, statusesById, fullInfoById }, currentUserId } = global;
|
const {
|
||||||
|
users: {
|
||||||
|
byId, statusesById, fullInfoById,
|
||||||
|
}, currentUserId,
|
||||||
|
} = global;
|
||||||
const currentChatIds = compact(
|
const currentChatIds = compact(
|
||||||
Object.values(global.byTabId)
|
Object.values(global.byTabId)
|
||||||
.map(({ id: tabId }) => selectCurrentMessageList(global, tabId)),
|
.map(({ id: tabId }) => selectCurrentMessageList(global, tabId)),
|
||||||
@ -400,6 +407,7 @@ function reduceUsers<T extends GlobalState>(global: T): GlobalState['users'] {
|
|||||||
byId: pick(byId, idsToSave),
|
byId: pick(byId, idsToSave),
|
||||||
statusesById: pick(statusesById, idsToSave),
|
statusesById: pick(statusesById, idsToSave),
|
||||||
fullInfoById: pick(fullInfoById, idsToSave),
|
fullInfoById: pick(fullInfoById, idsToSave),
|
||||||
|
previewMediaByBotId: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -97,6 +97,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
byId: {},
|
byId: {},
|
||||||
statusesById: {},
|
statusesById: {},
|
||||||
fullInfoById: {},
|
fullInfoById: {},
|
||||||
|
previewMediaByBotId: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
chats: {
|
chats: {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type {
|
|||||||
ApiAvailableReaction,
|
ApiAvailableReaction,
|
||||||
ApiBoost,
|
ApiBoost,
|
||||||
ApiBoostsStatus,
|
ApiBoostsStatus,
|
||||||
|
ApiBotPreviewMedia,
|
||||||
ApiChannelMonetizationStatistics,
|
ApiChannelMonetizationStatistics,
|
||||||
ApiChannelStatistics,
|
ApiChannelStatistics,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
@ -925,6 +926,7 @@ export type GlobalState = {
|
|||||||
statusesById: Record<string, ApiUserStatus>;
|
statusesById: Record<string, ApiUserStatus>;
|
||||||
// Obtained from GetFullUser / UserFullInfo
|
// Obtained from GetFullUser / UserFullInfo
|
||||||
fullInfoById: Record<string, ApiUserFullInfo>;
|
fullInfoById: Record<string, ApiUserFullInfo>;
|
||||||
|
previewMediaByBotId: Record<string, ApiBotPreviewMedia[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
chats: {
|
chats: {
|
||||||
@ -2873,6 +2875,13 @@ export interface ActionPayloads {
|
|||||||
isFromBotMenu?: boolean;
|
isFromBotMenu?: boolean;
|
||||||
startParam?: string;
|
startParam?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
requestMainWebView: {
|
||||||
|
botId: string;
|
||||||
|
peerId: string;
|
||||||
|
theme?: ApiThemeParameters;
|
||||||
|
startParam?: string;
|
||||||
|
shouldMarkBotTrusted?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
prolongWebView: {
|
prolongWebView: {
|
||||||
botId: string;
|
botId: string;
|
||||||
peerId: string;
|
peerId: string;
|
||||||
@ -2898,6 +2907,9 @@ export interface ActionPayloads {
|
|||||||
isWriteAllowed?: boolean;
|
isWriteAllowed?: boolean;
|
||||||
isFromConfirm?: boolean;
|
isFromConfirm?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
loadPreviewMedias: {
|
||||||
|
botId: string;
|
||||||
|
};
|
||||||
setWebAppPaymentSlug: {
|
setWebAppPaymentSlug: {
|
||||||
slug?: string;
|
slug?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|||||||
@ -1547,6 +1547,7 @@ messages.getQuickReplyMessages#94a495c3 flags:# shortcut_id:int id:flags.0?Vecto
|
|||||||
messages.sendQuickReplyMessages#6c750de1 peer:InputPeer shortcut_id:int id:Vector<int> random_id:Vector<long> = Updates;
|
messages.sendQuickReplyMessages#6c750de1 peer:InputPeer shortcut_id:int id:Vector<int> random_id:Vector<long> = Updates;
|
||||||
messages.getAvailableEffects#dea20a39 hash:int = messages.AvailableEffects;
|
messages.getAvailableEffects#dea20a39 hash:int = messages.AvailableEffects;
|
||||||
messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector<int> = Vector<FactCheck>;
|
messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector<int> = Vector<FactCheck>;
|
||||||
|
messages.requestMainWebView#c9e01e7b flags:# compact:flags.7?true peer:InputPeer bot:InputUser start_param:flags.1?string theme_params:flags.0?DataJSON platform:string = WebViewResult;
|
||||||
updates.getState#edd4882a = updates.State;
|
updates.getState#edd4882a = updates.State;
|
||||||
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
||||||
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
||||||
@ -1617,6 +1618,7 @@ bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:fla
|
|||||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||||
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
||||||
|
bots.getPreviewMedias#a2a5594d bot:InputUser = Vector<BotPreviewMedia>;
|
||||||
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
||||||
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
||||||
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||||
|
|||||||
@ -175,6 +175,7 @@
|
|||||||
"messages.getQuickReplyMessages",
|
"messages.getQuickReplyMessages",
|
||||||
"messages.sendQuickReplyMessages",
|
"messages.sendQuickReplyMessages",
|
||||||
"messages.getFactCheck",
|
"messages.getFactCheck",
|
||||||
|
"messages.requestMainWebView",
|
||||||
"updates.getState",
|
"updates.getState",
|
||||||
"updates.getDifference",
|
"updates.getDifference",
|
||||||
"updates.getChannelDifference",
|
"updates.getChannelDifference",
|
||||||
@ -230,6 +231,7 @@
|
|||||||
"bots.invokeWebViewCustomMethod",
|
"bots.invokeWebViewCustomMethod",
|
||||||
"bots.getPopularAppBots",
|
"bots.getPopularAppBots",
|
||||||
"bots.setBotInfo",
|
"bots.setBotInfo",
|
||||||
|
"bots.getPreviewMedias",
|
||||||
"payments.getPaymentForm",
|
"payments.getPaymentForm",
|
||||||
"payments.getPaymentReceipt",
|
"payments.getPaymentReceipt",
|
||||||
"payments.validateRequestedInfo",
|
"payments.validateRequestedInfo",
|
||||||
|
|||||||
@ -329,6 +329,7 @@ export enum MediaViewerOrigin {
|
|||||||
SearchResult,
|
SearchResult,
|
||||||
SuggestedAvatar,
|
SuggestedAvatar,
|
||||||
StarsTransaction,
|
StarsTransaction,
|
||||||
|
PreviewMedia,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum StoryViewerOrigin {
|
export enum StoryViewerOrigin {
|
||||||
@ -392,6 +393,7 @@ export type ProfileTabType =
|
|||||||
| 'members'
|
| 'members'
|
||||||
| 'commonChats'
|
| 'commonChats'
|
||||||
| 'media'
|
| 'media'
|
||||||
|
| 'previewMedia'
|
||||||
| 'documents'
|
| 'documents'
|
||||||
| 'links'
|
| 'links'
|
||||||
| 'audio'
|
| 'audio'
|
||||||
|
|||||||
5
src/types/language.d.ts
vendored
5
src/types/language.d.ts
vendored
@ -1512,6 +1512,11 @@ export interface LangPair {
|
|||||||
'MenuInstallApp': undefined;
|
'MenuInstallApp': undefined;
|
||||||
'RemoveEffect': undefined;
|
'RemoveEffect': undefined;
|
||||||
'ReplyInPrivateMessage': undefined;
|
'ReplyInPrivateMessage': undefined;
|
||||||
|
'ProfileOpenAppAbout': {
|
||||||
|
'terms': string;
|
||||||
|
};
|
||||||
|
'ProfileOpenAppTerms': undefined;
|
||||||
|
'ProfileBotOpenAppInfoLink': undefined;
|
||||||
'MonetizationInfoTONTitle': undefined;
|
'MonetizationInfoTONTitle': undefined;
|
||||||
'ChannelEarnLearnCoinAbout': {
|
'ChannelEarnLearnCoinAbout': {
|
||||||
'link': string | number;
|
'link': string | number;
|
||||||
|
|||||||
@ -511,4 +511,6 @@ export default {
|
|||||||
SlowModeWait: 'Slow Mode — %d',
|
SlowModeWait: 'Slow Mode — %d',
|
||||||
OpenMapWith: 'Open map with...',
|
OpenMapWith: 'Open map with...',
|
||||||
FullDateTimeFormat: '%@, %@',
|
FullDateTimeFormat: '%@, %@',
|
||||||
|
ProfileOpenAppTerms: 'Terms of Service for Mini Apps',
|
||||||
|
ProfileBotOpenAppInfoLink: 'https://telegram.org/tos/mini-apps',
|
||||||
} as ApiOldLangPack;
|
} as ApiOldLangPack;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user