Introduce Telegram Premium, public chat join requests (#1912)
@ -12,6 +12,15 @@
|
||||
"./dev/wholePixel.js"
|
||||
],
|
||||
"rules": {
|
||||
"property-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreProperties": [
|
||||
"composes",
|
||||
"compose-with"
|
||||
]
|
||||
}
|
||||
],
|
||||
"number-leading-zero": "always",
|
||||
"selector-attribute-quotes": "always",
|
||||
"scss/operator-no-unspaced": null,
|
||||
|
||||
@ -3,9 +3,18 @@ import BigInt from 'big-integer';
|
||||
import localDb from '../localDb';
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
import type { ApiAppConfig } from '../../types';
|
||||
import type { ApiLimitType } from '../../../global/types';
|
||||
import { buildJson } from './misc';
|
||||
import { DEFAULT_LIMITS } from '../../../config';
|
||||
|
||||
type GramJsAppConfig = {
|
||||
type LimitType = 'default' | 'premium';
|
||||
type Limit = 'upload_max_fileparts' | 'stickers_faved_limit' | 'saved_gifs_limit' | 'dialog_filters_chats_limit' |
|
||||
'dialog_filters_limit' | 'dialogs_folder_pinned_limit' | 'dialogs_pinned_limit' | 'caption_length_limit' |
|
||||
'channels_limit' | 'channels_public_limit' | 'about_length_limit';
|
||||
type LimitKey = `${Limit}_${LimitType}`;
|
||||
type LimitsConfig = Record<LimitKey, number>;
|
||||
|
||||
interface GramJsAppConfig extends LimitsConfig {
|
||||
emojies_sounds: Record<string, {
|
||||
id: string;
|
||||
access_hash: string;
|
||||
@ -20,7 +29,11 @@ type GramJsAppConfig = {
|
||||
autologin_domains: string[];
|
||||
autologin_token: string;
|
||||
url_auth_domains: string[];
|
||||
};
|
||||
premium_purchase_blocked: boolean;
|
||||
premium_bot_username: string;
|
||||
premium_invoice_slug: string;
|
||||
premium_promo_order: string[];
|
||||
}
|
||||
|
||||
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||
const { emojies_sounds } = appConfig;
|
||||
@ -41,6 +54,12 @@ function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||
}, {}) : {};
|
||||
}
|
||||
|
||||
function getLimit(appConfig: GramJsAppConfig, key: Limit, fallbackKey: ApiLimitType) {
|
||||
const defaultLimit = appConfig[`${key}_default`] || DEFAULT_LIMITS[fallbackKey][0];
|
||||
const premiumLimit = appConfig[`${key}_premium`] || DEFAULT_LIMITS[fallbackKey][1];
|
||||
return [defaultLimit, premiumLimit] as const;
|
||||
}
|
||||
|
||||
export function buildAppConfig(json: GramJs.TypeJSONValue): ApiAppConfig {
|
||||
const appConfig = buildJson(json) as GramJsAppConfig;
|
||||
|
||||
@ -52,5 +71,20 @@ export function buildAppConfig(json: GramJs.TypeJSONValue): ApiAppConfig {
|
||||
autologinDomains: appConfig.autologin_domains || [],
|
||||
autologinToken: appConfig.autologin_token || '',
|
||||
urlAuthDomains: appConfig.url_auth_domains || [],
|
||||
premiumBotUsername: appConfig.premium_bot_username,
|
||||
premiumInvoiceSlug: appConfig.premium_invoice_slug,
|
||||
isPremiumPurchaseBlocked: appConfig.premium_purchase_blocked,
|
||||
limits: {
|
||||
uploadMaxFileparts: getLimit(appConfig, 'upload_max_fileparts', 'uploadMaxFileparts'),
|
||||
stickersFaved: getLimit(appConfig, 'stickers_faved_limit', 'stickersFaved'),
|
||||
savedGifs: getLimit(appConfig, 'saved_gifs_limit', 'savedGifs'),
|
||||
dialogFiltersChats: getLimit(appConfig, 'dialog_filters_chats_limit', 'dialogFiltersChats'),
|
||||
dialogFilters: getLimit(appConfig, 'dialog_filters_limit', 'dialogFilters'),
|
||||
dialogFolderPinned: getLimit(appConfig, 'dialogs_pinned_limit', 'dialogFolderPinned'),
|
||||
captionLength: getLimit(appConfig, 'caption_length_limit', 'captionLength'),
|
||||
channels: getLimit(appConfig, 'channels_limit', 'channels'),
|
||||
channelsPublic: getLimit(appConfig, 'channels_public_limit', 'channelsPublic'),
|
||||
aboutLength: getLimit(appConfig, 'about_length_limit', 'aboutLength'),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
||||
import type {
|
||||
ApiAttachMenuBot,
|
||||
ApiAttachMenuBotIcon,
|
||||
ApiAttachMenuPeerType,
|
||||
ApiBotCommand,
|
||||
ApiBotInfo,
|
||||
ApiBotInlineMediaResult,
|
||||
@ -9,12 +10,11 @@ import type {
|
||||
ApiBotInlineSwitchPm,
|
||||
ApiBotMenuButton,
|
||||
ApiInlineResultType,
|
||||
ApiWebDocument,
|
||||
} from '../../types';
|
||||
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import { buildApiPhoto, buildApiThumbnailFromStripped } from './common';
|
||||
import { buildApiDocument, buildVideoFromDocument } from './messages';
|
||||
import { buildApiDocument, buildApiWebDocument, buildVideoFromDocument } from './messages';
|
||||
import { buildStickerFromDocument } from './symbols';
|
||||
import localDb from '../localDb';
|
||||
import { buildApiPeerId } from './peers';
|
||||
@ -65,11 +65,22 @@ export function buildBotSwitchPm(switchPm?: GramJs.InlineBotSwitchPM) {
|
||||
export function buildApiAttachMenuBot(bot: GramJs.AttachMenuBot): ApiAttachMenuBot {
|
||||
return {
|
||||
id: bot.botId.toString(),
|
||||
hasSettings: bot.hasSettings,
|
||||
shortName: bot.shortName,
|
||||
peerTypes: bot.peerTypes.map(buildApiAttachMenuPeerType),
|
||||
icons: bot.icons.map(buildApiAttachMenuIcon).filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
function buildApiAttachMenuPeerType(peerType: GramJs.TypeAttachMenuPeerType): ApiAttachMenuPeerType {
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBotPM) return 'bot';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypePM) return 'private';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeChat) return 'chat';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBroadcast) return 'channel';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeSameBotPM) return 'self';
|
||||
return undefined!; // Never reached
|
||||
}
|
||||
|
||||
function buildApiAttachMenuIcon(icon: GramJs.AttachMenuBotIcon): ApiAttachMenuBotIcon | undefined {
|
||||
if (!(icon.icon instanceof GramJs.Document)) return undefined;
|
||||
|
||||
@ -85,23 +96,24 @@ function buildApiAttachMenuIcon(icon: GramJs.AttachMenuBotIcon): ApiAttachMenuBo
|
||||
};
|
||||
}
|
||||
|
||||
function buildApiWebDocument(document?: GramJs.TypeWebDocument): ApiWebDocument | undefined {
|
||||
return document ? pick(document, ['url', 'mimeType']) : undefined;
|
||||
}
|
||||
|
||||
export function buildApiBotInfo(botInfo: GramJs.BotInfo): ApiBotInfo {
|
||||
export function buildApiBotInfo(botInfo: GramJs.BotInfo, chatId: string): ApiBotInfo {
|
||||
const {
|
||||
description, userId, commands, menuButton,
|
||||
description, descriptionPhoto, descriptionDocument, userId, commands, menuButton,
|
||||
} = botInfo;
|
||||
|
||||
const botId = buildApiPeerId(userId, 'user');
|
||||
const commandsArray = commands.map((command) => buildApiBotCommand(botId, command));
|
||||
const botId = userId && buildApiPeerId(userId, 'user');
|
||||
const photo = descriptionPhoto instanceof GramJs.Photo ? buildApiPhoto(descriptionPhoto) : undefined;
|
||||
const gif = descriptionDocument instanceof GramJs.Document ? buildVideoFromDocument(descriptionDocument) : undefined;
|
||||
|
||||
const commandsArray = commands?.map((command) => buildApiBotCommand(botId || chatId, command));
|
||||
|
||||
return {
|
||||
botId,
|
||||
botId: botId || chatId,
|
||||
description,
|
||||
gif,
|
||||
photo,
|
||||
menuButton: buildApiBotMenuButton(menuButton),
|
||||
commands: commandsArray.length ? commandsArray : undefined,
|
||||
commands: commandsArray?.length ? commandsArray : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -112,7 +124,7 @@ function buildApiBotCommand(botId: string, command: GramJs.BotCommand): ApiBotCo
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiBotMenuButton(menuButton: GramJs.TypeBotMenuButton): ApiBotMenuButton {
|
||||
export function buildApiBotMenuButton(menuButton?: GramJs.TypeBotMenuButton): ApiBotMenuButton {
|
||||
if (menuButton instanceof GramJs.BotMenuButton) {
|
||||
return {
|
||||
type: 'webApp',
|
||||
|
||||
@ -31,17 +31,22 @@ function buildApiChatFieldsFromPeerEntity(
|
||||
): PeerEntityApiChatFields {
|
||||
const isMin = Boolean('min' in peerEntity && peerEntity.min);
|
||||
const accessHash = ('accessHash' in peerEntity) && String(peerEntity.accessHash);
|
||||
const hasVideoAvatar = 'photo' in peerEntity && peerEntity.photo && 'hasVideo' in peerEntity.photo
|
||||
&& peerEntity.photo.hasVideo;
|
||||
const avatarHash = ('photo' in peerEntity) && peerEntity.photo && buildAvatarHash(peerEntity.photo);
|
||||
const isSignaturesShown = Boolean('signatures' in peerEntity && peerEntity.signatures);
|
||||
const hasPrivateLink = Boolean('hasLink' in peerEntity && peerEntity.hasLink);
|
||||
const isScam = Boolean('scam' in peerEntity && peerEntity.scam);
|
||||
const isFake = Boolean('fake' in peerEntity && peerEntity.fake);
|
||||
const isJoinToSend = Boolean('joinToSend' in peerEntity && peerEntity.joinToSend);
|
||||
const isJoinRequest = Boolean('joinRequest' in peerEntity && peerEntity.joinRequest);
|
||||
|
||||
return {
|
||||
isMin,
|
||||
hasPrivateLink,
|
||||
isSignaturesShown,
|
||||
...(accessHash && { accessHash }),
|
||||
hasVideoAvatar,
|
||||
...(avatarHash && { avatarHash }),
|
||||
...(
|
||||
(peerEntity instanceof GramJs.Channel || peerEntity instanceof GramJs.User)
|
||||
@ -63,6 +68,8 @@ function buildApiChatFieldsFromPeerEntity(
|
||||
...buildApiChatRestrictions(peerEntity),
|
||||
...buildApiChatMigrationInfo(peerEntity),
|
||||
fakeType: isScam ? 'scam' : (isFake ? 'fake' : undefined),
|
||||
isJoinToSend,
|
||||
isJoinRequest,
|
||||
};
|
||||
}
|
||||
|
||||
@ -379,9 +386,10 @@ export function buildApiChatFolder(filter: GramJs.DialogFilter): ApiChatFolder {
|
||||
export function buildApiChatFolderFromSuggested({
|
||||
filter, description,
|
||||
}: {
|
||||
filter: GramJs.DialogFilter;
|
||||
filter: GramJs.TypeDialogFilter;
|
||||
description: string;
|
||||
}): ApiChatFolder {
|
||||
}): ApiChatFolder | undefined {
|
||||
if (!(filter instanceof GramJs.DialogFilter)) return undefined;
|
||||
return {
|
||||
...buildApiChatFolder(filter),
|
||||
description,
|
||||
@ -390,12 +398,14 @@ export function buildApiChatFolderFromSuggested({
|
||||
|
||||
export function buildApiChatBotCommands(botInfos: GramJs.BotInfo[]) {
|
||||
return botInfos.reduce((botCommands, botInfo) => {
|
||||
const botId = buildApiPeerId(botInfo.userId, 'user');
|
||||
const botId = buildApiPeerId(botInfo.userId!, 'user');
|
||||
|
||||
botCommands = botCommands.concat(botInfo.commands.map((mtpCommand) => ({
|
||||
botId,
|
||||
...omitVirtualClassFields(mtpCommand),
|
||||
})));
|
||||
if (botInfo.commands) {
|
||||
botCommands = botCommands.concat(botInfo.commands.map((mtpCommand) => ({
|
||||
botId,
|
||||
...omitVirtualClassFields(mtpCommand),
|
||||
})));
|
||||
}
|
||||
|
||||
return botCommands;
|
||||
}, [] as ApiBotCommand[]);
|
||||
|
||||
@ -2,7 +2,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
||||
import { strippedPhotoToJpg } from '../../../lib/gramjs/Utils';
|
||||
|
||||
import type {
|
||||
ApiPhoto, ApiPhotoSize, ApiThumbnail,
|
||||
ApiPhoto, ApiPhotoSize, ApiThumbnail, ApiVideoSize,
|
||||
} from '../../types';
|
||||
import { bytesToDataUri } from './helpers';
|
||||
import { pathBytesToSvg } from './pathBytesToSvg';
|
||||
@ -73,6 +73,21 @@ export function buildApiPhoto(photo: GramJs.Photo): ApiPhoto {
|
||||
id: String(photo.id),
|
||||
thumbnail: buildApiThumbnailFromStripped(photo.sizes),
|
||||
sizes,
|
||||
...(photo.videoSizes && { videoSizes: photo.videoSizes.map(buildApiVideoSize), isVideo: true }),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiVideoSize(videoSize: GramJs.VideoSize): ApiVideoSize {
|
||||
const {
|
||||
videoStartTs, size, h, w, type,
|
||||
} = videoSize;
|
||||
|
||||
return {
|
||||
videoStartTs,
|
||||
size,
|
||||
height: h,
|
||||
width: w,
|
||||
type: type as ('u' | 'v'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ import type {
|
||||
ApiLocation,
|
||||
ApiGame,
|
||||
PhoneCallAction,
|
||||
ApiWebDocument,
|
||||
} from '../../types';
|
||||
|
||||
import {
|
||||
@ -66,7 +67,7 @@ export function setMessageBuilderCurrentUserId(_currentUserId: string) {
|
||||
|
||||
export function buildApiSponsoredMessage(mtpMessage: GramJs.SponsoredMessage): ApiSponsoredMessage | undefined {
|
||||
const {
|
||||
fromId, message, entities, startParam, channelPost, chatInvite, chatInviteHash, randomId,
|
||||
fromId, message, entities, startParam, channelPost, chatInvite, chatInviteHash, randomId, recommended,
|
||||
} = mtpMessage;
|
||||
const chatId = fromId ? getApiChatIdFromMtpPeer(fromId) : undefined;
|
||||
const chatInviteTitle = chatInvite
|
||||
@ -80,6 +81,7 @@ export function buildApiSponsoredMessage(mtpMessage: GramJs.SponsoredMessage): A
|
||||
isBot: fromId ? isPeerUser(fromId) : false,
|
||||
text: buildMessageTextContent(message, entities),
|
||||
expiresAt: Math.round(Date.now() / 1000) + SPONSORED_MESSAGE_CACHE_MS,
|
||||
isRecommended: Boolean(recommended),
|
||||
...(chatId && { chatId }),
|
||||
...(chatInviteHash && { chatInviteHash }),
|
||||
...(chatInvite && { chatInviteTitle }),
|
||||
@ -237,17 +239,21 @@ export function buildMessagePeerReaction(userReaction: GramJs.MessagePeerReactio
|
||||
export function buildApiAvailableReaction(availableReaction: GramJs.AvailableReaction): ApiAvailableReaction {
|
||||
const {
|
||||
selectAnimation, staticIcon, reaction, title,
|
||||
inactive, aroundAnimation, centerIcon,
|
||||
inactive, aroundAnimation, centerIcon, effectAnimation, activateAnimation,
|
||||
premium,
|
||||
} = availableReaction;
|
||||
|
||||
return {
|
||||
selectAnimation: buildApiDocument(selectAnimation),
|
||||
activateAnimation: buildApiDocument(activateAnimation),
|
||||
effectAnimation: buildApiDocument(effectAnimation),
|
||||
staticIcon: buildApiDocument(staticIcon),
|
||||
aroundAnimation: aroundAnimation ? buildApiDocument(aroundAnimation) : undefined,
|
||||
centerIcon: centerIcon ? buildApiDocument(centerIcon) : undefined,
|
||||
reaction,
|
||||
title,
|
||||
isInactive: inactive,
|
||||
isPremium: premium,
|
||||
};
|
||||
}
|
||||
|
||||
@ -368,7 +374,7 @@ function buildSticker(media: GramJs.TypeMessageMedia): ApiSticker | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildStickerFromDocument(media.document);
|
||||
return buildStickerFromDocument(media.document, media.nopremium);
|
||||
}
|
||||
|
||||
function buildPhoto(media: GramJs.TypeMessageMedia): ApiPhoto | undefined {
|
||||
@ -427,7 +433,7 @@ export function buildVideoFromDocument(document: GramJs.Document): ApiVideo | un
|
||||
isRound,
|
||||
isGif: Boolean(gifAttr),
|
||||
thumbnail: buildApiThumbnailFromStripped(thumbs),
|
||||
size,
|
||||
size: size.toJSNumber(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -469,7 +475,8 @@ function buildAudio(media: GramJs.TypeMessageMedia): ApiAudio | undefined {
|
||||
id: String(media.document.id),
|
||||
fileName: getFilenameFromDocument(media.document, 'audio'),
|
||||
thumbnailSizes,
|
||||
...pick(media.document, ['size', 'mimeType']),
|
||||
size: media.document.size.toJSNumber(),
|
||||
...pick(media.document, ['mimeType']),
|
||||
...pick(audioAttribute, ['duration', 'performer', 'title']),
|
||||
};
|
||||
}
|
||||
@ -559,7 +566,7 @@ export function buildApiDocument(document: GramJs.TypeDocument): ApiDocument | u
|
||||
|
||||
return {
|
||||
id: String(id),
|
||||
size,
|
||||
size: size.toJSNumber(),
|
||||
mimeType,
|
||||
timestamp: date,
|
||||
fileName: getFilenameFromDocument(document),
|
||||
@ -717,22 +724,10 @@ export function buildInvoice(media: GramJs.MessageMediaInvoice): ApiInvoice {
|
||||
description: text, title, photo, test, totalAmount, currency, receiptMsgId,
|
||||
} = media;
|
||||
|
||||
const imageAttribute = photo?.attributes
|
||||
.find((a: any): a is GramJs.DocumentAttributeImageSize => a instanceof GramJs.DocumentAttributeImageSize);
|
||||
|
||||
let photoWidth: number | undefined;
|
||||
let photoHeight: number | undefined;
|
||||
if (imageAttribute) {
|
||||
photoWidth = imageAttribute.w;
|
||||
photoHeight = imageAttribute.h;
|
||||
}
|
||||
|
||||
return {
|
||||
text,
|
||||
title,
|
||||
photoUrl: photo?.url,
|
||||
photoWidth,
|
||||
photoHeight,
|
||||
photo: buildApiWebDocument(photo),
|
||||
receiptMsgId,
|
||||
amount: Number(totalAmount),
|
||||
currency,
|
||||
@ -1311,6 +1306,27 @@ function buildUploadingMedia(
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiWebDocument(document?: GramJs.TypeWebDocument): ApiWebDocument | undefined {
|
||||
if (!document) return undefined;
|
||||
|
||||
const {
|
||||
url, size, mimeType,
|
||||
} = document;
|
||||
const accessHash = document instanceof GramJs.WebDocument ? document.accessHash.toString() : undefined;
|
||||
const sizeAttr = document.attributes.find((attr): attr is GramJs.DocumentAttributeImageSize => (
|
||||
attr instanceof GramJs.DocumentAttributeImageSize
|
||||
));
|
||||
const dimensions = sizeAttr && { width: sizeAttr.w, height: sizeAttr.h };
|
||||
|
||||
return {
|
||||
url,
|
||||
accessHash,
|
||||
size,
|
||||
mimeType,
|
||||
dimensions,
|
||||
};
|
||||
}
|
||||
|
||||
function buildNewPoll(poll: ApiNewPoll, localId: number) {
|
||||
return {
|
||||
poll: {
|
||||
@ -1321,7 +1337,7 @@ function buildNewPoll(poll: ApiNewPoll, localId: number) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildApiMessageEntity(entity: GramJs.TypeMessageEntity): ApiMessageEntity {
|
||||
export function buildApiMessageEntity(entity: GramJs.TypeMessageEntity): ApiMessageEntity {
|
||||
const { className: type, offset, length } = entity;
|
||||
return {
|
||||
type,
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import type { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type { ApiInvoice, ApiPaymentSavedInfo, ApiPremiumPromo } from '../../types';
|
||||
|
||||
import { buildApiDocument, buildApiMessageEntity, buildApiWebDocument } from './messages';
|
||||
import { omitVirtualClassFields } from './helpers';
|
||||
|
||||
export function buildShippingOptions(shippingOptions: GramJs.ShippingOption[] | undefined) {
|
||||
if (!shippingOptions) {
|
||||
return undefined;
|
||||
@ -8,11 +13,11 @@ export function buildShippingOptions(shippingOptions: GramJs.ShippingOption[] |
|
||||
return {
|
||||
id: option.id,
|
||||
title: option.title,
|
||||
amount: option.prices.reduce((ac, cur) => ac + Number((cur.amount as any).value), 0),
|
||||
amount: option.prices.reduce((ac, cur) => ac + cur.amount.toJSNumber(), 0),
|
||||
prices: option.prices.map(({ label, amount }) => {
|
||||
return {
|
||||
label,
|
||||
amount: Number((amount as any).value),
|
||||
amount: amount.toJSNumber(),
|
||||
};
|
||||
}),
|
||||
};
|
||||
@ -34,7 +39,7 @@ export function buildReceipt(receipt: GramJs.payments.PaymentReceipt) {
|
||||
const { prices } = invoice;
|
||||
const mapedPrices = prices.map(({ label, amount }) => ({
|
||||
label,
|
||||
amount: Number((amount as any).value),
|
||||
amount: amount.toJSNumber(),
|
||||
}));
|
||||
|
||||
let shippingPrices;
|
||||
@ -44,7 +49,7 @@ export function buildReceipt(receipt: GramJs.payments.PaymentReceipt) {
|
||||
shippingPrices = shipping.prices.map(({ label, amount }) => {
|
||||
return {
|
||||
label,
|
||||
amount: Number((amount as any).value),
|
||||
amount: amount.toJSNumber(),
|
||||
};
|
||||
});
|
||||
shippingMethod = shipping.title;
|
||||
@ -54,7 +59,7 @@ export function buildReceipt(receipt: GramJs.payments.PaymentReceipt) {
|
||||
currency,
|
||||
prices: mapedPrices,
|
||||
info: { shippingAddress, phone, name },
|
||||
totalAmount: Number((totalAmount as any).value),
|
||||
totalAmount: totalAmount.toJSNumber(),
|
||||
credentialsTitle,
|
||||
shippingPrices,
|
||||
shippingMethod,
|
||||
@ -86,19 +91,25 @@ export function buildPaymentForm(form: GramJs.payments.PaymentForm) {
|
||||
prices,
|
||||
} = invoice;
|
||||
|
||||
const mapedPrices = prices.map(({ label, amount }) => ({
|
||||
const mappedPrices = prices.map(({ label, amount }) => ({
|
||||
label,
|
||||
amount: Number((amount as any).value),
|
||||
amount: amount.toJSNumber(),
|
||||
}));
|
||||
const { shippingAddress } = savedInfo || {};
|
||||
const cleanedInfo: ApiPaymentSavedInfo | undefined = savedInfo ? omitVirtualClassFields(savedInfo) : undefined;
|
||||
if (cleanedInfo && shippingAddress) {
|
||||
cleanedInfo.shippingAddress = omitVirtualClassFields(shippingAddress);
|
||||
}
|
||||
|
||||
const nativeData = nativeParams ? JSON.parse(nativeParams.data) : {};
|
||||
|
||||
return {
|
||||
canSaveCredentials,
|
||||
passwordMissing,
|
||||
formId: String(formId),
|
||||
providerId: String(providerId),
|
||||
nativeProvider,
|
||||
savedInfo,
|
||||
savedInfo: cleanedInfo,
|
||||
invoice: {
|
||||
test,
|
||||
nameRequested,
|
||||
@ -109,7 +120,7 @@ export function buildPaymentForm(form: GramJs.payments.PaymentForm) {
|
||||
phoneToProvider,
|
||||
emailToProvider,
|
||||
currency,
|
||||
prices: mapedPrices,
|
||||
prices: mappedPrices,
|
||||
},
|
||||
nativeParams: {
|
||||
needCardholderName: nativeData.need_cardholder_name,
|
||||
@ -120,3 +131,40 @@ export function buildPaymentForm(form: GramJs.payments.PaymentForm) {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiInvoiceFromForm(form: GramJs.payments.PaymentForm): ApiInvoice {
|
||||
const {
|
||||
invoice, description: text, title, photo,
|
||||
} = form;
|
||||
const {
|
||||
test, currency, prices, recurring, recurringTermsUrl,
|
||||
} = invoice;
|
||||
|
||||
const totalAmount = prices.reduce((ac, cur) => ac + cur.amount.toJSNumber(), 0);
|
||||
|
||||
return {
|
||||
text,
|
||||
title,
|
||||
photo: buildApiWebDocument(photo),
|
||||
amount: totalAmount,
|
||||
currency,
|
||||
isTest: test,
|
||||
isRecurring: recurring,
|
||||
recurringTermsUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiPremiumPromo(promo: GramJs.help.PremiumPromo): ApiPremiumPromo {
|
||||
const {
|
||||
statusText, statusEntities, videos, videoSections, currency, monthlyAmount,
|
||||
} = promo;
|
||||
|
||||
return {
|
||||
statusText,
|
||||
statusEntities: statusEntities.map((l) => buildApiMessageEntity(l)),
|
||||
videoSections,
|
||||
currency,
|
||||
videos: videos.map(buildApiDocument).filter(Boolean),
|
||||
monthlyAmount: monthlyAmount.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -10,12 +10,12 @@ import localDb from '../localDb';
|
||||
const LOTTIE_STICKER_MIME_TYPE = 'application/x-tgsticker';
|
||||
const VIDEO_STICKER_MIME_TYPE = 'video/webm';
|
||||
|
||||
export function buildStickerFromDocument(document: GramJs.TypeDocument): ApiSticker | undefined {
|
||||
export function buildStickerFromDocument(document: GramJs.TypeDocument, isNoPremium?: boolean): ApiSticker | undefined {
|
||||
if (document instanceof GramJs.DocumentEmpty) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { mimeType } = document;
|
||||
const { mimeType, videoThumbs } = document;
|
||||
const stickerAttribute = document.attributes
|
||||
.find((attr: any): attr is GramJs.DocumentAttributeSticker => (
|
||||
attr instanceof GramJs.DocumentAttributeSticker
|
||||
@ -78,6 +78,8 @@ export function buildStickerFromDocument(document: GramJs.TypeDocument): ApiStic
|
||||
|
||||
const { w: width, h: height } = cachedThumb as GramJs.PhotoCachedSize || sizeAttribute || {};
|
||||
|
||||
const hasEffect = !isNoPremium && videoThumbs?.some(({ type }) => type === 'f');
|
||||
|
||||
return {
|
||||
id: String(document.id),
|
||||
stickerSetId: stickerSetInfo ? String(stickerSetInfo.id) : NO_STICKER_SET_ID,
|
||||
@ -88,6 +90,7 @@ export function buildStickerFromDocument(document: GramJs.TypeDocument): ApiStic
|
||||
width,
|
||||
height,
|
||||
thumbnail,
|
||||
hasEffect,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -4,11 +4,13 @@ import type {
|
||||
} from '../../types';
|
||||
import { buildApiPeerId } from './peers';
|
||||
import { buildApiBotInfo } from './bots';
|
||||
import { buildApiPhoto } from './common';
|
||||
|
||||
export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUser {
|
||||
const {
|
||||
fullUser: {
|
||||
about, commonChatsCount, pinnedMsgId, botInfo, blocked,
|
||||
profilePhoto,
|
||||
},
|
||||
users,
|
||||
} = mtpUserFull;
|
||||
@ -18,11 +20,12 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
return {
|
||||
...user,
|
||||
fullInfo: {
|
||||
...(profilePhoto instanceof GramJs.Photo && { profilePhoto: buildApiPhoto(profilePhoto) }),
|
||||
bio: about,
|
||||
commonChatsCount,
|
||||
pinnedMessageId: pinnedMsgId,
|
||||
isBlocked: Boolean(blocked),
|
||||
...(botInfo && { botInfo: buildApiBotInfo(botInfo) }),
|
||||
...(botInfo && { botInfo: buildApiBotInfo(botInfo, user.id) }),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -35,6 +38,9 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
||||
const {
|
||||
id, firstName, lastName, fake, scam,
|
||||
} = mtpUser;
|
||||
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto
|
||||
? Boolean(mtpUser.photo.hasVideo)
|
||||
: undefined;
|
||||
const avatarHash = mtpUser.photo instanceof GramJs.UserProfilePhoto
|
||||
? String(mtpUser.photo.photoId)
|
||||
: undefined;
|
||||
@ -45,6 +51,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
||||
isMin: Boolean(mtpUser.min),
|
||||
fakeType: scam ? 'scam' : (fake ? 'fake' : undefined),
|
||||
...(mtpUser.self && { isSelf: true }),
|
||||
isPremium: Boolean(mtpUser.premium),
|
||||
...(mtpUser.verified && { isVerified: true }),
|
||||
...((mtpUser.contact || mtpUser.mutualContact) && { isContact: true }),
|
||||
type: userType,
|
||||
@ -56,6 +63,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
||||
noStatus: !mtpUser.status,
|
||||
...(mtpUser.accessHash && { accessHash: String(mtpUser.accessHash) }),
|
||||
...(avatarHash && { avatarHash }),
|
||||
hasVideoAvatar,
|
||||
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
|
||||
...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachMenuBot: mtpUser.botAttachMenu }),
|
||||
};
|
||||
|
||||
@ -19,6 +19,7 @@ import type {
|
||||
ApiVideo,
|
||||
ApiThemeParameters,
|
||||
ApiPoll,
|
||||
ApiRequestInputInvoice,
|
||||
} from '../../types';
|
||||
import {
|
||||
ApiMessageEntityTypes,
|
||||
@ -349,6 +350,8 @@ export function isMessageWithMedia(message: GramJs.Message | GramJs.UpdateServic
|
||||
) || (
|
||||
media instanceof GramJs.MessageMediaGame
|
||||
&& (media.game.document instanceof GramJs.Document || media.game.photo instanceof GramJs.Photo)
|
||||
) || (
|
||||
media instanceof GramJs.MessageMediaInvoice && media.photo
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -525,3 +528,16 @@ export function buildInputPhoneCall({ id, accessHash }: ApiPhoneCall) {
|
||||
accessHash: BigInt(accessHash!),
|
||||
});
|
||||
}
|
||||
|
||||
export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
|
||||
if ('slug' in invoice) {
|
||||
return new GramJs.InputInvoiceSlug({
|
||||
slug: invoice.slug,
|
||||
});
|
||||
} else {
|
||||
return new GramJs.InputInvoiceMessage({
|
||||
peer: buildInputPeer(invoice.chat.id, invoice.chat.accessHash),
|
||||
msgId: invoice.messageId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,30 +14,31 @@ export function addMessageToLocalDb(message: GramJs.Message | GramJs.MessageServ
|
||||
const messageFullId = `${resolveMessageApiChatId(message)}-${message.id}`;
|
||||
localDb.messages[messageFullId] = message;
|
||||
|
||||
if (
|
||||
message instanceof GramJs.Message
|
||||
&& message.media instanceof GramJs.MessageMediaDocument
|
||||
&& message.media.document instanceof GramJs.Document
|
||||
) {
|
||||
localDb.documents[String(message.media.document.id)] = message.media.document;
|
||||
}
|
||||
|
||||
if (
|
||||
message instanceof GramJs.Message
|
||||
&& message.media instanceof GramJs.MessageMediaWebPage
|
||||
&& message.media.webpage instanceof GramJs.WebPage
|
||||
&& message.media.webpage.document instanceof GramJs.Document
|
||||
) {
|
||||
localDb.documents[String(message.media.webpage.document.id)] = message.media.webpage.document;
|
||||
}
|
||||
|
||||
if (message instanceof GramJs.Message
|
||||
&& message.media instanceof GramJs.MessageMediaGame
|
||||
) {
|
||||
if (message.media.game.document instanceof GramJs.Document) {
|
||||
localDb.documents[String(message.media.game.document.id)] = message.media.game.document;
|
||||
if (message instanceof GramJs.Message) {
|
||||
if (message.media instanceof GramJs.MessageMediaDocument
|
||||
&& message.media.document instanceof GramJs.Document
|
||||
) {
|
||||
localDb.documents[String(message.media.document.id)] = message.media.document;
|
||||
}
|
||||
|
||||
if (message.media instanceof GramJs.MessageMediaWebPage
|
||||
&& message.media.webpage instanceof GramJs.WebPage
|
||||
&& message.media.webpage.document instanceof GramJs.Document
|
||||
) {
|
||||
localDb.documents[String(message.media.webpage.document.id)] = message.media.webpage.document;
|
||||
}
|
||||
|
||||
if (message.media instanceof GramJs.MessageMediaGame) {
|
||||
if (message.media.game.document instanceof GramJs.Document) {
|
||||
localDb.documents[String(message.media.game.document.id)] = message.media.game.document;
|
||||
}
|
||||
addPhotoToLocalDb(message.media.game.photo);
|
||||
}
|
||||
|
||||
if (message.media instanceof GramJs.MessageMediaInvoice
|
||||
&& message.media.photo) {
|
||||
localDb.webDocuments[String(message.media.photo.url)] = message.media.photo;
|
||||
}
|
||||
addPhotoToLocalDb(message.media.game.photo);
|
||||
}
|
||||
|
||||
if (message instanceof GramJs.MessageService && 'photo' in message.action) {
|
||||
|
||||
@ -157,6 +157,7 @@ export async function requestWebView({
|
||||
startParam,
|
||||
replyToMessageId,
|
||||
theme,
|
||||
sendAs,
|
||||
isFromBotMenu,
|
||||
}: {
|
||||
isSilent?: boolean;
|
||||
@ -166,6 +167,7 @@ export async function requestWebView({
|
||||
startParam?: string;
|
||||
replyToMessageId?: number;
|
||||
theme?: ApiThemeParameters;
|
||||
sendAs?: ApiUser | ApiChat;
|
||||
isFromBotMenu?: boolean;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.messages.RequestWebView({
|
||||
@ -177,6 +179,7 @@ export async function requestWebView({
|
||||
startParam,
|
||||
themeParams: theme ? buildInputThemeParams(theme) : undefined,
|
||||
fromBotMenu: isFromBotMenu || undefined,
|
||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||
}));
|
||||
|
||||
if (result instanceof GramJs.WebViewResultUrl) {
|
||||
@ -211,12 +214,14 @@ export function prolongWebView({
|
||||
bot,
|
||||
queryId,
|
||||
replyToMessageId,
|
||||
sendAs,
|
||||
}: {
|
||||
isSilent?: boolean;
|
||||
peer: ApiChat | ApiUser;
|
||||
bot: ApiUser;
|
||||
queryId: string;
|
||||
replyToMessageId?: number;
|
||||
sendAs?: ApiUser | ApiChat;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.messages.ProlongWebView({
|
||||
silent: isSilent || undefined,
|
||||
@ -224,6 +229,7 @@ export function prolongWebView({
|
||||
bot: buildInputPeer(bot.id, bot.accessHash),
|
||||
queryId: BigInt(queryId),
|
||||
replyToMsgId: replyToMessageId,
|
||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ import type {
|
||||
} from '../../types';
|
||||
|
||||
import {
|
||||
DEBUG, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SERVICE_NOTIFICATIONS_USER_ID,
|
||||
DEBUG, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SERVICE_NOTIFICATIONS_USER_ID, ALL_FOLDER_ID,
|
||||
} from '../../../config';
|
||||
import { invokeRequest, uploadFile } from './client';
|
||||
import {
|
||||
@ -45,6 +45,7 @@ import { addEntitiesWithPhotosToLocalDb, addMessageToLocalDb, addPhotoToLocalDb
|
||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||
import { buildApiPhoto } from '../apiBuilders/common';
|
||||
import { buildStickerSet } from '../apiBuilders/symbols';
|
||||
import localDb from '../localDb';
|
||||
|
||||
type FullChatData = {
|
||||
fullInfo: ApiChatFullInfo;
|
||||
@ -365,8 +366,13 @@ async function getFullChatInfo(chatId: string): Promise<FullChatData | undefined
|
||||
availableReactions,
|
||||
recentRequesters,
|
||||
requestsPending,
|
||||
chatPhoto,
|
||||
} = result.fullChat;
|
||||
|
||||
if (chatPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[chatPhoto.id.toString()] = chatPhoto;
|
||||
}
|
||||
|
||||
const members = buildChatMembers(participants);
|
||||
const adminMembers = members ? members.filter(({ isAdmin, isOwner }) => isAdmin || isOwner) : undefined;
|
||||
const botCommands = botInfo ? buildApiChatBotCommands(botInfo) : undefined;
|
||||
@ -374,12 +380,14 @@ async function getFullChatInfo(chatId: string): Promise<FullChatData | undefined
|
||||
|
||||
return {
|
||||
fullInfo: {
|
||||
...(chatPhoto instanceof GramJs.Photo && { profilePhoto: buildApiPhoto(chatPhoto) }),
|
||||
about,
|
||||
members,
|
||||
adminMembers,
|
||||
canViewMembers: true,
|
||||
botCommands,
|
||||
...(exportedInvite && {
|
||||
...(exportedInvite instanceof GramJs.ChatInviteExported && {
|
||||
// TODO Verify Exported Invite logic
|
||||
inviteLink: exportedInvite.link,
|
||||
}),
|
||||
groupCallId: call?.id.toString(),
|
||||
@ -437,8 +445,13 @@ async function getFullChannelInfo(
|
||||
statsDc,
|
||||
participantsCount,
|
||||
stickerset,
|
||||
chatPhoto,
|
||||
} = result.fullChat;
|
||||
|
||||
if (chatPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[chatPhoto.id.toString()] = chatPhoto;
|
||||
}
|
||||
|
||||
const inviteLink = exportedInvite instanceof GramJs.ChatInviteExported
|
||||
? exportedInvite.link
|
||||
: undefined;
|
||||
@ -474,6 +487,7 @@ async function getFullChannelInfo(
|
||||
|
||||
return {
|
||||
fullInfo: {
|
||||
...(chatPhoto instanceof GramJs.Photo && { profilePhoto: buildApiPhoto(chatPhoto) }),
|
||||
about,
|
||||
onlineCount,
|
||||
inviteLink,
|
||||
@ -551,7 +565,7 @@ export async function createChannel({
|
||||
broadcast: true,
|
||||
title,
|
||||
about,
|
||||
}));
|
||||
}), undefined, true);
|
||||
|
||||
// `createChannel` can return a lot of different update types according to docs,
|
||||
// but currently channel creation returns only `Updates` type.
|
||||
@ -596,7 +610,7 @@ export function joinChannel({
|
||||
}) {
|
||||
return invokeRequest(new GramJs.channels.JoinChannel({
|
||||
channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel,
|
||||
}), true);
|
||||
}), true, true);
|
||||
}
|
||||
|
||||
export function deleteChatUser({
|
||||
@ -747,9 +761,18 @@ export async function fetchChatFolders() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const defaultFolderPosition = result.findIndex((l) => l instanceof GramJs.DialogFilterDefault);
|
||||
const dialogFilters = result.filter((df): df is GramJs.DialogFilter => df instanceof GramJs.DialogFilter);
|
||||
const orderedIds = dialogFilters.map(({ id }) => id);
|
||||
if (defaultFolderPosition !== -1) {
|
||||
orderedIds.splice(defaultFolderPosition, 0, ALL_FOLDER_ID);
|
||||
}
|
||||
return {
|
||||
byId: buildCollectionByKey(result.map(buildApiChatFolder), 'id') as Record<number, ApiChatFolder>,
|
||||
orderedIds: result.map(({ id }) => id),
|
||||
byId: buildCollectionByKey(
|
||||
dialogFilters
|
||||
.map(buildApiChatFolder), 'id',
|
||||
) as Record<number, ApiChatFolder>,
|
||||
orderedIds,
|
||||
};
|
||||
}
|
||||
|
||||
@ -760,7 +783,7 @@ export async function fetchRecommendedChatFolders() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return results.map(buildApiChatFolderFromSuggested);
|
||||
return results.map(buildApiChatFolderFromSuggested).filter(Boolean);
|
||||
}
|
||||
|
||||
export async function editChatFolder({
|
||||
@ -1038,6 +1061,8 @@ export function setDiscussionGroup({
|
||||
export async function migrateChat(chat: ApiChat) {
|
||||
const result = await invokeRequest(
|
||||
new GramJs.messages.MigrateChat({ chatId: buildInputEntity(chat.id) as BigInt.BigInteger }),
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
// `migrateChat` can return a lot of different update types according to docs,
|
||||
@ -1156,6 +1181,20 @@ export function deleteChatMember(chat: ApiChat, user: ApiUser) {
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleJoinToSend(chat: ApiChat, isEnabled: boolean) {
|
||||
return invokeRequest(new GramJs.channels.ToggleJoinToSend({
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
enabled: isEnabled,
|
||||
}), true);
|
||||
}
|
||||
|
||||
export function toggleJoinRequest(chat: ApiChat, isEnabled: boolean) {
|
||||
return invokeRequest(new GramJs.channels.ToggleJoinRequest({
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
enabled: isEnabled,
|
||||
}), true);
|
||||
}
|
||||
|
||||
function preparePeers(
|
||||
result: GramJs.messages.Dialogs | GramJs.messages.DialogsSlice | GramJs.messages.PeerDialogs,
|
||||
) {
|
||||
|
||||
@ -312,6 +312,9 @@ export async function fetchCurrentUser() {
|
||||
|
||||
const user = userFull.users[0];
|
||||
|
||||
if (user.photo instanceof GramJs.Photo) {
|
||||
localDb.photos[user.photo.id.toString()] = user.photo;
|
||||
}
|
||||
localDb.users[buildApiPeerId(user.id, 'user')] = user;
|
||||
const currentUser = buildApiUserFromFull(userFull);
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ export {
|
||||
getChatByUsername, togglePreHistoryHidden, updateChatDefaultBannedRights, updateChatMemberBannedRights,
|
||||
updateChatTitle, updateChatAbout, toggleSignatures, updateChatAdmin, fetchGroupsForDiscussion, setDiscussionGroup,
|
||||
migrateChat, openChatByInvite, fetchMembers, importChatInvite, addChatMembers, deleteChatMember, toggleIsProtected,
|
||||
getChatByPhoneNumber,
|
||||
getChatByPhoneNumber, toggleJoinToSend, toggleJoinRequest,
|
||||
} from './chats';
|
||||
|
||||
export {
|
||||
@ -28,7 +28,8 @@ export {
|
||||
fetchWebPagePreview, editMessage, forwardMessages, loadPollOptionResults, sendPollVote, findFirstMessageIdAfterDate,
|
||||
fetchPinnedMessages, fetchScheduledHistory, sendScheduledMessages, rescheduleMessage, deleteScheduledMessages,
|
||||
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
||||
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, closePoll,
|
||||
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
||||
closePoll,
|
||||
} from './messages';
|
||||
|
||||
export {
|
||||
@ -58,6 +59,7 @@ export {
|
||||
fetchNotificationExceptions, fetchNotificationSettings, updateContactSignUpNotification, updateNotificationSettings,
|
||||
fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings, registerDevice, unregisterDevice,
|
||||
updateIsOnline, fetchContentSettings, updateContentSettings, fetchLangStrings, fetchCountryList, fetchAppConfig,
|
||||
fetchGlobalPrivacySettings, updateGlobalPrivacySettings,
|
||||
} from './settings';
|
||||
|
||||
export {
|
||||
@ -71,7 +73,7 @@ export {
|
||||
} from './bots';
|
||||
|
||||
export {
|
||||
validateRequestedInfo, sendPaymentForm, getPaymentForm, getReceipt,
|
||||
validateRequestedInfo, sendPaymentForm, getPaymentForm, getReceipt, fetchPremiumPromo,
|
||||
} from './payments';
|
||||
|
||||
export {
|
||||
|
||||
@ -49,9 +49,8 @@ export async function updatePrivateLink({
|
||||
expireDate,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
// TODO Verify Exported Invite logic
|
||||
if (!(result instanceof GramJs.ChatInviteExported)) return undefined;
|
||||
|
||||
onUpdate({
|
||||
'@type': 'updateChatFullInfo',
|
||||
@ -76,7 +75,10 @@ export async function fetchExportedChatInvites({
|
||||
|
||||
if (!exportedInvites) return undefined;
|
||||
addEntitiesWithPhotosToLocalDb(exportedInvites.users);
|
||||
return exportedInvites.invites.map(buildApiExportedInvite);
|
||||
// TODO Verify Exported Invite logic
|
||||
return (exportedInvites.invites
|
||||
.filter((l) => l instanceof GramJs.ChatInviteExported) as GramJs.ChatInviteExported[])
|
||||
.map(buildApiExportedInvite);
|
||||
}
|
||||
|
||||
export async function editExportedChatInvite({
|
||||
@ -90,6 +92,7 @@ export async function editExportedChatInvite({
|
||||
isRequestNeeded?: boolean;
|
||||
title?: string;
|
||||
}) {
|
||||
// TODO Verify Exported Invite logic
|
||||
const invite = await invokeRequest(new GramJs.messages.EditExportedChatInvite({
|
||||
link,
|
||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||
@ -103,7 +106,7 @@ export async function editExportedChatInvite({
|
||||
if (!invite) return undefined;
|
||||
|
||||
addEntitiesWithPhotosToLocalDb(invite.users);
|
||||
if (invite instanceof GramJs.messages.ExportedChatInvite) {
|
||||
if (invite instanceof GramJs.messages.ExportedChatInvite && invite.invite instanceof GramJs.ChatInviteExported) {
|
||||
const replaceInvite = buildApiExportedInvite(invite.invite);
|
||||
return {
|
||||
oldInvite: replaceInvite,
|
||||
@ -111,7 +114,9 @@ export async function editExportedChatInvite({
|
||||
};
|
||||
}
|
||||
|
||||
if (invite instanceof GramJs.messages.ExportedChatInviteReplaced) {
|
||||
if (invite instanceof GramJs.messages.ExportedChatInviteReplaced
|
||||
&& invite.invite instanceof GramJs.ChatInviteExported
|
||||
&& invite.newInvite instanceof GramJs.ChatInviteExported) {
|
||||
const oldInvite = buildApiExportedInvite(invite.invite);
|
||||
const newInvite = buildApiExportedInvite(invite.newInvite);
|
||||
return {
|
||||
@ -139,7 +144,8 @@ export async function exportChatInvite({
|
||||
title,
|
||||
}));
|
||||
|
||||
if (!invite) return undefined;
|
||||
// TODO Verify Exported Invite logic
|
||||
if (!(invite instanceof GramJs.ChatInviteExported)) return undefined;
|
||||
return buildApiExportedInvite(invite);
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,9 @@ import localDb from '../localDb';
|
||||
import * as cacheApi from '../../../util/cacheApi';
|
||||
import { getEntityTypeById } from '../gramjsBuilders';
|
||||
|
||||
const MEDIA_ENTITY_TYPES = new Set(['msg', 'sticker', 'gif', 'wallpaper', 'photo', 'webDocument', 'document']);
|
||||
const MEDIA_ENTITY_TYPES = new Set([
|
||||
'msg', 'sticker', 'gif', 'wallpaper', 'photo', 'webDocument', 'document', 'videoAvatar',
|
||||
]);
|
||||
|
||||
export default async function downloadMedia(
|
||||
{
|
||||
@ -31,6 +33,7 @@ export default async function downloadMedia(
|
||||
const {
|
||||
data, mimeType, fullSize,
|
||||
} = await download(url, client, isConnected, onProgress, start, end, mediaFormat, isHtmlAllowed) || {};
|
||||
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
@ -62,7 +65,7 @@ export default async function downloadMedia(
|
||||
|
||||
export type EntityType = (
|
||||
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'channel' | 'chat' | 'user' | 'photo' | 'stickerSet' | 'webDocument' |
|
||||
'document' | 'staticMap'
|
||||
'document' | 'staticMap' | 'videoAvatar'
|
||||
);
|
||||
|
||||
async function download(
|
||||
@ -127,6 +130,7 @@ async function download(
|
||||
case 'wallpaper':
|
||||
entity = localDb.documents[entityId];
|
||||
break;
|
||||
case 'videoAvatar':
|
||||
case 'photo':
|
||||
entity = localDb.photos[entityId];
|
||||
break;
|
||||
@ -159,22 +163,27 @@ async function download(
|
||||
if (entity instanceof GramJs.Message) {
|
||||
mimeType = getMessageMediaMimeType(entity, sizeType);
|
||||
if (entity.media instanceof GramJs.MessageMediaDocument && entity.media.document instanceof GramJs.Document) {
|
||||
fullSize = entity.media.document.size;
|
||||
fullSize = entity.media.document.size.toJSNumber();
|
||||
}
|
||||
if (entity.media instanceof GramJs.MessageMediaWebPage
|
||||
&& entity.media.webpage instanceof GramJs.WebPage
|
||||
&& entity.media.webpage.document instanceof GramJs.Document) {
|
||||
fullSize = entity.media.webpage.document.size;
|
||||
fullSize = entity.media.webpage.document.size.toJSNumber();
|
||||
}
|
||||
} else if (entity instanceof GramJs.Photo) {
|
||||
mimeType = 'image/jpeg';
|
||||
if (entityType === 'videoAvatar') {
|
||||
mimeType = 'video/mp4';
|
||||
} else {
|
||||
mimeType = 'image/jpeg';
|
||||
}
|
||||
} else if (entityType === 'sticker' && sizeType) {
|
||||
mimeType = 'image/webp';
|
||||
} else if (entityType === 'webDocument') {
|
||||
mimeType = (entity as GramJs.TypeWebDocument).mimeType;
|
||||
fullSize = (entity as GramJs.TypeWebDocument).size;
|
||||
} else {
|
||||
mimeType = (entity as GramJs.Document).mimeType;
|
||||
fullSize = (entity as GramJs.Document).size;
|
||||
fullSize = (entity as GramJs.Document).size.toJSNumber();
|
||||
}
|
||||
|
||||
// Prevent HTML-in-video attacks
|
||||
@ -296,7 +305,8 @@ export function parseMediaUrl(url: string) {
|
||||
: url.startsWith('webDocument')
|
||||
? url.match(/(webDocument):(.+)/)
|
||||
: url.match(
|
||||
/(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|document)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/,
|
||||
// eslint-disable-next-line max-len
|
||||
/(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|document|videoAvatar)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/,
|
||||
);
|
||||
if (!mediaMatch) {
|
||||
return undefined;
|
||||
|
||||
@ -1431,3 +1431,25 @@ export async function fetchUnreadReactions({
|
||||
chats,
|
||||
};
|
||||
}
|
||||
|
||||
export async function transcribeAudio({
|
||||
chat, messageId,
|
||||
}: {
|
||||
chat: ApiChat; messageId: number;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.messages.TranscribeAudio({
|
||||
msgId: messageId,
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
}));
|
||||
|
||||
if (!result) return undefined;
|
||||
|
||||
onUpdate({
|
||||
'@type': 'updateTranscribedAudio',
|
||||
isPending: result.pending,
|
||||
transcriptionId: result.transcriptionId.toString(),
|
||||
text: result.text,
|
||||
});
|
||||
|
||||
return result.transcriptionId.toString();
|
||||
}
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import BigInt from 'big-integer';
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
import { invokeRequest } from './client';
|
||||
import { buildInputPeer, buildShippingInfo } from '../gramjsBuilders';
|
||||
import { buildShippingOptions, buildPaymentForm, buildReceipt } from '../apiBuilders/payments';
|
||||
import type { ApiChat, OnApiUpdate } from '../../types';
|
||||
import { buildInputInvoice, buildInputPeer, buildShippingInfo } from '../gramjsBuilders';
|
||||
import {
|
||||
buildShippingOptions, buildPaymentForm, buildReceipt, buildApiPremiumPromo, buildApiInvoiceFromForm,
|
||||
} from '../apiBuilders/payments';
|
||||
import type {
|
||||
ApiChat, OnApiUpdate, ApiRequestInputInvoice,
|
||||
} from '../../types';
|
||||
import localDb from '../localDb';
|
||||
import { addEntitiesWithPhotosToLocalDb } from '../helpers';
|
||||
import { buildApiUser } from '../apiBuilders/users';
|
||||
|
||||
let onUpdate: OnApiUpdate;
|
||||
|
||||
@ -12,13 +19,11 @@ export function init(_onUpdate: OnApiUpdate) {
|
||||
}
|
||||
|
||||
export async function validateRequestedInfo({
|
||||
chat,
|
||||
messageId,
|
||||
inputInvoice,
|
||||
requestInfo,
|
||||
shouldSave,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
messageId: number;
|
||||
inputInvoice: ApiRequestInputInvoice;
|
||||
requestInfo: GramJs.TypePaymentRequestedInfo;
|
||||
shouldSave?: boolean;
|
||||
}): Promise<{
|
||||
@ -26,8 +31,7 @@ export async function validateRequestedInfo({
|
||||
shippingOptions: any;
|
||||
} | undefined> {
|
||||
const result = await invokeRequest(new GramJs.payments.ValidateRequestedInfo({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
msgId: messageId,
|
||||
invoice: buildInputInvoice(inputInvoice),
|
||||
save: shouldSave || undefined,
|
||||
info: buildShippingInfo(requestInfo),
|
||||
}));
|
||||
@ -47,15 +51,13 @@ export async function validateRequestedInfo({
|
||||
}
|
||||
|
||||
export async function sendPaymentForm({
|
||||
chat,
|
||||
messageId,
|
||||
inputInvoice,
|
||||
formId,
|
||||
requestedInfoId,
|
||||
shippingOptionId,
|
||||
credentials,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
messageId: number;
|
||||
inputInvoice: ApiRequestInputInvoice;
|
||||
formId: string;
|
||||
credentials: any;
|
||||
requestedInfoId?: string;
|
||||
@ -63,8 +65,7 @@ export async function sendPaymentForm({
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.SendPaymentForm({
|
||||
formId: BigInt(formId),
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
msgId: messageId,
|
||||
invoice: buildInputInvoice(inputInvoice),
|
||||
requestedInfoId,
|
||||
shippingOptionId,
|
||||
credentials: new GramJs.InputPaymentCredentials({
|
||||
@ -85,22 +86,23 @@ export async function sendPaymentForm({
|
||||
return Boolean(result);
|
||||
}
|
||||
|
||||
export async function getPaymentForm({
|
||||
chat, messageId,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
messageId: number;
|
||||
}) {
|
||||
export async function getPaymentForm(inputInvoice: ApiRequestInputInvoice) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetPaymentForm({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
msgId: messageId,
|
||||
invoice: buildInputInvoice(inputInvoice),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildPaymentForm(result);
|
||||
if (result.photo) {
|
||||
localDb.webDocuments[result.photo.url] = result.photo;
|
||||
}
|
||||
|
||||
return {
|
||||
form: buildPaymentForm(result),
|
||||
invoice: buildApiInvoiceFromForm(result),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getReceipt(chat: ApiChat, msgId: number) {
|
||||
@ -114,3 +116,22 @@ export async function getReceipt(chat: ApiChat, msgId: number) {
|
||||
|
||||
return buildReceipt(result);
|
||||
}
|
||||
|
||||
export async function fetchPremiumPromo() {
|
||||
const result = await invokeRequest(new GramJs.help.GetPremiumPromo());
|
||||
if (!result) return undefined;
|
||||
|
||||
addEntitiesWithPhotosToLocalDb(result.users);
|
||||
|
||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||
result.videos.forEach((video) => {
|
||||
if (video instanceof GramJs.Document) {
|
||||
localDb.documents[video.id.toString()] = video;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
promo: buildApiPremiumPromo(result),
|
||||
users,
|
||||
};
|
||||
}
|
||||
|
||||
@ -492,3 +492,33 @@ export async function fetchCountryList({ langCode = 'en' }: { langCode?: LangCod
|
||||
}
|
||||
return buildApiCountryList(countryList.countries);
|
||||
}
|
||||
|
||||
export async function fetchGlobalPrivacySettings() {
|
||||
const result = await invokeRequest(new GramJs.account.GetGlobalPrivacySettings());
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateGlobalPrivacySettings({ shouldArchiveAndMuteNewNonContact } : {
|
||||
shouldArchiveAndMuteNewNonContact: boolean;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({
|
||||
settings: new GramJs.GlobalPrivacySettings({
|
||||
archiveAndMuteNewNoncontactPeers: shouldArchiveAndMuteNewNonContact,
|
||||
}),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
||||
};
|
||||
}
|
||||
|
||||
@ -70,6 +70,7 @@ export async function fetchFeaturedStickers({ hash = '0' }: { hash?: string }) {
|
||||
|
||||
return {
|
||||
hash: String(result.hash),
|
||||
isPremium: Boolean(result.premium),
|
||||
sets: result.sets.map(buildStickerSetCovered),
|
||||
};
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { buildApiPhoto } from '../apiBuilders/common';
|
||||
import { addEntitiesWithPhotosToLocalDb, addPhotoToLocalDb, addUserToLocalDb } from '../helpers';
|
||||
import { buildApiPeerId } from '../apiBuilders/peers';
|
||||
import localDb from '../localDb';
|
||||
|
||||
let onUpdate: OnApiUpdate;
|
||||
|
||||
@ -44,6 +45,18 @@ export async function fetchFullUser({
|
||||
return;
|
||||
}
|
||||
|
||||
if (fullInfo.fullUser.profilePhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[fullInfo.fullUser.profilePhoto.id.toString()] = fullInfo.fullUser.profilePhoto;
|
||||
}
|
||||
|
||||
const botInfo = fullInfo.fullUser.botInfo;
|
||||
if (botInfo?.descriptionPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[botInfo.descriptionPhoto.id.toString()] = botInfo.descriptionPhoto;
|
||||
}
|
||||
if (botInfo?.descriptionDocument instanceof GramJs.Document) {
|
||||
localDb.documents[botInfo.descriptionDocument.id.toString()] = botInfo.descriptionDocument;
|
||||
}
|
||||
|
||||
const userWithFullInfo = buildApiUserFromFull(fullInfo);
|
||||
|
||||
onUpdate({
|
||||
|
||||
@ -194,6 +194,7 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
||||
if (action instanceof GramJs.MessageActionPaymentSent) {
|
||||
onUpdate({
|
||||
'@type': 'updatePaymentStateCompleted',
|
||||
slug: action.invoiceSlug,
|
||||
});
|
||||
} else if (action instanceof GramJs.MessageActionChatEditTitle) {
|
||||
onUpdate({
|
||||
@ -559,7 +560,7 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateDialogFilter) {
|
||||
const { id, filter } = update;
|
||||
const folder = filter ? buildApiChatFolder(filter) : undefined;
|
||||
const folder = filter instanceof GramJs.DialogFilter ? buildApiChatFolder(filter) : undefined;
|
||||
|
||||
onUpdate({
|
||||
'@type': 'updateChatFolder',
|
||||
@ -941,7 +942,10 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
||||
queryId: queryId.toString(),
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateBotMenuButton) {
|
||||
const { botId, button } = update;
|
||||
const {
|
||||
botId,
|
||||
button,
|
||||
} = update;
|
||||
|
||||
const id = buildApiPeerId(botId, 'user');
|
||||
|
||||
@ -950,6 +954,27 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
||||
botId: id,
|
||||
button: buildApiBotMenuButton(button),
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateTranscribedAudio) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const entities = update._entities;
|
||||
if (entities) {
|
||||
addEntitiesWithPhotosToLocalDb(entities);
|
||||
dispatchUserAndChatUpdates(entities);
|
||||
}
|
||||
|
||||
onUpdate({
|
||||
'@type': 'updateTranscribedAudio',
|
||||
transcriptionId: update.transcriptionId.toString(),
|
||||
text: update.text,
|
||||
isPending: update.pending,
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateConfig) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const entities = update._entities;
|
||||
if (entities) {
|
||||
addEntitiesWithPhotosToLocalDb(entities);
|
||||
dispatchUserAndChatUpdates(entities);
|
||||
}
|
||||
} else if (DEBUG) {
|
||||
const params = typeof update === 'object' && 'className' in update ? update.className : update;
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ApiDimensions,
|
||||
ApiPhoto, ApiSticker, ApiThumbnail, ApiVideo,
|
||||
} from './messages';
|
||||
|
||||
@ -9,7 +10,10 @@ export type ApiInlineResultType = (
|
||||
|
||||
export interface ApiWebDocument {
|
||||
url: string;
|
||||
size: number;
|
||||
mimeType: string;
|
||||
accessHash?: string;
|
||||
dimensions?: ApiDimensions;
|
||||
}
|
||||
|
||||
export interface ApiBotInlineResult {
|
||||
@ -60,6 +64,8 @@ export type ApiBotMenuButton = ApiBotMenuButtonWebApp | ApiBotMenuButtonCommands
|
||||
export interface ApiBotInfo {
|
||||
botId: string;
|
||||
commands?: ApiBotCommand[];
|
||||
description: string;
|
||||
description?: string;
|
||||
photo?: ApiPhoto;
|
||||
gif?: ApiVideo;
|
||||
menuButton: ApiBotMenuButton;
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ export interface ApiChat {
|
||||
hasPrivateLink?: boolean;
|
||||
accessHash?: string;
|
||||
isMin?: boolean;
|
||||
hasVideoAvatar?: boolean;
|
||||
avatarHash?: string;
|
||||
username?: string;
|
||||
membersCount?: number;
|
||||
@ -65,6 +66,8 @@ export interface ApiChat {
|
||||
typingStatus?: ApiTypingStatus;
|
||||
|
||||
joinRequests?: ApiChatInviteImporter[];
|
||||
isJoinToSend?: boolean;
|
||||
isJoinRequest?: boolean;
|
||||
sendAsIds?: string[];
|
||||
|
||||
unreadReactions?: number[];
|
||||
@ -105,6 +108,7 @@ export interface ApiChatFullInfo {
|
||||
requestsPending?: number;
|
||||
statisticsDcId?: number;
|
||||
stickerSet?: ApiStickerSet;
|
||||
profilePhoto?: ApiPhoto;
|
||||
}
|
||||
|
||||
export interface ApiChatMember {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import type { ApiWebDocument } from './bots';
|
||||
import type { ApiGroupCall, PhoneCallAction } from './calls';
|
||||
import type { ApiChat } from './chats';
|
||||
|
||||
export interface ApiDimensions {
|
||||
width: number;
|
||||
@ -9,6 +11,12 @@ export interface ApiPhotoSize extends ApiDimensions {
|
||||
type: 's' | 'm' | 'x' | 'y' | 'z';
|
||||
}
|
||||
|
||||
export interface ApiVideoSize extends ApiDimensions {
|
||||
type: 'u' | 'v';
|
||||
videoStartTs: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ApiThumbnail extends ApiDimensions {
|
||||
dataUri: string;
|
||||
}
|
||||
@ -16,7 +24,9 @@ export interface ApiThumbnail extends ApiDimensions {
|
||||
export interface ApiPhoto {
|
||||
id: string;
|
||||
thumbnail?: ApiThumbnail;
|
||||
isVideo?: boolean;
|
||||
sizes: ApiPhotoSize[];
|
||||
videoSizes?: ApiVideoSize[];
|
||||
blobUrl?: string;
|
||||
}
|
||||
|
||||
@ -31,6 +41,7 @@ export interface ApiSticker {
|
||||
height?: number;
|
||||
thumbnail?: ApiThumbnail;
|
||||
isPreloadedGlobally?: boolean;
|
||||
hasEffect?: boolean;
|
||||
}
|
||||
|
||||
export interface ApiStickerSet {
|
||||
@ -134,16 +145,31 @@ export interface ApiPoll {
|
||||
};
|
||||
}
|
||||
|
||||
// First type used for state, second - for API requests
|
||||
export type ApiInputInvoice = {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
} | {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type ApiRequestInputInvoice = {
|
||||
chat: ApiChat;
|
||||
messageId: number;
|
||||
} | {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export interface ApiInvoice {
|
||||
text: string;
|
||||
title: string;
|
||||
photoUrl?: string;
|
||||
photoWidth?: number;
|
||||
photoHeight?: number;
|
||||
photo?: ApiWebDocument;
|
||||
amount: number;
|
||||
currency: string;
|
||||
receiptMsgId?: number;
|
||||
isTest?: boolean;
|
||||
isRecurring?: boolean;
|
||||
recurringTermsUrl?: string;
|
||||
}
|
||||
|
||||
interface ApiGeoPoint {
|
||||
@ -321,6 +347,8 @@ export interface ApiMessage {
|
||||
isFromScheduled?: boolean;
|
||||
seenByUserIds?: string[];
|
||||
isProtected?: boolean;
|
||||
transcriptionId?: string;
|
||||
isTranscriptionError?: boolean;
|
||||
reactors?: {
|
||||
nextOffset?: string;
|
||||
count: number;
|
||||
@ -350,12 +378,15 @@ export interface ApiReactionCount {
|
||||
|
||||
export interface ApiAvailableReaction {
|
||||
selectAnimation?: ApiDocument;
|
||||
activateAnimation?: ApiDocument;
|
||||
effectAnimation?: ApiDocument;
|
||||
staticIcon?: ApiDocument;
|
||||
centerIcon?: ApiDocument;
|
||||
aroundAnimation?: ApiDocument;
|
||||
reaction: string;
|
||||
title: string;
|
||||
isInactive?: boolean;
|
||||
isPremium?: boolean;
|
||||
}
|
||||
|
||||
export interface ApiThreadInfo {
|
||||
@ -374,6 +405,7 @@ export type ApiMessageOutgoingStatus = 'read' | 'succeeded' | 'pending' | 'faile
|
||||
export type ApiSponsoredMessage = {
|
||||
chatId?: string;
|
||||
randomId: string;
|
||||
isRecommended?: boolean;
|
||||
isBot?: boolean;
|
||||
channelPostId?: number;
|
||||
startParam?: string;
|
||||
@ -446,6 +478,12 @@ interface ApiKeyboardButtonUrlAuth {
|
||||
buttonId: number;
|
||||
}
|
||||
|
||||
export type ApiTranscription = {
|
||||
text: string;
|
||||
isPending?: boolean;
|
||||
transcriptionId: string;
|
||||
};
|
||||
|
||||
export type ApiKeyboardButton = (
|
||||
ApiKeyboardButtonSimple
|
||||
| ApiKeyboardButtonReceipt
|
||||
@ -484,6 +522,7 @@ export type ApiThemeParameters = {
|
||||
link_color: string;
|
||||
button_color: string;
|
||||
button_text_color: string;
|
||||
secondary_bg_color: string;
|
||||
};
|
||||
|
||||
export const MAIN_THREAD_ID = -1;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ApiDocument, ApiPhoto } from './messages';
|
||||
import type { ApiUser } from './users';
|
||||
import type { ApiLimitType } from '../../global/types';
|
||||
|
||||
export interface ApiInitialArgs {
|
||||
userAgent: string;
|
||||
@ -93,7 +94,11 @@ export type ApiNotifyException = {
|
||||
|
||||
export type ApiNotification = {
|
||||
localId: string;
|
||||
title?: string;
|
||||
message: string;
|
||||
actionText?: string;
|
||||
action: VoidFunction;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export type ApiError = {
|
||||
@ -161,6 +166,10 @@ export interface ApiAppConfig {
|
||||
autologinDomains: string[];
|
||||
autologinToken: string;
|
||||
urlAuthDomains: string[];
|
||||
premiumInvoiceSlug: string;
|
||||
premiumBotUsername: string;
|
||||
isPremiumPurchaseBlocked: boolean;
|
||||
limits: Record<ApiLimitType, readonly [number, number]>;
|
||||
}
|
||||
|
||||
export interface GramJsEmojiInteraction {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { ApiDocument, ApiMessageEntity } from './messages';
|
||||
|
||||
export interface ApiShippingAddress {
|
||||
streetLine1: string;
|
||||
streetLine2: string;
|
||||
@ -61,3 +63,12 @@ export interface ApiReceipt {
|
||||
shippingPrices?: ApiLabeledPrice[];
|
||||
shippingMethod?: string;
|
||||
}
|
||||
|
||||
export interface ApiPremiumPromo {
|
||||
currency: string;
|
||||
monthlyAmount: string;
|
||||
videoSections: string[];
|
||||
videos: ApiDocument[];
|
||||
statusText: string;
|
||||
statusEntities: ApiMessageEntity[];
|
||||
}
|
||||
|
||||
@ -412,6 +412,7 @@ export type ApiUpdatePaymentVerificationNeeded = {
|
||||
|
||||
export type ApiUpdatePaymentStateCompleted = {
|
||||
'@type': 'updatePaymentStateCompleted';
|
||||
slug?: string;
|
||||
};
|
||||
|
||||
export type ApiUpdatePrivacy = {
|
||||
@ -516,6 +517,13 @@ export type ApiUpdateBotMenuButton = {
|
||||
button: ApiBotMenuButton;
|
||||
};
|
||||
|
||||
export type ApiUpdateTranscribedAudio = {
|
||||
'@type': 'updateTranscribedAudio';
|
||||
transcriptionId: string;
|
||||
text: string;
|
||||
isPending?: boolean;
|
||||
};
|
||||
|
||||
export type ApiUpdate = (
|
||||
ApiUpdateReady | ApiUpdateSession |
|
||||
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
|
||||
@ -539,7 +547,7 @@ export type ApiUpdate = (
|
||||
ApiUpdateGroupCallConnectionState | ApiUpdateGroupCallLeavePresentation | ApiUpdateGroupCallChatId |
|
||||
ApiUpdatePendingJoinRequests | ApiUpdatePaymentVerificationNeeded | ApiUpdatePaymentStateCompleted |
|
||||
ApiUpdatePhoneCall | ApiUpdatePhoneCallSignalingData | ApiUpdatePhoneCallMediaState |
|
||||
ApiUpdatePhoneCallConnectionState | ApiUpdateBotMenuButton
|
||||
ApiUpdatePhoneCallConnectionState | ApiUpdateBotMenuButton | ApiUpdateTranscribedAudio
|
||||
);
|
||||
|
||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||
|
||||
@ -6,6 +6,7 @@ export interface ApiUser {
|
||||
isMin: boolean;
|
||||
isSelf?: true;
|
||||
isVerified?: true;
|
||||
isPremium?: boolean;
|
||||
isContact?: true;
|
||||
type: ApiUserType;
|
||||
firstName?: string;
|
||||
@ -14,6 +15,7 @@ export interface ApiUser {
|
||||
username: string;
|
||||
phoneNumber: string;
|
||||
accessHash?: string;
|
||||
hasVideoAvatar?: boolean;
|
||||
avatarHash?: string;
|
||||
photos?: ApiPhoto[];
|
||||
botPlaceholder?: string;
|
||||
@ -36,6 +38,7 @@ export interface ApiUserFullInfo {
|
||||
commonChatsCount?: number;
|
||||
pinnedMessageId?: number;
|
||||
botInfo?: ApiBotInfo;
|
||||
profilePhoto?: ApiPhoto;
|
||||
}
|
||||
|
||||
export type ApiFakeType = 'fake' | 'scam';
|
||||
@ -51,9 +54,13 @@ export interface ApiUserStatus {
|
||||
expires?: number;
|
||||
}
|
||||
|
||||
export type ApiAttachMenuPeerType = 'self' | 'bot' | 'private' | 'chat' | 'channel';
|
||||
|
||||
export interface ApiAttachMenuBot {
|
||||
id: string;
|
||||
hasSettings?: boolean;
|
||||
shortName: string;
|
||||
peerTypes: ApiAttachMenuPeerType[];
|
||||
icons: ApiAttachMenuBotIcon[];
|
||||
}
|
||||
|
||||
|
||||
5
src/assets/premium/DeviceFrame.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="284" height="578" viewBox="0 0 284 578" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M233.32 0.281264C239.512 0.281006 244.932 0.280782 249.411 0.646737C254.162 1.03492 258.999 1.89942 263.684 4.28679C270.599 7.81013 276.221 13.4322 279.745 20.3471C282.132 25.0326 282.996 29.8694 283.385 34.6206C283.751 39.0996 283.75 44.5187 283.75 50.7112V527.351C283.75 533.544 283.751 538.963 283.385 543.442C282.996 548.193 282.132 553.03 279.745 557.715C276.221 564.63 270.599 570.252 263.684 573.776C258.999 576.163 254.162 577.028 249.411 577.416C244.932 577.782 239.513 577.782 233.32 577.781H50.68C44.4875 577.782 39.0684 577.782 34.5894 577.416C29.8382 577.028 25.0013 576.163 20.3159 573.776C13.4009 570.252 7.77888 564.63 4.25554 557.715C1.86817 553.03 1.00367 548.193 0.615487 543.442C0.249532 538.963 0.249756 533.544 0.250014 527.351V50.7115C0.249756 44.5189 0.249532 39.0997 0.615487 34.6206C1.00367 29.8694 1.86817 25.0326 4.25554 20.3471C7.77888 13.4322 13.4009 7.81013 20.3159 4.28679C25.0013 1.89942 29.8382 1.03492 34.5894 0.646737C39.0685 0.280782 44.4876 0.281006 50.6803 0.281264H233.32ZM15.95 26.3057C13.375 31.3594 13.375 37.975 13.375 51.2063V526.856C13.375 540.088 13.375 546.703 15.95 551.757C18.215 556.202 21.8292 559.816 26.2745 562.081C31.3282 564.656 37.9438 564.656 51.175 564.656H232.825C246.056 564.656 252.672 564.656 257.726 562.081C262.171 559.816 265.785 556.202 268.05 551.757C270.625 546.703 270.625 540.088 270.625 526.856V51.2063C270.625 37.975 270.625 31.3594 268.05 26.3057C265.785 21.8604 262.171 18.2463 257.726 15.9812C252.672 13.4063 246.056 13.4063 232.825 13.4063H51.175C37.9438 13.4063 31.3282 13.4063 26.2745 15.9812C21.8292 18.2463 18.215 21.8604 15.95 26.3057Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M233.196 4.21876C239.496 4.21862 244.774 4.21851 249.09 4.57114C253.598 4.93944 257.859 5.73771 261.897 7.79511C268.071 10.941 273.09 15.9606 276.236 22.1347C278.294 26.1726 279.092 30.4336 279.46 34.9412C279.813 39.2573 279.813 44.5354 279.813 50.8354V527.227C279.813 533.527 279.813 538.805 279.46 543.121C279.092 547.629 278.294 551.89 276.236 555.928C273.09 562.102 268.071 567.122 261.897 570.267C257.859 572.325 253.598 573.123 249.09 573.491C244.774 573.844 239.495 573.844 233.195 573.844H50.8051C44.5047 573.844 39.2262 573.844 34.91 573.491C30.4023 573.123 26.1413 572.325 22.1034 570.267C15.9294 567.122 10.9097 562.102 7.76386 555.928C5.70646 551.89 4.90819 547.629 4.53989 543.121C4.18726 538.805 4.18737 533.527 4.18751 527.227V50.8356C4.18737 44.5355 4.18726 39.2573 4.53989 34.9412C4.90819 30.4336 5.70646 26.1726 7.76386 22.1347C10.9097 15.9606 15.9294 10.941 22.1034 7.79511C26.1413 5.73771 30.4023 4.93944 34.91 4.57114C39.2261 4.21851 44.5042 4.21862 50.8043 4.21876H233.196ZM15.95 26.3057C13.375 31.3594 13.375 37.975 13.375 51.2062V526.856C13.375 540.087 13.375 546.703 15.95 551.757C18.215 556.202 21.8292 559.816 26.2745 562.081C31.3281 564.656 37.9438 564.656 51.175 564.656H232.825C246.056 564.656 252.672 564.656 257.726 562.081C262.171 559.816 265.785 556.202 268.05 551.757C270.625 546.703 270.625 540.087 270.625 526.856V51.2063C270.625 37.975 270.625 31.3594 268.05 26.3057C265.785 21.8604 262.171 18.2462 257.726 15.9812C252.672 13.4063 246.056 13.4063 232.825 13.4063H51.175C37.9438 13.4063 31.3281 13.4063 26.2745 15.9812C21.8292 18.2462 18.215 21.8604 15.95 26.3057Z" fill="black"/>
|
||||
<circle cx="142" cy="23.9062" r="6.5625" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
5
src/assets/premium/PremiumAds.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="10" fill="#B24CB5"/>
|
||||
<rect x="7.30762" y="8.41418" width="2" height="24" rx="1" transform="rotate(-45 7.30762 8.41418)" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6342 19.5007H11C8.79086 19.5007 7 17.7098 7 15.5007C7 14.2442 7.57935 13.123 8.48549 12.3897L17.6842 21.5883L16.0781 20.3391C15.8157 20.1351 15.6845 20.033 15.5464 19.9478C15.1662 19.7131 14.7384 19.5663 14.2941 19.5181C14.1329 19.5007 13.9667 19.5007 13.6342 19.5007ZM23.0425 24.1182L10.4609 11.5367C10.6372 11.5129 10.8172 11.5007 11 11.5007H13.6342C13.9667 11.5007 14.1329 11.5007 14.2941 11.4832C14.7384 11.435 15.1662 11.2882 15.5464 11.0535C15.6845 10.9683 15.8157 10.8663 16.0781 10.6622L18.8354 8.51757L18.8354 8.51757L18.8354 8.51756C20.4927 7.22858 21.3213 6.58409 22.017 6.59001C22.6222 6.59516 23.1926 6.8741 23.5682 7.34866C24 7.89419 24 8.94396 24 11.0435V19.9578C24 22.0573 24 23.1071 23.5682 23.6526C23.42 23.8399 23.2415 23.9967 23.0425 24.1182ZM11 22.3869C11 21.8675 11 21.6077 11.0941 21.4064C11.1934 21.1941 11.3641 21.0234 11.5764 20.9241C11.7778 20.83 12.0375 20.83 12.5569 20.83H13.4431C13.9625 20.83 14.2222 20.83 14.4236 20.9241C14.6359 21.0234 14.8066 21.1941 14.9059 21.4064C15 21.6077 15 21.8675 15 22.3869V23.5007C15 24.6052 14.1046 25.5007 13 25.5007C11.8954 25.5007 11 24.6052 11 23.5007V22.3869Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
4
src/assets/premium/PremiumBadge.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 10.3995 0 7.59921 1.08993 5.46009C2.04867 3.57847 3.57847 2.04867 5.46009 1.08993C7.59921 0 10.3995 0 16 0C21.6005 0 24.4008 0 26.5399 1.08993C28.4215 2.04867 29.9513 3.57847 30.9101 5.46009C32 7.59921 32 10.3995 32 16C32 21.6005 32 24.4008 30.9101 26.5399C29.9513 28.4215 28.4215 29.9513 26.5399 30.9101C24.4008 32 21.6005 32 16 32C10.3995 32 7.59921 32 5.46009 30.9101C3.57847 29.9513 2.04867 28.4215 1.08993 26.5399C0 24.4008 0 21.6005 0 16Z" fill="#5A6EEE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4014 22.7046L10.3894 25.775C9.8683 26.0942 9.18701 25.9306 8.86775 25.4094C8.71182 25.1549 8.66533 24.8482 8.73884 24.5589L9.51469 21.5051C9.79476 20.4027 10.5491 19.4812 11.5744 18.989L17.0422 16.3638C17.2972 16.2414 17.4046 15.9355 17.2822 15.6806C17.1831 15.4742 16.9589 15.3587 16.7333 15.3977L10.6469 16.4514C9.40971 16.6656 8.14095 16.324 7.17858 15.5175L5.25584 13.9062C4.78741 13.5136 4.72591 12.8156 5.11846 12.3472C5.30939 12.1194 5.58394 11.978 5.88029 11.9548L11.7548 11.495C12.1698 11.4625 12.5315 11.1999 12.6908 10.8153L14.9571 5.34464C15.191 4.78 15.8384 4.51189 16.403 4.7458C16.6741 4.85811 16.8895 5.07352 17.0019 5.34464L19.2681 10.8153C19.4275 11.1999 19.7891 11.4625 20.2042 11.495L26.111 11.9573C26.7203 12.005 27.1756 12.5376 27.1279 13.1469C27.1049 13.44 26.9663 13.7119 26.7426 13.9027L22.2377 17.7432C21.9206 18.0135 21.7822 18.4391 21.8797 18.8443L23.2647 24.5976C23.4077 25.1918 23.042 25.7894 22.4478 25.9325C22.1623 26.0012 21.8611 25.9536 21.6107 25.8002L16.5576 22.7046C16.2028 22.4873 15.7562 22.4873 15.4014 22.7046Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
4
src/assets/premium/PremiumChats.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 10.3995 0 7.59921 1.08993 5.46009C2.04867 3.57847 3.57847 2.04867 5.46009 1.08993C7.59921 0 10.3995 0 16 0C21.6005 0 24.4008 0 26.5399 1.08993C28.4215 2.04867 29.9513 3.57847 30.9101 5.46009C32 7.59921 32 10.3995 32 16C32 21.6005 32 24.4008 30.9101 26.5399C29.9513 28.4215 28.4215 29.9513 26.5399 30.9101C24.4008 32 21.6005 32 16 32C10.3995 32 7.59921 32 5.46009 30.9101C3.57847 29.9513 2.04867 28.4215 1.08993 26.5399C0 24.4008 0 21.6005 0 16Z" fill="#7561EB"/>
|
||||
<path d="M6 23.1373V13.4C6 11.1598 6 10.0397 6.43597 9.18404C6.81947 8.43139 7.43139 7.81947 8.18404 7.43597C9.03969 7 10.1598 7 12.4 7H19.6C21.8402 7 22.9603 7 23.816 7.43597C24.5686 7.81947 25.1805 8.43139 25.564 9.18404C26 10.0397 26 11.1598 26 13.4V15.6C26 17.8402 26 18.9603 25.564 19.816C25.1805 20.5686 24.5686 21.1805 23.816 21.564C22.9603 22 21.8402 22 19.6 22H12.3255C11.8363 22 11.5917 22 11.3615 22.0553C11.1575 22.1043 10.9624 22.1851 10.7834 22.2947C10.5816 22.4184 10.4086 22.5914 10.0627 22.9373L8.73137 24.2686C7.87462 25.1254 7.44624 25.5538 7.07846 25.5827C6.75934 25.6078 6.44749 25.4786 6.23959 25.2352C6 24.9547 6 24.3489 6 23.1373Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
4
src/assets/premium/PremiumFile.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 10.3995 0 7.59921 1.08993 5.46009C2.04867 3.57847 3.57847 2.04867 5.46009 1.08993C7.59921 0 10.3995 0 16 0C21.6005 0 24.4008 0 26.5399 1.08993C28.4215 2.04867 29.9513 3.57847 30.9101 5.46009C32 7.59921 32 10.3995 32 16C32 21.6005 32 24.4008 30.9101 26.5399C29.9513 28.4215 28.4215 29.9513 26.5399 30.9101C24.4008 32 21.6005 32 16 32C10.3995 32 7.59921 32 5.46009 30.9101C3.57847 29.9513 2.04867 28.4215 1.08993 26.5399C0 24.4008 0 21.6005 0 16Z" fill="#E36850"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.152 6.40001C13.6611 6.40001 13.9986 6.40049 14.259 6.41826C14.5114 6.43548 14.6261 6.46595 14.6963 6.49501C15.0021 6.62168 15.245 6.86463 15.3717 7.17043C15.4008 7.24059 15.4312 7.35525 15.4484 7.60768C15.4662 7.86806 15.4667 8.20556 15.4667 8.71468V11.168V11.2033C15.4667 11.7706 15.4667 12.2492 15.4987 12.6413C15.5322 13.0518 15.6052 13.4447 15.7957 13.8185C16.0851 14.3865 16.5469 14.8483 17.1149 15.1377C17.4887 15.3281 17.8816 15.4011 18.2921 15.4347C18.6841 15.4667 19.1627 15.4667 19.73 15.4667H19.7654H22.2187C22.7278 15.4667 23.0653 15.4672 23.3257 15.4849C23.5781 15.5021 23.6928 15.5326 23.7629 15.5617C24.0687 15.6883 24.3117 15.9313 24.4384 16.2371C24.4674 16.3073 24.4979 16.4219 24.5151 16.6743C24.5319 16.92 24.5332 17.2343 24.5333 17.6966V20.48C24.5333 22.2722 24.5333 23.1682 24.1846 23.8528C23.8778 24.4549 23.3882 24.9444 22.7861 25.2512C22.1016 25.6 21.2055 25.6 19.4133 25.6H12.5867C10.7945 25.6 9.89842 25.6 9.21391 25.2512C8.61179 24.9444 8.12225 24.4549 7.81545 23.8528C7.46667 23.1682 7.46667 22.2722 7.46667 20.48V11.52C7.46667 9.72783 7.46667 8.83174 7.81545 8.14722C8.12225 7.54511 8.61179 7.05557 9.21391 6.74877C9.89842 6.39999 10.7945 6.39999 12.5867 6.39999H13.152V6.40001ZM24.1292 13.8214C24.0622 13.6787 23.9872 13.5395 23.9046 13.4046C23.6408 12.9741 23.2718 12.6051 22.5339 11.8672L22.5339 11.8672L22.5339 11.8672L19.0662 8.39948L19.0662 8.39947L19.0662 8.39947C18.3282 7.66155 17.9593 7.29259 17.5287 7.02873C17.3938 6.94609 17.2547 6.87114 17.1119 6.80414C17.1705 7.02692 17.1991 7.25409 17.215 7.48715C17.2374 7.81501 17.2374 8.21331 17.2374 8.68534V8.68538V8.71468V11.168C17.2374 11.78 17.238 12.1858 17.2635 12.4971C17.2881 12.7981 17.3313 12.932 17.3734 13.0146C17.493 13.2494 17.6839 13.4403 17.9188 13.56C18.0013 13.6021 18.1353 13.6453 18.4363 13.6699C18.7476 13.6953 19.1534 13.696 19.7654 13.696H22.2187H22.248H22.248C22.7201 13.696 23.1184 13.696 23.4462 13.7184C23.6793 13.7343 23.9064 13.7628 24.1292 13.8214Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
4
src/assets/premium/PremiumLimits.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 10.3995 0 7.59921 1.08993 5.46009C2.04867 3.57847 3.57847 2.04867 5.46009 1.08993C7.59921 0 10.3995 0 16 0C21.6005 0 24.4008 0 26.5399 1.08993C28.4215 2.04867 29.9513 3.57847 30.9101 5.46009C32 7.59921 32 10.3995 32 16C32 21.6005 32 24.4008 30.9101 26.5399C29.9513 28.4215 28.4215 29.9513 26.5399 30.9101C24.4008 32 21.6005 32 16 32C10.3995 32 7.59921 32 5.46009 30.9101C3.57847 29.9513 2.04867 28.4215 1.08993 26.5399C0 24.4008 0 21.6005 0 16Z" fill="#F27C30"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.96766 9.73466C6.48238 9.34861 7.21261 9.45293 7.59866 9.96766L10.6667 14.0583L13.7347 9.96766C14.1207 9.45293 14.8509 9.34861 15.3657 9.73466C15.8804 10.1207 15.9847 10.8509 15.5987 11.3657L12.1229 16L15.5987 20.6343C15.9847 21.1491 15.8804 21.8793 15.3657 22.2653C14.8509 22.6514 14.1207 22.5471 13.7347 22.0323L10.6667 17.9417L7.59866 22.0323C7.21261 22.5471 6.48238 22.6514 5.96766 22.2653C5.45293 21.8793 5.34861 21.1491 5.73466 20.6343L9.21041 16L5.73466 11.3657C5.34861 10.8509 5.45293 10.1207 5.96766 9.73466ZM20.6667 9.50166C18.9187 9.50166 17.5017 10.9187 17.5017 12.6667V13.3333C17.5017 13.9767 18.0232 14.4983 18.6667 14.4983C19.3101 14.4983 19.8317 13.9767 19.8317 13.3333V12.6667C19.8317 12.2055 20.2055 11.8317 20.6667 11.8317H22.4492C23.3986 11.8317 24.1683 12.6013 24.1683 13.5508C24.1683 14.202 23.8004 14.7973 23.218 15.0885L19.6198 16.8876C18.3216 17.5367 17.5017 18.8634 17.5017 20.3147V21.3333C17.5017 21.9767 18.0232 22.4983 18.6667 22.4983L25.3333 22.4983C25.9767 22.4983 26.4983 21.9767 26.4983 21.3333C26.4983 20.6899 25.9767 20.1683 25.3333 20.1683L19.8388 20.1683C19.8887 19.658 20.197 19.204 20.6618 18.9716L24.26 17.1725C25.6318 16.4866 26.4983 15.0845 26.4983 13.5508C26.4983 11.3145 24.6855 9.50166 22.4492 9.50166H20.6667Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
181
src/assets/premium/PremiumLogo.svg
Normal file
@ -0,0 +1,181 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M47.3065 75.4791L27.7285 87.4727C26.0852 88.4794 23.9369 87.9633 22.9303 86.32C22.4386 85.5175 22.292 84.5503 22.5238 83.638L25.5544 71.7092C26.5877 67.6424 29.3706 64.2428 33.1533 62.4267L54.512 52.172C55.9226 51.4947 56.5171 49.8021 55.8398 48.3915C55.2914 47.2491 54.051 46.6098 52.8023 46.826L29.0274 50.942C24.4362 51.7369 19.7279 50.4691 16.1566 47.4762L8.6459 41.182C7.16885 39.9442 6.97491 37.7434 8.21272 36.2664C8.81474 35.548 9.68047 35.102 10.6149 35.0289L33.5623 33.233C35.496 33.0817 37.1812 31.8579 37.9235 30.066L46.7762 8.69618C47.5137 6.91577 49.5549 6.07037 51.3354 6.80793C52.1903 7.16207 52.8695 7.84129 53.2236 8.69618L62.0763 30.066C62.8186 31.8579 64.5038 33.0817 66.4375 33.233L89.511 35.0387C91.4322 35.1891 92.8678 36.8685 92.7175 38.7897C92.6451 39.714 92.208 40.5714 91.5025 41.1729L73.9053 56.1749C72.4277 57.4345 71.7831 59.4175 72.2375 61.3053L77.6474 83.7792C78.0984 85.6528 76.9452 87.5373 75.0716 87.9883C74.1713 88.205 73.2218 88.055 72.4322 87.5713L52.6933 75.4791C51.0404 74.4666 48.9594 74.4666 47.3065 75.4791Z" fill="white"/>
|
||||
<path d="M47.3065 75.4791L27.7285 87.4727C26.0852 88.4794 23.9369 87.9633 22.9303 86.32C22.4386 85.5175 22.292 84.5503 22.5238 83.638L25.5544 71.7092C26.5877 67.6424 29.3706 64.2428 33.1533 62.4267L54.512 52.172C55.9226 51.4947 56.5171 49.8021 55.8398 48.3915C55.2914 47.2491 54.051 46.6098 52.8023 46.826L29.0274 50.942C24.4362 51.7369 19.7279 50.4691 16.1566 47.4762L8.6459 41.182C7.16885 39.9442 6.97491 37.7434 8.21272 36.2664C8.81474 35.548 9.68047 35.102 10.6149 35.0289L33.5623 33.233C35.496 33.0817 37.1812 31.8579 37.9235 30.066L46.7762 8.69618C47.5137 6.91577 49.5549 6.07037 51.3354 6.80793C52.1903 7.16207 52.8695 7.84129 53.2236 8.69618L62.0763 30.066C62.8186 31.8579 64.5038 33.0817 66.4375 33.233L89.511 35.0387C91.4322 35.1891 92.8678 36.8685 92.7175 38.7897C92.6451 39.714 92.208 40.5714 91.5025 41.1729L73.9053 56.1749C72.4277 57.4345 71.7831 59.4175 72.2375 61.3053L77.6474 83.7792C78.0984 85.6528 76.9452 87.5373 75.0716 87.9883C74.1713 88.205 73.2218 88.055 72.4322 87.5713L52.6933 75.4791C51.0404 74.4666 48.9594 74.4666 47.3065 75.4791Z" fill="url(#paint0_linear_124_4926)"/>
|
||||
<path d="M47.3065 75.4791L27.7285 87.4727C26.0852 88.4794 23.9369 87.9633 22.9303 86.32C22.4386 85.5175 22.292 84.5503 22.5238 83.638L25.5544 71.7092C26.5877 67.6424 29.3706 64.2428 33.1533 62.4267L54.512 52.172C55.9226 51.4947 56.5171 49.8021 55.8398 48.3915C55.2914 47.2491 54.051 46.6098 52.8023 46.826L29.0274 50.942C24.4362 51.7369 19.7279 50.4691 16.1566 47.4762L8.6459 41.182C7.16885 39.9442 6.97491 37.7434 8.21272 36.2664C8.81474 35.548 9.68047 35.102 10.6149 35.0289L33.5623 33.233C35.496 33.0817 37.1812 31.8579 37.9235 30.066L46.7762 8.69618C47.5137 6.91577 49.5549 6.07037 51.3354 6.80793C52.1903 7.16207 52.8695 7.84129 53.2236 8.69618L62.0763 30.066C62.8186 31.8579 64.5038 33.0817 66.4375 33.233L89.511 35.0387C91.4322 35.1891 92.8678 36.8685 92.7175 38.7897C92.6451 39.714 92.208 40.5714 91.5025 41.1729L73.9053 56.1749C72.4277 57.4345 71.7831 59.4175 72.2375 61.3053L77.6474 83.7792C78.0984 85.6528 76.9452 87.5373 75.0716 87.9883C74.1713 88.205 73.2218 88.055 72.4322 87.5713L52.6933 75.4791C51.0404 74.4666 48.9594 74.4666 47.3065 75.4791Z" stroke="url(#paint1_linear_124_4926)" stroke-width="1.66667" style="mix-blend-mode:soft-light"/>
|
||||
<g opacity="0.3">
|
||||
<path d="M87.1958 21.9339C87.1162 22.1962 87.0513 22.4925 86.9933 22.8247C86.9318 22.4938 86.8645 22.1997 86.7836 21.9402C86.6432 21.4904 86.4523 21.1117 86.1497 20.81C85.8465 20.5076 85.4699 20.3211 85.026 20.1879C84.7934 20.1181 84.5335 20.0605 84.245 20.0089C85.0724 19.8756 85.7113 19.6695 86.1663 19.2123C86.4708 18.9063 86.6622 18.5214 86.8003 18.066C86.8792 17.806 86.9437 17.5126 87.0013 17.1838C87.0613 17.5123 87.1276 17.805 87.2077 18.0641C87.3478 18.517 87.5394 18.8994 87.8429 19.2038C88.2893 19.6514 88.9123 19.8586 89.7194 19.9966C88.9076 20.1302 88.2795 20.3367 87.8304 20.7875C87.5256 21.0935 87.3341 21.4785 87.1958 21.9339Z" fill="white"/>
|
||||
<path d="M87.1958 21.9339C87.1162 22.1962 87.0513 22.4925 86.9933 22.8247C86.9318 22.4938 86.8645 22.1997 86.7836 21.9402C86.6432 21.4904 86.4523 21.1117 86.1497 20.81C85.8465 20.5076 85.4699 20.3211 85.026 20.1879C84.7934 20.1181 84.5335 20.0605 84.245 20.0089C85.0724 19.8756 85.7113 19.6695 86.1663 19.2123C86.4708 18.9063 86.6622 18.5214 86.8003 18.066C86.8792 17.806 86.9437 17.5126 87.0013 17.1838C87.0613 17.5123 87.1276 17.805 87.2077 18.0641C87.3478 18.517 87.5394 18.8994 87.8429 19.2038C88.2893 19.6514 88.9123 19.8586 89.7194 19.9966C88.9076 20.1302 88.2795 20.3367 87.8304 20.7875C87.5256 21.0935 87.3341 21.4785 87.1958 21.9339Z" fill="url(#paint2_linear_124_4926)"/>
|
||||
<path d="M87.1958 21.9339C87.1162 22.1962 87.0513 22.4925 86.9933 22.8247C86.9318 22.4938 86.8645 22.1997 86.7836 21.9402C86.6432 21.4904 86.4523 21.1117 86.1497 20.81C85.8465 20.5076 85.4699 20.3211 85.026 20.1879C84.7934 20.1181 84.5335 20.0605 84.245 20.0089C85.0724 19.8756 85.7113 19.6695 86.1663 19.2123C86.4708 18.9063 86.6622 18.5214 86.8003 18.066C86.8792 17.806 86.9437 17.5126 87.0013 17.1838C87.0613 17.5123 87.1276 17.805 87.2077 18.0641C87.3478 18.517 87.5394 18.8994 87.8429 19.2038C88.2893 19.6514 88.9123 19.8586 89.7194 19.9966C88.9076 20.1302 88.2795 20.3367 87.8304 20.7875C87.5256 21.0935 87.3341 21.4785 87.1958 21.9339Z" stroke="url(#paint3_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
</g>
|
||||
<path d="M4.19585 55.9339C4.11625 56.1962 4.05127 56.4925 3.99329 56.8247C3.93183 56.4938 3.86453 56.1997 3.78356 55.9402C3.64322 55.4904 3.45227 55.1117 3.14971 54.81C2.84652 54.5076 2.46991 54.3211 2.02602 54.1879C1.79338 54.1181 1.53351 54.0605 1.24504 54.0089C2.07244 53.8756 2.7113 53.6695 3.16632 53.2123C3.47085 52.9063 3.66216 52.5214 3.80029 52.066C3.87917 51.806 3.94369 51.5126 4.00128 51.1838C4.06133 51.5123 4.12764 51.805 4.20775 52.0641C4.34777 52.517 4.53935 52.8994 4.84292 53.2038C5.28932 53.6514 5.9123 53.8586 6.71942 53.9966C5.9076 54.1302 5.27951 54.3367 4.83043 54.7875C4.52556 55.0935 4.33407 55.4785 4.19585 55.9339Z" fill="white"/>
|
||||
<path d="M4.19585 55.9339C4.11625 56.1962 4.05127 56.4925 3.99329 56.8247C3.93183 56.4938 3.86453 56.1997 3.78356 55.9402C3.64322 55.4904 3.45227 55.1117 3.14971 54.81C2.84652 54.5076 2.46991 54.3211 2.02602 54.1879C1.79338 54.1181 1.53351 54.0605 1.24504 54.0089C2.07244 53.8756 2.7113 53.6695 3.16632 53.2123C3.47085 52.9063 3.66216 52.5214 3.80029 52.066C3.87917 51.806 3.94369 51.5126 4.00128 51.1838C4.06133 51.5123 4.12764 51.805 4.20775 52.0641C4.34777 52.517 4.53935 52.8994 4.84292 53.2038C5.28932 53.6514 5.9123 53.8586 6.71942 53.9966C5.9076 54.1302 5.27951 54.3367 4.83043 54.7875C4.52556 55.0935 4.33407 55.4785 4.19585 55.9339Z" fill="url(#paint4_linear_124_4926)"/>
|
||||
<path d="M4.19585 55.9339C4.11625 56.1962 4.05127 56.4925 3.99329 56.8247C3.93183 56.4938 3.86453 56.1997 3.78356 55.9402C3.64322 55.4904 3.45227 55.1117 3.14971 54.81C2.84652 54.5076 2.46991 54.3211 2.02602 54.1879C1.79338 54.1181 1.53351 54.0605 1.24504 54.0089C2.07244 53.8756 2.7113 53.6695 3.16632 53.2123C3.47085 52.9063 3.66216 52.5214 3.80029 52.066C3.87917 51.806 3.94369 51.5126 4.00128 51.1838C4.06133 51.5123 4.12764 51.805 4.20775 52.0641C4.34777 52.517 4.53935 52.8994 4.84292 53.2038C5.28932 53.6514 5.9123 53.8586 6.71942 53.9966C5.9076 54.1302 5.27951 54.3367 4.83043 54.7875C4.52556 55.0935 4.33407 55.4785 4.19585 55.9339Z" stroke="url(#paint5_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
<path d="M32.2998 21.9989C30.8424 22.1689 29.8077 22.3889 29.1266 23.0726C28.7649 23.4357 28.5337 23.8961 28.3644 24.4537C28.2101 24.9622 28.0996 25.5784 27.9963 26.3166C27.8832 25.5784 27.7672 24.9658 27.6101 24.4625C27.4383 23.9117 27.2078 23.4589 26.8489 23.101C26.4893 22.7424 26.0395 22.5175 25.4966 22.3546C25.0022 22.2063 24.4041 22.1019 23.6882 22.0024C25.1497 21.8322 26.187 21.6128 26.8693 20.9272C27.2306 20.5641 27.4616 20.1038 27.6307 19.5462C27.7848 19.0384 27.8952 18.4232 27.9984 17.6862C28.1082 18.4232 28.2226 19.0372 28.3791 19.5432C28.5506 20.0979 28.7819 20.5553 29.1422 20.9165C29.8199 21.5961 30.847 21.8183 32.2998 21.9989ZM32.5 22C32.5 21.9978 32.5002 21.9956 32.5004 21.9935L32.5012 22L32.5026 22.0121C32.5005 22.0068 32.5 22.0023 32.5 22ZM23.4986 22.024L23.4382 22.4728L23.4987 22.024L23.4986 22.024Z" fill="white"/>
|
||||
<path d="M32.2998 21.9989C30.8424 22.1689 29.8077 22.3889 29.1266 23.0726C28.7649 23.4357 28.5337 23.8961 28.3644 24.4537C28.2101 24.9622 28.0996 25.5784 27.9963 26.3166C27.8832 25.5784 27.7672 24.9658 27.6101 24.4625C27.4383 23.9117 27.2078 23.4589 26.8489 23.101C26.4893 22.7424 26.0395 22.5175 25.4966 22.3546C25.0022 22.2063 24.4041 22.1019 23.6882 22.0024C25.1497 21.8322 26.187 21.6128 26.8693 20.9272C27.2306 20.5641 27.4616 20.1038 27.6307 19.5462C27.7848 19.0384 27.8952 18.4232 27.9984 17.6862C28.1082 18.4232 28.2226 19.0372 28.3791 19.5432C28.5506 20.0979 28.7819 20.5553 29.1422 20.9165C29.8199 21.5961 30.847 21.8183 32.2998 21.9989ZM32.5 22C32.5 21.9978 32.5002 21.9956 32.5004 21.9935L32.5012 22L32.5026 22.0121C32.5005 22.0068 32.5 22.0023 32.5 22ZM23.4986 22.024L23.4382 22.4728L23.4987 22.024L23.4986 22.024Z" fill="url(#paint6_linear_124_4926)"/>
|
||||
<path d="M32.2998 21.9989C30.8424 22.1689 29.8077 22.3889 29.1266 23.0726C28.7649 23.4357 28.5337 23.8961 28.3644 24.4537C28.2101 24.9622 28.0996 25.5784 27.9963 26.3166C27.8832 25.5784 27.7672 24.9658 27.6101 24.4625C27.4383 23.9117 27.2078 23.4589 26.8489 23.101C26.4893 22.7424 26.0395 22.5175 25.4966 22.3546C25.0022 22.2063 24.4041 22.1019 23.6882 22.0024C25.1497 21.8322 26.187 21.6128 26.8693 20.9272C27.2306 20.5641 27.4616 20.1038 27.6307 19.5462C27.7848 19.0384 27.8952 18.4232 27.9984 17.6862C28.1082 18.4232 28.2226 19.0372 28.3791 19.5432C28.5506 20.0979 28.7819 20.5553 29.1422 20.9165C29.8199 21.5961 30.847 21.8183 32.2998 21.9989ZM32.5 22C32.5 21.9978 32.5002 21.9956 32.5004 21.9935L32.5012 22L32.5026 22.0121C32.5005 22.0068 32.5 22.0023 32.5 22ZM23.4986 22.024L23.4382 22.4728L23.4987 22.024L23.4986 22.024Z" stroke="url(#paint7_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
<path d="M71.2998 12.9989C69.8424 13.1689 68.8077 13.3889 68.1266 14.0726C67.7649 14.4357 67.5337 14.8961 67.3644 15.4537C67.2101 15.9622 67.0996 16.5784 66.9963 17.3166C66.8832 16.5784 66.7672 15.9658 66.6101 15.4625C66.4383 14.9117 66.2078 14.4589 65.8489 14.101C65.4893 13.7424 65.0395 13.5175 64.4966 13.3546C64.0022 13.2063 63.4041 13.1019 62.6882 13.0024C64.1497 12.8322 65.187 12.6128 65.8693 11.9272C66.2306 11.5641 66.4616 11.1038 66.6307 10.5462C66.7848 10.0384 66.8952 9.42317 66.9984 8.68625C67.1082 9.42315 67.2226 10.0372 67.3791 10.5432C67.5506 11.0979 67.7819 11.5553 68.1422 11.9165C68.8199 12.5961 69.847 12.8183 71.2998 12.9989ZM71.5 13C71.5 12.9978 71.5002 12.9956 71.5004 12.9935L71.5012 13L71.5026 13.0121C71.5005 13.0068 71.5 13.0023 71.5 13ZM62.4986 13.024L62.4382 13.4728L62.4987 13.024L62.4986 13.024Z" fill="white"/>
|
||||
<path d="M71.2998 12.9989C69.8424 13.1689 68.8077 13.3889 68.1266 14.0726C67.7649 14.4357 67.5337 14.8961 67.3644 15.4537C67.2101 15.9622 67.0996 16.5784 66.9963 17.3166C66.8832 16.5784 66.7672 15.9658 66.6101 15.4625C66.4383 14.9117 66.2078 14.4589 65.8489 14.101C65.4893 13.7424 65.0395 13.5175 64.4966 13.3546C64.0022 13.2063 63.4041 13.1019 62.6882 13.0024C64.1497 12.8322 65.187 12.6128 65.8693 11.9272C66.2306 11.5641 66.4616 11.1038 66.6307 10.5462C66.7848 10.0384 66.8952 9.42317 66.9984 8.68625C67.1082 9.42315 67.2226 10.0372 67.3791 10.5432C67.5506 11.0979 67.7819 11.5553 68.1422 11.9165C68.8199 12.5961 69.847 12.8183 71.2998 12.9989ZM71.5 13C71.5 12.9978 71.5002 12.9956 71.5004 12.9935L71.5012 13L71.5026 13.0121C71.5005 13.0068 71.5 13.0023 71.5 13ZM62.4986 13.024L62.4382 13.4728L62.4987 13.024L62.4986 13.024Z" fill="url(#paint8_linear_124_4926)"/>
|
||||
<path d="M71.2998 12.9989C69.8424 13.1689 68.8077 13.3889 68.1266 14.0726C67.7649 14.4357 67.5337 14.8961 67.3644 15.4537C67.2101 15.9622 67.0996 16.5784 66.9963 17.3166C66.8832 16.5784 66.7672 15.9658 66.6101 15.4625C66.4383 14.9117 66.2078 14.4589 65.8489 14.101C65.4893 13.7424 65.0395 13.5175 64.4966 13.3546C64.0022 13.2063 63.4041 13.1019 62.6882 13.0024C64.1497 12.8322 65.187 12.6128 65.8693 11.9272C66.2306 11.5641 66.4616 11.1038 66.6307 10.5462C66.7848 10.0384 66.8952 9.42317 66.9984 8.68625C67.1082 9.42315 67.2226 10.0372 67.3791 10.5432C67.5506 11.0979 67.7819 11.5553 68.1422 11.9165C68.8199 12.5961 69.847 12.8183 71.2998 12.9989ZM71.5 13C71.5 12.9978 71.5002 12.9956 71.5004 12.9935L71.5012 13L71.5026 13.0121C71.5005 13.0068 71.5 13.0023 71.5 13ZM62.4986 13.024L62.4382 13.4728L62.4987 13.024L62.4986 13.024Z" stroke="url(#paint9_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
<g opacity="0.3">
|
||||
<path d="M15.2998 70.9989C13.8424 71.1689 12.8077 71.3889 12.1266 72.0726C11.7649 72.4357 11.5337 72.8961 11.3644 73.4537C11.2101 73.9622 11.0996 74.5784 10.9963 75.3166C10.8832 74.5784 10.7672 73.9658 10.6101 73.4625C10.4383 72.9117 10.2078 72.4589 9.84888 72.101C9.4893 71.7424 9.03946 71.5175 8.4966 71.3546C8.00217 71.2063 7.40406 71.1019 6.68822 71.0024C8.14969 70.8322 9.18698 70.6128 9.8693 69.9272C10.2306 69.5641 10.4616 69.1038 10.6307 68.5462C10.7848 68.0384 10.8952 67.4232 10.9984 66.6862C11.1082 67.4232 11.2226 68.0372 11.3791 68.5432C11.5506 69.0979 11.7819 69.5553 12.1422 69.9165C12.8199 70.5961 13.847 70.8183 15.2998 70.9989ZM15.5 71C15.5 70.9978 15.5002 70.9956 15.5004 70.9935L15.5012 71L15.5026 71.0121C15.5005 71.0068 15.5 71.0023 15.5 71ZM6.4986 71.024L6.43822 71.4728L6.49866 71.024L6.4986 71.024Z" fill="white"/>
|
||||
<path d="M15.2998 70.9989C13.8424 71.1689 12.8077 71.3889 12.1266 72.0726C11.7649 72.4357 11.5337 72.8961 11.3644 73.4537C11.2101 73.9622 11.0996 74.5784 10.9963 75.3166C10.8832 74.5784 10.7672 73.9658 10.6101 73.4625C10.4383 72.9117 10.2078 72.4589 9.84888 72.101C9.4893 71.7424 9.03946 71.5175 8.4966 71.3546C8.00217 71.2063 7.40406 71.1019 6.68822 71.0024C8.14969 70.8322 9.18698 70.6128 9.8693 69.9272C10.2306 69.5641 10.4616 69.1038 10.6307 68.5462C10.7848 68.0384 10.8952 67.4232 10.9984 66.6862C11.1082 67.4232 11.2226 68.0372 11.3791 68.5432C11.5506 69.0979 11.7819 69.5553 12.1422 69.9165C12.8199 70.5961 13.847 70.8183 15.2998 70.9989ZM15.5 71C15.5 70.9978 15.5002 70.9956 15.5004 70.9935L15.5012 71L15.5026 71.0121C15.5005 71.0068 15.5 71.0023 15.5 71ZM6.4986 71.024L6.43822 71.4728L6.49866 71.024L6.4986 71.024Z" fill="url(#paint10_linear_124_4926)"/>
|
||||
<path d="M15.2998 70.9989C13.8424 71.1689 12.8077 71.3889 12.1266 72.0726C11.7649 72.4357 11.5337 72.8961 11.3644 73.4537C11.2101 73.9622 11.0996 74.5784 10.9963 75.3166C10.8832 74.5784 10.7672 73.9658 10.6101 73.4625C10.4383 72.9117 10.2078 72.4589 9.84888 72.101C9.4893 71.7424 9.03946 71.5175 8.4966 71.3546C8.00217 71.2063 7.40406 71.1019 6.68822 71.0024C8.14969 70.8322 9.18698 70.6128 9.8693 69.9272C10.2306 69.5641 10.4616 69.1038 10.6307 68.5462C10.7848 68.0384 10.8952 67.4232 10.9984 66.6862C11.1082 67.4232 11.2226 68.0372 11.3791 68.5432C11.5506 69.0979 11.7819 69.5553 12.1422 69.9165C12.8199 70.5961 13.847 70.8183 15.2998 70.9989ZM15.5 71C15.5 70.9978 15.5002 70.9956 15.5004 70.9935L15.5012 71L15.5026 71.0121C15.5005 71.0068 15.5 71.0023 15.5 71ZM6.4986 71.024L6.43822 71.4728L6.49866 71.024L6.4986 71.024Z" stroke="url(#paint11_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
</g>
|
||||
<path d="M96.2998 75.9989C94.8424 76.1689 93.8077 76.3889 93.1266 77.0726C92.7649 77.4357 92.5337 77.8961 92.3644 78.4537C92.2101 78.9622 92.0996 79.5784 91.9963 80.3166C91.8832 79.5784 91.7672 78.9658 91.6101 78.4625C91.4383 77.9117 91.2078 77.4589 90.8489 77.101C90.4893 76.7424 90.0395 76.5175 89.4966 76.3546C89.0022 76.2063 88.4041 76.1019 87.6882 76.0024C89.1497 75.8322 90.187 75.6128 90.8693 74.9272C91.2306 74.5641 91.4616 74.1038 91.6307 73.5462C91.7848 73.0384 91.8952 72.4232 91.9984 71.6862C92.1082 72.4232 92.2226 73.0372 92.3791 73.5432C92.5506 74.0979 92.7819 74.5553 93.1422 74.9165C93.8199 75.5961 94.847 75.8183 96.2998 75.9989ZM96.5 76C96.5 75.9978 96.5002 75.9956 96.5004 75.9935L96.5012 76L96.5026 76.0121C96.5005 76.0068 96.5 76.0023 96.5 76ZM87.4986 76.024L87.4382 76.4728L87.4987 76.024L87.4986 76.024Z" fill="white"/>
|
||||
<path d="M96.2998 75.9989C94.8424 76.1689 93.8077 76.3889 93.1266 77.0726C92.7649 77.4357 92.5337 77.8961 92.3644 78.4537C92.2101 78.9622 92.0996 79.5784 91.9963 80.3166C91.8832 79.5784 91.7672 78.9658 91.6101 78.4625C91.4383 77.9117 91.2078 77.4589 90.8489 77.101C90.4893 76.7424 90.0395 76.5175 89.4966 76.3546C89.0022 76.2063 88.4041 76.1019 87.6882 76.0024C89.1497 75.8322 90.187 75.6128 90.8693 74.9272C91.2306 74.5641 91.4616 74.1038 91.6307 73.5462C91.7848 73.0384 91.8952 72.4232 91.9984 71.6862C92.1082 72.4232 92.2226 73.0372 92.3791 73.5432C92.5506 74.0979 92.7819 74.5553 93.1422 74.9165C93.8199 75.5961 94.847 75.8183 96.2998 75.9989ZM96.5 76C96.5 75.9978 96.5002 75.9956 96.5004 75.9935L96.5012 76L96.5026 76.0121C96.5005 76.0068 96.5 76.0023 96.5 76ZM87.4986 76.024L87.4382 76.4728L87.4987 76.024L87.4986 76.024Z" fill="url(#paint12_linear_124_4926)"/>
|
||||
<path d="M96.2998 75.9989C94.8424 76.1689 93.8077 76.3889 93.1266 77.0726C92.7649 77.4357 92.5337 77.8961 92.3644 78.4537C92.2101 78.9622 92.0996 79.5784 91.9963 80.3166C91.8832 79.5784 91.7672 78.9658 91.6101 78.4625C91.4383 77.9117 91.2078 77.4589 90.8489 77.101C90.4893 76.7424 90.0395 76.5175 89.4966 76.3546C89.0022 76.2063 88.4041 76.1019 87.6882 76.0024C89.1497 75.8322 90.187 75.6128 90.8693 74.9272C91.2306 74.5641 91.4616 74.1038 91.6307 73.5462C91.7848 73.0384 91.8952 72.4232 91.9984 71.6862C92.1082 72.4232 92.2226 73.0372 92.3791 73.5432C92.5506 74.0979 92.7819 74.5553 93.1422 74.9165C93.8199 75.5961 94.847 75.8183 96.2998 75.9989ZM96.5 76C96.5 75.9978 96.5002 75.9956 96.5004 75.9935L96.5012 76L96.5026 76.0121C96.5005 76.0068 96.5 76.0023 96.5 76ZM87.4986 76.024L87.4382 76.4728L87.4987 76.024L87.4986 76.024Z" stroke="url(#paint13_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
<g opacity="0.3">
|
||||
<path d="M15.1958 16.9339C15.1162 17.1962 15.0513 17.4925 14.9933 17.8247C14.9318 17.4938 14.8645 17.1997 14.7836 16.9402C14.6432 16.4904 14.4523 16.1117 14.1497 15.81C13.8465 15.5076 13.4699 15.3211 13.026 15.1879C12.7934 15.1181 12.5335 15.0605 12.245 15.0089C13.0724 14.8756 13.7113 14.6695 14.1663 14.2123C14.4708 13.9063 14.6622 13.5214 14.8003 13.066C14.8792 12.806 14.9437 12.5126 15.0013 12.1838C15.0613 12.5123 15.1276 12.805 15.2077 13.0641C15.3478 13.517 15.5394 13.8994 15.8429 14.2038C16.2893 14.6514 16.9123 14.8586 17.7194 14.9966C16.9076 15.1302 16.2795 15.3367 15.8304 15.7875C15.5256 16.0935 15.3341 16.4785 15.1958 16.9339Z" fill="white"/>
|
||||
<path d="M15.1958 16.9339C15.1162 17.1962 15.0513 17.4925 14.9933 17.8247C14.9318 17.4938 14.8645 17.1997 14.7836 16.9402C14.6432 16.4904 14.4523 16.1117 14.1497 15.81C13.8465 15.5076 13.4699 15.3211 13.026 15.1879C12.7934 15.1181 12.5335 15.0605 12.245 15.0089C13.0724 14.8756 13.7113 14.6695 14.1663 14.2123C14.4708 13.9063 14.6622 13.5214 14.8003 13.066C14.8792 12.806 14.9437 12.5126 15.0013 12.1838C15.0613 12.5123 15.1276 12.805 15.2077 13.0641C15.3478 13.517 15.5394 13.8994 15.8429 14.2038C16.2893 14.6514 16.9123 14.8586 17.7194 14.9966C16.9076 15.1302 16.2795 15.3367 15.8304 15.7875C15.5256 16.0935 15.3341 16.4785 15.1958 16.9339Z" fill="url(#paint14_linear_124_4926)"/>
|
||||
<path d="M15.1958 16.9339C15.1162 17.1962 15.0513 17.4925 14.9933 17.8247C14.9318 17.4938 14.8645 17.1997 14.7836 16.9402C14.6432 16.4904 14.4523 16.1117 14.1497 15.81C13.8465 15.5076 13.4699 15.3211 13.026 15.1879C12.7934 15.1181 12.5335 15.0605 12.245 15.0089C13.0724 14.8756 13.7113 14.6695 14.1663 14.2123C14.4708 13.9063 14.6622 13.5214 14.8003 13.066C14.8792 12.806 14.9437 12.5126 15.0013 12.1838C15.0613 12.5123 15.1276 12.805 15.2077 13.0641C15.3478 13.517 15.5394 13.8994 15.8429 14.2038C16.2893 14.6514 16.9123 14.8586 17.7194 14.9966C16.9076 15.1302 16.2795 15.3367 15.8304 15.7875C15.5256 16.0935 15.3341 16.4785 15.1958 16.9339Z" stroke="url(#paint15_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
</g>
|
||||
<g opacity="0.6">
|
||||
<path d="M20.1958 60.9339C20.1162 61.1962 20.0513 61.4925 19.9933 61.8247C19.9318 61.4938 19.8645 61.1997 19.7836 60.9402C19.6432 60.4904 19.4523 60.1117 19.1497 59.81C18.8465 59.5076 18.4699 59.3211 18.026 59.1879C17.7934 59.1181 17.5335 59.0605 17.245 59.0089C18.0724 58.8756 18.7113 58.6695 19.1663 58.2123C19.4708 57.9063 19.6622 57.5214 19.8003 57.066C19.8792 56.806 19.9437 56.5126 20.0013 56.1838C20.0613 56.5123 20.1276 56.805 20.2077 57.0641C20.3478 57.517 20.5394 57.8994 20.8429 58.2038C21.2893 58.6514 21.9123 58.8586 22.7194 58.9966C21.9076 59.1302 21.2795 59.3367 20.8304 59.7875C20.5256 60.0935 20.3341 60.4785 20.1958 60.9339Z" fill="white"/>
|
||||
<path d="M20.1958 60.9339C20.1162 61.1962 20.0513 61.4925 19.9933 61.8247C19.9318 61.4938 19.8645 61.1997 19.7836 60.9402C19.6432 60.4904 19.4523 60.1117 19.1497 59.81C18.8465 59.5076 18.4699 59.3211 18.026 59.1879C17.7934 59.1181 17.5335 59.0605 17.245 59.0089C18.0724 58.8756 18.7113 58.6695 19.1663 58.2123C19.4708 57.9063 19.6622 57.5214 19.8003 57.066C19.8792 56.806 19.9437 56.5126 20.0013 56.1838C20.0613 56.5123 20.1276 56.805 20.2077 57.0641C20.3478 57.517 20.5394 57.8994 20.8429 58.2038C21.2893 58.6514 21.9123 58.8586 22.7194 58.9966C21.9076 59.1302 21.2795 59.3367 20.8304 59.7875C20.5256 60.0935 20.3341 60.4785 20.1958 60.9339Z" fill="url(#paint16_linear_124_4926)"/>
|
||||
<path d="M20.1958 60.9339C20.1162 61.1962 20.0513 61.4925 19.9933 61.8247C19.9318 61.4938 19.8645 61.1997 19.7836 60.9402C19.6432 60.4904 19.4523 60.1117 19.1497 59.81C18.8465 59.5076 18.4699 59.3211 18.026 59.1879C17.7934 59.1181 17.5335 59.0605 17.245 59.0089C18.0724 58.8756 18.7113 58.6695 19.1663 58.2123C19.4708 57.9063 19.6622 57.5214 19.8003 57.066C19.8792 56.806 19.9437 56.5126 20.0013 56.1838C20.0613 56.5123 20.1276 56.805 20.2077 57.0641C20.3478 57.517 20.5394 57.8994 20.8429 58.2038C21.2893 58.6514 21.9123 58.8586 22.7194 58.9966C21.9076 59.1302 21.2795 59.3367 20.8304 59.7875C20.5256 60.0935 20.3341 60.4785 20.1958 60.9339Z" stroke="url(#paint17_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
</g>
|
||||
<g opacity="0.3">
|
||||
<path d="M83.1958 64.9339C83.1162 65.1962 83.0513 65.4925 82.9933 65.8247C82.9318 65.4938 82.8645 65.1997 82.7836 64.9402C82.6432 64.4904 82.4523 64.1117 82.1497 63.81C81.8465 63.5076 81.4699 63.3211 81.026 63.1879C80.7934 63.1181 80.5335 63.0605 80.245 63.0089C81.0724 62.8756 81.7113 62.6695 82.1663 62.2123C82.4708 61.9063 82.6622 61.5214 82.8003 61.066C82.8792 60.806 82.9437 60.5126 83.0013 60.1838C83.0613 60.5123 83.1276 60.805 83.2077 61.0641C83.3478 61.517 83.5394 61.8994 83.8429 62.2038C84.2893 62.6514 84.9123 62.8586 85.7194 62.9966C84.9076 63.1302 84.2795 63.3367 83.8304 63.7875C83.5256 64.0935 83.3341 64.4785 83.1958 64.9339Z" fill="white"/>
|
||||
<path d="M83.1958 64.9339C83.1162 65.1962 83.0513 65.4925 82.9933 65.8247C82.9318 65.4938 82.8645 65.1997 82.7836 64.9402C82.6432 64.4904 82.4523 64.1117 82.1497 63.81C81.8465 63.5076 81.4699 63.3211 81.026 63.1879C80.7934 63.1181 80.5335 63.0605 80.245 63.0089C81.0724 62.8756 81.7113 62.6695 82.1663 62.2123C82.4708 61.9063 82.6622 61.5214 82.8003 61.066C82.8792 60.806 82.9437 60.5126 83.0013 60.1838C83.0613 60.5123 83.1276 60.805 83.2077 61.0641C83.3478 61.517 83.5394 61.8994 83.8429 62.2038C84.2893 62.6514 84.9123 62.8586 85.7194 62.9966C84.9076 63.1302 84.2795 63.3367 83.8304 63.7875C83.5256 64.0935 83.3341 64.4785 83.1958 64.9339Z" fill="url(#paint18_linear_124_4926)"/>
|
||||
<path d="M83.1958 64.9339C83.1162 65.1962 83.0513 65.4925 82.9933 65.8247C82.9318 65.4938 82.8645 65.1997 82.7836 64.9402C82.6432 64.4904 82.4523 64.1117 82.1497 63.81C81.8465 63.5076 81.4699 63.3211 81.026 63.1879C80.7934 63.1181 80.5335 63.0605 80.245 63.0089C81.0724 62.8756 81.7113 62.6695 82.1663 62.2123C82.4708 61.9063 82.6622 61.5214 82.8003 61.066C82.8792 60.806 82.9437 60.5126 83.0013 60.1838C83.0613 60.5123 83.1276 60.805 83.2077 61.0641C83.3478 61.517 83.5394 61.8994 83.8429 62.2038C84.2893 62.6514 84.9123 62.8586 85.7194 62.9966C84.9076 63.1302 84.2795 63.3367 83.8304 63.7875C83.5256 64.0935 83.3341 64.4785 83.1958 64.9339Z" stroke="url(#paint19_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
</g>
|
||||
<g opacity="0.6">
|
||||
<path d="M74.1958 25.9339C74.1162 26.1962 74.0513 26.4925 73.9933 26.8247C73.9318 26.4938 73.8645 26.1997 73.7836 25.9402C73.6432 25.4904 73.4523 25.1117 73.1497 24.81C72.8465 24.5076 72.4699 24.3211 72.026 24.1879C71.7934 24.1181 71.5335 24.0605 71.245 24.0089C72.0724 23.8756 72.7113 23.6695 73.1663 23.2123C73.4708 22.9063 73.6622 22.5214 73.8003 22.066C73.8792 21.806 73.9437 21.5126 74.0013 21.1838C74.0613 21.5123 74.1276 21.805 74.2077 22.0641C74.3478 22.517 74.5394 22.8994 74.8429 23.2038C75.2893 23.6514 75.9123 23.8586 76.7194 23.9966C75.9076 24.1302 75.2795 24.3367 74.8304 24.7875C74.5256 25.0935 74.3341 25.4785 74.1958 25.9339Z" fill="white"/>
|
||||
<path d="M74.1958 25.9339C74.1162 26.1962 74.0513 26.4925 73.9933 26.8247C73.9318 26.4938 73.8645 26.1997 73.7836 25.9402C73.6432 25.4904 73.4523 25.1117 73.1497 24.81C72.8465 24.5076 72.4699 24.3211 72.026 24.1879C71.7934 24.1181 71.5335 24.0605 71.245 24.0089C72.0724 23.8756 72.7113 23.6695 73.1663 23.2123C73.4708 22.9063 73.6622 22.5214 73.8003 22.066C73.8792 21.806 73.9437 21.5126 74.0013 21.1838C74.0613 21.5123 74.1276 21.805 74.2077 22.0641C74.3478 22.517 74.5394 22.8994 74.8429 23.2038C75.2893 23.6514 75.9123 23.8586 76.7194 23.9966C75.9076 24.1302 75.2795 24.3367 74.8304 24.7875C74.5256 25.0935 74.3341 25.4785 74.1958 25.9339Z" fill="url(#paint20_linear_124_4926)"/>
|
||||
<path d="M74.1958 25.9339C74.1162 26.1962 74.0513 26.4925 73.9933 26.8247C73.9318 26.4938 73.8645 26.1997 73.7836 25.9402C73.6432 25.4904 73.4523 25.1117 73.1497 24.81C72.8465 24.5076 72.4699 24.3211 72.026 24.1879C71.7934 24.1181 71.5335 24.0605 71.245 24.0089C72.0724 23.8756 72.7113 23.6695 73.1663 23.2123C73.4708 22.9063 73.6622 22.5214 73.8003 22.066C73.8792 21.806 73.9437 21.5126 74.0013 21.1838C74.0613 21.5123 74.1276 21.805 74.2077 22.0641C74.3478 22.517 74.5394 22.8994 74.8429 23.2038C75.2893 23.6514 75.9123 23.8586 76.7194 23.9966C75.9076 24.1302 75.2795 24.3367 74.8304 24.7875C74.5256 25.0935 74.3341 25.4785 74.1958 25.9339Z" stroke="url(#paint21_linear_124_4926)" style="mix-blend-mode:soft-light"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_4926" x1="9" y1="68.5" x2="90.1475" y2="3.67735" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_124_4926" x1="100.417" y1="31.6667" x2="50.063" y2="47.3132" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_124_4926" x1="83" y1="21.3624" x2="90.8409" y2="15.8603" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_124_4926" x1="91.6304" y1="18.4957" x2="86.965" y2="19.8823" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_124_4926" x1="-4.71218e-07" y1="55.3624" x2="7.84087" y2="49.8603" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_124_4926" x1="8.63037" y1="52.4957" x2="3.96502" y2="53.8823" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_124_4926" x1="23" y1="23.703" x2="32.8011" y2="16.8253" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_124_4926" x1="33.788" y1="20.1197" x2="27.9563" y2="21.8529" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_124_4926" x1="62" y1="14.703" x2="71.8011" y2="7.82533" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear_124_4926" x1="72.788" y1="11.1197" x2="66.9563" y2="12.8529" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear_124_4926" x1="6" y1="72.703" x2="15.8011" y2="65.8253" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint11_linear_124_4926" x1="16.788" y1="69.1197" x2="10.9563" y2="70.8529" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint12_linear_124_4926" x1="87" y1="77.703" x2="96.8011" y2="70.8253" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint13_linear_124_4926" x1="97.788" y1="74.1197" x2="91.9563" y2="75.8529" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint14_linear_124_4926" x1="11" y1="16.3624" x2="18.8409" y2="10.8603" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint15_linear_124_4926" x1="19.6304" y1="13.4957" x2="14.965" y2="14.8823" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint16_linear_124_4926" x1="16" y1="60.3624" x2="23.8409" y2="54.8603" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint17_linear_124_4926" x1="24.6304" y1="57.4957" x2="19.965" y2="58.8823" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint18_linear_124_4926" x1="79" y1="64.3624" x2="86.8409" y2="58.8603" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint19_linear_124_4926" x1="87.6304" y1="61.4957" x2="82.965" y2="62.8823" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint20_linear_124_4926" x1="70" y1="25.3624" x2="77.8409" y2="19.8603" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF"/>
|
||||
<stop offset="0.439058" stop-color="#976FFF"/>
|
||||
<stop offset="1" stop-color="#E46ACE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint21_linear_124_4926" x1="78.6304" y1="22.4957" x2="73.965" y2="23.8823" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="0.520833" stop-color="white"/>
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 33 KiB |
4
src/assets/premium/PremiumReactions.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 10.3995 0 7.59921 1.08993 5.46009C2.04867 3.57847 3.57847 2.04867 5.46009 1.08993C7.59921 0 10.3995 0 16 0C21.6005 0 24.4008 0 26.5399 1.08993C28.4215 2.04867 29.9513 3.57847 30.9101 5.46009C32 7.59921 32 10.3995 32 16C32 21.6005 32 24.4008 30.9101 26.5399C29.9513 28.4215 28.4215 29.9513 26.5399 30.9101C24.4008 32 21.6005 32 16 32C10.3995 32 7.59921 32 5.46009 30.9101C3.57847 29.9513 2.04867 28.4215 1.08993 26.5399C0 24.4008 0 21.6005 0 16Z" fill="#A34ED0"/>
|
||||
<path d="M16 25.2504C16.2472 25.2504 16.5988 25.0698 16.884 24.8892C22.1973 21.4675 25.6 17.4564 25.6 13.3883C25.6 9.89994 23.1953 7.46667 20.1632 7.46667C18.2717 7.46667 16.8555 8.51222 16 10.0805C15.1636 8.52172 13.7378 7.46667 11.8464 7.46667C8.81428 7.46667 6.40002 9.89994 6.40002 13.3883C6.40002 17.4564 9.8028 21.4675 15.1161 24.8892C15.4107 25.0698 15.7624 25.2504 16 25.2504Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 984 B |
4
src/assets/premium/PremiumSpeed.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 10.3995 0 7.59921 1.08993 5.46009C2.04867 3.57847 3.57847 2.04867 5.46009 1.08993C7.59921 0 10.3995 0 16 0C21.6005 0 24.4008 0 26.5399 1.08993C28.4215 2.04867 29.9513 3.57847 30.9101 5.46009C32 7.59921 32 10.3995 32 16C32 21.6005 32 24.4008 30.9101 26.5399C29.9513 28.4215 28.4215 29.9513 26.5399 30.9101C24.4008 32 21.6005 32 16 32C10.3995 32 7.59921 32 5.46009 30.9101C3.57847 29.9513 2.04867 28.4215 1.08993 26.5399C0 24.4008 0 21.6005 0 16Z" fill="#D15078"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.9431 21.6456C27.4332 21.6857 27.7374 21.2341 27.7374 20.5076C27.7374 18.239 27.1709 16.0675 26.1648 14.1834L23.6474 17.0423C23.9605 18.1516 24.1212 19.3489 24.1212 20.5854C24.1212 21.3118 24.453 21.6055 25.9431 21.6456ZM22.9008 10.2546C20.9911 8.75039 18.6244 7.84753 16.002 7.84753C9.29914 7.84753 4.2666 13.7335 4.2666 20.4949C4.2666 21.5779 4.65406 21.6438 5.30003 21.6478H5.54718C6.18185 21.6438 6.47326 21.5779 6.47326 20.4949C6.47326 15.1864 10.6338 10.6908 15.8964 10.6908C17.6083 10.6908 19.0768 11.1761 20.2791 12.0137L22.9008 10.2546ZM18.5812 15.961C18.9954 16.1768 19.331 16.4439 19.5877 16.7621C19.8433 17.0787 20.0582 17.4421 20.2324 17.8522C20.3191 18.0561 20.5547 18.1512 20.7586 18.0645C20.8101 18.0426 20.8565 18.0102 20.8934 17.9682L28.4039 9.92015C28.7063 9.59615 28.6888 9.08839 28.3648 8.78603C28.0813 8.52151 27.6493 8.49748 27.3382 8.72895L18.529 15.2847C18.3498 15.415 18.3126 15.6663 18.4446 15.8443C18.4807 15.893 18.5274 15.9329 18.5812 15.961ZM16.1059 22.4C17.7582 22.4 19.0976 21.041 19.0976 19.3646C19.0976 17.6882 17.7582 16.3292 16.1059 16.3292C14.4537 16.3292 13.1143 17.6882 13.1143 19.3646C13.1143 21.041 14.4537 22.4 16.1059 22.4Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
4
src/assets/premium/PremiumStickers.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 10.3995 0 7.59921 1.08993 5.46009C2.04867 3.57847 3.57847 2.04867 5.46009 1.08993C7.59921 0 10.3995 0 16 0C21.6005 0 24.4008 0 26.5399 1.08993C28.4215 2.04867 29.9513 3.57847 30.9101 5.46009C32 7.59921 32 10.3995 32 16C32 21.6005 32 24.4008 30.9101 26.5399C29.9513 28.4215 28.4215 29.9513 26.5399 30.9101C24.4008 32 21.6005 32 16 32C10.3995 32 7.59921 32 5.46009 30.9101C3.57847 29.9513 2.04867 28.4215 1.08993 26.5399C0 24.4008 0 21.6005 0 16Z" fill="#9054E9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.86506 8.72963C6.40002 9.64232 6.40002 10.8371 6.40002 13.2267V19.6923C6.40002 21.2192 6.40002 21.9827 6.59322 22.6027C7.01037 23.9414 8.05865 24.9896 9.39734 25.4068C10.0173 25.6 10.7808 25.6 12.3077 25.6C14.0084 25.6 15.2881 25.6 16.3162 25.5473C16.4183 25.5304 16.4887 25.5096 16.5449 25.4863C16.8939 25.3417 17.1711 25.0645 17.3156 24.7156C17.3546 24.6215 17.3873 24.4841 17.4053 24.217C17.4236 23.9443 17.424 23.5926 17.424 23.077V23.05V23.05C17.424 22.3782 17.424 21.8221 17.4467 21.3581C16.9898 21.4601 16.5066 21.5093 16 21.5093C14.4712 21.5093 13.2611 20.8973 12.4495 20.307C12.0423 20.0109 11.7275 19.7148 11.5129 19.4908C11.4053 19.3785 11.322 19.2834 11.264 19.2143C11.2349 19.1798 11.2122 19.1516 11.1958 19.1309L11.176 19.1056L11.1697 19.0974L11.1675 19.0944L11.1666 19.0933L11.1662 19.0927C11.166 19.0925 11.1659 19.0923 11.7333 18.6667L12.3008 18.2411L12.3005 18.2406C12.3006 18.2409 12.3008 18.2411 11.7333 18.6667L11.1659 19.0923C10.9308 18.7789 10.9943 18.3343 11.3077 18.0992C11.621 17.8643 12.0652 17.9276 12.3004 18.2405L12.3002 18.2403L12.3 18.24L12.3081 18.2503C12.3165 18.2609 12.3307 18.2786 12.3506 18.3023C12.3905 18.3499 12.4531 18.4215 12.5371 18.5092C12.7058 18.6852 12.9577 18.9225 13.2839 19.1597C13.9389 19.636 14.8621 20.0907 16 20.0907C16.6721 20.0907 17.2491 19.9849 17.7424 19.7882C17.7483 19.7744 17.7542 19.7607 17.7603 19.747C18.1535 18.8615 18.8615 18.1535 19.747 17.7603C20.1643 17.5751 20.6139 17.4973 21.1323 17.4602C21.6389 17.424 22.2655 17.424 23.05 17.424H23.05H23.077C23.5926 17.424 23.9443 17.4236 24.217 17.4053C24.4841 17.3873 24.6215 17.3546 24.7156 17.3156C25.0645 17.1711 25.3417 16.8939 25.4863 16.5449C25.5096 16.4887 25.5305 16.4181 25.5473 16.3159C25.6 15.2879 25.6 14.0082 25.6 12.3077C25.6 10.7808 25.6 10.0173 25.4068 9.39731C24.9897 8.05862 23.9414 7.01034 22.6027 6.59319C21.9827 6.39999 21.2193 6.39999 19.6923 6.39999H13.2267C10.8371 6.39999 9.64236 6.39999 8.72967 6.86503C7.92684 7.27409 7.27412 7.92681 6.86506 8.72963ZM25.229 18.6383C24.94 18.7529 24.6378 18.7989 24.3121 18.8208C23.9862 18.8427 23.5867 18.8427 23.1012 18.8427H23.077C22.2594 18.8427 21.6835 18.8431 21.2335 18.8753C20.7896 18.907 20.5255 18.9669 20.3227 19.0569C19.7585 19.3074 19.3074 19.7585 19.0569 20.3227C18.9669 20.5255 18.907 20.7896 18.8753 21.2335C18.8431 21.6835 18.8427 22.2594 18.8427 23.077V23.1012C18.8427 23.5867 18.8427 23.9862 18.8208 24.3121C18.7989 24.6378 18.7529 24.9399 18.6383 25.2289C18.7118 25.2088 18.7844 25.1877 18.8561 25.1653C21.8681 24.2267 24.2267 21.8681 25.1653 18.856C25.1877 18.7844 25.2089 18.7118 25.229 18.6383ZM14.4 13.6667C14.4 14.5871 13.803 15.3333 13.0667 15.3333C12.3303 15.3333 11.7333 14.5871 11.7333 13.6667C11.7333 12.7462 12.3303 12 13.0667 12C13.803 12 14.4 12.7462 14.4 13.6667ZM20.2667 13.6667C20.2667 14.5871 19.6697 15.3333 18.9333 15.3333C18.1969 15.3333 17.6 14.5871 17.6 13.6667C17.6 12.7462 18.1969 12 18.9333 12C19.6697 12 20.2667 12.7462 20.2667 13.6667Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
4
src/assets/premium/PremiumVideo.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 10.3995 0 7.59921 1.08993 5.46009C2.04867 3.57847 3.57847 2.04867 5.46009 1.08993C7.59921 0 10.3995 0 16 0C21.6005 0 24.4008 0 26.5399 1.08993C28.4215 2.04867 29.9513 3.57847 30.9101 5.46009C32 7.59921 32 10.3995 32 16C32 21.6005 32 24.4008 30.9101 26.5399C29.9513 28.4215 28.4215 29.9513 26.5399 30.9101C24.4008 32 21.6005 32 16 32C10.3995 32 7.59921 32 5.46009 30.9101C3.57847 29.9513 2.04867 28.4215 1.08993 26.5399C0 24.4008 0 21.6005 0 16Z" fill="#547AFF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 27C22.0751 27 27 22.0751 27 16C27 9.92487 22.0751 5 16 5C9.92487 5 5 9.92487 5 16C5 22.0751 9.92487 27 16 27ZM21.2687 16.8875L14.3583 21.4944C13.6495 21.967 12.7 21.4589 12.7 20.6069V11.3931C12.7 10.5411 13.6495 10.033 14.3583 10.5056L21.2687 15.1125C21.902 15.5347 21.902 16.4653 21.2687 16.8875Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 950 B |
4
src/assets/premium/PremiumVoice.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 10.3995 0 7.59921 1.08993 5.46009C2.04867 3.57847 3.57847 2.04867 5.46009 1.08993C7.59921 0 10.3995 0 16 0C21.6005 0 24.4008 0 26.5399 1.08993C28.4215 2.04867 29.9513 3.57847 30.9101 5.46009C32 7.59921 32 10.3995 32 16C32 21.6005 32 24.4008 30.9101 26.5399C29.9513 28.4215 28.4215 29.9513 26.5399 30.9101C24.4008 32 21.6005 32 16 32C10.3995 32 7.59921 32 5.46009 30.9101C3.57847 29.9513 2.04867 28.4215 1.08993 26.5399C0 24.4008 0 21.6005 0 16Z" fill="#C14998"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 6C13.7909 6 12 7.79086 12 10V15C12 17.2091 13.7909 19 16 19C18.2091 19 20 17.2091 20 15V10C20 7.79086 18.2091 6 16 6ZM8.5 14C9.05228 14 9.5 14.4477 9.5 15C9.5 18.5899 12.4101 21.5 16 21.5C19.5899 21.5 22.5 18.5899 22.5 15C22.5 14.4477 22.9477 14 23.5 14C24.0523 14 24.5 14.4477 24.5 15C24.5 19.3561 21.2232 22.9469 17 23.4418V25.5C17 26.0523 16.5523 26.5 16 26.5C15.4477 26.5 15 26.0523 15 25.5V23.4418C10.7768 22.9469 7.5 19.3561 7.5 15C7.5 14.4477 7.94772 14 8.5 14Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/reaction-thumbs-premium.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 44 KiB |
@ -11,7 +11,11 @@ export { default as NewContactModal } from '../components/main/NewContactModal';
|
||||
export { default as WebAppModal } from '../components/main/WebAppModal';
|
||||
export { default as BotTrustModal } from '../components/main/BotTrustModal';
|
||||
export { default as BotAttachModal } from '../components/main/BotAttachModal';
|
||||
export { default as DeleteFolderDialog } from '../components/main/DeleteFolderDialog';
|
||||
export { default as PremiumMainModal } from '../components/main/premium/PremiumMainModal';
|
||||
export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal';
|
||||
|
||||
export { default as AboutAdsModal } from '../components/common/AboutAdsModal';
|
||||
export { default as CalendarModal } from '../components/common/CalendarModal';
|
||||
export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal';
|
||||
export { default as PinMessageModal } from '../components/common/PinMessageModal';
|
||||
@ -34,6 +38,8 @@ export { default as ArchivedChats } from '../components/left/ArchivedChats';
|
||||
export { default as ChatFolderModal } from '../components/left/ChatFolderModal';
|
||||
|
||||
export { default as ContextMenuContainer } from '../components/middle/message/ContextMenuContainer';
|
||||
export { default as SponsoredMessageContextMenuContainer }
|
||||
from '../components/middle/message/SponsoredMessageContextMenuContainer';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
export { default as StickerSetModal } from '../components/common/StickerSetModal';
|
||||
export { default as HeaderMenuContainer } from '../components/middle/HeaderMenuContainer';
|
||||
|
||||
@ -79,7 +79,7 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
|
||||
onClick={handleOnClick}
|
||||
ref={anchorRef}
|
||||
>
|
||||
<Avatar user={user} chat={chat} size="medium" />
|
||||
<Avatar user={user} chat={chat} size="medium" noVideo />
|
||||
<div className="info">
|
||||
<span className="name">{name}</span>
|
||||
<span className={buildClassName('about', aboutColor)}>{aboutText}</span>
|
||||
|
||||
@ -60,7 +60,7 @@ const GroupCallParticipantVideo: FC<OwnProps & StateProps> = ({
|
||||
{lang('Back')}
|
||||
</button>
|
||||
)}
|
||||
<Avatar user={user} chat={chat} className="thumbnail-avatar" />
|
||||
<Avatar user={user} chat={chat} className="thumbnail-avatar" noVideo />
|
||||
{ENABLE_THUMBNAIL_VIDEO && (
|
||||
<div className="thumbnail-wrapper">
|
||||
<video className="thumbnail" muted autoPlay playsInline srcObject={streams?.[type]} />
|
||||
|
||||
@ -20,12 +20,12 @@
|
||||
z-index: -1;
|
||||
transform: scale(1.1);
|
||||
|
||||
:global(.Avatar__img) {
|
||||
:global(.Avatar__media) {
|
||||
border-radius: 0;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&.blurred :global(.Avatar__img) {
|
||||
&.blurred :global(.Avatar__media) {
|
||||
filter: blur(10px);
|
||||
}
|
||||
}
|
||||
@ -75,13 +75,13 @@
|
||||
|
||||
&.open {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
pointer-events: all;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.emojis {
|
||||
user-select: none;
|
||||
pointer-events: all;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
margin-top: 1rem;
|
||||
height: 3rem;
|
||||
|
||||
@ -231,7 +231,12 @@ const PhoneCall: FC<StateProps> = ({
|
||||
)}
|
||||
dialogRef={containerRef}
|
||||
>
|
||||
<Avatar user={user} size="jumbo" className={hasVideo || hasPresentation ? styles.blurred : ''} />
|
||||
<Avatar
|
||||
user={user}
|
||||
size="jumbo"
|
||||
className={hasVideo || hasPresentation ? styles.blurred : ''}
|
||||
noLoop={phoneCall?.state !== 'requesting'}
|
||||
/>
|
||||
{phoneCall?.screencastState === 'active' && streams?.presentation
|
||||
&& <video className={styles.mainVideo} muted autoPlay playsInline srcObject={streams.presentation} />}
|
||||
{phoneCall?.videoState === 'active' && streams?.video
|
||||
|
||||
16
src/components/common/AboutAdsModal.async.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import type { OwnProps } from './AboutAdsModal';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const AboutAdsModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const AboutAdsModal = useModuleLoader(Bundles.Extra, 'AboutAdsModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return AboutAdsModal ? <AboutAdsModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(AboutAdsModalAsync);
|
||||
44
src/components/common/AboutAdsModal.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
|
||||
import renderText from './helpers/renderText';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import Modal from '../ui/Modal';
|
||||
import Button from '../ui/Button';
|
||||
import SafeLink from './SafeLink';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const AboutAdsModal: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
hasCloseButton
|
||||
title={lang('SponsoredMessageInfo')}
|
||||
>
|
||||
<p>{renderText(lang('SponsoredMessageInfoDescription1'), ['br'])}</p>
|
||||
<p>{renderText(lang('SponsoredMessageInfoDescription2'), ['br'])}</p>
|
||||
<p>{renderText(lang('SponsoredMessageInfoDescription3'), ['br'])}</p>
|
||||
<p>
|
||||
<SafeLink
|
||||
url={lang('SponsoredMessageAlertLearnMoreUrl')}
|
||||
text={lang('SponsoredMessageAlertLearnMoreUrl')}
|
||||
/>
|
||||
</p>
|
||||
<p>{renderText(lang('SponsoredMessageInfoDescription4'), ['br'])}</p>
|
||||
<Button className="confirm-dialog-button" isText onClick={onClose}>{lang('Close')}</Button>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AboutAdsModal);
|
||||
@ -190,7 +190,6 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
if (!animation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (play || playSegment) {
|
||||
if (isFrozen.current) {
|
||||
wasPlaying.current = true;
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
pointer-events: none;
|
||||
|
||||
&.interactive {
|
||||
pointer-events: all;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,10 +159,60 @@
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.waveform-wrapper {
|
||||
display: flex;
|
||||
|
||||
@keyframes loader-rectangle {
|
||||
from {
|
||||
stroke-dashoffset: 100;
|
||||
}
|
||||
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.transcribe-icon {
|
||||
transition: 0.25s ease-in-out transform;
|
||||
}
|
||||
|
||||
.transcribe-shown {
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
.loading-svg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.loading-rect {
|
||||
animation: 1s linear loader-rectangle infinite;
|
||||
}
|
||||
|
||||
.Button {
|
||||
border-radius: var(--border-radius-default-tiny);
|
||||
background: var(--color-voice-transcribe);
|
||||
color: var(--accent-color);
|
||||
width: auto;
|
||||
margin-inline-start: 0.25rem;
|
||||
font-size: 1.25rem;
|
||||
height: 1.5rem;
|
||||
padding: 0.375rem;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-voice-transcribe) !important;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.waveform {
|
||||
cursor: pointer;
|
||||
margin-left: 1px;
|
||||
touch-action: none;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.meta,
|
||||
|
||||
@ -54,17 +54,24 @@ type OwnProps = {
|
||||
isSelectable?: boolean;
|
||||
isSelected?: boolean;
|
||||
isDownloading: boolean;
|
||||
isTranscribing?: boolean;
|
||||
isTranscribed?: boolean;
|
||||
canTranscribe?: boolean;
|
||||
isTranscriptionHidden?: boolean;
|
||||
isTranscriptionError?: boolean;
|
||||
onHideTranscription?: (isHidden: boolean) => void;
|
||||
onPlay: (messageId: number, chatId: string) => void;
|
||||
onReadMedia?: () => void;
|
||||
onCancelUpload?: () => void;
|
||||
onDateClick?: (messageId: number, chatId: string) => void;
|
||||
};
|
||||
|
||||
export const TINY_SCREEN_WIDTH_MQL = window.matchMedia('(max-width: 365px)');
|
||||
const AVG_VOICE_DURATION = 10;
|
||||
const MIN_SPIKES = IS_SINGLE_COLUMN_LAYOUT ? 20 : 25;
|
||||
const MAX_SPIKES = IS_SINGLE_COLUMN_LAYOUT ? 50 : 75;
|
||||
// This is needed for browsers requiring user interaction before playing.
|
||||
const PRELOAD = true;
|
||||
// eslint-disable-next-line max-len
|
||||
const TRANSCRIBE_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 24" class="loading-svg"><rect class="loading-rect" fill="transparent" width="32" height="24" stroke-width="3" stroke-linejoin="round" rx="6" ry="6" stroke="var(--accent-color)" stroke-dashoffset="1" stroke-dasharray="32,68"></rect></svg>';
|
||||
|
||||
const Audio: FC<OwnProps> = ({
|
||||
theme,
|
||||
@ -78,12 +85,18 @@ const Audio: FC<OwnProps> = ({
|
||||
isSelectable,
|
||||
isSelected,
|
||||
isDownloading,
|
||||
isTranscribing,
|
||||
isTranscriptionHidden,
|
||||
isTranscribed,
|
||||
isTranscriptionError,
|
||||
canTranscribe,
|
||||
onHideTranscription,
|
||||
onPlay,
|
||||
onReadMedia,
|
||||
onCancelUpload,
|
||||
onDateClick,
|
||||
}) => {
|
||||
const { cancelMessageMediaDownload, downloadMessageMedia } = getActions();
|
||||
const { cancelMessageMediaDownload, downloadMessageMedia, transcribeAudio } = getActions();
|
||||
|
||||
const { content: { audio, voice, video }, isMediaUnread } = message;
|
||||
const isVoice = Boolean(voice || video);
|
||||
@ -217,6 +230,10 @@ const Audio: FC<OwnProps> = ({
|
||||
onDateClick!(message.id, message.chatId);
|
||||
}, [onDateClick, message.id, message.chatId]);
|
||||
|
||||
const handleTranscribe = useCallback(() => {
|
||||
transcribeAudio({ chatId: message.chatId, messageId: message.id });
|
||||
}, [message.chatId, message.id, transcribeAudio]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!seekerRef.current || !withSeekline) return undefined;
|
||||
return captureEvents(seekerRef.current, {
|
||||
@ -366,12 +383,31 @@ const Audio: FC<OwnProps> = ({
|
||||
)}
|
||||
{origin === AudioOrigin.SharedMedia && (voice || video) && renderWithTitle()}
|
||||
{origin === AudioOrigin.Inline && voice && (
|
||||
renderVoice(voice, seekerRef, waveformCanvasRef, playProgress, isMediaUnread)
|
||||
renderVoice(
|
||||
voice,
|
||||
seekerRef,
|
||||
waveformCanvasRef,
|
||||
playProgress,
|
||||
isMediaUnread,
|
||||
isTranscribing,
|
||||
isTranscriptionHidden,
|
||||
isTranscribed,
|
||||
isTranscriptionError,
|
||||
canTranscribe ? handleTranscribe : undefined,
|
||||
onHideTranscription,
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getSeeklineSpikeAmounts() {
|
||||
return {
|
||||
MIN_SPIKES: IS_SINGLE_COLUMN_LAYOUT ? (TINY_SCREEN_WIDTH_MQL.matches ? 16 : 20) : 25,
|
||||
MAX_SPIKES: IS_SINGLE_COLUMN_LAYOUT ? (TINY_SCREEN_WIDTH_MQL.matches ? 35 : 48) : 75,
|
||||
};
|
||||
}
|
||||
|
||||
function renderAudio(
|
||||
lang: LangFn,
|
||||
audio: ApiAudio,
|
||||
@ -436,15 +472,42 @@ function renderVoice(
|
||||
waveformCanvasRef: React.Ref<HTMLCanvasElement>,
|
||||
playProgress: number,
|
||||
isMediaUnread?: boolean,
|
||||
isTranscribing?: boolean,
|
||||
isTranscriptionHidden?: boolean,
|
||||
isTranscribed?: boolean,
|
||||
isTranscriptionError?: boolean,
|
||||
onClickTranscribe?: VoidFunction,
|
||||
onHideTranscription?: (isHidden: boolean) => void,
|
||||
) {
|
||||
return (
|
||||
<div className="content">
|
||||
<div
|
||||
className="waveform"
|
||||
draggable={false}
|
||||
ref={seekerRef}
|
||||
>
|
||||
<canvas ref={waveformCanvasRef} />
|
||||
<div className="waveform-wrapper">
|
||||
<div
|
||||
className="waveform"
|
||||
draggable={false}
|
||||
ref={seekerRef}
|
||||
>
|
||||
<canvas ref={waveformCanvasRef} />
|
||||
</div>
|
||||
{onClickTranscribe && (
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
<Button onClick={() => {
|
||||
if ((isTranscribed || isTranscriptionError) && onHideTranscription) {
|
||||
onHideTranscription(!isTranscriptionHidden);
|
||||
} else if (!isTranscribing) {
|
||||
onClickTranscribe();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className={buildClassName(
|
||||
'transcribe-icon',
|
||||
(isTranscribed || isTranscriptionError) ? 'icon-down' : 'icon-transcribe',
|
||||
(isTranscribed || isTranscriptionError) && !isTranscriptionHidden && 'transcribe-shown',
|
||||
)}
|
||||
/>
|
||||
{isTranscribing && <div dangerouslySetInnerHTML={{ __html: TRANSCRIBE_SVG }} />}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className={buildClassName('voice-duration', isMediaUnread && 'unread')} dir="auto">
|
||||
{playProgress === 0 ? formatMediaDuration(voice.duration) : formatMediaDuration(voice.duration * playProgress)}
|
||||
@ -475,6 +538,7 @@ function useWaveformCanvas(
|
||||
};
|
||||
}
|
||||
|
||||
const { MIN_SPIKES, MAX_SPIKES } = getSeeklineSpikeAmounts();
|
||||
const durationFactor = Math.min(duration / AVG_VOICE_DURATION, 1);
|
||||
const spikesCount = Math.round(MIN_SPIKES + (MAX_SPIKES - MIN_SPIKES) * durationFactor);
|
||||
const decodedWaveform = decodeWaveform(new Uint8Array(waveform));
|
||||
|
||||
@ -12,11 +12,13 @@
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
|
||||
&__img {
|
||||
&__media {
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
@ -105,8 +107,6 @@
|
||||
}
|
||||
|
||||
&.online {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
@ -119,10 +119,18 @@
|
||||
border: 2px solid var(--color-background);
|
||||
background-color: #0ac630;
|
||||
flex-shrink: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.poster {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions } from '../../global';
|
||||
|
||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||
import type {
|
||||
ApiChat, ApiPhoto, ApiUser, ApiUserStatus,
|
||||
} from '../../api/types';
|
||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||
import { ApiMediaFormat } from '../../api/types';
|
||||
|
||||
import { IS_TEST } from '../../config';
|
||||
@ -21,14 +25,17 @@ import {
|
||||
import { getFirstLetters } from '../../util/textFormat';
|
||||
import buildClassName, { createClassNameBuilder } from '../../util/buildClassName';
|
||||
import renderText from './helpers/renderText';
|
||||
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||
|
||||
import './Avatar.scss';
|
||||
import useVideoAutoPause from '../middle/message/hooks/useVideoAutoPause';
|
||||
|
||||
const cn = createClassNameBuilder('Avatar');
|
||||
cn.img = cn('img');
|
||||
cn.media = cn('media');
|
||||
cn.icon = cn('icon');
|
||||
|
||||
type OwnProps = {
|
||||
@ -40,8 +47,11 @@ type OwnProps = {
|
||||
userStatus?: ApiUserStatus;
|
||||
text?: string;
|
||||
isSavedMessages?: boolean;
|
||||
noVideo?: boolean;
|
||||
noLoop?: boolean;
|
||||
lastSyncTime?: number;
|
||||
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasPhoto: boolean) => void;
|
||||
observeIntersection?: ObserveFn;
|
||||
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void;
|
||||
};
|
||||
|
||||
const Avatar: FC<OwnProps> = ({
|
||||
@ -53,15 +63,33 @@ const Avatar: FC<OwnProps> = ({
|
||||
userStatus,
|
||||
text,
|
||||
isSavedMessages,
|
||||
noVideo,
|
||||
noLoop,
|
||||
lastSyncTime,
|
||||
observeIntersection,
|
||||
onClick,
|
||||
}) => {
|
||||
const { loadFullUser } = getActions();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||
const isDeleted = user && isDeletedUser(user);
|
||||
const isReplies = user && isChatWithRepliesBot(user.id);
|
||||
let imageHash: string | undefined;
|
||||
let videoHash: string | undefined;
|
||||
|
||||
const hasVideoAvatar = (user || chat)?.hasVideoAvatar;
|
||||
const profilePhoto = (user?.fullInfo?.profilePhoto || chat?.fullInfo?.profilePhoto);
|
||||
const shouldShowVideo = !noVideo && Boolean(user?.isPremium && profilePhoto?.isVideo);
|
||||
const shouldPlayVideo = isIntersecting && shouldShowVideo;
|
||||
|
||||
const shouldFetchBig = size === 'jumbo';
|
||||
if (!isSavedMessages && !isDeleted) {
|
||||
if (shouldShowVideo) {
|
||||
videoHash = getChatAvatarHash(user!, undefined, 'video');
|
||||
}
|
||||
if (user) {
|
||||
imageHash = getChatAvatarHash(user, shouldFetchBig ? 'big' : undefined);
|
||||
} else if (chat) {
|
||||
@ -71,8 +99,29 @@ const Avatar: FC<OwnProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const blobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
|
||||
const hasBlobUrl = Boolean(blobUrl);
|
||||
useVideoAutoPause(videoRef, shouldPlayVideo);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video || !noLoop) return undefined;
|
||||
|
||||
const returnToStart = () => {
|
||||
video.currentTime = 0;
|
||||
};
|
||||
|
||||
video.addEventListener('ended', returnToStart);
|
||||
return () => video.removeEventListener('ended', returnToStart);
|
||||
}, [noLoop]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isIntersecting && !noVideo && user && hasVideoAvatar && !profilePhoto) {
|
||||
loadFullUser({ userId: user.id });
|
||||
}
|
||||
}, [hasVideoAvatar, profilePhoto, loadFullUser, user, noVideo, isIntersecting]);
|
||||
|
||||
const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
|
||||
const videoBlobUrl = useMedia(videoHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
|
||||
const hasBlobUrl = Boolean(imgBlobUrl || videoBlobUrl);
|
||||
const { transitionClassNames } = useShowTransition(hasBlobUrl, undefined, hasBlobUrl, 'slow');
|
||||
|
||||
const lang = useLang();
|
||||
@ -86,14 +135,27 @@ const Avatar: FC<OwnProps> = ({
|
||||
content = <i className={buildClassName(cn.icon, 'icon-avatar-deleted-account')} aria-label={author} />;
|
||||
} else if (isReplies) {
|
||||
content = <i className={buildClassName(cn.icon, 'icon-reply-filled')} aria-label={author} />;
|
||||
} else if (blobUrl) {
|
||||
} else if (hasBlobUrl) {
|
||||
content = (
|
||||
<img
|
||||
src={blobUrl}
|
||||
className={buildClassName(cn.img, 'avatar-media', transitionClassNames)}
|
||||
alt={author}
|
||||
decoding="async"
|
||||
/>
|
||||
<>
|
||||
<img
|
||||
src={imgBlobUrl}
|
||||
className={buildClassName(cn.media, 'avatar-media', transitionClassNames, videoBlobUrl && 'poster')}
|
||||
alt={author}
|
||||
decoding="async"
|
||||
/>
|
||||
{videoBlobUrl && (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={videoBlobUrl}
|
||||
className={buildClassName(cn.media, 'avatar-media', transitionClassNames)}
|
||||
muted
|
||||
autoPlay
|
||||
loop={!noLoop}
|
||||
playsInline
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else if (user) {
|
||||
const userFullName = getUserFullName(user);
|
||||
@ -115,20 +177,21 @@ const Avatar: FC<OwnProps> = ({
|
||||
isReplies && 'replies-bot-account',
|
||||
isOnline && 'online',
|
||||
onClick && 'interactive',
|
||||
(!isSavedMessages && !blobUrl) && 'no-photo',
|
||||
(!isSavedMessages && !imgBlobUrl) && 'no-photo',
|
||||
);
|
||||
|
||||
const hasImage = Boolean(isSavedMessages || blobUrl);
|
||||
const hasMedia = Boolean(isSavedMessages || imgBlobUrl);
|
||||
const handleClick = useCallback((e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
if (onClick) {
|
||||
onClick(e, hasImage);
|
||||
onClick(e, hasMedia);
|
||||
}
|
||||
}, [onClick, hasImage]);
|
||||
}, [onClick, hasMedia]);
|
||||
|
||||
const senderId = (user || chat) && (user || chat)!.id;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={fullClassName}
|
||||
onClick={handleClick}
|
||||
data-test-sender-id={IS_TEST ? senderId : undefined}
|
||||
|
||||
@ -111,7 +111,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
||||
narrow
|
||||
isStatic
|
||||
>
|
||||
<span className="title" dir="auto">
|
||||
<span className="title word-break" dir="auto">
|
||||
{renderText(description, ['br', 'links', 'emoji'])}
|
||||
</span>
|
||||
<span className="subtitle">{lang(userId ? 'UserBio' : 'Info')}</span>
|
||||
|
||||
@ -11,6 +11,12 @@
|
||||
cursor: pointer;
|
||||
direction: ltr;
|
||||
|
||||
@for $i from 1 through 8 {
|
||||
&.color-#{$i} {
|
||||
--accent-color: var(--color-user-#{$i});
|
||||
}
|
||||
}
|
||||
|
||||
body.animation-level-1 & {
|
||||
.ripple-container {
|
||||
display: none;
|
||||
@ -100,7 +106,7 @@
|
||||
}
|
||||
|
||||
.embedded-action-message {
|
||||
color: var(--accent-color);
|
||||
color: var(---secondary-color);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
isActionMessage,
|
||||
getSenderTitle,
|
||||
getMessageRoundVideo,
|
||||
getUserColorKey,
|
||||
} from '../../global/helpers';
|
||||
import renderText from './helpers/renderText';
|
||||
import { getPictogramDimensions } from './helpers/mediaDimensions';
|
||||
@ -30,6 +31,7 @@ type OwnProps = {
|
||||
sender?: ApiUser | ApiChat;
|
||||
title?: string;
|
||||
customText?: string;
|
||||
noUserColors?: boolean;
|
||||
isProtected?: boolean;
|
||||
onClick: NoneToVoidFunction;
|
||||
};
|
||||
@ -43,6 +45,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
title,
|
||||
customText,
|
||||
isProtected,
|
||||
noUserColors,
|
||||
observeIntersection,
|
||||
onClick,
|
||||
}) => {
|
||||
@ -61,7 +64,11 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={buildClassName('EmbeddedMessage', className)}
|
||||
className={buildClassName(
|
||||
'EmbeddedMessage',
|
||||
className,
|
||||
sender && !noUserColors && `color-${getUserColorKey(sender)}`,
|
||||
)}
|
||||
onClick={message ? onClick : undefined}
|
||||
>
|
||||
{mediaThumbnail && renderPictogram(mediaThumbnail, mediaBlobUrl, isRoundVideo, isProtected)}
|
||||
|
||||
@ -34,6 +34,7 @@ type OwnProps = {
|
||||
withFullInfo?: boolean;
|
||||
withUpdatingStatus?: boolean;
|
||||
withChatType?: boolean;
|
||||
withVideoAvatar?: boolean;
|
||||
noRtl?: boolean;
|
||||
};
|
||||
|
||||
@ -55,6 +56,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
||||
withFullInfo,
|
||||
withUpdatingStatus,
|
||||
withChatType,
|
||||
withVideoAvatar,
|
||||
noRtl,
|
||||
chat,
|
||||
onlineCount,
|
||||
@ -75,8 +77,8 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [chatId, isMin, lastSyncTime, withFullInfo, loadFullChat, isSuperGroup]);
|
||||
|
||||
const handleAvatarViewerOpen = useCallback((e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasPhoto: boolean) => {
|
||||
if (chat && hasPhoto) {
|
||||
const handleAvatarViewerOpen = useCallback((e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
||||
if (chat && hasMedia) {
|
||||
e.stopPropagation();
|
||||
openMediaViewer({
|
||||
avatarOwnerId: chat.id,
|
||||
@ -140,6 +142,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
||||
size={avatarSize}
|
||||
chat={chat}
|
||||
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
|
||||
noVideo={!withVideoAvatar}
|
||||
/>
|
||||
<div className="info">
|
||||
<div className="title">
|
||||
|
||||
@ -61,6 +61,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
||||
user={user}
|
||||
size="small"
|
||||
isSavedMessages={user?.isSelf}
|
||||
noVideo
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
27
src/components/common/PremiumIcon.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.PremiumIcon {
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
&.big {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
--color-fill: var(--color-primary);
|
||||
|
||||
& > svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:not(.gradient) {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
47
src/components/common/PremiumIcon.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo, useMemo } from '../../lib/teact/teact';
|
||||
|
||||
import generateIdFor from '../../util/generateIdFor';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
import './PremiumIcon.scss';
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const PREMIUM_ICON = { __html: '<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z" fill="var(--color-fill)"/></svg>' };
|
||||
const store: Record<string, boolean> = {};
|
||||
|
||||
type OwnProps = {
|
||||
withGradient?: boolean;
|
||||
big?: boolean;
|
||||
onClick?: VoidFunction;
|
||||
};
|
||||
|
||||
const PremiumIcon: FC<OwnProps> = ({
|
||||
withGradient,
|
||||
big,
|
||||
onClick,
|
||||
}) => {
|
||||
const html = useMemo(() => {
|
||||
return withGradient ? getPremiumIconGradient() : PREMIUM_ICON;
|
||||
}, [withGradient]);
|
||||
|
||||
return (
|
||||
<i
|
||||
onClick={onClick}
|
||||
className={buildClassName('PremiumIcon', withGradient && 'gradient', onClick && 'clickable', big && 'big')}
|
||||
dangerouslySetInnerHTML={html}
|
||||
title="Premium"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function getPremiumIconGradient() {
|
||||
const id = generateIdFor(store);
|
||||
return {
|
||||
// eslint-disable-next-line max-len
|
||||
__html: `<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="${id}" x1="3" y1="63.5001" x2="84.1475" y2="-1.32262" gradientUnits="userSpaceOnUse"><stop stop-color="#6B93FF"/><stop offset="0.439058" stop-color="#976FFF"/><stop offset="1" stop-color="#E46ACE"/></linearGradient></defs><path fill-rule="evenodd" clip-rule="evenodd" d="M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z" fill="url(#${id})"/></svg>`,
|
||||
};
|
||||
}
|
||||
|
||||
export default memo(PremiumIcon);
|
||||
@ -17,6 +17,7 @@ import VerifiedIcon from './VerifiedIcon';
|
||||
import TypingStatus from './TypingStatus';
|
||||
import DotAnimation from './DotAnimation';
|
||||
import FakeIcon from './FakeIcon';
|
||||
import PremiumIcon from './PremiumIcon';
|
||||
|
||||
type OwnProps = {
|
||||
userId: string;
|
||||
@ -29,6 +30,7 @@ type OwnProps = {
|
||||
withUsername?: boolean;
|
||||
withFullInfo?: boolean;
|
||||
withUpdatingStatus?: boolean;
|
||||
withVideoAvatar?: boolean;
|
||||
noStatusOrTyping?: boolean;
|
||||
noRtl?: boolean;
|
||||
};
|
||||
@ -52,6 +54,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
||||
withUsername,
|
||||
withFullInfo,
|
||||
withUpdatingStatus,
|
||||
withVideoAvatar,
|
||||
noStatusOrTyping,
|
||||
noRtl,
|
||||
user,
|
||||
@ -75,8 +78,8 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [userId, loadFullUser, lastSyncTime, withFullInfo]);
|
||||
|
||||
const handleAvatarViewerOpen = useCallback((e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasPhoto: boolean) => {
|
||||
if (user && hasPhoto) {
|
||||
const handleAvatarViewerOpen = useCallback((e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
||||
if (user && hasMedia) {
|
||||
e.stopPropagation();
|
||||
openMediaViewer({
|
||||
avatarOwnerId: user.id,
|
||||
@ -130,6 +133,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
||||
user={user}
|
||||
isSavedMessages={isSavedMessages}
|
||||
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
|
||||
noVideo={!withVideoAvatar}
|
||||
/>
|
||||
<div className="info">
|
||||
{isSavedMessages ? (
|
||||
@ -140,6 +144,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
||||
<div className="title">
|
||||
<h3 dir="auto">{fullName && renderText(fullName)}</h3>
|
||||
{user?.isVerified && <VerifiedIcon />}
|
||||
{user.isPremium && <PremiumIcon />}
|
||||
{user.fakeType && <FakeIcon fakeType={user.fakeType} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -122,18 +122,21 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
h3 {
|
||||
.fullName {
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.375rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin-bottom: 0.25rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.VerifiedIcon {
|
||||
.VerifiedIcon, .PremiumIcon {
|
||||
margin-left: 0.25rem;
|
||||
margin-top: -0.125rem;
|
||||
z-index: 2;
|
||||
--color-fill: var(--color-white);
|
||||
--color-checkmark: var(--color-primary);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
|
||||
@ -23,6 +23,7 @@ import VerifiedIcon from './VerifiedIcon';
|
||||
import ProfilePhoto from './ProfilePhoto';
|
||||
import Transition from '../ui/Transition';
|
||||
import FakeIcon from './FakeIcon';
|
||||
import PremiumIcon from './PremiumIcon';
|
||||
|
||||
import './ProfileInfo.scss';
|
||||
|
||||
@ -55,6 +56,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
const {
|
||||
loadFullUser,
|
||||
openMediaViewer,
|
||||
openPremiumModal,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
@ -94,6 +96,12 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
}, [openMediaViewer, userId, chatId, currentPhotoIndex, forceShowSelf]);
|
||||
|
||||
const handleClickPremium = useCallback(() => {
|
||||
if (!user) return;
|
||||
|
||||
openPremiumModal({ fromUserId: user.id });
|
||||
}, [openPremiumModal, user]);
|
||||
|
||||
const selectPreviousMedia = useCallback(() => {
|
||||
if (isFirst) {
|
||||
return;
|
||||
@ -151,9 +159,8 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
function renderPhoto() {
|
||||
const photo = !isSavedMessages && photos && photos.length > 0 ? photos[currentPhotoIndex] : undefined;
|
||||
|
||||
function renderPhoto(isActive?: boolean) {
|
||||
const photo = !isSavedMessages && photos.length > 0 ? photos[currentPhotoIndex] : undefined;
|
||||
return (
|
||||
<ProfilePhoto
|
||||
key={currentPhotoIndex}
|
||||
@ -162,6 +169,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
photo={photo}
|
||||
isSavedMessages={isSavedMessages}
|
||||
isFirstPhoto={isFirst}
|
||||
notActive={!isActive}
|
||||
onClick={handleProfilePhotoClick}
|
||||
/>
|
||||
);
|
||||
@ -187,6 +195,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
const isVerifiedIconShown = (user || chat)?.isVerified;
|
||||
const isPremiumIconShown = user?.isPremium;
|
||||
const fakeType = (user || chat)?.fakeType;
|
||||
|
||||
return (
|
||||
@ -194,7 +203,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
<div className="photo-wrapper">
|
||||
{renderPhotoTabs()}
|
||||
<Transition activeKey={currentPhotoIndex} name={slideAnimation} className="profile-slide-container">
|
||||
{renderPhoto()}
|
||||
{renderPhoto}
|
||||
</Transition>
|
||||
|
||||
{!isFirst && (
|
||||
@ -218,12 +227,13 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
<div className="info" dir={lang.isRtl ? 'rtl' : 'auto'}>
|
||||
{isSavedMessages ? (
|
||||
<div className="title">
|
||||
<h3 dir="auto">{lang('SavedMessages')}</h3>
|
||||
<div className="fullName" dir="auto">{lang('SavedMessages')}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="title">
|
||||
<h3 dir="auto">{fullName && renderText(fullName)}</h3>
|
||||
<div className="fullName" dir="auto">{fullName && renderText(fullName)}</div>
|
||||
{isVerifiedIconShown && <VerifiedIcon />}
|
||||
{isPremiumIconShown && <PremiumIcon onClick={handleClickPremium} />}
|
||||
{fakeType && <FakeIcon fakeType={fakeType} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import React, { memo, useEffect, useRef } from '../../lib/teact/teact';
|
||||
|
||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||
import type { ApiChat, ApiPhoto, ApiUser } from '../../api/types';
|
||||
import { ApiMediaFormat } from '../../api/types';
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
getUserFullName,
|
||||
isUserId,
|
||||
isChatWithRepliesBot,
|
||||
isDeletedUser,
|
||||
isDeletedUser, getVideoAvatarMediaHash,
|
||||
} from '../../global/helpers';
|
||||
import renderText from './helpers/renderText';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
@ -30,6 +30,7 @@ type OwnProps = {
|
||||
isSavedMessages?: boolean;
|
||||
photo?: ApiPhoto;
|
||||
lastSyncTime?: number;
|
||||
notActive?: boolean;
|
||||
onClick: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
@ -39,34 +40,55 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
photo,
|
||||
isFirstPhoto,
|
||||
isSavedMessages,
|
||||
notActive,
|
||||
lastSyncTime,
|
||||
onClick,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const lang = useLang();
|
||||
const isDeleted = user && isDeletedUser(user);
|
||||
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
||||
|
||||
function getMediaHash(size: 'normal' | 'big', forceAvatar?: boolean) {
|
||||
if (photo && !forceAvatar) {
|
||||
return `photo${photo.id}?size=c`;
|
||||
}
|
||||
function getMediaHash(size: 'normal' | 'big', type: 'photo' | 'video' = 'photo') {
|
||||
const userOrChat = user || chat;
|
||||
const profilePhoto = photo || userOrChat?.fullInfo?.profilePhoto;
|
||||
const hasVideo = profilePhoto?.isVideo;
|
||||
const forceAvatar = isFirstPhoto;
|
||||
|
||||
let hash: string | undefined;
|
||||
if (!isSavedMessages && !isDeleted && !isRepliesChat) {
|
||||
if (user) {
|
||||
hash = getChatAvatarHash(user, size);
|
||||
} else if (chat) {
|
||||
hash = getChatAvatarHash(chat, size);
|
||||
if (type === 'video' && !hasVideo) return undefined;
|
||||
|
||||
if (photo && !forceAvatar) {
|
||||
if (hasVideo && type === 'video') {
|
||||
return getVideoAvatarMediaHash(photo);
|
||||
}
|
||||
if (type === 'photo') {
|
||||
return `photo${photo.id}?size=c`;
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
if (!isSavedMessages && !isDeleted && !isRepliesChat && userOrChat) {
|
||||
return getChatAvatarHash(userOrChat, size, type);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const photoBlobUrl = useMedia(getMediaHash('big'), false, ApiMediaFormat.BlobUrl, lastSyncTime);
|
||||
const avatarMediaHash = isFirstPhoto && !photoBlobUrl ? getMediaHash('normal', true) : undefined;
|
||||
const avatarBlobUrl = useMedia(avatarMediaHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
|
||||
const imageSrc = photoBlobUrl || avatarBlobUrl || photo?.thumbnail?.dataUri;
|
||||
useEffect(() => {
|
||||
if (!videoRef.current) return;
|
||||
if (notActive) {
|
||||
videoRef.current.pause();
|
||||
videoRef.current.currentTime = 0;
|
||||
} else {
|
||||
videoRef.current.play();
|
||||
}
|
||||
}, [notActive]);
|
||||
|
||||
const photoHash = getMediaHash('big', 'photo');
|
||||
const photoBlobUrl = useMedia(photoHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
|
||||
const videoHash = getMediaHash('normal', 'video');
|
||||
const videoBlobUrl = useMedia(videoHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
|
||||
const imageSrc = videoBlobUrl || photoBlobUrl || photo?.thumbnail?.dataUri;
|
||||
|
||||
let content: TeactNode | undefined;
|
||||
|
||||
@ -77,7 +99,21 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
} else if (isRepliesChat) {
|
||||
content = <i className="icon-reply-filled" />;
|
||||
} else if (imageSrc) {
|
||||
content = <img src={imageSrc} className="avatar-media" alt="" />;
|
||||
if (videoBlobUrl) {
|
||||
content = (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={imageSrc}
|
||||
className="avatar-media"
|
||||
muted
|
||||
autoPlay={!notActive}
|
||||
loop
|
||||
playsInline
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = <img src={imageSrc} className="avatar-media" alt="" />;
|
||||
}
|
||||
} else if (user) {
|
||||
const userFullName = getUserFullName(user);
|
||||
content = userFullName ? getFirstLetters(userFullName, 2) : undefined;
|
||||
@ -98,7 +134,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
isSavedMessages && 'saved-messages',
|
||||
isDeleted && 'deleted-account',
|
||||
isRepliesChat && 'replies-bot-account',
|
||||
(!isSavedMessages && !(imageSrc)) && 'no-photo',
|
||||
(!isSavedMessages && !imageSrc) && 'no-photo',
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
62
src/components/common/SliderDots.module.scss
Normal file
@ -0,0 +1,62 @@
|
||||
.root {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 2rem;
|
||||
align-items: center;
|
||||
--fake-translate: calc(8px);
|
||||
--dot-width: calc(10px + 8px);
|
||||
--start-from: 0;
|
||||
--length: 50;
|
||||
--count: 8;
|
||||
--center: calc((var(--length) * var(--dot-width)) / 2);
|
||||
transform: translateX(calc(var(--center) - (var(--dot-width) * var(--count) / 2) - (var(--dot-width) * var(--start-from))));
|
||||
transition: 0.25s ease-out transform;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
left: 0.5rem;
|
||||
top: 0.5rem;
|
||||
transition: 0.25s ease-in-out opacity;
|
||||
}
|
||||
|
||||
.right {
|
||||
left: initial;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.arrow.arrow-hidden {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
.dot {
|
||||
flex: none;
|
||||
cursor: pointer;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #DFE1E5;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
transition: 0.25s ease-in-out opacity;
|
||||
}
|
||||
|
||||
.medium {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.small {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.active {
|
||||
background: #8F77FF;
|
||||
}
|
||||
|
||||
90
src/components/common/SliderDots.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
import styles from './SliderDots.module.scss';
|
||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||
import Button from '../ui/Button';
|
||||
|
||||
type OwnProps = {
|
||||
length: number;
|
||||
active: number;
|
||||
onSelectSlide: (index: number) => void;
|
||||
};
|
||||
|
||||
const SliderDots: FC<OwnProps> = ({
|
||||
length,
|
||||
active,
|
||||
onSelectSlide,
|
||||
}) => {
|
||||
const startFrom = Math.max(0, Math.min(length - 8, active - 4));
|
||||
const isPreLastBatch = startFrom === length - 8 - 1;
|
||||
const isLastBatch = startFrom === length - 8;
|
||||
const isFirstBatch = startFrom === 0;
|
||||
const isPreFirstBatch = startFrom === 1;
|
||||
const shownDotsCount = Math.min(length, 8);
|
||||
|
||||
const handleGoForward = useCallback(() => {
|
||||
onSelectSlide(active + 1);
|
||||
}, [active, onSelectSlide]);
|
||||
|
||||
const handleGoBack = useCallback(() => {
|
||||
onSelectSlide(active - 1);
|
||||
}, [active, onSelectSlide]);
|
||||
|
||||
const count = Math.min(8, length);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!IS_TOUCH_ENV && (
|
||||
<Button
|
||||
className={buildClassName(styles.arrow, active === 0 && styles.arrowHidden)}
|
||||
color="translucent"
|
||||
disabled={active === 0}
|
||||
round
|
||||
onClick={handleGoBack}
|
||||
>
|
||||
<i className="icon-previous" />
|
||||
</Button>
|
||||
)}
|
||||
<div className={styles.root} style={`--start-from: ${startFrom}; --length: ${length}; --count: ${count};`}>
|
||||
{Array(length).fill(undefined).map((_, i) => {
|
||||
const index = i;
|
||||
const isLast = (i === startFrom + shownDotsCount - 1 && !isLastBatch && !isPreLastBatch);
|
||||
const isPreLast = (i === startFrom + shownDotsCount - 2 && !isPreLastBatch && !isLastBatch)
|
||||
|| (i === startFrom + shownDotsCount - 1 && isPreLastBatch);
|
||||
const isFirst = (i === startFrom) && !isFirstBatch && !isPreFirstBatch;
|
||||
const isPreFirst = ((i === startFrom + 1) && !isFirstBatch && !isPreFirstBatch)
|
||||
|| (i === startFrom && isPreFirstBatch);
|
||||
const isInvisible = i < startFrom || i >= startFrom + shownDotsCount;
|
||||
return (
|
||||
<div
|
||||
onClick={() => onSelectSlide(i)}
|
||||
className={buildClassName(
|
||||
styles.dot,
|
||||
index === active && styles.active,
|
||||
(isPreLast || isPreFirst) && styles.medium,
|
||||
(isLast || isFirst) && styles.small,
|
||||
isInvisible && styles.invisible,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!IS_TOUCH_ENV && (
|
||||
<Button
|
||||
className={buildClassName(styles.arrow, active === length - 1 && styles.arrowHidden, styles.right)}
|
||||
color="translucent"
|
||||
round
|
||||
disabled={active === length - 1}
|
||||
onClick={handleGoForward}
|
||||
>
|
||||
<i className="icon-next" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SliderDots);
|
||||
@ -8,6 +8,36 @@
|
||||
background-size: contain;
|
||||
transition: background-color 0.15s ease, opacity 0.3s ease !important;
|
||||
position: relative;
|
||||
--premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
||||
|
||||
.sticker-locked {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
background: var(--premium-gradient);
|
||||
}
|
||||
|
||||
.sticker-premium {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
background: var(--premium-gradient);
|
||||
}
|
||||
|
||||
&.interactive {
|
||||
cursor: pointer;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||
import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useRef,
|
||||
memo, useCallback, useEffect, useMemo, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions } from '../../global';
|
||||
|
||||
@ -38,6 +38,7 @@ type OwnProps<T> = {
|
||||
noContextMenu?: boolean;
|
||||
isSavedMessages?: boolean;
|
||||
canViewSet?: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
observeIntersection: ObserveFn;
|
||||
onClick?: (arg: OwnProps<T>['clickArg'], isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||
onFaveClick?: (sticker: ApiSticker) => void;
|
||||
@ -60,8 +61,9 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
onFaveClick,
|
||||
onUnfaveClick,
|
||||
onRemoveRecentClick,
|
||||
isCurrentUserPremium,
|
||||
}: OwnProps<T>) => {
|
||||
const { openStickerSet } = getActions();
|
||||
const { openStickerSet, openPremiumModal } = getActions();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const lang = useLang();
|
||||
@ -81,6 +83,8 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
const isVideo = sticker.isVideo && IS_WEBM_SUPPORTED;
|
||||
const videoBlobUrl = useMedia(isVideo && localMediaHash, !shouldPlay, ApiMediaFormat.BlobUrl);
|
||||
const canVideoPlay = Boolean(isVideo && videoBlobUrl && shouldPlay);
|
||||
const isPremiumSticker = sticker.hasEffect;
|
||||
const isLocked = !isCurrentUserPremium && isPremiumSticker;
|
||||
|
||||
const { transitionClassNames: previewTransitionClassNames } = useShowTransition(
|
||||
Boolean(previewBlobUrl || canLottiePlay),
|
||||
@ -140,6 +144,10 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
|
||||
const handleClick = () => {
|
||||
if (isContextMenuOpen) return;
|
||||
if (isLocked) {
|
||||
openPremiumModal({ initialSection: 'stickers' });
|
||||
return;
|
||||
}
|
||||
onClick?.(clickArg);
|
||||
};
|
||||
|
||||
@ -190,6 +198,58 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
|
||||
const style = (thumbDataUri && !canLottiePlay && !canVideoPlay) ? `background-image: url('${thumbDataUri}');` : '';
|
||||
|
||||
const contextMenuItems = useMemo(() => {
|
||||
const items: ReactNode[] = [];
|
||||
if (noContextMenu) return items;
|
||||
|
||||
if (onUnfaveClick) {
|
||||
items.push(
|
||||
<MenuItem icon="favorite" onClick={handleContextUnfave}>
|
||||
{lang('Stickers.RemoveFromFavorites')}
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
|
||||
if (onFaveClick) {
|
||||
items.push(
|
||||
<MenuItem icon="favorite" onClick={handleContextFave}>
|
||||
{lang('Stickers.AddToFavorites')}
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLocked && onClick) {
|
||||
if (!isSavedMessages) {
|
||||
items.push(<MenuItem onClick={handleSendQuiet} icon="muted">{lang('SendWithoutSound')}</MenuItem>);
|
||||
}
|
||||
items.push(
|
||||
<MenuItem onClick={handleSendScheduled} icon="calendar">
|
||||
{lang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')}
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
|
||||
if (canViewSet) {
|
||||
items.push(
|
||||
<MenuItem onClick={handleOpenSet} icon="stickers">
|
||||
{lang('ViewPackPreview')}
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
if (onRemoveRecentClick) {
|
||||
items.push(
|
||||
<MenuItem icon="delete" onClick={handleContextRemoveRecent}>
|
||||
{lang('DeleteFromRecent')}
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}, [
|
||||
canViewSet, handleContextFave, handleContextRemoveRecent, handleContextUnfave, handleOpenSet, handleSendQuiet,
|
||||
handleSendScheduled, isLocked, isSavedMessages, lang, onFaveClick, onRemoveRecentClick, onUnfaveClick, onClick,
|
||||
noContextMenu,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
@ -224,6 +284,18 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
onLoad={markLoaded}
|
||||
/>
|
||||
)}
|
||||
{isLocked && (
|
||||
<div
|
||||
className="sticker-locked"
|
||||
>
|
||||
<i className="icon-lock-badge" />
|
||||
</div>
|
||||
)}
|
||||
{isPremiumSticker && !isLocked && (
|
||||
<div className="sticker-premium">
|
||||
<i className="icon-premium" />
|
||||
</div>
|
||||
)}
|
||||
{shouldShowCloseButton && (
|
||||
<Button
|
||||
className="sticker-remove-button"
|
||||
@ -234,7 +306,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
<i className="icon-close" />
|
||||
</Button>
|
||||
)}
|
||||
{!noContextMenu && onClick && contextMenuPosition !== undefined && (
|
||||
{Boolean(contextMenuItems.length) && contextMenuPosition !== undefined && (
|
||||
<Menu
|
||||
isOpen={isContextMenuOpen}
|
||||
transformOriginX={transformOriginX}
|
||||
@ -247,30 +319,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
onClose={handleContextMenuClose}
|
||||
onCloseAnimationEnd={handleContextMenuHide}
|
||||
>
|
||||
{onUnfaveClick && (
|
||||
<MenuItem icon="favorite" onClick={handleContextUnfave}>
|
||||
{lang('Stickers.RemoveFromFavorites')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{onFaveClick && (
|
||||
<MenuItem icon="favorite" onClick={handleContextFave}>
|
||||
{lang('AddToFavorites')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{!isSavedMessages && <MenuItem onClick={handleSendQuiet} icon="muted">{lang('SendWithoutSound')}</MenuItem>}
|
||||
<MenuItem onClick={handleSendScheduled} icon="calendar">
|
||||
{lang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')}
|
||||
</MenuItem>
|
||||
{canViewSet && (
|
||||
<MenuItem onClick={handleOpenSet} icon="stickers">
|
||||
{lang('ViewPackPreview')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{onRemoveRecentClick && (
|
||||
<MenuItem icon="delete" onClick={handleContextRemoveRecent}>
|
||||
{lang('DeleteFromRecent')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{contextMenuItems}
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
selectCanScheduleUntilOnline,
|
||||
selectChat,
|
||||
selectCurrentMessageList,
|
||||
selectIsChatWithSelf,
|
||||
selectIsChatWithSelf, selectIsCurrentUserPremium,
|
||||
selectShouldSchedule,
|
||||
selectStickerSet,
|
||||
selectStickerSetByShortName,
|
||||
@ -42,6 +42,7 @@ type StateProps = {
|
||||
canScheduleUntilOnline?: boolean;
|
||||
shouldSchedule?: boolean;
|
||||
isSavedMessages?: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
};
|
||||
|
||||
const INTERSECTION_THROTTLE = 200;
|
||||
@ -55,6 +56,7 @@ const StickerSetModal: FC<OwnProps & StateProps> = ({
|
||||
canScheduleUntilOnline,
|
||||
shouldSchedule,
|
||||
isSavedMessages,
|
||||
isCurrentUserPremium,
|
||||
onClose,
|
||||
}) => {
|
||||
const {
|
||||
@ -132,6 +134,7 @@ const StickerSetModal: FC<OwnProps & StateProps> = ({
|
||||
onClick={canSendStickers ? handleSelect : undefined}
|
||||
clickArg={sticker}
|
||||
isSavedMessages={isSavedMessages}
|
||||
isCurrentUserPremium={isCurrentUserPremium}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -179,6 +182,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
: stickerSetShortName
|
||||
? selectStickerSetByShortName(global, stickerSetShortName)
|
||||
: undefined,
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
};
|
||||
},
|
||||
)(StickerSetModal));
|
||||
|
||||
@ -7,7 +7,7 @@ import type { GlobalState } from '../../global/types';
|
||||
import type { ThemeKey } from '../../types';
|
||||
|
||||
import { getChatAvatarHash } from '../../global/helpers/chats'; // Direct import for better module splitting
|
||||
import { selectIsRightColumnShown, selectTheme } from '../../global/selectors';
|
||||
import { selectIsRightColumnShown, selectTheme, selectIsCurrentUserPremium } from '../../global/selectors';
|
||||
import { DARK_THEME_BG_COLOR, LIGHT_THEME_BG_COLOR } from '../../config';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
@ -22,6 +22,7 @@ import styles from './UiLoader.module.scss';
|
||||
|
||||
import telegramLogoPath from '../../assets/telegram-logo.svg';
|
||||
import reactionThumbsPath from '../../assets/reaction-thumbs.png';
|
||||
import premiumReactionThumbsPath from '../../assets/reaction-thumbs-premium.png';
|
||||
import lockPreviewPath from '../../assets/lock.png';
|
||||
import monkeyPath from '../../assets/monkey.svg';
|
||||
|
||||
@ -42,6 +43,7 @@ type StateProps = Pick<GlobalState, 'uiReadyState' | 'shouldSkipHistoryAnimation
|
||||
isRightColumnShown?: boolean;
|
||||
leftColumnWidth?: number;
|
||||
theme: ThemeKey;
|
||||
isCurrentUserPremium?: boolean;
|
||||
};
|
||||
|
||||
const MAX_PRELOAD_DELAY = 700;
|
||||
@ -70,11 +72,11 @@ function preloadAvatars() {
|
||||
}
|
||||
|
||||
const preloadTasks = {
|
||||
main: () => Promise.all([
|
||||
main: (isCurrentUserPremium: boolean) => Promise.all([
|
||||
loadModule(Bundles.Main, 'Main')
|
||||
.then(preloadFonts),
|
||||
preloadAvatars(),
|
||||
preloadImage(reactionThumbsPath),
|
||||
preloadImage(isCurrentUserPremium ? premiumReactionThumbsPath : reactionThumbsPath),
|
||||
]),
|
||||
authPhoneNumber: () => Promise.all([
|
||||
preloadFonts(),
|
||||
@ -96,6 +98,7 @@ const UiLoader: FC<OwnProps & StateProps> = ({
|
||||
shouldSkipHistoryAnimations,
|
||||
leftColumnWidth,
|
||||
theme,
|
||||
isCurrentUserPremium,
|
||||
}) => {
|
||||
const { setIsUiReady } = getActions();
|
||||
|
||||
@ -109,7 +112,7 @@ const UiLoader: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const safePreload = async () => {
|
||||
try {
|
||||
await preloadTasks[page!]();
|
||||
await preloadTasks[page!](isCurrentUserPremium!);
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
}
|
||||
@ -175,6 +178,7 @@ export default withGlobal<OwnProps>(
|
||||
isRightColumnShown: selectIsRightColumnShown(global),
|
||||
leftColumnWidth: global.leftColumnWidth,
|
||||
theme,
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
};
|
||||
},
|
||||
)(UiLoader);
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
background-color: var(--color-background-compact-menu);
|
||||
backdrop-filter: blur(1px);
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
pointer-events: all;
|
||||
pointer-events: auto;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
|
||||
@ -12,6 +12,7 @@ export const REM = parseInt(getComputedStyle(document.documentElement).fontSize,
|
||||
export const ROUND_VIDEO_DIMENSIONS_PX = 240;
|
||||
export const GIF_MIN_WIDTH = 300;
|
||||
export const AVATAR_FULL_DIMENSIONS = { width: 640, height: 640 };
|
||||
export const VIDEO_AVATAR_FULL_DIMENSIONS = { width: 800, height: 800 };
|
||||
export const LIKE_STICKER_ID = '4986041492570112461';
|
||||
|
||||
const DEFAULT_MEDIA_DIMENSIONS: ApiDimensions = { width: 100, height: 100 };
|
||||
|
||||
@ -61,7 +61,7 @@ export function renderActionMessageText(
|
||||
processed = processPlaceholder(
|
||||
unprocessed,
|
||||
'%payment_amount%',
|
||||
formatCurrency(amount!, currency, lang.code),
|
||||
formatCurrency(amount!, currency!, lang.code),
|
||||
);
|
||||
unprocessed = processed.pop() as string;
|
||||
content.push(...processed);
|
||||
|
||||
@ -106,7 +106,9 @@ export function renderTextWithEntities(
|
||||
// Render the entity itself
|
||||
const newEntity = shouldRenderAsHtml
|
||||
? processEntityAsHtml(entity, entityContent, nestedEntityContent)
|
||||
: processEntity(entity, entityContent, nestedEntityContent, highlight, messageId, isSimple, isProtected);
|
||||
: processEntity(
|
||||
entity, entityContent, nestedEntityContent, highlight, messageId, isSimple, isProtected,
|
||||
);
|
||||
|
||||
if (Array.isArray(newEntity)) {
|
||||
renderResult.push(...newEntity);
|
||||
|
||||
@ -33,7 +33,7 @@ export function renderWaveform(
|
||||
ctx.globalAlpha = (i / spikes.length >= progress) ? 0.5 : 1;
|
||||
ctx.fillStyle = progress > i / spikes.length ? progressFillStyle : fillStyle;
|
||||
const spikeHeight = Math.max(2, HEIGHT * (item / Math.max(1, peak)));
|
||||
roundedRectangle(ctx, i * SPIKE_STEP, height, SPIKE_WIDTH, spikeHeight, SPIKE_RADIUS);
|
||||
roundedRectangle(ctx, i * SPIKE_STEP, (height + spikeHeight) / 2, SPIKE_WIDTH, spikeHeight, SPIKE_RADIUS);
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type { ApiChatFolder } from '../../api/types';
|
||||
|
||||
import { ALL_FOLDER_ID } from '../../config';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import Modal from '../ui/Modal';
|
||||
@ -54,7 +55,7 @@ const ChatFolderModal: FC<OwnProps & StateProps> = ({
|
||||
const [selectedFolderIds, setSelectedFolderIds] = useState<string[]>(initialSelectedFolderIds);
|
||||
|
||||
const folders = useMemo(() => {
|
||||
return folderOrderedIds?.map((folderId) => ({
|
||||
return folderOrderedIds?.filter((folderId) => folderId !== ALL_FOLDER_ID).map((folderId) => ({
|
||||
label: foldersById ? foldersById[folderId].title : '',
|
||||
value: String(folderId),
|
||||
})) || [];
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
&.selected:hover {
|
||||
--background-color: var(--color-chat-active) !important;
|
||||
|
||||
.VerifiedIcon {
|
||||
.VerifiedIcon, .PremiumIcon {
|
||||
--color-fill: #fff;
|
||||
--color-checkmark: var(--color-primary)
|
||||
}
|
||||
|
||||
@ -5,8 +5,7 @@ import React, {
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import type { LangFn } from '../../../hooks/useLang';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import type {
|
||||
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus, ApiFormattedText, ApiUserStatus,
|
||||
} from '../../../api/types';
|
||||
@ -37,12 +36,14 @@ import { renderActionMessageText } from '../../common/helpers/renderActionMessag
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import { fastRaf } from '../../../util/schedulers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||
|
||||
import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
||||
import useChatContextActions from '../../../hooks/useChatContextActions';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
import { ChatAnimationTypes } from './hooks';
|
||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import VerifiedIcon from '../../common/VerifiedIcon';
|
||||
@ -55,6 +56,7 @@ import ChatFolderModal from '../ChatFolderModal.async';
|
||||
import ChatCallStatus from './ChatCallStatus';
|
||||
import ReportModal from '../../common/ReportModal';
|
||||
import FakeIcon from '../../common/FakeIcon';
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
|
||||
import './Chat.scss';
|
||||
|
||||
@ -65,6 +67,7 @@ type OwnProps = {
|
||||
orderDiff: number;
|
||||
animationType: ChatAnimationTypes;
|
||||
isPinned?: boolean;
|
||||
observeIntersection?: ObserveFn;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -94,6 +97,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
orderDiff,
|
||||
animationType,
|
||||
isPinned,
|
||||
observeIntersection,
|
||||
chat,
|
||||
isMuted,
|
||||
user,
|
||||
@ -303,6 +307,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
userStatus={userStatus}
|
||||
isSavedMessages={user?.isSelf}
|
||||
lastSyncTime={lastSyncTime}
|
||||
observeIntersection={observeIntersection}
|
||||
/>
|
||||
{chat.isCallActive && chat.isCallNotEmpty && (
|
||||
<ChatCallStatus isSelected={isSelected} isActive={animationLevel !== 0} />
|
||||
@ -312,6 +317,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
<div className="title">
|
||||
<h3>{renderText(getChatTitle(lang, chat, user))}</h3>
|
||||
{chat.isVerified && <VerifiedIcon />}
|
||||
{user?.isPremium && !user.isSelf && <PremiumIcon />}
|
||||
{chat.fakeType && <FakeIcon fakeType={chat.fakeType} />}
|
||||
{isMuted && <i className="icon-muted" />}
|
||||
{chat.lastMessage && (
|
||||
@ -402,7 +408,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
animationLevel: global.settings.byKey.animationLevel,
|
||||
isSelected,
|
||||
canScrollDown: isSelected && messageListType === 'thread',
|
||||
canChangeFolder: Boolean(global.chatFolders.orderedIds?.length),
|
||||
canChangeFolder: (global.chatFolders.orderedIds?.length || 0) > 1,
|
||||
lastSyncTime: global.lastSyncTime,
|
||||
...(isOutgoing && { lastMessageOutgoingStatus: selectOutgoingStatus(global, chat.lastMessage) }),
|
||||
...(privateChatUserId && {
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
background-color: #0ac630;
|
||||
border: 2px solid var(--color-background);
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.indicator {
|
||||
width: 100%;
|
||||
|
||||
@ -21,6 +21,7 @@ import Transition from '../../ui/Transition';
|
||||
import TabList from '../../ui/TabList';
|
||||
import ChatList from './ChatList';
|
||||
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
|
||||
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
||||
|
||||
type OwnProps = {
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
@ -34,6 +35,7 @@ type StateProps = {
|
||||
currentUserId?: string;
|
||||
lastSyncTime?: number;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
maxFolders: number;
|
||||
};
|
||||
|
||||
const SAVED_MESSAGES_HOTKEY = '0';
|
||||
@ -47,6 +49,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
currentUserId,
|
||||
lastSyncTime,
|
||||
shouldSkipHistoryAnimations,
|
||||
maxFolders,
|
||||
}) => {
|
||||
const {
|
||||
loadChatFolders,
|
||||
@ -65,11 +68,26 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [lastSyncTime, loadChatFolders]);
|
||||
|
||||
const defaultFolder = useMemo(() => {
|
||||
return {
|
||||
id: ALL_FOLDER_ID,
|
||||
title: orderedFolderIds?.[0] === ALL_FOLDER_ID ? lang('FilterAllChatsShort') : lang('FilterAllChats'),
|
||||
};
|
||||
}, [orderedFolderIds, lang]);
|
||||
|
||||
const displayedFolders = useMemo(() => {
|
||||
return orderedFolderIds
|
||||
? orderedFolderIds.map((id) => chatFoldersById[id] || {}).filter(Boolean)
|
||||
? orderedFolderIds.map((id) => {
|
||||
if (id === ALL_FOLDER_ID) {
|
||||
return defaultFolder;
|
||||
}
|
||||
return chatFoldersById[id] || {};
|
||||
}).filter(Boolean)
|
||||
: undefined;
|
||||
}, [chatFoldersById, orderedFolderIds]);
|
||||
}, [chatFoldersById, defaultFolder, orderedFolderIds]);
|
||||
|
||||
const allFolderIndex = displayedFolders?.findIndex((folder) => folder.id === 0);
|
||||
const isInAllFolder = allFolderIndex === activeChatFolder;
|
||||
|
||||
const folderCountersById = useFolderManagerForUnreadCounters();
|
||||
const folderTabs = useMemo(() => {
|
||||
@ -77,19 +95,18 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: ALL_FOLDER_ID,
|
||||
title: lang.code === 'en' ? 'All' : lang('FilterAllChats'),
|
||||
},
|
||||
...displayedFolders.map(({ id, title }) => ({
|
||||
return displayedFolders.map(({ id, title }, i) => {
|
||||
const isBlocked = id !== ALL_FOLDER_ID && i > maxFolders - 1;
|
||||
|
||||
return ({
|
||||
id,
|
||||
title,
|
||||
badgeCount: folderCountersById[id]?.chatsCount,
|
||||
isBadgeActive: Boolean(folderCountersById[id]?.notificationsCount),
|
||||
})),
|
||||
];
|
||||
}, [displayedFolders, folderCountersById, lang]);
|
||||
isBlocked,
|
||||
});
|
||||
});
|
||||
}, [displayedFolders, folderCountersById, maxFolders]);
|
||||
|
||||
const handleSwitchTab = useCallback((index: number) => {
|
||||
setActiveChatFolder(index, { forceOnHeavyAnimation: true });
|
||||
@ -102,9 +119,9 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
if (activeChatFolder >= folderTabs.length) {
|
||||
setActiveChatFolder(0);
|
||||
setActiveChatFolder(allFolderIndex);
|
||||
}
|
||||
}, [activeChatFolder, folderTabs, setActiveChatFolder]);
|
||||
}, [activeChatFolder, allFolderIndex, folderTabs, setActiveChatFolder]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!transitionRef.current || !IS_TOUCH_ENV || !folderTabs || !folderTabs.length) {
|
||||
@ -128,16 +145,16 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
}, [activeChatFolder, folderTabs, setActiveChatFolder]);
|
||||
|
||||
const isNotInAllTabRef = useRef();
|
||||
isNotInAllTabRef.current = activeChatFolder !== 0;
|
||||
isNotInAllTabRef.current = !isInAllFolder;
|
||||
useEffect(() => (isNotInAllTabRef.current ? captureEscKeyListener(() => {
|
||||
if (isNotInAllTabRef.current) {
|
||||
setActiveChatFolder(0);
|
||||
setActiveChatFolder(allFolderIndex);
|
||||
}
|
||||
}) : undefined), [activeChatFolder, setActiveChatFolder]);
|
||||
}) : undefined), [activeChatFolder, allFolderIndex, setActiveChatFolder]);
|
||||
|
||||
useHistoryBack({
|
||||
isActive: activeChatFolder !== 0,
|
||||
onBack: () => setActiveChatFolder(0, { forceOnHeavyAnimation: true }),
|
||||
isActive: !isInAllFolder,
|
||||
onBack: () => setActiveChatFolder(allFolderIndex, { forceOnHeavyAnimation: true }),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@ -174,7 +191,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
const activeFolder = Object.values(chatFoldersById)
|
||||
.find(({ id }) => id === folderTabs![activeChatFolder].id);
|
||||
|
||||
if (!activeFolder || activeChatFolder === 0) {
|
||||
if (!activeFolder || isInAllFolder) {
|
||||
return (
|
||||
<ChatList
|
||||
folderType="all"
|
||||
@ -198,9 +215,11 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const shouldRenderFolders = folderTabs && folderTabs.length > 1;
|
||||
|
||||
return (
|
||||
<div className="ChatFolders">
|
||||
{folderTabs?.length ? (
|
||||
{shouldRenderFolders ? (
|
||||
<TabList tabs={folderTabs} activeTab={activeChatFolder} onSwitchTab={handleSwitchTab} />
|
||||
) : shouldRenderPlaceholder ? (
|
||||
<div className={buildClassName('tabs-placeholder', transitionClassNames)} />
|
||||
@ -209,7 +228,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
ref={transitionRef}
|
||||
name={shouldSkipHistoryAnimations ? 'none' : lang.isRtl ? 'slide-optimized-rtl' : 'slide-optimized'}
|
||||
activeKey={activeChatFolder}
|
||||
renderCount={folderTabs ? folderTabs.length : undefined}
|
||||
renderCount={shouldRenderFolders ? folderTabs.length : undefined}
|
||||
>
|
||||
{renderCurrentTab}
|
||||
</Transition>
|
||||
@ -230,6 +249,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
shouldSkipHistoryAnimations,
|
||||
} = global;
|
||||
|
||||
const maxFolders = selectCurrentLimit(global, 'dialogFilters');
|
||||
|
||||
return {
|
||||
chatFoldersById,
|
||||
orderedFolderIds,
|
||||
@ -237,6 +258,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
currentUserId,
|
||||
lastSyncTime,
|
||||
shouldSkipHistoryAnimations,
|
||||
maxFolders,
|
||||
};
|
||||
},
|
||||
)(ChatFolders));
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useMemo, useEffect } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useMemo, useEffect, useRef,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { SettingsScreens } from '../../../types';
|
||||
import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
|
||||
|
||||
@ -14,10 +16,12 @@ import {
|
||||
import { IS_MAC_OS, IS_PWA } from '../../../util/environment';
|
||||
import { mapValues } from '../../../util/iteratees';
|
||||
import { getPinnedChatsCount, getOrderKey } from '../../../util/folderManager';
|
||||
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||
import { useChatAnimationType } from './hooks';
|
||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||
import { useHotkeys } from '../../../hooks/useHotkeys';
|
||||
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
@ -34,6 +38,8 @@ type OwnProps = {
|
||||
onScreenSelect?: (screen: SettingsScreens) => void;
|
||||
};
|
||||
|
||||
const INTERSECTION_THROTTLE = 200;
|
||||
|
||||
const ChatList: FC<OwnProps> = ({
|
||||
folderType,
|
||||
folderId,
|
||||
@ -42,6 +48,8 @@ const ChatList: FC<OwnProps> = ({
|
||||
onScreenSelect,
|
||||
}) => {
|
||||
const { openChat, openNextChat } = getActions();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const resolvedFolderId = (
|
||||
folderType === 'all' ? ALL_FOLDER_ID : folderType === 'archived' ? ARCHIVED_FOLDER_ID : folderId!
|
||||
@ -113,6 +121,11 @@ const ChatList: FC<OwnProps> = ({
|
||||
|
||||
const getAnimationType = useChatAnimationType(orderDiffById);
|
||||
|
||||
const { observe } = useIntersectionObserver({
|
||||
rootRef: containerRef,
|
||||
throttleMs: INTERSECTION_THROTTLE,
|
||||
});
|
||||
|
||||
function renderChats() {
|
||||
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
|
||||
const pinnedCount = getPinnedChatsCount(resolvedFolderId) || 0;
|
||||
@ -130,6 +143,7 @@ const ChatList: FC<OwnProps> = ({
|
||||
animationType={getAnimationType(id)}
|
||||
orderDiff={orderDiffById[id]}
|
||||
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
|
||||
observeIntersection={observe}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@ -138,6 +152,7 @@ const ChatList: FC<OwnProps> = ({
|
||||
return (
|
||||
<InfiniteScroll
|
||||
className="chat-list custom-scroll"
|
||||
ref={containerRef}
|
||||
items={viewportIds}
|
||||
preloadBackwards={CHAT_LIST_SLICE}
|
||||
withAbsolutePositioning
|
||||
|
||||
@ -22,7 +22,7 @@ import { formatDateToString } from '../../../util/dateFormat';
|
||||
import switchTheme from '../../../util/switchTheme';
|
||||
import { setPermanentWebVersion } from '../../../util/permanentWebVersion';
|
||||
import { clearWebsync } from '../../../util/websync';
|
||||
import { selectCurrentMessageList, selectTheme } from '../../../global/selectors';
|
||||
import { selectCurrentMessageList, selectIsPremiumPurchaseBlocked, selectTheme } from '../../../global/selectors';
|
||||
import { isChatArchived } from '../../../global/helpers';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useConnectionStatus from '../../../hooks/useConnectionStatus';
|
||||
@ -34,6 +34,7 @@ import MenuItem from '../../ui/MenuItem';
|
||||
import Button from '../../ui/Button';
|
||||
import SearchInput from '../../ui/SearchInput';
|
||||
import PickerSelectedItem from '../../common/PickerSelectedItem';
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
import Switcher from '../../ui/Switcher';
|
||||
import ShowTransition from '../../ui/ShowTransition';
|
||||
import ConnectionStatusOverlay from '../ConnectionStatusOverlay';
|
||||
@ -65,6 +66,7 @@ type StateProps =
|
||||
isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized'];
|
||||
areChatsLoaded?: boolean;
|
||||
hasPasscode?: boolean;
|
||||
isPremiumPurchaseBlocked?: boolean;
|
||||
}
|
||||
& Pick<GlobalState, 'connectionState' | 'isSyncing' | 'canInstall'>;
|
||||
|
||||
@ -96,6 +98,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
areChatsLoaded,
|
||||
hasPasscode,
|
||||
canInstall,
|
||||
isPremiumPurchaseBlocked,
|
||||
}) => {
|
||||
const {
|
||||
openChat,
|
||||
@ -106,6 +109,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
lockScreen,
|
||||
requestNextSettingsScreen,
|
||||
skipLockOnUnload,
|
||||
openPremiumModal,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
@ -190,11 +194,9 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
openChat({ id: currentUserId, shouldReplaceHistory: true });
|
||||
}, [currentUserId, openChat]);
|
||||
|
||||
const handleSelectPasscode = useCallback(() => {
|
||||
requestNextSettingsScreen(
|
||||
hasPasscode ? SettingsScreens.PasscodeEnabled : SettingsScreens.PasscodeDisabled,
|
||||
);
|
||||
}, [hasPasscode, requestNextSettingsScreen]);
|
||||
const handleSelectPremium = useCallback(() => {
|
||||
openPremiumModal();
|
||||
}, [openPremiumModal]);
|
||||
|
||||
const handleDarkModeToggle = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
@ -273,13 +275,6 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
{lang('Settings')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="lock"
|
||||
onClick={handleSelectPasscode}
|
||||
>
|
||||
{lang('Passcode')}
|
||||
<span className="menu-item-badge">{lang('New')}</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="darkmode"
|
||||
onClick={handleDarkModeToggle}
|
||||
@ -292,6 +287,15 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
noAnimation
|
||||
/>
|
||||
</MenuItem>
|
||||
{!isPremiumPurchaseBlocked && (
|
||||
<MenuItem
|
||||
customIcon={<PremiumIcon withGradient big />}
|
||||
onClick={handleSelectPremium}
|
||||
>
|
||||
{lang('TelegramPremium')}
|
||||
<span className="menu-item-badge">{lang('New')}</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
icon="help"
|
||||
onClick={handleOpenTipsChat}
|
||||
@ -336,7 +340,6 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
onClick={getPromptInstall()}
|
||||
>
|
||||
Install App
|
||||
<span className="menu-item-badge">{lang('New')}</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
@ -431,6 +434,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
areChatsLoaded: Boolean(global.chats.listIds.active),
|
||||
hasPasscode: Boolean(global.passcode.hasPasscode),
|
||||
canInstall: Boolean(global.canInstall),
|
||||
isPremiumPurchaseBlocked: selectIsPremiumPurchaseBlocked(global),
|
||||
};
|
||||
},
|
||||
)(LeftMainHeader));
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
getMessageMediaThumbDataUri,
|
||||
getMessageVideo,
|
||||
getMessageRoundVideo,
|
||||
getMessageSticker,
|
||||
} from '../../../global/helpers';
|
||||
import { selectChat, selectUser } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
@ -31,6 +32,7 @@ import VerifiedIcon from '../../common/VerifiedIcon';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Link from '../../ui/Link';
|
||||
import FakeIcon from '../../common/FakeIcon';
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
|
||||
import './ChatMessage.scss';
|
||||
|
||||
@ -57,7 +59,7 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
const { focusMessage } = getActions();
|
||||
|
||||
const mediaThumbnail = getMessageMediaThumbDataUri(message);
|
||||
const mediaThumbnail = !getMessageSticker(message) ? getMessageMediaThumbDataUri(message) : undefined;
|
||||
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'micro'));
|
||||
const isRoundVideo = Boolean(getMessageRoundVideo(message));
|
||||
|
||||
@ -91,6 +93,7 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
|
||||
<div className="title">
|
||||
<h3 dir="auto">{renderText(getChatTitle(lang, chat, privateChatUser))}</h3>
|
||||
{chat.isVerified && <VerifiedIcon />}
|
||||
{privateChatUser?.isPremium && <PremiumIcon />}
|
||||
{chat.fakeType && <FakeIcon fakeType={chat.fakeType} />}
|
||||
</div>
|
||||
<div className="message-date">
|
||||
|
||||
@ -73,9 +73,9 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
buttonRef={buttonRef}
|
||||
>
|
||||
{isUserId(chatId) ? (
|
||||
<PrivateChatInfo userId={chatId} withUsername={withUsername} avatarSize="large" />
|
||||
<PrivateChatInfo userId={chatId} withUsername={withUsername} avatarSize="large" withVideoAvatar />
|
||||
) : (
|
||||
<GroupChatInfo chatId={chatId} withUsername={withUsername} avatarSize="large" />
|
||||
<GroupChatInfo chatId={chatId} withUsername={withUsername} avatarSize="large" withVideoAvatar />
|
||||
)}
|
||||
<DeleteChatModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
|
||||
@ -93,6 +93,10 @@
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.settings-main-menu-premium .PremiumIcon {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.settings-main-menu {
|
||||
padding: 0 0.5rem 0.75rem;
|
||||
background-color: var(--color-background);
|
||||
@ -330,6 +334,7 @@
|
||||
.settings-quick-reaction {
|
||||
.Radio-main .label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ReactionStaticEmoji {
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
margin: 0 auto 1rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
:global(.Avatar__img) {
|
||||
:global(.Avatar__media) {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
margin-inline-end: 1.5rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
:global(.Avatar__img) {
|
||||
:global(.Avatar__media) {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,14 +13,16 @@ import { selectUser } from '../../../global/selectors';
|
||||
import { getChatAvatarHash } from '../../../global/helpers';
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import AvatarEditable from '../../ui/AvatarEditable';
|
||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
import Spinner from '../../ui/Spinner';
|
||||
import InputText from '../../ui/InputText';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import UsernameInput from '../../common/UsernameInput';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import TextArea from '../../ui/TextArea';
|
||||
|
||||
type OwnProps = {
|
||||
isActive: boolean;
|
||||
@ -35,14 +37,12 @@ type StateProps = {
|
||||
currentUsername?: string;
|
||||
progress?: ProfileEditProgress;
|
||||
isUsernameAvailable?: boolean;
|
||||
maxBioLength: number;
|
||||
};
|
||||
|
||||
const runThrottled = throttle((cb) => cb(), 60000, true);
|
||||
|
||||
const MAX_BIO_LENGTH = 70;
|
||||
|
||||
const ERROR_FIRST_NAME_MISSING = 'Please provide your first name';
|
||||
const ERROR_BIO_TOO_LONG = 'Bio can\' be longer than 70 characters';
|
||||
|
||||
const SettingsEditProfile: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
@ -54,6 +54,7 @@ const SettingsEditProfile: FC<OwnProps & StateProps> = ({
|
||||
currentUsername,
|
||||
progress,
|
||||
isUsernameAvailable,
|
||||
maxBioLength,
|
||||
}) => {
|
||||
const {
|
||||
loadCurrentUser,
|
||||
@ -135,7 +136,7 @@ const SettingsEditProfile: FC<OwnProps & StateProps> = ({
|
||||
setIsProfileFieldsTouched(true);
|
||||
}, []);
|
||||
|
||||
const handleBioChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
const handleBioChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setBio(e.target.value);
|
||||
setIsProfileFieldsTouched(true);
|
||||
}, []);
|
||||
@ -155,11 +156,6 @@ const SettingsEditProfile: FC<OwnProps & StateProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (trimmedBio.length > MAX_BIO_LENGTH) {
|
||||
setError(ERROR_BIO_TOO_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
updateProfile({
|
||||
photo,
|
||||
...(isProfileFieldsTouched && {
|
||||
@ -201,12 +197,13 @@ const SettingsEditProfile: FC<OwnProps & StateProps> = ({
|
||||
label={lang('LastName')}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<InputText
|
||||
<TextArea
|
||||
value={bio}
|
||||
onChange={handleBioChange}
|
||||
label={lang('UserBio')}
|
||||
disabled={isLoading}
|
||||
error={error === ERROR_BIO_TOO_LONG ? error : undefined}
|
||||
maxLength={maxBioLength}
|
||||
maxLengthIndicator={maxBioLength ? (maxBioLength - bio.length).toString() : undefined}
|
||||
/>
|
||||
|
||||
<p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
@ -259,10 +256,13 @@ export default memo(withGlobal<OwnProps>(
|
||||
const { progress, isUsernameAvailable } = global.profileEdit || {};
|
||||
const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined;
|
||||
|
||||
const maxBioLength = selectCurrentLimit(global, 'aboutLength');
|
||||
|
||||
if (!currentUser) {
|
||||
return {
|
||||
progress,
|
||||
isUsernameAvailable,
|
||||
maxBioLength,
|
||||
};
|
||||
}
|
||||
|
||||
@ -283,6 +283,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
currentUsername,
|
||||
progress,
|
||||
isUsernameAvailable,
|
||||
maxBioLength,
|
||||
};
|
||||
},
|
||||
)(SettingsEditProfile));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
|
||||
import { getActions } from '../../../global';
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
|
||||
|
||||
@ -32,11 +32,10 @@ const SettingsHeader: FC<OwnProps> = ({
|
||||
}) => {
|
||||
const {
|
||||
signOut,
|
||||
deleteChatFolder,
|
||||
openDeleteChatFolderModal,
|
||||
} = getActions();
|
||||
|
||||
const [isSignOutDialogOpen, setIsSignOutDialogOpen] = useState(false);
|
||||
const [isDeleteFolderDialogOpen, setIsDeleteFolderDialogOpen] = useState(false);
|
||||
|
||||
const handleMultiClick = useMultiClick(5, () => {
|
||||
onScreenSelect(SettingsScreens.Experimental);
|
||||
@ -51,24 +50,16 @@ const SettingsHeader: FC<OwnProps> = ({
|
||||
}, []);
|
||||
|
||||
const openDeleteFolderConfirmation = useCallback(() => {
|
||||
setIsDeleteFolderDialogOpen(true);
|
||||
}, []);
|
||||
if (!editedFolderId) return;
|
||||
|
||||
const closeDeleteFolderConfirmation = useCallback(() => {
|
||||
setIsDeleteFolderDialogOpen(false);
|
||||
}, []);
|
||||
openDeleteChatFolderModal({ folderId: editedFolderId });
|
||||
}, [editedFolderId, openDeleteChatFolderModal]);
|
||||
|
||||
const handleSignOutMessage = useCallback(() => {
|
||||
closeSignOutConfirmation();
|
||||
signOut();
|
||||
}, [closeSignOutConfirmation, signOut]);
|
||||
|
||||
const handleDeleteFolderMessage = useCallback(() => {
|
||||
closeDeleteFolderConfirmation();
|
||||
deleteChatFolder({ id: editedFolderId });
|
||||
onReset();
|
||||
}, [editedFolderId, closeDeleteFolderConfirmation, deleteChatFolder, onReset]);
|
||||
|
||||
const SettingsMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
||||
return ({ onTrigger, isOpen }) => (
|
||||
<Button
|
||||
@ -283,14 +274,6 @@ const SettingsHeader: FC<OwnProps> = ({
|
||||
confirmHandler={handleSignOutMessage}
|
||||
confirmIsDestructive
|
||||
/>
|
||||
<ConfirmDialog
|
||||
isOpen={isDeleteFolderDialogOpen}
|
||||
onClose={closeDeleteFolderConfirmation}
|
||||
text={lang('FilterDeleteAlert')}
|
||||
confirmLabel={lang('Delete')}
|
||||
confirmHandler={handleDeleteFolderMessage}
|
||||
confirmIsDestructive
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,13 +5,14 @@ import { getActions, withGlobal } from '../../../global';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
import type { ApiUser } from '../../../api/types';
|
||||
|
||||
import { selectUser } from '../../../global/selectors';
|
||||
import { selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import ProfileInfo from '../../common/ProfileInfo';
|
||||
import ChatExtra from '../../common/ChatExtra';
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
@ -23,6 +24,7 @@ type StateProps = {
|
||||
sessionCount: number;
|
||||
currentUser?: ApiUser;
|
||||
lastSyncTime?: number;
|
||||
canBuyPremium?: boolean;
|
||||
};
|
||||
|
||||
const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
@ -32,8 +34,14 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
currentUser,
|
||||
sessionCount,
|
||||
lastSyncTime,
|
||||
canBuyPremium,
|
||||
}) => {
|
||||
const { loadProfilePhotos, loadAuthorizations, loadWebAuthorizations } = getActions();
|
||||
const {
|
||||
loadProfilePhotos,
|
||||
loadAuthorizations,
|
||||
loadWebAuthorizations,
|
||||
openPremiumModal,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const profileId = currentUser?.id;
|
||||
@ -106,6 +114,16 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
{lang('Filters')}
|
||||
</ListItem>
|
||||
{canBuyPremium && (
|
||||
<ListItem
|
||||
leftElement={<PremiumIcon withGradient big />}
|
||||
className="settings-main-menu-premium"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openPremiumModal()}
|
||||
>
|
||||
{lang('TelegramPremium')}
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem
|
||||
icon="active-sessions"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
@ -135,6 +153,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
sessionCount: global.activeSessions.orderedHashes.length,
|
||||
currentUser: currentUserId ? selectUser(global, currentUserId) : undefined,
|
||||
lastSyncTime,
|
||||
canBuyPremium: !selectIsPremiumPurchaseBlocked(global),
|
||||
};
|
||||
},
|
||||
)(SettingsMain));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useEffect } from '../../../lib/teact/teact';
|
||||
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiPrivacySettings } from '../../../types';
|
||||
@ -24,6 +24,7 @@ type StateProps = {
|
||||
webAuthCount: number;
|
||||
isSensitiveEnabled?: boolean;
|
||||
canChangeSensitive?: boolean;
|
||||
shouldArchiveAndMuteNewNonContact?: boolean;
|
||||
privacyPhoneNumber?: ApiPrivacySettings;
|
||||
privacyLastSeen?: ApiPrivacySettings;
|
||||
privacyProfilePhoto?: ApiPrivacySettings;
|
||||
@ -41,6 +42,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
webAuthCount,
|
||||
isSensitiveEnabled,
|
||||
canChangeSensitive,
|
||||
shouldArchiveAndMuteNewNonContact,
|
||||
privacyPhoneNumber,
|
||||
privacyLastSeen,
|
||||
privacyProfilePhoto,
|
||||
@ -57,6 +59,8 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
loadAuthorizations,
|
||||
loadContentSettings,
|
||||
updateContentSettings,
|
||||
loadGlobalPrivacySettings,
|
||||
updateGlobalPrivacySettings,
|
||||
} = getActions();
|
||||
|
||||
useEffect(() => {
|
||||
@ -66,6 +70,12 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
loadContentSettings();
|
||||
}, [loadBlockedContacts, loadAuthorizations, loadPrivacySettings, loadContentSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActive) {
|
||||
loadGlobalPrivacySettings();
|
||||
}
|
||||
}, [isActive, loadGlobalPrivacySettings]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack({
|
||||
@ -73,6 +83,12 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
onBack: onReset,
|
||||
});
|
||||
|
||||
const handleArchiveAndMuteChange = useCallback((isEnabled: boolean) => {
|
||||
updateGlobalPrivacySettings({
|
||||
shouldArchiveAndMuteNewNonContact: isEnabled,
|
||||
});
|
||||
}, [updateGlobalPrivacySettings]);
|
||||
|
||||
function getVisibilityValue(setting?: ApiPrivacySettings) {
|
||||
const { visibility } = setting || {};
|
||||
const blockCount = setting ? setting.blockChatIds.length + setting.blockUserIds.length : 0;
|
||||
@ -253,6 +269,18 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
</ListItem>
|
||||
</div>
|
||||
|
||||
<div className="settings-item">
|
||||
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{lang('NewChatsFromNonContacts')}
|
||||
</h4>
|
||||
<Checkbox
|
||||
label={lang('ArchiveAndMute')}
|
||||
subLabel={lang('ArchiveAndMuteInfo')}
|
||||
checked={Boolean(shouldArchiveAndMuteNewNonContact)}
|
||||
onCheck={handleArchiveAndMuteChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{canChangeSensitive && (
|
||||
<div className="settings-item">
|
||||
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
@ -276,7 +304,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const {
|
||||
settings: {
|
||||
byKey: {
|
||||
hasPassword, isSensitiveEnabled, canChangeSensitive,
|
||||
hasPassword, isSensitiveEnabled, canChangeSensitive, shouldArchiveAndMuteNewNonContact,
|
||||
},
|
||||
privacy,
|
||||
},
|
||||
@ -292,6 +320,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
blockedCount: blocked.totalCount,
|
||||
webAuthCount: global.activeWebSessions.orderedHashes.length,
|
||||
isSensitiveEnabled,
|
||||
shouldArchiveAndMuteNewNonContact,
|
||||
canChangeSensitive,
|
||||
privacyPhoneNumber: privacy.phoneNumber,
|
||||
privacyLastSeen: privacy.lastSeen,
|
||||
|
||||
@ -79,7 +79,7 @@ const SettingsPrivacyBlockedUsers: FC<OwnProps & StateProps> = ({
|
||||
}]}
|
||||
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
|
||||
>
|
||||
<Avatar size="medium" user={user} chat={chat} />
|
||||
<Avatar size="medium" user={user} chat={chat} noVideo />
|
||||
<div className="contact-info" dir="auto">
|
||||
<h3 dir="auto">{renderText((isPrivate ? getUserFullName(user) : getChatTitle(lang, chat!)) || '')}</h3>
|
||||
{user?.phoneNumber && (
|
||||
|
||||
@ -4,6 +4,8 @@ import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiAvailableReaction } from '../../../api/types';
|
||||
|
||||
import { selectIsCurrentUserPremium } from '../../../global/selectors';
|
||||
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
|
||||
@ -16,14 +18,16 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
availableReactions?: ApiAvailableReaction[];
|
||||
isPremium?: boolean;
|
||||
selectedReaction?: string;
|
||||
};
|
||||
|
||||
const SettingsQuickReaction: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
onReset,
|
||||
availableReactions,
|
||||
isPremium,
|
||||
selectedReaction,
|
||||
onReset,
|
||||
}) => {
|
||||
const { setDefaultReaction } = getActions();
|
||||
|
||||
@ -32,7 +36,9 @@ const SettingsQuickReaction: FC<OwnProps & StateProps> = ({
|
||||
onBack: onReset,
|
||||
});
|
||||
|
||||
const options = availableReactions?.filter((l) => !l.isInactive).map((l) => {
|
||||
const options = availableReactions?.filter((l) => (
|
||||
!(l.isInactive || (!isPremium && l.isPremium))
|
||||
)).map((l) => {
|
||||
return {
|
||||
label: <><ReactionStaticEmoji reaction={l.reaction} />{l.title}</>,
|
||||
value: l.reaction,
|
||||
@ -58,10 +64,12 @@ const SettingsQuickReaction: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global) => {
|
||||
const { availableReactions, appConfig } = global;
|
||||
const isPremium = selectIsCurrentUserPremium(global);
|
||||
|
||||
return {
|
||||
availableReactions,
|
||||
selectedReaction: appConfig?.defaultReaction,
|
||||
isPremium,
|
||||
};
|
||||
},
|
||||
)(SettingsQuickReaction));
|
||||
|
||||
@ -81,6 +81,7 @@ const SettingsStickerSet: FC<OwnProps> = ({
|
||||
observeIntersection={observeIntersection}
|
||||
clickArg={undefined}
|
||||
noContextMenu
|
||||
isCurrentUserPremium
|
||||
/>
|
||||
<div className="multiline-menu-item">
|
||||
<div className="title">{stickerSet.title}</div>
|
||||
|
||||