Support paid messages (#5712)
This commit is contained in:
parent
dc61b12f9b
commit
5bb202857d
@ -87,6 +87,10 @@ export interface GramJsAppConfig extends LimitsConfig {
|
|||||||
stargifts_convert_period_max?: number;
|
stargifts_convert_period_max?: number;
|
||||||
starref_start_param_prefixes?: string[];
|
starref_start_param_prefixes?: string[];
|
||||||
ton_blockchain_explorer_url?: string;
|
ton_blockchain_explorer_url?: string;
|
||||||
|
stars_paid_messages_available?: boolean;
|
||||||
|
stars_usd_withdraw_rate_x1000?: number;
|
||||||
|
stars_paid_message_commission_permille?: number;
|
||||||
|
stars_paid_message_amount_max?: number;
|
||||||
stargifts_pinned_to_top_limit?: number;
|
stargifts_pinned_to_top_limit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +168,10 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
maxPinnedStoriesCount: appConfig.stories_pinned_to_top_count_max,
|
maxPinnedStoriesCount: appConfig.stories_pinned_to_top_count_max,
|
||||||
groupTranscribeLevelMin: appConfig.group_transcribe_level_min,
|
groupTranscribeLevelMin: appConfig.group_transcribe_level_min,
|
||||||
canLimitNewMessagesWithoutPremium: appConfig.new_noncontact_peers_require_premium_without_ownpremium,
|
canLimitNewMessagesWithoutPremium: appConfig.new_noncontact_peers_require_premium_without_ownpremium,
|
||||||
|
starsPaidMessagesAvailable: appConfig.stars_paid_messages_available,
|
||||||
|
starsPaidMessageCommissionPermille: appConfig.stars_paid_message_commission_permille,
|
||||||
|
starsPaidMessageAmountMax: appConfig.stars_paid_message_amount_max,
|
||||||
|
starsUsdWithdrawRateX1000: appConfig.stars_usd_withdraw_rate_x1000,
|
||||||
bandwidthPremiumNotifyPeriod: appConfig.upload_premium_speedup_notify_period,
|
bandwidthPremiumNotifyPeriod: appConfig.upload_premium_speedup_notify_period,
|
||||||
bandwidthPremiumUploadSpeedup: appConfig.upload_premium_speedup_upload,
|
bandwidthPremiumUploadSpeedup: appConfig.upload_premium_speedup_upload,
|
||||||
bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download,
|
bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download,
|
||||||
|
|||||||
@ -75,6 +75,7 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
const boostLevel = ('level' in peerEntity) ? peerEntity.level : undefined;
|
const boostLevel = ('level' in peerEntity) ? peerEntity.level : undefined;
|
||||||
const areProfilesShown = Boolean('signatureProfiles' in peerEntity && peerEntity.signatureProfiles);
|
const areProfilesShown = Boolean('signatureProfiles' in peerEntity && peerEntity.signatureProfiles);
|
||||||
const subscriptionUntil = 'subscriptionUntilDate' in peerEntity ? peerEntity.subscriptionUntilDate : undefined;
|
const subscriptionUntil = 'subscriptionUntilDate' in peerEntity ? peerEntity.subscriptionUntilDate : undefined;
|
||||||
|
const paidMessagesStars = 'sendPaidMessagesStars' in peerEntity ? peerEntity.sendPaidMessagesStars : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isMin,
|
isMin,
|
||||||
@ -110,6 +111,7 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
boostLevel,
|
boostLevel,
|
||||||
botVerificationIconId,
|
botVerificationIconId,
|
||||||
subscriptionUntil,
|
subscriptionUntil,
|
||||||
|
paidMessagesStars: paidMessagesStars?.toJSNumber(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -267,6 +267,7 @@ export function buildApiMessageWithChatId(
|
|||||||
isInvertedMedia,
|
isInvertedMedia,
|
||||||
isVideoProcessingPending,
|
isVideoProcessingPending,
|
||||||
reportDeliveryUntilDate: mtpMessage.reportDeliveryUntilDate,
|
reportDeliveryUntilDate: mtpMessage.reportDeliveryUntilDate,
|
||||||
|
paidMessageStars: mtpMessage.paidMessageStars?.toJSNumber(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,6 +393,8 @@ export function buildLocalMessage(
|
|||||||
story?: ApiStory | ApiStorySkipped,
|
story?: ApiStory | ApiStorySkipped,
|
||||||
isInvertedMedia?: true,
|
isInvertedMedia?: true,
|
||||||
effectId?: string,
|
effectId?: string,
|
||||||
|
isPending?: true,
|
||||||
|
messagePriceInStars?: number,
|
||||||
) {
|
) {
|
||||||
const localId = getNextLocalMessageId(lastMessageId);
|
const localId = getNextLocalMessageId(lastMessageId);
|
||||||
const media = attachment && buildUploadingMedia(attachment);
|
const media = attachment && buildUploadingMedia(attachment);
|
||||||
@ -427,11 +430,13 @@ export function buildLocalMessage(
|
|||||||
isForwardingAllowed: true,
|
isForwardingAllowed: true,
|
||||||
isInvertedMedia,
|
isInvertedMedia,
|
||||||
effectId,
|
effectId,
|
||||||
|
...(isPending && { sendingState: 'messageSendingStatePending' }),
|
||||||
|
...(messagePriceInStars && { paidMessageStars: messagePriceInStars }),
|
||||||
} satisfies ApiMessage;
|
} satisfies ApiMessage;
|
||||||
|
|
||||||
const emojiOnlyCount = getEmojiOnlyCountForMessage(message.content, message.groupedId);
|
const emojiOnlyCount = getEmojiOnlyCountForMessage(message.content, message.groupedId);
|
||||||
|
|
||||||
const finalMessage = {
|
const finalMessage : ApiMessage = {
|
||||||
...message,
|
...message,
|
||||||
...(emojiOnlyCount && { emojiOnlyCount }),
|
...(emojiOnlyCount && { emojiOnlyCount }),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -101,6 +101,8 @@ export function buildPrivacyKey(key: GramJs.TypePrivacyKey): ApiPrivacyKey | und
|
|||||||
return 'birthday';
|
return 'birthday';
|
||||||
case 'PrivacyKeyStarGiftsAutoSave':
|
case 'PrivacyKeyStarGiftsAutoSave':
|
||||||
return 'gifts';
|
return 'gifts';
|
||||||
|
case 'PrivacyKeyNoPaidMessages':
|
||||||
|
return 'noPaidMessages';
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -535,7 +535,7 @@ export function buildApiStarsTransactionPeer(peer: GramJs.TypeStarsTransactionPe
|
|||||||
export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction): ApiStarsTransaction {
|
export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction): ApiStarsTransaction {
|
||||||
const {
|
const {
|
||||||
date, id, peer, stars, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction,
|
date, id, peer, stars, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction,
|
||||||
subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade,
|
subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade, paidMessages,
|
||||||
} = transaction;
|
} = transaction;
|
||||||
|
|
||||||
if (photo) {
|
if (photo) {
|
||||||
@ -567,6 +567,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
|
|||||||
giveawayPostId,
|
giveawayPostId,
|
||||||
starRefCommision,
|
starRefCommision,
|
||||||
isGiftUpgrade: stargiftUpgrade,
|
isGiftUpgrade: stargiftUpgrade,
|
||||||
|
paidMessages,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
|||||||
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
||||||
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
|
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
|
||||||
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
|
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
|
||||||
botCanManageEmojiStatus, settings,
|
botCanManageEmojiStatus, settings, sendPaidMessagesStars,
|
||||||
},
|
},
|
||||||
users,
|
users,
|
||||||
} = mtpUserFull;
|
} = mtpUserFull;
|
||||||
@ -56,6 +56,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
|||||||
starGiftCount: stargiftsCount,
|
starGiftCount: stargiftsCount,
|
||||||
isBotCanManageEmojiStatus: botCanManageEmojiStatus,
|
isBotCanManageEmojiStatus: botCanManageEmojiStatus,
|
||||||
hasScheduledMessages: hasScheduled,
|
hasScheduledMessages: hasScheduled,
|
||||||
|
paidMessagesStars: sendPaidMessagesStars?.toJSNumber(),
|
||||||
settings: buildApiPeerSettings(settings),
|
settings: buildApiPeerSettings(settings),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -69,6 +70,7 @@ export function buildApiPeerSettings({
|
|||||||
phoneCountry,
|
phoneCountry,
|
||||||
nameChangeDate,
|
nameChangeDate,
|
||||||
photoChangeDate,
|
photoChangeDate,
|
||||||
|
chargePaidMessageStars,
|
||||||
}: GramJs.PeerSettings): ApiPeerSettings {
|
}: GramJs.PeerSettings): ApiPeerSettings {
|
||||||
return {
|
return {
|
||||||
isAutoArchived: Boolean(autoarchived),
|
isAutoArchived: Boolean(autoarchived),
|
||||||
@ -79,6 +81,7 @@ export function buildApiPeerSettings({
|
|||||||
phoneCountry,
|
phoneCountry,
|
||||||
nameChangeDate,
|
nameChangeDate,
|
||||||
photoChangeDate,
|
photoChangeDate,
|
||||||
|
chargedPaidMessageStars: chargePaidMessageStars?.toJSNumber(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +93,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
const {
|
const {
|
||||||
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
|
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
|
||||||
bot, botActiveUsers, botVerificationIcon, botInlinePlaceholder, botAttachMenu, botCanEdit,
|
bot, botActiveUsers, botVerificationIcon, botInlinePlaceholder, botAttachMenu, botCanEdit,
|
||||||
|
sendPaidMessagesStars,
|
||||||
} = mtpUser;
|
} = mtpUser;
|
||||||
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined;
|
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined;
|
||||||
const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo);
|
const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo);
|
||||||
@ -128,6 +132,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
botActiveUsers,
|
botActiveUsers,
|
||||||
botVerificationIconId: botVerificationIcon?.toString(),
|
botVerificationIconId: botVerificationIcon?.toString(),
|
||||||
color: mtpUser.color && buildApiPeerColor(mtpUser.color),
|
color: mtpUser.color && buildApiPeerColor(mtpUser.color),
|
||||||
|
paidMessagesStars: sendPaidMessagesStars?.toJSNumber(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -488,6 +488,9 @@ export function buildInputPrivacyKey(privacyKey: ApiPrivacyKey) {
|
|||||||
|
|
||||||
case 'gifts':
|
case 'gifts':
|
||||||
return new GramJs.InputPrivacyKeyStarGiftsAutoSave();
|
return new GramJs.InputPrivacyKeyStarGiftsAutoSave();
|
||||||
|
|
||||||
|
case 'noPaidMessages':
|
||||||
|
return new GramJs.InputPrivacyKeyNoPaidMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -143,7 +143,7 @@ export async function fetchInlineBotResults({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function sendInlineBotResult({
|
export async function sendInlineBotResult({
|
||||||
chat, replyInfo, resultId, queryId, sendAs, isSilent, scheduleDate,
|
chat, replyInfo, resultId, queryId, sendAs, isSilent, scheduleDate, allowPaidStars,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
replyInfo?: ApiInputMessageReplyInfo;
|
replyInfo?: ApiInputMessageReplyInfo;
|
||||||
@ -152,6 +152,7 @@ export async function sendInlineBotResult({
|
|||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduleDate?: number;
|
scheduleDate?: number;
|
||||||
|
allowPaidStars?: number;
|
||||||
}) {
|
}) {
|
||||||
const randomId = generateRandomBigInt();
|
const randomId = generateRandomBigInt();
|
||||||
|
|
||||||
@ -165,6 +166,7 @@ export async function sendInlineBotResult({
|
|||||||
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
||||||
...(isSilent && { silent: true }),
|
...(isSilent && { silent: true }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
|
...(allowPaidStars && { allowPaidStars: BigInt(allowPaidStars) }),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,14 @@ import BigInt from 'big-integer';
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
import { RPCError } from '../../../lib/gramjs/errors';
|
import { RPCError } from '../../../lib/gramjs/errors';
|
||||||
|
|
||||||
import type { ThreadId, WebPageMediaSize } from '../../../types';
|
import type {
|
||||||
|
ForwardMessagesParams,
|
||||||
|
SendMessageParams,
|
||||||
|
ThreadId,
|
||||||
|
} from '../../../types';
|
||||||
import type {
|
import type {
|
||||||
ApiAttachment,
|
ApiAttachment,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiContact,
|
|
||||||
ApiError,
|
ApiError,
|
||||||
ApiFormattedText,
|
ApiFormattedText,
|
||||||
ApiGlobalMessageSearchType,
|
ApiGlobalMessageSearchType,
|
||||||
@ -15,18 +18,13 @@ import type {
|
|||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
ApiMessageSearchContext,
|
ApiMessageSearchContext,
|
||||||
ApiMessageSearchType,
|
ApiMessageSearchType,
|
||||||
ApiNewPoll,
|
|
||||||
ApiOnProgress,
|
ApiOnProgress,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiPoll,
|
ApiPoll,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiSendMessageAction,
|
ApiSendMessageAction,
|
||||||
ApiSticker,
|
|
||||||
ApiStory,
|
|
||||||
ApiStorySkipped,
|
|
||||||
ApiUser,
|
ApiUser,
|
||||||
ApiUserStatus,
|
ApiUserStatus,
|
||||||
ApiVideo,
|
|
||||||
MediaContent,
|
MediaContent,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import {
|
import {
|
||||||
@ -255,56 +253,16 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message
|
|||||||
|
|
||||||
let mediaQueue = Promise.resolve();
|
let mediaQueue = Promise.resolve();
|
||||||
|
|
||||||
export function sendMessage(
|
export function sendMessageLocal(
|
||||||
{
|
params: SendMessageParams,
|
||||||
chat,
|
|
||||||
lastMessageId,
|
|
||||||
text,
|
|
||||||
entities,
|
|
||||||
replyInfo,
|
|
||||||
attachment,
|
|
||||||
sticker,
|
|
||||||
story,
|
|
||||||
gif,
|
|
||||||
poll,
|
|
||||||
contact,
|
|
||||||
isSilent,
|
|
||||||
scheduledAt,
|
|
||||||
groupedId,
|
|
||||||
noWebPage,
|
|
||||||
sendAs,
|
|
||||||
shouldUpdateStickerSetOrder,
|
|
||||||
wasDrafted,
|
|
||||||
isInvertedMedia,
|
|
||||||
effectId,
|
|
||||||
webPageMediaSize,
|
|
||||||
webPageUrl,
|
|
||||||
}: {
|
|
||||||
chat: ApiChat;
|
|
||||||
lastMessageId?: number;
|
|
||||||
text?: string;
|
|
||||||
entities?: ApiMessageEntity[];
|
|
||||||
replyInfo?: ApiInputReplyInfo;
|
|
||||||
attachment?: ApiAttachment;
|
|
||||||
sticker?: ApiSticker;
|
|
||||||
story?: ApiStory | ApiStorySkipped;
|
|
||||||
gif?: ApiVideo;
|
|
||||||
poll?: ApiNewPoll;
|
|
||||||
contact?: ApiContact;
|
|
||||||
isSilent?: boolean;
|
|
||||||
scheduledAt?: number;
|
|
||||||
groupedId?: string;
|
|
||||||
noWebPage?: boolean;
|
|
||||||
sendAs?: ApiPeer;
|
|
||||||
shouldUpdateStickerSetOrder?: boolean;
|
|
||||||
wasDrafted?: boolean;
|
|
||||||
isInvertedMedia?: true;
|
|
||||||
effectId?: string;
|
|
||||||
webPageMediaSize?: WebPageMediaSize;
|
|
||||||
webPageUrl?: string;
|
|
||||||
},
|
|
||||||
onProgress?: ApiOnProgress,
|
|
||||||
) {
|
) {
|
||||||
|
const {
|
||||||
|
chat, lastMessageId, text, entities, replyInfo, attachment, sticker, story, gif, poll, contact,
|
||||||
|
scheduledAt, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending, messagePriceInStars,
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
if (!chat) return undefined;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
message: localMessage,
|
message: localMessage,
|
||||||
poll: localPoll,
|
poll: localPoll,
|
||||||
@ -325,6 +283,8 @@ export function sendMessage(
|
|||||||
story,
|
story,
|
||||||
isInvertedMedia,
|
isInvertedMedia,
|
||||||
effectId,
|
effectId,
|
||||||
|
isPending,
|
||||||
|
messagePriceInStars,
|
||||||
);
|
);
|
||||||
|
|
||||||
sendApiUpdate({
|
sendApiUpdate({
|
||||||
@ -336,6 +296,22 @@ export function sendMessage(
|
|||||||
wasDrafted,
|
wasDrafted,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return localMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendApiMessage(
|
||||||
|
params: SendMessageParams,
|
||||||
|
localMessage: ApiMessage,
|
||||||
|
onProgress?: ApiOnProgress,
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
chat, text, entities, replyInfo, attachment, sticker, story, gif, poll, contact,
|
||||||
|
isSilent, scheduledAt, groupedId, noWebPage, sendAs, shouldUpdateStickerSetOrder,
|
||||||
|
isInvertedMedia, effectId, webPageMediaSize, webPageUrl, messagePriceInStars,
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
if (!chat) return undefined;
|
||||||
|
|
||||||
// This is expected to arrive after `updateMessageSendSucceeded` which replaces the local ID,
|
// This is expected to arrive after `updateMessageSendSucceeded` which replaces the local ID,
|
||||||
// so in most cases this will be simply ignored
|
// so in most cases this will be simply ignored
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
@ -361,6 +337,7 @@ export function sendMessage(
|
|||||||
groupedId,
|
groupedId,
|
||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
messagePriceInStars,
|
||||||
}, randomId, localMessage, onProgress);
|
}, randomId, localMessage, onProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,6 +397,7 @@ export function sendMessage(
|
|||||||
...(shouldUpdateStickerSetOrder && { updateStickersetsOrder: shouldUpdateStickerSetOrder }),
|
...(shouldUpdateStickerSetOrder && { updateStickersetsOrder: shouldUpdateStickerSetOrder }),
|
||||||
...(isInvertedMedia && { invertMedia: isInvertedMedia }),
|
...(isInvertedMedia && { invertMedia: isInvertedMedia }),
|
||||||
...(effectId && { effect: BigInt(effectId) }),
|
...(effectId && { effect: BigInt(effectId) }),
|
||||||
|
...(messagePriceInStars && { allowPaidStars: BigInt(messagePriceInStars) }),
|
||||||
}), {
|
}), {
|
||||||
shouldThrow: true,
|
shouldThrow: true,
|
||||||
shouldIgnoreUpdates: true,
|
shouldIgnoreUpdates: true,
|
||||||
@ -443,6 +421,14 @@ export function sendMessage(
|
|||||||
return messagePromise;
|
return messagePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sendMessage(
|
||||||
|
params: SendMessageParams,
|
||||||
|
onProgress?: ApiOnProgress,
|
||||||
|
) {
|
||||||
|
const localMessage = params.localMessage || sendMessageLocal(params);
|
||||||
|
return localMessage ? sendApiMessage(params, localMessage, onProgress) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const groupedUploads: Record<string, {
|
const groupedUploads: Record<string, {
|
||||||
counter: number;
|
counter: number;
|
||||||
singleMediaByIndex: Record<number, GramJs.InputSingleMedia>;
|
singleMediaByIndex: Record<number, GramJs.InputSingleMedia>;
|
||||||
@ -460,6 +446,7 @@ function sendGroupedMedia(
|
|||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
sendAs,
|
sendAs,
|
||||||
|
messagePriceInStars,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
text?: string;
|
text?: string;
|
||||||
@ -470,6 +457,7 @@ function sendGroupedMedia(
|
|||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
|
messagePriceInStars?: number;
|
||||||
},
|
},
|
||||||
randomId: GramJs.long,
|
randomId: GramJs.long,
|
||||||
localMessage: ApiMessage,
|
localMessage: ApiMessage,
|
||||||
@ -536,6 +524,7 @@ function sendGroupedMedia(
|
|||||||
|
|
||||||
const { singleMediaByIndex, localMessages } = groupedUploads[groupedId];
|
const { singleMediaByIndex, localMessages } = groupedUploads[groupedId];
|
||||||
delete groupedUploads[groupedId];
|
delete groupedUploads[groupedId];
|
||||||
|
const count = Object.values(singleMediaByIndex).length;
|
||||||
|
|
||||||
const update = await invokeRequest(new GramJs.messages.SendMultiMedia({
|
const update = await invokeRequest(new GramJs.messages.SendMultiMedia({
|
||||||
clearDraft: true,
|
clearDraft: true,
|
||||||
@ -545,6 +534,7 @@ function sendGroupedMedia(
|
|||||||
...(isSilent && { silent: isSilent }),
|
...(isSilent && { silent: isSilent }),
|
||||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
|
...(messagePriceInStars && { allowPaidStars: BigInt(messagePriceInStars * count) }),
|
||||||
}), {
|
}), {
|
||||||
shouldIgnoreUpdates: true,
|
shouldIgnoreUpdates: true,
|
||||||
});
|
});
|
||||||
@ -1534,40 +1524,17 @@ export async function fetchExtendedMedia({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function forwardMessages({
|
export function forwardMessagesLocal(params: ForwardMessagesParams) {
|
||||||
fromChat,
|
const {
|
||||||
toChat,
|
toChat, toThreadId, messages,
|
||||||
toThreadId,
|
scheduledAt, sendAs, noAuthors, noCaptions,
|
||||||
messages,
|
isCurrentUserPremium, wasDrafted, lastMessageId,
|
||||||
isSilent,
|
} = params;
|
||||||
scheduledAt,
|
|
||||||
sendAs,
|
|
||||||
withMyScore,
|
|
||||||
noAuthors,
|
|
||||||
noCaptions,
|
|
||||||
isCurrentUserPremium,
|
|
||||||
wasDrafted,
|
|
||||||
lastMessageId,
|
|
||||||
}: {
|
|
||||||
fromChat: ApiChat;
|
|
||||||
toChat: ApiChat;
|
|
||||||
toThreadId?: ThreadId;
|
|
||||||
messages: ApiMessage[];
|
|
||||||
isSilent?: boolean;
|
|
||||||
scheduledAt?: number;
|
|
||||||
sendAs?: ApiPeer;
|
|
||||||
withMyScore?: boolean;
|
|
||||||
noAuthors?: boolean;
|
|
||||||
noCaptions?: boolean;
|
|
||||||
isCurrentUserPremium?: boolean;
|
|
||||||
wasDrafted?: boolean;
|
|
||||||
lastMessageId?: number;
|
|
||||||
}) {
|
|
||||||
const messageIds = messages.map(({ id }) => id);
|
|
||||||
const randomIds = messages.map(generateRandomBigInt);
|
|
||||||
const localMessages: Record<string, ApiMessage> = {};
|
|
||||||
|
|
||||||
messages.forEach((message, index) => {
|
const messageIds = messages.map(({ id }) => id);
|
||||||
|
const localMessages: ApiMessage[] = [];
|
||||||
|
|
||||||
|
messages.forEach((message) => {
|
||||||
const localMessage = buildLocalForwardedMessage({
|
const localMessage = buildLocalForwardedMessage({
|
||||||
toChat,
|
toChat,
|
||||||
toThreadId: Number(toThreadId),
|
toThreadId: Number(toThreadId),
|
||||||
@ -1579,7 +1546,7 @@ export async function forwardMessages({
|
|||||||
lastMessageId,
|
lastMessageId,
|
||||||
sendAs,
|
sendAs,
|
||||||
});
|
});
|
||||||
localMessages[randomIds[index].toString()] = localMessage;
|
localMessages.push(localMessage);
|
||||||
|
|
||||||
sendApiUpdate({
|
sendApiUpdate({
|
||||||
'@type': localMessage.isScheduled ? 'newScheduledMessage' : 'newMessage',
|
'@type': localMessage.isScheduled ? 'newScheduledMessage' : 'newMessage',
|
||||||
@ -1589,7 +1556,25 @@ export async function forwardMessages({
|
|||||||
wasDrafted,
|
wasDrafted,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return { messageIds, localMessages };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function forwardApiMessages(params: ForwardMessagesParams) {
|
||||||
|
const {
|
||||||
|
fromChat, toChat, toThreadId, isSilent,
|
||||||
|
scheduledAt, sendAs, withMyScore, noAuthors, noCaptions,
|
||||||
|
forwardedLocalMessagesSlice, messagePriceInStars,
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
if (!forwardedLocalMessagesSlice) return;
|
||||||
|
|
||||||
|
const {
|
||||||
|
messageIds, localMessages,
|
||||||
|
} = forwardedLocalMessagesSlice;
|
||||||
|
|
||||||
|
const priceInStars = messagePriceInStars ? messagePriceInStars * messageIds.length : undefined;
|
||||||
|
|
||||||
|
const randomIds = messageIds.map(generateRandomBigInt);
|
||||||
try {
|
try {
|
||||||
const update = await invokeRequest(new GramJs.messages.ForwardMessages({
|
const update = await invokeRequest(new GramJs.messages.ForwardMessages({
|
||||||
fromPeer: buildInputPeer(fromChat.id, fromChat.accessHash),
|
fromPeer: buildInputPeer(fromChat.id, fromChat.accessHash),
|
||||||
@ -1603,11 +1588,16 @@ export async function forwardMessages({
|
|||||||
...(toThreadId && { topMsgId: Number(toThreadId) }),
|
...(toThreadId && { topMsgId: Number(toThreadId) }),
|
||||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
|
...(priceInStars && { allowPaidStars: BigInt(priceInStars) }),
|
||||||
}), {
|
}), {
|
||||||
shouldThrow: true,
|
shouldThrow: true,
|
||||||
shouldIgnoreUpdates: true,
|
shouldIgnoreUpdates: true,
|
||||||
});
|
});
|
||||||
if (update) handleMultipleLocalMessagesUpdate(localMessages, update);
|
const messagesForUpdate: Record<string, ApiMessage> = {};
|
||||||
|
localMessages.forEach((message, index) => {
|
||||||
|
messagesForUpdate[randomIds[index].toString()] = message;
|
||||||
|
});
|
||||||
|
if (update) handleMultipleLocalMessagesUpdate(messagesForUpdate, update);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
Object.values(localMessages).forEach((localMessage) => {
|
Object.values(localMessages).forEach((localMessage) => {
|
||||||
sendApiUpdate({
|
sendApiUpdate({
|
||||||
@ -1620,6 +1610,18 @@ export async function forwardMessages({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function forwardMessages(params: ForwardMessagesParams) {
|
||||||
|
if (params.forwardedLocalMessagesSlice) {
|
||||||
|
await forwardApiMessages(params);
|
||||||
|
} else {
|
||||||
|
const newParams = {
|
||||||
|
...params,
|
||||||
|
forwardedLocalMessagesSlice: forwardMessagesLocal(params),
|
||||||
|
};
|
||||||
|
await forwardApiMessages(newParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function findFirstMessageIdAfterDate({
|
export async function findFirstMessageIdAfterDate({
|
||||||
chat,
|
chat,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|||||||
@ -656,6 +656,7 @@ export async function fetchGlobalPrivacySettings() {
|
|||||||
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
||||||
shouldHideReadMarks: Boolean(result.hideReadMarks),
|
shouldHideReadMarks: Boolean(result.hideReadMarks),
|
||||||
shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium),
|
shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium),
|
||||||
|
nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -663,16 +664,19 @@ export async function updateGlobalPrivacySettings({
|
|||||||
shouldArchiveAndMuteNewNonContact,
|
shouldArchiveAndMuteNewNonContact,
|
||||||
shouldHideReadMarks,
|
shouldHideReadMarks,
|
||||||
shouldNewNonContactPeersRequirePremium,
|
shouldNewNonContactPeersRequirePremium,
|
||||||
|
nonContactPeersPaidStars,
|
||||||
}: {
|
}: {
|
||||||
shouldArchiveAndMuteNewNonContact?: boolean;
|
shouldArchiveAndMuteNewNonContact?: boolean;
|
||||||
shouldHideReadMarks?: boolean;
|
shouldHideReadMarks?: boolean;
|
||||||
shouldNewNonContactPeersRequirePremium?: boolean;
|
shouldNewNonContactPeersRequirePremium?: boolean;
|
||||||
|
nonContactPeersPaidStars?: number | null;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({
|
const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({
|
||||||
settings: new GramJs.GlobalPrivacySettings({
|
settings: new GramJs.GlobalPrivacySettings({
|
||||||
...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }),
|
...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }),
|
||||||
...(shouldHideReadMarks && { hideReadMarks: true }),
|
...(shouldHideReadMarks && { hideReadMarks: true }),
|
||||||
...(shouldNewNonContactPeersRequirePremium && { newNoncontactPeersRequirePremium: true }),
|
...(shouldNewNonContactPeersRequirePremium && { newNoncontactPeersRequirePremium: true }),
|
||||||
|
noncontactPeersPaidStars: BigInt(nonContactPeersPaidStars || 0),
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -684,6 +688,7 @@ export async function updateGlobalPrivacySettings({
|
|||||||
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
||||||
shouldHideReadMarks: Boolean(result.hideReadMarks),
|
shouldHideReadMarks: Boolean(result.hideReadMarks),
|
||||||
shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium),
|
shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium),
|
||||||
|
nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -103,6 +103,22 @@ export async function fetchCommonChats(user: ApiUser, maxId?: string) {
|
|||||||
return { chatIds, count };
|
return { chatIds, count };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchPaidMessagesStarsAmount(user: ApiUser) {
|
||||||
|
const result = await invokeRequest(new GramJs.users.GetRequirementsToContact({
|
||||||
|
id: [buildInputEntity(user.id, user.accessHash) as GramJs.InputUser],
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result[0] instanceof GramJs.RequirementToContactPaidMessages) {
|
||||||
|
return result[0].starsAmount?.toJSNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchNearestCountry() {
|
export async function fetchNearestCountry() {
|
||||||
const dcInfo = await invokeRequest(new GramJs.help.GetNearestDc());
|
const dcInfo = await invokeRequest(new GramJs.help.GetNearestDc());
|
||||||
|
|
||||||
@ -231,6 +247,17 @@ export async function deleteContact({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addNoPaidMessagesException({ user, shouldRefundCharged }: {
|
||||||
|
user: ApiUser;
|
||||||
|
shouldRefundCharged?: boolean;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.account.AddNoPaidMessagesException({
|
||||||
|
refundCharged: shouldRefundCharged ? true : undefined,
|
||||||
|
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
|
||||||
|
}));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchProfilePhotos({
|
export async function fetchProfilePhotos({
|
||||||
peer,
|
peer,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
|
|||||||
@ -88,6 +88,8 @@ export interface ApiChat {
|
|||||||
|
|
||||||
// Locally determined field
|
// Locally determined field
|
||||||
detectedLanguage?: string;
|
detectedLanguage?: string;
|
||||||
|
|
||||||
|
paidMessagesStars?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiTypingStatus {
|
export interface ApiTypingStatus {
|
||||||
@ -232,6 +234,7 @@ export interface ApiPeerSettings {
|
|||||||
canReportSpam?: boolean;
|
canReportSpam?: boolean;
|
||||||
canAddContact?: boolean;
|
canAddContact?: boolean;
|
||||||
canBlockContact?: boolean;
|
canBlockContact?: boolean;
|
||||||
|
chargedPaidMessageStars?: number;
|
||||||
registrationMonth?: string;
|
registrationMonth?: string;
|
||||||
phoneCountry?: string;
|
phoneCountry?: string;
|
||||||
nameChangeDate?: number;
|
nameChangeDate?: number;
|
||||||
|
|||||||
@ -585,6 +585,7 @@ export interface ApiMessage {
|
|||||||
isVideoProcessingPending?: true;
|
isVideoProcessingPending?: true;
|
||||||
areReactionsPossible?: true;
|
areReactionsPossible?: true;
|
||||||
reportDeliveryUntilDate?: number;
|
reportDeliveryUntilDate?: number;
|
||||||
|
paidMessageStars?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiReactions {
|
export interface ApiReactions {
|
||||||
|
|||||||
@ -109,6 +109,7 @@ export interface ApiSessionData {
|
|||||||
export type ApiNotification = {
|
export type ApiNotification = {
|
||||||
localId: string;
|
localId: string;
|
||||||
containerSelector?: string;
|
containerSelector?: string;
|
||||||
|
type?: 'paidMessage' | undefined;
|
||||||
title?: string | RegularLangFnParameters;
|
title?: string | RegularLangFnParameters;
|
||||||
message: TeactNode | RegularLangFnParameters;
|
message: TeactNode | RegularLangFnParameters;
|
||||||
cacheBreaker?: string;
|
cacheBreaker?: string;
|
||||||
@ -120,6 +121,7 @@ export type ApiNotification = {
|
|||||||
shouldShowTimer?: boolean;
|
shouldShowTimer?: boolean;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
customEmojiIconId?: string;
|
customEmojiIconId?: string;
|
||||||
|
shouldUseCustomIcon?: boolean;
|
||||||
dismissAction?: CallbackAction;
|
dismissAction?: CallbackAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -224,6 +226,10 @@ export interface ApiAppConfig {
|
|||||||
maxPinnedStoriesCount?: number;
|
maxPinnedStoriesCount?: number;
|
||||||
groupTranscribeLevelMin?: number;
|
groupTranscribeLevelMin?: number;
|
||||||
canLimitNewMessagesWithoutPremium?: boolean;
|
canLimitNewMessagesWithoutPremium?: boolean;
|
||||||
|
starsPaidMessagesAvailable?: boolean;
|
||||||
|
starsPaidMessageCommissionPermille?: number;
|
||||||
|
starsPaidMessageAmountMax?: number;
|
||||||
|
starsUsdWithdrawRateX1000?: number;
|
||||||
bandwidthPremiumNotifyPeriod?: number;
|
bandwidthPremiumNotifyPeriod?: number;
|
||||||
bandwidthPremiumUploadSpeedup?: number;
|
bandwidthPremiumUploadSpeedup?: number;
|
||||||
bandwidthPremiumDownloadSpeedup?: number;
|
bandwidthPremiumDownloadSpeedup?: number;
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { ApiChat } from './chats';
|
|||||||
import type { ApiUser } from './users';
|
import type { ApiUser } from './users';
|
||||||
|
|
||||||
export type ApiPrivacyKey = 'phoneNumber' | 'addByPhone' | 'lastSeen' | 'profilePhoto' | 'voiceMessages' |
|
export type ApiPrivacyKey = 'phoneNumber' | 'addByPhone' | 'lastSeen' | 'profilePhoto' | 'voiceMessages' |
|
||||||
'forwards' | 'chatInvite' | 'phoneCall' | 'phoneP2P' | 'bio' | 'birthday' | 'gifts';
|
'forwards' | 'chatInvite' | 'phoneCall' | 'phoneP2P' | 'bio' | 'birthday' | 'gifts' | 'noPaidMessages';
|
||||||
export type PrivacyVisibility = 'everybody' | 'contacts' | 'closeFriends' | 'nonContacts' | 'nobody';
|
export type PrivacyVisibility = 'everybody' | 'contacts' | 'closeFriends' | 'nonContacts' | 'nobody';
|
||||||
export type BotsPrivacyType = 'allow' | 'disallow' | 'none';
|
export type BotsPrivacyType = 'allow' | 'disallow' | 'none';
|
||||||
|
|
||||||
|
|||||||
@ -180,6 +180,7 @@ export interface ApiStarsTransaction {
|
|||||||
subscriptionPeriod?: number;
|
subscriptionPeriod?: number;
|
||||||
starRefCommision?: number;
|
starRefCommision?: number;
|
||||||
isGiftUpgrade?: true;
|
isGiftUpgrade?: true;
|
||||||
|
paidMessages?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiStarsSubscription {
|
export interface ApiStarsSubscription {
|
||||||
|
|||||||
@ -38,6 +38,7 @@ export interface ApiUser {
|
|||||||
hasMainMiniApp?: boolean;
|
hasMainMiniApp?: boolean;
|
||||||
botActiveUsers?: number;
|
botActiveUsers?: number;
|
||||||
botVerificationIconId?: string;
|
botVerificationIconId?: string;
|
||||||
|
paidMessagesStars?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiUserFullInfo {
|
export interface ApiUserFullInfo {
|
||||||
@ -65,6 +66,7 @@ export interface ApiUserFullInfo {
|
|||||||
isBotAccessEmojiGranted?: boolean;
|
isBotAccessEmojiGranted?: boolean;
|
||||||
hasScheduledMessages?: boolean;
|
hasScheduledMessages?: boolean;
|
||||||
botVerification?: ApiBotVerification;
|
botVerification?: ApiBotVerification;
|
||||||
|
paidMessagesStars?: number;
|
||||||
settings?: ApiPeerSettings;
|
settings?: ApiPeerSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1853,3 +1853,41 @@
|
|||||||
"GiftPremiumDescriptionYourBalance" = "Your balance is **{stars}**. {link}";
|
"GiftPremiumDescriptionYourBalance" = "Your balance is **{stars}**. {link}";
|
||||||
"StarsGiftCompleted"= "Gift sent!";
|
"StarsGiftCompleted"= "Gift sent!";
|
||||||
"GiftSent"= "Gift sent!";
|
"GiftSent"= "Gift sent!";
|
||||||
|
"PrivacyDescriptionMessagesContactsAndPremium" = "You can restrict messages from users who are not in your contacts and don't have Premium.";
|
||||||
|
"PrivacyChargeForMessages" = "Charge for Messages";
|
||||||
|
"PrivacyDescriptionChargeForMessages" = "Charge a fee for messages from people outside your contacts or those you haven't messaged first.";
|
||||||
|
"RemoveFeeTitle" = "Remove Fee";
|
||||||
|
"ExceptionTitlePrivacyChargeForMessages" = "Remove fee";
|
||||||
|
"ExceptionDescriptionPrivacyChargeForMessages" = "Add users or entire groups who won't be charged for sending messages to you.";
|
||||||
|
"SectionTitleStarsForForMessages" = "Set your price per message";
|
||||||
|
"SectionDescriptionStarsForForMessages" = "You will receive {percent}% of the selected fee (~{amount}) for each incoming message.";
|
||||||
|
"SubtitlePrivacyAddUsers" = "Add Users";
|
||||||
|
"SubtitlePrivacyUsersCount" = "{count} users";
|
||||||
|
"PrivacyPaidMessagesValue" = "Paid";
|
||||||
|
"FirstMessageInPaidMessagesChat" = "**{user}** charges {amount} for each message.";
|
||||||
|
"ButtonBuyStars" = "Buy Stars";
|
||||||
|
"ComposerPlaceholderPaidMessage" = "Message for {amount}";
|
||||||
|
"ComposerPlaceholderPaidReply" = "Reply for {amount}";
|
||||||
|
"TitleConfirmPayment" = "Confirm Payment";
|
||||||
|
"ConfirmationModalPaymentForOneMessage" = "{user} charges **{amount} Stars** per incoming message. Would you like to pay **{amount} Stars** to send one message?";
|
||||||
|
"ConfirmationModalPaymentForMessages" = "{user} charges **{price} Stars** per incoming message. Would you like to pay **{amount} Stars** to send **{count} messages?**";
|
||||||
|
"ButtonPayForMessage" = "Pay for {count} message";
|
||||||
|
"ToastTitleMessageSent" = "Message sent!";
|
||||||
|
"ToastTitleMessagesSent" = "{count} Messages sent!";
|
||||||
|
"ToastMessageSent" = "You paid {amount} stars.";
|
||||||
|
"ButtonUndo" = "Undo";
|
||||||
|
"ActionPaidOneMessageOutgoing" = "You paid {amount} Stars to send a message";
|
||||||
|
"ActionPaidOneMessageIncoming" = "You received {amount} Stars from {user}";
|
||||||
|
"PaneMessagePaidMessageCharge" = "{peer} must pay {amount} for each message to you.";
|
||||||
|
"ConfirmRemoveMessageFee" = "Yes";
|
||||||
|
"ConfirmDialogMessageRemoveFee" = "Are you sure you want to allow **{peer}** to message you for free?";
|
||||||
|
"ConfirmDialogRemoveFeeRefundStars" = "Refund already paid **{amount} Stars**";
|
||||||
|
"DescriptionGiftPaidMessage" = "{user} charges **{amount}** Stars for each message. That price has been added to the cost of the gift.";
|
||||||
|
"StoryTooltipGifSent" = "Gif Sent!";
|
||||||
|
"StoryTooltipStickerSent" = "Sticker Sent!";
|
||||||
|
"StoryTooltipReactionSent" = "Reaction Sent!";
|
||||||
|
"StarsNeededTextSendPaidMessages" = "Buy **Stars** to send messages.";
|
||||||
|
"PaidMessageTransaction_one" = "Fee for {count} Message";
|
||||||
|
"PaidMessageTransaction_other" = "Fee for {count} Messages";
|
||||||
|
"PaidMessageTransactionDescription" = "You receive **{percent}** of the price that you charge for each incoming message.";
|
||||||
|
"PaidMessageTransactionTotal" = "Total";
|
||||||
@ -72,6 +72,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .Button {
|
> .Button {
|
||||||
|
overflow: visible;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
width: var(--base-height);
|
width: var(--base-height);
|
||||||
@ -96,6 +97,15 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paidStarsBadgeText {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.star-amount-icon {
|
||||||
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
&:not(:active):not(:focus):not(:hover) {
|
&:not(:active):not(:focus):not(:hover) {
|
||||||
.icon-send,
|
.icon-send,
|
||||||
@ -152,6 +162,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paidStarsBadgeIcon {
|
||||||
|
margin-inline-start: 0;
|
||||||
|
margin-inline-end: 0.0625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paidStarsBadge {
|
||||||
|
animation: hide-icon 0.4s forwards ease-out;
|
||||||
|
&.visible {
|
||||||
|
animation: grow-icon 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: -1rem;
|
||||||
|
height: auto;
|
||||||
|
padding-inline: 0.375rem;
|
||||||
|
padding-block: 0.25rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
margin-top: 0.625rem;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: var(--font-weight-semibold) !important;
|
||||||
|
}
|
||||||
|
|
||||||
&.send, &.sendOneTime {
|
&.send, &.sendOneTime {
|
||||||
.icon-send {
|
.icon-send {
|
||||||
animation: grow-icon 0.4s ease-out;
|
animation: grow-icon 0.4s ease-out;
|
||||||
@ -652,8 +689,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.placeholder-star-icon {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
.forced-placeholder,
|
.forced-placeholder,
|
||||||
.placeholder-text {
|
.placeholder-text {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: var(--color-placeholders);
|
color: var(--color-placeholders);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useEffect, useMemo, useRef, useSignal, useState,
|
memo, useEffect, useMemo, useRef, useSignal, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
@ -57,6 +57,7 @@ import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterd
|
|||||||
import {
|
import {
|
||||||
canEditMedia,
|
canEditMedia,
|
||||||
getAllowedAttachmentOptions,
|
getAllowedAttachmentOptions,
|
||||||
|
getPeerTitle,
|
||||||
getReactionKey,
|
getReactionKey,
|
||||||
getStoryKey,
|
getStoryKey,
|
||||||
isChatAdmin,
|
isChatAdmin,
|
||||||
@ -90,6 +91,7 @@ import {
|
|||||||
selectNotifyDefaults,
|
selectNotifyDefaults,
|
||||||
selectNotifyException,
|
selectNotifyException,
|
||||||
selectNoWebPage,
|
selectNoWebPage,
|
||||||
|
selectPeerPaidMessagesStars,
|
||||||
selectPeerStory,
|
selectPeerStory,
|
||||||
selectPerformanceSettingsValue,
|
selectPerformanceSettingsValue,
|
||||||
selectRequestedDraft,
|
selectRequestedDraft,
|
||||||
@ -108,6 +110,7 @@ import { tryParseDeepLink } from '../../util/deepLinkParser';
|
|||||||
import deleteLastCharacterOutsideSelection from '../../util/deleteLastCharacterOutsideSelection';
|
import deleteLastCharacterOutsideSelection from '../../util/deleteLastCharacterOutsideSelection';
|
||||||
import { processMessageInputForCustomEmoji } from '../../util/emoji/customEmojiManager';
|
import { processMessageInputForCustomEmoji } from '../../util/emoji/customEmojiManager';
|
||||||
import focusEditableElement from '../../util/focusEditableElement';
|
import focusEditableElement from '../../util/focusEditableElement';
|
||||||
|
import { formatStarsAsIcon } from '../../util/localization/format';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
import parseHtmlAsFormattedText from '../../util/parseHtmlAsFormattedText';
|
import parseHtmlAsFormattedText from '../../util/parseHtmlAsFormattedText';
|
||||||
import { insertHtmlInSelection } from '../../util/selection';
|
import { insertHtmlInSelection } from '../../util/selection';
|
||||||
@ -147,6 +150,7 @@ import useEditing from '../middle/composer/hooks/useEditing';
|
|||||||
import useEmojiTooltip from '../middle/composer/hooks/useEmojiTooltip';
|
import useEmojiTooltip from '../middle/composer/hooks/useEmojiTooltip';
|
||||||
import useInlineBotTooltip from '../middle/composer/hooks/useInlineBotTooltip';
|
import useInlineBotTooltip from '../middle/composer/hooks/useInlineBotTooltip';
|
||||||
import useMentionTooltip from '../middle/composer/hooks/useMentionTooltip';
|
import useMentionTooltip from '../middle/composer/hooks/useMentionTooltip';
|
||||||
|
import usePaidMessageConfirmation from '../middle/composer/hooks/usePaidMessageConfirmation';
|
||||||
import useStickerTooltip from '../middle/composer/hooks/useStickerTooltip';
|
import useStickerTooltip from '../middle/composer/hooks/useStickerTooltip';
|
||||||
import useVoiceRecording from '../middle/composer/hooks/useVoiceRecording';
|
import useVoiceRecording from '../middle/composer/hooks/useVoiceRecording';
|
||||||
|
|
||||||
@ -175,8 +179,10 @@ import Button from '../ui/Button';
|
|||||||
import ResponsiveHoverButton from '../ui/ResponsiveHoverButton';
|
import ResponsiveHoverButton from '../ui/ResponsiveHoverButton';
|
||||||
import Spinner from '../ui/Spinner';
|
import Spinner from '../ui/Spinner';
|
||||||
import Transition from '../ui/Transition';
|
import Transition from '../ui/Transition';
|
||||||
|
import AnimatedCounter from './AnimatedCounter';
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import Icon from './icons/Icon';
|
import Icon from './icons/Icon';
|
||||||
|
import PaymentMessageConfirmDialog from './PaymentMessageConfirmDialog';
|
||||||
import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji';
|
import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji';
|
||||||
|
|
||||||
import './Composer.scss';
|
import './Composer.scss';
|
||||||
@ -196,7 +202,7 @@ type OwnProps = {
|
|||||||
editableInputCssSelector: string;
|
editableInputCssSelector: string;
|
||||||
editableInputId: string;
|
editableInputId: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
inputPlaceholder?: string;
|
inputPlaceholder?: TeactNode | string;
|
||||||
onDropHide?: NoneToVoidFunction;
|
onDropHide?: NoneToVoidFunction;
|
||||||
onForward?: NoneToVoidFunction;
|
onForward?: NoneToVoidFunction;
|
||||||
onFocus?: NoneToVoidFunction;
|
onFocus?: NoneToVoidFunction;
|
||||||
@ -220,6 +226,7 @@ type StateProps =
|
|||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
isReactionPickerOpen?: boolean;
|
isReactionPickerOpen?: boolean;
|
||||||
isForwarding?: boolean;
|
isForwarding?: boolean;
|
||||||
|
forwardedMessagesCount?: number;
|
||||||
pollModal: TabState['pollModal'];
|
pollModal: TabState['pollModal'];
|
||||||
botKeyboardMessageId?: number;
|
botKeyboardMessageId?: number;
|
||||||
botKeyboardPlaceholder?: string;
|
botKeyboardPlaceholder?: string;
|
||||||
@ -271,13 +278,16 @@ type StateProps =
|
|||||||
webPagePreview?: ApiWebPage;
|
webPagePreview?: ApiWebPage;
|
||||||
noWebPage?: boolean;
|
noWebPage?: boolean;
|
||||||
isContactRequirePremium?: boolean;
|
isContactRequirePremium?: boolean;
|
||||||
|
paidMessagesStars?: number;
|
||||||
effect?: ApiAvailableEffect;
|
effect?: ApiAvailableEffect;
|
||||||
effectReactions?: ApiReaction[];
|
effectReactions?: ApiReaction[];
|
||||||
areEffectsSupported?: boolean;
|
areEffectsSupported?: boolean;
|
||||||
canPlayEffect?: boolean;
|
canPlayEffect?: boolean;
|
||||||
shouldPlayEffect?: boolean;
|
shouldPlayEffect?: boolean;
|
||||||
maxMessageLength: number;
|
maxMessageLength: number;
|
||||||
|
shouldPaidMessageAutoApprove?: boolean;
|
||||||
isSilentPosting?: boolean;
|
isSilentPosting?: boolean;
|
||||||
|
isPaymentMessageConfirmDialogOpen: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum MainButtonState {
|
enum MainButtonState {
|
||||||
@ -331,6 +341,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isSelectModeActive,
|
isSelectModeActive,
|
||||||
isReactionPickerOpen,
|
isReactionPickerOpen,
|
||||||
isForwarding,
|
isForwarding,
|
||||||
|
forwardedMessagesCount,
|
||||||
pollModal,
|
pollModal,
|
||||||
botKeyboardMessageId,
|
botKeyboardMessageId,
|
||||||
botKeyboardPlaceholder,
|
botKeyboardPlaceholder,
|
||||||
@ -382,6 +393,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
webPagePreview,
|
webPagePreview,
|
||||||
noWebPage,
|
noWebPage,
|
||||||
isContactRequirePremium,
|
isContactRequirePremium,
|
||||||
|
paidMessagesStars,
|
||||||
effect,
|
effect,
|
||||||
effectReactions,
|
effectReactions,
|
||||||
areEffectsSupported,
|
areEffectsSupported,
|
||||||
@ -393,12 +405,12 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
onFocus,
|
onFocus,
|
||||||
onBlur,
|
onBlur,
|
||||||
onForward,
|
onForward,
|
||||||
|
isPaymentMessageConfirmDialogOpen,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
sendMessage,
|
sendMessage,
|
||||||
clearDraft,
|
clearDraft,
|
||||||
showDialog,
|
showDialog,
|
||||||
forwardMessages,
|
|
||||||
openPollModal,
|
openPollModal,
|
||||||
closePollModal,
|
closePollModal,
|
||||||
loadScheduledHistory,
|
loadScheduledHistory,
|
||||||
@ -427,6 +439,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const inputRef = useRef<HTMLDivElement>(null);
|
const inputRef = useRef<HTMLDivElement>(null);
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const counterRef = useRef<HTMLSpanElement>(null);
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const storyReactionRef = useRef<HTMLButtonElement>(null);
|
const storyReactionRef = useRef<HTMLButtonElement>(null);
|
||||||
@ -513,6 +527,22 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
const isNeedPremium = isContactRequirePremium && isInStoryViewer;
|
const isNeedPremium = isContactRequirePremium && isInStoryViewer;
|
||||||
const isSendTextBlocked = isNeedPremium || !canSendPlainText;
|
const isSendTextBlocked = isNeedPremium || !canSendPlainText;
|
||||||
|
|
||||||
|
const messagesCount = useDerivedState(() => {
|
||||||
|
if (hasAttachments) return attachments.length;
|
||||||
|
const messagesInInput = (getHtml() || hasAttachments) ? 1 : 0;
|
||||||
|
if (!isForwarding || !forwardedMessagesCount) return messagesInInput || 1;
|
||||||
|
return forwardedMessagesCount + messagesInInput;
|
||||||
|
}, [getHtml, hasAttachments, attachments, isForwarding, forwardedMessagesCount]);
|
||||||
|
const starsForAllMessages = paidMessagesStars ? messagesCount * paidMessagesStars : 0;
|
||||||
|
|
||||||
|
const {
|
||||||
|
closeConfirmDialog: closeConfirmModalPayForMessage,
|
||||||
|
dialogHandler: paymentMessageConfirmDialogHandler,
|
||||||
|
shouldAutoApprove: shouldPaidMessageAutoApprove,
|
||||||
|
setAutoApprove: setShouldPaidMessageAutoApprove,
|
||||||
|
handleWithConfirmation: handleActionWithPaymentConfirmation,
|
||||||
|
} = usePaidMessageConfirmation(starsForAllMessages);
|
||||||
|
|
||||||
const hasWebPagePreview = !hasAttachments && canAttachEmbedLinks && !noWebPage && Boolean(webPagePreview);
|
const hasWebPagePreview = !hasAttachments && canAttachEmbedLinks && !noWebPage && Boolean(webPagePreview);
|
||||||
const isComposerBlocked = isSendTextBlocked && !editingMessage;
|
const isComposerBlocked = isSendTextBlocked && !editingMessage;
|
||||||
|
|
||||||
@ -933,6 +963,21 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const canSendAttachments = (attachmentsToSend: ApiAttachment[]): boolean => {
|
||||||
|
if (!currentMessageList && !storyId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { text } = parseHtmlAsFormattedText(getHtml());
|
||||||
|
if (!text && !attachmentsToSend.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!validateTextLength(text, true)) return false;
|
||||||
|
if (!checkSlowMode()) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const sendAttachments = useLastCallback(({
|
const sendAttachments = useLastCallback(({
|
||||||
attachments: attachmentsToSend,
|
attachments: attachmentsToSend,
|
||||||
sendCompressed = attachmentSettings.shouldCompress,
|
sendCompressed = attachmentSettings.shouldCompress,
|
||||||
@ -954,11 +999,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isSilent = isSilent || isSilentPosting;
|
isSilent = isSilent || isSilentPosting;
|
||||||
|
|
||||||
const { text, entities } = parseHtmlAsFormattedText(getHtml());
|
const { text, entities } = parseHtmlAsFormattedText(getHtml());
|
||||||
if (!text && !attachmentsToSend.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!validateTextLength(text, true)) return;
|
|
||||||
if (!checkSlowMode()) return;
|
|
||||||
|
|
||||||
isInvertedMedia = text && sendCompressed && sendGrouped ? isInvertedMedia : undefined;
|
isInvertedMedia = text && sendCompressed && sendGrouped ? isInvertedMedia : undefined;
|
||||||
|
|
||||||
@ -998,12 +1038,24 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendGrouped: boolean,
|
sendGrouped: boolean,
|
||||||
isInvertedMedia?: true,
|
isInvertedMedia?: true,
|
||||||
) => {
|
) => {
|
||||||
sendAttachments({
|
if (canSendAttachments(attachments)) {
|
||||||
attachments,
|
if (editingMessage) {
|
||||||
sendCompressed,
|
sendAttachments({
|
||||||
sendGrouped,
|
attachments,
|
||||||
isInvertedMedia,
|
sendCompressed,
|
||||||
});
|
sendGrouped,
|
||||||
|
isInvertedMedia,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleActionWithPaymentConfirmation(sendAttachments, {
|
||||||
|
attachments,
|
||||||
|
sendCompressed,
|
||||||
|
sendGrouped,
|
||||||
|
isInvertedMedia,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSendAttachments = useLastCallback((
|
const handleSendAttachments = useLastCallback((
|
||||||
@ -1013,16 +1065,81 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
scheduledAt?: number,
|
scheduledAt?: number,
|
||||||
isInvertedMedia?: true,
|
isInvertedMedia?: true,
|
||||||
) => {
|
) => {
|
||||||
sendAttachments({
|
if (canSendAttachments(attachments)) {
|
||||||
attachments,
|
sendAttachments({
|
||||||
sendCompressed,
|
attachments,
|
||||||
sendGrouped,
|
sendCompressed,
|
||||||
isSilent,
|
sendGrouped,
|
||||||
scheduledAt,
|
isSilent,
|
||||||
isInvertedMedia,
|
scheduledAt,
|
||||||
});
|
isInvertedMedia,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSendCore = useLastCallback(
|
||||||
|
(currentAttachments: ApiAttachment[], isSilent = false, scheduledAt?: number) => {
|
||||||
|
const { text, entities } = parseHtmlAsFormattedText(getHtml());
|
||||||
|
|
||||||
|
if (currentAttachments.length) {
|
||||||
|
if (canSendAttachments(currentAttachments)) {
|
||||||
|
sendAttachments({
|
||||||
|
attachments: currentAttachments,
|
||||||
|
scheduledAt,
|
||||||
|
isSilent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text && !isForwarding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateTextLength(text)) return;
|
||||||
|
|
||||||
|
const messageInput = document.querySelector<HTMLDivElement>(editableInputCssSelector);
|
||||||
|
|
||||||
|
const effectId = effect?.id;
|
||||||
|
|
||||||
|
if (text || isForwarding) {
|
||||||
|
if (!checkSlowMode()) return;
|
||||||
|
|
||||||
|
const isInvertedMedia = hasWebPagePreview ? attachmentSettings.isInvertedMedia : undefined;
|
||||||
|
|
||||||
|
if (areEffectsSupported) saveEffectInDraft({ chatId, threadId, effectId: undefined });
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
messageList: currentMessageList,
|
||||||
|
text,
|
||||||
|
entities,
|
||||||
|
scheduledAt,
|
||||||
|
isSilent,
|
||||||
|
shouldUpdateStickerSetOrder,
|
||||||
|
isInvertedMedia,
|
||||||
|
effectId,
|
||||||
|
webPageMediaSize: attachmentSettings.webPageMediaSize,
|
||||||
|
webPageUrl: hasWebPagePreview ? webPagePreview!.url : undefined,
|
||||||
|
isForwarding,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMessageSendTimeSeconds.current = getServerTime();
|
||||||
|
clearDraft({
|
||||||
|
chatId, threadId, isLocalOnly: true, shouldKeepReply: isForwarding,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (IS_IOS && messageInput && messageInput === document.activeElement) {
|
||||||
|
applyIosAutoCapitalizationFix(messageInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until message animation starts
|
||||||
|
requestMeasure(() => {
|
||||||
|
resetComposer();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const handleSend = useLastCallback(async (isSilent = false, scheduledAt?: number) => {
|
const handleSend = useLastCallback(async (isSilent = false, scheduledAt?: number) => {
|
||||||
if (!currentMessageList && !storyId) {
|
if (!currentMessageList && !storyId) {
|
||||||
return;
|
return;
|
||||||
@ -1045,68 +1162,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { text, entities } = parseHtmlAsFormattedText(getHtml());
|
handleSendCore(currentAttachments, isSilent, scheduledAt);
|
||||||
|
});
|
||||||
|
|
||||||
if (currentAttachments.length) {
|
const handleSendWithConfirmation = useLastCallback((isSilent = false, scheduledAt?: number) => {
|
||||||
sendAttachments({
|
handleActionWithPaymentConfirmation(handleSend, isSilent, scheduledAt);
|
||||||
attachments: currentAttachments,
|
|
||||||
scheduledAt,
|
|
||||||
isSilent,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!text && !isForwarding) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateTextLength(text)) return;
|
|
||||||
|
|
||||||
const messageInput = document.querySelector<HTMLDivElement>(editableInputCssSelector);
|
|
||||||
|
|
||||||
const effectId = effect?.id;
|
|
||||||
|
|
||||||
if (text) {
|
|
||||||
if (!checkSlowMode()) return;
|
|
||||||
|
|
||||||
const isInvertedMedia = hasWebPagePreview ? attachmentSettings.isInvertedMedia : undefined;
|
|
||||||
|
|
||||||
if (areEffectsSupported) saveEffectInDraft({ chatId, threadId, effectId: undefined });
|
|
||||||
|
|
||||||
sendMessage({
|
|
||||||
messageList: currentMessageList,
|
|
||||||
text,
|
|
||||||
entities,
|
|
||||||
scheduledAt,
|
|
||||||
isSilent,
|
|
||||||
shouldUpdateStickerSetOrder,
|
|
||||||
isInvertedMedia,
|
|
||||||
effectId,
|
|
||||||
webPageMediaSize: attachmentSettings.webPageMediaSize,
|
|
||||||
webPageUrl: hasWebPagePreview ? webPagePreview!.url : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isForwarding) {
|
|
||||||
forwardMessages({
|
|
||||||
scheduledAt,
|
|
||||||
isSilent,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
lastMessageSendTimeSeconds.current = getServerTime();
|
|
||||||
clearDraft({
|
|
||||||
chatId, threadId, isLocalOnly: true, shouldKeepReply: isForwarding,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (IS_IOS && messageInput && messageInput === document.activeElement) {
|
|
||||||
applyIosAutoCapitalizationFix(messageInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until message animation starts
|
|
||||||
requestMeasure(() => {
|
|
||||||
resetComposer();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClickBotMenu = useLastCallback(() => {
|
const handleClickBotMenu = useLastCallback(() => {
|
||||||
@ -1215,13 +1275,13 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
forceShowSymbolMenu();
|
forceShowSymbolMenu();
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
cancelForceShowSymbolMenu();
|
cancelForceShowSymbolMenu();
|
||||||
handleMessageSchedule({ gif, isSilent }, scheduledAt, currentMessageList!);
|
handleActionWithPaymentConfirmation(handleMessageSchedule, { gif, isSilent }, scheduledAt, currentMessageList!);
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer(true);
|
resetComposer(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
sendMessage({ messageList: currentMessageList, gif, isSilent });
|
handleActionWithPaymentConfirmation(sendMessage, { messageList: currentMessageList, gif, isSilent });
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer(true);
|
resetComposer(true);
|
||||||
});
|
});
|
||||||
@ -1250,18 +1310,23 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
forceShowSymbolMenu();
|
forceShowSymbolMenu();
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
cancelForceShowSymbolMenu();
|
cancelForceShowSymbolMenu();
|
||||||
handleMessageSchedule({ sticker, isSilent }, scheduledAt, currentMessageList!);
|
handleActionWithPaymentConfirmation(
|
||||||
|
handleMessageSchedule, { sticker, isSilent }, scheduledAt, currentMessageList!,
|
||||||
|
);
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer(shouldPreserveInput);
|
resetComposer(shouldPreserveInput);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
sendMessage({
|
handleActionWithPaymentConfirmation(
|
||||||
messageList: currentMessageList,
|
sendMessage,
|
||||||
sticker,
|
{
|
||||||
isSilent,
|
messageList: currentMessageList,
|
||||||
shouldUpdateStickerSetOrder: shouldUpdateStickerSetOrder && canUpdateStickerSetsOrder,
|
sticker,
|
||||||
});
|
isSilent,
|
||||||
|
shouldUpdateStickerSetOrder: shouldUpdateStickerSetOrder && canUpdateStickerSetsOrder,
|
||||||
|
},
|
||||||
|
);
|
||||||
clearDraft({ chatId, threadId, isLocalOnly: true });
|
clearDraft({ chatId, threadId, isLocalOnly: true });
|
||||||
|
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
@ -1281,20 +1346,24 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
if (isInScheduledList || isScheduleRequested) {
|
if (isInScheduledList || isScheduleRequested) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
handleMessageSchedule({
|
handleActionWithPaymentConfirmation(handleMessageSchedule,
|
||||||
id: inlineResult.id,
|
{
|
||||||
queryId: inlineResult.queryId,
|
id: inlineResult.id,
|
||||||
isSilent,
|
queryId: inlineResult.queryId,
|
||||||
}, scheduledAt, currentMessageList!);
|
isSilent,
|
||||||
|
},
|
||||||
|
scheduledAt,
|
||||||
|
currentMessageList!);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
sendInlineBotResult({
|
handleActionWithPaymentConfirmation(sendInlineBotResult,
|
||||||
id: inlineResult.id,
|
{
|
||||||
queryId: inlineResult.queryId,
|
id: inlineResult.id,
|
||||||
threadId,
|
queryId: inlineResult.queryId,
|
||||||
chatId,
|
threadId,
|
||||||
isSilent,
|
chatId,
|
||||||
});
|
isSilent,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageInput = document.querySelector<HTMLDivElement>(editableInputCssSelector);
|
const messageInput = document.querySelector<HTMLDivElement>(editableInputCssSelector);
|
||||||
@ -1331,6 +1400,10 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handlePollSendWithPaymentConfirmation = useLastCallback((poll: ApiNewPoll) => {
|
||||||
|
handleActionWithPaymentConfirmation(handlePollSend, poll);
|
||||||
|
});
|
||||||
|
|
||||||
const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => {
|
const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => {
|
||||||
if (isInScheduledList) {
|
if (isInScheduledList) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
@ -1458,6 +1531,13 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
if (!isComposerBlocked) {
|
if (!isComposerBlocked) {
|
||||||
if (botKeyboardPlaceholder) return botKeyboardPlaceholder;
|
if (botKeyboardPlaceholder) return botKeyboardPlaceholder;
|
||||||
if (inputPlaceholder) return inputPlaceholder;
|
if (inputPlaceholder) return inputPlaceholder;
|
||||||
|
if (paidMessagesStars) {
|
||||||
|
return lang('ComposerPlaceholderPaidMessage', {
|
||||||
|
amount: formatStarsAsIcon(lang, paidMessagesStars, { asFont: true, className: 'placeholder-star-icon' }),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (chat?.isForum && chat?.isForumAsMessages && threadId === MAIN_THREAD_ID) {
|
if (chat?.isForum && chat?.isForumAsMessages && threadId === MAIN_THREAD_ID) {
|
||||||
return replyToTopic
|
return replyToTopic
|
||||||
? lang('ComposerPlaceholderTopic', { topic: replyToTopic.title })
|
? lang('ComposerPlaceholderTopic', { topic: replyToTopic.title })
|
||||||
@ -1474,7 +1554,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
return lang('ComposerPlaceholderNoText');
|
return lang('ComposerPlaceholderNoText');
|
||||||
}, [
|
}, [
|
||||||
activeVoiceRecording, botKeyboardPlaceholder, chat, inputPlaceholder, isChannel, isComposerBlocked,
|
activeVoiceRecording, botKeyboardPlaceholder, chat, inputPlaceholder, isChannel, isComposerBlocked,
|
||||||
isInStoryViewer, isSilentPosting, lang, replyToTopic, threadId, windowWidth,
|
isInStoryViewer, isSilentPosting, lang, replyToTopic, threadId, windowWidth, paidMessagesStars,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1498,7 +1578,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
onForward?.();
|
onForward?.();
|
||||||
break;
|
break;
|
||||||
case MainButtonState.Send:
|
case MainButtonState.Send:
|
||||||
void handleSend();
|
handleSendWithConfirmation();
|
||||||
break;
|
break;
|
||||||
case MainButtonState.Record: {
|
case MainButtonState.Record: {
|
||||||
if (areVoiceMessagesNotAllowed) {
|
if (areVoiceMessagesNotAllowed) {
|
||||||
@ -1586,7 +1666,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
entities = customEmojiMessage.entities;
|
entities = customEmojiMessage.entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage({ text, entities, isReaction: true });
|
handleActionWithPaymentConfirmation(sendMessage, { text, entities, isReaction: true });
|
||||||
closeReactionPicker();
|
closeReactionPicker();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1622,24 +1702,29 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSendSilent = useLastCallback(() => {
|
const handleSendSilent = useLastCallback(() => {
|
||||||
sendSilent();
|
handleActionWithPaymentConfirmation(sendSilent);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSendWhenOnline = useLastCallback(() => {
|
const handleSendWhenOnline = useLastCallback(() => {
|
||||||
handleMessageSchedule({}, SCHEDULED_WHEN_ONLINE, currentMessageList!, effect?.id);
|
handleActionWithPaymentConfirmation(
|
||||||
|
handleMessageSchedule, {}, SCHEDULED_WHEN_ONLINE, currentMessageList!, effect?.id,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSendScheduledAttachments = useLastCallback(
|
const handleSendScheduledAttachments = useLastCallback(
|
||||||
(sendCompressed: boolean, sendGrouped: boolean, isInvertedMedia?: true) => {
|
(sendCompressed: boolean, sendGrouped: boolean, isInvertedMedia?: true) => {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
handleMessageSchedule({ sendCompressed, sendGrouped, isInvertedMedia }, scheduledAt, currentMessageList!);
|
handleActionWithPaymentConfirmation(handleMessageSchedule,
|
||||||
|
{ sendCompressed, sendGrouped, isInvertedMedia },
|
||||||
|
scheduledAt,
|
||||||
|
currentMessageList!);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSendSilentAttachments = useLastCallback(
|
const handleSendSilentAttachments = useLastCallback(
|
||||||
(sendCompressed: boolean, sendGrouped: boolean, isInvertedMedia?: true) => {
|
(sendCompressed: boolean, sendGrouped: boolean, isInvertedMedia?: true) => {
|
||||||
sendSilent({ sendCompressed, sendGrouped, isInvertedMedia });
|
handleActionWithPaymentConfirmation(sendSilent, { sendCompressed, sendGrouped, isInvertedMedia });
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1654,15 +1739,17 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
case MainButtonState.Schedule:
|
case MainButtonState.Schedule:
|
||||||
return handleSendScheduled;
|
return handleSendScheduled;
|
||||||
default:
|
default:
|
||||||
return handleSend;
|
return handleSendWithConfirmation;
|
||||||
}
|
}
|
||||||
}, [mainButtonState, handleEditComplete]);
|
}, [mainButtonState, handleEditComplete, handleSendWithConfirmation]);
|
||||||
|
|
||||||
const withBotCommands = isChatWithBot && botMenuButton?.type === 'commands' && !editingMessage
|
const withBotCommands = isChatWithBot && botMenuButton?.type === 'commands' && !editingMessage
|
||||||
&& botCommands !== false && !activeVoiceRecording;
|
&& botCommands !== false && !activeVoiceRecording;
|
||||||
|
|
||||||
const effectEmoji = areEffectsSupported && effect?.emoticon;
|
const effectEmoji = areEffectsSupported && effect?.emoticon;
|
||||||
|
|
||||||
|
const shouldRenderPaidBadge = Boolean(paidMessagesStars && mainButtonState === MainButtonState.Send);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={fullClassName}>
|
<div className={fullClassName}>
|
||||||
{isInMessageList && canAttachMedia && isReady && (
|
{isInMessageList && canAttachMedia && isReady && (
|
||||||
@ -1702,7 +1789,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
shouldForceAsFile={shouldForceAsFile}
|
shouldForceAsFile={shouldForceAsFile}
|
||||||
isForCurrentMessageList={isForCurrentMessageList}
|
isForCurrentMessageList={isForCurrentMessageList}
|
||||||
isForMessage={isInMessageList}
|
isForMessage={isInMessageList}
|
||||||
shouldSchedule={isInScheduledList}
|
shouldSchedule={!paidMessagesStars && isInScheduledList}
|
||||||
|
canSchedule={!paidMessagesStars}
|
||||||
forceDarkTheme={isInStoryViewer}
|
forceDarkTheme={isInStoryViewer}
|
||||||
onCaptionUpdate={onCaptionUpdate}
|
onCaptionUpdate={onCaptionUpdate}
|
||||||
onSendSilent={handleSendSilentAttachments}
|
onSendSilent={handleSendSilentAttachments}
|
||||||
@ -1717,13 +1805,14 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
editingMessage={editingMessage}
|
editingMessage={editingMessage}
|
||||||
onSendWhenOnline={handleSendWhenOnline}
|
onSendWhenOnline={handleSendWhenOnline}
|
||||||
canScheduleUntilOnline={canScheduleUntilOnline && !isViewOnceEnabled}
|
canScheduleUntilOnline={canScheduleUntilOnline && !isViewOnceEnabled}
|
||||||
|
paidMessagesStars={paidMessagesStars}
|
||||||
/>
|
/>
|
||||||
<PollModal
|
<PollModal
|
||||||
isOpen={pollModal.isOpen}
|
isOpen={pollModal.isOpen}
|
||||||
isQuiz={pollModal.isQuiz}
|
isQuiz={pollModal.isQuiz}
|
||||||
shouldBeAnonymous={isChannel}
|
shouldBeAnonymous={isChannel}
|
||||||
onClear={closePollModal}
|
onClear={closePollModal}
|
||||||
onSend={handlePollSend}
|
onSend={handlePollSendWithPaymentConfirmation}
|
||||||
/>
|
/>
|
||||||
<SendAsMenu
|
<SendAsMenu
|
||||||
isOpen={isSendAsMenuOpen}
|
isOpen={isSendAsMenuOpen}
|
||||||
@ -2109,6 +2198,22 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
{onForward && <Icon name="forward" />}
|
{onForward && <Icon name="forward" />}
|
||||||
{isInMessageList && <Icon name="schedule" />}
|
{isInMessageList && <Icon name="schedule" />}
|
||||||
{isInMessageList && <Icon name="check" />}
|
{isInMessageList && <Icon name="check" />}
|
||||||
|
<Button
|
||||||
|
className={buildClassName('paidStarsBadge', shouldRenderPaidBadge && 'visible')}
|
||||||
|
nonInteractive
|
||||||
|
size="tiny"
|
||||||
|
color="stars"
|
||||||
|
pill
|
||||||
|
fluid
|
||||||
|
>
|
||||||
|
<div className="paidStarsBadgeText">
|
||||||
|
<Icon name="star" className={buildClassName('star-amount-icon', className)} />
|
||||||
|
<AnimatedCounter
|
||||||
|
ref={counterRef}
|
||||||
|
text={lang.number(starsForAllMessages)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</Button>
|
</Button>
|
||||||
{effectEmoji && (
|
{effectEmoji && (
|
||||||
<span className="effect-icon" onClick={handleRemoveEffect}>
|
<span className="effect-icon" onClick={handleRemoveEffect}>
|
||||||
@ -2125,7 +2230,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
{canShowCustomSendMenu && (
|
{canShowCustomSendMenu && (
|
||||||
<CustomSendMenu
|
<CustomSendMenu
|
||||||
isOpen={isCustomSendMenuOpen}
|
isOpen={isCustomSendMenuOpen}
|
||||||
canSchedule={isInMessageList && !isViewOnceEnabled}
|
canSchedule={!paidMessagesStars && isInMessageList && !isViewOnceEnabled}
|
||||||
canScheduleUntilOnline={canScheduleUntilOnline && !isViewOnceEnabled}
|
canScheduleUntilOnline={canScheduleUntilOnline && !isViewOnceEnabled}
|
||||||
onSendSilent={!isChatWithSelf ? handleSendSilent : undefined}
|
onSendSilent={!isChatWithSelf ? handleSendSilent : undefined}
|
||||||
onSendSchedule={!isInScheduledList ? handleSendScheduled : undefined}
|
onSendSchedule={!isInScheduledList ? handleSendScheduled : undefined}
|
||||||
@ -2147,6 +2252,16 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{calendar}
|
{calendar}
|
||||||
|
<PaymentMessageConfirmDialog
|
||||||
|
isOpen={isPaymentMessageConfirmDialogOpen}
|
||||||
|
onClose={closeConfirmModalPayForMessage}
|
||||||
|
userName={chat ? getPeerTitle(lang, chat) : undefined}
|
||||||
|
messagePriceInStars={paidMessagesStars || 0}
|
||||||
|
messagesCount={messagesCount}
|
||||||
|
shouldAutoApprove={shouldPaidMessageAutoApprove}
|
||||||
|
setAutoApprove={setShouldPaidMessageAutoApprove}
|
||||||
|
confirmHandler={paymentMessageConfirmDialogHandler}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -2161,12 +2276,18 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
const isChatWithUser = isUserId(chatId);
|
const isChatWithUser = isUserId(chatId);
|
||||||
const userFullInfo = isChatWithUser ? selectUserFullInfo(global, chatId) : undefined;
|
const userFullInfo = isChatWithUser ? selectUserFullInfo(global, chatId) : undefined;
|
||||||
|
const paidMessagesStars = selectPeerPaidMessagesStars(global, chatId);
|
||||||
|
|
||||||
const chatFullInfo = !isChatWithUser ? selectChatFullInfo(global, chatId) : undefined;
|
const chatFullInfo = !isChatWithUser ? selectChatFullInfo(global, chatId) : undefined;
|
||||||
const messageWithActualBotKeyboard = (isChatWithBot || !isChatWithUser)
|
const messageWithActualBotKeyboard = (isChatWithBot || !isChatWithUser)
|
||||||
&& selectNewestMessageWithBotKeyboardButtons(global, chatId, threadId);
|
&& selectNewestMessageWithBotKeyboardButtons(global, chatId, threadId);
|
||||||
const {
|
const {
|
||||||
language, shouldSuggestStickers, shouldSuggestCustomEmoji, shouldUpdateStickerSetOrder,
|
language, shouldSuggestStickers, shouldSuggestCustomEmoji, shouldUpdateStickerSetOrder,
|
||||||
|
shouldPaidMessageAutoApprove,
|
||||||
} = global.settings.byKey;
|
} = global.settings.byKey;
|
||||||
|
const {
|
||||||
|
forwardMessages: { messageIds: forwardMessageIds },
|
||||||
|
} = selectTabState(global);
|
||||||
const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG];
|
const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG];
|
||||||
const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined;
|
const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined;
|
||||||
const botKeyboardMessageId = messageWithActualBotKeyboard ? messageWithActualBotKeyboard.id : undefined;
|
const botKeyboardMessageId = messageWithActualBotKeyboard ? messageWithActualBotKeyboard.id : undefined;
|
||||||
@ -2230,6 +2351,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const effectReactions = global.reactions.effectReactions;
|
const effectReactions = global.reactions.effectReactions;
|
||||||
|
|
||||||
const maxMessageLength = global.config?.maxMessageLength || DEFAULT_MAX_MESSAGE_LENGTH;
|
const maxMessageLength = global.config?.maxMessageLength || DEFAULT_MAX_MESSAGE_LENGTH;
|
||||||
|
const isForwarding = chatId === tabState.forwardMessages.toChatId;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
availableReactions: global.reactions.availableReactions,
|
availableReactions: global.reactions.availableReactions,
|
||||||
@ -2252,7 +2374,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isInScheduledList,
|
isInScheduledList,
|
||||||
botKeyboardMessageId,
|
botKeyboardMessageId,
|
||||||
botKeyboardPlaceholder: keyboardMessage?.keyboardPlaceholder,
|
botKeyboardPlaceholder: keyboardMessage?.keyboardPlaceholder,
|
||||||
isForwarding: chatId === tabState.forwardMessages.toChatId,
|
isForwarding,
|
||||||
|
forwardedMessagesCount: isForwarding ? forwardMessageIds!.length : undefined,
|
||||||
pollModal: tabState.pollModal,
|
pollModal: tabState.pollModal,
|
||||||
stickersForEmoji: global.stickers.forEmoji.stickers,
|
stickersForEmoji: global.stickers.forEmoji.stickers,
|
||||||
customEmojiForEmoji: global.customEmojis.forEmoji.stickers,
|
customEmojiForEmoji: global.customEmojis.forEmoji.stickers,
|
||||||
@ -2307,7 +2430,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canPlayEffect,
|
canPlayEffect,
|
||||||
shouldPlayEffect,
|
shouldPlayEffect,
|
||||||
maxMessageLength,
|
maxMessageLength,
|
||||||
|
paidMessagesStars,
|
||||||
|
shouldPaidMessageAutoApprove,
|
||||||
isSilentPosting,
|
isSilentPosting,
|
||||||
|
isPaymentMessageConfirmDialogOpen: tabState.isPaymentMessageConfirmDialogOpen,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Composer));
|
)(Composer));
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
.checkBox {
|
||||||
|
margin-top: 0.375rem;
|
||||||
|
margin-inline: -1.125rem;
|
||||||
|
padding-inline-start: 3.5rem;
|
||||||
|
}
|
||||||
73
src/components/common/PaymentMessageConfirmDialog.tsx
Normal file
73
src/components/common/PaymentMessageConfirmDialog.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import type { FC, StateHookSetter } from '../../lib/teact/teact';
|
||||||
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
|
import Checkbox from '../ui/Checkbox';
|
||||||
|
import ConfirmDialog from '../ui/ConfirmDialog';
|
||||||
|
|
||||||
|
import styles from './PaymentMessageConfirmDialog.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: NoneToVoidFunction;
|
||||||
|
userName?: string;
|
||||||
|
messagePriceInStars: number;
|
||||||
|
messagesCount: number;
|
||||||
|
shouldAutoApprove: boolean;
|
||||||
|
setAutoApprove: StateHookSetter<boolean>;
|
||||||
|
confirmHandler: NoneToVoidFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PaymentMessageConfirmDialog: FC<OwnProps> = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
userName,
|
||||||
|
messagePriceInStars,
|
||||||
|
messagesCount,
|
||||||
|
shouldAutoApprove: shouldPaidMessageAutoApprove,
|
||||||
|
setAutoApprove: setShouldPaidMessageAutoApprove,
|
||||||
|
confirmHandler,
|
||||||
|
}) => {
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const confirmPaymentMessage = messagesCount === 1 ? lang('ConfirmationModalPaymentForOneMessage', {
|
||||||
|
user: userName,
|
||||||
|
amount: messagePriceInStars,
|
||||||
|
}, {
|
||||||
|
withMarkdown: true,
|
||||||
|
withNodes: true,
|
||||||
|
}) : lang('ConfirmationModalPaymentForMessages', {
|
||||||
|
user: userName,
|
||||||
|
price: messagePriceInStars,
|
||||||
|
amount: messagePriceInStars * messagesCount,
|
||||||
|
count: messagesCount,
|
||||||
|
}, {
|
||||||
|
withMarkdown: true,
|
||||||
|
withNodes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const confirmLabel = lang('ButtonPayForMessage', { count: messagesCount }, {
|
||||||
|
withNodes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmDialog
|
||||||
|
title={lang('TitleConfirmPayment')}
|
||||||
|
confirmLabel={confirmLabel}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
confirmHandler={confirmHandler}
|
||||||
|
>
|
||||||
|
{confirmPaymentMessage}
|
||||||
|
<Checkbox
|
||||||
|
className={styles.checkBox}
|
||||||
|
label={lang('DoNotAskAgain')}
|
||||||
|
checked={shouldPaidMessageAutoApprove}
|
||||||
|
onCheck={setShouldPaidMessageAutoApprove}
|
||||||
|
/>
|
||||||
|
</ConfirmDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(PaymentMessageConfirmDialog);
|
||||||
@ -30,6 +30,7 @@ export type OwnProps = {
|
|||||||
onSelectRecipient: (peerId: string, threadId?: ThreadId) => void;
|
onSelectRecipient: (peerId: string, threadId?: ThreadId) => void;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
onCloseAnimationEnd?: NoneToVoidFunction;
|
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||||
|
isLowStackPriority?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -54,6 +55,7 @@ const RecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
onSelectRecipient,
|
onSelectRecipient,
|
||||||
onClose,
|
onClose,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
|
isLowStackPriority,
|
||||||
}) => {
|
}) => {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const ids = useMemo(() => {
|
const ids = useMemo(() => {
|
||||||
@ -112,6 +114,7 @@ const RecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
onSelectChatOrUser={onSelectRecipient}
|
onSelectChatOrUser={onSelectRecipient}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||||
|
isLowStackPriority={isLowStackPriority}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
|
selectPeerPaidMessagesStars,
|
||||||
selectShouldSchedule,
|
selectShouldSchedule,
|
||||||
selectStickerSet,
|
selectStickerSet,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
@ -276,12 +277,13 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
: stickerSetShortName ? { shortName: stickerSetShortName } : undefined;
|
: stickerSetShortName ? { shortName: stickerSetShortName } : undefined;
|
||||||
|
|
||||||
const stickerSet = stickerSetInfo ? selectStickerSet(global, stickerSetInfo) : undefined;
|
const stickerSet = stickerSetInfo ? selectStickerSet(global, stickerSetInfo) : undefined;
|
||||||
|
const paidMessagesStars = chatId ? selectPeerPaidMessagesStars(global, chatId) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canScheduleUntilOnline: Boolean(chatId) && selectCanScheduleUntilOnline(global, chatId),
|
canScheduleUntilOnline: Boolean(chatId) && selectCanScheduleUntilOnline(global, chatId),
|
||||||
canSendStickers,
|
canSendStickers,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
shouldSchedule: selectShouldSchedule(global),
|
shouldSchedule: !paidMessagesStars && selectShouldSchedule(global),
|
||||||
stickerSet,
|
stickerSet,
|
||||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||||
shouldUpdateStickerSetOrder: global.settings.byKey.shouldUpdateStickerSetOrder,
|
shouldUpdateStickerSetOrder: global.settings.byKey.shouldUpdateStickerSetOrder,
|
||||||
|
|||||||
@ -51,6 +51,7 @@ export type OwnProps = {
|
|||||||
onSelectChatOrUser: (chatOrUserId: string, threadId?: ThreadId) => void;
|
onSelectChatOrUser: (chatOrUserId: string, threadId?: ThreadId) => void;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
onCloseAnimationEnd?: NoneToVoidFunction;
|
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||||
|
isLowStackPriority?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CHAT_LIST_SLIDE = 0;
|
const CHAT_LIST_SLIDE = 0;
|
||||||
@ -71,6 +72,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
onSelectChatOrUser,
|
onSelectChatOrUser,
|
||||||
onClose,
|
onClose,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
|
isLowStackPriority,
|
||||||
}) => {
|
}) => {
|
||||||
const { loadTopics } = getActions();
|
const { loadTopics } = getActions();
|
||||||
|
|
||||||
@ -323,6 +325,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
className={buildClassName('ChatOrUserPicker', className)}
|
className={buildClassName('ChatOrUserPicker', className)}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||||
|
isLowStackPriority={isLowStackPriority}
|
||||||
>
|
>
|
||||||
<Transition activeKey={activeKey} name="slideFade" slideClassName="ChatOrUserPicker_slide">
|
<Transition activeKey={activeKey} name="slideFade" slideClassName="ChatOrUserPicker_slide">
|
||||||
{() => {
|
{() => {
|
||||||
|
|||||||
@ -339,6 +339,11 @@ function LeftColumn({
|
|||||||
case SettingsScreens.DoNotTranslate:
|
case SettingsScreens.DoNotTranslate:
|
||||||
setSettingsScreen(SettingsScreens.Language);
|
setSettingsScreen(SettingsScreens.Language);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case SettingsScreens.PrivacyNoPaidMessages:
|
||||||
|
setSettingsScreen(SettingsScreens.PrivacyMessages);
|
||||||
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
.contacts_and_premium_option-title {
|
.root {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ function PrivacyLockedOption({ label }: OwnProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.contactsAndPremiumOptionTitle}
|
className={styles.root}
|
||||||
onClick={() => showNotification({ message: lang('OptionPremiumRequiredMessage') })}
|
onClick={() => showNotification({ message: lang('OptionPremiumRequiredMessage') })}
|
||||||
>
|
>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
|
|||||||
@ -1,88 +1,245 @@
|
|||||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
import React, {
|
||||||
|
memo, useCallback, useMemo, useState,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import { selectIsCurrentUserPremium, selectNewNoncontactPeersRequirePremium } from '../../../global/selectors';
|
import { SettingsScreens } from '../../../types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DEFAULT_CHARGE_FOR_MESSAGES,
|
||||||
|
DEFAULT_MAXIMUM_CHARGE_FOR_MESSAGES,
|
||||||
|
MINIMUM_CHARGE_FOR_MESSAGES,
|
||||||
|
} from '../../../config';
|
||||||
|
import {
|
||||||
|
selectIsCurrentUserPremium,
|
||||||
|
selectNewNoncontactPeersRequirePremium,
|
||||||
|
selectNonContactPeersPaidStars,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
import { formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||||
|
import { formatStarsAsText } from '../../../util/localization/format';
|
||||||
|
|
||||||
|
import useDebouncedCallback from '../../../hooks/useDebouncedCallback';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import ListItem from '../../ui/ListItem';
|
||||||
import RadioGroup from '../../ui/RadioGroup';
|
import RadioGroup from '../../ui/RadioGroup';
|
||||||
|
import RangeSlider from '../../ui/RangeSlider';
|
||||||
import PremiumStatusItem from './PremiumStatusItem';
|
import PremiumStatusItem from './PremiumStatusItem';
|
||||||
import PrivacyLockedOption from './PrivacyLockedOption';
|
import PrivacyLockedOption from './PrivacyLockedOption';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
onReset: VoidFunction;
|
onReset: VoidFunction;
|
||||||
|
onScreenSelect: (screen: SettingsScreens) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
shouldNewNonContactPeersRequirePremium?: boolean;
|
shouldNewNonContactPeersRequirePremium?: boolean;
|
||||||
|
shouldChargeForMessages?: boolean;
|
||||||
canLimitNewMessagesWithoutPremium?: boolean;
|
canLimitNewMessagesWithoutPremium?: boolean;
|
||||||
|
canChargeForMessages?: boolean;
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
|
starsUsdWithdrawRate: number;
|
||||||
|
starsPaidMessageCommissionPermille: number;
|
||||||
|
starsPaidMessageAmountMax?: number;
|
||||||
|
nonContactPeersPaidStars: number;
|
||||||
|
noPaidReactionsForUsersCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function PrivacyMessages({
|
function PrivacyMessages({
|
||||||
isActive,
|
isActive,
|
||||||
canLimitNewMessagesWithoutPremium,
|
canLimitNewMessagesWithoutPremium,
|
||||||
|
canChargeForMessages,
|
||||||
shouldNewNonContactPeersRequirePremium,
|
shouldNewNonContactPeersRequirePremium,
|
||||||
|
shouldChargeForMessages,
|
||||||
|
nonContactPeersPaidStars,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
|
starsPaidMessageCommissionPermille,
|
||||||
|
starsPaidMessageAmountMax,
|
||||||
|
starsUsdWithdrawRate,
|
||||||
|
noPaidReactionsForUsersCount,
|
||||||
onReset,
|
onReset,
|
||||||
|
onScreenSelect,
|
||||||
}: OwnProps & StateProps) {
|
}: OwnProps & StateProps) {
|
||||||
const { updateGlobalPrivacySettings } = getActions();
|
const { updateGlobalPrivacySettings } = getActions();
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
const canChange = isCurrentUserPremium || canLimitNewMessagesWithoutPremium;
|
const canChangeForContactsAndPremium = isCurrentUserPremium || canLimitNewMessagesWithoutPremium;
|
||||||
|
const canChangeChargeForMessages = isCurrentUserPremium && canChargeForMessages;
|
||||||
|
const [chargeForMessages, setChargeForMessages] = useState<number>(nonContactPeersPaidStars);
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{ value: 'everybody', label: lang('P2PEverybody') },
|
{ value: 'everybody', label: oldLang('P2PEverybody') },
|
||||||
{
|
{
|
||||||
value: 'contacts_and_premium',
|
value: 'contacts_and_premium',
|
||||||
label: canChange ? (
|
label: canChangeForContactsAndPremium ? (
|
||||||
lang('PrivacyMessagesContactsAndPremium')
|
oldLang('PrivacyMessagesContactsAndPremium')
|
||||||
) : (
|
) : (
|
||||||
<PrivacyLockedOption label={lang('PrivacyMessagesContactsAndPremium')} />
|
<PrivacyLockedOption label={oldLang('PrivacyMessagesContactsAndPremium')} />
|
||||||
),
|
),
|
||||||
hidden: !canChange,
|
hidden: !canChangeForContactsAndPremium,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'charge_for_messages',
|
||||||
|
label: canChangeChargeForMessages ? (
|
||||||
|
lang('PrivacyChargeForMessages')
|
||||||
|
) : (
|
||||||
|
<PrivacyLockedOption label={lang('PrivacyChargeForMessages')} />
|
||||||
|
),
|
||||||
|
hidden: !canChangeChargeForMessages,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [lang, canChange]);
|
}, [oldLang, lang, canChangeForContactsAndPremium, canChangeChargeForMessages]);
|
||||||
|
|
||||||
const handleChange = useLastCallback((privacy: string) => {
|
const handleChange = useLastCallback((privacy: string) => {
|
||||||
updateGlobalPrivacySettings({ shouldNewNonContactPeersRequirePremium: privacy === 'contacts_and_premium' });
|
updateGlobalPrivacySettings({
|
||||||
|
shouldNewNonContactPeersRequirePremium: privacy === 'contacts_and_premium',
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
nonContactPeersPaidStars: privacy === 'charge_for_messages' ? chargeForMessages : null,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updateGlobalPrivacySettingsWithDebounced = useDebouncedCallback((value: number) => {
|
||||||
|
updateGlobalPrivacySettings({
|
||||||
|
nonContactPeersPaidStars: value,
|
||||||
|
});
|
||||||
|
}, [updateGlobalPrivacySettings], 300, true);
|
||||||
|
|
||||||
|
const handleChargeForMessagesChange = useCallback((value: number) => {
|
||||||
|
setChargeForMessages(value);
|
||||||
|
updateGlobalPrivacySettingsWithDebounced(value);
|
||||||
|
}, [setChargeForMessages, updateGlobalPrivacySettingsWithDebounced]);
|
||||||
|
|
||||||
|
const renderValueForStarsRange = useCallback((value: number) => {
|
||||||
|
return formatStarsAsText(lang, value);
|
||||||
|
}, [lang]);
|
||||||
|
|
||||||
|
function renderSectionStarsAmountForPaidMessages() {
|
||||||
|
return (
|
||||||
|
<div className="settings-item">
|
||||||
|
<h4 className="settings-item-header" dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{lang('SectionTitleStarsForForMessages')}
|
||||||
|
</h4>
|
||||||
|
<RangeSlider
|
||||||
|
isCenteredLayout
|
||||||
|
min={MINIMUM_CHARGE_FOR_MESSAGES}
|
||||||
|
max={starsPaidMessageAmountMax}
|
||||||
|
value={chargeForMessages}
|
||||||
|
onChange={handleChargeForMessagesChange}
|
||||||
|
renderValue={renderValueForStarsRange}
|
||||||
|
/>
|
||||||
|
<p className="settings-item-description-larger" dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{lang('SectionDescriptionStarsForForMessages', {
|
||||||
|
percent: starsPaidMessageCommissionPermille * 100,
|
||||||
|
amount: formatCurrencyAsString(
|
||||||
|
chargeForMessages * starsUsdWithdrawRate * starsPaidMessageCommissionPermille,
|
||||||
|
'USD',
|
||||||
|
lang.code,
|
||||||
|
|
||||||
|
),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSectionNoPaidMessagesForUsers() {
|
||||||
|
const itemSubtitle = !noPaidReactionsForUsersCount ? lang('SubtitlePrivacyAddUsers')
|
||||||
|
: oldLang('Users', noPaidReactionsForUsersCount, 'i');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="settings-item">
|
||||||
|
<h4 className="settings-item-header" dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{lang('RemoveFeeTitle')}
|
||||||
|
</h4>
|
||||||
|
<ListItem
|
||||||
|
narrow
|
||||||
|
icon="delete-user"
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => {
|
||||||
|
onScreenSelect(SettingsScreens.PrivacyNoPaidMessages);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="multiline-item full-size">
|
||||||
|
<span className="title">{lang('ExceptionTitlePrivacyChargeForMessages')}</span>
|
||||||
|
<span className="subtitle">{
|
||||||
|
itemSubtitle
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ListItem>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive,
|
isActive,
|
||||||
onBack: onReset,
|
onBack: onReset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectedValue = useMemo(() => {
|
||||||
|
if (shouldChargeForMessages) return 'charge_for_messages';
|
||||||
|
if (shouldNewNonContactPeersRequirePremium) return 'contacts_and_premium';
|
||||||
|
return 'everybody';
|
||||||
|
}, [shouldChargeForMessages, shouldNewNonContactPeersRequirePremium]);
|
||||||
|
|
||||||
|
const privacyDescription = useMemo(() => {
|
||||||
|
if (shouldChargeForMessages) return lang('PrivacyDescriptionChargeForMessages');
|
||||||
|
return lang('PrivacyDescriptionMessagesContactsAndPremium');
|
||||||
|
}, [shouldChargeForMessages, lang]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="settings-item">
|
<div className="settings-item">
|
||||||
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
<h4 className="settings-item-header" dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||||
{lang('PrivacyMessagesTitle')}
|
{oldLang('PrivacyMessagesTitle')}
|
||||||
</h4>
|
</h4>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
name="privacy-messages"
|
name="privacy-messages"
|
||||||
options={options}
|
options={options}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
selected={shouldNewNonContactPeersRequirePremium ? 'contacts_and_premium' : 'everybody'}
|
selected={selectedValue}
|
||||||
/>
|
/>
|
||||||
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>
|
<p className="settings-item-description-larger" dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||||
{lang('Privacy.Messages.SectionFooter')}
|
{privacyDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{!canChange && <PremiumStatusItem premiumSection="message_privacy" />}
|
{canChangeChargeForMessages
|
||||||
|
&& selectedValue === 'charge_for_messages' && renderSectionStarsAmountForPaidMessages()}
|
||||||
|
{canChangeChargeForMessages && selectedValue === 'charge_for_messages' && renderSectionNoPaidMessagesForUsers()}
|
||||||
|
{!isCurrentUserPremium && <PremiumStatusItem premiumSection="message_privacy" />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||||
|
const nonContactPeersPaidStars = selectNonContactPeersPaidStars(global);
|
||||||
|
|
||||||
|
const starsUsdWithdrawRateX1000 = global.appConfig?.starsUsdWithdrawRateX1000;
|
||||||
|
const starsUsdWithdrawRate = starsUsdWithdrawRateX1000 ? starsUsdWithdrawRateX1000 / 1000 : 1;
|
||||||
|
const configStarsPaidMessageCommissionPermille = global.appConfig?.starsPaidMessageCommissionPermille;
|
||||||
|
const starsPaidMessageCommissionPermille = configStarsPaidMessageCommissionPermille
|
||||||
|
? configStarsPaidMessageCommissionPermille / 1000 : 100;
|
||||||
|
|
||||||
|
const noPaidReactionsForUsersCount = global.settings.privacy.noPaidMessages?.allowUserIds.length || 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shouldNewNonContactPeersRequirePremium: selectNewNoncontactPeersRequirePremium(global),
|
shouldNewNonContactPeersRequirePremium: selectNewNoncontactPeersRequirePremium(global),
|
||||||
|
shouldChargeForMessages: Boolean(nonContactPeersPaidStars),
|
||||||
|
nonContactPeersPaidStars: nonContactPeersPaidStars || DEFAULT_CHARGE_FOR_MESSAGES,
|
||||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||||
canLimitNewMessagesWithoutPremium: global.appConfig?.canLimitNewMessagesWithoutPremium,
|
canLimitNewMessagesWithoutPremium: global.appConfig?.canLimitNewMessagesWithoutPremium,
|
||||||
|
canChargeForMessages: global.appConfig?.starsPaidMessagesAvailable,
|
||||||
|
starsPaidMessageAmountMax: global.appConfig?.starsPaidMessageAmountMax || DEFAULT_MAXIMUM_CHARGE_FOR_MESSAGES,
|
||||||
|
starsPaidMessageCommissionPermille,
|
||||||
|
starsUsdWithdrawRate,
|
||||||
|
noPaidReactionsForUsersCount,
|
||||||
};
|
};
|
||||||
})(PrivacyMessages));
|
})(PrivacyMessages));
|
||||||
|
|||||||
@ -141,6 +141,10 @@ const PRIVACY_GROUP_CHATS_SCREENS = [
|
|||||||
SettingsScreens.PrivacyGroupChatsDeniedContacts,
|
SettingsScreens.PrivacyGroupChatsDeniedContacts,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const PRIVACY_MESSAGES_SCREENS = [
|
||||||
|
SettingsScreens.PrivacyNoPaidMessages,
|
||||||
|
];
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
currentScreen: SettingsScreens;
|
currentScreen: SettingsScreens;
|
||||||
@ -224,6 +228,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
[SettingsScreens.PrivacyForwarding]: PRIVACY_FORWARDING_SCREENS.includes(activeScreen),
|
[SettingsScreens.PrivacyForwarding]: PRIVACY_FORWARDING_SCREENS.includes(activeScreen),
|
||||||
[SettingsScreens.PrivacyVoiceMessages]: PRIVACY_VOICE_MESSAGES_SCREENS.includes(activeScreen),
|
[SettingsScreens.PrivacyVoiceMessages]: PRIVACY_VOICE_MESSAGES_SCREENS.includes(activeScreen),
|
||||||
[SettingsScreens.PrivacyGroupChats]: PRIVACY_GROUP_CHATS_SCREENS.includes(activeScreen),
|
[SettingsScreens.PrivacyGroupChats]: PRIVACY_GROUP_CHATS_SCREENS.includes(activeScreen),
|
||||||
|
[SettingsScreens.PrivacyMessages]: PRIVACY_MESSAGES_SCREENS.includes(activeScreen),
|
||||||
};
|
};
|
||||||
|
|
||||||
const isTwoFaScreen = TWO_FA_SCREENS.includes(activeScreen);
|
const isTwoFaScreen = TWO_FA_SCREENS.includes(activeScreen);
|
||||||
@ -370,13 +375,14 @@ const Settings: FC<OwnProps> = ({
|
|||||||
case SettingsScreens.PrivacyForwardingAllowedContacts:
|
case SettingsScreens.PrivacyForwardingAllowedContacts:
|
||||||
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
|
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
|
||||||
case SettingsScreens.PrivacyGroupChatsAllowedContacts:
|
case SettingsScreens.PrivacyGroupChatsAllowedContacts:
|
||||||
|
case SettingsScreens.PrivacyNoPaidMessages:
|
||||||
return (
|
return (
|
||||||
<SettingsPrivacyVisibilityExceptionList
|
<SettingsPrivacyVisibilityExceptionList
|
||||||
isAllowList
|
isAllowList
|
||||||
|
usersOnly={currentScreen === SettingsScreens.PrivacyNoPaidMessages}
|
||||||
withPremiumCategory={currentScreen === SettingsScreens.PrivacyGroupChatsAllowedContacts}
|
withPremiumCategory={currentScreen === SettingsScreens.PrivacyGroupChatsAllowedContacts}
|
||||||
withMiniAppsCategory={currentScreen === SettingsScreens.PrivacyGiftsAllowedContacts}
|
withMiniAppsCategory={currentScreen === SettingsScreens.PrivacyGiftsAllowedContacts}
|
||||||
screen={currentScreen}
|
screen={currentScreen}
|
||||||
onScreenSelect={onScreenSelect}
|
|
||||||
isActive={isScreenActive || privacyAllowScreens[currentScreen]}
|
isActive={isScreenActive || privacyAllowScreens[currentScreen]}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
/>
|
/>
|
||||||
@ -396,7 +402,6 @@ const Settings: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<SettingsPrivacyVisibilityExceptionList
|
<SettingsPrivacyVisibilityExceptionList
|
||||||
screen={currentScreen}
|
screen={currentScreen}
|
||||||
onScreenSelect={onScreenSelect}
|
|
||||||
isActive={isScreenActive}
|
isActive={isScreenActive}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
/>
|
/>
|
||||||
@ -407,6 +412,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
<PrivacyMessages
|
<PrivacyMessages
|
||||||
isActive={isScreenActive}
|
isActive={isScreenActive}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
|
onScreenSelect={onScreenSelect}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -163,6 +163,9 @@ const SettingsHeader: FC<OwnProps> = ({
|
|||||||
case SettingsScreens.PrivacyPhoneP2PDeniedContacts:
|
case SettingsScreens.PrivacyPhoneP2PDeniedContacts:
|
||||||
return <h3>{oldLang('NeverAllow')}</h3>;
|
return <h3>{oldLang('NeverAllow')}</h3>;
|
||||||
|
|
||||||
|
case SettingsScreens.PrivacyNoPaidMessages:
|
||||||
|
return <h3>{lang('RemoveFeeTitle')}</h3>;
|
||||||
|
|
||||||
case SettingsScreens.Performance:
|
case SettingsScreens.Performance:
|
||||||
return <h3>{lang('MenuAnimations')}</h3>;
|
return <h3>{lang('MenuAnimations')}</h3>;
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@ type StateProps = {
|
|||||||
canDisplayAutoarchiveSetting: boolean;
|
canDisplayAutoarchiveSetting: boolean;
|
||||||
shouldArchiveAndMuteNewNonContact?: boolean;
|
shouldArchiveAndMuteNewNonContact?: boolean;
|
||||||
shouldNewNonContactPeersRequirePremium?: boolean;
|
shouldNewNonContactPeersRequirePremium?: boolean;
|
||||||
|
shouldChargeForMessages: boolean;
|
||||||
canDisplayChatInTitle?: boolean;
|
canDisplayChatInTitle?: boolean;
|
||||||
privacy: GlobalState['settings']['privacy'];
|
privacy: GlobalState['settings']['privacy'];
|
||||||
};
|
};
|
||||||
@ -50,6 +51,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
canDisplayAutoarchiveSetting,
|
canDisplayAutoarchiveSetting,
|
||||||
shouldArchiveAndMuteNewNonContact,
|
shouldArchiveAndMuteNewNonContact,
|
||||||
shouldNewNonContactPeersRequirePremium,
|
shouldNewNonContactPeersRequirePremium,
|
||||||
|
shouldChargeForMessages,
|
||||||
canDisplayChatInTitle,
|
canDisplayChatInTitle,
|
||||||
canSetPasscode,
|
canSetPasscode,
|
||||||
privacy,
|
privacy,
|
||||||
@ -332,9 +334,10 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="multiline-item">
|
<div className="multiline-item">
|
||||||
<span className="title">{oldLang('PrivacyMessagesTitle')}</span>
|
<span className="title">{oldLang('PrivacyMessagesTitle')}</span>
|
||||||
<span className="subtitle" dir="auto">
|
<span className="subtitle" dir="auto">
|
||||||
{shouldNewNonContactPeersRequirePremium
|
{shouldChargeForMessages ? lang('PrivacyPaidMessagesValue')
|
||||||
? oldLang('PrivacyMessagesContactsAndPremium')
|
: shouldNewNonContactPeersRequirePremium
|
||||||
: oldLang('P2PEverybody')}
|
? oldLang('PrivacyMessagesContactsAndPremium')
|
||||||
|
: oldLang('P2PEverybody')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -402,7 +405,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
settings: {
|
settings: {
|
||||||
byKey: {
|
byKey: {
|
||||||
hasPassword, isSensitiveEnabled, canChangeSensitive, shouldArchiveAndMuteNewNonContact,
|
hasPassword, isSensitiveEnabled, canChangeSensitive, shouldArchiveAndMuteNewNonContact,
|
||||||
canDisplayChatInTitle, shouldNewNonContactPeersRequirePremium,
|
canDisplayChatInTitle, shouldNewNonContactPeersRequirePremium, nonContactPeersPaidStars,
|
||||||
},
|
},
|
||||||
privacy,
|
privacy,
|
||||||
},
|
},
|
||||||
@ -413,6 +416,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
appConfig,
|
appConfig,
|
||||||
} = global;
|
} = global;
|
||||||
|
|
||||||
|
const shouldChargeForMessages = Boolean(nonContactPeersPaidStars);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||||
hasPassword,
|
hasPassword,
|
||||||
@ -424,6 +429,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
shouldArchiveAndMuteNewNonContact,
|
shouldArchiveAndMuteNewNonContact,
|
||||||
canChangeSensitive,
|
canChangeSensitive,
|
||||||
shouldNewNonContactPeersRequirePremium,
|
shouldNewNonContactPeersRequirePremium,
|
||||||
|
shouldChargeForMessages,
|
||||||
privacy,
|
privacy,
|
||||||
canDisplayChatInTitle,
|
canDisplayChatInTitle,
|
||||||
canSetPasscode: selectCanSetPasscode(global),
|
canSetPasscode: selectCanSetPasscode(global),
|
||||||
|
|||||||
@ -31,9 +31,9 @@ export type OwnProps = {
|
|||||||
isAllowList?: boolean;
|
isAllowList?: boolean;
|
||||||
withPremiumCategory?: boolean;
|
withPremiumCategory?: boolean;
|
||||||
withMiniAppsCategory?: boolean;
|
withMiniAppsCategory?: boolean;
|
||||||
|
usersOnly?: boolean;
|
||||||
screen: SettingsScreens;
|
screen: SettingsScreens;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
onScreenSelect: (screen: SettingsScreens) => void;
|
|
||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
isActive,
|
isActive,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
settings,
|
settings,
|
||||||
onScreenSelect,
|
usersOnly = false,
|
||||||
onReset,
|
onReset,
|
||||||
}) => {
|
}) => {
|
||||||
const { setPrivacySettings } = getActions();
|
const { setPrivacySettings } = getActions();
|
||||||
@ -120,7 +120,10 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
const user = usersById[chatId];
|
const user = usersById[chatId];
|
||||||
const isDeleted = user && isDeletedUser(user);
|
const isDeleted = user && isDeletedUser(user);
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
return chatId !== currentUserId && chatId !== SERVICE_NOTIFICATIONS_USER_ID && !isChannel && !isDeleted;
|
return (!usersOnly || user)
|
||||||
|
&& chatId !== currentUserId
|
||||||
|
&& chatId !== SERVICE_NOTIFICATIONS_USER_ID
|
||||||
|
&& !isChannel && !isDeleted;
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredChats = filterPeersByQuery({ ids: chatIds, query: searchQuery });
|
const filteredChats = filterPeersByQuery({ ids: chatIds, query: searchQuery });
|
||||||
@ -132,7 +135,7 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
...selectedContactIds,
|
...selectedContactIds,
|
||||||
...chatIds,
|
...chatIds,
|
||||||
]);
|
]);
|
||||||
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedContactIds, searchQuery, currentUserId]);
|
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedContactIds, searchQuery, currentUserId, usersOnly]);
|
||||||
|
|
||||||
const handleSelectedCategoriesChange = useCallback((value: CustomPeerType[]) => {
|
const handleSelectedCategoriesChange = useCallback((value: CustomPeerType[]) => {
|
||||||
setNewSelectedCategoryTypes(value);
|
setNewSelectedCategoryTypes(value);
|
||||||
@ -154,13 +157,13 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
: (newSelectedCategoryTypes.includes(customPeerBots.type) ? 'allow' : 'disallow'),
|
: (newSelectedCategoryTypes.includes(customPeerBots.type) ? 'allow' : 'disallow'),
|
||||||
});
|
});
|
||||||
|
|
||||||
onScreenSelect(SettingsScreens.Privacy);
|
onReset();
|
||||||
}, [
|
}, [
|
||||||
isAllowList,
|
isAllowList,
|
||||||
withMiniAppsCategory,
|
withMiniAppsCategory,
|
||||||
newSelectedCategoryTypes,
|
newSelectedCategoryTypes,
|
||||||
newSelectedContactIds,
|
newSelectedContactIds,
|
||||||
onScreenSelect,
|
onReset,
|
||||||
screen,
|
screen,
|
||||||
customPeerBots,
|
customPeerBots,
|
||||||
]);
|
]);
|
||||||
@ -244,6 +247,8 @@ function getCurrentPrivacySettings(global: GlobalState, screen: SettingsScreens)
|
|||||||
case SettingsScreens.PrivacyGroupChatsDeniedContacts:
|
case SettingsScreens.PrivacyGroupChatsDeniedContacts:
|
||||||
case SettingsScreens.PrivacyGroupChatsAllowedContacts:
|
case SettingsScreens.PrivacyGroupChatsAllowedContacts:
|
||||||
return privacy.chatInvite;
|
return privacy.chatInvite;
|
||||||
|
case SettingsScreens.PrivacyNoPaidMessages:
|
||||||
|
return privacy.noPaidMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -49,6 +49,8 @@ export function getPrivacyKey(screen: SettingsScreens): ApiPrivacyKey | undefine
|
|||||||
return 'phoneP2P';
|
return 'phoneP2P';
|
||||||
case SettingsScreens.PrivacyAddByPhone:
|
case SettingsScreens.PrivacyAddByPhone:
|
||||||
return 'addByPhone';
|
return 'addByPhone';
|
||||||
|
case SettingsScreens.PrivacyNoPaidMessages:
|
||||||
|
return 'noPaidMessages';
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import type { MessageList } from '../../types';
|
|||||||
|
|
||||||
import { selectCurrentMessageList, selectTabState } from '../../global/selectors';
|
import { selectCurrentMessageList, selectTabState } from '../../global/selectors';
|
||||||
import getReadableErrorText from '../../util/getReadableErrorText';
|
import getReadableErrorText from '../../util/getReadableErrorText';
|
||||||
import { pick } from '../../util/iteratees';
|
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -49,7 +48,7 @@ const Dialogs: FC<StateProps> = ({ dialogs, currentMessageList }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendMessage({
|
sendMessage({
|
||||||
contact: pick(contactRequest, ['firstName', 'lastName', 'phoneNumber']),
|
contact: contactRequest,
|
||||||
messageList: currentMessageList,
|
messageList: currentMessageList,
|
||||||
});
|
});
|
||||||
closeModal();
|
closeModal();
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const Notifications: FC<StateProps> = ({ notifications }) => {
|
|||||||
return (
|
return (
|
||||||
<div id="Notifications">
|
<div id="Notifications">
|
||||||
{notifications.map((notification) => (
|
{notifications.map((notification) => (
|
||||||
<Notification notification={notification} />
|
<Notification key={notification.localId} notification={notification} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -80,7 +80,7 @@ import ContactGreeting from './ContactGreeting';
|
|||||||
import MessageListAccountInfo from './MessageListAccountInfo';
|
import MessageListAccountInfo from './MessageListAccountInfo';
|
||||||
import MessageListContent from './MessageListContent';
|
import MessageListContent from './MessageListContent';
|
||||||
import NoMessages from './NoMessages';
|
import NoMessages from './NoMessages';
|
||||||
import PremiumRequiredMessage from './PremiumRequiredMessage';
|
import RequirementToContactMessage from './RequirementToContactMessage';
|
||||||
|
|
||||||
import './MessageList.scss';
|
import './MessageList.scss';
|
||||||
|
|
||||||
@ -97,6 +97,7 @@ type OwnProps = {
|
|||||||
withDefaultBg: boolean;
|
withDefaultBg: boolean;
|
||||||
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
||||||
isContactRequirePremium?: boolean;
|
isContactRequirePremium?: boolean;
|
||||||
|
paidMessagesStars?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -187,6 +188,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
isServiceNotificationsChat,
|
isServiceNotificationsChat,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
isContactRequirePremium,
|
isContactRequirePremium,
|
||||||
|
paidMessagesStars,
|
||||||
areAdsEnabled,
|
areAdsEnabled,
|
||||||
channelJoinInfo,
|
channelJoinInfo,
|
||||||
isChatProtected,
|
isChatProtected,
|
||||||
@ -689,8 +691,10 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
{restrictionReason ? restrictionReason.text : `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
|
{restrictionReason ? restrictionReason.text : `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
) : paidMessagesStars && isPrivate && !hasMessages && !shouldRenderGreeting ? (
|
||||||
|
<RequirementToContactMessage paidMessagesStars={paidMessagesStars} userId={chatId} />
|
||||||
) : isContactRequirePremium && !hasMessages ? (
|
) : isContactRequirePremium && !hasMessages ? (
|
||||||
<PremiumRequiredMessage userId={chatId} />
|
<RequirementToContactMessage userId={chatId} />
|
||||||
) : (isBot || isNonContact) && !hasMessages ? (
|
) : (isBot || isNonContact) && !hasMessages ? (
|
||||||
<MessageListAccountInfo chatId={chatId} />
|
<MessageListAccountInfo chatId={chatId} />
|
||||||
) : shouldRenderGreeting ? (
|
) : shouldRenderGreeting ? (
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { getIsHeavyAnimating, memo } from '../../lib/teact/teact';
|
import React, { getIsHeavyAnimating, memo } from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions, getGlobal } from '../../global';
|
||||||
|
|
||||||
import type { MessageListType, ThreadId } from '../../types';
|
import type { ApiMessage } from '../../api/types';
|
||||||
|
import type { IAlbum, MessageListType, ThreadId } from '../../types';
|
||||||
import type { Signal } from '../../util/signals';
|
import type { Signal } from '../../util/signals';
|
||||||
import type { MessageDateGroup } from './helpers/groupMessages';
|
import type { MessageDateGroup } from './helpers/groupMessages';
|
||||||
import type { OnIntersectPinnedMessage } from './hooks/usePinnedMessage';
|
import type { OnIntersectPinnedMessage } from './hooks/usePinnedMessage';
|
||||||
@ -13,10 +14,12 @@ import { SCHEDULED_WHEN_ONLINE } from '../../config';
|
|||||||
import {
|
import {
|
||||||
getMessageHtmlId,
|
getMessageHtmlId,
|
||||||
getMessageOriginalId,
|
getMessageOriginalId,
|
||||||
|
getPeerTitle,
|
||||||
isActionMessage,
|
isActionMessage,
|
||||||
isOwnMessage,
|
isOwnMessage,
|
||||||
isServiceNotificationMessage,
|
isServiceNotificationMessage,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
|
import { selectSender } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { formatHumanDate } from '../../util/dates/dateFormat';
|
import { formatHumanDate } from '../../util/dates/dateFormat';
|
||||||
import { compact } from '../../util/iteratees';
|
import { compact } from '../../util/iteratees';
|
||||||
@ -24,6 +27,7 @@ import { isAlbum } from './helpers/groupMessages';
|
|||||||
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
||||||
|
|
||||||
import useDerivedSignal from '../../hooks/useDerivedSignal';
|
import useDerivedSignal from '../../hooks/useDerivedSignal';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
||||||
import useMessageObservers from './hooks/useMessageObservers';
|
import useMessageObservers from './hooks/useMessageObservers';
|
||||||
@ -131,12 +135,42 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
const unreadDivider = (
|
const unreadDivider = (
|
||||||
<div className={buildClassName(UNREAD_DIVIDER_CLASS, 'local-action-message')} key="unread-messages">
|
<div className={buildClassName(UNREAD_DIVIDER_CLASS, 'local-action-message')} key="unread-messages">
|
||||||
<span>{oldLang('UnreadMessages')}</span>
|
<span>{oldLang('UnreadMessages')}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
const renderPaidMessageAction = (message: ApiMessage, album?: IAlbum) => {
|
||||||
|
if (message.paidMessageStars) {
|
||||||
|
const messagesLength = album?.messages?.length || 1;
|
||||||
|
const amount = message.paidMessageStars * messagesLength;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName('local-action-message')}
|
||||||
|
key={`paid-messages-action-${message.id}`}
|
||||||
|
>
|
||||||
|
<span>{
|
||||||
|
message.isOutgoing
|
||||||
|
? lang('ActionPaidOneMessageOutgoing', {
|
||||||
|
amount,
|
||||||
|
})
|
||||||
|
: (() => {
|
||||||
|
const sender = selectSender(getGlobal(), message);
|
||||||
|
const userTitle = sender ? getPeerTitle(lang, sender) : '';
|
||||||
|
return lang('ActionPaidOneMessageIncoming', {
|
||||||
|
user: userTitle,
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => {
|
const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => {
|
||||||
return acc + messageGroup.senderGroups.flat().length;
|
return acc + messageGroup.senderGroups.flat().length;
|
||||||
}, 0);
|
}, 0);
|
||||||
@ -228,6 +262,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
|
|
||||||
return compact([
|
return compact([
|
||||||
message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
|
message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
|
||||||
|
message.paidMessageStars && !withUsers && renderPaidMessageAction(message, album),
|
||||||
<Message
|
<Message
|
||||||
key={key}
|
key={key}
|
||||||
message={message}
|
message={message}
|
||||||
|
|||||||
@ -65,7 +65,9 @@
|
|||||||
> .Button {
|
> .Button {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
|
/* stylelint-disable plugin/no-low-performance-animation-properties */
|
||||||
transition:
|
transition:
|
||||||
|
border-radius 0.15s,
|
||||||
opacity var(--select-transition),
|
opacity var(--select-transition),
|
||||||
transform var(--select-transition),
|
transform var(--select-transition),
|
||||||
background-color 0.15s,
|
background-color 0.15s,
|
||||||
@ -162,7 +164,6 @@
|
|||||||
z-index: var(--z-middle-footer);
|
z-index: var(--z-middle-footer);
|
||||||
|
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, 0, 0);
|
||||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
|
||||||
transition: top 200ms, transform var(--layer-transition);
|
transition: top 200ms, transform var(--layer-transition);
|
||||||
|
|
||||||
body.no-page-transitions & {
|
body.no-page-transitions & {
|
||||||
@ -170,7 +171,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.no-right-column-animations & {
|
body.no-right-column-animations & {
|
||||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
|
||||||
transition: top 200ms !important;
|
transition: top 200ms !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,7 @@ import {
|
|||||||
selectIsInSelectMode,
|
selectIsInSelectMode,
|
||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectIsUserBlocked,
|
selectIsUserBlocked,
|
||||||
|
selectPeerPaidMessagesStars,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectTheme,
|
selectTheme,
|
||||||
@ -153,6 +154,7 @@ type StateProps = {
|
|||||||
canShowOpenChatButton?: boolean;
|
canShowOpenChatButton?: boolean;
|
||||||
isContactRequirePremium?: boolean;
|
isContactRequirePremium?: boolean;
|
||||||
topics?: Record<number, ApiTopic>;
|
topics?: Record<number, ApiTopic>;
|
||||||
|
paidMessagesStars?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isImage(item: DataTransferItem) {
|
function isImage(item: DataTransferItem) {
|
||||||
@ -213,6 +215,7 @@ function MiddleColumn({
|
|||||||
canShowOpenChatButton,
|
canShowOpenChatButton,
|
||||||
isContactRequirePremium,
|
isContactRequirePremium,
|
||||||
topics,
|
topics,
|
||||||
|
paidMessagesStars,
|
||||||
}: OwnProps & StateProps) {
|
}: OwnProps & StateProps) {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
@ -551,6 +554,7 @@ function MiddleColumn({
|
|||||||
onNotchToggle={setIsNotchShown}
|
onNotchToggle={setIsNotchShown}
|
||||||
isReady={isReady}
|
isReady={isReady}
|
||||||
isContactRequirePremium={isContactRequirePremium}
|
isContactRequirePremium={isContactRequirePremium}
|
||||||
|
paidMessagesStars={paidMessagesStars}
|
||||||
withBottomShift={withMessageListBottomShift}
|
withBottomShift={withMessageListBottomShift}
|
||||||
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
||||||
onIntersectPinnedMessage={renderingHandleIntersectPinnedMessage!}
|
onIntersectPinnedMessage={renderingHandleIntersectPinnedMessage!}
|
||||||
@ -800,7 +804,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const isContactRequirePremium = selectUserFullInfo(global, chatId)?.isContactRequirePremium;
|
const userFull = selectUserFullInfo(global, chatId);
|
||||||
|
|
||||||
|
const isContactRequirePremium = userFull?.isContactRequirePremium;
|
||||||
|
const paidMessagesStars = selectPeerPaidMessagesStars(global, chatId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -838,6 +845,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canShowOpenChatButton,
|
canShowOpenChatButton,
|
||||||
isContactRequirePremium,
|
isContactRequirePremium,
|
||||||
topics,
|
topics,
|
||||||
|
paidMessagesStars,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(MiddleColumn));
|
)(MiddleColumn));
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import BotAdPane from './panes/BotAdPane';
|
|||||||
import BotVerificationPane from './panes/BotVerificationPane';
|
import BotVerificationPane from './panes/BotVerificationPane';
|
||||||
import ChatReportPane from './panes/ChatReportPane';
|
import ChatReportPane from './panes/ChatReportPane';
|
||||||
import HeaderPinnedMessage from './panes/HeaderPinnedMessage';
|
import HeaderPinnedMessage from './panes/HeaderPinnedMessage';
|
||||||
|
import PaidMessageChargePane from './panes/PaidMessageChargePane';
|
||||||
|
|
||||||
import styles from './MiddleHeaderPanes.module.scss';
|
import styles from './MiddleHeaderPanes.module.scss';
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ const MiddleHeaderPanes = ({
|
|||||||
const [getChatReportState, setChatReportState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
const [getChatReportState, setChatReportState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
||||||
const [getBotAdState, setBotAdState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
const [getBotAdState, setBotAdState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
||||||
const [getBotVerificationState, setBotVerificationState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
const [getBotVerificationState, setBotVerificationState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
||||||
|
const [getPaidMessageChargeState, setPaidMessageChargeState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
||||||
|
|
||||||
const isPinnedMessagesFullWidth = isAudioPlayerRendered || !isDesktop;
|
const isPinnedMessagesFullWidth = isAudioPlayerRendered || !isDesktop;
|
||||||
|
|
||||||
@ -94,10 +96,11 @@ const MiddleHeaderPanes = ({
|
|||||||
const groupCallState = getGroupCallState();
|
const groupCallState = getGroupCallState();
|
||||||
const chatReportState = getChatReportState();
|
const chatReportState = getChatReportState();
|
||||||
const botAdState = getBotAdState();
|
const botAdState = getBotAdState();
|
||||||
|
const paidMessageState = getPaidMessageChargeState();
|
||||||
|
|
||||||
// Keep in sync with the order of the panes in the DOM
|
// Keep in sync with the order of the panes in the DOM
|
||||||
const stateArray = [audioPlayerState, groupCallState,
|
const stateArray = [audioPlayerState, groupCallState,
|
||||||
chatReportState, botVerificationState, pinnedState, botAdState];
|
chatReportState, botVerificationState, pinnedState, botAdState, paidMessageState];
|
||||||
|
|
||||||
const isFirstRender = isFirstRenderRef.current;
|
const isFirstRender = isFirstRenderRef.current;
|
||||||
const totalHeight = stateArray.reduce((acc, state) => acc + state.height, 0);
|
const totalHeight = stateArray.reduce((acc, state) => acc + state.height, 0);
|
||||||
@ -111,7 +114,7 @@ const MiddleHeaderPanes = ({
|
|||||||
'--middle-header-panes-height': `${totalHeight}px`,
|
'--middle-header-panes-height': `${totalHeight}px`,
|
||||||
});
|
});
|
||||||
}, [getAudioPlayerState, getGroupCallState, getPinnedState,
|
}, [getAudioPlayerState, getGroupCallState, getPinnedState,
|
||||||
getChatReportState, getBotAdState, getBotVerificationState]);
|
getChatReportState, getBotAdState, getBotVerificationState, getPaidMessageChargeState]);
|
||||||
|
|
||||||
if (!shouldRender) return undefined;
|
if (!shouldRender) return undefined;
|
||||||
|
|
||||||
@ -140,6 +143,10 @@ const MiddleHeaderPanes = ({
|
|||||||
peerId={chatId}
|
peerId={chatId}
|
||||||
onPaneStateChange={setBotVerificationState}
|
onPaneStateChange={setBotVerificationState}
|
||||||
/>
|
/>
|
||||||
|
<PaidMessageChargePane
|
||||||
|
peerId={chatId}
|
||||||
|
onPaneStateChange={setPaidMessageChargeState}
|
||||||
|
/>
|
||||||
<HeaderPinnedMessage
|
<HeaderPinnedMessage
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
|
|||||||
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
.button {
|
.button {
|
||||||
background: var(--pattern-color);
|
background: var(--pattern-color);
|
||||||
width: 10rem;
|
width: auto;
|
||||||
margin-top: 0.5rem;
|
margin-top: 1.0625rem;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
@ -28,8 +28,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--pattern-color);
|
background: var(--pattern-color);
|
||||||
max-width: 15rem;
|
max-width: 13.5rem;
|
||||||
padding: 0.75rem 0;
|
padding: 1.0625rem 0;
|
||||||
border-radius: 1.5rem;
|
border-radius: 1.5rem;
|
||||||
|
|
||||||
&[dir="rtl"] {
|
&[dir="rtl"] {
|
||||||
@ -60,7 +60,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
|
white-space: pre;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.starIconContainer {
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starIcon {
|
||||||
|
margin-inline-start: 0 !important;
|
||||||
|
margin-inline-end: 0.0625rem !important;
|
||||||
|
}
|
||||||
@ -3,20 +3,25 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import { getUserFirstOrLastName } from '../../global/helpers';
|
import { getUserFirstOrLastName } from '../../global/helpers';
|
||||||
import { selectTheme, selectUser } from '../../global/selectors';
|
import { selectTheme, selectUser } from '../../global/selectors';
|
||||||
|
import { formatStarsAsIcon } from '../../util/localization/format';
|
||||||
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import AnimatedIconWithPreview from '../common/AnimatedIconWithPreview';
|
import AnimatedIconWithPreview from '../common/AnimatedIconWithPreview';
|
||||||
import Icon from '../common/icons/Icon';
|
import Icon from '../common/icons/Icon';
|
||||||
|
import Sparkles from '../common/Sparkles';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
|
|
||||||
import styles from './PremiumRequiredMessage.module.scss';
|
import styles from './RequirementToContactMessage.module.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
userId: string;
|
userId: string;
|
||||||
|
paidMessagesStars?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -24,12 +29,15 @@ type StateProps = {
|
|||||||
userName?: string;
|
userName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function PremiumRequiredMessage({ patternColor, userName }: StateProps) {
|
function RequirementToContactMessage({ patternColor, userName, paidMessagesStars }: OwnProps & StateProps) {
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
const { openPremiumModal } = getActions();
|
const lang = useLang();
|
||||||
|
const { openPremiumModal, openStarsBalanceModal } = getActions();
|
||||||
|
|
||||||
const handleOpenPremiumModal = useLastCallback(() => openPremiumModal());
|
const handleOpenPremiumModal = useLastCallback(() => openPremiumModal());
|
||||||
|
|
||||||
|
const handleGetMoreStars = useLastCallback(() => { openStarsBalanceModal({}); });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<div className={styles.inner}>
|
<div className={styles.inner}>
|
||||||
@ -43,15 +51,41 @@ function PremiumRequiredMessage({ patternColor, userName }: StateProps) {
|
|||||||
<Icon name="comments-sticker" className={styles.commentsIcon} />
|
<Icon name="comments-sticker" className={styles.commentsIcon} />
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.description}>
|
<span className={styles.description}>
|
||||||
{renderText(lang('MessageLockedPremium', userName), ['simple_markdown'])}
|
{
|
||||||
|
paidMessagesStars
|
||||||
|
? lang('FirstMessageInPaidMessagesChat', {
|
||||||
|
user: userName,
|
||||||
|
amount: formatStarsAsIcon(lang,
|
||||||
|
paidMessagesStars,
|
||||||
|
{
|
||||||
|
asFont: true,
|
||||||
|
className: styles.starIcon,
|
||||||
|
containerClassName: styles.starIconContainer,
|
||||||
|
}),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
})
|
||||||
|
: renderText(oldLang('MessageLockedPremium', userName), ['simple_markdown'])
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
color="translucent-black"
|
color="translucent-black"
|
||||||
size="tiny"
|
size="default"
|
||||||
onClick={handleOpenPremiumModal}
|
pill
|
||||||
|
onClick={paidMessagesStars ? handleGetMoreStars : handleOpenPremiumModal}
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
>
|
>
|
||||||
{lang('MessagePremiumUnlock')}
|
{
|
||||||
|
paidMessagesStars
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
{lang('ButtonBuyStars')}
|
||||||
|
<Sparkles preset="button" />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: oldLang('MessagePremiumUnlock')
|
||||||
|
}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -68,5 +102,5 @@ export default memo(
|
|||||||
patternColor,
|
patternColor,
|
||||||
userName: getUserFirstOrLastName(user),
|
userName: getUserFirstOrLastName(user),
|
||||||
};
|
};
|
||||||
})(PremiumRequiredMessage),
|
})(RequirementToContactMessage),
|
||||||
);
|
);
|
||||||
@ -86,6 +86,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sendButtonStar {
|
||||||
|
margin-inline-start: 0 !important;
|
||||||
|
margin-inline-end: 0.125rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
.attachments {
|
.attachments {
|
||||||
max-height: 26rem;
|
max-height: 26rem;
|
||||||
min-height: 5rem;
|
min-height: 5rem;
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import { selectCurrentLimit } from '../../../global/selectors/limits';
|
|||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
import { validateFiles } from '../../../util/files';
|
import { validateFiles } from '../../../util/files';
|
||||||
|
import { formatStarsAsIcon } from '../../../util/localization/format';
|
||||||
import { removeAllSelections } from '../../../util/selection';
|
import { removeAllSelections } from '../../../util/selection';
|
||||||
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
||||||
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
||||||
@ -36,6 +37,7 @@ import useDerivedState from '../../../hooks/useDerivedState';
|
|||||||
import useEffectOnce from '../../../hooks/useEffectOnce';
|
import useEffectOnce from '../../../hooks/useEffectOnce';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useGetSelectionRange from '../../../hooks/useGetSelectionRange';
|
import useGetSelectionRange from '../../../hooks/useGetSelectionRange';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
|
||||||
@ -87,7 +89,9 @@ export type OwnProps = {
|
|||||||
onRemoveSymbol: VoidFunction;
|
onRemoveSymbol: VoidFunction;
|
||||||
onEmojiSelect: (emoji: string) => void;
|
onEmojiSelect: (emoji: string) => void;
|
||||||
canScheduleUntilOnline?: boolean;
|
canScheduleUntilOnline?: boolean;
|
||||||
|
canSchedule?: boolean;
|
||||||
onSendWhenOnline?: NoneToVoidFunction;
|
onSendWhenOnline?: NoneToVoidFunction;
|
||||||
|
paidMessagesStars?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -144,7 +148,9 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
onRemoveSymbol,
|
onRemoveSymbol,
|
||||||
onEmojiSelect,
|
onEmojiSelect,
|
||||||
canScheduleUntilOnline,
|
canScheduleUntilOnline,
|
||||||
|
canSchedule,
|
||||||
onSendWhenOnline,
|
onSendWhenOnline,
|
||||||
|
paidMessagesStars,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@ -152,7 +158,8 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
const svgRef = useRef<SVGSVGElement>(null);
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
const { addRecentCustomEmoji, addRecentEmoji, updateAttachmentSettings } = getActions();
|
const { addRecentCustomEmoji, addRecentEmoji, updateAttachmentSettings } = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const mainButtonRef = useRef<HTMLButtonElement | null>(null);
|
const mainButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||||
@ -426,7 +433,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
requestMutation(() => {
|
requestMutation(() => {
|
||||||
input.style.setProperty('--margin-for-scrollbar', `${width}px`);
|
input.style.setProperty('--margin-for-scrollbar', `${width}px`);
|
||||||
});
|
});
|
||||||
}, [lang, isOpen]);
|
}, [oldLang, isOpen]);
|
||||||
|
|
||||||
const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
||||||
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||||
@ -481,14 +488,15 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
|
const attachmentsLength = renderingAttachments.length;
|
||||||
if (areAllPhotos) {
|
if (areAllPhotos) {
|
||||||
title = lang(isEditing ? 'EditMessageReplacePhoto' : 'PreviewSender.SendPhoto', renderingAttachments.length, 'i');
|
title = oldLang(isEditing ? 'EditMessageReplacePhoto' : 'PreviewSender.SendPhoto', attachmentsLength, 'i');
|
||||||
} else if (areAllVideos) {
|
} else if (areAllVideos) {
|
||||||
title = lang(isEditing ? 'EditMessageReplaceVideo' : 'PreviewSender.SendVideo', renderingAttachments.length, 'i');
|
title = oldLang(isEditing ? 'EditMessageReplaceVideo' : 'PreviewSender.SendVideo', attachmentsLength, 'i');
|
||||||
} else if (areAllAudios) {
|
} else if (areAllAudios) {
|
||||||
title = lang(isEditing ? 'EditMessageReplaceAudio' : 'PreviewSender.SendAudio', renderingAttachments.length, 'i');
|
title = oldLang(isEditing ? 'EditMessageReplaceAudio' : 'PreviewSender.SendAudio', attachmentsLength, 'i');
|
||||||
} else {
|
} else {
|
||||||
title = lang(isEditing ? 'EditMessageReplaceFile' : 'PreviewSender.SendFile', renderingAttachments.length, 'i');
|
title = oldLang(isEditing ? 'EditMessageReplaceFile' : 'PreviewSender.SendFile', attachmentsLength, 'i');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderHeader() {
|
function renderHeader() {
|
||||||
@ -497,7 +505,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-header-condensed" dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className="modal-header-condensed" dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||||
<Button round color="translucent" size="smaller" ariaLabel="Cancel attachments" onClick={onClear}>
|
<Button round color="translucent" size="smaller" ariaLabel="Cancel attachments" onClick={onClear}>
|
||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -510,7 +518,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
positionX="right"
|
positionX="right"
|
||||||
>
|
>
|
||||||
{Boolean(!editingMessage) && (
|
{Boolean(!editingMessage) && (
|
||||||
<MenuItem icon="add" onClick={handleDocumentSelect}>{lang('Add')}</MenuItem>
|
<MenuItem icon="add" onClick={handleDocumentSelect}>{oldLang('Add')}</MenuItem>
|
||||||
)}
|
)}
|
||||||
{hasMedia && (
|
{hasMedia && (
|
||||||
<>
|
<>
|
||||||
@ -518,12 +526,12 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
canInvertMedia && (!isInvertedMedia ? (
|
canInvertMedia && (!isInvertedMedia ? (
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
<MenuItem icon="move-caption-up" onClick={() => setIsInvertedMedia(true)}>
|
<MenuItem icon="move-caption-up" onClick={() => setIsInvertedMedia(true)}>
|
||||||
{lang('PreviewSender.MoveTextUp')}
|
{oldLang('PreviewSender.MoveTextUp')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : (
|
) : (
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
<MenuItem icon="move-caption-down" onClick={() => setIsInvertedMedia(undefined)}>
|
<MenuItem icon="move-caption-down" onClick={() => setIsInvertedMedia(undefined)}>
|
||||||
{lang(('PreviewSender.MoveTextDown'))}
|
{oldLang(('PreviewSender.MoveTextDown'))}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -531,7 +539,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
!shouldForceAsFile && !shouldForceCompression && (isSendingCompressed ? (
|
!shouldForceAsFile && !shouldForceCompression && (isSendingCompressed ? (
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
<MenuItem icon="document" onClick={() => setShouldSendCompressed(false)}>
|
<MenuItem icon="document" onClick={() => setShouldSendCompressed(false)}>
|
||||||
{lang(isMultiple ? 'Attachment.SendAsFiles' : 'Attachment.SendAsFile')}
|
{oldLang(isMultiple ? 'Attachment.SendAsFiles' : 'Attachment.SendAsFile')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : (
|
) : (
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
@ -543,11 +551,11 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
{isSendingCompressed && hasAnySpoilerable && Boolean(!editingMessage) && (
|
{isSendingCompressed && hasAnySpoilerable && Boolean(!editingMessage) && (
|
||||||
hasSpoiler ? (
|
hasSpoiler ? (
|
||||||
<MenuItem icon="spoiler-disable" onClick={handleDisableSpoilers}>
|
<MenuItem icon="spoiler-disable" onClick={handleDisableSpoilers}>
|
||||||
{lang('Attachment.DisableSpoiler')}
|
{oldLang('Attachment.DisableSpoiler')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : (
|
) : (
|
||||||
<MenuItem icon="spoiler" onClick={handleEnableSpoilers}>
|
<MenuItem icon="spoiler" onClick={handleEnableSpoilers}>
|
||||||
{lang('Attachment.EnableSpoiler')}
|
{oldLang('Attachment.EnableSpoiler')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
@ -576,6 +584,12 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isBottomDividerShown = !areAttachmentsScrolledToBottom || !isCaptionNotScrolled;
|
const isBottomDividerShown = !areAttachmentsScrolledToBottom || !isCaptionNotScrolled;
|
||||||
|
const buttonSendCaption = paidMessagesStars ? formatStarsAsIcon(lang,
|
||||||
|
attachmentsLength * paidMessagesStars,
|
||||||
|
{
|
||||||
|
className: styles.sendButtonStar,
|
||||||
|
asFont: true,
|
||||||
|
}) : oldLang('Send');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -591,6 +605,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
forceDarkTheme && 'component-theme-dark',
|
forceDarkTheme && 'component-theme-dark',
|
||||||
)}
|
)}
|
||||||
noBackdropClose
|
noBackdropClose
|
||||||
|
isLowStackPriority
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={styles.dropTarget}
|
className={styles.dropTarget}
|
||||||
@ -599,7 +614,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
onClick={unmarkHovered}
|
onClick={unmarkHovered}
|
||||||
data-attach-description={lang('Preview.Dragging.AddItems', 10)}
|
data-attach-description={oldLang('Preview.Dragging.AddItems', 10)}
|
||||||
data-dropzone
|
data-dropzone
|
||||||
>
|
>
|
||||||
<svg className={styles.dropOutlineContainer}>
|
<svg className={styles.dropOutlineContainer}>
|
||||||
@ -684,7 +699,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
isActive={isOpen}
|
isActive={isOpen}
|
||||||
getHtml={getHtml}
|
getHtml={getHtml}
|
||||||
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
||||||
placeholder={lang('AddCaption')}
|
placeholder={oldLang('AddCaption')}
|
||||||
onUpdate={onCaptionUpdate}
|
onUpdate={onCaptionUpdate}
|
||||||
onSend={handleSendClick}
|
onSend={handleSendClick}
|
||||||
onScroll={handleCaptionScroll}
|
onScroll={handleCaptionScroll}
|
||||||
@ -700,12 +715,13 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
onClick={handleSendClick}
|
onClick={handleSendClick}
|
||||||
onContextMenu={canShowCustomSendMenu ? handleContextMenu : undefined}
|
onContextMenu={canShowCustomSendMenu ? handleContextMenu : undefined}
|
||||||
>
|
>
|
||||||
{shouldSchedule && !editingMessage ? lang('Next') : editingMessage ? lang('Save') : lang('Send')}
|
{shouldSchedule && !editingMessage ? oldLang('Next')
|
||||||
|
: editingMessage ? oldLang('Save') : buttonSendCaption}
|
||||||
</Button>
|
</Button>
|
||||||
{canShowCustomSendMenu && (
|
{canShowCustomSendMenu && (
|
||||||
<CustomSendMenu
|
<CustomSendMenu
|
||||||
isOpen={isCustomSendMenuOpen}
|
isOpen={isCustomSendMenuOpen}
|
||||||
canSchedule={isForMessage}
|
canSchedule={canSchedule && isForMessage}
|
||||||
onSendSilent={!isChatWithSelf ? handleSendSilent : undefined}
|
onSendSilent={!isChatWithSelf ? handleSendSilent : undefined}
|
||||||
onSendSchedule={handleScheduleClick}
|
onSendSchedule={handleScheduleClick}
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ChangeEvent, RefObject } from 'react';
|
import type { ChangeEvent, RefObject } from 'react';
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
getIsHeavyAnimating,
|
getIsHeavyAnimating,
|
||||||
memo, useEffect, useLayoutEffect,
|
memo, useEffect, useLayoutEffect,
|
||||||
@ -58,7 +58,7 @@ type OwnProps = {
|
|||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
getHtml: Signal<string>;
|
getHtml: Signal<string>;
|
||||||
placeholder: string;
|
placeholder: TeactNode | string;
|
||||||
timedPlaceholderLangKey?: string;
|
timedPlaceholderLangKey?: string;
|
||||||
timedPlaceholderDate?: number;
|
timedPlaceholderDate?: number;
|
||||||
forcedPlaceholder?: string;
|
forcedPlaceholder?: string;
|
||||||
@ -168,7 +168,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const absoluteContainerRef = useRef<HTMLDivElement>(null);
|
const absoluteContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
const isContextMenuOpenRef = useRef(false);
|
const isContextMenuOpenRef = useRef(false);
|
||||||
const [isTextFormatterOpen, openTextFormatter, closeTextFormatter] = useFlag();
|
const [isTextFormatterOpen, openTextFormatter, closeTextFormatter] = useFlag();
|
||||||
const [textFormatterAnchorPosition, setTextFormatterAnchorPosition] = useState<IAnchorPosition>();
|
const [textFormatterAnchorPosition, setTextFormatterAnchorPosition] = useState<IAnchorPosition>();
|
||||||
@ -561,9 +561,10 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const inputScrollerContentClass = buildClassName('input-scroller-content', isNeedPremium && 'is-need-premium');
|
const inputScrollerContentClass = buildClassName('input-scroller-content', isNeedPremium && 'is-need-premium');
|
||||||
|
const placeholderAriaLabel = typeof placeholder === 'string' ? placeholder : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={id} onClick={shouldSuppressFocus ? onSuppressedFocus : undefined} dir={lang.isRtl ? 'rtl' : undefined}>
|
<div id={id} onClick={shouldSuppressFocus ? onSuppressedFocus : undefined} dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||||
<div
|
<div
|
||||||
className={buildClassName('custom-scroll', SCROLLER_CLASS, isNeedPremium && 'is-need-premium')}
|
className={buildClassName('custom-scroll', SCROLLER_CLASS, isNeedPremium && 'is-need-premium')}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
@ -584,7 +585,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onContextMenu={IS_ANDROID ? handleAndroidContextMenu : undefined}
|
onContextMenu={IS_ANDROID ? handleAndroidContextMenu : undefined}
|
||||||
onTouchCancel={IS_ANDROID ? processSelectionWithTimeout : undefined}
|
onTouchCancel={IS_ANDROID ? processSelectionWithTimeout : undefined}
|
||||||
aria-label={placeholder}
|
aria-label={placeholderAriaLabel}
|
||||||
onFocus={!isNeedPremium ? onFocus : undefined}
|
onFocus={!isNeedPremium ? onFocus : undefined}
|
||||||
onBlur={!isNeedPremium ? onBlur : undefined}
|
onBlur={!isNeedPremium ? onBlur : undefined}
|
||||||
/>
|
/>
|
||||||
@ -604,7 +605,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
) : placeholder}
|
) : placeholder}
|
||||||
{isStoryInput && isNeedPremium && (
|
{isStoryInput && isNeedPremium && (
|
||||||
<Button className="unlock-button" size="tiny" color="adaptive" onClick={handleOpenPremiumModal}>
|
<Button className="unlock-button" size="tiny" color="adaptive" onClick={handleOpenPremiumModal}>
|
||||||
{lang('StoryRepliesLockedButton')}
|
{oldLang('StoryRepliesLockedButton')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
import { useRef, useState } from '../../../../lib/teact/teact';
|
||||||
|
import { getActions, getGlobal } from '../../../../global';
|
||||||
|
|
||||||
|
import { PAID_MESSAGES_PURPOSE } from '../../../../config';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
export default function usePaidMessageConfirmation(
|
||||||
|
starsForAllMessages: number,
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
shouldPaidMessageAutoApprove,
|
||||||
|
} = getGlobal().settings.byKey;
|
||||||
|
|
||||||
|
const [shouldAutoApprove,
|
||||||
|
setAutoApprove] = useState(Boolean(shouldPaidMessageAutoApprove));
|
||||||
|
const confirmPaymentHandlerRef = useRef<NoneToVoidFunction | undefined>(undefined);
|
||||||
|
|
||||||
|
const closeConfirmDialog = useLastCallback(() => {
|
||||||
|
getActions().closePaymentMessageConfirmDialogOpen();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleWithConfirmation = <T extends (...args: any[]) => void>(
|
||||||
|
handler: T,
|
||||||
|
...args: Parameters<T>
|
||||||
|
) => {
|
||||||
|
if (starsForAllMessages) {
|
||||||
|
const balance = getGlobal().stars?.balance.amount;
|
||||||
|
if (balance && starsForAllMessages > balance) {
|
||||||
|
getActions().openStarsBalanceModal({
|
||||||
|
topup:
|
||||||
|
{ balanceNeeded: starsForAllMessages, purpose: PAID_MESSAGES_PURPOSE },
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldPaidMessageAutoApprove && starsForAllMessages) {
|
||||||
|
confirmPaymentHandlerRef.current = () => handler(...args);
|
||||||
|
getActions().openPaymentMessageConfirmDialogOpen();
|
||||||
|
} else {
|
||||||
|
handler(...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogHandler = useLastCallback(() => {
|
||||||
|
confirmPaymentHandlerRef.current?.();
|
||||||
|
getActions().closePaymentMessageConfirmDialogOpen();
|
||||||
|
if (shouldAutoApprove) getActions().setPaidMessageAutoApprove();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeConfirmDialog,
|
||||||
|
handleWithConfirmation,
|
||||||
|
dialogHandler,
|
||||||
|
shouldAutoApprove,
|
||||||
|
setAutoApprove,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -90,6 +90,7 @@ export function groupMessages(
|
|||||||
} else if (
|
} else if (
|
||||||
nextMessage.id === firstUnreadId
|
nextMessage.id === firstUnreadId
|
||||||
|| message.senderId !== nextMessage.senderId
|
|| message.senderId !== nextMessage.senderId
|
||||||
|
|| message.paidMessageStars
|
||||||
|| message.isOutgoing !== nextMessage.isOutgoing
|
|| message.isOutgoing !== nextMessage.isOutgoing
|
||||||
|| message.postAuthorTitle !== nextMessage.postAuthorTitle
|
|| message.postAuthorTitle !== nextMessage.postAuthorTitle
|
||||||
|| (isActionMessage(message) && message.content.action?.type !== 'phoneCall')
|
|| (isActionMessage(message) && message.content.action?.type !== 'phoneCall')
|
||||||
|
|||||||
@ -301,6 +301,8 @@ type StateProps = {
|
|||||||
poll?: ApiPoll;
|
poll?: ApiPoll;
|
||||||
maxTimestamp?: number;
|
maxTimestamp?: number;
|
||||||
lastPlaybackTimestamp?: number;
|
lastPlaybackTimestamp?: number;
|
||||||
|
paidMessageStars?: number;
|
||||||
|
isChatWithUser?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MetaPosition =
|
type MetaPosition =
|
||||||
@ -421,6 +423,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
maxTimestamp,
|
maxTimestamp,
|
||||||
lastPlaybackTimestamp,
|
lastPlaybackTimestamp,
|
||||||
onIntersectPinnedMessage,
|
onIntersectPinnedMessage,
|
||||||
|
paidMessageStars,
|
||||||
|
isChatWithUser,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
toggleMessageSelection,
|
toggleMessageSelection,
|
||||||
@ -787,6 +791,10 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const withAppendix = contentClassName.includes('has-appendix');
|
const withAppendix = contentClassName.includes('has-appendix');
|
||||||
const emojiSize = getCustomEmojiSize(message.emojiOnlyCount);
|
const emojiSize = getCustomEmojiSize(message.emojiOnlyCount);
|
||||||
|
|
||||||
|
const paidMessageStarsInMeta = !isChatWithUser
|
||||||
|
? (isAlbum && paidMessageStars ? album.messages.length * paidMessageStars : paidMessageStars)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
let metaPosition!: MetaPosition;
|
let metaPosition!: MetaPosition;
|
||||||
if (phoneCall) {
|
if (phoneCall) {
|
||||||
metaPosition = 'none';
|
metaPosition = 'none';
|
||||||
@ -1019,6 +1027,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
onEffectClick={handleEffectClick}
|
onEffectClick={handleEffectClick}
|
||||||
onTranslationClick={handleTranslationClick}
|
onTranslationClick={handleTranslationClick}
|
||||||
onOpenThread={handleOpenThread}
|
onOpenThread={handleOpenThread}
|
||||||
|
paidMessageStars={paidMessageStarsInMeta}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1119,7 +1128,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{hasAnimatedEmoji && animatedCustomEmoji && (
|
{hasAnimatedEmoji && animatedCustomEmoji && (
|
||||||
<AnimatedCustomEmoji
|
<AnimatedCustomEmoji
|
||||||
customEmojiId={animatedCustomEmoji}
|
customEmojiId={animatedCustomEmoji}
|
||||||
withEffects={withAnimatedEffects && isUserId(chatId) && !effect}
|
withEffects={withAnimatedEffects && isChatWithUser && !effect}
|
||||||
isOwn={isOwn}
|
isOwn={isOwn}
|
||||||
observeIntersection={observeIntersectionForLoading}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
forceLoadPreview={isLocal}
|
forceLoadPreview={isLocal}
|
||||||
@ -1131,7 +1140,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{hasAnimatedEmoji && animatedEmoji && (
|
{hasAnimatedEmoji && animatedEmoji && (
|
||||||
<AnimatedEmoji
|
<AnimatedEmoji
|
||||||
emoji={animatedEmoji}
|
emoji={animatedEmoji}
|
||||||
withEffects={withAnimatedEffects && isUserId(chatId) && !effect}
|
withEffects={withAnimatedEffects && isChatWithUser && !effect}
|
||||||
isOwn={isOwn}
|
isOwn={isOwn}
|
||||||
observeIntersection={observeIntersectionForLoading}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
forceLoadPreview={isLocal}
|
forceLoadPreview={isLocal}
|
||||||
@ -1719,15 +1728,18 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
} = ownProps;
|
} = ownProps;
|
||||||
const {
|
const {
|
||||||
id, chatId, viaBotId, isOutgoing, forwardInfo, transcriptionId, isPinned, viaBusinessBotId, effectId,
|
id, chatId, viaBotId, isOutgoing, forwardInfo, transcriptionId, isPinned, viaBusinessBotId, effectId,
|
||||||
|
paidMessageStars,
|
||||||
} = message;
|
} = message;
|
||||||
|
|
||||||
|
const isChatWithUser = isUserId(chatId);
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
const isSystemBotChat = isSystemBot(chatId);
|
const isSystemBotChat = isSystemBot(chatId);
|
||||||
const isAnonymousForwards = isAnonymousForwardsChat(chatId);
|
const isAnonymousForwards = isAnonymousForwardsChat(chatId);
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
const isGroup = chat && isChatGroup(chat);
|
const isGroup = chat && isChatGroup(chat);
|
||||||
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
|
const chatFullInfo = !isChatWithUser ? selectChatFullInfo(global, chatId) : undefined;
|
||||||
const webPageStoryData = message.content.webPage?.story;
|
const webPageStoryData = message.content.webPage?.story;
|
||||||
const webPageStory = webPageStoryData
|
const webPageStory = webPageStoryData
|
||||||
? selectPeerStory(global, webPageStoryData.peerId, webPageStoryData.id)
|
? selectPeerStory(global, webPageStoryData.peerId, webPageStoryData.id)
|
||||||
@ -1931,6 +1943,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
poll,
|
poll,
|
||||||
maxTimestamp,
|
maxTimestamp,
|
||||||
lastPlaybackTimestamp,
|
lastPlaybackTimestamp,
|
||||||
|
paidMessageStars,
|
||||||
|
isChatWithUser,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Message));
|
)(Message));
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
cursor: var(--custom-cursor, pointer);
|
cursor: var(--custom-cursor, pointer);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
.message-price,
|
||||||
.message-time,
|
.message-time,
|
||||||
.message-imported,
|
.message-imported,
|
||||||
.message-signature,
|
.message-signature,
|
||||||
@ -26,6 +27,22 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-price-stars-container {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-price-star-icon {
|
||||||
|
margin-inline-start: 0 !important;
|
||||||
|
margin-inline-end: 0.0625rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-price {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-inline-end: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.message-replies-wrapper {
|
.message-replies-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatDateTimeToString, formatPastTimeShort, formatTime } from '../../../util/dates/dateFormat';
|
import { formatDateTimeToString, formatPastTimeShort, formatTime } from '../../../util/dates/dateFormat';
|
||||||
|
import { formatStarsAsIcon } from '../../../util/localization/format';
|
||||||
import { formatIntegerCompact } from '../../../util/textFormat';
|
import { formatIntegerCompact } from '../../../util/textFormat';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ type OwnProps = {
|
|||||||
onEffectClick: (e: React.MouseEvent<HTMLDivElement>) => void;
|
onEffectClick: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
renderQuickReactionButton?: () => TeactNode | undefined;
|
renderQuickReactionButton?: () => TeactNode | undefined;
|
||||||
onOpenThread: NoneToVoidFunction;
|
onOpenThread: NoneToVoidFunction;
|
||||||
|
paidMessageStars?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MessageMeta: FC<OwnProps> = ({
|
const MessageMeta: FC<OwnProps> = ({
|
||||||
@ -56,6 +58,7 @@ const MessageMeta: FC<OwnProps> = ({
|
|||||||
onTranslationClick,
|
onTranslationClick,
|
||||||
onEffectClick,
|
onEffectClick,
|
||||||
onOpenThread,
|
onOpenThread,
|
||||||
|
paidMessageStars,
|
||||||
}) => {
|
}) => {
|
||||||
const { showNotification } = getActions();
|
const { showNotification } = getActions();
|
||||||
|
|
||||||
@ -176,6 +179,16 @@ const MessageMeta: FC<OwnProps> = ({
|
|||||||
{signature && (
|
{signature && (
|
||||||
<span className="message-signature">{renderText(signature)}</span>
|
<span className="message-signature">{renderText(signature)}</span>
|
||||||
)}
|
)}
|
||||||
|
{paidMessageStars && (
|
||||||
|
<span className="message-price">{
|
||||||
|
formatStarsAsIcon(lang, paidMessageStars, {
|
||||||
|
asFont: true,
|
||||||
|
className: 'message-price-star-icon',
|
||||||
|
containerClassName: 'message-price-stars-container',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<span className="message-time" title={dateTitle} onMouseEnter={markActivated}>
|
<span className="message-time" title={dateTitle} onMouseEnter={markActivated}>
|
||||||
{message.forwardInfo?.isImported && (
|
{message.forwardInfo?.isImported && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
@include mixins.header-pane;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding-inline: 1rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.375rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageStars {
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
padding-inline: 0.25rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageStarIcon {
|
||||||
|
margin-inline-start: 0 !important;
|
||||||
|
margin-inline-end: 0.125rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkBox {
|
||||||
|
margin-top: 0.375rem;
|
||||||
|
margin-inline: -1.125rem;
|
||||||
|
padding-inline-start: 3.5rem;
|
||||||
|
}
|
||||||
144
src/components/middle/panes/PaidMessageChargePane.tsx
Normal file
144
src/components/middle/panes/PaidMessageChargePane.tsx
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiChat,
|
||||||
|
} from '../../../api/types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getPeerTitle,
|
||||||
|
} from '../../../global/helpers';
|
||||||
|
import {
|
||||||
|
selectChat,
|
||||||
|
selectUserFullInfo,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
import { formatStarsAsIcon } from '../../../util/localization/format';
|
||||||
|
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
// import useTimeout from '../../../hooks/schedulers/useTimeout';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useHeaderPane, { type PaneState } from '../hooks/useHeaderPane';
|
||||||
|
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Checkbox from '../../ui/Checkbox';
|
||||||
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
|
|
||||||
|
// import CustomEmoji from '../../common/CustomEmoji';
|
||||||
|
import styles from './PaidMessageChargePane.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
peerId: string;
|
||||||
|
onPaneStateChange?: (state: PaneState) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
chargedPaidMessageStars?: number;
|
||||||
|
chat?: ApiChat;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PaidMessageChargePane: FC<OwnProps & StateProps> = ({
|
||||||
|
chargedPaidMessageStars,
|
||||||
|
chat,
|
||||||
|
onPaneStateChange,
|
||||||
|
peerId,
|
||||||
|
}) => {
|
||||||
|
const isOpen = Boolean(chargedPaidMessageStars);
|
||||||
|
const lang = useLang();
|
||||||
|
const [isRemoveFeeDialogOpen, openRemoveFeeDialog, closeRemoveFeeDialog] = useFlag();
|
||||||
|
const [shouldRefoundStars, setShouldRefoundStars] = useFlag(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
addNoPaidMessagesException,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const { ref, shouldRender } = useHeaderPane({
|
||||||
|
isOpen,
|
||||||
|
onStateChange: onPaneStateChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRemoveFee = useLastCallback(() => {
|
||||||
|
openRemoveFeeDialog();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleConfirmRemoveFee = useLastCallback(() => {
|
||||||
|
addNoPaidMessagesException({ userId: peerId, shouldRefundCharged: shouldRefoundStars });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!shouldRender || !chargedPaidMessageStars) return undefined;
|
||||||
|
|
||||||
|
const peerName = chat ? getPeerTitle(lang, chat) : undefined;
|
||||||
|
|
||||||
|
const message = lang('PaneMessagePaidMessageCharge', {
|
||||||
|
peer: peerName,
|
||||||
|
amount: formatStarsAsIcon(lang,
|
||||||
|
chargedPaidMessageStars,
|
||||||
|
{ asFont: true, className: styles.messageStarIcon, containerClassName: styles.messageStars }),
|
||||||
|
}, {
|
||||||
|
withMarkdown: true,
|
||||||
|
withNodes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogMessage = lang('ConfirmDialogMessageRemoveFee', {
|
||||||
|
peer: peerName,
|
||||||
|
}, {
|
||||||
|
withMarkdown: true,
|
||||||
|
withNodes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkBoxTitle = lang('ConfirmDialogRemoveFeeRefundStars', {
|
||||||
|
amount: chargedPaidMessageStars,
|
||||||
|
}, {
|
||||||
|
withMarkdown: true,
|
||||||
|
withNodes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={styles.root}>
|
||||||
|
<div className={styles.message}>
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
isText
|
||||||
|
noForcedUpperCase
|
||||||
|
pill
|
||||||
|
fluid
|
||||||
|
size="tiny"
|
||||||
|
className={styles.button}
|
||||||
|
onClick={handleRemoveFee}
|
||||||
|
>
|
||||||
|
{lang('RemoveFeeTitle')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isRemoveFeeDialogOpen}
|
||||||
|
onClose={closeRemoveFeeDialog}
|
||||||
|
title={lang('RemoveFeeTitle')}
|
||||||
|
confirmLabel={lang('ConfirmRemoveMessageFee')}
|
||||||
|
confirmHandler={handleConfirmRemoveFee}
|
||||||
|
>
|
||||||
|
{dialogMessage}
|
||||||
|
<Checkbox
|
||||||
|
className={styles.checkBox}
|
||||||
|
label={checkBoxTitle}
|
||||||
|
checked={shouldRefoundStars}
|
||||||
|
onCheck={setShouldRefoundStars}
|
||||||
|
/>
|
||||||
|
</ConfirmDialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { peerId }): StateProps => {
|
||||||
|
const chat = selectChat(global, peerId);
|
||||||
|
const peerFullInfo = selectUserFullInfo(global, peerId);
|
||||||
|
const chargedPaidMessageStars = peerFullInfo?.settings?.chargedPaidMessageStars;
|
||||||
|
|
||||||
|
return {
|
||||||
|
chargedPaidMessageStars,
|
||||||
|
chat,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(PaidMessageChargePane));
|
||||||
@ -10,9 +10,14 @@ import {
|
|||||||
type ApiMessage, type ApiPeer, type ApiStarsAmount, MAIN_THREAD_ID,
|
type ApiMessage, type ApiPeer, type ApiStarsAmount, MAIN_THREAD_ID,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
import { getPeerTitle } from '../../../global/helpers';
|
import {
|
||||||
|
getPeerTitle,
|
||||||
|
} from '../../../global/helpers';
|
||||||
import { isApiPeerUser } from '../../../global/helpers/peers';
|
import { isApiPeerUser } from '../../../global/helpers/peers';
|
||||||
import { selectPeer, selectTabState, selectTheme } from '../../../global/selectors';
|
import {
|
||||||
|
selectPeer, selectPeerPaidMessagesStars,
|
||||||
|
selectTabState, selectTheme,
|
||||||
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import buildStyle from '../../../util/buildStyle';
|
import buildStyle from '../../../util/buildStyle';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
@ -49,6 +54,7 @@ export type StateProps = {
|
|||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
isPaymentFormLoading?: boolean;
|
isPaymentFormLoading?: boolean;
|
||||||
starBalance?: ApiStarsAmount;
|
starBalance?: ApiStarsAmount;
|
||||||
|
paidMessagesStars?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIMIT_DISPLAY_THRESHOLD = 50;
|
const LIMIT_DISPLAY_THRESHOLD = 50;
|
||||||
@ -67,6 +73,7 @@ function GiftComposer({
|
|||||||
currentUserId,
|
currentUserId,
|
||||||
isPaymentFormLoading,
|
isPaymentFormLoading,
|
||||||
starBalance,
|
starBalance,
|
||||||
|
paidMessagesStars,
|
||||||
}: OwnProps & StateProps) {
|
}: OwnProps & StateProps) {
|
||||||
const {
|
const {
|
||||||
sendStarGift, sendPremiumGiftByStars, openInvoice, openGiftUpgradeModal, openStarsBalanceModal,
|
sendStarGift, sendPremiumGiftByStars, openInvoice, openGiftUpgradeModal, openStarsBalanceModal,
|
||||||
@ -202,14 +209,19 @@ function GiftComposer({
|
|||||||
const title = getPeerTitle(lang, peer!)!;
|
const title = getPeerTitle(lang, peer!)!;
|
||||||
return (
|
return (
|
||||||
<div className={styles.optionsSection}>
|
<div className={styles.optionsSection}>
|
||||||
<TextArea
|
|
||||||
className={styles.messageInput}
|
{!paidMessagesStars && (
|
||||||
onChange={handleGiftMessageChange}
|
<TextArea
|
||||||
value={giftMessage}
|
className={styles.messageInput}
|
||||||
label={lang('GiftMessagePlaceholder')}
|
onChange={handleGiftMessageChange}
|
||||||
maxLength={captionLimit}
|
value={giftMessage}
|
||||||
maxLengthIndicator={symbolsLeft && symbolsLeft < LIMIT_DISPLAY_THRESHOLD ? symbolsLeft.toString() : undefined}
|
label={lang('GiftMessagePlaceholder')}
|
||||||
/>
|
maxLength={captionLimit}
|
||||||
|
maxLengthIndicator={
|
||||||
|
symbolsLeft && symbolsLeft < LIMIT_DISPLAY_THRESHOLD ? symbolsLeft.toString() : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{canUseStarsPayment && (
|
{canUseStarsPayment && (
|
||||||
<ListItem className={styles.switcher} narrow ripple onClick={toggleShouldPayByStars}>
|
<ListItem className={styles.switcher} narrow ripple onClick={toggleShouldPayByStars}>
|
||||||
@ -377,6 +389,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
backgroundColor,
|
backgroundColor,
|
||||||
} = global.settings.themes[theme] || {};
|
} = global.settings.themes[theme] || {};
|
||||||
const peer = selectPeer(global, peerId);
|
const peer = selectPeer(global, peerId);
|
||||||
|
const paidMessagesStars = selectPeerPaidMessagesStars(global, peerId);
|
||||||
|
|
||||||
const tabState = selectTabState(global);
|
const tabState = selectTabState(global);
|
||||||
|
|
||||||
@ -391,6 +404,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
captionLimit: global.appConfig?.starGiftMaxMessageLength,
|
captionLimit: global.appConfig?.starGiftMaxMessageLength,
|
||||||
currentUserId: global.currentUserId,
|
currentUserId: global.currentUserId,
|
||||||
isPaymentFormLoading: tabState.isPaymentFormLoading,
|
isPaymentFormLoading: tabState.isPaymentFormLoading,
|
||||||
|
paidMessagesStars,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(GiftComposer));
|
)(GiftComposer));
|
||||||
|
|||||||
@ -55,7 +55,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.giftTitle {
|
.giftTitle {
|
||||||
font-weight: 500;
|
font-weight: var(--font-weight-medium);
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
|
|||||||
@ -2,45 +2,78 @@ import React, {
|
|||||||
type FC,
|
type FC,
|
||||||
memo, useEffect,
|
memo, useEffect,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal } from '../../../global';
|
import {
|
||||||
|
getActions, getGlobal, withGlobal,
|
||||||
|
} from '../../../global';
|
||||||
|
|
||||||
import type { TabState } from '../../../global/types';
|
import type { TabState } from '../../../global/types';
|
||||||
import type { ThreadId } from '../../../types';
|
import type { ThreadId } from '../../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
|
||||||
import { getPeerTitle } from '../../../global/helpers';
|
import {
|
||||||
import { selectPeer } from '../../../global/selectors';
|
getPeerTitle,
|
||||||
|
} from '../../../global/helpers';
|
||||||
|
import {
|
||||||
|
selectPeer, selectTabState,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
import usePaidMessageConfirmation from '../../middle/composer/hooks/usePaidMessageConfirmation';
|
||||||
|
|
||||||
|
import PaymentMessageConfirmDialog from '../../common/PaymentMessageConfirmDialog';
|
||||||
import RecipientPicker from '../../common/RecipientPicker';
|
import RecipientPicker from '../../common/RecipientPicker';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
modal: TabState['sharePreparedMessageModal'];
|
modal: TabState['sharePreparedMessageModal'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const SharePreparedMessageModal: FC<OwnProps> = ({
|
type StateProps = {
|
||||||
modal,
|
isPaymentMessageConfirmDialogOpen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SendParams = {
|
||||||
|
peerName?: string;
|
||||||
|
starsForSendMessage: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SharePreparedMessageModal: FC<OwnProps & StateProps> = ({
|
||||||
|
modal, isPaymentMessageConfirmDialogOpen,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
closeSharePreparedMessageModal,
|
closeSharePreparedMessageModal,
|
||||||
sendInlineBotResult,
|
sendInlineBotResult,
|
||||||
sendWebAppEvent,
|
sendWebAppEvent,
|
||||||
showNotification,
|
showNotification,
|
||||||
|
updateSharePreparedMessageModalSendArgs,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const isOpen = Boolean(modal);
|
const isOpen = Boolean(modal);
|
||||||
|
|
||||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
markIsShown();
|
markIsShown();
|
||||||
}
|
}
|
||||||
}, [isOpen, markIsShown]);
|
}, [isOpen, markIsShown]);
|
||||||
|
|
||||||
const { message, filter, webAppKey } = modal || {};
|
const {
|
||||||
|
message, filter, webAppKey, pendingSendArgs,
|
||||||
|
} = modal || {};
|
||||||
|
|
||||||
|
const {
|
||||||
|
starsForSendMessage,
|
||||||
|
} = pendingSendArgs || {};
|
||||||
|
|
||||||
|
const {
|
||||||
|
closeConfirmDialog: closeConfirmModalPayForMessage,
|
||||||
|
dialogHandler: paymentMessageConfirmDialogHandler,
|
||||||
|
shouldAutoApprove: shouldPaidMessageAutoApprove,
|
||||||
|
setAutoApprove: setShouldPaidMessageAutoApprove,
|
||||||
|
handleWithConfirmation: handleActionWithPaymentConfirmation,
|
||||||
|
} = usePaidMessageConfirmation(starsForSendMessage || 0);
|
||||||
|
|
||||||
const handleClose = useLastCallback(() => {
|
const handleClose = useLastCallback(() => {
|
||||||
closeSharePreparedMessageModal();
|
closeSharePreparedMessageModal();
|
||||||
@ -55,7 +88,7 @@ const SharePreparedMessageModal: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSelectRecipient = useLastCallback((id: string, threadId?: ThreadId) => {
|
const handleSend = useLastCallback((id: string, threadId?: ThreadId) => {
|
||||||
if (message && webAppKey) {
|
if (message && webAppKey) {
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
const peer = selectPeer(global, id);
|
const peer = selectPeer(global, id);
|
||||||
@ -65,33 +98,82 @@ const SharePreparedMessageModal: FC<OwnProps> = ({
|
|||||||
id: message.result.id,
|
id: message.result.id,
|
||||||
queryId: message.result.queryId,
|
queryId: message.result.queryId,
|
||||||
});
|
});
|
||||||
|
if (!starsForSendMessage) {
|
||||||
|
showNotification({
|
||||||
|
message: lang('BotSharedToOne', getPeerTitle(lang, peer!)),
|
||||||
|
});
|
||||||
|
}
|
||||||
sendWebAppEvent({
|
sendWebAppEvent({
|
||||||
webAppKey,
|
webAppKey,
|
||||||
event: {
|
event: {
|
||||||
eventType: 'prepared_message_sent',
|
eventType: 'prepared_message_sent',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
showNotification({
|
|
||||||
message: lang('BotSharedToOne', getPeerTitle(lang, peer!)),
|
|
||||||
});
|
|
||||||
closeSharePreparedMessageModal();
|
closeSharePreparedMessageModal();
|
||||||
|
updateSharePreparedMessageModalSendArgs({ args: undefined });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSelectRecipient = useLastCallback((id: string, threadId?: ThreadId) => {
|
||||||
|
updateSharePreparedMessageModalSendArgs({ args: { peerId: id, threadId } });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSendWithPaymentConformation = useLastCallback(() => {
|
||||||
|
if (pendingSendArgs) {
|
||||||
|
handleActionWithPaymentConfirmation(handleSend, pendingSendArgs.peerId, pendingSendArgs.threadId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClosePaymentMessageConfirmDialog = useLastCallback(() => {
|
||||||
|
closeConfirmModalPayForMessage();
|
||||||
|
updateSharePreparedMessageModalSendArgs({ args: undefined });
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pendingSendArgs) {
|
||||||
|
handleSendWithPaymentConformation();
|
||||||
|
}
|
||||||
|
}, [pendingSendArgs]);
|
||||||
|
|
||||||
|
const global = getGlobal();
|
||||||
|
const peer = pendingSendArgs ? selectPeer(global, pendingSendArgs.peerId) : undefined;
|
||||||
|
const peerName = peer ? getPeerTitle(lang, peer) : undefined;
|
||||||
|
|
||||||
if (!isOpen && !isShown) {
|
if (!isOpen && !isShown) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecipientPicker
|
<>
|
||||||
isOpen={isOpen}
|
<RecipientPicker
|
||||||
searchPlaceholder={lang('Search')}
|
isOpen={isOpen}
|
||||||
filter={filter}
|
searchPlaceholder={lang('Search')}
|
||||||
onSelectRecipient={handleSelectRecipient}
|
filter={filter}
|
||||||
onClose={handleClose}
|
onSelectRecipient={handleSelectRecipient}
|
||||||
onCloseAnimationEnd={unmarkIsShown}
|
onClose={handleClose}
|
||||||
/>
|
onCloseAnimationEnd={unmarkIsShown}
|
||||||
|
isLowStackPriority
|
||||||
|
/>
|
||||||
|
<PaymentMessageConfirmDialog
|
||||||
|
isOpen={isPaymentMessageConfirmDialogOpen}
|
||||||
|
onClose={handleClosePaymentMessageConfirmDialog}
|
||||||
|
userName={peerName}
|
||||||
|
messagePriceInStars={starsForSendMessage || 0}
|
||||||
|
messagesCount={1}
|
||||||
|
shouldAutoApprove={shouldPaidMessageAutoApprove}
|
||||||
|
setAutoApprove={setShouldPaidMessageAutoApprove}
|
||||||
|
confirmHandler={paymentMessageConfirmDialogHandler}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(SharePreparedMessageModal);
|
export default memo(withGlobal(
|
||||||
|
(global): StateProps => {
|
||||||
|
const tabState = selectTabState(global);
|
||||||
|
const { isPaymentMessageConfirmDialogOpen } = tabState;
|
||||||
|
return {
|
||||||
|
isPaymentMessageConfirmDialogOpen,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(SharePreparedMessageModal));
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import type { ApiStarTopupOption } from '../../../api/types';
|
|||||||
import type { GlobalState, TabState } from '../../../global/types';
|
import type { GlobalState, TabState } from '../../../global/types';
|
||||||
import type { RegularLangKey } from '../../../types/language';
|
import type { RegularLangKey } from '../../../types/language';
|
||||||
|
|
||||||
|
import { PAID_MESSAGES_PURPOSE } from '../../../config';
|
||||||
import { getChatTitle, getPeerTitle, getUserFullName } from '../../../global/helpers';
|
import { getChatTitle, getPeerTitle, getUserFullName } from '../../../global/helpers';
|
||||||
import { selectChat, selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors';
|
import { selectChat, selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
@ -108,6 +109,13 @@ const StarsBalanceModal = ({
|
|||||||
return oldLang('StarsNeededTextLink');
|
return oldLang('StarsNeededTextLink');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (topup?.purpose === PAID_MESSAGES_PURPOSE) {
|
||||||
|
return lang('StarsNeededTextSendPaidMessages', undefined, {
|
||||||
|
withMarkdown: true,
|
||||||
|
withNodes: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [originReaction, originStarsPayment, originGift, topup?.purpose, lang, oldLang]);
|
}, [originReaction, originStarsPayment, originGift, topup?.purpose, lang, oldLang]);
|
||||||
|
|
||||||
|
|||||||
@ -2,27 +2,40 @@ import type { ApiStarsAmount, ApiStarsTransaction } from '../../../../api/types'
|
|||||||
import type { OldLangFn } from '../../../../hooks/useOldLang';
|
import type { OldLangFn } from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
import { buildStarsTransactionCustomPeer } from '../../../../global/helpers/payments';
|
import { buildStarsTransactionCustomPeer } from '../../../../global/helpers/payments';
|
||||||
|
import {
|
||||||
|
type LangFn,
|
||||||
|
} from '../../../../util/localization';
|
||||||
import { formatPercent } from '../../../../util/textFormat';
|
import { formatPercent } from '../../../../util/textFormat';
|
||||||
|
|
||||||
export function getTransactionTitle(lang: OldLangFn, transaction: ApiStarsTransaction) {
|
export function getTransactionTitle(oldLang: OldLangFn, lang: LangFn, transaction: ApiStarsTransaction) {
|
||||||
if (transaction.starRefCommision) {
|
if (transaction.paidMessages) {
|
||||||
return lang('StarTransactionCommission', formatPercent(transaction.starRefCommision));
|
return lang(
|
||||||
|
'PaidMessageTransaction',
|
||||||
|
{ count: transaction.paidMessages },
|
||||||
|
{
|
||||||
|
withNodes: true,
|
||||||
|
pluralValue: transaction.paidMessages,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (transaction.isGiftUpgrade) return lang('Gift2TransactionUpgraded');
|
if (transaction.starRefCommision) {
|
||||||
if (transaction.extendedMedia) return lang('StarMediaPurchase');
|
return oldLang('StarTransactionCommission', formatPercent(transaction.starRefCommision));
|
||||||
if (transaction.subscriptionPeriod) return transaction.title || lang('StarSubscriptionPurchase');
|
}
|
||||||
if (transaction.isReaction) return lang('StarsReactionsSent');
|
if (transaction.isGiftUpgrade) return oldLang('Gift2TransactionUpgraded');
|
||||||
if (transaction.giveawayPostId) return lang('StarsGiveawayPrizeReceived');
|
if (transaction.extendedMedia) return oldLang('StarMediaPurchase');
|
||||||
if (transaction.isMyGift) return lang('StarsGiftSent');
|
if (transaction.subscriptionPeriod) return transaction.title || oldLang('StarSubscriptionPurchase');
|
||||||
if (transaction.isGift) return lang('StarsGiftReceived');
|
if (transaction.isReaction) return oldLang('StarsReactionsSent');
|
||||||
|
if (transaction.giveawayPostId) return oldLang('StarsGiveawayPrizeReceived');
|
||||||
|
if (transaction.isMyGift) return oldLang('StarsGiftSent');
|
||||||
|
if (transaction.isGift) return oldLang('StarsGiftReceived');
|
||||||
if (transaction.starGift) {
|
if (transaction.starGift) {
|
||||||
return isNegativeStarsAmount(transaction.stars) ? lang('Gift2TransactionSent') : lang('Gift2ConvertedTitle');
|
return isNegativeStarsAmount(transaction.stars) ? oldLang('Gift2TransactionSent') : oldLang('Gift2ConvertedTitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
const customPeer = (transaction.peer && transaction.peer.type !== 'peer'
|
const customPeer = (transaction.peer && transaction.peer.type !== 'peer'
|
||||||
&& buildStarsTransactionCustomPeer(transaction.peer)) || undefined;
|
&& buildStarsTransactionCustomPeer(transaction.peer)) || undefined;
|
||||||
|
|
||||||
if (customPeer) return customPeer.title || lang(customPeer.titleKey!);
|
if (customPeer) return customPeer.title || oldLang(customPeer.titleKey!);
|
||||||
|
|
||||||
return transaction.title;
|
return transaction.title;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,7 +64,7 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
|
|||||||
const giftSticker = starGift && getStickerFromGift(starGift);
|
const giftSticker = starGift && getStickerFromGift(starGift);
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
let title = getTransactionTitle(oldLang, transaction);
|
let title = getTransactionTitle(oldLang, lang, transaction);
|
||||||
let description;
|
let description;
|
||||||
let status: string | undefined;
|
let status: string | undefined;
|
||||||
let avatarPeer: ApiPeer | CustomPeer | undefined;
|
let avatarPeer: ApiPeer | CustomPeer | undefined;
|
||||||
@ -105,7 +105,7 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
|
|||||||
avatarPeer,
|
avatarPeer,
|
||||||
status,
|
status,
|
||||||
};
|
};
|
||||||
}, [oldLang, peer, transaction]);
|
}, [oldLang, lang, peer, transaction]);
|
||||||
|
|
||||||
const previewContent = useMemo(() => {
|
const previewContent = useMemo(() => {
|
||||||
if (isUniqueGift) {
|
if (isUniqueGift) {
|
||||||
|
|||||||
@ -64,6 +64,16 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.totalStars {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starIcon {
|
||||||
|
line-height: 1 !important;
|
||||||
|
margin-inline-start: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-block: 0.5rem;
|
margin-block: 0.5rem;
|
||||||
|
|||||||
@ -10,7 +10,10 @@ import type { TabState } from '../../../../global/types';
|
|||||||
import { MediaViewerOrigin } from '../../../../types';
|
import { MediaViewerOrigin } from '../../../../types';
|
||||||
|
|
||||||
import { getMessageLink } from '../../../../global/helpers';
|
import { getMessageLink } from '../../../../global/helpers';
|
||||||
import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments';
|
import {
|
||||||
|
buildStarsTransactionCustomPeer,
|
||||||
|
formatStarsTransactionAmount,
|
||||||
|
} from '../../../../global/helpers/payments';
|
||||||
import {
|
import {
|
||||||
selectCanPlayAnimatedEmojis,
|
selectCanPlayAnimatedEmojis,
|
||||||
selectGiftStickerForStars,
|
selectGiftStickerForStars,
|
||||||
@ -19,6 +22,8 @@ import {
|
|||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import { copyTextToClipboard } from '../../../../util/clipboard';
|
import { copyTextToClipboard } from '../../../../util/clipboard';
|
||||||
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||||
|
import { formatStarsAsIcon } from '../../../../util/localization/format';
|
||||||
|
import { formatPercent } from '../../../../util/textFormat';
|
||||||
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
|
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
|
||||||
import { getTransactionTitle, isNegativeStarsAmount } from '../helpers/transaction';
|
import { getTransactionTitle, isNegativeStarsAmount } from '../helpers/transaction';
|
||||||
|
|
||||||
@ -48,10 +53,11 @@ type StateProps = {
|
|||||||
peer?: ApiPeer;
|
peer?: ApiPeer;
|
||||||
canPlayAnimatedEmojis?: boolean;
|
canPlayAnimatedEmojis?: boolean;
|
||||||
topSticker?: ApiSticker;
|
topSticker?: ApiSticker;
|
||||||
|
paidMessageCommission?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||||
modal, peer, canPlayAnimatedEmojis, topSticker,
|
modal, peer, canPlayAnimatedEmojis, topSticker, paidMessageCommission,
|
||||||
}) => {
|
}) => {
|
||||||
const { showNotification, openMediaViewer, closeStarsTransactionModal } = getActions();
|
const { showNotification, openMediaViewer, closeStarsTransactionModal } = getActions();
|
||||||
|
|
||||||
@ -90,7 +96,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
const peerId = transaction.peer?.type === 'peer' ? transaction.peer.id : undefined;
|
const peerId = transaction.peer?.type === 'peer' ? transaction.peer.id : undefined;
|
||||||
const toName = transaction.peer && oldLang(getStarsPeerTitleKey(transaction.peer));
|
const toName = transaction.peer && oldLang(getStarsPeerTitleKey(transaction.peer));
|
||||||
|
|
||||||
const title = getTransactionTitle(oldLang, transaction);
|
const title = getTransactionTitle(oldLang, lang, transaction);
|
||||||
|
|
||||||
const messageLink = peer && transaction.messageId && !isGiftUpgrade
|
const messageLink = peer && transaction.messageId && !isGiftUpgrade
|
||||||
? getMessageLink(peer, undefined, transaction.messageId) : undefined;
|
? getMessageLink(peer, undefined, transaction.messageId) : undefined;
|
||||||
@ -163,12 +169,25 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
<StarIcon type="gold" size="middle" />
|
<StarIcon type="gold" size="middle" />
|
||||||
</p>
|
</p>
|
||||||
|
{transaction.paidMessages && transaction.starRefCommision && paidMessageCommission
|
||||||
|
&& (
|
||||||
|
<p className={styles.description}>
|
||||||
|
{lang(
|
||||||
|
'PaidMessageTransactionDescription',
|
||||||
|
{ percent: formatPercent(paidMessageCommission / 10) },
|
||||||
|
{
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const tableData: TableData = [];
|
const tableData: TableData = [];
|
||||||
|
|
||||||
if (transaction.starRefCommision) {
|
if (transaction && !transaction.paidMessages) {
|
||||||
tableData.push([
|
tableData.push([
|
||||||
oldLang('StarsTransaction.StarRefReason.Title'),
|
oldLang('StarsTransaction.StarRefReason.Title'),
|
||||||
oldLang('StarsTransaction.StarRefReason.Program'),
|
oldLang('StarsTransaction.StarRefReason.Program'),
|
||||||
@ -187,7 +206,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
peerLabel = oldLang('Stars.Transaction.GiftFrom');
|
peerLabel = oldLang('Stars.Transaction.GiftFrom');
|
||||||
} else if (isNegativeStarsAmount(stars) || transaction.isMyGift) {
|
} else if (isNegativeStarsAmount(stars) || transaction.isMyGift) {
|
||||||
peerLabel = oldLang('Stars.Transaction.To');
|
peerLabel = oldLang('Stars.Transaction.To');
|
||||||
} else if (transaction.starRefCommision) {
|
} else if (transaction.starRefCommision && !transaction.paidMessages) {
|
||||||
peerLabel = oldLang('StarsTransaction.StarRefReason.Miniapp');
|
peerLabel = oldLang('StarsTransaction.StarRefReason.Miniapp');
|
||||||
} else if (peerId) {
|
} else if (peerId) {
|
||||||
peerLabel = oldLang('Star.Transaction.From');
|
peerLabel = oldLang('Star.Transaction.From');
|
||||||
@ -200,6 +219,15 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
peerId ? { chatId: peerId } : toName || '',
|
peerId ? { chatId: peerId } : toName || '',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (transaction.starRefCommision && transaction.paidMessages) {
|
||||||
|
tableData.push([
|
||||||
|
lang('PaidMessageTransactionTotal'),
|
||||||
|
formatStarsAsIcon(lang,
|
||||||
|
transaction.stars.amount / ((100 - transaction.starRefCommision) / 100),
|
||||||
|
{ asFont: false, className: styles.starIcon, containerClassName: styles.totalStars }),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (messageLink) {
|
if (messageLink) {
|
||||||
tableData.push([oldLang('Stars.Transaction.Reaction.Post'), <SafeLink url={messageLink} text={messageLink} />]);
|
tableData.push([oldLang('Stars.Transaction.Reaction.Post'), <SafeLink url={messageLink} text={messageLink} />]);
|
||||||
}
|
}
|
||||||
@ -252,7 +280,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
tableData,
|
tableData,
|
||||||
footer,
|
footer,
|
||||||
};
|
};
|
||||||
}, [transaction, oldLang, lang, peer, canPlayAnimatedEmojis, topSticker]);
|
}, [transaction, oldLang, lang, peer, canPlayAnimatedEmojis, topSticker, paidMessageCommission]);
|
||||||
|
|
||||||
const prevModalData = usePrevious(starModalData);
|
const prevModalData = usePrevious(starModalData);
|
||||||
const renderingModalData = prevModalData || starModalData;
|
const renderingModalData = prevModalData || starModalData;
|
||||||
@ -275,6 +303,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { modal }): StateProps => {
|
(global, { modal }): StateProps => {
|
||||||
const peerId = modal?.transaction?.peer?.type === 'peer' && modal.transaction.peer.id;
|
const peerId = modal?.transaction?.peer?.type === 'peer' && modal.transaction.peer.id;
|
||||||
const peer = peerId ? selectPeer(global, peerId) : undefined;
|
const peer = peerId ? selectPeer(global, peerId) : undefined;
|
||||||
|
const paidMessageCommission = global.appConfig?.starsPaidMessageCommissionPermille;
|
||||||
|
|
||||||
const starCount = modal?.transaction.stars;
|
const starCount = modal?.transaction.stars;
|
||||||
const starsGiftSticker = modal?.transaction.isGift && selectGiftStickerForStars(global, starCount?.amount);
|
const starsGiftSticker = modal?.transaction.isGift && selectGiftStickerForStars(global, starCount?.amount);
|
||||||
@ -283,6 +312,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
peer,
|
peer,
|
||||||
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
||||||
topSticker: starsGiftSticker,
|
topSticker: starsGiftSticker,
|
||||||
|
paidMessageCommission,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(StarsTransactionModal));
|
)(StarsTransactionModal));
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
selectChat,
|
selectChat,
|
||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
selectPeer,
|
selectPeer,
|
||||||
|
selectPeerPaidMessagesStars,
|
||||||
selectPeerStory,
|
selectPeerStory,
|
||||||
selectPerformanceSettingsValue,
|
selectPerformanceSettingsValue,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
@ -30,6 +31,7 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
||||||
import { formatMediaDuration, formatRelativePastTime } from '../../util/dates/dateFormat';
|
import { formatMediaDuration, formatRelativePastTime } from '../../util/dates/dateFormat';
|
||||||
import download from '../../util/download';
|
import download from '../../util/download';
|
||||||
|
import { formatStarsAsIcon } from '../../util/localization/format';
|
||||||
import { round } from '../../util/math';
|
import { round } from '../../util/math';
|
||||||
import { getServerTime } from '../../util/serverTime';
|
import { getServerTime } from '../../util/serverTime';
|
||||||
import { IS_SAFARI } from '../../util/windowEnvironment';
|
import { IS_SAFARI } from '../../util/windowEnvironment';
|
||||||
@ -43,6 +45,7 @@ import useCanvasBlur from '../../hooks/useCanvasBlur';
|
|||||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLongPress from '../../hooks/useLongPress';
|
import useLongPress from '../../hooks/useLongPress';
|
||||||
import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated';
|
import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated';
|
||||||
@ -99,6 +102,7 @@ interface StateProps {
|
|||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
stealthMode: ApiStealthMode;
|
stealthMode: ApiStealthMode;
|
||||||
withHeaderAnimation?: boolean;
|
withHeaderAnimation?: boolean;
|
||||||
|
paidMessagesStars?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VIDEO_MIN_READY_STATE = IS_SAFARI ? 4 : 3;
|
const VIDEO_MIN_READY_STATE = IS_SAFARI ? 4 : 3;
|
||||||
@ -131,6 +135,7 @@ function Story({
|
|||||||
onDelete,
|
onDelete,
|
||||||
onClose,
|
onClose,
|
||||||
onReport,
|
onReport,
|
||||||
|
paidMessagesStars,
|
||||||
}: OwnProps & StateProps) {
|
}: OwnProps & StateProps) {
|
||||||
const {
|
const {
|
||||||
viewStory,
|
viewStory,
|
||||||
@ -151,7 +156,8 @@ function Story({
|
|||||||
} = getActions();
|
} = getActions();
|
||||||
const serverTime = getServerTime();
|
const serverTime = getServerTime();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
const [isComposerHasFocus, markComposerHasFocus, unmarkComposerHasFocus] = useFlag(false);
|
const [isComposerHasFocus, markComposerHasFocus, unmarkComposerHasFocus] = useFlag(false);
|
||||||
const [isStoryPlaybackRequested, playStory, pauseStory] = useFlag(false);
|
const [isStoryPlaybackRequested, playStory, pauseStory] = useFlag(false);
|
||||||
@ -199,7 +205,7 @@ function Story({
|
|||||||
isOut && (story!.date + viewersExpirePeriod) < getServerTime(),
|
isOut && (story!.date + viewersExpirePeriod) < getServerTime(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const forwardSenderTitle = forwardSender ? getPeerTitle(lang, forwardSender)
|
const forwardSenderTitle = forwardSender ? getPeerTitle(oldLang, forwardSender)
|
||||||
: (isLoadedStory && story.forwardInfo?.fromName);
|
: (isLoadedStory && story.forwardInfo?.fromName);
|
||||||
|
|
||||||
const canCopyLink = Boolean(
|
const canCopyLink = Boolean(
|
||||||
@ -492,16 +498,16 @@ function Story({
|
|||||||
: story.isForContacts ? 'contacts' : (story.isForCloseFriends ? 'closeFriends' : 'nobody');
|
: story.isForContacts ? 'contacts' : (story.isForCloseFriends ? 'closeFriends' : 'nobody');
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
const myName = getPeerTitle(lang, peer);
|
const myName = getPeerTitle(oldLang, peer);
|
||||||
switch (visibility) {
|
switch (visibility) {
|
||||||
case 'nobody':
|
case 'nobody':
|
||||||
message = lang('StorySelectedContactsHint', myName);
|
message = oldLang('StorySelectedContactsHint', myName);
|
||||||
break;
|
break;
|
||||||
case 'contacts':
|
case 'contacts':
|
||||||
message = lang('StoryContactsHint', myName);
|
message = oldLang('StoryContactsHint', myName);
|
||||||
break;
|
break;
|
||||||
case 'closeFriends':
|
case 'closeFriends':
|
||||||
message = lang('StoryCloseFriendsHint', myName);
|
message = oldLang('StoryCloseFriendsHint', myName);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
@ -512,7 +518,7 @@ function Story({
|
|||||||
const handleVolumeMuted = useLastCallback(() => {
|
const handleVolumeMuted = useLastCallback(() => {
|
||||||
if (noSound) {
|
if (noSound) {
|
||||||
showNotification({
|
showNotification({
|
||||||
message: lang('Story.TooltipVideoHasNoSound'),
|
message: oldLang('Story.TooltipVideoHasNoSound'),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -525,8 +531,8 @@ function Story({
|
|||||||
if (stealthMode.activeUntil && getServerTime() < stealthMode.activeUntil) {
|
if (stealthMode.activeUntil && getServerTime() < stealthMode.activeUntil) {
|
||||||
const diff = stealthMode.activeUntil - getServerTime();
|
const diff = stealthMode.activeUntil - getServerTime();
|
||||||
showNotification({
|
showNotification({
|
||||||
title: lang('StealthModeOn'),
|
title: oldLang('StealthModeOn'),
|
||||||
message: lang('Story.ToastStealthModeActiveText', formatMediaDuration(diff)),
|
message: oldLang('Story.ToastStealthModeActiveText', formatMediaDuration(diff)),
|
||||||
duration: STEALTH_MODE_NOTIFICATION_DURATION,
|
duration: STEALTH_MODE_NOTIFICATION_DURATION,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -544,9 +550,9 @@ function Story({
|
|||||||
if (!isDeletedStory) return;
|
if (!isDeletedStory) return;
|
||||||
|
|
||||||
showNotification({
|
showNotification({
|
||||||
message: lang('StoryNotFound'),
|
message: oldLang('StoryNotFound'),
|
||||||
});
|
});
|
||||||
}, [lang, isDeletedStory]);
|
}, [oldLang, isDeletedStory]);
|
||||||
|
|
||||||
const MenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
const MenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
||||||
return ({ onTrigger, isOpen }) => {
|
return ({ onTrigger, isOpen }) => {
|
||||||
@ -558,13 +564,13 @@ function Story({
|
|||||||
color="translucent-white"
|
color="translucent-white"
|
||||||
onClick={onTrigger}
|
onClick={onTrigger}
|
||||||
className={buildClassName(styles.button, isOpen && 'active')}
|
className={buildClassName(styles.button, isOpen && 'active')}
|
||||||
ariaLabel={lang('AccDescrOpenMenu2')}
|
ariaLabel={oldLang('AccDescrOpenMenu2')}
|
||||||
>
|
>
|
||||||
<Icon name="more" />
|
<Icon name="more" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}, [isMobile, lang]);
|
}, [isMobile, oldLang]);
|
||||||
|
|
||||||
function renderStoriesTabs() {
|
function renderStoriesTabs() {
|
||||||
return (
|
return (
|
||||||
@ -643,7 +649,7 @@ function Story({
|
|||||||
/>
|
/>
|
||||||
<div className={styles.senderMeta}>
|
<div className={styles.senderMeta}>
|
||||||
<span onClick={handleOpenChat} className={styles.senderName}>
|
<span onClick={handleOpenChat} className={styles.senderName}>
|
||||||
{renderText(getPeerTitle(lang, peer) || '')}
|
{renderText(getPeerTitle(oldLang, peer) || '')}
|
||||||
</span>
|
</span>
|
||||||
<div className={styles.storyMetaRow}>
|
<div className={styles.storyMetaRow}>
|
||||||
{forwardSenderTitle && (
|
{forwardSenderTitle && (
|
||||||
@ -668,15 +674,15 @@ function Story({
|
|||||||
>
|
>
|
||||||
<Avatar peer={fromPeer} size="micro" />
|
<Avatar peer={fromPeer} size="micro" />
|
||||||
<span className={styles.headerTitle}>
|
<span className={styles.headerTitle}>
|
||||||
{renderText(getPeerTitle(lang, fromPeer) || '')}
|
{renderText(getPeerTitle(oldLang, fromPeer) || '')}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{story && 'date' in story && (
|
{story && 'date' in story && (
|
||||||
<span className={styles.storyMeta}>{formatRelativePastTime(lang, serverTime, story.date)}</span>
|
<span className={styles.storyMeta}>{formatRelativePastTime(oldLang, serverTime, story.date)}</span>
|
||||||
)}
|
)}
|
||||||
{isLoadedStory && story.isEdited && (
|
{isLoadedStory && story.isEdited && (
|
||||||
<span className={styles.storyMeta}>{lang('Story.HeaderEdited')}</span>
|
<span className={styles.storyMeta}>{oldLang('Story.HeaderEdited')}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -702,7 +708,7 @@ function Story({
|
|||||||
color="translucent-white"
|
color="translucent-white"
|
||||||
disabled={!hasFullData}
|
disabled={!hasFullData}
|
||||||
onClick={handleVolumeMuted}
|
onClick={handleVolumeMuted}
|
||||||
ariaLabel={lang('Volume')}
|
ariaLabel={oldLang('Volume')}
|
||||||
>
|
>
|
||||||
<Icon name={(isMuted || noSound) ? 'speaker-muted-story' : 'speaker-story'} />
|
<Icon name={(isMuted || noSound) ? 'speaker-muted-story' : 'speaker-story'} />
|
||||||
</Button>
|
</Button>
|
||||||
@ -714,36 +720,43 @@ function Story({
|
|||||||
onOpen={handleDropdownMenuOpen}
|
onOpen={handleDropdownMenuOpen}
|
||||||
onClose={handleDropdownMenuClose}
|
onClose={handleDropdownMenuClose}
|
||||||
>
|
>
|
||||||
{canCopyLink && <MenuItem icon="copy" onClick={handleCopyStoryLink}>{lang('CopyLink')}</MenuItem>}
|
{canCopyLink && <MenuItem icon="copy" onClick={handleCopyStoryLink}>{oldLang('CopyLink')}</MenuItem>}
|
||||||
{canPinToProfile && (
|
{canPinToProfile && (
|
||||||
<MenuItem icon="save-story" onClick={handlePinClick}>
|
<MenuItem icon="save-story" onClick={handlePinClick}>
|
||||||
{lang(isUserStory ? 'StorySave' : 'SaveToPosts')}
|
{oldLang(isUserStory ? 'StorySave' : 'SaveToPosts')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{canUnpinFromProfile && (
|
{canUnpinFromProfile && (
|
||||||
<MenuItem icon="delete" onClick={handleUnpinClick}>
|
<MenuItem icon="delete" onClick={handleUnpinClick}>
|
||||||
{lang(isUserStory ? 'ArchiveStory' : 'RemoveFromPosts')}
|
{oldLang(isUserStory ? 'ArchiveStory' : 'RemoveFromPosts')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{canDownload && (
|
{canDownload && (
|
||||||
<MenuItem icon="download" disabled={!downloadMediaData} onClick={handleDownload}>
|
<MenuItem icon="download" disabled={!downloadMediaData} onClick={handleDownload}>
|
||||||
{lang('lng_media_download')}
|
{oldLang('lng_media_download')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{!isOut && isUserStory && (
|
{!isOut && isUserStory && (
|
||||||
<MenuItem icon="eye-crossed-outline" onClick={handleOpenStealthModal}>
|
<MenuItem icon="eye-crossed-outline" onClick={handleOpenStealthModal}>
|
||||||
{lang('StealthMode')}
|
{oldLang('StealthMode')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOut && <MenuItem icon="flag" onClick={handleReportStoryClick}>{oldLang('lng_report_story')}</MenuItem>}
|
||||||
|
{isOut && (
|
||||||
|
<MenuItem
|
||||||
|
icon="delete"
|
||||||
|
destructive
|
||||||
|
onClick={handleDeleteStoryClick}
|
||||||
|
>{oldLang('Delete')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{!isOut && <MenuItem icon="flag" onClick={handleReportStoryClick}>{lang('lng_report_story')}</MenuItem>}
|
|
||||||
{isOut && <MenuItem icon="delete" destructive onClick={handleDeleteStoryClick}>{lang('Delete')}</MenuItem>}
|
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<Button
|
<Button
|
||||||
className={buildClassName(styles.button, styles.closeButton)}
|
className={buildClassName(styles.button, styles.closeButton)}
|
||||||
round
|
round
|
||||||
size="tiny"
|
size="tiny"
|
||||||
color="translucent-white"
|
color="translucent-white"
|
||||||
ariaLabel={lang('Close')}
|
ariaLabel={oldLang('Close')}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
@ -753,6 +766,14 @@ function Story({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inputPlaceholder = paidMessagesStars
|
||||||
|
? lang('ComposerPlaceholderPaidReply', {
|
||||||
|
amount: formatStarsAsIcon(lang, paidMessagesStars, { asFont: true, className: 'placeholder-star-icon' }),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
})
|
||||||
|
: oldLang(isChatStory ? 'ReplyToGroupStory' : 'ReplyPrivately');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName(styles.slideInner, 'component-theme-dark')}
|
className={buildClassName(styles.slideInner, 'component-theme-dark')}
|
||||||
@ -821,13 +842,13 @@ function Story({
|
|||||||
type="button"
|
type="button"
|
||||||
className={buildClassName(styles.navigate, styles.prev)}
|
className={buildClassName(styles.navigate, styles.prev)}
|
||||||
onClick={handleOpenPrevStory}
|
onClick={handleOpenPrevStory}
|
||||||
aria-label={lang('Previous')}
|
aria-label={oldLang('Previous')}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={buildClassName(styles.navigate, styles.next)}
|
className={buildClassName(styles.navigate, styles.next)}
|
||||||
onClick={handleOpenNextStory}
|
onClick={handleOpenNextStory}
|
||||||
aria-label={lang('Next')}
|
aria-label={oldLang('Next')}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -847,7 +868,7 @@ function Story({
|
|||||||
withStory
|
withStory
|
||||||
storyViewerMode="disabled"
|
storyViewerMode="disabled"
|
||||||
/>
|
/>
|
||||||
<div className={styles.name}>{renderText(getPeerTitle(lang, peer) || '')}</div>
|
<div className={styles.name}>{renderText(getPeerTitle(oldLang, peer) || '')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -862,7 +883,7 @@ function Story({
|
|||||||
role="button"
|
role="button"
|
||||||
className={buildClassName(styles.captionBackdrop, captionBackdropTransitionClassNames)}
|
className={buildClassName(styles.captionBackdrop, captionBackdropTransitionClassNames)}
|
||||||
onClick={() => foldCaption()}
|
onClick={() => foldCaption()}
|
||||||
aria-label={lang('Close')}
|
aria-label={oldLang('Close')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasText && <div className={buildClassName(styles.captionGradient, captionAppearanceAnimationClassNames)} />}
|
{hasText && <div className={buildClassName(styles.captionGradient, captionAppearanceAnimationClassNames)} />}
|
||||||
@ -889,7 +910,7 @@ function Story({
|
|||||||
editableInputId={EDITABLE_STORY_INPUT_ID}
|
editableInputId={EDITABLE_STORY_INPUT_ID}
|
||||||
inputId="story-input-text"
|
inputId="story-input-text"
|
||||||
className={buildClassName(styles.composer, composerAppearanceAnimationClassNames)}
|
className={buildClassName(styles.composer, composerAppearanceAnimationClassNames)}
|
||||||
inputPlaceholder={lang(isChatStory ? 'ReplyToGroupStory' : 'ReplyPrivately')}
|
inputPlaceholder={inputPlaceholder}
|
||||||
onForward={canShare ? handleForwardClick : undefined}
|
onForward={canShare ? handleForwardClick : undefined}
|
||||||
onFocus={markComposerHasFocus}
|
onFocus={markComposerHasFocus}
|
||||||
onBlur={unmarkComposerHasFocus}
|
onBlur={unmarkComposerHasFocus}
|
||||||
@ -923,12 +944,14 @@ export default memo(withGlobal<OwnProps>((global, {
|
|||||||
mapModal,
|
mapModal,
|
||||||
reportModal,
|
reportModal,
|
||||||
giftInfoModal,
|
giftInfoModal,
|
||||||
|
isPaymentMessageConfirmDialogOpen,
|
||||||
} = tabState;
|
} = tabState;
|
||||||
const { isOpen: isPremiumModalOpen } = premiumModal || {};
|
const { isOpen: isPremiumModalOpen } = premiumModal || {};
|
||||||
const story = selectPeerStory(global, peerId, storyId);
|
const story = selectPeerStory(global, peerId, storyId);
|
||||||
const isLoadedStory = story && 'content' in story;
|
const isLoadedStory = story && 'content' in story;
|
||||||
const shouldForcePause = Boolean(
|
const shouldForcePause = Boolean(
|
||||||
viewModal || forwardedStoryId || tabState.reactionPicker?.storyId || reportModal || isPrivacyModalOpen
|
isPaymentMessageConfirmDialogOpen
|
||||||
|
|| viewModal || forwardedStoryId || tabState.reactionPicker?.storyId || reportModal || isPrivacyModalOpen
|
||||||
|| isPremiumModalOpen || isDeleteModalOpen || safeLinkModalUrl || isStealthModalOpen || mapModal || giftInfoModal,
|
|| isPremiumModalOpen || isDeleteModalOpen || safeLinkModalUrl || isStealthModalOpen || mapModal || giftInfoModal,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -940,6 +963,7 @@ export default memo(withGlobal<OwnProps>((global, {
|
|||||||
const withHeaderAnimation = selectPerformanceSettingsValue(global, 'mediaViewerAnimations');
|
const withHeaderAnimation = selectPerformanceSettingsValue(global, 'mediaViewerAnimations');
|
||||||
|
|
||||||
const fromPeer = isLoadedStory && story.fromId ? selectPeer(global, story.fromId) : undefined;
|
const fromPeer = isLoadedStory && story.fromId ? selectPeer(global, story.fromId) : undefined;
|
||||||
|
const paidMessagesStars = selectPeerPaidMessagesStars(global, peerId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
peer: (user || chat)!,
|
peer: (user || chat)!,
|
||||||
@ -956,5 +980,6 @@ export default memo(withGlobal<OwnProps>((global, {
|
|||||||
arePeerSettingsLoaded: Boolean(userFullInfo?.settings),
|
arePeerSettingsLoaded: Boolean(userFullInfo?.settings),
|
||||||
stealthMode: global.stories.stealthMode,
|
stealthMode: global.stories.stealthMode,
|
||||||
withHeaderAnimation,
|
withHeaderAnimation,
|
||||||
|
paidMessagesStars,
|
||||||
};
|
};
|
||||||
})(Story));
|
})(Story));
|
||||||
|
|||||||
@ -60,7 +60,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notification-button {
|
.notification-button {
|
||||||
color: var(--color-primary);
|
color: var(--color-toast-action);
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
margin-inline-start: 0.125rem;
|
margin-inline-start: 0.125rem;
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated
|
|||||||
|
|
||||||
import CustomEmoji from '../common/CustomEmoji';
|
import CustomEmoji from '../common/CustomEmoji';
|
||||||
import Icon from '../common/icons/Icon';
|
import Icon from '../common/icons/Icon';
|
||||||
|
import StarIcon from '../common/icons/StarIcon';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import Portal from './Portal';
|
import Portal from './Portal';
|
||||||
import RoundTimer from './RoundTimer';
|
import RoundTimer from './RoundTimer';
|
||||||
@ -54,6 +55,7 @@ const Notification: FC<OwnProps> = ({
|
|||||||
dismissAction,
|
dismissAction,
|
||||||
duration = DEFAULT_DURATION,
|
duration = DEFAULT_DURATION,
|
||||||
icon,
|
icon,
|
||||||
|
shouldUseCustomIcon,
|
||||||
customEmojiIconId,
|
customEmojiIconId,
|
||||||
shouldShowTimer,
|
shouldShowTimer,
|
||||||
title,
|
title,
|
||||||
@ -79,6 +81,23 @@ const Notification: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleActionClick = useLastCallback(() => {
|
||||||
|
if (action) {
|
||||||
|
if (Array.isArray(action)) {
|
||||||
|
// @ts-ignore
|
||||||
|
action.forEach((cb) => actions[cb.action](cb.payload));
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
actions[action.action](action.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (disableClickDismiss) {
|
||||||
|
setIsOpen(false);
|
||||||
|
setTimeout(handleDismiss, ANIMATION_DURATION + ANIMATION_END_DELAY);
|
||||||
|
}
|
||||||
|
closeAndDismiss();
|
||||||
|
});
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (action) {
|
if (action) {
|
||||||
if (Array.isArray(action)) {
|
if (Array.isArray(action)) {
|
||||||
@ -151,6 +170,28 @@ const Notification: FC<OwnProps> = ({
|
|||||||
return actionText;
|
return actionText;
|
||||||
}, [lang, actionText]);
|
}, [lang, actionText]);
|
||||||
|
|
||||||
|
const renderedIcon = useMemo(() => {
|
||||||
|
if (customEmojiIconId) {
|
||||||
|
return (
|
||||||
|
<CustomEmoji
|
||||||
|
className="notification-emoji-icon"
|
||||||
|
forceAlways
|
||||||
|
size={CUSTOM_EMOJI_SIZE}
|
||||||
|
documentId={customEmojiIconId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUseCustomIcon) {
|
||||||
|
if (icon === 'star') {
|
||||||
|
return (
|
||||||
|
<StarIcon type="gold" className={buildClassName('notification-icon')} size="adaptive" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <Icon name={icon || 'info-filled'} className="notification-icon" />;
|
||||||
|
}, [customEmojiIconId, icon, shouldUseCustomIcon]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal className="Notification-container" containerSelector={containerSelector}>
|
<Portal className="Notification-container" containerSelector={containerSelector}>
|
||||||
<div
|
<div
|
||||||
@ -159,26 +200,17 @@ const Notification: FC<OwnProps> = ({
|
|||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
{customEmojiIconId ? (
|
{renderedIcon}
|
||||||
<CustomEmoji
|
|
||||||
className="notification-emoji-icon"
|
|
||||||
forceAlways
|
|
||||||
size={CUSTOM_EMOJI_SIZE}
|
|
||||||
documentId={customEmojiIconId}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Icon name={icon || 'info-filled'} className="notification-icon" />
|
|
||||||
)}
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{renderedTitle && (
|
{renderedTitle && (
|
||||||
<div className="notification-title">{renderedTitle}</div>
|
<div className="notification-title">{renderedTitle}</div>
|
||||||
)}
|
)}
|
||||||
{renderedMessage}
|
{renderedMessage}
|
||||||
</div>
|
</div>
|
||||||
{action && renderedActionText && (
|
{renderedActionText && (
|
||||||
<Button
|
<Button
|
||||||
color="translucent-white"
|
color="translucent-white"
|
||||||
onClick={handleClick}
|
onClick={handleActionClick}
|
||||||
className="notification-button"
|
className="notification-button"
|
||||||
>
|
>
|
||||||
{renderedActionText}
|
{renderedActionText}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
.root {
|
.root {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--color-primary);
|
color: var(--color-toast-action);
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.circle {
|
.circle {
|
||||||
stroke: var(--color-primary);
|
stroke: var(--color-toast-action);
|
||||||
fill: transparent;
|
fill: transparent;
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
|
|||||||
@ -19,6 +19,7 @@ export const IS_TEST = process.env.APP_ENV === 'test';
|
|||||||
export const IS_PERF = process.env.APP_ENV === 'perf';
|
export const IS_PERF = process.env.APP_ENV === 'perf';
|
||||||
export const IS_BETA = process.env.APP_ENV === 'staging';
|
export const IS_BETA = process.env.APP_ENV === 'staging';
|
||||||
export const IS_PACKAGED_ELECTRON = process.env.IS_PACKAGED_ELECTRON;
|
export const IS_PACKAGED_ELECTRON = process.env.IS_PACKAGED_ELECTRON;
|
||||||
|
export const PAID_MESSAGES_PURPOSE = 'paid_messages';
|
||||||
|
|
||||||
export const DEBUG = process.env.APP_ENV !== 'production';
|
export const DEBUG = process.env.APP_ENV !== 'production';
|
||||||
export const DEBUG_MORE = false;
|
export const DEBUG_MORE = false;
|
||||||
@ -121,6 +122,10 @@ export const TOP_CHAT_MESSAGES_PRELOAD_LIMIT = 20;
|
|||||||
|
|
||||||
export const SPONSORED_MESSAGE_CACHE_MS = 300000; // 5 min
|
export const SPONSORED_MESSAGE_CACHE_MS = 300000; // 5 min
|
||||||
|
|
||||||
|
export const DEFAULT_CHARGE_FOR_MESSAGES = 250;
|
||||||
|
export const MINIMUM_CHARGE_FOR_MESSAGES = 1;
|
||||||
|
export const DEFAULT_MAXIMUM_CHARGE_FOR_MESSAGES = 10000;
|
||||||
|
|
||||||
export const DEFAULT_VOLUME = 1;
|
export const DEFAULT_VOLUME = 1;
|
||||||
export const DEFAULT_PLAYBACK_RATE = 1;
|
export const DEFAULT_PLAYBACK_RATE = 1;
|
||||||
export const PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION = 20 * 60; // 20 min
|
export const PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION = 20 * 60; // 20 min
|
||||||
@ -177,6 +182,7 @@ export const TMP_CHAT_ID = '0';
|
|||||||
export const ANIMATION_END_DELAY = 100;
|
export const ANIMATION_END_DELAY = 100;
|
||||||
export const ANIMATION_WAVE_MIN_INTERVAL = 200;
|
export const ANIMATION_WAVE_MIN_INTERVAL = 200;
|
||||||
export const MESSAGE_APPEARANCE_DELAY = 10;
|
export const MESSAGE_APPEARANCE_DELAY = 10;
|
||||||
|
export const PAID_SEND_DELAY = 5000;
|
||||||
|
|
||||||
export const SCROLL_MIN_DURATION = 300;
|
export const SCROLL_MIN_DURATION = 300;
|
||||||
export const SCROLL_MAX_DURATION = 600;
|
export const SCROLL_MAX_DURATION = 600;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { ManagementProgress } from '../../../types';
|
import { ManagementProgress } from '../../../types';
|
||||||
|
|
||||||
import { BOT_FATHER_USERNAME, GENERAL_REFETCH_INTERVAL } from '../../../config';
|
import { BOT_FATHER_USERNAME, GENERAL_REFETCH_INTERVAL, PAID_SEND_DELAY } from '../../../config';
|
||||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { oldTranslate } from '../../../util/oldLangProvider';
|
import { oldTranslate } from '../../../util/oldLangProvider';
|
||||||
@ -62,6 +62,7 @@ import {
|
|||||||
selectUserFullInfo,
|
selectUserFullInfo,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { fetchChatByUsername } from './chats';
|
import { fetchChatByUsername } from './chats';
|
||||||
|
import { getPeerStarsForMessage } from './messages';
|
||||||
|
|
||||||
import { getIsWebAppsFullscreenSupported } from '../../../hooks/useAppLayout';
|
import { getIsWebAppsFullscreenSupported } from '../../../hooks/useAppLayout';
|
||||||
|
|
||||||
@ -379,7 +380,26 @@ addActionHandler('switchBotInline', (global, actions, payload): ActionReturnType
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('sendInlineBotResult', (global, actions, payload): ActionReturnType => {
|
addActionHandler('sendInlineBotApiResult', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
chat, id, queryId, replyInfo, sendAs, isSilent, scheduledAt, allowPaidStars,
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
await callApi('sendInlineBotResult', {
|
||||||
|
chat,
|
||||||
|
resultId: id,
|
||||||
|
queryId,
|
||||||
|
replyInfo,
|
||||||
|
sendAs,
|
||||||
|
isSilent,
|
||||||
|
scheduleDate: scheduledAt,
|
||||||
|
allowPaidStars,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allowPaidStars) actions.loadStarStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('sendInlineBotResult', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
id, queryId, isSilent, scheduledAt, threadId, chatId,
|
id, queryId, isSilent, scheduledAt, threadId, chatId,
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
@ -396,14 +416,39 @@ addActionHandler('sendInlineBotResult', (global, actions, payload): ActionReturn
|
|||||||
actions.resetDraftReplyInfo({ tabId });
|
actions.resetDraftReplyInfo({ tabId });
|
||||||
actions.clearWebPagePreview({ tabId });
|
actions.clearWebPagePreview({ tabId });
|
||||||
|
|
||||||
void callApi('sendInlineBotResult', {
|
const starsForOneMessage = await getPeerStarsForMessage(global, chat);
|
||||||
|
const params = {
|
||||||
chat,
|
chat,
|
||||||
resultId: id,
|
id,
|
||||||
queryId,
|
queryId,
|
||||||
replyInfo,
|
replyInfo,
|
||||||
sendAs: selectSendAs(global, chatId),
|
sendAs: selectSendAs(global, chatId),
|
||||||
isSilent,
|
isSilent,
|
||||||
scheduleDate: scheduledAt,
|
scheduledAt,
|
||||||
|
allowPaidStars: starsForOneMessage,
|
||||||
|
};
|
||||||
|
if (!starsForOneMessage) {
|
||||||
|
actions.sendInlineBotApiResult(params);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line eslint-multitab-tt/no-getactions-in-actions
|
||||||
|
actions.showNotification({
|
||||||
|
localId: queryId,
|
||||||
|
title: { key: 'ToastTitleMessageSent' },
|
||||||
|
message: { key: 'ToastMessageSent', variables: { amount: starsForOneMessage } },
|
||||||
|
actionText: { key: 'ButtonUndo' },
|
||||||
|
dismissAction: {
|
||||||
|
action: 'sendInlineBotApiResult',
|
||||||
|
payload: params,
|
||||||
|
},
|
||||||
|
duration: PAID_SEND_DELAY,
|
||||||
|
shouldShowTimer: true,
|
||||||
|
disableClickDismiss: true,
|
||||||
|
icon: 'star',
|
||||||
|
shouldUseCustomIcon: true,
|
||||||
|
type: 'paidMessage',
|
||||||
|
tabId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,33 +5,33 @@ import type {
|
|||||||
ApiDraft,
|
ApiDraft,
|
||||||
ApiError,
|
ApiError,
|
||||||
ApiInputMessageReplyInfo,
|
ApiInputMessageReplyInfo,
|
||||||
ApiInputReplyInfo,
|
|
||||||
ApiInputStoryReplyInfo,
|
ApiInputStoryReplyInfo,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
|
||||||
ApiNewPoll,
|
|
||||||
ApiOnProgress,
|
ApiOnProgress,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiSticker,
|
|
||||||
ApiStory,
|
ApiStory,
|
||||||
ApiStorySkipped,
|
|
||||||
ApiUser,
|
ApiUser,
|
||||||
ApiVideo,
|
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
import type {
|
||||||
|
ForwardMessagesParams,
|
||||||
|
SendMessageParams,
|
||||||
|
ThreadId,
|
||||||
|
} from '../../../types';
|
||||||
import type { MessageKey } from '../../../util/keys/messageKey';
|
import type { MessageKey } from '../../../util/keys/messageKey';
|
||||||
|
import type { RegularLangFnParameters } from '../../../util/localization';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type {
|
import type {
|
||||||
ActionReturnType, GlobalState, TabArgs,
|
ActionReturnType, GlobalState, TabArgs,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { MAIN_THREAD_ID, MESSAGE_DELETED } from '../../../api/types';
|
import { MAIN_THREAD_ID, MESSAGE_DELETED } from '../../../api/types';
|
||||||
import { LoadMoreDirection, type ThreadId, type WebPageMediaSize } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GIF_MIME_TYPE,
|
GIF_MIME_TYPE,
|
||||||
MAX_MEDIA_FILES_FOR_ALBUM,
|
MAX_MEDIA_FILES_FOR_ALBUM,
|
||||||
MESSAGE_ID_REQUIRED_ERROR,
|
MESSAGE_ID_REQUIRED_ERROR,
|
||||||
MESSAGE_LIST_SLICE,
|
MESSAGE_LIST_SLICE,
|
||||||
RE_TELEGRAM_LINK,
|
PAID_SEND_DELAY, RE_TELEGRAM_LINK,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
SUPPORTED_AUDIO_CONTENT_TYPES,
|
SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||||
SUPPORTED_PHOTO_CONTENT_TYPES,
|
SUPPORTED_PHOTO_CONTENT_TYPES,
|
||||||
@ -61,6 +61,7 @@ import {
|
|||||||
isChatSuperGroup,
|
isChatSuperGroup,
|
||||||
isDeletedUser,
|
isDeletedUser,
|
||||||
isMessageLocal,
|
isMessageLocal,
|
||||||
|
isPeerUser,
|
||||||
isServiceNotificationMessage,
|
isServiceNotificationMessage,
|
||||||
isUserBot,
|
isUserBot,
|
||||||
splitMessagesForForwarding,
|
splitMessagesForForwarding,
|
||||||
@ -301,14 +302,14 @@ addActionHandler('loadMessage', async (global, actions, payload): Promise<void>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('sendMessage', (global, actions, payload): ActionReturnType => {
|
addActionHandler('sendMessage', async (global, actions, payload): Promise<void> => {
|
||||||
const { messageList, tabId = getCurrentTabId() } = payload;
|
const { messageList, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
const { storyId, peerId: storyPeerId } = selectCurrentViewedStory(global, tabId);
|
const { storyId, peerId: storyPeerId } = selectCurrentViewedStory(global, tabId);
|
||||||
const isStoryReply = Boolean(storyId && storyPeerId);
|
const isStoryReply = Boolean(storyId && storyPeerId);
|
||||||
|
|
||||||
if (!messageList && !isStoryReply) {
|
if (!messageList && !isStoryReply) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { chatId, threadId, type } = messageList || {};
|
let { chatId, threadId, type } = messageList || {};
|
||||||
@ -321,9 +322,11 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
payload = omit(payload, ['tabId']);
|
payload = omit(payload, ['tabId']);
|
||||||
|
|
||||||
if (type === 'scheduled' && !payload.scheduledAt) {
|
if (type === 'scheduled' && !payload.scheduledAt) {
|
||||||
return updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
contentToBeScheduled: payload,
|
contentToBeScheduled: payload,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chat = selectChat(global, chatId!)!;
|
const chat = selectChat(global, chatId!)!;
|
||||||
@ -342,30 +345,36 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
|
|
||||||
const replyInfo = storyReplyInfo || messageReplyInfo;
|
const replyInfo = storyReplyInfo || messageReplyInfo;
|
||||||
const lastMessageId = selectChatLastMessageId(global, chatId!);
|
const lastMessageId = selectChatLastMessageId(global, chatId!);
|
||||||
|
const messagePriceInStars = await getPeerStarsForMessage(global, chat);
|
||||||
|
|
||||||
const params = {
|
const params : SendMessageParams = {
|
||||||
...payload,
|
...payload,
|
||||||
chat,
|
chat,
|
||||||
replyInfo,
|
replyInfo,
|
||||||
noWebPage: selectNoWebPage(global, chatId!, threadId!),
|
noWebPage: selectNoWebPage(global, chatId!, threadId!),
|
||||||
sendAs: selectSendAs(global, chatId!),
|
sendAs: selectSendAs(global, chatId!),
|
||||||
lastMessageId,
|
lastMessageId,
|
||||||
|
messagePriceInStars,
|
||||||
|
isStoryReply,
|
||||||
|
isPending: messagePriceInStars ? true : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isStoryReply) {
|
if (!isStoryReply) {
|
||||||
actions.clearWebPagePreview({ tabId });
|
actions.clearWebPagePreview({ tabId });
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSingle = !payload.attachments || payload.attachments.length <= 1;
|
const isSingle = (!payload.attachments || payload.attachments.length <= 1) && !isForwarding;
|
||||||
const isGrouped = !isSingle && payload.shouldGroupMessages;
|
const isGrouped = !isSingle && payload.shouldGroupMessages;
|
||||||
|
const localMessages: SendMessageParams[] = [];
|
||||||
|
|
||||||
if (isSingle) {
|
if (isSingle) {
|
||||||
const { attachments, ...restParams } = params;
|
const { attachments, ...restParams } = params;
|
||||||
sendMessage(global, {
|
const sendParams: SendMessageParams = {
|
||||||
...restParams,
|
...restParams,
|
||||||
attachment: attachments ? attachments[0] : undefined,
|
attachment: attachments ? attachments[0] : undefined,
|
||||||
wasDrafted: Boolean(draft),
|
wasDrafted: Boolean(draft),
|
||||||
});
|
};
|
||||||
|
await sendMessageOrReduceLocal(global, sendParams, localMessages);
|
||||||
} else if (isGrouped) {
|
} else if (isGrouped) {
|
||||||
const {
|
const {
|
||||||
text, entities, attachments, ...commonParams
|
text, entities, attachments, ...commonParams
|
||||||
@ -373,7 +382,8 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
const byType = splitAttachmentsByType(attachments!);
|
const byType = splitAttachmentsByType(attachments!);
|
||||||
|
|
||||||
let hasSentCaption = false;
|
let hasSentCaption = false;
|
||||||
byType.forEach((group, groupIndex) => {
|
for (let groupIndex = 0; groupIndex < byType.length; groupIndex++) {
|
||||||
|
const group = byType[groupIndex];
|
||||||
const groupedAttachments = split(group as ApiAttachment[], MAX_MEDIA_FILES_FOR_ALBUM);
|
const groupedAttachments = split(group as ApiAttachment[], MAX_MEDIA_FILES_FOR_ALBUM);
|
||||||
for (let i = 0; i < groupedAttachments.length; i++) {
|
for (let i = 0; i < groupedAttachments.length; i++) {
|
||||||
const groupedId = `${Date.now()}${groupIndex}${i}`;
|
const groupedId = `${Date.now()}${groupIndex}${i}`;
|
||||||
@ -383,70 +393,86 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
|
|
||||||
if (group[0].quick && !group[0].shouldSendAsFile) {
|
if (group[0].quick && !group[0].shouldSendAsFile) {
|
||||||
const [firstAttachment, ...restAttachments] = groupedAttachments[i];
|
const [firstAttachment, ...restAttachments] = groupedAttachments[i];
|
||||||
sendMessage(global, {
|
|
||||||
|
let sendParams: SendMessageParams = {
|
||||||
...commonParams,
|
...commonParams,
|
||||||
text: isFirst && !hasSentCaption ? text : undefined,
|
text: isFirst && !hasSentCaption ? text : undefined,
|
||||||
entities: isFirst && !hasSentCaption ? entities : undefined,
|
entities: isFirst && !hasSentCaption ? entities : undefined,
|
||||||
attachment: firstAttachment,
|
attachment: firstAttachment,
|
||||||
groupedId: restAttachments.length > 0 ? groupedId : undefined,
|
groupedId: restAttachments.length > 0 ? groupedId : undefined,
|
||||||
wasDrafted: Boolean(draft),
|
wasDrafted: Boolean(draft),
|
||||||
});
|
};
|
||||||
|
await sendMessageOrReduceLocal(global, sendParams, localMessages);
|
||||||
|
|
||||||
hasSentCaption = true;
|
hasSentCaption = true;
|
||||||
|
|
||||||
restAttachments.forEach((attachment: ApiAttachment) => {
|
for (const attachment of restAttachments) {
|
||||||
sendMessage(global, {
|
sendParams = {
|
||||||
...commonParams,
|
...commonParams,
|
||||||
attachment,
|
attachment,
|
||||||
groupedId,
|
groupedId,
|
||||||
});
|
};
|
||||||
});
|
await sendMessageOrReduceLocal(global, sendParams, localMessages);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const firstAttachments = groupedAttachments[i].slice(0, -1);
|
const firstAttachments = groupedAttachments[i].slice(0, -1);
|
||||||
const lastAttachment = groupedAttachments[i][groupedAttachments[i].length - 1];
|
const lastAttachment = groupedAttachments[i][groupedAttachments[i].length - 1];
|
||||||
firstAttachments.forEach((attachment: ApiAttachment) => {
|
for (const attachment of firstAttachments) {
|
||||||
sendMessage(global, {
|
const sendParams = {
|
||||||
...commonParams,
|
...commonParams,
|
||||||
attachment,
|
attachment,
|
||||||
groupedId,
|
groupedId,
|
||||||
});
|
};
|
||||||
});
|
await sendMessageOrReduceLocal(global, sendParams, localMessages);
|
||||||
|
}
|
||||||
|
|
||||||
sendMessage(global, {
|
const sendParams = {
|
||||||
...commonParams,
|
...commonParams,
|
||||||
text: isLast && !hasSentCaption ? text : undefined,
|
text: isLast && !hasSentCaption ? text : undefined,
|
||||||
entities: isLast && !hasSentCaption ? entities : undefined,
|
entities: isLast && !hasSentCaption ? entities : undefined,
|
||||||
attachment: lastAttachment,
|
attachment: lastAttachment,
|
||||||
groupedId: firstAttachments.length > 0 ? groupedId : undefined,
|
groupedId: firstAttachments.length > 0 ? groupedId : undefined,
|
||||||
wasDrafted: Boolean(draft),
|
wasDrafted: Boolean(draft),
|
||||||
});
|
};
|
||||||
|
await sendMessageOrReduceLocal(global, sendParams, localMessages);
|
||||||
|
|
||||||
hasSentCaption = true;
|
hasSentCaption = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else {
|
} else {
|
||||||
const {
|
const {
|
||||||
text, entities, attachments, replyInfo: replyToForFirstMessage, ...commonParams
|
text, entities, attachments, replyInfo: replyToForFirstMessage, ...commonParams
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
sendMessage(global, {
|
const sendParams = {
|
||||||
...commonParams,
|
...commonParams,
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
replyInfo: replyToForFirstMessage,
|
replyInfo: replyToForFirstMessage,
|
||||||
wasDrafted: Boolean(draft),
|
wasDrafted: Boolean(draft),
|
||||||
});
|
};
|
||||||
|
await sendMessageOrReduceLocal(global, sendParams, localMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
attachments?.forEach((attachment: ApiAttachment) => {
|
if (attachments) {
|
||||||
sendMessage(global, {
|
for (const attachment of attachments) {
|
||||||
...commonParams,
|
const sendParams = {
|
||||||
attachment,
|
...commonParams,
|
||||||
});
|
attachment,
|
||||||
});
|
};
|
||||||
|
await sendMessageOrReduceLocal(global, sendParams, localMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (isForwarding) {
|
||||||
return undefined;
|
const localForwards = await executeForwardMessages(global, params, tabId);
|
||||||
|
if (localForwards) {
|
||||||
|
localMessages.push(...localForwards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (localMessages?.length) sendMessagesWithNotification(global, localMessages);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('sendInviteMessages', async (global, actions, payload): Promise<void> => {
|
addActionHandler('sendInviteMessages', async (global, actions, payload): Promise<void> => {
|
||||||
@ -735,21 +761,37 @@ addActionHandler('unpinAllMessages', async (global, actions, payload): Promise<v
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('deleteMessages', (global, actions, payload): ActionReturnType => {
|
addActionHandler('deleteMessages', (global, actions, payload): ActionReturnType => {
|
||||||
const { messageIds, shouldDeleteForAll, tabId = getCurrentTabId() } = payload!;
|
const {
|
||||||
|
messageIds, shouldDeleteForAll, messageList: payloadMessageList, tabId = getCurrentTabId(),
|
||||||
|
} = payload!;
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
if (!currentMessageList) {
|
const messageList = payloadMessageList || currentMessageList;
|
||||||
|
if (!messageList) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { chatId, threadId } = currentMessageList;
|
const { chatId, threadId } = messageList;
|
||||||
const chat = selectChat(global, chatId)!;
|
const chat = selectChat(global, chatId)!;
|
||||||
const messageIdsToDelete = messageIds.filter((id) => {
|
const messageIdsToDelete = messageIds.filter((id) => {
|
||||||
const message = selectChatMessage(global, chatId, id);
|
const message = selectChatMessage(global, chatId, id);
|
||||||
return message && !isMessageLocal(message);
|
return message && !isMessageLocal(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Object.values(global.byTabId)
|
||||||
|
.forEach(({ id }) => {
|
||||||
|
messageIds.forEach((messageId) => {
|
||||||
|
const message = selectChatMessage(global, chatId, messageId);
|
||||||
|
if (message) {
|
||||||
|
actions.dismissNotification({
|
||||||
|
localId: getMessageKey(message),
|
||||||
|
tabId: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Only local messages
|
// Only local messages
|
||||||
if (!messageIdsToDelete.length && messageIds.length) {
|
if (!messageIdsToDelete.length && messageIds.length) {
|
||||||
deleteMessages(global, isChatChannel(chat) ? chatId : undefined, messageIds, actions);
|
deleteMessages(global, isChatChannel(chat) || isChatSuperGroup(chat) ? chatId : undefined, messageIds, actions);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -761,6 +803,25 @@ addActionHandler('deleteMessages', (global, actions, payload): ActionReturnType
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('resetLocalPaidMessages', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
const notifications = selectTabState(global, tabId).notifications;
|
||||||
|
if (!notifications || !notifications.length) return global;
|
||||||
|
|
||||||
|
notifications.forEach((notification) => {
|
||||||
|
if (notification.type === 'paidMessage') {
|
||||||
|
const action = notification.dismissAction;
|
||||||
|
if (action && !Array.isArray(action)) {
|
||||||
|
// @ts-ignore
|
||||||
|
actions[action.action](action.payload);
|
||||||
|
}
|
||||||
|
actions.dismissNotification({ localId: notification.localId, tabId });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return global;
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('deleteParticipantHistory', (global, actions, payload): ActionReturnType => {
|
addActionHandler('deleteParticipantHistory', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
chatId, peerId,
|
chatId, peerId,
|
||||||
@ -1129,91 +1190,6 @@ addActionHandler('loadExtendedMedia', (global, actions, payload): ActionReturnTy
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType => {
|
|
||||||
const {
|
|
||||||
isSilent, scheduledAt, tabId = getCurrentTabId(),
|
|
||||||
} = payload;
|
|
||||||
|
|
||||||
const {
|
|
||||||
fromChatId, messageIds, toChatId, withMyScore, noAuthors, noCaptions, toThreadId = MAIN_THREAD_ID,
|
|
||||||
} = selectTabState(global, tabId).forwardMessages;
|
|
||||||
|
|
||||||
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
|
||||||
const isToMainThread = toThreadId === MAIN_THREAD_ID;
|
|
||||||
|
|
||||||
const fromChat = fromChatId ? selectChat(global, fromChatId) : undefined;
|
|
||||||
const toChat = toChatId ? selectChat(global, toChatId) : undefined;
|
|
||||||
|
|
||||||
const messages = fromChatId && messageIds
|
|
||||||
? messageIds
|
|
||||||
.sort((a, b) => a - b)
|
|
||||||
.map((id) => selectChatMessage(global, fromChatId, id)).filter(Boolean)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (!fromChat || !toChat || !messages || (toThreadId && !isToMainThread && !toChat.isForum)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendAs = selectSendAs(global, toChatId!);
|
|
||||||
const draft = selectDraft(global, toChatId!, toThreadId || MAIN_THREAD_ID);
|
|
||||||
const lastMessageId = selectChatLastMessageId(global, toChat.id);
|
|
||||||
|
|
||||||
const [realMessages, serviceMessages] = partition(messages, (m) => !isServiceNotificationMessage(m));
|
|
||||||
const forwardableRealMessages = realMessages.filter((message) => selectCanForwardMessage(global, message));
|
|
||||||
if (forwardableRealMessages.length) {
|
|
||||||
const messageBatches = global.config?.maxForwardedCount
|
|
||||||
? splitMessagesForForwarding(forwardableRealMessages, global.config.maxForwardedCount)
|
|
||||||
: [forwardableRealMessages];
|
|
||||||
(async () => {
|
|
||||||
await rafPromise(); // Wait one frame for any previous `sendMessage` to be processed
|
|
||||||
messageBatches.forEach((batch) => {
|
|
||||||
callApi('forwardMessages', {
|
|
||||||
fromChat,
|
|
||||||
toChat,
|
|
||||||
toThreadId,
|
|
||||||
messages: batch,
|
|
||||||
isSilent,
|
|
||||||
scheduledAt,
|
|
||||||
sendAs,
|
|
||||||
withMyScore,
|
|
||||||
noAuthors,
|
|
||||||
noCaptions,
|
|
||||||
isCurrentUserPremium,
|
|
||||||
wasDrafted: Boolean(draft),
|
|
||||||
lastMessageId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceMessages
|
|
||||||
.forEach((message) => {
|
|
||||||
const { text, entities } = message.content.text || {};
|
|
||||||
const { sticker } = message.content;
|
|
||||||
|
|
||||||
const replyInfo = selectMessageReplyInfo(global, toChat.id, toThreadId);
|
|
||||||
|
|
||||||
void sendMessage(global, {
|
|
||||||
chat: toChat,
|
|
||||||
replyInfo,
|
|
||||||
text,
|
|
||||||
entities,
|
|
||||||
sticker,
|
|
||||||
isSilent,
|
|
||||||
scheduledAt,
|
|
||||||
sendAs,
|
|
||||||
lastMessageId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
global = getGlobal();
|
|
||||||
global = updateTabState(global, {
|
|
||||||
forwardMessages: {},
|
|
||||||
isShareMessageModalShown: false,
|
|
||||||
}, tabId);
|
|
||||||
setGlobal(global);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('loadScheduledHistory', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadScheduledHistory', async (global, actions, payload): Promise<void> => {
|
||||||
const { chatId } = payload;
|
const { chatId } = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
@ -1336,6 +1312,110 @@ addActionHandler('loadCustomEmojis', async (global, actions, payload): Promise<v
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
isSilent, scheduledAt, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
const { toChatId } = selectTabState(global, tabId).forwardMessages;
|
||||||
|
const toChat = toChatId ? selectChat(global, toChatId) : undefined;
|
||||||
|
if (!toChat) return;
|
||||||
|
executeForwardMessages(global, { chat: toChat, isSilent, scheduledAt }, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function executeForwardMessages(global: GlobalState, sendParams: SendMessageParams, tabId: number) {
|
||||||
|
const {
|
||||||
|
fromChatId, messageIds, toChatId, withMyScore, noAuthors, noCaptions, toThreadId = MAIN_THREAD_ID,
|
||||||
|
} = selectTabState(global, tabId).forwardMessages;
|
||||||
|
const { messagePriceInStars, isSilent, scheduledAt } = sendParams;
|
||||||
|
|
||||||
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
const isToMainThread = toThreadId === MAIN_THREAD_ID;
|
||||||
|
|
||||||
|
const fromChat = fromChatId ? selectChat(global, fromChatId) : undefined;
|
||||||
|
const toChat = toChatId ? selectChat(global, toChatId) : undefined;
|
||||||
|
|
||||||
|
const messages = fromChatId && messageIds
|
||||||
|
? messageIds
|
||||||
|
.sort((a, b) => a - b)
|
||||||
|
.map((id) => selectChatMessage(global, fromChatId, id)).filter(Boolean)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (!fromChat || !toChat || !messages || (toThreadId && !isToMainThread && !toChat.isForum)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendAs = selectSendAs(global, toChatId!);
|
||||||
|
const draft = selectDraft(global, toChatId!, toThreadId || MAIN_THREAD_ID);
|
||||||
|
const lastMessageId = selectChatLastMessageId(global, toChat.id);
|
||||||
|
const localMessages: SendMessageParams[] = [];
|
||||||
|
|
||||||
|
const [realMessages, serviceMessages] = partition(messages, (m) => !isServiceNotificationMessage(m));
|
||||||
|
const forwardableRealMessages = realMessages.filter((message) => selectCanForwardMessage(global, message));
|
||||||
|
if (forwardableRealMessages.length) {
|
||||||
|
const messageSlices = global.config?.maxForwardedCount
|
||||||
|
? splitMessagesForForwarding(forwardableRealMessages, global.config.maxForwardedCount)
|
||||||
|
: [forwardableRealMessages];
|
||||||
|
for (const slice of messageSlices) {
|
||||||
|
const forwardParams: ForwardMessagesParams = {
|
||||||
|
fromChat,
|
||||||
|
toChat,
|
||||||
|
toThreadId,
|
||||||
|
messages: slice,
|
||||||
|
isSilent,
|
||||||
|
scheduledAt,
|
||||||
|
sendAs,
|
||||||
|
withMyScore,
|
||||||
|
noAuthors,
|
||||||
|
noCaptions,
|
||||||
|
isCurrentUserPremium,
|
||||||
|
wasDrafted: Boolean(draft),
|
||||||
|
lastMessageId,
|
||||||
|
messagePriceInStars,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!messagePriceInStars) {
|
||||||
|
callApi('forwardMessages', forwardParams);
|
||||||
|
} else {
|
||||||
|
const forwardedLocalMessagesSlice = await callApi('forwardMessagesLocal', forwardParams);
|
||||||
|
localMessages.push({
|
||||||
|
...sendParams,
|
||||||
|
forwardParams: { ...forwardParams, forwardedLocalMessagesSlice },
|
||||||
|
forwardedLocalMessagesSlice,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const message of serviceMessages) {
|
||||||
|
const { text, entities } = message.content.text || {};
|
||||||
|
const { sticker } = message.content;
|
||||||
|
|
||||||
|
const replyInfo = selectMessageReplyInfo(global, toChat.id, toThreadId);
|
||||||
|
|
||||||
|
const params: SendMessageParams = {
|
||||||
|
chat: toChat,
|
||||||
|
replyInfo,
|
||||||
|
text,
|
||||||
|
entities,
|
||||||
|
sticker,
|
||||||
|
isSilent,
|
||||||
|
scheduledAt,
|
||||||
|
sendAs,
|
||||||
|
lastMessageId,
|
||||||
|
};
|
||||||
|
|
||||||
|
await sendMessageOrReduceLocal(global, params, localMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
forwardMessages: {},
|
||||||
|
isShareMessageModalShown: false,
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
return localMessages;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadViewportMessages<T extends GlobalState>(
|
async function loadViewportMessages<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chat: ApiChat,
|
chat: ApiChat,
|
||||||
@ -1525,26 +1605,49 @@ function getViewportSlice(
|
|||||||
return { newViewportIds, areSomeLocal, areAllLocal };
|
return { newViewportIds, areSomeLocal, areAllLocal };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendMessage<T extends GlobalState>(global: T, params: {
|
export async function getPeerStarsForMessage<T extends GlobalState>(
|
||||||
chat: ApiChat;
|
global: T,
|
||||||
text?: string;
|
peer: ApiPeer,
|
||||||
entities?: ApiMessageEntity[];
|
): Promise<number | undefined> {
|
||||||
replyInfo?: ApiInputReplyInfo;
|
if (!isPeerUser(peer)) {
|
||||||
attachment?: ApiAttachment;
|
return peer.paidMessagesStars;
|
||||||
sticker?: ApiSticker;
|
}
|
||||||
story?: ApiStory | ApiStorySkipped;
|
|
||||||
gif?: ApiVideo;
|
if (!peer?.paidMessagesStars) return undefined;
|
||||||
poll?: ApiNewPoll;
|
|
||||||
isSilent?: boolean;
|
const fullInfo = selectUserFullInfo(global, peer.id);
|
||||||
scheduledAt?: number;
|
if (fullInfo) {
|
||||||
sendAs?: ApiPeer;
|
return fullInfo?.paidMessagesStars;
|
||||||
groupedId?: string;
|
}
|
||||||
wasDrafted?: boolean;
|
|
||||||
lastMessageId?: number;
|
const result = await callApi('fetchPaidMessagesStarsAmount', peer);
|
||||||
isInvertedMedia?: true;
|
return result;
|
||||||
effectId?: string;
|
}
|
||||||
webPageMediaSize?: WebPageMediaSize;
|
|
||||||
}) {
|
async function sendMessageOrReduceLocal<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
sendParams: SendMessageParams,
|
||||||
|
localMessages: SendMessageParams[],
|
||||||
|
) {
|
||||||
|
if (!sendParams.messagePriceInStars) {
|
||||||
|
sendMessage(global, sendParams);
|
||||||
|
} else {
|
||||||
|
const message = await callApi('sendMessageLocal', sendParams);
|
||||||
|
if (message) {
|
||||||
|
localMessages.push({
|
||||||
|
...sendParams,
|
||||||
|
localMessage: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendMessage<T extends GlobalState>(global: T, params: SendMessageParams) {
|
||||||
|
// @optimization
|
||||||
|
if (params.replyInfo || IS_IOS) {
|
||||||
|
await rafPromise();
|
||||||
|
}
|
||||||
|
|
||||||
let currentMessageKey: MessageKey | undefined;
|
let currentMessageKey: MessageKey | undefined;
|
||||||
const progressCallback = params.attachment ? (progress: number, messageKey: MessageKey) => {
|
const progressCallback = params.attachment ? (progress: number, messageKey: MessageKey) => {
|
||||||
if (!uploadProgressCallbacks.has(messageKey)) {
|
if (!uploadProgressCallbacks.has(messageKey)) {
|
||||||
@ -1556,14 +1659,7 @@ async function sendMessage<T extends GlobalState>(global: T, params: {
|
|||||||
global = updateUploadByMessageKey(global, messageKey, progress);
|
global = updateUploadByMessageKey(global, messageKey, progress);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
} : undefined;
|
} : undefined;
|
||||||
|
|
||||||
// @optimization
|
|
||||||
if (params.replyInfo || IS_IOS) {
|
|
||||||
await rafPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
await callApi('sendMessage', params, progressCallback);
|
await callApi('sendMessage', params, progressCallback);
|
||||||
|
|
||||||
if (progressCallback && currentMessageKey) {
|
if (progressCallback && currentMessageKey) {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateUploadByMessageKey(global, currentMessageKey, undefined);
|
global = updateUploadByMessageKey(global, currentMessageKey, undefined);
|
||||||
@ -1573,6 +1669,92 @@ async function sendMessage<T extends GlobalState>(global: T, params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendMessagesWithNotification<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
sendParams: SendMessageParams[],
|
||||||
|
) {
|
||||||
|
const chat = sendParams[0]?.chat;
|
||||||
|
if (!chat || !sendParams.length) return;
|
||||||
|
const starsForOneMessage = await getPeerStarsForMessage(global, chat);
|
||||||
|
if (!starsForOneMessage) {
|
||||||
|
// eslint-disable-next-line eslint-multitab-tt/no-getactions-in-actions
|
||||||
|
getActions().sendMessages({ sendParams });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const messageIdsForUndo = sendParams.reduce((ids, params) => {
|
||||||
|
if (params.localMessage?.id) {
|
||||||
|
ids.push(params.localMessage.id);
|
||||||
|
} else if (params.forwardedLocalMessagesSlice?.localMessages) {
|
||||||
|
const forwardedIds = Object.values(params.forwardedLocalMessagesSlice.localMessages)
|
||||||
|
.map((forwardedMessage) => forwardedMessage.id)
|
||||||
|
.filter(Boolean);
|
||||||
|
ids.push(...forwardedIds);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}, [] as number[]);
|
||||||
|
|
||||||
|
const localForwards = sendParams[0]?.forwardedLocalMessagesSlice?.localMessages;
|
||||||
|
const firstMessage = sendParams[0]?.localMessage
|
||||||
|
|| (localForwards && Object.values(localForwards)[0]);
|
||||||
|
if (!firstMessage) return;
|
||||||
|
|
||||||
|
const messagesCount = messageIdsForUndo.length;
|
||||||
|
|
||||||
|
const firstSendParam = sendParams[0];
|
||||||
|
let storySendMessage: RegularLangFnParameters | undefined;
|
||||||
|
if (sendParams.length === 1 && firstSendParam.isStoryReply) {
|
||||||
|
const { gif, sticker, isReaction } = firstSendParam;
|
||||||
|
|
||||||
|
if (gif) {
|
||||||
|
storySendMessage = { key: 'ToastTitleMessageSent' };
|
||||||
|
} else if (sticker) {
|
||||||
|
storySendMessage = { key: 'StoryTooltipStickerSent' };
|
||||||
|
} else if (isReaction) {
|
||||||
|
storySendMessage = { key: 'StoryTooltipReactionSent' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleKey: RegularLangFnParameters = storySendMessage || (messagesCount === 1 ? { key: 'ToastTitleMessageSent' }
|
||||||
|
: { key: 'ToastTitleMessagesSent', variables: { count: messagesCount } });
|
||||||
|
|
||||||
|
// eslint-disable-next-line eslint-multitab-tt/no-getactions-in-actions
|
||||||
|
getActions().showNotification({
|
||||||
|
localId: getMessageKey(firstMessage),
|
||||||
|
title: titleKey,
|
||||||
|
message: { key: 'ToastMessageSent', variables: { amount: starsForOneMessage * messagesCount } },
|
||||||
|
actionText: { key: 'ButtonUndo' },
|
||||||
|
action: {
|
||||||
|
action: 'deleteMessages',
|
||||||
|
payload: { messageList: firstSendParam.messageList, messageIds: messageIdsForUndo, shouldDeleteForAll: true },
|
||||||
|
},
|
||||||
|
dismissAction: {
|
||||||
|
action: 'sendMessages',
|
||||||
|
payload: {
|
||||||
|
sendParams,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
duration: PAID_SEND_DELAY,
|
||||||
|
shouldShowTimer: true,
|
||||||
|
disableClickDismiss: true,
|
||||||
|
icon: 'star',
|
||||||
|
shouldUseCustomIcon: true,
|
||||||
|
type: 'paidMessage',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addActionHandler('sendMessages', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { sendParams } = payload;
|
||||||
|
await Promise.all(sendParams.map(async (params) => {
|
||||||
|
if (params.forwardedLocalMessagesSlice && params.forwardParams) {
|
||||||
|
await rafPromise();
|
||||||
|
await callApi('forwardApiMessages', params.forwardParams);
|
||||||
|
} else {
|
||||||
|
await sendMessage(global, params);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
if (sendParams.length > 0 && sendParams[0].messagePriceInStars) actions.loadStarStatus();
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('loadPinnedMessages', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadPinnedMessages', async (global, actions, payload): Promise<void> => {
|
||||||
const { chatId, threadId } = payload;
|
const { chatId, threadId } = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
|
|||||||
@ -408,6 +408,7 @@ addActionHandler('loadPrivacySettings', async (global): Promise<void> => {
|
|||||||
callApi('fetchPrivacySettings', 'bio'),
|
callApi('fetchPrivacySettings', 'bio'),
|
||||||
callApi('fetchPrivacySettings', 'birthday'),
|
callApi('fetchPrivacySettings', 'birthday'),
|
||||||
callApi('fetchPrivacySettings', 'gifts'),
|
callApi('fetchPrivacySettings', 'gifts'),
|
||||||
|
callApi('fetchPrivacySettings', 'noPaidMessages'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (result.some((e) => e === undefined)) {
|
if (result.some((e) => e === undefined)) {
|
||||||
@ -427,6 +428,7 @@ addActionHandler('loadPrivacySettings', async (global): Promise<void> => {
|
|||||||
bioSettings,
|
bioSettings,
|
||||||
birthdaySettings,
|
birthdaySettings,
|
||||||
giftsSettings,
|
giftsSettings,
|
||||||
|
noPaidMessagesSettings,
|
||||||
] = result as {
|
] = result as {
|
||||||
rules: ApiPrivacySettings;
|
rules: ApiPrivacySettings;
|
||||||
}[];
|
}[];
|
||||||
@ -450,6 +452,7 @@ addActionHandler('loadPrivacySettings', async (global): Promise<void> => {
|
|||||||
bio: bioSettings.rules,
|
bio: bioSettings.rules,
|
||||||
birthday: birthdaySettings.rules,
|
birthday: birthdaySettings.rules,
|
||||||
gifts: giftsSettings.rules,
|
gifts: giftsSettings.rules,
|
||||||
|
noPaidMessages: noPaidMessagesSettings.rules,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -703,14 +706,24 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload)
|
|||||||
const shouldHideReadMarks = payload.shouldHideReadMarks ?? Boolean(global.settings.byKey.shouldHideReadMarks);
|
const shouldHideReadMarks = payload.shouldHideReadMarks ?? Boolean(global.settings.byKey.shouldHideReadMarks);
|
||||||
const shouldNewNonContactPeersRequirePremium = payload.shouldNewNonContactPeersRequirePremium
|
const shouldNewNonContactPeersRequirePremium = payload.shouldNewNonContactPeersRequirePremium
|
||||||
?? Boolean(global.settings.byKey.shouldNewNonContactPeersRequirePremium);
|
?? Boolean(global.settings.byKey.shouldNewNonContactPeersRequirePremium);
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const nonContactPeersPaidStars = payload.nonContactPeersPaidStars === null ? undefined
|
||||||
|
: payload.nonContactPeersPaidStars || global.settings.byKey.nonContactPeersPaidStars;
|
||||||
|
|
||||||
global = replaceSettings(global, { shouldArchiveAndMuteNewNonContact, shouldHideReadMarks });
|
global = getGlobal();
|
||||||
|
global = replaceSettings(global, {
|
||||||
|
shouldArchiveAndMuteNewNonContact,
|
||||||
|
shouldHideReadMarks,
|
||||||
|
shouldNewNonContactPeersRequirePremium,
|
||||||
|
nonContactPeersPaidStars,
|
||||||
|
});
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
const result = await callApi('updateGlobalPrivacySettings', {
|
const result = await callApi('updateGlobalPrivacySettings', {
|
||||||
shouldArchiveAndMuteNewNonContact,
|
shouldArchiveAndMuteNewNonContact,
|
||||||
shouldHideReadMarks,
|
shouldHideReadMarks,
|
||||||
shouldNewNonContactPeersRequirePremium,
|
shouldNewNonContactPeersRequirePremium,
|
||||||
|
nonContactPeersPaidStars,
|
||||||
});
|
});
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
@ -722,6 +735,9 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload)
|
|||||||
shouldNewNonContactPeersRequirePremium: !result
|
shouldNewNonContactPeersRequirePremium: !result
|
||||||
? !shouldNewNonContactPeersRequirePremium
|
? !shouldNewNonContactPeersRequirePremium
|
||||||
: result.shouldNewNonContactPeersRequirePremium,
|
: result.shouldNewNonContactPeersRequirePremium,
|
||||||
|
nonContactPeersPaidStars: !result
|
||||||
|
? undefined
|
||||||
|
: result.nonContactPeersPaidStars,
|
||||||
});
|
});
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -184,6 +184,26 @@ addActionHandler('loadCommonChats', async (global, actions, payload): Promise<vo
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('addNoPaidMessagesException', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { userId, shouldRefundCharged } = payload;
|
||||||
|
const user = selectUser(global, userId);
|
||||||
|
if (!user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await callApi('addNoPaidMessagesException',
|
||||||
|
{ user, shouldRefundCharged });
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateUserFullInfo(global, userId, {
|
||||||
|
settings: undefined,
|
||||||
|
});
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('updateContact', async (global, actions, payload): Promise<void> => {
|
addActionHandler('updateContact', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
userId, isMuted = false, firstName, lastName, shouldSharePhoneNumber,
|
userId, isMuted = false, firstName, lastName, shouldSharePhoneNumber,
|
||||||
|
|||||||
@ -62,6 +62,7 @@ import {
|
|||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectIsViewportNewest,
|
selectIsViewportNewest,
|
||||||
selectMessageIdsByGroupId,
|
selectMessageIdsByGroupId,
|
||||||
|
selectPeer,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
selectReplyStack,
|
selectReplyStack,
|
||||||
selectRequestedChatTranslationLanguage,
|
selectRequestedChatTranslationLanguage,
|
||||||
@ -71,6 +72,7 @@ import {
|
|||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
import { getPeerStarsForMessage } from '../api/messages';
|
||||||
|
|
||||||
import { getIsMobile } from '../../../hooks/useAppLayout';
|
import { getIsMobile } from '../../../hooks/useAppLayout';
|
||||||
|
|
||||||
@ -1093,3 +1095,39 @@ addActionHandler('closeSharePreparedMessageModal', (global, actions, payload): A
|
|||||||
sharePreparedMessageModal: undefined,
|
sharePreparedMessageModal: undefined,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('updateSharePreparedMessageModalSendArgs', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { args, tabId = getCurrentTabId() } = payload || {};
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
|
||||||
|
if (!tabState.sharePreparedMessageModal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args) {
|
||||||
|
global = updateTabState(global, {
|
||||||
|
sharePreparedMessageModal: {
|
||||||
|
...tabState.sharePreparedMessageModal,
|
||||||
|
pendingSendArgs: undefined,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const peer = selectPeer(global, args.peerId);
|
||||||
|
const starsForSendMessage = peer ? await getPeerStarsForMessage(global, peer) : undefined;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
sharePreparedMessageModal: {
|
||||||
|
...tabState.sharePreparedMessageModal,
|
||||||
|
pendingSendArgs: {
|
||||||
|
peerId: args.peerId,
|
||||||
|
threadId: args.threadId,
|
||||||
|
starsForSendMessage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|||||||
@ -542,6 +542,21 @@ addActionHandler('hideEffectInComposer', (global, actions, payload): ActionRetur
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('setPaidMessageAutoApprove', (global): ActionReturnType => {
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
settings: {
|
||||||
|
...global.settings,
|
||||||
|
byKey: {
|
||||||
|
...global.settings.byKey,
|
||||||
|
shouldPaidMessageAutoApprove: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return global;
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('setReactionEffect', (global, actions, payload): ActionReturnType => {
|
addActionHandler('setReactionEffect', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
chatId, threadId, reaction, tabId = getCurrentTabId(),
|
chatId, threadId, reaction, tabId = getCurrentTabId(),
|
||||||
|
|||||||
@ -135,3 +135,19 @@ addActionHandler('resetGiftProfileFilter', (global, actions, payload): ActionRet
|
|||||||
peerId, shouldRefresh: true, tabId: tabState.id,
|
peerId, shouldRefresh: true, tabId: tabState.id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openPaymentMessageConfirmDialogOpen', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
isPaymentMessageConfirmDialogOpen: true,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closePaymentMessageConfirmDialogOpen', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
isPaymentMessageConfirmDialogOpen: false,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
|||||||
import { addStoriesForPeer } from '../../reducers';
|
import { addStoriesForPeer } from '../../reducers';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
|
selectChat,
|
||||||
selectCurrentViewedStory,
|
selectCurrentViewedStory,
|
||||||
selectPeer,
|
selectPeer,
|
||||||
selectPeerFirstStoryId,
|
selectPeerFirstStoryId,
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { fetchChatByUsername } from '../api/chats';
|
import { fetchChatByUsername } from '../api/chats';
|
||||||
|
import { getPeerStarsForMessage } from '../api/messages';
|
||||||
|
|
||||||
addActionHandler('openStoryViewer', async (global, actions, payload): Promise<void> => {
|
addActionHandler('openStoryViewer', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
@ -289,12 +291,16 @@ addActionHandler('copyStoryLink', async (global, actions, payload): Promise<void
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('sendMessage', (global, actions, payload): ActionReturnType => {
|
addActionHandler('sendMessage', async (global, actions, payload): Promise<void> => {
|
||||||
const { tabId = getCurrentTabId() } = payload;
|
const { tabId = getCurrentTabId() } = payload;
|
||||||
const { storyId, peerId: storyPeerId } = selectCurrentViewedStory(global, tabId);
|
const { storyId, peerId: storyPeerId } = selectCurrentViewedStory(global, tabId);
|
||||||
const isStoryReply = Boolean(storyId && storyPeerId);
|
const isStoryReply = Boolean(storyId && storyPeerId);
|
||||||
|
|
||||||
if (!isStoryReply) {
|
const chat = storyPeerId ? selectChat(global, storyPeerId) : undefined;
|
||||||
|
if (!chat) return;
|
||||||
|
const messagePriceInStars = await getPeerStarsForMessage(global, chat);
|
||||||
|
|
||||||
|
if (!isStoryReply || messagePriceInStars) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -271,6 +271,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
shouldSuggestStickers: true,
|
shouldSuggestStickers: true,
|
||||||
shouldSuggestCustomEmoji: true,
|
shouldSuggestCustomEmoji: true,
|
||||||
shouldSkipWebAppCloseConfirmation: false,
|
shouldSkipWebAppCloseConfirmation: false,
|
||||||
|
shouldPaidMessageAutoApprove: false,
|
||||||
shouldUpdateStickerSetOrder: true,
|
shouldUpdateStickerSetOrder: true,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
timeFormat: '24h',
|
timeFormat: '24h',
|
||||||
@ -278,6 +279,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
isConnectionStatusMinimized: true,
|
isConnectionStatusMinimized: true,
|
||||||
shouldArchiveAndMuteNewNonContact: false,
|
shouldArchiveAndMuteNewNonContact: false,
|
||||||
shouldNewNonContactPeersRequirePremium: false,
|
shouldNewNonContactPeersRequirePremium: false,
|
||||||
|
nonContactPeersPaidStars: 0,
|
||||||
shouldHideReadMarks: false,
|
shouldHideReadMarks: false,
|
||||||
canTranslate: false,
|
canTranslate: false,
|
||||||
canTranslateChats: true,
|
canTranslateChats: true,
|
||||||
@ -423,4 +425,6 @@ export const INITIAL_TAB_STATE: TabState = {
|
|||||||
requestedTranslations: {
|
requestedTranslations: {
|
||||||
byChatId: {},
|
byChatId: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isPaymentMessageConfirmDialogOpen: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -73,7 +73,7 @@ import {
|
|||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
selectRequestedChatTranslationLanguage,
|
selectRequestedChatTranslationLanguage,
|
||||||
} from './chats';
|
} from './chats';
|
||||||
import { selectPeer } from './peers';
|
import { selectPeer, selectPeerPaidMessagesStars } from './peers';
|
||||||
import { selectPeerStory } from './stories';
|
import { selectPeerStory } from './stories';
|
||||||
import { selectIsStickerFavorite } from './symbols';
|
import { selectIsStickerFavorite } from './symbols';
|
||||||
import { selectTabState } from './tabs';
|
import { selectTabState } from './tabs';
|
||||||
@ -1331,12 +1331,21 @@ export function selectShouldSchedule<T extends GlobalState>(
|
|||||||
return selectCurrentMessageList(global, tabId)?.type === 'scheduled';
|
return selectCurrentMessageList(global, tabId)?.type === 'scheduled';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectCanSchedule<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
) {
|
||||||
|
const chatId = selectCurrentMessageList(global, tabId)?.chatId;
|
||||||
|
const paidMessagesStars = chatId ? selectPeerPaidMessagesStars(global, chatId) : undefined;
|
||||||
|
return !paidMessagesStars;
|
||||||
|
}
|
||||||
|
|
||||||
export function selectCanScheduleUntilOnline<T extends GlobalState>(global: T, id: string) {
|
export function selectCanScheduleUntilOnline<T extends GlobalState>(global: T, id: string) {
|
||||||
const isChatWithSelf = selectIsChatWithSelf(global, id);
|
const isChatWithSelf = selectIsChatWithSelf(global, id);
|
||||||
const chatBot = selectBot(global, id);
|
const chatBot = selectBot(global, id);
|
||||||
return Boolean(
|
const paidMessagesStars = selectPeerPaidMessagesStars(global, id);
|
||||||
!isChatWithSelf && !chatBot && isUserId(id) && selectUserStatus(global, id)?.wasOnline,
|
return Boolean(!paidMessagesStars
|
||||||
);
|
&& !isChatWithSelf && !chatBot && isUserId(id) && selectUserStatus(global, id)?.wasOnline);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectCustomEmojis(message: ApiMessage) {
|
export function selectCustomEmojis(message: ApiMessage) {
|
||||||
|
|||||||
@ -3,10 +3,10 @@ import type { GlobalState, TabArgs } from '../types';
|
|||||||
|
|
||||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||||
import { isDeletedUser } from '../helpers';
|
import { isChatAdmin, isDeletedUser, isUserId } from '../helpers';
|
||||||
import { selectChat, selectChatFullInfo } from './chats';
|
import { selectChat, selectChatFullInfo } from './chats';
|
||||||
import { selectTabState } from './tabs';
|
import { selectTabState } from './tabs';
|
||||||
import { selectBot, selectUser } from './users';
|
import { selectBot, selectUser, selectUserFullInfo } from './users';
|
||||||
|
|
||||||
export function selectPeer<T extends GlobalState>(global: T, peerId: string): ApiPeer | undefined {
|
export function selectPeer<T extends GlobalState>(global: T, peerId: string): ApiPeer | undefined {
|
||||||
return selectUser(global, peerId) || selectChat(global, peerId);
|
return selectUser(global, peerId) || selectChat(global, peerId);
|
||||||
@ -34,3 +34,19 @@ export function selectPeerSavedGifts<T extends GlobalState>(
|
|||||||
) : ApiSavedGifts {
|
) : ApiSavedGifts {
|
||||||
return selectTabState(global, tabId).savedGifts.giftsByPeerId[peerId];
|
return selectTabState(global, tabId).savedGifts.giftsByPeerId[peerId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectPeerPaidMessagesStars<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
peerId: string,
|
||||||
|
) {
|
||||||
|
const isChatWithUser = isUserId(peerId);
|
||||||
|
if (isChatWithUser) {
|
||||||
|
const userFullInfo = isChatWithUser ? selectUserFullInfo(global, peerId) : undefined;
|
||||||
|
return userFullInfo?.paidMessagesStars;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chat = selectChat(global, peerId);
|
||||||
|
if (!chat) return undefined;
|
||||||
|
if (isChatAdmin(chat)) return undefined;
|
||||||
|
return chat.paidMessagesStars;
|
||||||
|
}
|
||||||
|
|||||||
@ -24,6 +24,10 @@ export function selectNewNoncontactPeersRequirePremium<T extends GlobalState>(gl
|
|||||||
return global.settings.byKey.shouldNewNonContactPeersRequirePremium;
|
return global.settings.byKey.shouldNewNonContactPeersRequirePremium;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectNonContactPeersPaidStars<T extends GlobalState>(global: T) {
|
||||||
|
return global.settings.byKey.nonContactPeersPaidStars;
|
||||||
|
}
|
||||||
|
|
||||||
export function selectShouldHideReadMarks<T extends GlobalState>(global: T) {
|
export function selectShouldHideReadMarks<T extends GlobalState>(global: T) {
|
||||||
return global.settings.byKey.shouldHideReadMarks;
|
return global.settings.byKey.shouldHideReadMarks;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import type {
|
|||||||
ApiChatlistInvite,
|
ApiChatlistInvite,
|
||||||
ApiChatReactions,
|
ApiChatReactions,
|
||||||
ApiChatType,
|
ApiChatType,
|
||||||
ApiContact,
|
|
||||||
ApiDraft,
|
ApiDraft,
|
||||||
ApiExportedInvite,
|
ApiExportedInvite,
|
||||||
ApiFormattedText,
|
ApiFormattedText,
|
||||||
@ -23,10 +22,10 @@ import type {
|
|||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
ApiMessageSearchContext,
|
ApiMessageSearchContext,
|
||||||
ApiNewPoll,
|
|
||||||
ApiNotification,
|
ApiNotification,
|
||||||
ApiNotifyPeerType,
|
ApiNotifyPeerType,
|
||||||
ApiPaymentStatus,
|
ApiPaymentStatus,
|
||||||
|
ApiPeer,
|
||||||
ApiPhoto,
|
ApiPhoto,
|
||||||
ApiPremiumSection,
|
ApiPremiumSection,
|
||||||
ApiPreparedInlineMessage,
|
ApiPreparedInlineMessage,
|
||||||
@ -80,6 +79,7 @@ import type {
|
|||||||
Point,
|
Point,
|
||||||
ProfileTabType,
|
ProfileTabType,
|
||||||
ScrollTargetPosition,
|
ScrollTargetPosition,
|
||||||
|
SendMessageParams,
|
||||||
SettingsScreens,
|
SettingsScreens,
|
||||||
SharedMediaType,
|
SharedMediaType,
|
||||||
Size,
|
Size,
|
||||||
@ -441,25 +441,10 @@ export interface ActionPayloads {
|
|||||||
onLoaded?: NoneToVoidFunction;
|
onLoaded?: NoneToVoidFunction;
|
||||||
onError?: NoneToVoidFunction;
|
onError?: NoneToVoidFunction;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
sendMessage: {
|
sendMessage: Partial<SendMessageParams> & WithTabId;
|
||||||
text?: string;
|
sendMessages: {
|
||||||
entities?: ApiMessageEntity[];
|
sendParams: SendMessageParams[];
|
||||||
attachments?: ApiAttachment[];
|
};
|
||||||
sticker?: ApiSticker;
|
|
||||||
isSilent?: boolean;
|
|
||||||
scheduledAt?: number;
|
|
||||||
gif?: ApiVideo;
|
|
||||||
poll?: ApiNewPoll;
|
|
||||||
contact?: Partial<ApiContact>;
|
|
||||||
shouldUpdateStickerSetOrder?: boolean;
|
|
||||||
shouldGroupMessages?: boolean;
|
|
||||||
messageList?: MessageList;
|
|
||||||
isReaction?: true; // Reaction to the story are sent in the form of a message
|
|
||||||
isInvertedMedia?: true;
|
|
||||||
effectId?: string;
|
|
||||||
webPageMediaSize?: WebPageMediaSize;
|
|
||||||
webPageUrl?: string;
|
|
||||||
} & WithTabId;
|
|
||||||
sendInviteMessages: {
|
sendInviteMessages: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
userIds: string[];
|
userIds: string[];
|
||||||
@ -478,7 +463,9 @@ export interface ActionPayloads {
|
|||||||
deleteMessages: {
|
deleteMessages: {
|
||||||
messageIds: number[];
|
messageIds: number[];
|
||||||
shouldDeleteForAll?: boolean;
|
shouldDeleteForAll?: boolean;
|
||||||
|
messageList?: MessageList;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
resetLocalPaidMessages: WithTabId | undefined;
|
||||||
deleteParticipantHistory: {
|
deleteParticipantHistory: {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -543,6 +530,12 @@ export interface ActionPayloads {
|
|||||||
message: ApiPreparedInlineMessage;
|
message: ApiPreparedInlineMessage;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
closeSharePreparedMessageModal: WithTabId | undefined;
|
closeSharePreparedMessageModal: WithTabId | undefined;
|
||||||
|
updateSharePreparedMessageModalSendArgs: {
|
||||||
|
args?: {
|
||||||
|
peerId: string;
|
||||||
|
threadId?: ThreadId;
|
||||||
|
};
|
||||||
|
} & WithTabId;
|
||||||
openPreviousReportAdModal: WithTabId | undefined;
|
openPreviousReportAdModal: WithTabId | undefined;
|
||||||
openPreviousReportModal: WithTabId | undefined;
|
openPreviousReportModal: WithTabId | undefined;
|
||||||
closeReportAdModal: WithTabId | undefined;
|
closeReportAdModal: WithTabId | undefined;
|
||||||
@ -1744,6 +1737,10 @@ export interface ActionPayloads {
|
|||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
shouldSharePhoneNumber?: boolean;
|
shouldSharePhoneNumber?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
addNoPaidMessagesException: {
|
||||||
|
userId: string;
|
||||||
|
shouldRefundCharged: boolean;
|
||||||
|
};
|
||||||
loadMoreProfilePhotos: {
|
loadMoreProfilePhotos: {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
isPreload?: boolean;
|
isPreload?: boolean;
|
||||||
@ -1902,7 +1899,18 @@ export interface ActionPayloads {
|
|||||||
threadId: ThreadId;
|
threadId: ThreadId;
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
|
paidMessagesStars?: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
sendInlineBotApiResult: {
|
||||||
|
chat: ApiChat;
|
||||||
|
id: string;
|
||||||
|
queryId: string;
|
||||||
|
replyInfo?: ApiInputMessageReplyInfo;
|
||||||
|
sendAs?: ApiPeer;
|
||||||
|
isSilent?: boolean;
|
||||||
|
scheduledAt?: number;
|
||||||
|
allowPaidStars?: number;
|
||||||
|
};
|
||||||
resetInlineBot: {
|
resetInlineBot: {
|
||||||
username: string;
|
username: string;
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
@ -2154,6 +2162,7 @@ export interface ActionPayloads {
|
|||||||
|
|
||||||
requestEffectInComposer: WithTabId;
|
requestEffectInComposer: WithTabId;
|
||||||
hideEffectInComposer: WithTabId;
|
hideEffectInComposer: WithTabId;
|
||||||
|
setPaidMessageAutoApprove: undefined;
|
||||||
|
|
||||||
updateArchiveSettings: {
|
updateArchiveSettings: {
|
||||||
isMinimized?: boolean;
|
isMinimized?: boolean;
|
||||||
@ -2304,6 +2313,7 @@ export interface ActionPayloads {
|
|||||||
shouldArchiveAndMuteNewNonContact?: boolean;
|
shouldArchiveAndMuteNewNonContact?: boolean;
|
||||||
shouldHideReadMarks?: boolean;
|
shouldHideReadMarks?: boolean;
|
||||||
shouldNewNonContactPeersRequirePremium?: boolean;
|
shouldNewNonContactPeersRequirePremium?: boolean;
|
||||||
|
nonContactPeersPaidStars?: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Premium
|
// Premium
|
||||||
@ -2475,6 +2485,9 @@ export interface ActionPayloads {
|
|||||||
status: ApiPaymentStatus;
|
status: ApiPaymentStatus;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
|
openPaymentMessageConfirmDialogOpen: WithTabId | undefined;
|
||||||
|
closePaymentMessageConfirmDialogOpen: WithTabId | undefined;
|
||||||
|
|
||||||
// Forums
|
// Forums
|
||||||
toggleForum: {
|
toggleForum: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
|
|||||||
@ -278,6 +278,8 @@ export type TabState = {
|
|||||||
byChatId: Record<string, ManagementState>;
|
byChatId: Record<string, ManagementState>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
isPaymentMessageConfirmDialogOpen: boolean;
|
||||||
|
|
||||||
storyViewer: {
|
storyViewer: {
|
||||||
isRibbonShown?: boolean;
|
isRibbonShown?: boolean;
|
||||||
isArchivedRibbonShown?: boolean;
|
isArchivedRibbonShown?: boolean;
|
||||||
@ -292,6 +294,7 @@ export type TabState = {
|
|||||||
// Used for better switch animation between peers.
|
// Used for better switch animation between peers.
|
||||||
lastViewedByPeerIds?: Record<string, number>;
|
lastViewedByPeerIds?: Record<string, number>;
|
||||||
isPrivacyModalOpen?: boolean;
|
isPrivacyModalOpen?: boolean;
|
||||||
|
isPaymentConfirmDialogOpen?: boolean;
|
||||||
isStealthModalOpen?: boolean;
|
isStealthModalOpen?: boolean;
|
||||||
viewModal?: {
|
viewModal?: {
|
||||||
storyId: number;
|
storyId: number;
|
||||||
@ -509,6 +512,11 @@ export type TabState = {
|
|||||||
webAppKey: string;
|
webAppKey: string;
|
||||||
message: ApiPreparedInlineMessage;
|
message: ApiPreparedInlineMessage;
|
||||||
filter: ApiChatType[];
|
filter: ApiChatType[];
|
||||||
|
pendingSendArgs?: {
|
||||||
|
peerId: string;
|
||||||
|
threadId?: ThreadId;
|
||||||
|
starsForSendMessage?: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
webApps: {
|
webApps: {
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export function useViewTransition(): ViewTransitionController {
|
|||||||
|
|
||||||
transition.ready.then(() => {
|
transition.ready.then(() => {
|
||||||
setTransitionState('animating');
|
setTransitionState('animating');
|
||||||
}).catch((e) => {
|
}).catch((e:unknown) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setTransitionState('skipped');
|
setTransitionState('skipped');
|
||||||
|
|||||||
@ -1472,8 +1472,10 @@ account.toggleUsername#58d6b376 username:string active:Bool = Bool;
|
|||||||
account.resolveBusinessChatLink#5492e5ee slug:string = account.ResolvedBusinessChatLinks;
|
account.resolveBusinessChatLink#5492e5ee slug:string = account.ResolvedBusinessChatLinks;
|
||||||
account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool;
|
account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool;
|
||||||
account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses;
|
account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses;
|
||||||
|
account.addNoPaidMessagesException#6f688aa7 flags:# refund_charged:flags.0?true user_id:InputUser = Bool;
|
||||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||||
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
|
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
|
||||||
|
users.getRequirementsToContact#d89a83a3 id:Vector<InputUser> = Vector<RequirementToContact>;
|
||||||
contacts.getContacts#5dd69e12 hash:long = contacts.Contacts;
|
contacts.getContacts#5dd69e12 hash:long = contacts.Contacts;
|
||||||
contacts.importContacts#2c800be5 contacts:Vector<InputContact> = contacts.ImportedContacts;
|
contacts.importContacts#2c800be5 contacts:Vector<InputContact> = contacts.ImportedContacts;
|
||||||
contacts.deleteContacts#96a0e00 id:Vector<InputUser> = Updates;
|
contacts.deleteContacts#96a0e00 id:Vector<InputUser> = Updates;
|
||||||
|
|||||||
@ -61,6 +61,7 @@
|
|||||||
"account.resolveBusinessChatLink",
|
"account.resolveBusinessChatLink",
|
||||||
"account.toggleSponsoredMessages",
|
"account.toggleSponsoredMessages",
|
||||||
"account.getCollectibleEmojiStatuses",
|
"account.getCollectibleEmojiStatuses",
|
||||||
|
"account.addNoPaidMessagesException",
|
||||||
"users.getUsers",
|
"users.getUsers",
|
||||||
"users.getFullUser",
|
"users.getFullUser",
|
||||||
"contacts.getContacts",
|
"contacts.getContacts",
|
||||||
|
|||||||
@ -103,6 +103,7 @@ $color-message-story-mention-to: #74bcff;
|
|||||||
--color-voice-transcribe-button: #e8f3ff;
|
--color-voice-transcribe-button: #e8f3ff;
|
||||||
--color-voice-transcribe-button-own: #cceebf;
|
--color-voice-transcribe-button-own: #cceebf;
|
||||||
|
|
||||||
|
--color-toast-action: #64D1FF;
|
||||||
--color-primary: #{$color-primary};
|
--color-primary: #{$color-primary};
|
||||||
--color-primary-shade: #{color.mix($color-primary, $color-black, 92%)};
|
--color-primary-shade: #{color.mix($color-primary, $color-black, 92%)};
|
||||||
--color-primary-shade-darker: #{color.mix($color-primary, $color-black, 84%)};
|
--color-primary-shade-darker: #{color.mix($color-primary, $color-black, 84%)};
|
||||||
|
|||||||
@ -1,26 +1,36 @@
|
|||||||
import type { TeactNode } from '../lib/teact/teact';
|
import type { TeactNode } from '../lib/teact/teact';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ApiAttachment,
|
||||||
ApiBotInlineMediaResult,
|
ApiBotInlineMediaResult,
|
||||||
ApiBotInlineResult,
|
ApiBotInlineResult,
|
||||||
ApiBotInlineSwitchPm,
|
ApiBotInlineSwitchPm,
|
||||||
ApiBotInlineSwitchWebview,
|
ApiBotInlineSwitchWebview,
|
||||||
|
ApiChat,
|
||||||
ApiChatInviteImporter,
|
ApiChatInviteImporter,
|
||||||
|
ApiContact,
|
||||||
ApiDocument,
|
ApiDocument,
|
||||||
ApiDraft,
|
ApiDraft,
|
||||||
ApiExportedInvite,
|
ApiExportedInvite,
|
||||||
ApiFakeType,
|
ApiFakeType,
|
||||||
ApiFormattedText,
|
ApiFormattedText,
|
||||||
|
ApiInputReplyInfo,
|
||||||
ApiLabeledPrice,
|
ApiLabeledPrice,
|
||||||
ApiMediaFormat,
|
ApiMediaFormat,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
|
ApiMessageEntity,
|
||||||
|
ApiNewPoll,
|
||||||
|
ApiPeer,
|
||||||
ApiPhoto,
|
ApiPhoto,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiReactionWithPaid,
|
ApiReactionWithPaid,
|
||||||
ApiStarGiftRegular,
|
ApiStarGiftRegular,
|
||||||
ApiStarsSubscription,
|
ApiStarsSubscription,
|
||||||
ApiStarsTransaction,
|
ApiStarsTransaction,
|
||||||
|
ApiSticker,
|
||||||
ApiStickerSet,
|
ApiStickerSet,
|
||||||
|
ApiStory,
|
||||||
|
ApiStorySkipped,
|
||||||
ApiThreadInfo,
|
ApiThreadInfo,
|
||||||
ApiTopic,
|
ApiTopic,
|
||||||
ApiTypingStatus,
|
ApiTypingStatus,
|
||||||
@ -114,6 +124,7 @@ export interface ISettings {
|
|||||||
isConnectionStatusMinimized: boolean;
|
isConnectionStatusMinimized: boolean;
|
||||||
shouldArchiveAndMuteNewNonContact?: boolean;
|
shouldArchiveAndMuteNewNonContact?: boolean;
|
||||||
shouldNewNonContactPeersRequirePremium?: boolean;
|
shouldNewNonContactPeersRequirePremium?: boolean;
|
||||||
|
nonContactPeersPaidStars?: number;
|
||||||
shouldHideReadMarks?: boolean;
|
shouldHideReadMarks?: boolean;
|
||||||
canTranslate: boolean;
|
canTranslate: boolean;
|
||||||
canTranslateChats: boolean;
|
canTranslateChats: boolean;
|
||||||
@ -126,6 +137,7 @@ export interface ISettings {
|
|||||||
shouldDebugExportedSenders?: boolean;
|
shouldDebugExportedSenders?: boolean;
|
||||||
shouldWarnAboutSvg?: boolean;
|
shouldWarnAboutSvg?: boolean;
|
||||||
shouldSkipWebAppCloseConfirmation: boolean;
|
shouldSkipWebAppCloseConfirmation: boolean;
|
||||||
|
shouldPaidMessageAutoApprove: boolean;
|
||||||
hasContactJoinedNotifications?: boolean;
|
hasContactJoinedNotifications?: boolean;
|
||||||
hasWebNotifications: boolean;
|
hasWebNotifications: boolean;
|
||||||
hasPushNotifications: boolean;
|
hasPushNotifications: boolean;
|
||||||
@ -191,6 +203,7 @@ export enum SettingsScreens {
|
|||||||
PrivacyGroupChatsAllowedContacts,
|
PrivacyGroupChatsAllowedContacts,
|
||||||
PrivacyGroupChatsDeniedContacts,
|
PrivacyGroupChatsDeniedContacts,
|
||||||
PrivacyBlockedUsers,
|
PrivacyBlockedUsers,
|
||||||
|
PrivacyNoPaidMessages,
|
||||||
Performance,
|
Performance,
|
||||||
Folders,
|
Folders,
|
||||||
FoldersCreateFolder,
|
FoldersCreateFolder,
|
||||||
@ -653,3 +666,63 @@ export type GiftProfileFilterOptions = {
|
|||||||
shouldIncludeDisplayed: boolean;
|
shouldIncludeDisplayed: boolean;
|
||||||
shouldIncludeHidden: boolean;
|
shouldIncludeHidden: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SendMessageParams = {
|
||||||
|
chat?: ApiChat;
|
||||||
|
attachments?: ApiAttachment[];
|
||||||
|
lastMessageId?: number;
|
||||||
|
text?: string;
|
||||||
|
entities?: ApiMessageEntity[];
|
||||||
|
replyInfo?: ApiInputReplyInfo;
|
||||||
|
attachment?: ApiAttachment;
|
||||||
|
sticker?: ApiSticker;
|
||||||
|
story?: ApiStory | ApiStorySkipped;
|
||||||
|
gif?: ApiVideo;
|
||||||
|
poll?: ApiNewPoll;
|
||||||
|
contact?: ApiContact;
|
||||||
|
isSilent?: boolean;
|
||||||
|
scheduledAt?: number;
|
||||||
|
groupedId?: string;
|
||||||
|
noWebPage?: boolean;
|
||||||
|
sendAs?: ApiPeer;
|
||||||
|
shouldGroupMessages?: boolean;
|
||||||
|
shouldUpdateStickerSetOrder?: boolean;
|
||||||
|
wasDrafted?: boolean;
|
||||||
|
isInvertedMedia?: true;
|
||||||
|
effectId?: string;
|
||||||
|
webPageMediaSize?: WebPageMediaSize;
|
||||||
|
webPageUrl?: string;
|
||||||
|
starsAmount?: number;
|
||||||
|
isPending?: true;
|
||||||
|
messageList?: MessageList;
|
||||||
|
isReaction?: true; // Reaction to the story are sent in the form of a message
|
||||||
|
messagePriceInStars?: number;
|
||||||
|
localMessage?: ApiMessage;
|
||||||
|
forwardedLocalMessagesSlice?: ForwardedLocalMessagesSlice;
|
||||||
|
isForwarding?: boolean;
|
||||||
|
forwardParams?: ForwardMessagesParams;
|
||||||
|
isStoryReply?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ForwardedLocalMessagesSlice = {
|
||||||
|
messageIds: number[];
|
||||||
|
localMessages: ApiMessage[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ForwardMessagesParams = {
|
||||||
|
fromChat: ApiChat;
|
||||||
|
toChat: ApiChat;
|
||||||
|
toThreadId?: ThreadId;
|
||||||
|
messages: ApiMessage[];
|
||||||
|
isSilent?: boolean;
|
||||||
|
scheduledAt?: number;
|
||||||
|
sendAs?: ApiPeer;
|
||||||
|
withMyScore?: boolean;
|
||||||
|
noAuthors?: boolean;
|
||||||
|
noCaptions?: boolean;
|
||||||
|
isCurrentUserPremium?: boolean;
|
||||||
|
wasDrafted?: boolean;
|
||||||
|
lastMessageId?: number;
|
||||||
|
forwardedLocalMessagesSlice?: ForwardedLocalMessagesSlice;
|
||||||
|
messagePriceInStars?: number;
|
||||||
|
};
|
||||||
|
|||||||
82
src/types/language.d.ts
vendored
82
src/types/language.d.ts
vendored
@ -1428,6 +1428,25 @@ export interface LangPair {
|
|||||||
'GetMoreStarsLinkText': undefined;
|
'GetMoreStarsLinkText': undefined;
|
||||||
'StarsGiftCompleted': undefined;
|
'StarsGiftCompleted': undefined;
|
||||||
'GiftSent': undefined;
|
'GiftSent': undefined;
|
||||||
|
'PrivacyDescriptionMessagesContactsAndPremium': undefined;
|
||||||
|
'PrivacyChargeForMessages': undefined;
|
||||||
|
'PrivacyDescriptionChargeForMessages': undefined;
|
||||||
|
'RemoveFeeTitle': undefined;
|
||||||
|
'ExceptionTitlePrivacyChargeForMessages': undefined;
|
||||||
|
'ExceptionDescriptionPrivacyChargeForMessages': undefined;
|
||||||
|
'SectionTitleStarsForForMessages': undefined;
|
||||||
|
'SubtitlePrivacyAddUsers': undefined;
|
||||||
|
'PrivacyPaidMessagesValue': undefined;
|
||||||
|
'ButtonBuyStars': undefined;
|
||||||
|
'TitleConfirmPayment': undefined;
|
||||||
|
'ToastTitleMessageSent': undefined;
|
||||||
|
'ButtonUndo': undefined;
|
||||||
|
'ConfirmRemoveMessageFee': undefined;
|
||||||
|
'StoryTooltipGifSent': undefined;
|
||||||
|
'StoryTooltipStickerSent': undefined;
|
||||||
|
'StoryTooltipReactionSent': undefined;
|
||||||
|
'StarsNeededTextSendPaidMessages': undefined;
|
||||||
|
'PaidMessageTransactionTotal': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||||
@ -2273,6 +2292,66 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
|||||||
'stars': V;
|
'stars': V;
|
||||||
'link': V;
|
'link': V;
|
||||||
};
|
};
|
||||||
|
'SectionDescriptionStarsForForMessages': {
|
||||||
|
'percent': V;
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'SubtitlePrivacyUsersCount': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'FirstMessageInPaidMessagesChat': {
|
||||||
|
'user': V;
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'ComposerPlaceholderPaidMessage': {
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'ComposerPlaceholderPaidReply': {
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'ConfirmationModalPaymentForOneMessage': {
|
||||||
|
'user': V;
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'ConfirmationModalPaymentForMessages': {
|
||||||
|
'user': V;
|
||||||
|
'price': V;
|
||||||
|
'amount': V;
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'ButtonPayForMessage': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'ToastTitleMessagesSent': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'ToastMessageSent': {
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'ActionPaidOneMessageOutgoing': {
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'ActionPaidOneMessageIncoming': {
|
||||||
|
'amount': V;
|
||||||
|
'user': V;
|
||||||
|
};
|
||||||
|
'PaneMessagePaidMessageCharge': {
|
||||||
|
'peer': V;
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'ConfirmDialogMessageRemoveFee': {
|
||||||
|
'peer': V;
|
||||||
|
};
|
||||||
|
'ConfirmDialogRemoveFeeRefundStars': {
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'DescriptionGiftPaidMessage': {
|
||||||
|
'user': V;
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'PaidMessageTransactionDescription': {
|
||||||
|
'percent': V;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairPlural {
|
export interface LangPairPlural {
|
||||||
@ -2549,6 +2628,9 @@ export interface LangPairPluralWithVariables<V extends unknown = LangVariable> {
|
|||||||
'from': V;
|
'from': V;
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
|
'PaidMessageTransaction': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type RegularLangKey = keyof LangPair;
|
export type RegularLangKey = keyof LangPair;
|
||||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||||
|
|||||||
@ -12,11 +12,26 @@ export function formatStarsAsText(lang: LangFn, amount: number) {
|
|||||||
return lang('StarsAmountText', { amount }, { pluralValue: amount });
|
return lang('StarsAmountText', { amount }, { pluralValue: amount });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatStarsAsIcon(lang: LangFn, amount: number, options?: { asFont?: boolean; className?: string }) {
|
export function formatStarsAsIcon(lang: LangFn, amount: number, options?: {
|
||||||
const { asFont, className } = options || {};
|
asFont?: boolean; className?: string; containerClassName?: string; }) {
|
||||||
|
const { asFont, className, containerClassName } = options || {};
|
||||||
const icon = asFont
|
const icon = asFont
|
||||||
? <Icon name="star" className={buildClassName('star-amount-icon', className)} />
|
? <Icon name="star" className={buildClassName('star-amount-icon', className)} />
|
||||||
: <StarIcon type="gold" className={buildClassName('star-amount-icon', className)} size="adaptive" />;
|
: <StarIcon type="gold" className={buildClassName('star-amount-icon', className)} size="adaptive" />;
|
||||||
|
|
||||||
|
if (containerClassName) {
|
||||||
|
return (
|
||||||
|
<span className={containerClassName}>
|
||||||
|
{lang('StarsAmount', { amount }, {
|
||||||
|
withNodes: true,
|
||||||
|
specialReplacement: {
|
||||||
|
[STARS_ICON_PLACEHOLDER]: icon,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return lang('StarsAmount', { amount }, {
|
return lang('StarsAmount', { amount }, {
|
||||||
withNodes: true,
|
withNodes: true,
|
||||||
specialReplacement: {
|
specialReplacement: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user