mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-11 11:47:09 +02:00
Merge tag 'v5.12.1' into dev
This commit is contained in:
commit
adc691f516
271 changed files with 7375 additions and 2789 deletions
|
@ -9,10 +9,7 @@
|
|||
"--compile-commands-dir=${workspaceFolder}/out"
|
||||
],
|
||||
"cmake.generator": "Ninja Multi-Config",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/out",
|
||||
"cmake.configureSettings": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
"cmake.buildDirectory": "${workspaceFolder}/out"
|
||||
},
|
||||
"extensions": [
|
||||
"ms-vscode.cpptools-extension-pack",
|
||||
|
|
BIN
Telegram/Resources/icons/chat/mini_info_alert.png
Normal file
BIN
Telegram/Resources/icons/chat/mini_info_alert.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 311 B |
BIN
Telegram/Resources/icons/chat/mini_info_alert@2x.png
Normal file
BIN
Telegram/Resources/icons/chat/mini_info_alert@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 578 B |
BIN
Telegram/Resources/icons/chat/mini_info_alert@3x.png
Normal file
BIN
Telegram/Resources/icons/chat/mini_info_alert@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 829 B |
BIN
Telegram/Resources/icons/payments/premium_emoji.png
Normal file
BIN
Telegram/Resources/icons/payments/premium_emoji.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 370 B |
BIN
Telegram/Resources/icons/payments/premium_emoji@2x.png
Normal file
BIN
Telegram/Resources/icons/payments/premium_emoji@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 712 B |
BIN
Telegram/Resources/icons/payments/premium_emoji@3x.png
Normal file
BIN
Telegram/Resources/icons/payments/premium_emoji@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 926 B |
|
@ -1218,6 +1218,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_edit_privacy_contacts" = "My contacts";
|
||||
"lng_edit_privacy_close_friends" = "Close friends";
|
||||
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
|
||||
"lng_edit_privacy_paid" = "Paid";
|
||||
"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
|
||||
"lng_edit_privacy_nobody" = "Nobody";
|
||||
"lng_edit_privacy_premium" = "Premium users";
|
||||
|
@ -1356,6 +1357,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_messages_privacy_premium_about" = "Subscribe now to change this setting and get access to other exclusive features of Telegram Premium.";
|
||||
"lng_messages_privacy_premium" = "Only subscribers of {link} can select this option.";
|
||||
"lng_messages_privacy_premium_link" = "Telegram Premium";
|
||||
"lng_messages_privacy_charge" = "Charge for messages";
|
||||
"lng_messages_privacy_charge_about" = "Charge a fee for messages from people outside your contacts or those you haven't messaged first.";
|
||||
"lng_messages_privacy_price" = "Set your price per message";
|
||||
"lng_messages_privacy_price_about" = "You will receive {percent} of the selected fee ({amount}) for each incoming message.";
|
||||
"lng_messages_privacy_exceptions" = "Exceptions";
|
||||
"lng_messages_privacy_remove_fee" = "Remove Fee";
|
||||
"lng_messages_privacy_remove_about" = "Add users or entire groups who won't be charged for sending messages to you.";
|
||||
|
||||
"lng_self_destruct_title" = "Account self-destruction";
|
||||
"lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts.";
|
||||
|
@ -2158,6 +2166,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
|
||||
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
|
||||
"lng_action_payment_refunded" = "{peer} refunded {amount}";
|
||||
"lng_action_paid_message_sent#one" = "You paid {count} Star to {action}";
|
||||
"lng_action_paid_message_sent#other" = "You paid {count} Stars to {action}";
|
||||
"lng_action_paid_message_one" = "send a message";
|
||||
"lng_action_paid_message_some#one" = "send {count} message";
|
||||
"lng_action_paid_message_some#other" = "send {count} messages";
|
||||
"lng_action_paid_message_got#one" = "You received {count} Star from {name}";
|
||||
"lng_action_paid_message_got#other" = "You received {count} Stars from {name}";
|
||||
"lng_you_paid_stars#one" = "You paid {count} Star.";
|
||||
"lng_you_paid_stars#other" = "You paid {count} Stars.";
|
||||
|
||||
"lng_similar_channels_title" = "Similar channels";
|
||||
"lng_similar_channels_view_all" = "View all";
|
||||
|
@ -2664,6 +2681,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_commission" = "{amount} commission";
|
||||
"lng_credits_paid_messages_fee#one" = "Fee for {count} Message";
|
||||
"lng_credits_paid_messages_fee#other" = "Fee for {count} Messages";
|
||||
"lng_credits_paid_messages_fee_about" = "You receive {percent} of the price that you charge for each incoming message. {link}";
|
||||
"lng_credits_paid_messages_fee_about_link" = "Change Fee {emoji}";
|
||||
"lng_credits_paid_messages_full" = "Full Price";
|
||||
"lng_credits_premium_gift_duration" = "Duration";
|
||||
"lng_credits_more_options" = "More Options";
|
||||
"lng_credits_balance_me" = "your balance";
|
||||
"lng_credits_buy_button" = "Buy More Stars";
|
||||
|
@ -2777,6 +2800,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
||||
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
|
||||
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
|
||||
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
|
||||
"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages.";
|
||||
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
"lng_credits_enough" = "You have enough stars at the moment. {link}";
|
||||
|
@ -3310,6 +3335,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
|
||||
"lng_gift_premium_features" = "See Features >";
|
||||
"lng_gift_premium_label" = "Premium";
|
||||
"lng_gift_premium_by_stars" = "or {amount}";
|
||||
"lng_gift_stars_subtitle" = "Gift Stars";
|
||||
"lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}";
|
||||
"lng_gift_stars_link" = "What are Stars >";
|
||||
|
@ -3321,8 +3347,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_send_title" = "Send a Gift";
|
||||
"lng_gift_send_message" = "Enter Message";
|
||||
"lng_gift_send_anonymous" = "Hide My Name";
|
||||
"lng_gift_send_pay_with_stars" = "Pay with {amount}";
|
||||
"lng_gift_send_stars_balance" = "Your balance is {amount}. {link}";
|
||||
"lng_gift_send_stars_balance_link" = "Get More Stars >";
|
||||
"lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile.";
|
||||
"lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message.";
|
||||
"lng_gift_send_anonymous_about_paid" = "You can hide your name from visitors to {user}'s profile. {recipient} will still see your name.";
|
||||
"lng_gift_send_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins.";
|
||||
"lng_gift_send_unique" = "Make Unique for {price}";
|
||||
"lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
|
||||
|
@ -3398,6 +3428,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts.";
|
||||
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
|
||||
"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts.";
|
||||
"lng_gift_pinned_done" = "The gift will always be shown on top.";
|
||||
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
|
||||
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
|
||||
"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift.";
|
||||
|
@ -3456,6 +3487,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_transfer_button_for" = "Transfer for {price}";
|
||||
"lng_gift_transfer_wear" = "Wear";
|
||||
"lng_gift_transfer_take_off" = "Take Off";
|
||||
"lng_gift_menu_show" = "Show";
|
||||
"lng_gift_menu_hide" = "Hide";
|
||||
"lng_gift_wear_title" = "Wear {name}";
|
||||
"lng_gift_wear_about" = "and get these benefits:";
|
||||
"lng_gift_wear_badge_title" = "Radiant Badge";
|
||||
|
@ -3609,6 +3642,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join.";
|
||||
"lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}.";
|
||||
"lng_new_contact_about_status_link" = "Telegram Premium";
|
||||
"lng_new_contact_not_contact" = "Not a contact";
|
||||
"lng_new_contact_phone_number" = "Phone number";
|
||||
"lng_new_contact_registration" = "Registration";
|
||||
"lng_new_contact_common_groups" = "Common groups";
|
||||
"lng_new_contact_groups#one" = "{count} group {emoji} {arrow}";
|
||||
"lng_new_contact_groups#other" = "{count} groups {emoji} {arrow}";
|
||||
"lng_new_contact_not_official" = "Not an official account";
|
||||
"lng_new_contact_updated_name" = "User updated name {when}";
|
||||
"lng_new_contact_updated_photo" = "User updated photo {when}";
|
||||
"lng_new_contact_updated_now" = "less than an hour ago";
|
||||
"lng_new_contact_updated_hours#one" = "{count} hour ago";
|
||||
"lng_new_contact_updated_hours#other" = "{count} hours ago";
|
||||
"lng_new_contact_updated_days#one" = "{count} day ago";
|
||||
"lng_new_contact_updated_days#other" = "{count} days ago";
|
||||
"lng_new_contact_updated_months#one" = "{count} month ago";
|
||||
"lng_new_contact_updated_months#other" = "{count} months ago";
|
||||
"lng_from_request_title_channel" = "Response to your join request";
|
||||
"lng_from_request_title_group" = "Response to your join request";
|
||||
"lng_from_request_body" = "You received this message because you requested to join {name} on {date}.";
|
||||
|
@ -3637,6 +3686,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_send_anonymous_ph" = "Send anonymously...";
|
||||
"lng_story_reply_ph" = "Reply privately...";
|
||||
"lng_story_comment_ph" = "Comment story...";
|
||||
"lng_message_paid_ph" = "Message for {amount}";
|
||||
"lng_send_text_no" = "Text not allowed.";
|
||||
"lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
|
||||
"lng_send_text_type_and_last" = "{types} and {last}";
|
||||
|
@ -4798,6 +4848,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_rights_boosts_no_restrict" = "Do not restrict boosters";
|
||||
"lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media.";
|
||||
"lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages.";
|
||||
"lng_rights_charge_stars" = "Charge Stars for Messages";
|
||||
"lng_rights_charge_stars_about" = "If you turn this on, regular members of the group will have to pay Stars to send messages.";
|
||||
"lng_rights_charge_price" = "Set price per message";
|
||||
"lng_rights_charge_price_about" = "Your group will receive {percent} of the selected fee ({amount}) for each incoming message.";
|
||||
|
||||
"lng_slowmode_enabled" = "Slow Mode is active.\nYou can send your next message in {left}.";
|
||||
"lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time.";
|
||||
|
@ -4805,6 +4859,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_slowmode_seconds#one" = "{count} second";
|
||||
"lng_slowmode_seconds#other" = "{count} seconds";
|
||||
|
||||
"lng_payment_confirm_title" = "Confirm payment";
|
||||
"lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message.";
|
||||
"lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message.";
|
||||
"lng_payment_confirm_amount#one" = "**{count}** Star";
|
||||
"lng_payment_confirm_amount#other" = "**{count}** Stars";
|
||||
"lng_payment_confirm_users#one" = "You selected **{count}** user who charge Stars for messages.";
|
||||
"lng_payment_confirm_users#other" = "You selected **{count}** users who charge Stars for messages.";
|
||||
"lng_payment_confirm_chats#one" = "You selected **{count}** chat where you pay Stars for messages.";
|
||||
"lng_payment_confirm_chats#other" = "You selected **{count}** chats where you pay Stars for messages.";
|
||||
"lng_payment_confirm_sure#one" = "Would you like to pay {amount} to send **{count}** message?";
|
||||
"lng_payment_confirm_sure#other" = "Would you like to pay {amount} to send **{count}** messages?";
|
||||
"lng_payment_confirm_dont_ask" = "Don't ask me again";
|
||||
"lng_payment_confirm_button#one" = "Pay for {count} Message";
|
||||
"lng_payment_confirm_button#other" = "Pay for {count} Messages";
|
||||
"lng_payment_bar_text" = "{name} must pay {cost} for each message to you.";
|
||||
"lng_payment_bar_button" = "Remove Fee";
|
||||
"lng_payment_refund_title" = "Remove Fee";
|
||||
"lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?";
|
||||
"lng_payment_refund_also#one" = "Refund already paid {count} Star";
|
||||
"lng_payment_refund_also#other" = "Refund already paid {count} Stars";
|
||||
"lng_payment_refund_confirm" = "Confirm";
|
||||
|
||||
"lng_rights_gigagroup_title" = "Broadcast group";
|
||||
"lng_rights_gigagroup_convert" = "Convert to Broadcast Group";
|
||||
"lng_rights_gigagroup_about" = "Broadcast groups can have over 200,000 members, but only admins can send messages in them.";
|
||||
|
@ -4936,6 +5012,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers.";
|
||||
"lng_send_non_premium_message_toast_link" = "Telegram Premium";
|
||||
|
||||
"lng_send_charges_stars_text" = "{user} charges {amount} for each message.";
|
||||
"lng_send_charges_stars_go" = "Buy Stars";
|
||||
|
||||
"lng_exceptions_list_title" = "Exceptions";
|
||||
"lng_removed_list_title" = "Removed users";
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.11.1.0" />
|
||||
Version="5.12.1.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,11,1,0
|
||||
PRODUCTVERSION 5,11,1,0
|
||||
FILEVERSION 5,12,1,0
|
||||
PRODUCTVERSION 5,12,1,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -62,10 +62,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "5.11.1.0"
|
||||
VALUE "FileVersion", "5.12.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.11.1.0"
|
||||
VALUE "ProductVersion", "5.12.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,11,1,0
|
||||
PRODUCTVERSION 5,11,1,0
|
||||
FILEVERSION 5,12,1,0
|
||||
PRODUCTVERSION 5,12,1,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -53,10 +53,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "5.11.1.0"
|
||||
VALUE "FileVersion", "5.12.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.11.1.0"
|
||||
VALUE "ProductVersion", "5.12.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -149,18 +149,14 @@ void InitFilterLinkHeader(
|
|||
iconEmoji
|
||||
).value_or(Ui::FilterIcon::Custom)).active;
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> repaint) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &box->peerListUiShow()->session(),
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
auto header = Ui::MakeFilterLinkHeader(box, {
|
||||
.type = type,
|
||||
.title = TitleText(type)(tr::now),
|
||||
.about = AboutText(type, title.text),
|
||||
.makeAboutContext = makeContext,
|
||||
.aboutContext = Core::TextContext({
|
||||
.session = &box->peerListUiShow()->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
.folderTitle = title.text,
|
||||
.folderIcon = icon,
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
|
@ -560,16 +556,12 @@ void ShowImportToast(
|
|||
text.append('\n').append(phrase(tr::now, lt_count, added));
|
||||
}
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](not_null<QWidget*> widget) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &strong->session(),
|
||||
.customEmojiRepaint = [=] { widget->update(); },
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
strong->showToast({
|
||||
.text = std::move(text),
|
||||
.textContext = makeContext,
|
||||
.textContext = Core::TextContext({
|
||||
.session = &strong->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -640,18 +632,14 @@ void ProcessFilterInvite(
|
|||
raw->setRealContentHeight(box->heightValue());
|
||||
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &strong->session(),
|
||||
.customEmojiRepaint = update,
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
type,
|
||||
title.text,
|
||||
makeContext,
|
||||
Core::TextContext({
|
||||
.session = &strong->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
std::move(badge));
|
||||
|
||||
const auto button = owned.data();
|
||||
|
@ -873,18 +861,14 @@ void ProcessFilterRemove(
|
|||
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
|
||||
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &strong->session(),
|
||||
.customEmojiRepaint = update,
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
type,
|
||||
title.text,
|
||||
makeContext,
|
||||
Core::TextContext({
|
||||
.session = &strong->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
std::move(badge));
|
||||
|
||||
const auto button = owned.data();
|
||||
|
|
|
@ -25,6 +25,7 @@ struct SendOptions {
|
|||
TimeId scheduled = 0;
|
||||
BusinessShortcutId shortcutId = 0;
|
||||
EffectId effectId = 0;
|
||||
int starsApproved = 0;
|
||||
bool silent = false;
|
||||
bool handleSupportSwitch = false;
|
||||
bool invertCaption = false;
|
||||
|
|
|
@ -90,7 +90,13 @@ constexpr auto kTransactionsLimit = 100;
|
|||
? peerFromMTP(*tl.data().vstarref_peer()).value
|
||||
: 0;
|
||||
const auto incoming = (amount >= StarsAmount());
|
||||
const auto saveActorId = (reaction || !extended.empty()) && incoming;
|
||||
const auto paidMessagesCount
|
||||
= tl.data().vpaid_messages().value_or_empty();
|
||||
const auto premiumMonthsForStars
|
||||
= tl.data().vpremium_gift_months().value_or_empty();
|
||||
const auto saveActorId = (reaction
|
||||
|| !extended.empty()
|
||||
|| paidMessagesCount) && incoming;
|
||||
const auto parsedGift = stargift
|
||||
? FromTL(&peer->session(), *stargift)
|
||||
: std::optional<Data::StarGift>();
|
||||
|
@ -110,9 +116,9 @@ constexpr auto kTransactionsLimit = 100;
|
|||
.bareGiftStickerId = giftStickerId,
|
||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
|
||||
.starrefAmount = starrefAmount,
|
||||
.starrefCommission = starrefCommission,
|
||||
.starrefRecipientId = starrefBarePeerId,
|
||||
.starrefAmount = paidMessagesCount ? StarsAmount() : starrefAmount,
|
||||
.starrefCommission = paidMessagesCount ? 0 : starrefCommission,
|
||||
.starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId,
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
|
@ -138,9 +144,15 @@ constexpr auto kTransactionsLimit = 100;
|
|||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.paidMessagesCount = paidMessagesCount,
|
||||
.paidMessagesAmount = (paidMessagesCount
|
||||
? starrefAmount
|
||||
: StarsAmount()),
|
||||
.paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
|
||||
.starsConverted = int(nonUniqueGift
|
||||
? nonUniqueGift->vconvert_stars().v
|
||||
: 0),
|
||||
.premiumMonthsForStars = premiumMonthsForStars,
|
||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||
.converted = stargift && incoming,
|
||||
.stargift = stargift.has_value(),
|
||||
|
|
|
@ -114,7 +114,8 @@ void GlobalPrivacy::updateHideReadTime(bool hide) {
|
|||
archiveAndMuteCurrent(),
|
||||
unarchiveOnNewMessageCurrent(),
|
||||
hide,
|
||||
newRequirePremiumCurrent());
|
||||
newRequirePremiumCurrent(),
|
||||
newChargeStarsCurrent());
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::hideReadTimeCurrent() const {
|
||||
|
@ -125,14 +126,6 @@ rpl::producer<bool> GlobalPrivacy::hideReadTime() const {
|
|||
return _hideReadTime.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateNewRequirePremium(bool value) {
|
||||
update(
|
||||
archiveAndMuteCurrent(),
|
||||
unarchiveOnNewMessageCurrent(),
|
||||
hideReadTimeCurrent(),
|
||||
value);
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::newRequirePremiumCurrent() const {
|
||||
return _newRequirePremium.current();
|
||||
}
|
||||
|
@ -141,6 +134,25 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
|||
return _newRequirePremium.value();
|
||||
}
|
||||
|
||||
int GlobalPrivacy::newChargeStarsCurrent() const {
|
||||
return _newChargeStars.current();
|
||||
}
|
||||
|
||||
rpl::producer<int> GlobalPrivacy::newChargeStars() const {
|
||||
return _newChargeStars.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateMessagesPrivacy(
|
||||
bool requirePremium,
|
||||
int chargeStars) {
|
||||
update(
|
||||
archiveAndMuteCurrent(),
|
||||
unarchiveOnNewMessageCurrent(),
|
||||
hideReadTimeCurrent(),
|
||||
requirePremium,
|
||||
chargeStars);
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionShownPeer() {
|
||||
if (_paidReactionShownPeerLoaded) {
|
||||
return;
|
||||
|
@ -169,7 +181,8 @@ void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
|||
value,
|
||||
unarchiveOnNewMessageCurrent(),
|
||||
hideReadTimeCurrent(),
|
||||
newRequirePremiumCurrent());
|
||||
newRequirePremiumCurrent(),
|
||||
newChargeStarsCurrent());
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateUnarchiveOnNewMessage(
|
||||
|
@ -178,14 +191,16 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage(
|
|||
archiveAndMuteCurrent(),
|
||||
value,
|
||||
hideReadTimeCurrent(),
|
||||
newRequirePremiumCurrent());
|
||||
newRequirePremiumCurrent(),
|
||||
newChargeStarsCurrent());
|
||||
}
|
||||
|
||||
void GlobalPrivacy::update(
|
||||
bool archiveAndMute,
|
||||
UnarchiveOnNewMessage unarchiveOnNewMessage,
|
||||
bool hideReadTime,
|
||||
bool newRequirePremium) {
|
||||
bool newRequirePremium,
|
||||
int newChargeStars) {
|
||||
using Flag = MTPDglobalPrivacySettings::Flag;
|
||||
|
||||
_api.request(_requestId).cancel();
|
||||
|
@ -204,35 +219,44 @@ void GlobalPrivacy::update(
|
|||
| (hideReadTime ? Flag::f_hide_read_marks : Flag())
|
||||
| ((newRequirePremium && newRequirePremiumAllowed)
|
||||
? Flag::f_new_noncontact_peers_require_premium
|
||||
: Flag());
|
||||
: Flag())
|
||||
| Flag::f_noncontact_peers_paid_stars;
|
||||
_requestId = _api.request(MTPaccount_SetGlobalPrivacySettings(
|
||||
MTP_globalPrivacySettings(MTP_flags(flags))
|
||||
MTP_globalPrivacySettings(
|
||||
MTP_flags(flags),
|
||||
MTP_long(newChargeStars))
|
||||
)).done([=](const MTPGlobalPrivacySettings &result) {
|
||||
_requestId = 0;
|
||||
apply(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requestId = 0;
|
||||
if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
|
||||
update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {});
|
||||
update(
|
||||
archiveAndMute,
|
||||
unarchiveOnNewMessage,
|
||||
hideReadTime,
|
||||
false,
|
||||
0);
|
||||
}
|
||||
}).send();
|
||||
_archiveAndMute = archiveAndMute;
|
||||
_unarchiveOnNewMessage = unarchiveOnNewMessage;
|
||||
_hideReadTime = hideReadTime;
|
||||
_newRequirePremium = newRequirePremium;
|
||||
_newChargeStars = newChargeStars;
|
||||
}
|
||||
|
||||
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) {
|
||||
data.match([&](const MTPDglobalPrivacySettings &data) {
|
||||
_archiveAndMute = data.is_archive_and_mute_new_noncontact_peers();
|
||||
_unarchiveOnNewMessage = data.is_keep_archived_unmuted()
|
||||
? UnarchiveOnNewMessage::None
|
||||
: data.is_keep_archived_folders()
|
||||
? UnarchiveOnNewMessage::NotInFoldersUnmuted
|
||||
: UnarchiveOnNewMessage::AnyUnmuted;
|
||||
_hideReadTime = data.is_hide_read_marks();
|
||||
_newRequirePremium = data.is_new_noncontact_peers_require_premium();
|
||||
});
|
||||
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) {
|
||||
const auto &data = settings.data();
|
||||
_archiveAndMute = data.is_archive_and_mute_new_noncontact_peers();
|
||||
_unarchiveOnNewMessage = data.is_keep_archived_unmuted()
|
||||
? UnarchiveOnNewMessage::None
|
||||
: data.is_keep_archived_folders()
|
||||
? UnarchiveOnNewMessage::NotInFoldersUnmuted
|
||||
: UnarchiveOnNewMessage::AnyUnmuted;
|
||||
_hideReadTime = data.is_hide_read_marks();
|
||||
_newRequirePremium = data.is_new_noncontact_peers_require_premium();
|
||||
_newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -49,23 +49,28 @@ public:
|
|||
[[nodiscard]] bool hideReadTimeCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> hideReadTime() const;
|
||||
|
||||
void updateNewRequirePremium(bool value);
|
||||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||
|
||||
[[nodiscard]] int newChargeStarsCurrent() const;
|
||||
[[nodiscard]] rpl::producer<int> newChargeStars() const;
|
||||
|
||||
void updateMessagesPrivacy(bool requirePremium, int chargeStars);
|
||||
|
||||
void loadPaidReactionShownPeer();
|
||||
void updatePaidReactionShownPeer(PeerId shownPeer);
|
||||
[[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
|
||||
[[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
void apply(const MTPGlobalPrivacySettings &settings);
|
||||
|
||||
void update(
|
||||
bool archiveAndMute,
|
||||
UnarchiveOnNewMessage unarchiveOnNewMessage,
|
||||
bool hideReadTime,
|
||||
bool newRequirePremium);
|
||||
bool newRequirePremium,
|
||||
int newChargeStars);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
@ -76,6 +81,7 @@ private:
|
|||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
rpl::variable<bool> _hideReadTime = false;
|
||||
rpl::variable<bool> _newRequirePremium = false;
|
||||
rpl::variable<int> _newChargeStars = 0;
|
||||
rpl::variable<PeerId> _paidReactionShownPeer = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
bool _paidReactionShownPeerLoaded = false;
|
||||
|
|
|
@ -42,7 +42,7 @@ Polls::Polls(not_null<ApiWrap*> api)
|
|||
|
||||
void Polls::create(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail) {
|
||||
_session->api().sendAction(action);
|
||||
|
@ -64,6 +64,9 @@ void Polls::create(
|
|||
history->startSavingCloudDraft(topicRootId);
|
||||
}
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
|
@ -76,6 +79,10 @@ void Polls::create(
|
|||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
|
@ -98,7 +105,8 @@ void Polls::create(
|
|||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
|
||||
void create(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail);
|
||||
void sendVotes(
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_peer_values.h"
|
||||
|
@ -377,15 +378,15 @@ const Data::PremiumSubscriptionOptions &Premium::subscriptionOptions() const {
|
|||
return _subscriptionOptions;
|
||||
}
|
||||
|
||||
rpl::producer<> Premium::somePremiumRequiredResolved() const {
|
||||
return _somePremiumRequiredResolved.events();
|
||||
rpl::producer<> Premium::someMessageMoneyRestrictionsResolved() const {
|
||||
return _someMessageMoneyRestrictionsResolved.events();
|
||||
}
|
||||
|
||||
void Premium::resolvePremiumRequired(not_null<UserData*> user) {
|
||||
_resolvePremiumRequiredUsers.emplace(user);
|
||||
if (!_premiumRequiredRequestScheduled
|
||||
&& _resolvePremiumRequestedUsers.empty()) {
|
||||
_premiumRequiredRequestScheduled = true;
|
||||
void Premium::resolveMessageMoneyRestrictions(not_null<UserData*> user) {
|
||||
_resolveMessageMoneyRequiredUsers.emplace(user);
|
||||
if (!_messageMoneyRequestScheduled
|
||||
&& _resolveMessageMoneyRequestedUsers.empty()) {
|
||||
_messageMoneyRequestScheduled = true;
|
||||
crl::on_main(_session, [=] {
|
||||
requestPremiumRequiredSlice();
|
||||
});
|
||||
|
@ -393,50 +394,65 @@ void Premium::resolvePremiumRequired(not_null<UserData*> user) {
|
|||
}
|
||||
|
||||
void Premium::requestPremiumRequiredSlice() {
|
||||
_premiumRequiredRequestScheduled = false;
|
||||
if (!_resolvePremiumRequestedUsers.empty()
|
||||
|| _resolvePremiumRequiredUsers.empty()) {
|
||||
_messageMoneyRequestScheduled = false;
|
||||
if (!_resolveMessageMoneyRequestedUsers.empty()
|
||||
|| _resolveMessageMoneyRequiredUsers.empty()) {
|
||||
return;
|
||||
}
|
||||
constexpr auto kPerRequest = 100;
|
||||
auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers
|
||||
auto users = MTP_vector_from_range(_resolveMessageMoneyRequiredUsers
|
||||
| ranges::views::transform(&UserData::inputUser));
|
||||
if (users.v.size() > kPerRequest) {
|
||||
auto shortened = users.v;
|
||||
shortened.resize(kPerRequest);
|
||||
users = MTP_vector<MTPInputUser>(std::move(shortened));
|
||||
const auto from = begin(_resolvePremiumRequiredUsers);
|
||||
_resolvePremiumRequestedUsers = { from, from + kPerRequest };
|
||||
_resolvePremiumRequiredUsers.erase(from, from + kPerRequest);
|
||||
const auto from = begin(_resolveMessageMoneyRequiredUsers);
|
||||
_resolveMessageMoneyRequestedUsers = { from, from + kPerRequest };
|
||||
_resolveMessageMoneyRequiredUsers.erase(from, from + kPerRequest);
|
||||
} else {
|
||||
_resolvePremiumRequestedUsers
|
||||
= base::take(_resolvePremiumRequiredUsers);
|
||||
_resolveMessageMoneyRequestedUsers
|
||||
= base::take(_resolveMessageMoneyRequiredUsers);
|
||||
}
|
||||
const auto finish = [=](const QVector<MTPBool> &list) {
|
||||
constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite;
|
||||
constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown;
|
||||
constexpr auto mask = me | known;
|
||||
const auto finish = [=](const QVector<MTPRequirementToContact> &list) {
|
||||
|
||||
auto index = 0;
|
||||
for (const auto &user : base::take(_resolvePremiumRequestedUsers)) {
|
||||
const auto require = (index < list.size())
|
||||
&& mtpIsTrue(list[index++]);
|
||||
user->setFlags((user->flags() & ~mask)
|
||||
| known
|
||||
| (require ? me : UserDataFlag()));
|
||||
for (const auto &user : base::take(_resolveMessageMoneyRequestedUsers)) {
|
||||
const auto set = [&](bool requirePremium, int stars) {
|
||||
using Flag = UserDataFlag;
|
||||
constexpr auto me = Flag::RequiresPremiumToWrite;
|
||||
constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
|
||||
constexpr auto hasPrem = Flag::HasRequirePremiumToWrite;
|
||||
constexpr auto hasStars = Flag::HasStarsPerMessage;
|
||||
user->setStarsPerMessage(stars);
|
||||
user->setFlags((user->flags() & ~(me | hasPrem | hasStars))
|
||||
| known
|
||||
| (requirePremium ? (me | hasPrem) : Flag())
|
||||
| (stars ? hasStars : Flag()));
|
||||
};
|
||||
if (index >= list.size()) {
|
||||
set(false, 0);
|
||||
continue;
|
||||
}
|
||||
list[index++].match([&](const MTPDrequirementToContactEmpty &) {
|
||||
set(false, 0);
|
||||
}, [&](const MTPDrequirementToContactPremium &) {
|
||||
set(true, 0);
|
||||
}, [&](const MTPDrequirementToContactPaidMessages &data) {
|
||||
set(false, data.vstars_amount().v);
|
||||
});
|
||||
}
|
||||
if (!_premiumRequiredRequestScheduled
|
||||
&& !_resolvePremiumRequiredUsers.empty()) {
|
||||
_premiumRequiredRequestScheduled = true;
|
||||
if (!_messageMoneyRequestScheduled
|
||||
&& !_resolveMessageMoneyRequiredUsers.empty()) {
|
||||
_messageMoneyRequestScheduled = true;
|
||||
crl::on_main(_session, [=] {
|
||||
requestPremiumRequiredSlice();
|
||||
});
|
||||
}
|
||||
_somePremiumRequiredResolved.fire({});
|
||||
_someMessageMoneyRestrictionsResolved.fire({});
|
||||
};
|
||||
_session->api().request(
|
||||
MTPusers_GetIsPremiumRequiredToContact(std::move(users))
|
||||
).done([=](const MTPVector<MTPBool> &result) {
|
||||
MTPusers_GetRequirementsToContact(std::move(users))
|
||||
).done([=](const MTPVector<MTPRequirementToContact> &result) {
|
||||
finish(result.v);
|
||||
}).fail([=] {
|
||||
finish({});
|
||||
|
@ -463,10 +479,14 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
|
|||
for (const auto &tlOption : result.v) {
|
||||
const auto &data = tlOption.data();
|
||||
tlMapOptions[data.vusers().v].push_back(tlOption);
|
||||
if (qs(data.vcurrency()) == Ui::kCreditsCurrency) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto token = Token{ data.vusers().v, data.vmonths().v };
|
||||
_stores[token] = Store{
|
||||
.amount = data.vamount().v,
|
||||
.currency = qs(data.vcurrency()),
|
||||
.product = qs(data.vstore_product().value_or_empty()),
|
||||
.quantity = data.vstore_quantity().value_or_empty(),
|
||||
};
|
||||
|
@ -475,14 +495,14 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
|
|||
}
|
||||
}
|
||||
for (const auto &[amount, tlOptions] : tlMapOptions) {
|
||||
if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) {
|
||||
_optionsForOnePerson.currency = qs(
|
||||
tlOptions.front().data().vcurrency());
|
||||
if (amount == 1 && _optionsForOnePerson.currencies.empty()) {
|
||||
for (const auto &option : tlOptions) {
|
||||
_optionsForOnePerson.months.push_back(
|
||||
option.data().vmonths().v);
|
||||
_optionsForOnePerson.totalCosts.push_back(
|
||||
option.data().vamount().v);
|
||||
_optionsForOnePerson.currencies.push_back(
|
||||
qs(option.data().vcurrency()));
|
||||
}
|
||||
}
|
||||
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
||||
|
@ -509,7 +529,7 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
|
|||
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
||||
_peer->input,
|
||||
MTP_long(prepaidId),
|
||||
invoice.creditsAmount
|
||||
invoice.giveawayCredits
|
||||
? Payments::InvoiceCreditsGiveawayToTL(invoice)
|
||||
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
|
@ -540,7 +560,7 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
|||
const auto token = Token{ users, months };
|
||||
const auto &store = _stores[token];
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.currency = store.currency,
|
||||
.storeProduct = store.product,
|
||||
.randomId = randomId,
|
||||
.amount = store.amount,
|
||||
|
@ -553,14 +573,15 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
|||
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
|
||||
auto result = std::vector<GiftOptionData>();
|
||||
|
||||
if (!_optionsForOnePerson.currency.isEmpty()) {
|
||||
if (!_optionsForOnePerson.currencies.empty()) {
|
||||
const auto count = int(_optionsForOnePerson.months.size());
|
||||
result.reserve(count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
Assert(i < _optionsForOnePerson.totalCosts.size());
|
||||
Assert(i < _optionsForOnePerson.currencies.size());
|
||||
result.push_back({
|
||||
.cost = _optionsForOnePerson.totalCosts[i],
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.currency = _optionsForOnePerson.currencies[i],
|
||||
.months = _optionsForOnePerson.months[i],
|
||||
});
|
||||
}
|
||||
|
@ -581,7 +602,7 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
|||
MTP_int(_optionsForOnePerson.months[i]),
|
||||
MTPstring(),
|
||||
MTPint(),
|
||||
MTP_string(_optionsForOnePerson.currency),
|
||||
MTP_string(_optionsForOnePerson.currencies[i]),
|
||||
MTP_long(_optionsForOnePerson.totalCosts[i] * amount)));
|
||||
}
|
||||
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
||||
|
@ -694,28 +715,38 @@ rpl::producer<rpl::no_value, QString> SponsoredToggle::setToggled(bool v) {
|
|||
};
|
||||
}
|
||||
|
||||
RequirePremiumState ResolveRequiresPremiumToWrite(
|
||||
MessageMoneyRestriction ResolveMessageMoneyRestrictions(
|
||||
not_null<PeerData*> peer,
|
||||
History *maybeHistory) {
|
||||
const auto user = peer->asUser();
|
||||
if (!user
|
||||
|| !user->someRequirePremiumToWrite()
|
||||
|| user->session().premium()) {
|
||||
return RequirePremiumState::No;
|
||||
} else if (user->requirePremiumToWriteKnown()) {
|
||||
return user->meRequiresPremiumToWrite()
|
||||
? RequirePremiumState::Yes
|
||||
: RequirePremiumState::No;
|
||||
} else if (user->flags() & UserDataFlag::MutualContact) {
|
||||
return RequirePremiumState::No;
|
||||
} else if (!maybeHistory) {
|
||||
return RequirePremiumState::Unknown;
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
return {
|
||||
.starsPerMessage = channel->starsPerMessageChecked(),
|
||||
.known = true,
|
||||
};
|
||||
}
|
||||
const auto user = peer->asUser();
|
||||
if (!user) {
|
||||
return { .known = true };
|
||||
} else if (user->messageMoneyRestrictionsKnown()) {
|
||||
return {
|
||||
.starsPerMessage = user->starsPerMessageChecked(),
|
||||
.premiumRequired = (user->requiresPremiumToWrite()
|
||||
&& !user->session().premium()),
|
||||
.known = true,
|
||||
};
|
||||
} else if (user->hasStarsPerMessage()) {
|
||||
return {};
|
||||
} else if (!user->hasRequirePremiumToWrite()) {
|
||||
return { .known = true };
|
||||
} else if (user->flags() & UserDataFlag::MutualContact) {
|
||||
return { .known = true };
|
||||
} else if (!maybeHistory) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto update = [&](bool require) {
|
||||
using Flag = UserDataFlag;
|
||||
constexpr auto known = Flag::RequirePremiumToWriteKnown;
|
||||
constexpr auto me = Flag::MeRequiresPremiumToWrite;
|
||||
constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
|
||||
constexpr auto me = Flag::RequiresPremiumToWrite;
|
||||
user->setFlags((user->flags() & ~me)
|
||||
| known
|
||||
| (require ? me : Flag()));
|
||||
|
@ -727,16 +758,19 @@ RequirePremiumState ResolveRequiresPremiumToWrite(
|
|||
const auto item = view->data();
|
||||
if (!item->out() && !item->isService()) {
|
||||
update(false);
|
||||
return RequirePremiumState::No;
|
||||
return { .known = true };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (user->isContact() // Here we know, that we're not in his contacts.
|
||||
&& maybeHistory->loadedAtTop() // And no incoming messages.
|
||||
&& maybeHistory->loadedAtBottom()) {
|
||||
update(true);
|
||||
return {
|
||||
.premiumRequired = !user->session().premium(),
|
||||
.known = true,
|
||||
};
|
||||
}
|
||||
return RequirePremiumState::Unknown;
|
||||
return {};
|
||||
}
|
||||
|
||||
rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
|
@ -870,6 +904,7 @@ std::optional<Data::SavedStarGift> FromTL(
|
|||
.date = data.vdate().v,
|
||||
.upgradable = data.is_can_upgrade(),
|
||||
.anonymous = data.is_name_hidden(),
|
||||
.pinned = data.is_pinned_to_top(),
|
||||
.hidden = data.is_unsaved(),
|
||||
.mine = to->isSelf(),
|
||||
};
|
||||
|
|
|
@ -116,8 +116,9 @@ public:
|
|||
[[nodiscard]] auto subscriptionOptions() const
|
||||
-> const Data::PremiumSubscriptionOptions &;
|
||||
|
||||
[[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const;
|
||||
void resolvePremiumRequired(not_null<UserData*> user);
|
||||
[[nodiscard]] auto someMessageMoneyRestrictionsResolved() const
|
||||
-> rpl::producer<>;
|
||||
void resolveMessageMoneyRestrictions(not_null<UserData*> user);
|
||||
|
||||
private:
|
||||
void reloadPromo();
|
||||
|
@ -166,10 +167,10 @@ private:
|
|||
|
||||
Data::PremiumSubscriptionOptions _subscriptionOptions;
|
||||
|
||||
rpl::event_stream<> _somePremiumRequiredResolved;
|
||||
base::flat_set<not_null<UserData*>> _resolvePremiumRequiredUsers;
|
||||
base::flat_set<not_null<UserData*>> _resolvePremiumRequestedUsers;
|
||||
bool _premiumRequiredRequestScheduled = false;
|
||||
rpl::event_stream<> _someMessageMoneyRestrictionsResolved;
|
||||
base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequiredUsers;
|
||||
base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequestedUsers;
|
||||
bool _messageMoneyRequestScheduled = false;
|
||||
|
||||
};
|
||||
|
||||
|
@ -208,6 +209,7 @@ private:
|
|||
};
|
||||
struct Store final {
|
||||
uint64 amount = 0;
|
||||
QString currency;
|
||||
QString product;
|
||||
int quantity = 0;
|
||||
};
|
||||
|
@ -218,7 +220,7 @@ private:
|
|||
struct {
|
||||
std::vector<int> months;
|
||||
std::vector<int64> totalCosts;
|
||||
QString currency;
|
||||
std::vector<QString> currencies;
|
||||
} _optionsForOnePerson;
|
||||
|
||||
std::vector<int> _availablePresets;
|
||||
|
@ -244,12 +246,20 @@ private:
|
|||
|
||||
};
|
||||
|
||||
enum class RequirePremiumState {
|
||||
Unknown,
|
||||
Yes,
|
||||
No,
|
||||
struct MessageMoneyRestriction {
|
||||
int starsPerMessage = 0;
|
||||
bool premiumRequired = false;
|
||||
bool known = false;
|
||||
|
||||
explicit operator bool() const {
|
||||
return starsPerMessage != 0 || premiumRequired;
|
||||
}
|
||||
|
||||
friend inline bool operator==(
|
||||
const MessageMoneyRestriction &,
|
||||
const MessageMoneyRestriction &) = default;
|
||||
};
|
||||
[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite(
|
||||
[[nodiscard]] MessageMoneyRestriction ResolveMessageMoneyRestrictions(
|
||||
not_null<PeerData*> peer,
|
||||
History *maybeHistory);
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ Data::PremiumSubscriptionOption CreateSubscriptionOption(
|
|||
}();
|
||||
return {
|
||||
.duration = Ui::FormatTTL(months * 86400 * 31),
|
||||
.discount = discount
|
||||
.discount = (discount > 0)
|
||||
? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount)
|
||||
: QString(),
|
||||
.costPerMonth = Ui::FillAmountAndCurrency(
|
||||
|
|
|
@ -24,15 +24,26 @@ template<typename Option>
|
|||
if (tlOpts.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
auto monthlyAmountPerCurrency = base::flat_map<QString, int>();
|
||||
auto result = Data::PremiumSubscriptionOptions();
|
||||
const auto monthlyAmount = [&] {
|
||||
const auto monthlyAmount = [&](const QString ¤cy) -> int {
|
||||
const auto it = monthlyAmountPerCurrency.find(currency);
|
||||
if (it != end(monthlyAmountPerCurrency)) {
|
||||
return it->second;
|
||||
}
|
||||
const auto &min = ranges::min_element(
|
||||
tlOpts,
|
||||
ranges::less(),
|
||||
[](const Option &o) { return o.data().vamount().v; }
|
||||
[&](const Option &o) {
|
||||
return currency == qs(o.data().vcurrency())
|
||||
? o.data().vamount().v
|
||||
: std::numeric_limits<int64_t>::max();
|
||||
}
|
||||
)->data();
|
||||
return min.vamount().v / float64(min.vmonths().v);
|
||||
}();
|
||||
const auto monthly = min.vamount().v / float64(min.vmonths().v);
|
||||
monthlyAmountPerCurrency.emplace(currency, monthly);
|
||||
return monthly;
|
||||
};
|
||||
result.reserve(tlOpts.size());
|
||||
for (const auto &tlOption : tlOpts) {
|
||||
const auto &option = tlOption.data();
|
||||
|
@ -45,7 +56,7 @@ template<typename Option>
|
|||
const auto currency = qs(option.vcurrency());
|
||||
result.push_back(CreateSubscriptionOption(
|
||||
months,
|
||||
monthlyAmount,
|
||||
monthlyAmount(currency),
|
||||
amount,
|
||||
currency,
|
||||
botUrl));
|
||||
|
|
|
@ -95,7 +95,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
|||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? session->user()->name()
|
||||
: QString();
|
||||
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
|
@ -111,6 +113,10 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
|||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
histories.sendPreparedMessage(
|
||||
|
@ -129,7 +135,8 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
|||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
api->sendMessageFail(error, peer, randomId);
|
||||
|
@ -160,7 +167,7 @@ void SendExistingMedia(
|
|||
? (*localMessageId)
|
||||
: session->data().nextLocalMessageId());
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
const auto &action = message.action;
|
||||
auto &action = message.action;
|
||||
|
||||
auto flags = NewMessageFlags(peer);
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
|
@ -190,7 +197,9 @@ void SendExistingMedia(
|
|||
sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
||||
}
|
||||
const auto captionText = caption.text;
|
||||
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
|
@ -206,6 +215,10 @@ void SendExistingMedia(
|
|||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
|
||||
session->data().registerMessageRandomId(randomId, newId);
|
||||
|
||||
|
@ -216,6 +229,7 @@ void SendExistingMedia(
|
|||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, media, caption);
|
||||
|
@ -240,7 +254,8 @@ void SendExistingMedia(
|
|||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (error.code() == 400
|
||||
|
@ -341,7 +356,7 @@ bool SendDice(MessageToSend &message) {
|
|||
message.action.generateLocal = true;
|
||||
|
||||
|
||||
const auto &action = message.action;
|
||||
auto &action = message.action;
|
||||
api->sendAction(action);
|
||||
|
||||
const auto newId = FullMsgId(
|
||||
|
@ -380,6 +395,13 @@ bool SendDice(MessageToSend &message) {
|
|||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
|
||||
session->data().registerMessageRandomId(randomId, newId);
|
||||
|
||||
|
@ -390,6 +412,7 @@ bool SendDice(MessageToSend &message) {
|
|||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaDice(
|
||||
|
@ -411,7 +434,8 @@ bool SendDice(MessageToSend &message) {
|
|||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
api->sendMessageFail(error, peer, randomId, newId);
|
||||
|
@ -610,6 +634,9 @@ void SendConfirmedFile(
|
|||
.replyTo = file->to.replyTo,
|
||||
.date = NewMessageDate(file->to.options),
|
||||
.shortcutId = file->to.options.shortcutId,
|
||||
.starsPaid = std::min(
|
||||
history->peer->starsPerMessageChecked(),
|
||||
file->to.options.starsApproved),
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.groupedId = groupId,
|
||||
.effectId = file->to.options.effectId,
|
||||
|
|
|
@ -1227,7 +1227,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
|||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong()), // paid_message_stars
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
|
@ -1265,7 +1266,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
|||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong()), // paid_message_stars
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
|
|
|
@ -210,6 +210,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
|
|||
case Key::About: return MTP_inputPrivacyKeyAbout();
|
||||
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
|
||||
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
|
||||
case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages();
|
||||
}
|
||||
Unexpected("Key in Api::UserPrivacy::KetToTL.");
|
||||
}
|
||||
|
@ -241,6 +242,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
|
|||
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
|
||||
case mtpc_privacyKeyStarGiftsAutoSave:
|
||||
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
|
||||
case mtpc_privacyKeyNoPaidMessages:
|
||||
case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
About,
|
||||
Birthday,
|
||||
GiftsAutoSave,
|
||||
NoPaidMessages,
|
||||
};
|
||||
enum class Option {
|
||||
Everyone,
|
||||
|
|
|
@ -546,6 +546,7 @@ void ApiWrap::sendMessageFail(
|
|||
uint64 randomId,
|
||||
FullMsgId itemId) {
|
||||
const auto show = ShowForPeer(peer);
|
||||
const auto paidStarsPrefix = u"ALLOW_PAYMENT_REQUIRED_"_q;
|
||||
if (show && error == u"PEER_FLOOD"_q) {
|
||||
show->showBox(
|
||||
Ui::MakeInformBox(
|
||||
|
@ -600,6 +601,19 @@ void ApiWrap::sendMessageFail(
|
|||
if (show) {
|
||||
show->showToast(tr::lng_error_schedule_limit(tr::now));
|
||||
}
|
||||
} else if (error.startsWith(paidStarsPrefix)) {
|
||||
if (show) {
|
||||
show->showToast(
|
||||
u"Payment requirements changed. Please, try again."_q);
|
||||
}
|
||||
if (const auto stars = error.mid(paidStarsPrefix.size()).toInt()) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
user->setStarsPerMessage(stars);
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->setStarsPerMessage(stars);
|
||||
}
|
||||
}
|
||||
peer->updateFull();
|
||||
}
|
||||
if (const auto item = _session->data().message(itemId)) {
|
||||
Assert(randomId != 0);
|
||||
|
@ -3342,7 +3356,7 @@ void ApiWrap::finishForwarding(const SendAction &action) {
|
|||
|
||||
void ApiWrap::forwardMessages(
|
||||
Data::ResolvedForwardDraft &&draft,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
FnMut<void()> &&successCallback) {
|
||||
Expects(!draft.items.empty());
|
||||
|
||||
|
@ -3417,9 +3431,17 @@ void ApiWrap::forwardMessages(
|
|||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
const auto idsCopy = localIds;
|
||||
const auto scheduled = action.options.scheduled;
|
||||
const auto starsPaid = std::min(
|
||||
action.options.starsApproved,
|
||||
int(ids.size() * peer->starsPerMessageChecked()));
|
||||
auto oneFlags = sendFlags;
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
oneFlags |= SendFlag::f_allow_paid_stars;
|
||||
}
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
MTP_flags(oneFlags),
|
||||
forwardFrom->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_vector<MTPlong>(randomIds),
|
||||
|
@ -3428,7 +3450,8 @@ void ApiWrap::forwardMessages(
|
|||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTPint() // video_timestamp
|
||||
MTPint(), // video_timestamp
|
||||
MTP_long(starsPaid)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (!scheduled) {
|
||||
this->updates().checkForSentToScheduled(result);
|
||||
|
@ -3480,6 +3503,7 @@ void ApiWrap::forwardMessages(
|
|||
.replyTo = { .topicRootId = topMsgId },
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = action.options.starsApproved,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
|
||||
// forwarded messages don't have effects
|
||||
|
@ -3573,6 +3597,7 @@ void ApiWrap::sendSharedContact(
|
|||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = action.options.starsApproved,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaContact(
|
||||
|
@ -3944,6 +3969,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
|||
sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
lastMessage = history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
|
@ -3951,6 +3984,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
|||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, sending, media);
|
||||
|
@ -4001,7 +4035,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
|||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), done, fail);
|
||||
} else {
|
||||
histories.sendPreparedMessage(
|
||||
|
@ -4019,7 +4054,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
|||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), done, fail);
|
||||
}
|
||||
isFirst = false;
|
||||
|
@ -4084,7 +4120,7 @@ void ApiWrap::sendBotStart(
|
|||
void ApiWrap::sendInlineResult(
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done) {
|
||||
sendAction(action);
|
||||
|
@ -4124,6 +4160,13 @@ void ApiWrap::sendInlineResult(
|
|||
if (action.options.hideViaBot) {
|
||||
sendFlags |= SendFlag::f_hide_via;
|
||||
}
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= SendFlag::f_allow_paid_stars;
|
||||
}
|
||||
|
||||
const auto sendAs = action.options.sendAs;
|
||||
if (sendAs) {
|
||||
|
@ -4138,6 +4181,7 @@ void ApiWrap::sendInlineResult(
|
|||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.viaBotId = ((bot && !action.options.hideViaBot)
|
||||
? peerToUser(bot->id)
|
||||
: UserId()),
|
||||
|
@ -4161,7 +4205,8 @@ void ApiWrap::sendInlineResult(
|
|||
MTP_string(data->getId()),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
|
@ -4298,6 +4343,7 @@ void ApiWrap::sendMediaWithRandomId(
|
|||
|
||||
const auto history = item->history();
|
||||
const auto replyTo = item->replyTo();
|
||||
const auto peer = history->peer;
|
||||
|
||||
auto caption = item->originalText();
|
||||
TextUtilities::Trim(caption);
|
||||
|
@ -4307,6 +4353,12 @@ void ApiWrap::sendMediaWithRandomId(
|
|||
Api::ConvertOption::SkipLocal);
|
||||
|
||||
const auto updateRecentStickers = Api::HasAttachedStickers(media);
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
options.starsApproved);
|
||||
if (starsPaid) {
|
||||
options.starsApproved -= starsPaid;
|
||||
}
|
||||
|
||||
using Flag = MTPmessages_SendMedia::Flag;
|
||||
const auto flags = Flag(0)
|
||||
|
@ -4319,10 +4371,10 @@ void ApiWrap::sendMediaWithRandomId(
|
|||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
|
@ -4346,7 +4398,8 @@ void ApiWrap::sendMediaWithRandomId(
|
|||
MTP_int(options.scheduled),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId)
|
||||
MTP_long(options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (done) done(true);
|
||||
if (updateRecentStickers) {
|
||||
|
@ -4367,7 +4420,7 @@ void ApiWrap::sendMultiPaidMedia(
|
|||
Expects(album->options.price > 0);
|
||||
|
||||
const auto groupId = album->groupId;
|
||||
const auto &options = album->options;
|
||||
auto &options = album->options;
|
||||
const auto randomId = album->items.front().randomId;
|
||||
auto medias = album->items | ranges::view::transform([](
|
||||
const SendingAlbum::Item &part) {
|
||||
|
@ -4377,6 +4430,7 @@ void ApiWrap::sendMultiPaidMedia(
|
|||
|
||||
const auto history = item->history();
|
||||
const auto replyTo = item->replyTo();
|
||||
const auto peer = history->peer;
|
||||
|
||||
auto caption = item->originalText();
|
||||
TextUtilities::Trim(caption);
|
||||
|
@ -4384,6 +4438,12 @@ void ApiWrap::sendMultiPaidMedia(
|
|||
_session,
|
||||
caption.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
options.starsApproved);
|
||||
if (starsPaid) {
|
||||
options.starsApproved -= starsPaid;
|
||||
}
|
||||
|
||||
using Flag = MTPmessages_SendMedia::Flag;
|
||||
const auto flags = Flag(0)
|
||||
|
@ -4396,10 +4456,10 @@ void ApiWrap::sendMultiPaidMedia(
|
|||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
|
@ -4422,7 +4482,8 @@ void ApiWrap::sendMultiPaidMedia(
|
|||
MTP_int(options.scheduled),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId)
|
||||
MTP_long(options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (const auto album = _sendingAlbums.take(groupId)) {
|
||||
const auto copy = (*album)->items;
|
||||
|
@ -4518,6 +4579,12 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
|||
const auto history = sample->history();
|
||||
const auto replyTo = sample->replyTo();
|
||||
const auto sendAs = album->options.sendAs;
|
||||
const auto starsPaid = std::min(
|
||||
history->peer->starsPerMessageChecked() * int(medias.size()),
|
||||
album->options.starsApproved);
|
||||
if (starsPaid) {
|
||||
album->options.starsApproved -= starsPaid;
|
||||
}
|
||||
using Flag = MTPmessages_SendMultiMedia::Flag;
|
||||
const auto flags = Flag(0)
|
||||
| (replyTo ? Flag::f_reply_to : Flag(0))
|
||||
|
@ -4530,7 +4597,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
|||
? Flag::f_quick_reply_shortcut
|
||||
: Flag(0))
|
||||
| (album->options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
album->sent = true;
|
||||
|
@ -4546,7 +4614,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
|||
MTP_int(album->options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, album->options.shortcutId),
|
||||
MTP_long(album->options.effectId)
|
||||
MTP_long(album->options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
|
||||
|
|
|
@ -306,7 +306,7 @@ public:
|
|||
void finishForwarding(const SendAction &action);
|
||||
void forwardMessages(
|
||||
Data::ResolvedForwardDraft &&draft,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
FnMut<void()> &&successCallback = nullptr);
|
||||
void shareContact(
|
||||
const QString &phone,
|
||||
|
@ -368,7 +368,7 @@ public:
|
|||
void sendInlineResult(
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
void sendMessageFail(
|
||||
|
|
|
@ -1078,7 +1078,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
|
|||
? tr::lng_background_other_group(tr::now)
|
||||
: forChannel()
|
||||
? tr::lng_background_other_channel(tr::now)
|
||||
: (_forPeer && !_fromMessageId)
|
||||
: (_forPeer
|
||||
&& !_fromMessageId
|
||||
&& !_forPeer->starsPerMessageChecked())
|
||||
? tr::lng_background_other_info(
|
||||
tr::now,
|
||||
lt_user,
|
||||
|
|
|
@ -1122,3 +1122,10 @@ profileQrBackgroundRadius: 12px;
|
|||
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
|
||||
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
|
||||
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);
|
||||
|
||||
foldersMenu: PopupMenu(popupMenuWithIcons) {
|
||||
maxHeight: 320px;
|
||||
menu: Menu(menuWithIcons) {
|
||||
itemPadding: margins(54px, 8px, 44px, 8px);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,13 +172,6 @@ void ChangeFilterById(
|
|||
const auto account = not_null(&history->session().account());
|
||||
if (const auto controller = Core::App().windowFor(account)) {
|
||||
const auto isStatic = name.isStatic;
|
||||
const auto textContext = [=](not_null<QWidget*> widget) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &history->session(),
|
||||
.customEmojiRepaint = [=] { widget->update(); },
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
controller->showToast({
|
||||
.text = (add
|
||||
? tr::lng_filters_toast_add
|
||||
|
@ -189,7 +182,10 @@ void ChangeFilterById(
|
|||
lt_folder,
|
||||
Ui::Text::Wrapped(name.text, EntityType::Bold),
|
||||
Ui::Text::WithEntities),
|
||||
.textContext = textContext,
|
||||
.textContext = Core::TextContext({
|
||||
.session = &history->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
|
@ -290,19 +286,17 @@ void FillChooseFilterMenu(
|
|||
const auto title = filter.title();
|
||||
auto item = base::make_unique_q<FilterAction>(
|
||||
menu.get(),
|
||||
st::foldersMenu,
|
||||
menu->st().menu,
|
||||
Ui::Menu::CreateAction(
|
||||
menu.get(),
|
||||
Ui::Text::FixAmpersandInAction(title.text.text),
|
||||
std::move(callback)),
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr,
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr);
|
||||
const auto context = Core::MarkedTextContext{
|
||||
item->setMarkedText(title.text, QString(), Core::TextContext({
|
||||
.session = &history->session(),
|
||||
.customEmojiRepaint = [raw = item.get()] { raw->update(); },
|
||||
.customEmojiLoopLimit = title.isStatic ? -1 : 0,
|
||||
};
|
||||
item->setMarkedText(title.text, QString(), context);
|
||||
}));
|
||||
|
||||
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
|
||||
const auto action = menu->addAction(std::move(item));
|
||||
|
|
|
@ -817,13 +817,15 @@ CreatePollBox::CreatePollBox(
|
|||
not_null<Window::SessionController*> controller,
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
rpl::producer<int> starsRequired,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Details sendMenuDetails)
|
||||
: _controller(controller)
|
||||
, _chosen(chosen)
|
||||
, _disabled(disabled)
|
||||
, _sendType(sendType)
|
||||
, _sendMenuDetails([result = sendMenuDetails] { return result; }) {
|
||||
, _sendMenuDetails([result = sendMenuDetails] { return result; })
|
||||
, _starsRequired(std::move(starsRequired)) {
|
||||
}
|
||||
|
||||
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
|
||||
|
@ -1226,10 +1228,11 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
|||
_sendMenuDetails());
|
||||
};
|
||||
const auto submit = addButton(
|
||||
(isNormal
|
||||
? tr::lng_polls_create_button()
|
||||
: tr::lng_schedule_button()),
|
||||
tr::lng_polls_create_button(),
|
||||
[=] { isNormal ? send({}) : schedule(); });
|
||||
submit->setText(PaidSendButtonText(_starsRequired.value(), isNormal
|
||||
? tr::lng_polls_create_button()
|
||||
: tr::lng_schedule_button()));
|
||||
const auto sendMenuDetails = [=] {
|
||||
collectError();
|
||||
return (*error) ? SendMenu::Details() : _sendMenuDetails();
|
||||
|
|
|
@ -42,6 +42,7 @@ public:
|
|||
not_null<Window::SessionController*> controller,
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
rpl::producer<int> starsRequired,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Details sendMenuDetails);
|
||||
|
||||
|
@ -76,6 +77,7 @@ private:
|
|||
const PollData::Flags _disabled = PollData::Flags();
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
const Fn<SendMenu::Details()> _sendMenuDetails;
|
||||
rpl::variable<int> _starsRequired;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
Fn<void()> _setInnerFocus;
|
||||
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
||||
|
|
|
@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
|
@ -21,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "settings/settings_privacy_controllers.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
@ -42,6 +45,8 @@ namespace {
|
|||
|
||||
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
|
||||
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
|
||||
constexpr auto kStarsMin = 1;
|
||||
constexpr auto kDefaultChargeStars = 10;
|
||||
|
||||
using Exceptions = Api::UserPrivacy::Exceptions;
|
||||
|
||||
|
@ -452,6 +457,143 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
|
|||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeChargeStarsSlider(
|
||||
QWidget *parent,
|
||||
not_null<const style::MediaSlider*> sliderStyle,
|
||||
not_null<const style::FlatLabel*> labelStyle,
|
||||
int valuesCount,
|
||||
Fn<int(int)> valueByIndex,
|
||||
int value,
|
||||
int maxValue,
|
||||
Fn<void(int)> valueProgress,
|
||||
Fn<void(int)> valueFinished) {
|
||||
auto result = object_ptr<Ui::VerticalLayout>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto labels = raw->add(object_ptr<Ui::RpWidget>(raw));
|
||||
const auto min = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
QString::number(kStarsMin),
|
||||
*labelStyle);
|
||||
const auto max = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
QString::number(maxValue),
|
||||
*labelStyle);
|
||||
const auto current = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
QString::number(value),
|
||||
*labelStyle);
|
||||
min->setTextColorOverride(st::windowSubTextFg->c);
|
||||
max->setTextColorOverride(st::windowSubTextFg->c);
|
||||
const auto slider = raw->add(object_ptr<Ui::MediaSliderWheelless>(
|
||||
raw,
|
||||
*sliderStyle));
|
||||
labels->resize(
|
||||
labels->width(),
|
||||
current->height() + st::defaultVerticalListSkip);
|
||||
struct State {
|
||||
int indexMin = 0;
|
||||
int index = 0;
|
||||
};
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
const auto updateByIndex = [=] {
|
||||
const auto outer = labels->width();
|
||||
const auto minWidth = min->width();
|
||||
const auto maxWidth = max->width();
|
||||
const auto currentWidth = current->width();
|
||||
if (minWidth + maxWidth + currentWidth > outer) {
|
||||
return;
|
||||
}
|
||||
|
||||
min->moveToLeft(0, 0, outer);
|
||||
max->moveToRight(0, 0, outer);
|
||||
current->moveToLeft((outer - current->width()) / 2, 0, outer);
|
||||
};
|
||||
const auto updateByValue = [=](int value) {
|
||||
current->setText(
|
||||
tr::lng_action_gift_for_stars(tr::now, lt_count, value));
|
||||
|
||||
state->index = 0;
|
||||
auto maxIndex = valuesCount - 1;
|
||||
while (state->index < maxIndex) {
|
||||
const auto mid = (state->index + maxIndex) / 2;
|
||||
const auto midValue = valueByIndex(mid);
|
||||
if (midValue == value) {
|
||||
state->index = mid;
|
||||
break;
|
||||
} else if (midValue < value) {
|
||||
state->index = mid + 1;
|
||||
} else {
|
||||
maxIndex = mid - 1;
|
||||
}
|
||||
}
|
||||
updateByIndex();
|
||||
};
|
||||
const auto progress = [=](int value) {
|
||||
updateByValue(value);
|
||||
valueProgress(value);
|
||||
};
|
||||
const auto finished = [=](int value) {
|
||||
updateByValue(value);
|
||||
valueFinished(value);
|
||||
};
|
||||
style::PaletteChanged() | rpl::start_with_next([=] {
|
||||
min->setTextColorOverride(st::windowSubTextFg->c);
|
||||
max->setTextColorOverride(st::windowSubTextFg->c);
|
||||
}, raw->lifetime());
|
||||
updateByValue(value);
|
||||
state->indexMin = 0;
|
||||
|
||||
slider->setPseudoDiscrete(
|
||||
valuesCount,
|
||||
valueByIndex,
|
||||
value,
|
||||
progress,
|
||||
finished,
|
||||
state->indexMin);
|
||||
slider->resize(slider->width(), sliderStyle->seekSize.height());
|
||||
|
||||
raw->widthValue() | rpl::start_with_next([=](int width) {
|
||||
labels->resizeToWidth(width);
|
||||
updateByIndex();
|
||||
}, slider->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void EditNoPaidMessagesExceptions(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Api::UserPrivacy::Rule &value) {
|
||||
auto controller = std::make_unique<PrivacyExceptionsBoxController>(
|
||||
&window->session(),
|
||||
tr::lng_messages_privacy_remove_fee(),
|
||||
value.always,
|
||||
std::optional<SpecialRowType>());
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
auto copy = value;
|
||||
auto &setTo = copy.always;
|
||||
setTo.peers = box->collectSelectedRows();
|
||||
setTo.premiums = false;
|
||||
setTo.miniapps = false;
|
||||
auto &removeFrom = copy.never;
|
||||
for (const auto peer : setTo.peers) {
|
||||
removeFrom.peers.erase(
|
||||
ranges::remove(removeFrom.peers, peer),
|
||||
end(removeFrom.peers));
|
||||
}
|
||||
window->session().api().userPrivacy().save(
|
||||
Api::UserPrivacy::Key::NoPaidMessages,
|
||||
copy);
|
||||
box->closeBox();
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
};
|
||||
window->show(
|
||||
Box<PeerListBox>(std::move(controller), std::move(initBox)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool EditPrivacyController::hasOption(Option option) const {
|
||||
|
@ -812,19 +954,27 @@ void EditMessagesPrivacyBox(
|
|||
|
||||
constexpr auto kOptionAll = 0;
|
||||
constexpr auto kOptionPremium = 1;
|
||||
constexpr auto kOptionCharge = 2;
|
||||
|
||||
const auto session = &controller->session();
|
||||
const auto allowed = [=] {
|
||||
return controller->session().premium()
|
||||
|| controller->session().appConfig().newRequirePremiumFree();
|
||||
return session->premium()
|
||||
|| session->appConfig().newRequirePremiumFree();
|
||||
};
|
||||
const auto privacy = &controller->session().api().globalPrivacy();
|
||||
const auto privacy = &session->api().globalPrivacy();
|
||||
const auto inner = box->verticalLayout();
|
||||
inner->add(object_ptr<Ui::PlainShadow>(box));
|
||||
|
||||
Ui::AddSkip(inner, st::messagePrivacyTopSkip);
|
||||
Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle());
|
||||
const auto group = std::make_shared<Ui::RadiobuttonGroup>(
|
||||
privacy->newRequirePremiumCurrent() ? kOptionPremium : kOptionAll);
|
||||
(!allowed()
|
||||
? kOptionAll
|
||||
: privacy->newRequirePremiumCurrent()
|
||||
? kOptionPremium
|
||||
: privacy->newChargeStarsCurrent()
|
||||
? kOptionCharge
|
||||
: kOptionAll));
|
||||
inner->add(
|
||||
object_ptr<Ui::Radiobutton>(
|
||||
inner,
|
||||
|
@ -846,6 +996,92 @@ void EditMessagesPrivacyBox(
|
|||
0,
|
||||
st::messagePrivacyBottomSkip));
|
||||
|
||||
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
|
||||
|
||||
const auto available = session->appConfig().paidMessagesAvailable();
|
||||
|
||||
const auto charged = available
|
||||
? inner->add(
|
||||
object_ptr<Ui::Radiobutton>(
|
||||
inner,
|
||||
group,
|
||||
kOptionCharge,
|
||||
tr::lng_messages_privacy_charge(tr::now),
|
||||
st::messagePrivacyCheck),
|
||||
st::settingsSendTypePadding + style::margins(
|
||||
0,
|
||||
st::messagePrivacyBottomSkip,
|
||||
0,
|
||||
st::messagePrivacyBottomSkip))
|
||||
: nullptr;
|
||||
|
||||
struct State {
|
||||
rpl::variable<int> stars;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto savedValue = privacy->newChargeStarsCurrent();
|
||||
|
||||
if (available) {
|
||||
Ui::AddDividerText(inner, tr::lng_messages_privacy_charge_about());
|
||||
|
||||
const auto chargeWrap = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
const auto chargeInner = chargeWrap->entity();
|
||||
|
||||
Ui::AddSkip(chargeInner);
|
||||
|
||||
state->stars = SetupChargeSlider(
|
||||
chargeInner,
|
||||
session->user(),
|
||||
savedValue);
|
||||
|
||||
Ui::AddSkip(chargeInner);
|
||||
Ui::AddSubsectionTitle(
|
||||
chargeInner,
|
||||
tr::lng_messages_privacy_exceptions());
|
||||
|
||||
const auto key = Api::UserPrivacy::Key::NoPaidMessages;
|
||||
session->api().userPrivacy().reload(key);
|
||||
auto label = session->api().userPrivacy().value(
|
||||
key
|
||||
) | rpl::map([=](const Api::UserPrivacy::Rule &value) {
|
||||
using namespace Settings;
|
||||
const auto always = ExceptionUsersCount(value.always.peers);
|
||||
return always
|
||||
? tr::lng_edit_privacy_exceptions_count(
|
||||
tr::now,
|
||||
lt_count,
|
||||
always)
|
||||
: QString();
|
||||
});
|
||||
|
||||
const auto exceptions = Settings::AddButtonWithLabel(
|
||||
chargeInner,
|
||||
tr::lng_messages_privacy_remove_fee(),
|
||||
std::move(label),
|
||||
st::settingsButtonNoIcon);
|
||||
|
||||
const auto shower = exceptions->lifetime().make_state<rpl::lifetime>();
|
||||
exceptions->setClickedCallback([=] {
|
||||
*shower = session->api().userPrivacy().value(
|
||||
key
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) {
|
||||
EditNoPaidMessagesExceptions(controller, value);
|
||||
});
|
||||
});
|
||||
Ui::AddSkip(chargeInner);
|
||||
Ui::AddDividerText(
|
||||
chargeInner,
|
||||
tr::lng_messages_privacy_remove_about());
|
||||
|
||||
using namespace rpl::mappers;
|
||||
chargeWrap->toggleOn(group->value() | rpl::map(_1 == kOptionCharge));
|
||||
chargeWrap->finishAnimating();
|
||||
}
|
||||
using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
|
||||
const auto toast = std::make_shared<WeakToast>();
|
||||
const auto showToast = [=] {
|
||||
|
@ -875,19 +1111,20 @@ void EditMessagesPrivacyBox(
|
|||
}),
|
||||
});
|
||||
};
|
||||
|
||||
if (!allowed()) {
|
||||
CreateRadiobuttonLock(restricted, st::messagePrivacyCheck);
|
||||
if (charged) {
|
||||
CreateRadiobuttonLock(charged, st::messagePrivacyCheck);
|
||||
}
|
||||
|
||||
group->setChangedCallback([=](int value) {
|
||||
if (value == kOptionPremium) {
|
||||
if (value == kOptionPremium || value == kOptionCharge) {
|
||||
group->setValue(kOptionAll);
|
||||
showToast();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
|
||||
if (!allowed()) {
|
||||
Ui::AddSkip(inner);
|
||||
Settings::AddButtonWithIcon(
|
||||
inner,
|
||||
|
@ -907,8 +1144,12 @@ void EditMessagesPrivacyBox(
|
|||
} else {
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
if (allowed()) {
|
||||
privacy->updateNewRequirePremium(
|
||||
group->current() == kOptionPremium);
|
||||
const auto value = group->current();
|
||||
const auto premiumRequired = (value == kOptionPremium);
|
||||
const auto chargeStars = (value == kOptionCharge)
|
||||
? state->stars.current()
|
||||
: 0;
|
||||
privacy->updateMessagesPrivacy(premiumRequired, chargeStars);
|
||||
box->closeBox();
|
||||
} else {
|
||||
showToast();
|
||||
|
@ -919,3 +1160,78 @@ void EditMessagesPrivacyBox(
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SetupChargeSlider(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
int savedValue) {
|
||||
struct State {
|
||||
rpl::variable<int> stars;
|
||||
};
|
||||
const auto group = !peer->isUser();
|
||||
const auto state = container->lifetime().make_state<State>();
|
||||
const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars;
|
||||
state->stars = chargeStars;
|
||||
|
||||
Ui::AddSubsectionTitle(container, group
|
||||
? tr::lng_rights_charge_price()
|
||||
: tr::lng_messages_privacy_price());
|
||||
|
||||
auto values = std::vector<int>();
|
||||
const auto maxStars = peer->session().appConfig().paidMessageStarsMax();
|
||||
if (chargeStars < kStarsMin) {
|
||||
values.push_back(chargeStars);
|
||||
}
|
||||
for (auto i = kStarsMin; i < std::min(100, maxStars); ++i) {
|
||||
values.push_back(i);
|
||||
}
|
||||
for (auto i = 100; i < std::min(1000, maxStars); i += 10) {
|
||||
if (i < chargeStars + 10 && chargeStars < i) {
|
||||
values.push_back(chargeStars);
|
||||
}
|
||||
values.push_back(i);
|
||||
}
|
||||
for (auto i = 1000; i < maxStars + 1; i += 100) {
|
||||
if (i < chargeStars + 100 && chargeStars < i) {
|
||||
values.push_back(chargeStars);
|
||||
}
|
||||
values.push_back(i);
|
||||
}
|
||||
const auto valuesCount = int(values.size());
|
||||
const auto setStars = [=](int value) {
|
||||
state->stars = value;
|
||||
};
|
||||
container->add(
|
||||
MakeChargeStarsSlider(
|
||||
container,
|
||||
&st::settingsScale,
|
||||
&st::settingsScaleLabel,
|
||||
valuesCount,
|
||||
[=](int index) { return values[index]; },
|
||||
chargeStars,
|
||||
maxStars,
|
||||
setStars,
|
||||
setStars),
|
||||
st::boxRowPadding);
|
||||
|
||||
const auto skip = 2 * st::defaultVerticalListSkip;
|
||||
Ui::AddSkip(container, skip);
|
||||
|
||||
auto dollars = state->stars.value() | rpl::map([=](int stars) {
|
||||
const auto ratio = peer->session().appConfig().starsWithdrawRate();
|
||||
const auto dollars = int(base::SafeRound(stars * ratio));
|
||||
return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q);
|
||||
});
|
||||
const auto percent = peer->session().appConfig().paidMessageCommission();
|
||||
Ui::AddDividerText(
|
||||
container,
|
||||
(group
|
||||
? tr::lng_rights_charge_price_about
|
||||
: tr::lng_messages_privacy_price_about)(
|
||||
lt_percent,
|
||||
rpl::single(QString::number(percent / 10.) + '%'),
|
||||
lt_amount,
|
||||
std::move(dollars)));
|
||||
|
||||
return state->stars.value();
|
||||
}
|
||||
|
|
|
@ -169,3 +169,8 @@ private:
|
|||
void EditMessagesPrivacyBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> SetupChargeSlider(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
int savedValue);
|
||||
|
|
|
@ -441,13 +441,10 @@ void EditFilterBox(
|
|||
using namespace Window;
|
||||
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
|
||||
};
|
||||
name->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
.customEmojiLoopLimit = value ? -1 : 0,
|
||||
});
|
||||
}, [paused] {
|
||||
name->setCustomTextContext(Core::TextContext({
|
||||
.session = session,
|
||||
.customEmojiLoopLimit = value ? -1 : 0,
|
||||
}), [paused] {
|
||||
return On(PowerSaving::kEmojiChat) || paused();
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kChatSpoiler) || paused();
|
||||
|
@ -609,10 +606,7 @@ void EditFilterBox(
|
|||
float64 alpha = 1.;
|
||||
};
|
||||
const auto tag = preview->lifetime().make_state<TagState>();
|
||||
tag->context.textContext = Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [] {},
|
||||
};
|
||||
tag->context.textContext = Core::TextContext({ .session = session });
|
||||
preview->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(preview);
|
||||
p.setOpacity(tag->alpha);
|
||||
|
|
|
@ -163,10 +163,10 @@ ExceptionRow::ExceptionRow(
|
|||
st::defaultTextStyle,
|
||||
filters,
|
||||
kMarkupTextOptions,
|
||||
Core::MarkedTextContext{
|
||||
Core::TextContext({
|
||||
.session = &history->session(),
|
||||
.customEmojiRepaint = repaint,
|
||||
});
|
||||
.repaint = repaint,
|
||||
}));
|
||||
} else if (peer()->isSelf()) {
|
||||
setCustomStatus(tr::lng_saved_forward_here(tr::now));
|
||||
}
|
||||
|
|
|
@ -537,13 +537,6 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
|||
verticalLayout->add(std::move(icon.widget));
|
||||
|
||||
const auto isStatic = _filterTitle.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &_window->session(),
|
||||
.customEmojiRepaint = update,
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
verticalLayout->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
verticalLayout,
|
||||
|
@ -559,7 +552,10 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
|||
Ui::Text::WithEntities)),
|
||||
st::settingsFilterDividerLabel,
|
||||
st::defaultPopupMenu,
|
||||
makeContext)),
|
||||
Core::TextContext({
|
||||
.session = &_window->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}))),
|
||||
st::filterLinkDividerLabelPadding);
|
||||
|
||||
verticalLayout->geometryValue(
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "api/api_credits.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "core/ui_integration.h" // TextContext.
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -67,14 +68,9 @@ void GiftCreditsBox(
|
|||
2.);
|
||||
{
|
||||
Ui::AddSkip(content);
|
||||
const auto arrow = Ui::Text::SingleCustomEmoji(
|
||||
peer->owner().customEmojiManager().registerInternalEmoji(
|
||||
st::topicButtonArrow,
|
||||
st::channelEarnLearnArrowMargins,
|
||||
true));
|
||||
auto link = tr::lng_credits_box_history_entry_gift_about_link(
|
||||
lt_emoji,
|
||||
rpl::single(arrow),
|
||||
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
|
||||
Ui::Text::RichLangValue
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
|
@ -92,7 +88,7 @@ void GiftCreditsBox(
|
|||
lt_link,
|
||||
std::move(link),
|
||||
Ui::Text::RichLangValue),
|
||||
{ .session = &peer->session() },
|
||||
Core::TextContext({ .session = &peer->session() }),
|
||||
st::creditsBoxAbout)),
|
||||
st::boxRowPadding);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||
#include "boxes/transfer_gift_box.h" // ShowTransferGiftBox.
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
|
@ -58,7 +59,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/widgets/label_with_custom_emoji.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
|
@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
@ -516,13 +517,13 @@ not_null<Ui::FlatLabel*> AddTableRow(
|
|||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
rpl::producer<TextWithEntities> value,
|
||||
const Fn<std::any(Fn<void()>)> &makeContext = nullptr) {
|
||||
const Ui::Text::MarkedContext &context = {}) {
|
||||
auto widget = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
std::move(value),
|
||||
table->st().defaultValue,
|
||||
st::defaultPopupMenu,
|
||||
std::move(makeContext));
|
||||
context);
|
||||
const auto result = widget.data();
|
||||
AddTableRow(
|
||||
table,
|
||||
|
@ -1272,8 +1273,8 @@ void AddStarGiftTable(
|
|||
const auto selfBareId = session->userPeerId().value;
|
||||
const auto giftToSelf = (peerId == session->userPeerId())
|
||||
&& (entry.in || entry.bareGiftOwnerId == selfBareId);
|
||||
const auto giftToChannel = entry.giftSavedId
|
||||
&& peerIsChannel(PeerId(entry.bareGiftListPeerId));
|
||||
const auto giftToChannel = entry.giftChannelSavedId
|
||||
&& peerIsChannel(PeerId(entry.bareEntryOwnerId));
|
||||
|
||||
const auto raw = std::make_shared<Ui::ImportantTooltip*>(nullptr);
|
||||
const auto showTooltip = [=](
|
||||
|
@ -1394,14 +1395,14 @@ void AddStarGiftTable(
|
|||
? MakePeerTableValue(table, show, PeerId(entry.bareActorId))
|
||||
: MakeHiddenPeerTableValue(table)),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
if (entry.bareGiftListPeerId) {
|
||||
if (entry.bareEntryOwnerId) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer(),
|
||||
MakePeerTableValue(
|
||||
table,
|
||||
show,
|
||||
PeerId(entry.bareGiftListPeerId)),
|
||||
PeerId(entry.bareEntryOwnerId)),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
} else if (peerId && !giftToSelf) {
|
||||
|
@ -1526,12 +1527,6 @@ void AddStarGiftTable(
|
|||
: nullptr;
|
||||
const auto date = base::unixtime::parse(original.date).date();
|
||||
const auto dateText = TextWithEntities{ langDayOfMonth(date) };
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(update),
|
||||
};
|
||||
};
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
(from
|
||||
|
@ -1573,7 +1568,7 @@ void AddStarGiftTable(
|
|||
? *st.tableValueMessage
|
||||
: st::giveawayGiftMessage),
|
||||
st::defaultPopupMenu,
|
||||
makeContext);
|
||||
Core::TextContext({ .session = session }));
|
||||
const auto showBoxLink = [=](not_null<PeerData*> peer) {
|
||||
return std::make_shared<LambdaClickHandler>([=] {
|
||||
show->showBox(PrepareShortInfoBox(peer, show));
|
||||
|
@ -1591,12 +1586,6 @@ void AddStarGiftTable(
|
|||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
} else if (!entry.description.empty()) {
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(update),
|
||||
};
|
||||
};
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(entry.description),
|
||||
|
@ -1604,7 +1593,7 @@ void AddStarGiftTable(
|
|||
? *st.tableValueMessage
|
||||
: st::giveawayGiftMessage),
|
||||
st::defaultPopupMenu,
|
||||
makeContext);
|
||||
Core::TextContext({ .session = session }));
|
||||
label->setSelectable(true);
|
||||
table->addRow(
|
||||
nullptr,
|
||||
|
@ -1775,6 +1764,25 @@ void AddCreditsHistoryEntryTable(
|
|||
tr::lng_credits_box_history_entry_subscription(
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
if (entry.paidMessagesAmount) {
|
||||
auto value = Ui::Text::IconEmoji(&st::starIconEmojiColored);
|
||||
const auto full = (entry.in ? 1 : -1)
|
||||
* (entry.credits + entry.paidMessagesAmount);
|
||||
const auto starsText = Lang::FormatStarsAmountDecimal(full);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_paid_messages_full(),
|
||||
rpl::single(value.append(' ' + starsText)));
|
||||
}
|
||||
if (const auto months = entry.premiumMonthsForStars) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_premium_gift_duration(),
|
||||
tr::lng_months(
|
||||
lt_count,
|
||||
rpl::single(1. * months),
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
auto label = MakeMaybeMultilineTokenValue(table, entry.id, st);
|
||||
label->setClickHandlerFilter([=](const auto &...) {
|
||||
|
|
|
@ -453,10 +453,7 @@ void CreateModerateMessagesBox(
|
|||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
raw->setMarkedText(
|
||||
Ui::Text::Link(text, u"internal:"_q),
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { raw->update(); },
|
||||
});
|
||||
Core::TextContext({ .session = session }));
|
||||
}, label->lifetime());
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
|
|
|
@ -1154,8 +1154,7 @@ RecoverBox::RecoverBox(
|
|||
rpl::single(Ui::Text::WrapEmailPattern(pattern)),
|
||||
Ui::Text::WithEntities),
|
||||
st::termsContent,
|
||||
st::defaultPopupMenu,
|
||||
[=](Fn<void()> update) { return CommonTextContext{ std::move(update) }; })
|
||||
st::defaultPopupMenu)
|
||||
, _closeParent(std::move(closeParent)) {
|
||||
_patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (_cloudFields.pendingResetDate != 0 || !session) {
|
||||
|
|
|
@ -883,6 +883,7 @@ void PeerListRow::paintUserpic(
|
|||
} else if (const auto callback = generatePaintUserpicCallback(false)) {
|
||||
callback(p, x, y, outerWidth, st.photoSize);
|
||||
}
|
||||
paintUserpicOverlay(p, st, x, y, outerWidth);
|
||||
}
|
||||
|
||||
// Emulates Ui::RoundImageCheckbox::paint() in a checked state.
|
||||
|
|
|
@ -95,6 +95,13 @@ public:
|
|||
[[nodiscard]] virtual QString generateShortName();
|
||||
[[nodiscard]] virtual auto generatePaintUserpicCallback(
|
||||
bool forceRound) -> PaintRoundImageCallback;
|
||||
virtual void paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) {
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual auto generateNameFirstLetters() const
|
||||
-> const base::flat_set<QChar> &;
|
||||
|
|
|
@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/peer_list_controllers.h"
|
||||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_premium.h" // MessageMoneyRestriction.
|
||||
#include "base/random.h"
|
||||
#include "boxes/filters/edit_filter_chats_list.h"
|
||||
#include "settings/settings_premium.h"
|
||||
|
@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "payments/ui/payments_reaction_box.h"
|
||||
#include "ui/effects/outline_segments.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "window/window_separate_id.h"
|
||||
|
@ -275,40 +276,71 @@ bool PeerListGlobalSearchController::isLoading() {
|
|||
return _timer.isActive() || _requestId;
|
||||
}
|
||||
|
||||
struct RecipientRow::Restriction {
|
||||
Api::MessageMoneyRestriction value;
|
||||
RestrictionBadgeCache cache;
|
||||
};
|
||||
|
||||
RecipientRow::RecipientRow(
|
||||
not_null<PeerData*> peer,
|
||||
const style::PeerListItem *maybeLockedSt,
|
||||
History *maybeHistory)
|
||||
: PeerListRow(peer)
|
||||
, _maybeHistory(maybeHistory)
|
||||
, _resolvePremiumRequired(maybeLockedSt != nullptr) {
|
||||
if (maybeLockedSt
|
||||
&& (Api::ResolveRequiresPremiumToWrite(peer, maybeHistory)
|
||||
== Api::RequirePremiumState::Yes)) {
|
||||
_lockedSt = maybeLockedSt;
|
||||
, _maybeLockedSt(maybeLockedSt) {
|
||||
if (_maybeLockedSt) {
|
||||
setRestriction(Api::ResolveMessageMoneyRestrictions(
|
||||
peer,
|
||||
maybeHistory));
|
||||
}
|
||||
}
|
||||
|
||||
PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
auto result = PeerListRow::generatePaintUserpicCallback(forceRound);
|
||||
if (const auto st = _lockedSt) {
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) {
|
||||
result(p, x, y, outerWidth, size);
|
||||
PaintPremiumRequiredLock(p, st, x, y, outerWidth, size);
|
||||
};
|
||||
Api::MessageMoneyRestriction RecipientRow::restriction() const {
|
||||
return _restriction
|
||||
? _restriction->value
|
||||
: Api::MessageMoneyRestriction();
|
||||
}
|
||||
|
||||
void RecipientRow::setRestriction(Api::MessageMoneyRestriction restriction) {
|
||||
if (!restriction) {
|
||||
_restriction = nullptr;
|
||||
return;
|
||||
} else if (!_restriction) {
|
||||
_restriction = std::make_unique<Restriction>();
|
||||
}
|
||||
_restriction->value = restriction;
|
||||
}
|
||||
|
||||
void RecipientRow::paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) {
|
||||
if (const auto &r = _restriction) {
|
||||
PaintRestrictionBadge(
|
||||
p,
|
||||
_maybeLockedSt,
|
||||
r->value.starsPerMessage,
|
||||
r->cache,
|
||||
x,
|
||||
y,
|
||||
outerWidth,
|
||||
st.photoSize);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RecipientRow::refreshLock(
|
||||
not_null<const style::PeerListItem*> maybeLockedSt) {
|
||||
if (const auto user = peer()->asUser()) {
|
||||
const auto locked = _resolvePremiumRequired
|
||||
&& (Api::ResolveRequiresPremiumToWrite(user, _maybeHistory)
|
||||
== Api::RequirePremiumState::Yes);
|
||||
if (this->locked() != locked) {
|
||||
setLocked(locked ? maybeLockedSt.get() : nullptr);
|
||||
using Restriction = Api::MessageMoneyRestriction;
|
||||
const auto r = _maybeLockedSt
|
||||
? Api::ResolveMessageMoneyRestrictions(
|
||||
user,
|
||||
_maybeHistory)
|
||||
: Restriction();
|
||||
if ((_restriction ? _restriction->value : Restriction()) != r) {
|
||||
setRestriction(r);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -318,22 +350,30 @@ bool RecipientRow::refreshLock(
|
|||
void RecipientRow::preloadUserpic() {
|
||||
PeerListRow::preloadUserpic();
|
||||
|
||||
if (!_resolvePremiumRequired) {
|
||||
if (!_maybeLockedSt) {
|
||||
return;
|
||||
} else if (Api::ResolveRequiresPremiumToWrite(peer(), _maybeHistory)
|
||||
== Api::RequirePremiumState::Unknown) {
|
||||
const auto user = peer()->asUser();
|
||||
user->session().api().premium().resolvePremiumRequired(user);
|
||||
}
|
||||
const auto peer = this->peer();
|
||||
const auto known = Api::ResolveMessageMoneyRestrictions(
|
||||
peer,
|
||||
_maybeHistory).known;
|
||||
if (known) {
|
||||
return;
|
||||
} else if (const auto user = peer->asUser()) {
|
||||
const auto api = &user->session().api();
|
||||
api->premium().resolveMessageMoneyRestrictions(user);
|
||||
} else if (const auto group = peer->asChannel()) {
|
||||
group->updateFull();
|
||||
}
|
||||
}
|
||||
|
||||
void TrackPremiumRequiredChanges(
|
||||
void TrackMessageMoneyRestrictionsChanges(
|
||||
not_null<PeerListController*> controller,
|
||||
rpl::lifetime &lifetime) {
|
||||
const auto session = &controller->session();
|
||||
rpl::merge(
|
||||
Data::AmPremiumValue(session) | rpl::to_empty,
|
||||
session->api().premium().somePremiumRequiredResolved()
|
||||
session->api().premium().someMessageMoneyRestrictionsResolved()
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto st = &controller->computeListSt().item;
|
||||
const auto delegate = controller->delegate();
|
||||
|
@ -726,7 +766,7 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
|
|||
return std::make_unique<PeerListRow>(user);
|
||||
}
|
||||
|
||||
RecipientPremiumRequiredError WritePremiumRequiredError(
|
||||
RecipientMoneyRestrictionError WriteMoneyRestrictionError(
|
||||
not_null<UserData*> user) {
|
||||
return {
|
||||
.text = tr::lng_send_non_premium_message_toast(
|
||||
|
@ -759,7 +799,7 @@ ChooseRecipientBoxController::ChooseRecipientBoxController(
|
|||
, _session(args.session)
|
||||
, _callback(std::move(args.callback))
|
||||
, _filter(std::move(args.filter))
|
||||
, _premiumRequiredError(std::move(args.premiumRequiredError)) {
|
||||
, _moneyRestrictionError(std::move(args.moneyRestrictionError)) {
|
||||
}
|
||||
|
||||
Main::Session &ChooseRecipientBoxController::session() const {
|
||||
|
@ -769,14 +809,17 @@ Main::Session &ChooseRecipientBoxController::session() const {
|
|||
void ChooseRecipientBoxController::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(tr::lng_forward_choose());
|
||||
|
||||
if (_premiumRequiredError) {
|
||||
TrackPremiumRequiredChanges(this, lifetime());
|
||||
if (_moneyRestrictionError) {
|
||||
TrackMessageMoneyRestrictionsChanges(this, lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
bool ChooseRecipientBoxController::showLockedError(
|
||||
not_null<PeerListRow*> row) {
|
||||
return RecipientRow::ShowLockedError(this, row, _premiumRequiredError);
|
||||
return RecipientRow::ShowLockedError(
|
||||
this,
|
||||
row,
|
||||
_moneyRestrictionError);
|
||||
}
|
||||
|
||||
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
|
@ -836,8 +879,9 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
|||
bool RecipientRow::ShowLockedError(
|
||||
not_null<PeerListController*> controller,
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error) {
|
||||
if (!static_cast<RecipientRow*>(row.get())->locked()) {
|
||||
Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error) {
|
||||
const auto recipient = static_cast<RecipientRow*>(row.get());
|
||||
if (!recipient->restriction().premiumRequired) {
|
||||
return false;
|
||||
}
|
||||
::Settings::ShowPremiumPromoToast(
|
||||
|
@ -860,15 +904,15 @@ auto ChooseRecipientBoxController::createRow(
|
|||
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|
||||
|| peer->isRepliesChat()
|
||||
|| peer->isVerifyCodes()
|
||||
|| (peer->isUser() && (_premiumRequiredError
|
||||
? !peer->asUser()->canSendIgnoreRequirePremium()
|
||||
|| (peer->isUser() && (_moneyRestrictionError
|
||||
? !peer->asUser()->canSendIgnoreMoneyRestrictions()
|
||||
: !Data::CanSendAnything(peer))));
|
||||
if (skip) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Row>(
|
||||
history,
|
||||
_premiumRequiredError ? &computeListSt().item : nullptr);
|
||||
_moneyRestrictionError ? &computeListSt().item : nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1093,25 +1137,61 @@ auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
|
|||
return skip ? nullptr : std::make_unique<Row>(topic);
|
||||
};
|
||||
|
||||
void PaintPremiumRequiredLock(
|
||||
void PaintRestrictionBadge(
|
||||
Painter &p,
|
||||
not_null<const style::PeerListItem*> st,
|
||||
int stars,
|
||||
RestrictionBadgeCache &cache,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto paletteVersion = style::PaletteVersion();
|
||||
const auto good = !cache.badge.isNull()
|
||||
&& (cache.stars == stars)
|
||||
&& (cache.paletteVersion == paletteVersion);
|
||||
const auto &check = st->checkbox.check;
|
||||
auto pen = check.border->p;
|
||||
pen.setWidthF(check.width);
|
||||
p.setPen(pen);
|
||||
p.setBrush(st::premiumButtonBg2);
|
||||
const auto &icon = st::stickersPremiumLock;
|
||||
const auto width = icon.width();
|
||||
const auto height = icon.height();
|
||||
const auto rect = QRect(
|
||||
QPoint(x + size - width, y + size - height),
|
||||
icon.size());
|
||||
p.drawEllipse(rect);
|
||||
icon.paintInCenter(p, rect);
|
||||
const auto add = check.width;
|
||||
if (!good) {
|
||||
cache.stars = stars;
|
||||
cache.paletteVersion = paletteVersion;
|
||||
if (stars) {
|
||||
const auto text = (stars >= 1000)
|
||||
? (QString::number(stars / 1000) + 'K')
|
||||
: QString::number(stars);
|
||||
cache.badge = Ui::GenerateSmallBadgeImage(
|
||||
text,
|
||||
st::paidReactTopStarIcon,
|
||||
check.bgActive->c,
|
||||
st::premiumButtonFg->c,
|
||||
&check);
|
||||
} else {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto &icon = st::stickersPremiumLock;
|
||||
const auto width = icon.width();
|
||||
const auto height = icon.height();
|
||||
const auto rect = QRect(
|
||||
QPoint(x + size - width, y + size - height),
|
||||
icon.size());
|
||||
const auto added = QMargins(add, add, add, add);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
cache.badge = QImage(
|
||||
(rect + added).size() * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
cache.badge.setDevicePixelRatio(ratio);
|
||||
cache.badge.fill(Qt::transparent);
|
||||
const auto inner = QRect(add, add, rect.width(), rect.height());
|
||||
auto q = QPainter(&cache.badge);
|
||||
auto pen = check.border->p;
|
||||
pen.setWidthF(check.width);
|
||||
q.setPen(pen);
|
||||
q.setBrush(st::premiumButtonBg2);
|
||||
q.drawEllipse(inner);
|
||||
icon.paintInCenter(q, inner);
|
||||
}
|
||||
}
|
||||
const auto cached = cache.badge.size() / cache.badge.devicePixelRatio();
|
||||
const auto left = x + size + add - cached.width();
|
||||
const auto top = stars ? (y - add) : (y + size + add - cached.height());
|
||||
p.drawImage(left, top, cache.badge);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ namespace style {
|
|||
struct PeerListItem;
|
||||
} // namespace style
|
||||
|
||||
namespace Api {
|
||||
struct MessageMoneyRestriction;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
class Thread;
|
||||
class Forum;
|
||||
|
@ -93,13 +97,28 @@ private:
|
|||
|
||||
};
|
||||
|
||||
struct RecipientPremiumRequiredError {
|
||||
struct RecipientMoneyRestrictionError {
|
||||
TextWithEntities text;
|
||||
};
|
||||
|
||||
[[nodiscard]] RecipientPremiumRequiredError WritePremiumRequiredError(
|
||||
[[nodiscard]] RecipientMoneyRestrictionError WriteMoneyRestrictionError(
|
||||
not_null<UserData*> user);
|
||||
|
||||
struct RestrictionBadgeCache {
|
||||
int paletteVersion = 0;
|
||||
int stars = 0;
|
||||
QImage badge;
|
||||
};
|
||||
void PaintRestrictionBadge(
|
||||
Painter &p,
|
||||
not_null<const style::PeerListItem*> st,
|
||||
int stars,
|
||||
RestrictionBadgeCache &cache,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size);
|
||||
|
||||
class RecipientRow : public PeerListRow {
|
||||
public:
|
||||
explicit RecipientRow(
|
||||
|
@ -112,30 +131,33 @@ public:
|
|||
[[nodiscard]] static bool ShowLockedError(
|
||||
not_null<PeerListController*> controller,
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error);
|
||||
Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error);
|
||||
|
||||
[[nodiscard]] History *maybeHistory() const {
|
||||
return _maybeHistory;
|
||||
}
|
||||
[[nodiscard]] bool locked() const {
|
||||
return _lockedSt != nullptr;
|
||||
}
|
||||
void setLocked(const style::PeerListItem *lockedSt) {
|
||||
_lockedSt = lockedSt;
|
||||
}
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
void paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) override;
|
||||
|
||||
void preloadUserpic() override;
|
||||
|
||||
[[nodiscard]] Api::MessageMoneyRestriction restriction() const;
|
||||
void setRestriction(Api::MessageMoneyRestriction restriction);
|
||||
|
||||
private:
|
||||
struct Restriction;
|
||||
|
||||
History *_maybeHistory = nullptr;
|
||||
const style::PeerListItem *_lockedSt = nullptr;
|
||||
bool _resolvePremiumRequired = false;
|
||||
const style::PeerListItem *_maybeLockedSt = nullptr;
|
||||
std::shared_ptr<Restriction> _restriction;
|
||||
|
||||
};
|
||||
|
||||
void TrackPremiumRequiredChanges(
|
||||
void TrackMessageMoneyRestrictionsChanges(
|
||||
not_null<PeerListController*> controller,
|
||||
rpl::lifetime &lifetime);
|
||||
|
||||
|
@ -261,8 +283,8 @@ struct ChooseRecipientArgs {
|
|||
FnMut<void(not_null<Data::Thread*>)> callback;
|
||||
Fn<bool(not_null<Data::Thread*>)> filter;
|
||||
|
||||
using PremiumRequiredError = RecipientPremiumRequiredError;
|
||||
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
|
||||
using MoneyRestrictionError = RecipientMoneyRestrictionError;
|
||||
Fn<MoneyRestrictionError(not_null<UserData*>)> moneyRestrictionError;
|
||||
};
|
||||
|
||||
class ChooseRecipientBoxController
|
||||
|
@ -290,8 +312,8 @@ private:
|
|||
const not_null<Main::Session*> _session;
|
||||
FnMut<void(not_null<Data::Thread*>)> _callback;
|
||||
Fn<bool(not_null<Data::Thread*>)> _filter;
|
||||
Fn<RecipientPremiumRequiredError(
|
||||
not_null<UserData*>)> _premiumRequiredError;
|
||||
Fn<RecipientMoneyRestrictionError(
|
||||
not_null<UserData*>)> _moneyRestrictionError;
|
||||
|
||||
};
|
||||
|
||||
|
@ -371,11 +393,3 @@ private:
|
|||
Fn<bool(not_null<Data::ForumTopic*>)> _filter;
|
||||
|
||||
};
|
||||
|
||||
void PaintPremiumRequiredLock(
|
||||
Painter &p,
|
||||
not_null<const style::PeerListItem*> st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size);
|
||||
|
|
|
@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "boxes/peers/edit_participant_box.h"
|
||||
#include "boxes/peers/edit_peer_type_box.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/max_invite_box.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
|
@ -22,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_changes.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/show_or_premium_box.h"
|
||||
|
@ -52,16 +55,39 @@ constexpr auto kUserpicsLimit = 3;
|
|||
|
||||
class ForbiddenRow final : public PeerListRow {
|
||||
public:
|
||||
ForbiddenRow(not_null<PeerData*> peer, bool locked);
|
||||
ForbiddenRow(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const style::PeerListItem*> lockSt,
|
||||
bool locked);
|
||||
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
|
||||
Api::MessageMoneyRestriction restriction() const;
|
||||
void setRestriction(Api::MessageMoneyRestriction restriction);
|
||||
|
||||
void preloadUserpic() override;
|
||||
void paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) override;
|
||||
|
||||
bool refreshLock();
|
||||
|
||||
private:
|
||||
struct Restriction {
|
||||
Api::MessageMoneyRestriction value;
|
||||
RestrictionBadgeCache cache;
|
||||
};
|
||||
|
||||
const bool _locked = false;
|
||||
const not_null<const style::PeerListItem*> _lockSt;
|
||||
QImage _disabledFrame;
|
||||
InMemoryKey _userpicKey;
|
||||
int _paletteVersion = 0;
|
||||
std::shared_ptr<Restriction> _restriction;
|
||||
|
||||
};
|
||||
|
||||
|
@ -81,6 +107,9 @@ public:
|
|||
[[nodiscard]] rpl::producer<int> selectedValue() const {
|
||||
return _selected.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<int> starsToSend() const {
|
||||
return _starsToSend.value();
|
||||
}
|
||||
|
||||
void send(
|
||||
std::vector<not_null<PeerData*>> list,
|
||||
|
@ -89,10 +118,16 @@ public:
|
|||
|
||||
private:
|
||||
void appendRow(not_null<UserData*> user);
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
||||
[[nodiscard]] std::unique_ptr<ForbiddenRow> createRow(
|
||||
not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canInvite(not_null<PeerData*> peer) const;
|
||||
|
||||
void send(
|
||||
std::vector<not_null<PeerData*>> list,
|
||||
Ui::ShowPtr show,
|
||||
Fn<void()> close,
|
||||
Api::SendOptions options);
|
||||
|
||||
void setSimpleCover();
|
||||
void setComplexCover();
|
||||
|
||||
|
@ -101,8 +136,11 @@ private:
|
|||
const std::vector<not_null<UserData*>> &_users;
|
||||
const bool _can = false;
|
||||
rpl::variable<int> _selected;
|
||||
rpl::variable<int> _starsToSend;
|
||||
bool _sending = false;
|
||||
|
||||
rpl::lifetime _paymentCheckLifetime;
|
||||
|
||||
};
|
||||
|
||||
base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
|
||||
|
@ -256,11 +294,17 @@ Main::Session &InviteForbiddenController::session() const {
|
|||
return _peer->session();
|
||||
}
|
||||
|
||||
ForbiddenRow::ForbiddenRow(not_null<PeerData*> peer, bool locked)
|
||||
ForbiddenRow::ForbiddenRow(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const style::PeerListItem*> lockSt,
|
||||
bool locked)
|
||||
: PeerListRow(peer)
|
||||
, _locked(locked) {
|
||||
, _locked(locked)
|
||||
, _lockSt(lockSt) {
|
||||
if (_locked) {
|
||||
setCustomStatus(tr::lng_invite_status_disabled(tr::now));
|
||||
} else {
|
||||
setRestriction(Api::ResolveMessageMoneyRestrictions(peer, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,6 +383,76 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
Api::MessageMoneyRestriction ForbiddenRow::restriction() const {
|
||||
return _restriction
|
||||
? _restriction->value
|
||||
: Api::MessageMoneyRestriction();
|
||||
}
|
||||
|
||||
void ForbiddenRow::setRestriction(Api::MessageMoneyRestriction restriction) {
|
||||
if (!restriction || !restriction.starsPerMessage) {
|
||||
_restriction = nullptr;
|
||||
return;
|
||||
} else if (!_restriction) {
|
||||
_restriction = std::make_unique<Restriction>();
|
||||
}
|
||||
_restriction->value = restriction;
|
||||
}
|
||||
|
||||
void ForbiddenRow::paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) {
|
||||
if (const auto &r = _restriction) {
|
||||
PaintRestrictionBadge(
|
||||
p,
|
||||
_lockSt,
|
||||
r->value.starsPerMessage,
|
||||
r->cache,
|
||||
x,
|
||||
y,
|
||||
outerWidth,
|
||||
st.photoSize);
|
||||
}
|
||||
}
|
||||
|
||||
bool ForbiddenRow::refreshLock() {
|
||||
if (_locked) {
|
||||
return false;
|
||||
} else if (const auto user = peer()->asUser()) {
|
||||
using Restriction = Api::MessageMoneyRestriction;
|
||||
auto r = Api::ResolveMessageMoneyRestrictions(user, nullptr);
|
||||
if (!r || !r.starsPerMessage) {
|
||||
r = Restriction();
|
||||
}
|
||||
if ((_restriction ? _restriction->value : Restriction()) != r) {
|
||||
setRestriction(r);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ForbiddenRow::preloadUserpic() {
|
||||
PeerListRow::preloadUserpic();
|
||||
|
||||
const auto peer = this->peer();
|
||||
const auto known = Api::ResolveMessageMoneyRestrictions(
|
||||
peer,
|
||||
nullptr).known;
|
||||
if (known) {
|
||||
return;
|
||||
} else if (const auto user = peer->asUser()) {
|
||||
const auto api = &user->session().api();
|
||||
api->premium().resolveMessageMoneyRestrictions(user);
|
||||
} else if (const auto group = peer->asChannel()) {
|
||||
group->updateFull();
|
||||
}
|
||||
}
|
||||
|
||||
void InviteForbiddenController::setSimpleCover() {
|
||||
delegate()->peerListSetTitle(
|
||||
_can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant());
|
||||
|
@ -435,6 +549,30 @@ void InviteForbiddenController::setComplexCover() {
|
|||
}
|
||||
|
||||
void InviteForbiddenController::prepare() {
|
||||
session().api().premium().someMessageMoneyRestrictionsResolved(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto stars = 0;
|
||||
const auto process = [&](not_null<PeerListRow*> raw) {
|
||||
const auto row = static_cast<ForbiddenRow*>(raw.get());
|
||||
if (row->refreshLock()) {
|
||||
delegate()->peerListUpdateRow(raw);
|
||||
}
|
||||
if (const auto r = row->restriction()) {
|
||||
stars += r.starsPerMessage;
|
||||
}
|
||||
};
|
||||
auto count = delegate()->peerListFullRowsCount();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
process(delegate()->peerListRowAt(i));
|
||||
}
|
||||
_starsToSend = stars;
|
||||
|
||||
count = delegate()->peerListSearchRowsCount();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
process(delegate()->peerListSearchRowAt(i));
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
if (session().premium()
|
||||
|| (_forbidden.premiumAllowsInvite.empty()
|
||||
&& _forbidden.premiumAllowsWrite.empty())) {
|
||||
|
@ -464,6 +602,11 @@ void InviteForbiddenController::rowClicked(not_null<PeerListRow*> row) {
|
|||
const auto checked = row->checked();
|
||||
delegate()->peerListSetRowChecked(row, !checked);
|
||||
_selected = _selected.current() + (checked ? -1 : 1);
|
||||
const auto r = static_cast<ForbiddenRow*>(row.get())->restriction();
|
||||
if (r.starsPerMessage) {
|
||||
_starsToSend = _starsToSend.current()
|
||||
+ (checked ? -r.starsPerMessage : r.starsPerMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void InviteForbiddenController::appendRow(not_null<UserData*> user) {
|
||||
|
@ -473,6 +616,9 @@ void InviteForbiddenController::appendRow(not_null<UserData*> user) {
|
|||
delegate()->peerListAppendRow(std::move(row));
|
||||
if (canInvite(user)) {
|
||||
delegate()->peerListSetRowChecked(raw, true);
|
||||
if (const auto r = raw->restriction()) {
|
||||
_starsToSend = _starsToSend.current() + r.starsPerMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -481,7 +627,64 @@ void InviteForbiddenController::send(
|
|||
std::vector<not_null<PeerData*>> list,
|
||||
Ui::ShowPtr show,
|
||||
Fn<void()> close) {
|
||||
if (_sending || list.empty()) {
|
||||
send(list, show, close, {});
|
||||
}
|
||||
|
||||
void InviteForbiddenController::send(
|
||||
std::vector<not_null<PeerData*>> list,
|
||||
Ui::ShowPtr show,
|
||||
Fn<void()> close,
|
||||
Api::SendOptions options) {
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
_paymentCheckLifetime.destroy();
|
||||
|
||||
const auto withPaymentApproved = [=](int approved) {
|
||||
auto copy = options;
|
||||
copy.starsApproved = approved;
|
||||
send(list, show, close, copy);
|
||||
};
|
||||
const auto messagesCount = 1;
|
||||
const auto alreadyApproved = options.starsApproved;
|
||||
auto paid = std::vector<not_null<PeerData*>>();
|
||||
auto waiting = base::flat_set<not_null<PeerData*>>();
|
||||
auto totalStars = 0;
|
||||
for (const auto &peer : list) {
|
||||
const auto details = ComputePaymentDetails(peer, messagesCount);
|
||||
if (!details) {
|
||||
waiting.emplace(peer);
|
||||
} else if (details->stars > 0) {
|
||||
totalStars += details->stars;
|
||||
paid.push_back(peer);
|
||||
}
|
||||
}
|
||||
if (!waiting.empty()) {
|
||||
session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::FullInfo
|
||||
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
if (waiting.contains(update.peer)) {
|
||||
withPaymentApproved(alreadyApproved);
|
||||
}
|
||||
}, _paymentCheckLifetime);
|
||||
|
||||
if (!session().credits().loaded()) {
|
||||
session().credits().loadedValue(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
withPaymentApproved(alreadyApproved);
|
||||
}, _paymentCheckLifetime);
|
||||
}
|
||||
return;
|
||||
} else if (totalStars > alreadyApproved) {
|
||||
const auto sessionShow = Main::MakeSessionShow(show, &session());
|
||||
ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
|
||||
.messages = messagesCount,
|
||||
.stars = totalStars,
|
||||
}, [=] { withPaymentApproved(totalStars); });
|
||||
return;
|
||||
} else if (_sending) {
|
||||
return;
|
||||
}
|
||||
_sending = true;
|
||||
|
@ -492,12 +695,18 @@ void InviteForbiddenController::send(
|
|||
if (link.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
auto full = options;
|
||||
auto &api = _peer->session().api();
|
||||
auto options = Api::SendOptions();
|
||||
for (const auto &to : list) {
|
||||
auto copy = full;
|
||||
copy.starsApproved = std::min(
|
||||
to->starsPerMessageChecked(),
|
||||
full.starsApproved);
|
||||
full.starsApproved -= copy.starsApproved;
|
||||
|
||||
const auto history = to->owner().history(to);
|
||||
auto message = Api::MessageToSend(
|
||||
Api::SendAction(history, options));
|
||||
Api::SendAction(history, copy));
|
||||
message.textWithTags = { link };
|
||||
message.action.clearDraft = false;
|
||||
api.sendMessage(std::move(message));
|
||||
|
@ -542,10 +751,11 @@ void InviteForbiddenController::send(
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> InviteForbiddenController::createRow(
|
||||
std::unique_ptr<ForbiddenRow> InviteForbiddenController::createRow(
|
||||
not_null<UserData*> user) const {
|
||||
const auto locked = _can && !canInvite(user);
|
||||
return std::make_unique<ForbiddenRow>(user, locked);
|
||||
const auto lockSt = &computeListSt().item;
|
||||
return std::make_unique<ForbiddenRow>(user, lockSt, locked);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -584,8 +794,8 @@ void AddParticipantsBoxController::subscribeToMigration() {
|
|||
}
|
||||
|
||||
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto premiumRequiredError = WritePremiumRequiredError;
|
||||
if (RecipientRow::ShowLockedError(this, row, premiumRequiredError)) {
|
||||
const auto moneyRestrictionError = WriteMoneyRestrictionError;
|
||||
if (RecipientRow::ShowLockedError(this, row, moneyRestrictionError)) {
|
||||
return;
|
||||
}
|
||||
const auto &serverConfig = session().serverConfig();
|
||||
|
@ -614,7 +824,7 @@ void AddParticipantsBoxController::itemDeselectedHook(
|
|||
void AddParticipantsBoxController::prepareViewHook() {
|
||||
updateTitle();
|
||||
|
||||
TrackPremiumRequiredChanges(this, lifetime());
|
||||
TrackMessageMoneyRestrictionsChanges(this, lifetime());
|
||||
}
|
||||
|
||||
int AddParticipantsBoxController::alreadyInCount() const {
|
||||
|
@ -929,12 +1139,15 @@ bool ChatInviteForbidden(
|
|||
) | rpl::start_with_next([=](bool has) {
|
||||
box->clearButtons();
|
||||
if (has) {
|
||||
box->addButton(tr::lng_via_link_send(), [=] {
|
||||
const auto send = box->addButton(tr::lng_via_link_send(), [=] {
|
||||
weak->send(
|
||||
box->collectSelectedRows(),
|
||||
box->uiShow(),
|
||||
crl::guard(box, [=] { box->closeBox(); }));
|
||||
});
|
||||
send->setText(PaidSendButtonText(
|
||||
weak->starsToSend(),
|
||||
tr::lng_via_link_send()));
|
||||
}
|
||||
box->addButton(tr::lng_create_group_skip(), [=] {
|
||||
box->closeBox();
|
||||
|
|
|
@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/background_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "core/ui_integration.h" // TextContext
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_changes.h"
|
||||
|
@ -165,7 +165,7 @@ private:
|
|||
|
||||
const uint32 _level;
|
||||
const TextWithEntities _icon;
|
||||
const Core::MarkedTextContext _context;
|
||||
const Ui::Text::MarkedContext _context;
|
||||
Ui::Text::String _text;
|
||||
bool _minimal = false;
|
||||
|
||||
|
@ -466,7 +466,10 @@ LevelBadge::LevelBadge(
|
|||
st::settingsLevelBadgeLock,
|
||||
QMargins(0, st::settingsLevelBadgeLockSkip, 0, 0),
|
||||
false)))
|
||||
, _context({ .session = session }) {
|
||||
, _context(Core::TextContext({
|
||||
.session = session,
|
||||
.repaint = [this] { update(); },
|
||||
})) {
|
||||
updateText();
|
||||
}
|
||||
|
||||
|
|
|
@ -219,6 +219,33 @@ void SaveSlowmodeSeconds(
|
|||
api->registerModifyRequest(key, requestId);
|
||||
}
|
||||
|
||||
void SaveStarsPerMessage(
|
||||
not_null<ChannelData*> channel,
|
||||
int starsPerMessage,
|
||||
Fn<void()> done) {
|
||||
const auto api = &channel->session().api();
|
||||
const auto key = Api::RequestKey("stars_per_message", channel->id);
|
||||
|
||||
const auto requestId = api->request(MTPchannels_UpdatePaidMessagesPrice(
|
||||
channel->inputChannel,
|
||||
MTP_long(starsPerMessage)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->clearModifyRequest(key);
|
||||
api->applyUpdates(result);
|
||||
channel->setStarsPerMessage(starsPerMessage);
|
||||
done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
api->clearModifyRequest(key);
|
||||
if (error.type() != u"CHAT_NOT_MODIFIED"_q) {
|
||||
return;
|
||||
}
|
||||
channel->setStarsPerMessage(starsPerMessage);
|
||||
done();
|
||||
}).send();
|
||||
|
||||
api->registerModifyRequest(key, requestId);
|
||||
}
|
||||
|
||||
void SaveBoostsUnrestrict(
|
||||
not_null<ChannelData*> channel,
|
||||
int boostsUnrestrict,
|
||||
|
@ -271,6 +298,7 @@ void ShowEditPermissions(
|
|||
channel,
|
||||
result.boostsUnrestrict,
|
||||
close);
|
||||
SaveStarsPerMessage(channel, result.starsPerMessage, close);
|
||||
}
|
||||
};
|
||||
auto done = [=](EditPeerPermissionsBoxResult result) {
|
||||
|
@ -282,7 +310,9 @@ void ShowEditPermissions(
|
|||
const auto saveFor = peer->migrateToOrMe();
|
||||
const auto chat = saveFor->asChat();
|
||||
if (!chat
|
||||
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) {
|
||||
|| (!result.slowmodeSeconds
|
||||
&& !result.boostsUnrestrict
|
||||
&& !result.starsPerMessage)) {
|
||||
save(saveFor, result);
|
||||
return;
|
||||
}
|
||||
|
@ -2689,3 +2719,9 @@ bool EditPeerInfoBox::Available(not_null<PeerData*> peer) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ShowEditChatPermissions(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
ShowEditPermissions(navigation, peer);
|
||||
}
|
||||
|
|
|
@ -56,3 +56,7 @@ private:
|
|||
not_null<PeerData*> _peer;
|
||||
|
||||
};
|
||||
|
||||
void ShowEditChatPermissions(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer);
|
||||
|
|
|
@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "core/ui_integration.h" // TextContext
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
|
@ -740,10 +740,10 @@ void Controller::setupAboveJoinedWidget() {
|
|||
{ QString::number(current.subscription.credits) },
|
||||
Ui::Text::WithEntities),
|
||||
kMarkupTextOptions,
|
||||
Core::MarkedTextContext{
|
||||
Core::TextContext({
|
||||
.session = &session(),
|
||||
.customEmojiRepaint = [=] { widget->update(); },
|
||||
});
|
||||
.repaint = [=] { widget->update(); },
|
||||
}));
|
||||
auto &lifetime = widget->lifetime();
|
||||
const auto rateValue = lifetime.make_state<rpl::variable<float64>>(
|
||||
session().credits().rateValue(_peer));
|
||||
|
@ -994,10 +994,7 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
|||
lt_cost,
|
||||
{ QString::number(data.subscription.credits) },
|
||||
Ui::Text::WithEntities),
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { subtitle1->update(); },
|
||||
});
|
||||
Core::TextContext({ .session = session }));
|
||||
const auto subtitle2 = box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
|
@ -1484,8 +1481,12 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
|||
? tr::lng_group_invite_copied(tr::now)
|
||||
: copied);
|
||||
};
|
||||
auto countMessagesCallback = [=](const TextWithTags &comment) {
|
||||
return 1;
|
||||
};
|
||||
auto submitCallback = [=](
|
||||
std::vector<not_null<Data::Thread*>> &&result,
|
||||
Fn<bool()> checkPaid,
|
||||
TextWithTags &&comment,
|
||||
Api::SendOptions options,
|
||||
Data::ForwardOptions) {
|
||||
|
@ -1503,6 +1504,8 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
|||
result.size() > 1));
|
||||
}
|
||||
return;
|
||||
} else if (!checkPaid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
*sending = true;
|
||||
|
@ -1530,7 +1533,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
|||
};
|
||||
auto filterCallback = [](not_null<Data::Thread*> thread) {
|
||||
if (const auto user = thread->peer()->asUser()) {
|
||||
if (user->canSendIgnoreRequirePremium()) {
|
||||
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1539,9 +1542,10 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
|||
auto object = Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = session,
|
||||
.copyCallback = std::move(copyCallback),
|
||||
.countMessagesCallback = std::move(countMessagesCallback),
|
||||
.submitCallback = std::move(submitCallback),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||
});
|
||||
*box = Ui::MakeWeak(object.data());
|
||||
return object;
|
||||
|
|
|
@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "info/profile/info_profile_values.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "boxes/peers/edit_peer_info_box.h"
|
||||
#include "boxes/edit_privacy_box.h"
|
||||
#include "settings/settings_power_saving.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
|
@ -891,11 +892,10 @@ void AddBoostsUnrestrictLabels(
|
|||
manager->registerInternalEmoji(
|
||||
st::boostsMessageIcon,
|
||||
st::boostsMessageIconPadding));
|
||||
const auto context = Core::MarkedTextContext{
|
||||
const auto context = Core::TextContext({
|
||||
.session = session,
|
||||
.customEmojiRepaint = [] {},
|
||||
.customEmojiLoopLimit = 1,
|
||||
};
|
||||
});
|
||||
for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
labels,
|
||||
|
@ -942,9 +942,7 @@ rpl::producer<int> AddBoostsUnrestrictSlider(
|
|||
const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
|
||||
channel ? channel->boostsUnrestrict() : 0);
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::BoxContentDivider>(container),
|
||||
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
|
||||
Ui::AddSkip(container);
|
||||
|
||||
auto enabled = boostsUnrestrict->value(
|
||||
) | rpl::map(_1 > 0);
|
||||
|
@ -1008,19 +1006,20 @@ rpl::producer<int> AddBoostsUnrestrictWrapped(
|
|||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
wrap->toggleOn(rpl::duplicate(shown), anim::type::normal);
|
||||
wrap->toggleOn(std::move(shown), anim::type::normal);
|
||||
wrap->finishAnimating();
|
||||
|
||||
auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer);
|
||||
const auto divider = container->add(
|
||||
const auto inner = wrap->entity();
|
||||
|
||||
auto result = AddBoostsUnrestrictSlider(inner, peer);
|
||||
|
||||
const auto skip = st::defaultVerticalListSkip;
|
||||
const auto divider = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
|
||||
container,
|
||||
object_ptr<Ui::BoxContentDivider>(container),
|
||||
QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip }));
|
||||
divider->toggleOn(rpl::combine(
|
||||
std::move(shown),
|
||||
rpl::duplicate(result),
|
||||
!rpl::mappers::_1 || !rpl::mappers::_2));
|
||||
inner,
|
||||
object_ptr<Ui::BoxContentDivider>(inner),
|
||||
QMargins{ 0, skip, 0, skip }));
|
||||
divider->toggleOn(rpl::duplicate(result) | rpl::map(!rpl::mappers::_1));
|
||||
divider->finishAnimating();
|
||||
|
||||
return result;
|
||||
|
@ -1159,7 +1158,43 @@ void ShowEditPeerPermissionsBox(
|
|||
rpl::variable<int> slowmodeSeconds;
|
||||
rpl::variable<int> boostsUnrestrict;
|
||||
rpl::variable<bool> hasSendRestrictions;
|
||||
rpl::variable<int> starsPerMessage;
|
||||
};
|
||||
const auto state = inner->lifetime().make_state<State>();
|
||||
const auto channel = peer->asChannel();
|
||||
const auto available = channel && channel->paidMessagesAvailable();
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDivider(inner);
|
||||
auto charging = (Ui::SettingsButton*)nullptr;
|
||||
if (available) {
|
||||
Ui::AddSkip(inner);
|
||||
const auto starsPerMessage = peer->isChannel()
|
||||
? peer->asChannel()->starsPerMessage()
|
||||
: 0;
|
||||
charging = inner->add(object_ptr<Ui::SettingsButton>(
|
||||
inner,
|
||||
tr::lng_rights_charge_stars(),
|
||||
st::settingsButtonNoIcon));
|
||||
charging->toggleOn(rpl::single(starsPerMessage > 0));
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about());
|
||||
|
||||
const auto chargeWrap = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
chargeWrap->toggleOn(charging->toggledValue());
|
||||
chargeWrap->finishAnimating();
|
||||
const auto chargeInner = chargeWrap->entity();
|
||||
|
||||
Ui::AddSkip(chargeInner);
|
||||
state->starsPerMessage = SetupChargeSlider(
|
||||
chargeInner,
|
||||
peer,
|
||||
starsPerMessage);
|
||||
}
|
||||
|
||||
static constexpr auto kSendRestrictions = Flag::EmbedLinks
|
||||
| Flag::SendGames
|
||||
| Flag::SendGifs
|
||||
|
@ -1173,7 +1208,6 @@ void ShowEditPeerPermissionsBox(
|
|||
| Flag::SendVoiceMessages
|
||||
| Flag::SendFiles
|
||||
| Flag::SendOther;
|
||||
const auto state = inner->lifetime().make_state<State>();
|
||||
state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
|
||||
|| (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
|
||||
state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
|
||||
|
@ -1214,10 +1248,14 @@ void ShowEditPeerPermissionsBox(
|
|||
const auto boostsUnrestrict = hasRestrictions
|
||||
? state->boostsUnrestrict.current()
|
||||
: 0;
|
||||
const auto starsPerMessage = (charging && charging->toggled())
|
||||
? state->starsPerMessage.current()
|
||||
: 0;
|
||||
done({
|
||||
restrictions,
|
||||
slowmodeSeconds,
|
||||
boostsUnrestrict,
|
||||
starsPerMessage,
|
||||
});
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
|
|
|
@ -39,6 +39,7 @@ struct EditPeerPermissionsBoxResult final {
|
|||
ChatRestrictions rights;
|
||||
int slowmodeSeconds = 0;
|
||||
int boostsUnrestrict = 0;
|
||||
int starsPerMessage = 0;
|
||||
};
|
||||
|
||||
void ShowEditPeerPermissionsBox(
|
||||
|
|
|
@ -363,12 +363,15 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
|||
const auto customEmojiPaused = [controller = args.controller] {
|
||||
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
|
||||
};
|
||||
auto factory = [=](QStringView data, Fn<void()> update)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
auto context = Core::TextContext({
|
||||
.session = session,
|
||||
});
|
||||
context.customEmojiFactory = [=](
|
||||
QStringView data,
|
||||
const Ui::Text::MarkedContext &context
|
||||
) -> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
const auto id = Data::ParseCustomEmojiData(data);
|
||||
auto result = owner->customEmojiManager().create(
|
||||
data,
|
||||
std::move(update));
|
||||
auto result = Ui::Text::MakeCustomEmoji(data, context);
|
||||
if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) {
|
||||
return std::make_unique<MaybeDisabledEmoji>(
|
||||
std::move(result),
|
||||
|
@ -377,12 +380,10 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
|||
using namespace Ui::Text;
|
||||
return std::make_unique<FirstFrameEmoji>(std::move(result));
|
||||
};
|
||||
raw->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
});
|
||||
}, customEmojiPaused, customEmojiPaused, std::move(factory));
|
||||
raw->setCustomTextContext(
|
||||
std::move(context),
|
||||
customEmojiPaused,
|
||||
customEmojiPaused);
|
||||
|
||||
const auto callback = args.callback;
|
||||
const auto isCustom = [=](DocumentId id) {
|
||||
|
|
|
@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "api/api_credits.h"
|
||||
#include "apiwrap.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "core/ui_integration.h" // TextContext
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_photo.h"
|
||||
|
@ -511,32 +511,28 @@ TextWithEntities CreditsEmoji(not_null<Main::Session*> session) {
|
|||
}
|
||||
|
||||
TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::starIconSmall,
|
||||
st::starIconSmallPadding,
|
||||
true),
|
||||
return Ui::Text::IconEmoji(
|
||||
&st::starIconEmoji,
|
||||
QString(QChar(0x2B50)));
|
||||
}
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
Text::MarkedContext context,
|
||||
const style::FlatLabel &st,
|
||||
const style::color *textFg) {
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
st);
|
||||
context.repaint = [=] { buttonLabel->update(); };
|
||||
rpl::duplicate(
|
||||
text
|
||||
) | rpl::filter([=](const TextWithEntities &text) {
|
||||
return !text.text.isEmpty();
|
||||
}) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
buttonLabel->setMarkedText(
|
||||
text,
|
||||
context([=] { buttonLabel->update(); }));
|
||||
buttonLabel->setMarkedText(text, context);
|
||||
}, buttonLabel->lifetime());
|
||||
if (textFg) {
|
||||
buttonLabel->setTextColorOverride((*textFg)->c);
|
||||
|
@ -565,15 +561,12 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
|||
not_null<Main::Session*> session,
|
||||
const style::FlatLabel &st,
|
||||
const style::color *textFg) {
|
||||
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = update,
|
||||
};
|
||||
}, st, textFg);
|
||||
return SetButtonMarkedLabel(button, text, Core::TextContext({
|
||||
.session = session,
|
||||
}), st, textFg);
|
||||
}
|
||||
|
||||
void SendStarGift(
|
||||
void SendStarsForm(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void(std::optional<QString>)> done) {
|
||||
|
|
|
@ -41,7 +41,7 @@ void SendCreditsBox(
|
|||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
Text::MarkedContext context,
|
||||
const style::FlatLabel &st,
|
||||
const style::color *textFg = nullptr);
|
||||
|
||||
|
@ -52,7 +52,7 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
|||
const style::FlatLabel &st,
|
||||
const style::color *textFg = nullptr);
|
||||
|
||||
void SendStarGift(
|
||||
void SendStarsForm(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void(std::optional<QString>)> done);
|
||||
|
|
|
@ -59,9 +59,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/window_session_controller.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
|
||||
|
@ -722,6 +722,18 @@ void SendFilesBox::openDialogToAddFileToAlbum() {
|
|||
crl::guard(this, callback));
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshMessagesCount() {
|
||||
const auto way = _sendWay.current();
|
||||
const auto withCaption = _list.canAddCaption(
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos());
|
||||
const auto withComment = !withCaption
|
||||
&& _caption
|
||||
&& !_caption->isHidden()
|
||||
&& !_caption->getTextWithTags().text.isEmpty();
|
||||
_messagesCount = _list.files.size() + (withComment ? 1 : 0);
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshButtons() {
|
||||
clearButtons();
|
||||
|
||||
|
@ -730,6 +742,15 @@ void SendFilesBox::refreshButtons() {
|
|||
? tr::lng_send_button()
|
||||
: tr::lng_create_group_next()),
|
||||
[=] { send({}); });
|
||||
refreshMessagesCount();
|
||||
|
||||
const auto perMessage = _captionToPeer
|
||||
? _captionToPeer->starsPerMessageChecked()
|
||||
: 0;
|
||||
if (perMessage > 0) {
|
||||
_send->setText(PaidSendButtonText(_messagesCount.value(
|
||||
) | rpl::map(rpl::mappers::_1 * perMessage)));
|
||||
}
|
||||
if (_sendType == Api::SendType::Normal) {
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
_send,
|
||||
|
@ -846,10 +867,9 @@ void SendFilesBox::refreshPriceTag() {
|
|||
QString(),
|
||||
st::paidTagLabel);
|
||||
std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
|
||||
label->setMarkedText(text, Core::MarkedTextContext{
|
||||
label->setMarkedText(text, Core::TextContext({
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { label->update(); },
|
||||
});
|
||||
}));
|
||||
}, label->lifetime());
|
||||
label->show();
|
||||
label->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
|
@ -1489,6 +1509,7 @@ void SendFilesBox::setupCaption() {
|
|||
_caption->changes()
|
||||
) | rpl::start_with_next([=] {
|
||||
checkCharsLimitation();
|
||||
refreshMessagesCount();
|
||||
}, _caption->lifetime());
|
||||
}
|
||||
|
||||
|
|
|
@ -246,6 +246,7 @@ private:
|
|||
void addPreparedAsyncFile(Ui::PreparedFile &&file);
|
||||
|
||||
void checkCharsLimitation();
|
||||
void refreshMessagesCount();
|
||||
|
||||
[[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails(
|
||||
const SendFilesBoxDescriptor &descriptor);
|
||||
|
@ -261,6 +262,7 @@ private:
|
|||
|
||||
Ui::PreparedList _list;
|
||||
std::optional<int> _removingIndex;
|
||||
rpl::variable<int> _messagesCount;
|
||||
|
||||
SendFilesLimits _limits = {};
|
||||
Fn<MenuDetails()> _sendMenuDetails;
|
||||
|
|
|
@ -123,12 +123,13 @@ private:
|
|||
Ui::RoundImageCheckbox checkbox;
|
||||
Ui::Text::String name;
|
||||
Ui::Animations::Simple nameActive;
|
||||
bool locked = false;
|
||||
Api::MessageMoneyRestriction restriction;
|
||||
RestrictionBadgeCache badgeCache;
|
||||
};
|
||||
|
||||
void invalidateCache();
|
||||
bool showLockedError(not_null<Chat*> chat);
|
||||
void refreshLockedRows();
|
||||
void refreshRestrictedRows();
|
||||
|
||||
[[nodiscard]] int displayedChatsCount() const;
|
||||
[[nodiscard]] not_null<Data::Thread*> chatThread(
|
||||
|
@ -137,7 +138,7 @@ private:
|
|||
void paintChat(Painter &p, not_null<Chat*> chat, int index);
|
||||
void updateChat(not_null<PeerData*> peer);
|
||||
void updateChatName(not_null<Chat*> chat);
|
||||
void initChatLocked(not_null<Chat*> chat);
|
||||
void initChatRestriction(not_null<Chat*> chat);
|
||||
void repaintChat(not_null<PeerData*> peer);
|
||||
int chatIndex(not_null<PeerData*> peer) const;
|
||||
void repaintChatAtIndex(int index);
|
||||
|
@ -517,9 +518,19 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
|
|||
|
||||
SendMenu::Details ShareBox::sendMenuDetails() const {
|
||||
const auto selected = _inner->selected();
|
||||
const auto type = ranges::all_of(
|
||||
selected | ranges::views::transform(&Data::Thread::peer),
|
||||
HistoryView::CanScheduleUntilOnline)
|
||||
const auto hasPaid = [&] {
|
||||
for (const auto &thread : selected) {
|
||||
if (thread->peer()->starsPerMessageChecked()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
const auto type = hasPaid
|
||||
? SendMenu::Type::SilentOnly
|
||||
: ranges::all_of(
|
||||
selected | ranges::views::transform(&Data::Thread::peer),
|
||||
HistoryView::CanScheduleUntilOnline)
|
||||
? SendMenu::Type::ScheduledToUser
|
||||
: (selected.size() == 1 && selected.front()->peer()->isSelf())
|
||||
? SendMenu::Type::Reminder
|
||||
|
@ -614,6 +625,9 @@ void ShareBox::createButtons() {
|
|||
showMenu(send);
|
||||
}
|
||||
}, send->lifetime());
|
||||
send->setText(PaidSendButtonText(
|
||||
_starsToSend.value(),
|
||||
tr::lng_share_confirm()));
|
||||
} else if (_descriptor.copyCallback) {
|
||||
addButton(_copyLinkText.value(), [=] { copyLink(); });
|
||||
}
|
||||
|
@ -657,6 +671,73 @@ void ShareBox::innerSelectedChanged(
|
|||
}
|
||||
|
||||
void ShareBox::submit(Api::SendOptions options) {
|
||||
_submitLifetime.destroy();
|
||||
|
||||
auto threads = _inner->selected();
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
const auto field = _comment->entity();
|
||||
auto comment = field->getTextWithAppliedMarkdown();
|
||||
const auto checkPaid = [=] {
|
||||
if (!_descriptor.countMessagesCallback) {
|
||||
return true;
|
||||
}
|
||||
const auto withPaymentApproved = crl::guard(weak, [=](int approved) {
|
||||
auto copy = options;
|
||||
copy.starsApproved = approved;
|
||||
submit(copy);
|
||||
});
|
||||
const auto messagesCount = _descriptor.countMessagesCallback(
|
||||
comment);
|
||||
const auto alreadyApproved = options.starsApproved;
|
||||
auto paid = std::vector<not_null<PeerData*>>();
|
||||
auto waiting = base::flat_set<not_null<PeerData*>>();
|
||||
auto totalStars = 0;
|
||||
for (const auto &thread : threads) {
|
||||
const auto peer = thread->peer();
|
||||
const auto details = ComputePaymentDetails(peer, messagesCount);
|
||||
if (!details) {
|
||||
waiting.emplace(peer);
|
||||
} else if (details->stars > 0) {
|
||||
totalStars += details->stars;
|
||||
paid.push_back(peer);
|
||||
}
|
||||
}
|
||||
if (!waiting.empty()) {
|
||||
_descriptor.session->changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::FullInfo
|
||||
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
if (waiting.contains(update.peer)) {
|
||||
withPaymentApproved(alreadyApproved);
|
||||
}
|
||||
}, _submitLifetime);
|
||||
|
||||
if (!_descriptor.session->credits().loaded()) {
|
||||
_descriptor.session->credits().loadedValue(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
withPaymentApproved(alreadyApproved);
|
||||
}, _submitLifetime);
|
||||
}
|
||||
return false;
|
||||
} else if (totalStars > alreadyApproved) {
|
||||
const auto show = uiShow();
|
||||
const auto session = _descriptor.session;
|
||||
const auto sessionShow = Main::MakeSessionShow(show, session);
|
||||
const auto scheduleBoxSt = _descriptor.st.scheduleBox.get();
|
||||
ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
|
||||
.messages = messagesCount,
|
||||
.stars = totalStars,
|
||||
}, [=] { withPaymentApproved(totalStars); }, PaidConfirmStyles{
|
||||
.label = (scheduleBoxSt
|
||||
? scheduleBoxSt->chooseDateTimeArgs.labelStyle
|
||||
: nullptr),
|
||||
.checkbox = _descriptor.st.checkbox,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (const auto onstack = _descriptor.submitCallback) {
|
||||
const auto forwardOptions = (_forwardOptions.captionsCount
|
||||
&& _forwardOptions.dropCaptions)
|
||||
|
@ -665,8 +746,9 @@ void ShareBox::submit(Api::SendOptions options) {
|
|||
? Data::ForwardOptions::NoSenderNames
|
||||
: Data::ForwardOptions::PreserveInfo;
|
||||
onstack(
|
||||
_inner->selected(),
|
||||
_comment->entity()->getTextWithAppliedMarkdown(),
|
||||
std::move(threads),
|
||||
checkPaid,
|
||||
std::move(comment),
|
||||
options,
|
||||
forwardOptions);
|
||||
}
|
||||
|
@ -686,9 +768,23 @@ void ShareBox::selectedChanged() {
|
|||
_comment->toggle(_hasSelected, anim::type::normal);
|
||||
_comment->resizeToWidth(st::boxWideWidth);
|
||||
}
|
||||
computeStarsCount();
|
||||
update();
|
||||
}
|
||||
|
||||
void ShareBox::computeStarsCount() {
|
||||
auto perMessage = 0;
|
||||
for (const auto &thread : _inner->selected()) {
|
||||
perMessage += thread->peer()->starsPerMessageChecked();
|
||||
}
|
||||
const auto messagesCount = _descriptor.countMessagesCallback
|
||||
? _descriptor.countMessagesCallback(_comment
|
||||
? _comment->entity()->getTextWithTags()
|
||||
: TextWithTags())
|
||||
: 0;
|
||||
_starsToSend = perMessage * messagesCount;
|
||||
}
|
||||
|
||||
void ShareBox::scrollTo(Ui::ScrollToRequest request) {
|
||||
scrollToY(request.ymin, request.ymax);
|
||||
//auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height();
|
||||
|
@ -726,13 +822,13 @@ ShareBox::Inner::Inner(
|
|||
_rowHeight = st::shareRowHeight;
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
if (_descriptor.premiumRequiredError) {
|
||||
if (_descriptor.moneyRestrictionError) {
|
||||
const auto session = _descriptor.session;
|
||||
rpl::merge(
|
||||
Data::AmPremiumValue(session) | rpl::to_empty,
|
||||
session->api().premium().somePremiumRequiredResolved()
|
||||
session->api().premium().someMessageMoneyRestrictionsResolved()
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshLockedRows();
|
||||
refreshRestrictedRows();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
|
@ -793,38 +889,36 @@ void ShareBox::Inner::invalidateCache() {
|
|||
}
|
||||
|
||||
bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) {
|
||||
if (!chat->locked) {
|
||||
if (!chat->restriction.premiumRequired) {
|
||||
return false;
|
||||
}
|
||||
::Settings::ShowPremiumPromoToast(
|
||||
Main::MakeSessionShow(_show, _descriptor.session),
|
||||
ChatHelpers::ResolveWindowDefault(),
|
||||
_descriptor.premiumRequiredError(chat->peer->asUser()).text,
|
||||
_descriptor.moneyRestrictionError(chat->peer->asUser()).text,
|
||||
u"require_premium"_q);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShareBox::Inner::refreshLockedRows() {
|
||||
void ShareBox::Inner::refreshRestrictedRows() {
|
||||
auto changed = false;
|
||||
for (const auto &[peer, data] : _dataMap) {
|
||||
const auto history = data->history;
|
||||
const auto locked = (Api::ResolveRequiresPremiumToWrite(
|
||||
const auto restriction = Api::ResolveMessageMoneyRestrictions(
|
||||
history->peer,
|
||||
history
|
||||
) == Api::RequirePremiumState::Yes);
|
||||
if (data->locked != locked) {
|
||||
data->locked = locked;
|
||||
history);
|
||||
if (data->restriction != restriction) {
|
||||
data->restriction = restriction;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
for (const auto &data : d_byUsernameFiltered) {
|
||||
const auto history = data->history;
|
||||
const auto locked = (Api::ResolveRequiresPremiumToWrite(
|
||||
const auto restriction = Api::ResolveMessageMoneyRestrictions(
|
||||
history->peer,
|
||||
history
|
||||
) == Api::RequirePremiumState::Yes);
|
||||
if (data->locked != locked) {
|
||||
data->locked = locked;
|
||||
history);
|
||||
if (data->restriction != restriction) {
|
||||
data->restriction = restriction;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
@ -891,14 +985,14 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
|
|||
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void ShareBox::Inner::initChatLocked(not_null<Chat*> chat) {
|
||||
if (_descriptor.premiumRequiredError) {
|
||||
void ShareBox::Inner::initChatRestriction(not_null<Chat*> chat) {
|
||||
if (_descriptor.moneyRestrictionError) {
|
||||
const auto history = chat->history;
|
||||
if (Api::ResolveRequiresPremiumToWrite(
|
||||
const auto restriction = Api::ResolveMessageMoneyRestrictions(
|
||||
history->peer,
|
||||
history
|
||||
) == Api::RequirePremiumState::Yes) {
|
||||
chat->locked = true;
|
||||
history);
|
||||
if (restriction || restriction.known) {
|
||||
chat->restriction = restriction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1024,14 +1118,15 @@ void ShareBox::Inner::loadProfilePhotos() {
|
|||
void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
|
||||
entry->chatListPreloadData();
|
||||
const auto history = entry->asHistory();
|
||||
if (!_descriptor.premiumRequiredError || !history) {
|
||||
if (!_descriptor.moneyRestrictionError || !history) {
|
||||
return;
|
||||
} else if (Api::ResolveRequiresPremiumToWrite(
|
||||
history->peer,
|
||||
history
|
||||
) == Api::RequirePremiumState::Unknown) {
|
||||
const auto user = history->peer->asUser();
|
||||
_descriptor.session->api().premium().resolvePremiumRequired(user);
|
||||
} else if (!Api::ResolveMessageMoneyRestrictions(
|
||||
history->peer,
|
||||
history).known) {
|
||||
if (const auto user = history->peer->asUser()) {
|
||||
const auto api = &_descriptor.session->api();
|
||||
api->premium().resolveMessageMoneyRestrictions(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1054,7 +1149,7 @@ auto ShareBox::Inner::getChat(not_null<Dialogs::Row*> row)
|
|||
repaintChat(peer);
|
||||
}));
|
||||
updateChatName(i->second.get());
|
||||
initChatLocked(i->second.get());
|
||||
initChatRestriction(i->second.get());
|
||||
row->attached = i->second.get();
|
||||
return i->second.get();
|
||||
}
|
||||
|
@ -1088,10 +1183,12 @@ void ShareBox::Inner::paintChat(
|
|||
auto photoTop = st::sharePhotoTop;
|
||||
chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
|
||||
|
||||
if (chat->locked) {
|
||||
PaintPremiumRequiredLock(
|
||||
if (chat->restriction) {
|
||||
PaintRestrictionBadge(
|
||||
p,
|
||||
&_st.item,
|
||||
chat->restriction.starsPerMessage,
|
||||
chat->badgeCache,
|
||||
x + photoLeft,
|
||||
y + photoTop,
|
||||
outerWidth,
|
||||
|
@ -1446,7 +1543,7 @@ void ShareBox::Inner::peopleReceived(
|
|||
_st.item,
|
||||
[=] { repaintChat(peer); }));
|
||||
updateChatName(d_byUsernameFiltered.back().get());
|
||||
initChatLocked(d_byUsernameFiltered.back().get());
|
||||
initChatRestriction(d_byUsernameFiltered.back().get());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1499,6 +1596,15 @@ ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
|
|||
};
|
||||
}
|
||||
|
||||
ShareBox::CountMessagesCallback ShareBox::DefaultForwardCountMessages(
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds) {
|
||||
return [=](const TextWithTags &comment) {
|
||||
const auto items = history->owner().idsToItems(msgIds);
|
||||
return int(items.size()) + (comment.empty() ? 0 : 1);
|
||||
};
|
||||
}
|
||||
|
||||
ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<History*> history,
|
||||
|
@ -1510,12 +1616,14 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
const auto state = std::make_shared<State>();
|
||||
return [=](
|
||||
std::vector<not_null<Data::Thread*>> &&result,
|
||||
TextWithTags &&comment,
|
||||
Fn<bool()> checkPaid,
|
||||
TextWithTags comment,
|
||||
Api::SendOptions options,
|
||||
Data::ForwardOptions forwardOptions) {
|
||||
if (!state->requests.empty()) {
|
||||
return; // Share clicked already.
|
||||
}
|
||||
|
||||
const auto items = history->owner().idsToItems(msgIds);
|
||||
const auto existingIds = history->owner().itemsToIds(items);
|
||||
if (existingIds.empty() || result.empty()) {
|
||||
|
@ -1528,6 +1636,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
if (error.error) {
|
||||
show->showBox(MakeSendErrorBox(error, result.size() > 1));
|
||||
return;
|
||||
} else if (!checkPaid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
using Flag = MTPmessages_ForwardMessages::Flag;
|
||||
|
@ -1576,6 +1686,12 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
: topicRootId;
|
||||
const auto peer = thread->peer();
|
||||
const auto threadHistory = thread->owningHistory();
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
options.starsApproved);
|
||||
if (starsPaid) {
|
||||
options.starsApproved -= starsPaid;
|
||||
}
|
||||
histories.sendRequest(threadHistory, requestType, [=](
|
||||
Fn<void()> finish) {
|
||||
const auto session = &threadHistory->session();
|
||||
|
@ -1587,7 +1703,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
: Flag(0))
|
||||
| (options.shortcutId
|
||||
? Flag::f_quick_reply_shortcut
|
||||
: Flag(0));
|
||||
: Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag());
|
||||
threadHistory->sendRequestId = api.request(
|
||||
MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
|
@ -1599,7 +1716,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
MTP_int(options.scheduled),
|
||||
MTP_inputPeerEmpty(), // send_as
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId),
|
||||
MTP_int(videoTimestamp.value_or(0))
|
||||
MTP_int(videoTimestamp.value_or(0)),
|
||||
MTP_long(starsPaid)
|
||||
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
|
||||
threadHistory->session().api().applyUpdates(updates);
|
||||
state->requests.remove(reqId);
|
||||
|
@ -1621,7 +1739,11 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
|
||||
finish();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"VOICE_MESSAGES_FORBIDDEN"_q) {
|
||||
const auto type = error.type();
|
||||
if (type.startsWith(u"ALLOW_PAYMENT_REQUIRED_"_q)) {
|
||||
show->showToast(u"Payment requirements changed. "
|
||||
"Please, try again."_q);
|
||||
} else if (type == u"VOICE_MESSAGES_FORBIDDEN"_q) {
|
||||
show->showToast(
|
||||
tr::lng_restricted_send_voice_messages(
|
||||
tr::now,
|
||||
|
@ -1660,6 +1782,7 @@ ShareBoxStyleOverrides DarkShareBoxStyle() {
|
|||
.comment = &st::groupCallShareBoxComment,
|
||||
.peerList = &st::groupCallShareBoxList,
|
||||
.label = &st::groupCallField,
|
||||
.checkbox = &st::groupCallCheckbox,
|
||||
.scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()),
|
||||
};
|
||||
}
|
||||
|
@ -1716,7 +1839,7 @@ void FastShareMessage(
|
|||
const auto requiresInline = item->requiresSendInlineRight();
|
||||
auto filterCallback = [=](not_null<Data::Thread*> thread) {
|
||||
if (const auto user = thread->peer()->asUser()) {
|
||||
if (user->canSendIgnoreRequirePremium()) {
|
||||
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1731,6 +1854,9 @@ void FastShareMessage(
|
|||
show->show(Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = session,
|
||||
.copyCallback = std::move(copyLinkCallback),
|
||||
.countMessagesCallback = ShareBox::DefaultForwardCountMessages(
|
||||
history,
|
||||
msgIds),
|
||||
.submitCallback = ShareBox::DefaultForwardCallback(
|
||||
show,
|
||||
history,
|
||||
|
@ -1742,7 +1868,7 @@ void FastShareMessage(
|
|||
.captionsCount = ItemsForwardCaptionsCount(items),
|
||||
.show = !hasOnlyForcedForwardedInfo,
|
||||
},
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||
}), Ui::LayerOption::CloseOther);
|
||||
}
|
||||
|
||||
|
@ -1770,8 +1896,12 @@ void FastShareLink(
|
|||
QGuiApplication::clipboard()->setText(url);
|
||||
show->showToast(tr::lng_background_link_copied(tr::now));
|
||||
};
|
||||
auto countMessagesCallback = [=](const TextWithTags &comment) {
|
||||
return 1;
|
||||
};
|
||||
auto submitCallback = [=](
|
||||
std::vector<not_null<::Data::Thread*>> &&result,
|
||||
Fn<bool()> checkPaid,
|
||||
TextWithTags &&comment,
|
||||
Api::SendOptions options,
|
||||
::Data::ForwardOptions) {
|
||||
|
@ -1788,6 +1918,8 @@ void FastShareLink(
|
|||
MakeSendErrorBox(error, result.size() > 1));
|
||||
}
|
||||
return;
|
||||
} else if (!checkPaid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
*sending = true;
|
||||
|
@ -1815,7 +1947,7 @@ void FastShareLink(
|
|||
};
|
||||
auto filterCallback = [](not_null<::Data::Thread*> thread) {
|
||||
if (const auto user = thread->peer()->asUser()) {
|
||||
if (user->canSendIgnoreRequirePremium()) {
|
||||
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1825,16 +1957,17 @@ void FastShareLink(
|
|||
Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = &show->session(),
|
||||
.copyCallback = std::move(copyCallback),
|
||||
.countMessagesCallback = std::move(countMessagesCallback),
|
||||
.submitCallback = std::move(submitCallback),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.st = st,
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||
}),
|
||||
Ui::LayerOption::KeepOther,
|
||||
anim::type::normal);
|
||||
}
|
||||
|
||||
auto SharePremiumRequiredError()
|
||||
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
|
||||
return WritePremiumRequiredError;
|
||||
auto ShareMessageMoneyRestrictionError()
|
||||
-> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> {
|
||||
return WriteMoneyRestrictionError;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ struct ShareBoxStyleOverrides {
|
|||
const style::InputField *comment = nullptr;
|
||||
const style::PeerList *peerList = nullptr;
|
||||
const style::InputField *label = nullptr;
|
||||
const style::Checkbox *checkbox = nullptr;
|
||||
std::shared_ptr<HistoryView::ScheduleBoxStyleArgs> scheduleBox;
|
||||
};
|
||||
[[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle();
|
||||
|
@ -87,20 +88,25 @@ void FastShareLink(
|
|||
const QString &url,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
|
||||
struct RecipientPremiumRequiredError;
|
||||
[[nodiscard]] auto SharePremiumRequiredError()
|
||||
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)>;
|
||||
struct RecipientMoneyRestrictionError;
|
||||
[[nodiscard]] auto ShareMessageMoneyRestrictionError()
|
||||
-> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)>;
|
||||
|
||||
class ShareBox final : public Ui::BoxContent {
|
||||
public:
|
||||
using CopyCallback = Fn<void()>;
|
||||
using CountMessagesCallback = Fn<int(const TextWithTags&)>;
|
||||
using SubmitCallback = Fn<void(
|
||||
std::vector<not_null<Data::Thread*>>&&,
|
||||
Fn<bool()> checkPaid,
|
||||
TextWithTags&&,
|
||||
Api::SendOptions,
|
||||
Data::ForwardOptions)>;
|
||||
using FilterCallback = Fn<bool(not_null<Data::Thread*>)>;
|
||||
|
||||
[[nodiscard]] static auto DefaultForwardCountMessages(
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds) -> CountMessagesCallback;
|
||||
[[nodiscard]] static SubmitCallback DefaultForwardCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<History*> history,
|
||||
|
@ -110,6 +116,7 @@ public:
|
|||
struct Descriptor {
|
||||
not_null<Main::Session*> session;
|
||||
CopyCallback copyCallback;
|
||||
CountMessagesCallback countMessagesCallback;
|
||||
SubmitCallback submitCallback;
|
||||
FilterCallback filterCallback;
|
||||
object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
|
||||
|
@ -123,8 +130,9 @@ public:
|
|||
bool show = false;
|
||||
} forwardOptions;
|
||||
|
||||
using PremiumRequiredError = RecipientPremiumRequiredError;
|
||||
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
|
||||
using MoneyRestrictionError = RecipientMoneyRestrictionError;
|
||||
Fn<MoneyRestrictionError(
|
||||
not_null<UserData*>)> moneyRestrictionError;
|
||||
};
|
||||
ShareBox(QWidget*, Descriptor &&descriptor);
|
||||
|
||||
|
@ -149,6 +157,7 @@ private:
|
|||
void needSearchByUsername();
|
||||
void applyFilterUpdate(const QString &query);
|
||||
void selectedChanged();
|
||||
void computeStarsCount();
|
||||
void createButtons();
|
||||
int getTopScrollSkip() const;
|
||||
int getBottomScrollSkip() const;
|
||||
|
@ -180,6 +189,7 @@ private:
|
|||
|
||||
bool _hasSelected = false;
|
||||
rpl::variable<QString> _copyLinkText;
|
||||
rpl::variable<int> _starsToSend;
|
||||
|
||||
base::Timer _searchTimer;
|
||||
QString _peopleQuery;
|
||||
|
@ -195,5 +205,6 @@ private:
|
|||
PeopleQueries _peopleQueries;
|
||||
|
||||
Ui::Animations::Simple _scrollAnimation;
|
||||
rpl::lifetime _submitLifetime;
|
||||
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_document.h"
|
||||
|
@ -80,6 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
@ -106,6 +108,7 @@ constexpr auto kSentToastDuration = 3 * crl::time(1000);
|
|||
constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
|
||||
constexpr auto kCrossfadeDuration = crl::time(400);
|
||||
constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000);
|
||||
constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000);
|
||||
|
||||
using namespace HistoryView;
|
||||
using namespace Info::PeerGifts;
|
||||
|
@ -126,6 +129,7 @@ struct GiftDetails {
|
|||
uint64 randomId = 0;
|
||||
bool anonymous = false;
|
||||
bool upgraded = false;
|
||||
bool byStars = false;
|
||||
};
|
||||
|
||||
class PreviewDelegate final : public DefaultElementDelegate {
|
||||
|
@ -227,7 +231,7 @@ auto GenerateGiftMedia(
|
|||
TextWithEntities text,
|
||||
QMargins margins = {},
|
||||
const base::flat_map<uint16, ClickHandlerPtr> &links = {},
|
||||
const std::any &context = {}) {
|
||||
Ui::Text::MarkedContext context = {}) {
|
||||
if (text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -236,7 +240,7 @@ auto GenerateGiftMedia(
|
|||
margins,
|
||||
st::defaultTextStyle,
|
||||
links,
|
||||
context));
|
||||
std::move(context)));
|
||||
};
|
||||
|
||||
const auto sticker = [=] {
|
||||
|
@ -306,10 +310,10 @@ auto GenerateGiftMedia(
|
|||
auto description = data.text.empty()
|
||||
? std::move(textFallback)
|
||||
: data.text;
|
||||
const auto context = Core::MarkedTextContext{
|
||||
const auto context = Core::TextContext({
|
||||
.session = &parent->history()->session(),
|
||||
.customEmojiRepaint = [parent] { parent->repaint(); },
|
||||
};
|
||||
.repaint = [parent] { parent->repaint(); },
|
||||
});
|
||||
pushText(
|
||||
std::move(title),
|
||||
st::giftBoxPreviewTitlePadding,
|
||||
|
@ -495,7 +499,14 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
|
|||
std::move(details) | rpl::start_with_next([=](GiftDetails details) {
|
||||
const auto &descriptor = details.descriptor;
|
||||
const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
|
||||
return FillAmountAndCurrency(data.cost, data.currency, true);
|
||||
const auto stars = (details.byStars && data.stars)
|
||||
? data.stars
|
||||
: (data.currency == kCreditsCurrency)
|
||||
? data.cost
|
||||
: 0;
|
||||
return stars
|
||||
? tr::lng_gift_stars_title(tr::now, lt_count, stars)
|
||||
: FillAmountAndCurrency(data.cost, data.currency, true);
|
||||
}, [&](GiftTypeStars data) {
|
||||
const auto stars = data.info.stars
|
||||
+ (details.upgraded ? data.info.starsToUpgrade : 0);
|
||||
|
@ -622,14 +633,27 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
|||
list.reserve(options.size());
|
||||
auto minMonthsGift = GiftTypePremium();
|
||||
for (const auto &option : options) {
|
||||
list.push_back({
|
||||
.cost = option.cost,
|
||||
.currency = option.currency,
|
||||
.months = option.months,
|
||||
});
|
||||
if (!minMonthsGift.months
|
||||
|| option.months < minMonthsGift.months) {
|
||||
minMonthsGift = list.back();
|
||||
if (option.currency != kCreditsCurrency) {
|
||||
list.push_back({
|
||||
.cost = option.cost,
|
||||
.currency = option.currency,
|
||||
.months = option.months,
|
||||
});
|
||||
if (!minMonthsGift.months
|
||||
|| option.months < minMonthsGift.months) {
|
||||
minMonthsGift = list.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &option : options) {
|
||||
if (option.currency == kCreditsCurrency) {
|
||||
const auto i = ranges::find(
|
||||
list,
|
||||
option.months,
|
||||
&GiftTypePremium::months);
|
||||
if (i != end(list)) {
|
||||
i->stars = option.cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &gift : list) {
|
||||
|
@ -735,15 +759,11 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
auto &manager = session->data().customEmojiManager();
|
||||
auto result = Text::String();
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [] {},
|
||||
};
|
||||
result.setMarkedText(
|
||||
st::semiboldTextStyle,
|
||||
manager.creditsEmoji().append(QString::number(price)),
|
||||
kMarkupTextOptions,
|
||||
context);
|
||||
Core::TextContext({ .session = session }));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1103,16 +1123,35 @@ void SendGift(
|
|||
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
|
||||
const GiftDetails &details,
|
||||
Fn<void(Payments::CheckoutResult)> done) {
|
||||
const auto processNonPanelPaymentFormFactory
|
||||
= Payments::ProcessNonPanelPaymentFormFactory(window, done);
|
||||
v::match(details.descriptor, [&](const GiftTypePremium &gift) {
|
||||
auto invoice = api->invoice(1, gift.months);
|
||||
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{
|
||||
.users = { peer->asUser() },
|
||||
.message = details.text,
|
||||
};
|
||||
Payments::CheckoutProcess::Start(std::move(invoice), done);
|
||||
if (details.byStars && gift.stars) {
|
||||
auto invoice = Payments::InvoicePremiumGiftCode{
|
||||
.purpose = Payments::InvoicePremiumGiftCodeUsers{
|
||||
.users = { peer->asUser() },
|
||||
.message = details.text,
|
||||
},
|
||||
.currency = Ui::kCreditsCurrency,
|
||||
.randomId = details.randomId,
|
||||
.amount = uint64(gift.stars),
|
||||
.storeQuantity = 1,
|
||||
.users = 1,
|
||||
.months = gift.months,
|
||||
};
|
||||
Payments::CheckoutProcess::Start(
|
||||
std::move(invoice),
|
||||
done,
|
||||
processNonPanelPaymentFormFactory);
|
||||
} else {
|
||||
auto invoice = api->invoice(1, gift.months);
|
||||
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{
|
||||
.users = { peer->asUser() },
|
||||
.message = details.text,
|
||||
};
|
||||
Payments::CheckoutProcess::Start(std::move(invoice), done);
|
||||
}
|
||||
}, [&](const GiftTypeStars &gift) {
|
||||
const auto processNonPanelPaymentFormFactory
|
||||
= Payments::ProcessNonPanelPaymentFormFactory(window, done);
|
||||
Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{
|
||||
.giftId = gift.info.id,
|
||||
.randomId = details.randomId,
|
||||
|
@ -1279,12 +1318,6 @@ void AddUpgradeButton(
|
|||
button->toggleOn(rpl::single(false))->toggledValue(
|
||||
) | rpl::start_with_next(toggled, button->lifetime());
|
||||
|
||||
const auto makeContext = [session](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(update),
|
||||
};
|
||||
};
|
||||
auto star = session->data().customEmojiManager().creditsEmoji();
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
|
@ -1296,7 +1329,7 @@ void AddUpgradeButton(
|
|||
Text::WithEntities),
|
||||
st::boxLabel,
|
||||
st::defaultPopupMenu,
|
||||
std::move(makeContext));
|
||||
Core::TextContext({ .session = session }));
|
||||
label->show();
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
button->widthValue() | rpl::start_with_next([=](int outer) {
|
||||
|
@ -1424,6 +1457,7 @@ void SendGiftBox(
|
|||
|
||||
struct State {
|
||||
rpl::variable<GiftDetails> details;
|
||||
rpl::variable<bool> messageAllowed;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
bool submitting = false;
|
||||
};
|
||||
|
@ -1432,13 +1466,25 @@ void SendGiftBox(
|
|||
.descriptor = descriptor,
|
||||
.randomId = base::RandomValue<uint64>(),
|
||||
};
|
||||
peer->updateFull();
|
||||
state->messageAllowed = peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::StarsPerMessage
|
||||
) | rpl::map([=] {
|
||||
return peer->starsPerMessageChecked() == 0;
|
||||
});
|
||||
|
||||
auto cost = state->details.value(
|
||||
) | rpl::map([session](const GiftDetails &details) {
|
||||
return v::match(details.descriptor, [&](const GiftTypePremium &data) {
|
||||
if (data.currency == kCreditsCurrency) {
|
||||
const auto stars = (details.byStars && data.stars)
|
||||
? data.stars
|
||||
: (data.currency == kCreditsCurrency)
|
||||
? data.cost
|
||||
: 0;
|
||||
if (stars) {
|
||||
return CreditsEmojiSmall(session).append(
|
||||
Lang::FormatCountDecimal(std::abs(data.cost)));
|
||||
Lang::FormatCountDecimal(std::abs(stars)));
|
||||
}
|
||||
return TextWithEntities{
|
||||
FillAmountAndCurrency(data.cost, data.currency),
|
||||
|
@ -1462,10 +1508,17 @@ void SendGiftBox(
|
|||
peer,
|
||||
state->details.value()));
|
||||
|
||||
const auto messageWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
messageWrap->toggleOn(state->messageAllowed.value());
|
||||
messageWrap->finishAnimating();
|
||||
const auto messageInner = messageWrap->entity();
|
||||
const auto limit = StarGiftMessageLimit(session);
|
||||
const auto text = AddPartInput(
|
||||
window,
|
||||
container,
|
||||
messageInner,
|
||||
box->getDelegate()->outerContainer(),
|
||||
tr::lng_gift_send_message(),
|
||||
QString(),
|
||||
|
@ -1509,7 +1562,6 @@ void SendGiftBox(
|
|||
text,
|
||||
session,
|
||||
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
|
||||
|
||||
if (stars) {
|
||||
const auto cost = stars->info.starsToUpgrade;
|
||||
if (cost > 0 && !peer->isSelf()) {
|
||||
|
@ -1551,20 +1603,73 @@ void SendGiftBox(
|
|||
}, container->lifetime());
|
||||
AddSkip(container);
|
||||
}
|
||||
v::match(descriptor, [&](const GiftTypePremium &) {
|
||||
AddDividerText(container, tr::lng_gift_send_premium_about(
|
||||
v::match(descriptor, [&](const GiftTypePremium &data) {
|
||||
AddDividerText(messageInner, tr::lng_gift_send_premium_about(
|
||||
lt_user,
|
||||
rpl::single(peer->shortName())));
|
||||
|
||||
if (const auto byStars = data.stars) {
|
||||
const auto star = Ui::Text::IconEmoji(&st::starIconEmojiColored);
|
||||
AddSkip(container);
|
||||
container->add(
|
||||
object_ptr<SettingsButton>(
|
||||
container,
|
||||
tr::lng_gift_send_pay_with_stars(
|
||||
lt_amount,
|
||||
rpl::single(base::duplicate(star).append(Lang::FormatCountDecimal(byStars))),
|
||||
Ui::Text::WithEntities),
|
||||
st::settingsButtonNoIcon)
|
||||
)->toggleOn(rpl::single(false))->toggledValue(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
auto now = state->details.current();
|
||||
now.byStars = toggled;
|
||||
state->details = std::move(now);
|
||||
}, container->lifetime());
|
||||
AddSkip(container);
|
||||
|
||||
const auto balance = AddDividerText(
|
||||
container,
|
||||
tr::lng_gift_send_stars_balance(
|
||||
lt_amount,
|
||||
peer->session().credits().balanceValue(
|
||||
) | rpl::map([=](StarsAmount amount) {
|
||||
return base::duplicate(star).append(
|
||||
Lang::FormatStarsAmountDecimal(amount));
|
||||
}),
|
||||
lt_link,
|
||||
tr::lng_gift_send_stars_balance_link(
|
||||
) | Ui::Text::ToLink(),
|
||||
Ui::Text::WithEntities));
|
||||
struct State {
|
||||
Settings::BuyStarsHandler buyStars;
|
||||
rpl::variable<bool> loading;
|
||||
};
|
||||
const auto state = balance->lifetime().make_state<State>();
|
||||
state->loading = state->buyStars.loadingValue();
|
||||
balance->setClickHandlerFilter([=](const auto &...) {
|
||||
if (!state->loading.current()) {
|
||||
state->buyStars.handler(window->uiShow())();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}, [&](const GiftTypeStars &) {
|
||||
AddDividerText(container, peer->isSelf()
|
||||
? tr::lng_gift_send_anonymous_self()
|
||||
: peer->isBroadcast()
|
||||
? tr::lng_gift_send_anonymous_about_channel()
|
||||
: tr::lng_gift_send_anonymous_about(
|
||||
lt_user,
|
||||
rpl::single(peer->shortName()),
|
||||
lt_recipient,
|
||||
rpl::single(peer->shortName())));
|
||||
: rpl::conditional(
|
||||
state->messageAllowed.value(),
|
||||
tr::lng_gift_send_anonymous_about(
|
||||
lt_user,
|
||||
rpl::single(peer->shortName()),
|
||||
lt_recipient,
|
||||
rpl::single(peer->shortName())),
|
||||
tr::lng_gift_send_anonymous_about_paid(
|
||||
lt_user,
|
||||
rpl::single(peer->shortName()),
|
||||
lt_recipient,
|
||||
rpl::single(peer->shortName()))));
|
||||
});
|
||||
|
||||
const auto buttonWidth = st::boxWideWidth
|
||||
|
@ -1575,13 +1680,20 @@ void SendGiftBox(
|
|||
return;
|
||||
}
|
||||
state->submitting = true;
|
||||
const auto details = state->details.current();
|
||||
auto details = state->details.current();
|
||||
if (!state->messageAllowed.current()) {
|
||||
details.text = {};
|
||||
}
|
||||
const auto weak = MakeWeak(box);
|
||||
const auto done = [=](Payments::CheckoutResult result) {
|
||||
if (result == Payments::CheckoutResult::Paid) {
|
||||
if (details.byStars
|
||||
|| v::is<GiftTypeStars>(details.descriptor)) {
|
||||
window->session().credits().load(true);
|
||||
}
|
||||
const auto copy = state->media;
|
||||
window->showPeerHistory(peer);
|
||||
ShowSentToast(window, descriptor, details);
|
||||
ShowSentToast(window, details.descriptor, details);
|
||||
}
|
||||
if (const auto strong = weak.data()) {
|
||||
box->closeBox();
|
||||
|
@ -1814,6 +1926,8 @@ void GiftBox(
|
|||
box->setCustomCornersFilling(RectPart::FullTop);
|
||||
box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
|
||||
|
||||
window->session().credits().load();
|
||||
|
||||
FillBg(box);
|
||||
|
||||
const auto &stUser = st::premiumGiftsUserpicButton;
|
||||
|
@ -2070,7 +2184,66 @@ void ChooseStarGiftRecipient(
|
|||
void ShowStarGiftBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer) {
|
||||
controller->show(Box(GiftBox, controller, peer));
|
||||
struct Session {
|
||||
PeerData *peer = nullptr;
|
||||
bool premiumGiftsReady = false;
|
||||
bool starsGiftsReady = false;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
|
||||
|
||||
const auto session = &controller->session();
|
||||
auto i = Map.find(session);
|
||||
if (i == end(Map)) {
|
||||
i = Map.emplace(session).first;
|
||||
session->lifetime().add([=] { Map.remove(session); });
|
||||
} else if (i->second.peer == peer) {
|
||||
return;
|
||||
}
|
||||
i->second = Session{ .peer = peer };
|
||||
|
||||
const auto weak = base::make_weak(controller);
|
||||
const auto show = [=] {
|
||||
Map[session] = Session();
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->show(Box(GiftBox, strong, peer));
|
||||
}
|
||||
};
|
||||
|
||||
base::timer_once(
|
||||
kGiftsPreloadTimeout
|
||||
) | rpl::start_with_next(show, i->second.lifetime);
|
||||
|
||||
const auto user = peer->asUser();
|
||||
if (user && !user->isSelf()) {
|
||||
GiftsPremium(
|
||||
session,
|
||||
peer
|
||||
) | rpl::start_with_next([=](PremiumGiftsDescriptor &&gifts) {
|
||||
if (!gifts.list.empty()) {
|
||||
auto &entry = Map[session];
|
||||
entry.premiumGiftsReady = true;
|
||||
if (entry.starsGiftsReady) {
|
||||
show();
|
||||
}
|
||||
}
|
||||
}, i->second.lifetime);
|
||||
} else {
|
||||
i->second.premiumGiftsReady = true;
|
||||
}
|
||||
|
||||
GiftsStars(
|
||||
session,
|
||||
peer
|
||||
) | rpl::start_with_next([=](std::vector<GiftTypeStars> &&gifts) {
|
||||
if (!gifts.empty()) {
|
||||
auto &entry = Map[session];
|
||||
entry.starsGiftsReady = true;
|
||||
if (entry.premiumGiftsReady) {
|
||||
show();
|
||||
}
|
||||
}
|
||||
}, i->second.lifetime);
|
||||
}
|
||||
|
||||
void AddUniqueGiftCover(
|
||||
|
|
|
@ -150,10 +150,7 @@ void TranslateBox(
|
|||
original->entity()->setAnimationsPausedCallback(animationsPaused);
|
||||
original->entity()->setMarkedText(
|
||||
text,
|
||||
Core::MarkedTextContext{
|
||||
.session = &peer->session(),
|
||||
.customEmojiRepaint = [=] { original->entity()->update(); },
|
||||
});
|
||||
Core::TextContext({ .session = &peer->session() }));
|
||||
original->setMinimalHeight(lineHeight);
|
||||
original->hide(anim::type::instant);
|
||||
|
||||
|
@ -221,10 +218,7 @@ void TranslateBox(
|
|||
const auto label = translated->entity();
|
||||
label->setMarkedText(
|
||||
text,
|
||||
Core::MarkedTextContext{
|
||||
.session = &peer->session(),
|
||||
.customEmojiRepaint = [=] { label->update(); },
|
||||
});
|
||||
Core::TextContext({ .session = &peer->session() }));
|
||||
translated->show(anim::type::instant);
|
||||
loading->hide(anim::type::instant);
|
||||
};
|
||||
|
|
|
@ -132,8 +132,12 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
|||
QGuiApplication::clipboard()->setText(currentLink());
|
||||
show->showToast(tr::lng_group_invite_copied(tr::now));
|
||||
};
|
||||
auto countMessagesCallback = [=](const TextWithTags &comment) {
|
||||
return 1;
|
||||
};
|
||||
auto submitCallback = [=](
|
||||
std::vector<not_null<Data::Thread*>> &&result,
|
||||
Fn<bool()> checkPaid,
|
||||
TextWithTags &&comment,
|
||||
Api::SendOptions options,
|
||||
Data::ForwardOptions) {
|
||||
|
@ -150,6 +154,8 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
|||
MakeSendErrorBox(error, result.size() > 1));
|
||||
}
|
||||
return;
|
||||
} else if (!checkPaid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
*sending = true;
|
||||
|
@ -178,7 +184,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
|||
};
|
||||
auto filterCallback = [](not_null<Data::Thread*> thread) {
|
||||
if (const auto user = thread->peer()->asUser()) {
|
||||
if (user->canSendIgnoreRequirePremium()) {
|
||||
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +195,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
|||
auto result = Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = &peer->session(),
|
||||
.copyCallback = std::move(copyCallback),
|
||||
.countMessagesCallback = std::move(countMessagesCallback),
|
||||
.submitCallback = std::move(submitCallback),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.bottomWidget = std::move(bottom),
|
||||
|
@ -199,7 +206,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
|||
tr::lng_group_call_copy_speaker_link(),
|
||||
tr::lng_group_call_copy_listener_link()),
|
||||
.st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(),
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||
});
|
||||
*box = result.data();
|
||||
return result;
|
||||
|
|
|
@ -149,6 +149,7 @@ EmojiButton {
|
|||
|
||||
SendButton {
|
||||
inner: IconButton;
|
||||
stars: RoundButton;
|
||||
record: icon;
|
||||
recordOver: icon;
|
||||
round: icon;
|
||||
|
@ -855,6 +856,10 @@ historyComposeButton: FlatButton {
|
|||
color: historyComposeButtonBgRipple;
|
||||
}
|
||||
}
|
||||
historyComposeButtonText: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
textFg: windowActiveTextFg;
|
||||
}
|
||||
historyGiftToChannel: IconButton(defaultIconButton) {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
|
@ -913,6 +918,10 @@ historyBusinessBotSettings: IconButton(defaultIconButton) {
|
|||
height: 58px;
|
||||
width: 48px;
|
||||
}
|
||||
paysStatusLabel: FlatLabel(historyBusinessBotStatus) {
|
||||
align: align(top);
|
||||
minWidth: 240px;
|
||||
}
|
||||
|
||||
historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }};
|
||||
historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }};
|
||||
|
@ -1289,6 +1298,12 @@ historySend: SendButton {
|
|||
icon: historySendIcon;
|
||||
iconOver: historySendIconOver;
|
||||
}
|
||||
stars: RoundButton(defaultActiveButton) {
|
||||
height: 28px;
|
||||
padding: margins(0px, 0px, 6px, 0px);
|
||||
textTop: 5px;
|
||||
width: -8px;
|
||||
}
|
||||
record: historyRecordVoice;
|
||||
recordOver: historyRecordVoiceOver;
|
||||
round: historyRecordRound;
|
||||
|
|
|
@ -1747,7 +1747,8 @@ void InitFieldAutocomplete(
|
|||
&& peer->isUser()
|
||||
&& !peer->asUser()->isBot()
|
||||
&& (!shortcutMessages
|
||||
|| shortcutMessages->shortcuts().list.empty())) {
|
||||
|| shortcutMessages->shortcuts().list.empty()
|
||||
|| peer->starsPerMessageChecked() != 0)) {
|
||||
parsed = {};
|
||||
}
|
||||
raw->showFiltered(peer, parsed.query, parsed.fromStart);
|
||||
|
|
|
@ -695,14 +695,15 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(
|
|||
}
|
||||
|
||||
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(
|
||||
not_null<InlineResult*> result) {
|
||||
auto it = _inlineLayouts.find(result);
|
||||
std::shared_ptr<InlineResult> result) {
|
||||
const auto raw = result.get();
|
||||
auto it = _inlineLayouts.find(raw);
|
||||
if (it == _inlineLayouts.cend()) {
|
||||
if (auto layout = LayoutItem::createLayout(
|
||||
this,
|
||||
result,
|
||||
std::move(result),
|
||||
_inlineWithThumb)) {
|
||||
it = _inlineLayouts.emplace(result, std::move(layout)).first;
|
||||
it = _inlineLayouts.emplace(raw, std::move(layout)).first;
|
||||
it->second->initDimensions();
|
||||
} else {
|
||||
return nullptr;
|
||||
|
@ -775,8 +776,8 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result
|
|||
from,
|
||||
count
|
||||
) | ranges::views::transform([&](
|
||||
const std::unique_ptr<InlineBots::Result> &r) {
|
||||
return layoutPrepareInlineResult(r.get());
|
||||
const std::shared_ptr<InlineBots::Result> &r) {
|
||||
return layoutPrepareInlineResult(r);
|
||||
}) | ranges::views::filter([](const LayoutItem *item) {
|
||||
return item != nullptr;
|
||||
}) | ranges::to<std::vector<not_null<LayoutItem*>>>;
|
||||
|
@ -799,7 +800,7 @@ int GifsListWidget::validateExistingInlineRows(const InlineResults &results) {
|
|||
const auto until = _mosaic.validateExistingRows([&](
|
||||
not_null<const LayoutItem*> item,
|
||||
int untilIndex) {
|
||||
return item->getResult() != results[untilIndex].get();
|
||||
return item->getResult().get() != results[untilIndex].get();
|
||||
}, results.size());
|
||||
|
||||
if (_mosaic.empty()) {
|
||||
|
|
|
@ -131,7 +131,7 @@ private:
|
|||
};
|
||||
|
||||
using InlineResult = InlineBots::Result;
|
||||
using InlineResults = std::vector<std::unique_ptr<InlineResult>>;
|
||||
using InlineResults = std::vector<std::shared_ptr<InlineResult>>;
|
||||
using LayoutItem = InlineBots::Layout::ItemBase;
|
||||
|
||||
struct InlineCacheEntry {
|
||||
|
@ -162,7 +162,8 @@ private:
|
|||
|
||||
void clearInlineRows(bool resultsDeleted);
|
||||
LayoutItem *layoutPrepareSavedGif(not_null<DocumentData*> document);
|
||||
LayoutItem *layoutPrepareInlineResult(not_null<InlineResult*> result);
|
||||
LayoutItem *layoutPrepareInlineResult(
|
||||
std::shared_ptr<InlineResult> result);
|
||||
|
||||
void deleteUnusedGifLayouts();
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
|
@ -432,12 +433,9 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
|
|||
const auto session = args.session;
|
||||
field->setTagMimeProcessor(
|
||||
FieldTagMimeProcessor(session, args.allowPremiumEmoji));
|
||||
field->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
});
|
||||
}, [paused] {
|
||||
field->setCustomTextContext(Core::TextContext({
|
||||
.session = session
|
||||
}), [paused] {
|
||||
return On(PowerSaving::kEmojiChat) || paused();
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kChatSpoiler) || paused();
|
||||
|
@ -1280,3 +1278,26 @@ void SelectTextInFieldWithMargins(
|
|||
textCursor.setPosition(selection.to, QTextCursor::KeepAnchor);
|
||||
field->setTextCursor(textCursor);
|
||||
}
|
||||
|
||||
TextWithEntities PaidSendButtonText(tr::now_t, int stars) {
|
||||
return Ui::Text::IconEmoji(&st::starIconEmoji).append(
|
||||
Lang::FormatCountToShort(stars).string);
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> PaidSendButtonText(
|
||||
rpl::producer<int> stars,
|
||||
rpl::producer<QString> fallback) {
|
||||
if (fallback) {
|
||||
return rpl::combine(
|
||||
std::move(fallback),
|
||||
std::move(stars)
|
||||
) | rpl::map([=](QString zero, int count) {
|
||||
return count
|
||||
? PaidSendButtonText(tr::now, count)
|
||||
: TextWithEntities{ zero };
|
||||
});
|
||||
}
|
||||
return std::move(stars) | rpl::map([=](int count) {
|
||||
return PaidSendButtonText(tr::now, count);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace tr {
|
||||
struct now_t;
|
||||
} // namespace tr
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
class SessionShow;
|
||||
|
@ -169,3 +173,8 @@ private:
|
|||
void SelectTextInFieldWithMargins(
|
||||
not_null<Ui::InputField*> field,
|
||||
const TextSelection &selection);
|
||||
|
||||
[[nodiscard]] TextWithEntities PaidSendButtonText(tr::now_t, int stars);
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> PaidSendButtonText(
|
||||
rpl::producer<int> stars,
|
||||
rpl::producer<QString> fallback = nullptr);
|
||||
|
|
|
@ -37,7 +37,7 @@ std::map<int, const char*> BetaLogs() {
|
|||
"- Nice looking code blocks with syntax highlight.\n"
|
||||
|
||||
"- Copy full code block by click on its header.\n"
|
||||
|
||||
|
||||
"- Send a highlighted code block using ```language syntax.\n"
|
||||
}
|
||||
};
|
||||
|
|
|
@ -205,13 +205,13 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
|
|||
});
|
||||
};
|
||||
if (_bot->isVerified()
|
||||
|| _bot->session().local().isBotTrustedOpenGame(_bot->id)) {
|
||||
|| _bot->session().local().isPeerTrustedOpenGame(_bot->id)) {
|
||||
openGame();
|
||||
} else {
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
const auto callback = [=, bot = _bot](Fn<void()> close) {
|
||||
close();
|
||||
bot->session().local().markBotTrustedOpenGame(bot->id);
|
||||
bot->session().local().markPeerTrustedOpenGame(bot->id);
|
||||
openGame();
|
||||
};
|
||||
controller->show(Ui::MakeConfirmBox({
|
||||
|
|
|
@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/boxes/edit_birthday_box.h"
|
||||
#include "ui/integration.h"
|
||||
#include "payments/payments_non_panel_process.h"
|
||||
#include "boxes/peers/edit_peer_info_box.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/connection_box.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
|
@ -68,6 +69,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_domain.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
|
@ -1018,6 +1021,45 @@ bool CopyUsername(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool EditPaidMessagesFee(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto peerId = PeerId(match->captured(1).toULongLong());
|
||||
if (const auto id = peerToChannel(peerId)) {
|
||||
const auto channel = controller->session().data().channelLoaded(id);
|
||||
if (channel && channel->canEditPermissions()) {
|
||||
ShowEditChatPermissions(controller, channel);
|
||||
}
|
||||
} else {
|
||||
controller->show(Box(EditMessagesPrivacyBox, controller));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowCommonGroups(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto peerId = PeerId(match->captured(1).toULongLong());
|
||||
if (const auto id = peerToUser(peerId)) {
|
||||
const auto user = controller->session().data().userLoaded(id);
|
||||
if (user) {
|
||||
controller->showSection(
|
||||
std::make_shared<Info::Memento>(
|
||||
user,
|
||||
Info::Section::Type::CommonGroups));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowStarsExamples(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
|
@ -1529,6 +1571,14 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
|||
u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
||||
CopyUsername,
|
||||
},
|
||||
{
|
||||
u"^edit_paid_messages_fee/([0-9]+)$"_q,
|
||||
EditPaidMessagesFee,
|
||||
},
|
||||
{
|
||||
u"^common_groups/([0-9]+)$"_q,
|
||||
ShowCommonGroups,
|
||||
},
|
||||
{
|
||||
u"^stars_examples$"_q,
|
||||
ShowStarsExamples,
|
||||
|
|
|
@ -76,6 +76,14 @@ const auto CommandByName = base::flat_map<QString, Command>{
|
|||
{ u"first_chat"_q , Command::ChatFirst },
|
||||
{ u"last_chat"_q , Command::ChatLast },
|
||||
{ u"self_chat"_q , Command::ChatSelf },
|
||||
{ u"pinned_chat1"_q , Command::ChatPinned1 },
|
||||
{ u"pinned_chat2"_q , Command::ChatPinned2 },
|
||||
{ u"pinned_chat3"_q , Command::ChatPinned3 },
|
||||
{ u"pinned_chat4"_q , Command::ChatPinned4 },
|
||||
{ u"pinned_chat5"_q , Command::ChatPinned5 },
|
||||
{ u"pinned_chat6"_q , Command::ChatPinned6 },
|
||||
{ u"pinned_chat7"_q , Command::ChatPinned7 },
|
||||
{ u"pinned_chat8"_q , Command::ChatPinned8 },
|
||||
|
||||
{ u"previous_folder"_q , Command::FolderPrevious },
|
||||
{ u"next_folder"_q , Command::FolderNext },
|
||||
|
@ -168,6 +176,7 @@ private:
|
|||
void set(const QKeySequence &result, Command command, bool replace);
|
||||
void remove(const QString &keys);
|
||||
void remove(const QKeySequence &keys);
|
||||
void remove(const QKeySequence &keys, Command command);
|
||||
void unregister(base::unique_qptr<QAction> shortcut);
|
||||
|
||||
void pruneListened();
|
||||
|
@ -293,7 +302,7 @@ void Manager::change(
|
|||
Command command,
|
||||
std::optional<Command> restore) {
|
||||
if (!was.isEmpty()) {
|
||||
remove(was);
|
||||
remove(was, command);
|
||||
}
|
||||
if (!now.isEmpty()) {
|
||||
set(now, command, true);
|
||||
|
@ -397,6 +406,7 @@ bool Manager::readCustomFile() {
|
|||
const auto entry = (*i).toObject();
|
||||
const auto keys = entry.constFind(u"keys"_q);
|
||||
const auto command = entry.constFind(u"command"_q);
|
||||
const auto removed = entry.constFind(u"removed"_q);
|
||||
if (keys == entry.constEnd()
|
||||
|| command == entry.constEnd()
|
||||
|| !(*keys).isString()
|
||||
|
@ -410,7 +420,11 @@ bool Manager::readCustomFile() {
|
|||
const auto name = (*command).toString();
|
||||
const auto i = CommandByName.find(name);
|
||||
if (i != end(CommandByName)) {
|
||||
set((*keys).toString(), i->second, true);
|
||||
if (removed != entry.constEnd() && removed->toBool()) {
|
||||
remove((*keys).toString(), i->second);
|
||||
} else {
|
||||
set((*keys).toString(), i->second, true);
|
||||
}
|
||||
} else {
|
||||
LOG(("Shortcut Warning: "
|
||||
"could not find shortcut command handler '%1'"
|
||||
|
@ -565,12 +579,36 @@ void Manager::writeCustomFile() {
|
|||
}
|
||||
}
|
||||
}
|
||||
for (const auto &[sequence, command] : _defaults) {
|
||||
if (!_shortcuts.contains(sequence)) {
|
||||
const auto has = [&](not_null<QObject*> shortcut, Command command) {
|
||||
for (auto i = _commandByObject.findFirst(shortcut)
|
||||
; i != end(_commandByObject) && i->first == shortcut
|
||||
; ++i) {
|
||||
if (i->second == command) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
for (const auto &[sequence, commands] : _defaults) {
|
||||
const auto i = _shortcuts.find(sequence);
|
||||
if (i == end(_shortcuts)) {
|
||||
QJsonObject entry;
|
||||
entry.insert(u"keys"_q, sequence.toString().toLower());
|
||||
entry.insert(u"command"_q, QJsonValue());
|
||||
shortcuts.append(entry);
|
||||
continue;
|
||||
}
|
||||
for (const auto command : commands) {
|
||||
if (!has(i->second.get(), command)) {
|
||||
const auto j = CommandNames().find(command);
|
||||
if (j != CommandNames().end()) {
|
||||
QJsonObject entry;
|
||||
entry.insert(u"keys"_q, sequence.toString().toLower());
|
||||
entry.insert(u"command"_q, j->second);
|
||||
entry.insert(u"removed"_q, true);
|
||||
shortcuts.append(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -669,6 +707,17 @@ void Manager::remove(const QKeySequence &keys) {
|
|||
}
|
||||
}
|
||||
|
||||
void Manager::remove(const QKeySequence &keys, Command command) {
|
||||
const auto i = _shortcuts.find(keys);
|
||||
if (i != end(_shortcuts)) {
|
||||
_commandByObject.remove(i->second.get(), command);
|
||||
if (!_commandByObject.contains(i->second.get())) {
|
||||
unregister(std::move(i->second));
|
||||
_shortcuts.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::unregister(base::unique_qptr<QAction> shortcut) {
|
||||
if (shortcut) {
|
||||
_commandByObject.removeAll(shortcut.get());
|
||||
|
|
|
@ -60,6 +60,11 @@ public:
|
|||
normalize();
|
||||
return *this;
|
||||
}
|
||||
inline StarsAmount operator-() const {
|
||||
auto result = *this;
|
||||
result *= -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
friend inline auto operator<=>(StarsAmount, StarsAmount) = default;
|
||||
friend inline bool operator==(StarsAmount, StarsAmount) = default;
|
||||
|
@ -97,3 +102,7 @@ private:
|
|||
[[nodiscard]] inline StarsAmount operator*(StarsAmount a, int64 b) {
|
||||
return a *= b;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline StarsAmount operator*(int64 a, StarsAmount b) {
|
||||
return b *= a;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "iv/iv_instance.h"
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
@ -112,6 +113,40 @@ const auto kBadPrefix = u"http://"_q;
|
|||
|
||||
} // namespace
|
||||
|
||||
Ui::Text::MarkedContext TextContext(TextContextArgs &&args) {
|
||||
using Context = Ui::Text::MarkedContext;
|
||||
using Factory = Ui::Text::CustomEmojiFactory;
|
||||
|
||||
const auto session = args.session;
|
||||
auto simple = [session](QStringView data, const Context &context) {
|
||||
return session->data().customEmojiManager().create(
|
||||
data,
|
||||
context.repaint);
|
||||
};
|
||||
auto factory = !args.customEmojiLoopLimit
|
||||
? Factory(simple)
|
||||
: (args.customEmojiLoopLimit > 0)
|
||||
? Factory([simple, loop = args.customEmojiLoopLimit](
|
||||
QStringView data,
|
||||
const Context &context) {
|
||||
return std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||
simple(data, context),
|
||||
loop);
|
||||
})
|
||||
: Factory([simple](
|
||||
QStringView data,
|
||||
const Context &context) {
|
||||
return std::make_unique<Ui::Text::FirstFrameEmoji>(
|
||||
simple(data, context));
|
||||
});
|
||||
args.details.session = session;
|
||||
return {
|
||||
.repaint = std::move(args.repaint),
|
||||
.customEmojiFactory = std::move(factory),
|
||||
.other = std::move(args.details),
|
||||
};
|
||||
}
|
||||
|
||||
void UiIntegration::postponeCall(FnMut<void()> &&callable) {
|
||||
Sandbox::Instance().postponeCall(std::move(callable));
|
||||
}
|
||||
|
@ -152,8 +187,8 @@ bool UiIntegration::screenIsLocked() {
|
|||
|
||||
std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
||||
const EntityLinkData &data,
|
||||
const std::any &context) {
|
||||
const auto my = std::any_cast<MarkedTextContext>(&context);
|
||||
const Ui::Text::MarkedContext &context) {
|
||||
const auto my = std::any_cast<Core::TextContextDetails>(&context.other);
|
||||
switch (data.type) {
|
||||
case EntityType::Url:
|
||||
return (!data.data.isEmpty()
|
||||
|
@ -170,7 +205,7 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
|||
return std::make_shared<BotCommandClickHandler>(data.data);
|
||||
|
||||
case EntityType::Hashtag:
|
||||
using HashtagMentionType = MarkedTextContext::HashtagMentionType;
|
||||
using HashtagMentionType = TextContextDetails::HashtagMentionType;
|
||||
if (my && my->type == HashtagMentionType::Twitter) {
|
||||
return std::make_shared<UrlClickHandler>(
|
||||
(u"https://twitter.com/hashtag/"_q
|
||||
|
@ -190,7 +225,7 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
|||
return std::make_shared<CashtagClickHandler>(data.data);
|
||||
|
||||
case EntityType::Mention:
|
||||
using HashtagMentionType = MarkedTextContext::HashtagMentionType;
|
||||
using HashtagMentionType = TextContextDetails::HashtagMentionType;
|
||||
if (my && my->type == HashtagMentionType::Twitter) {
|
||||
return std::make_shared<UrlClickHandler>(
|
||||
u"https://twitter.com/"_q + data.data.mid(1),
|
||||
|
@ -222,7 +257,9 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
|||
case EntityType::Pre:
|
||||
return std::make_shared<MonospaceClickHandler>(data.text, data.type);
|
||||
case EntityType::Phone:
|
||||
return std::make_shared<PhoneClickHandler>(my->session, data.text);
|
||||
return my->session
|
||||
? std::make_shared<PhoneClickHandler>(my->session, data.text)
|
||||
: nullptr;
|
||||
}
|
||||
return Integration::createLinkHandler(data, context);
|
||||
}
|
||||
|
@ -280,36 +317,6 @@ bool UiIntegration::copyPreOnClick(const QVariant &context) {
|
|||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> UiIntegration::createCustomEmoji(
|
||||
QStringView data,
|
||||
const std::any &context) {
|
||||
const auto my = std::any_cast<MarkedTextContext>(&context);
|
||||
if (!my || !my->session) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = my->session->data().customEmojiManager().create(
|
||||
data,
|
||||
my->customEmojiRepaint);
|
||||
if (my->customEmojiLoopLimit > 0) {
|
||||
return std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||
std::move(result),
|
||||
my->customEmojiLoopLimit);
|
||||
} else if (my->customEmojiLoopLimit) {
|
||||
return std::make_unique<Ui::Text::FirstFrameEmoji>(
|
||||
std::move(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Fn<void()> UiIntegration::createSpoilerRepaint(const std::any &context) {
|
||||
const auto my = std::any_cast<MarkedTextContext>(&context);
|
||||
if (my) {
|
||||
return my->customEmojiRepaint;
|
||||
}
|
||||
const auto common = std::any_cast<CommonTextContext>(&context);
|
||||
return common ? common->repaint : nullptr;
|
||||
}
|
||||
|
||||
rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
|
||||
return Core::App().passcodeLockChanges() | rpl::to_empty;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class ElementDelegate;
|
|||
|
||||
namespace Core {
|
||||
|
||||
struct MarkedTextContext {
|
||||
struct TextContextDetails {
|
||||
enum class HashtagMentionType : uchar {
|
||||
Telegram,
|
||||
Twitter,
|
||||
|
@ -28,9 +28,15 @@ struct MarkedTextContext {
|
|||
|
||||
Main::Session *session = nullptr;
|
||||
HashtagMentionType type = HashtagMentionType::Telegram;
|
||||
Fn<void()> customEmojiRepaint;
|
||||
};
|
||||
|
||||
struct TextContextArgs {
|
||||
not_null<Main::Session*> session;
|
||||
TextContextDetails details;
|
||||
Fn<void()> repaint;
|
||||
int customEmojiLoopLimit = 0;
|
||||
};
|
||||
[[nodiscard]] Ui::Text::MarkedContext TextContext(TextContextArgs &&args);
|
||||
|
||||
class UiIntegration final : public Ui::Integration {
|
||||
public:
|
||||
|
@ -49,7 +55,7 @@ public:
|
|||
|
||||
std::shared_ptr<ClickHandler> createLinkHandler(
|
||||
const EntityLinkData &data,
|
||||
const std::any &context) override;
|
||||
const Ui::Text::MarkedContext &context) override;
|
||||
bool handleUrlClick(
|
||||
const QString &url,
|
||||
const QVariant &context) override;
|
||||
|
@ -57,10 +63,6 @@ public:
|
|||
rpl::producer<> forcePopupMenuHideRequests() override;
|
||||
const Ui::Emoji::One *defaultEmojiVariant(
|
||||
const Ui::Emoji::One *emoji) override;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> createCustomEmoji(
|
||||
QStringView data,
|
||||
const std::any &context) override;
|
||||
Fn<void()> createSpoilerRepaint(const std::any &context) override;
|
||||
|
||||
QString phraseContextCopyText() override;
|
||||
QString phraseContextCopyEmail() override;
|
||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
|||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||
constexpr auto AppFile = "AyuGram"_cs;
|
||||
constexpr auto AppVersion = 5011001;
|
||||
constexpr auto AppVersionStr = "5.11.1";
|
||||
constexpr auto AppVersion = 5012001;
|
||||
constexpr auto AppVersionStr = "5.12.1";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
|
|
@ -91,7 +91,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
|||
MTP_int(shortcutId),
|
||||
MTP_long(data.veffect().value_or_empty()),
|
||||
(data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()),
|
||||
MTP_int(data.vreport_delivery_until_date().value_or_empty()));
|
||||
MTP_int(data.vreport_delivery_until_date().value_or_empty()),
|
||||
MTP_long(data.vpaid_message_stars().value_or_empty()));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -37,10 +37,7 @@ void Credits::apply(const MTPDupdateStarsBalance &data) {
|
|||
|
||||
rpl::producer<float64> Credits::rateValue(
|
||||
not_null<PeerData*> ownedBotOrChannel) {
|
||||
return rpl::single(
|
||||
_session->appConfig().get<float64>(
|
||||
u"stars_usd_withdraw_rate_x1000"_q,
|
||||
1200) / 1000.);
|
||||
return rpl::single(_session->appConfig().starsWithdrawRate());
|
||||
}
|
||||
|
||||
void Credits::load(bool force) {
|
||||
|
|
|
@ -95,7 +95,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
|||
MTPint(), // quick_reply_shortcut_id
|
||||
MTP_long(data.veffect().value_or_empty()), // effect
|
||||
data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck(),
|
||||
MTP_int(data.vreport_delivery_until_date().value_or_empty()));
|
||||
MTP_int(data.vreport_delivery_until_date().value_or_empty()),
|
||||
MTP_long(data.vpaid_message_stars().value_or_empty()));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -269,7 +270,8 @@ void ScheduledMessages::sendNowSimpleMessage(
|
|||
MTPint(), // quick_reply_shortcut_id
|
||||
MTP_long(local->effectId()), // effect
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong()), // paid_message_stars
|
||||
localFlags,
|
||||
NewMessageType::Unread);
|
||||
|
||||
|
|
|
@ -75,46 +75,48 @@ struct PeerUpdate {
|
|||
BackgroundEmoji = (1ULL << 15),
|
||||
StoriesState = (1ULL << 16),
|
||||
VerifyInfo = (1ULL << 17),
|
||||
StarsPerMessage = (1ULL << 18),
|
||||
|
||||
// For users
|
||||
CanShareContact = (1ULL << 18),
|
||||
IsContact = (1ULL << 19),
|
||||
PhoneNumber = (1ULL << 20),
|
||||
OnlineStatus = (1ULL << 21),
|
||||
BotCommands = (1ULL << 22),
|
||||
BotCanBeInvited = (1ULL << 23),
|
||||
BotStartToken = (1ULL << 24),
|
||||
CommonChats = (1ULL << 25),
|
||||
PeerGifts = (1ULL << 26),
|
||||
HasCalls = (1ULL << 27),
|
||||
SupportInfo = (1ULL << 28),
|
||||
IsBot = (1ULL << 29),
|
||||
EmojiStatus = (1ULL << 30),
|
||||
BusinessDetails = (1ULL << 31),
|
||||
Birthday = (1ULL << 32),
|
||||
PersonalChannel = (1ULL << 33),
|
||||
StarRefProgram = (1ULL << 34),
|
||||
CanShareContact = (1ULL << 19),
|
||||
IsContact = (1ULL << 20),
|
||||
PhoneNumber = (1ULL << 21),
|
||||
OnlineStatus = (1ULL << 22),
|
||||
BotCommands = (1ULL << 23),
|
||||
BotCanBeInvited = (1ULL << 24),
|
||||
BotStartToken = (1ULL << 25),
|
||||
CommonChats = (1ULL << 26),
|
||||
PeerGifts = (1ULL << 27),
|
||||
HasCalls = (1ULL << 28),
|
||||
SupportInfo = (1ULL << 29),
|
||||
IsBot = (1ULL << 30),
|
||||
EmojiStatus = (1ULL << 31),
|
||||
BusinessDetails = (1ULL << 32),
|
||||
Birthday = (1ULL << 33),
|
||||
PersonalChannel = (1ULL << 34),
|
||||
StarRefProgram = (1ULL << 35),
|
||||
PaysPerMessage = (1ULL << 36),
|
||||
|
||||
// For chats and channels
|
||||
InviteLinks = (1ULL << 35),
|
||||
Members = (1ULL << 36),
|
||||
Admins = (1ULL << 37),
|
||||
BannedUsers = (1ULL << 38),
|
||||
Rights = (1ULL << 39),
|
||||
PendingRequests = (1ULL << 40),
|
||||
Reactions = (1ULL << 41),
|
||||
InviteLinks = (1ULL << 37),
|
||||
Members = (1ULL << 38),
|
||||
Admins = (1ULL << 39),
|
||||
BannedUsers = (1ULL << 40),
|
||||
Rights = (1ULL << 41),
|
||||
PendingRequests = (1ULL << 42),
|
||||
Reactions = (1ULL << 43),
|
||||
|
||||
// For channels
|
||||
ChannelAmIn = (1ULL << 42),
|
||||
StickersSet = (1ULL << 43),
|
||||
EmojiSet = (1ULL << 44),
|
||||
ChannelLinkedChat = (1ULL << 45),
|
||||
ChannelLocation = (1ULL << 46),
|
||||
Slowmode = (1ULL << 47),
|
||||
GroupCall = (1ULL << 48),
|
||||
ChannelAmIn = (1ULL << 44),
|
||||
StickersSet = (1ULL << 45),
|
||||
EmojiSet = (1ULL << 46),
|
||||
ChannelLinkedChat = (1ULL << 47),
|
||||
ChannelLocation = (1ULL << 48),
|
||||
Slowmode = (1ULL << 49),
|
||||
GroupCall = (1ULL << 50),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1ULL << 48),
|
||||
LastUsedBit = (1ULL << 50),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_channel.h"
|
||||
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel_admins.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "api/api_chat_invite.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "apiwrap.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "window/notifications_manager.h"
|
||||
|
||||
|
@ -859,6 +861,21 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) {
|
|||
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
|
||||
}
|
||||
|
||||
int ChannelData::starsPerMessage() const {
|
||||
if (const auto info = mgInfo.get()) {
|
||||
return info->_starsPerMessage;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ChannelData::setStarsPerMessage(int stars) {
|
||||
if (mgInfo && starsPerMessage() != stars) {
|
||||
mgInfo->_starsPerMessage = stars;
|
||||
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
|
||||
}
|
||||
checkTrustedPayForMessage();
|
||||
}
|
||||
|
||||
int ChannelData::peerGiftsCount() const {
|
||||
return _peerGiftsCount;
|
||||
}
|
||||
|
@ -1150,7 +1167,8 @@ void ApplyChannelUpdate(
|
|||
| Flag::CanViewRevenue
|
||||
| Flag::PaidMediaAllowed
|
||||
| Flag::CanViewCreditsRevenue
|
||||
| Flag::StargiftsAvailable;
|
||||
| Flag::StargiftsAvailable
|
||||
| Flag::PaidMessagesAvailable;
|
||||
channel->setFlags((channel->flags() & ~mask)
|
||||
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
|
||||
| (update.is_can_view_participants()
|
||||
|
@ -1174,6 +1192,9 @@ void ApplyChannelUpdate(
|
|||
: Flag())
|
||||
| (update.is_stargifts_available()
|
||||
? Flag::StargiftsAvailable
|
||||
: Flag())
|
||||
| (update.is_paid_messages_available()
|
||||
? Flag::PaidMessagesAvailable
|
||||
: Flag()));
|
||||
channel->setUserpicPhoto(update.vchat_photo());
|
||||
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
|
||||
|
|
|
@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_peer_bot_commands.h"
|
||||
#include "data/data_user_names.h"
|
||||
|
||||
class ChannelData;
|
||||
|
||||
struct ChannelLocation {
|
||||
QString address;
|
||||
Data::LocationPoint point;
|
||||
|
@ -70,6 +72,7 @@ enum class ChannelDataFlag : uint64 {
|
|||
CanViewCreditsRevenue = (1ULL << 34),
|
||||
SignatureProfiles = (1ULL << 35),
|
||||
StargiftsAvailable = (1ULL << 36),
|
||||
PaidMessagesAvailable = (1ULL << 37),
|
||||
};
|
||||
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
||||
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
||||
|
@ -150,6 +153,9 @@ private:
|
|||
ChannelLocation _location;
|
||||
Data::ChatBotCommands _botCommands;
|
||||
std::unique_ptr<Data::Forum> _forum;
|
||||
int _starsPerMessage = 0;
|
||||
|
||||
friend class ChannelData;
|
||||
|
||||
};
|
||||
|
||||
|
@ -257,6 +263,9 @@ public:
|
|||
[[nodiscard]] bool stargiftsAvailable() const {
|
||||
return flags() & Flag::StargiftsAvailable;
|
||||
}
|
||||
[[nodiscard]] bool paidMessagesAvailable() const {
|
||||
return flags() & Flag::PaidMessagesAvailable;
|
||||
}
|
||||
|
||||
[[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights(
|
||||
not_null<PeerData*> participant);
|
||||
|
@ -456,6 +465,9 @@ public:
|
|||
[[nodiscard]] TimeId slowmodeLastMessage() const;
|
||||
void growSlowmodeLastMessage(TimeId when);
|
||||
|
||||
void setStarsPerMessage(int stars);
|
||||
[[nodiscard]] int starsPerMessage() const;
|
||||
|
||||
[[nodiscard]] int peerGiftsCount() const;
|
||||
void setPeerGiftsCount(int count);
|
||||
|
||||
|
|
|
@ -17,10 +17,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -120,7 +123,7 @@ bool CanSendAnyOf(
|
|||
|| user->isRepliesChat()
|
||||
|| user->isVerifyCodes()) {
|
||||
return false;
|
||||
} else if (user->meRequiresPremiumToWrite()
|
||||
} else if (user->requiresPremiumToWrite()
|
||||
&& !user->session().premium()) {
|
||||
return false;
|
||||
} else if (rights
|
||||
|
@ -177,7 +180,7 @@ SendError RestrictionError(
|
|||
using Flag = ChatRestriction;
|
||||
if (const auto restricted = peer->amRestricted(restriction)) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (user->meRequiresPremiumToWrite()
|
||||
if (user->requiresPremiumToWrite()
|
||||
&& !user->session().premium()) {
|
||||
return SendError({
|
||||
.text = tr::lng_restricted_send_non_premium(
|
||||
|
|
|
@ -66,10 +66,11 @@ struct CreditsHistoryEntry final {
|
|||
uint64 bareGiftStickerId = 0;
|
||||
uint64 bareGiftOwnerId = 0;
|
||||
uint64 bareActorId = 0;
|
||||
uint64 bareGiftListPeerId = 0;
|
||||
uint64 giftSavedId = 0;
|
||||
uint64 bareEntryOwnerId = 0;
|
||||
uint64 giftChannelSavedId = 0;
|
||||
uint64 stargiftId = 0;
|
||||
std::shared_ptr<UniqueGift> uniqueGift;
|
||||
Fn<std::vector<CreditsHistoryEntry>()> pinnedSavedGifts;
|
||||
StarsAmount starrefAmount;
|
||||
int starrefCommission = 0;
|
||||
uint64 starrefRecipientId = 0;
|
||||
|
@ -77,11 +78,15 @@ struct CreditsHistoryEntry final {
|
|||
QDateTime subscriptionUntil;
|
||||
QDateTime successDate;
|
||||
QString successLink;
|
||||
int paidMessagesCount = 0;
|
||||
StarsAmount paidMessagesAmount;
|
||||
int paidMessagesCommission = 0;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int starsConverted = 0;
|
||||
int starsToUpgrade = 0;
|
||||
int starsUpgradedBySender = 0;
|
||||
int premiumMonthsForStars = 0;
|
||||
int floodSkip = 0;
|
||||
bool converted : 1 = false;
|
||||
bool anonymous : 1 = false;
|
||||
|
@ -89,6 +94,7 @@ struct CreditsHistoryEntry final {
|
|||
bool giftTransferred : 1 = false;
|
||||
bool giftRefunded : 1 = false;
|
||||
bool giftUpgraded : 1 = false;
|
||||
bool giftPinned : 1 = false;
|
||||
bool savedToProfile : 1 = false;
|
||||
bool fromGiftsList : 1 = false;
|
||||
bool fromGiftSlug : 1 = false;
|
||||
|
|
|
@ -16,7 +16,10 @@ namespace Data {
|
|||
|
||||
struct CreditsEarnStatistics final {
|
||||
explicit operator bool() const {
|
||||
return !!usdRate;
|
||||
return usdRate
|
||||
&& currentBalance
|
||||
&& availableBalance
|
||||
&& overallRevenue;
|
||||
}
|
||||
Data::StatisticalGraph revenueGraph;
|
||||
StarsAmount currentBalance;
|
||||
|
|
|
@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_item.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
|
||||
|
@ -65,6 +66,28 @@ using UpdateFlag = Data::PeerUpdate::Flag;
|
|||
return session->appConfig().ignoredRestrictionReasons();
|
||||
}
|
||||
|
||||
[[nodiscard]] int ParseRegistrationDate(const QString &text) {
|
||||
// MM.YYYY
|
||||
if (text.size() != 7 || text[2] != '.') {
|
||||
return 0;
|
||||
}
|
||||
const auto month = text.mid(0, 2).toInt();
|
||||
const auto year = text.mid(3, 4).toInt();
|
||||
return (year > 2012 && year < 2100 && month > 0 && month <= 12)
|
||||
? (year * 100) + month
|
||||
: 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] int RegistrationYear(int date) {
|
||||
const auto year = date / 100;
|
||||
return (year > 2012 && year < 2100) ? year : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] int RegistrationMonth(int date) {
|
||||
const auto month = date % 100;
|
||||
return (month > 0 && month <= 12) ? month : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Data {
|
||||
|
@ -311,6 +334,17 @@ void PeerData::invalidateEmptyUserpic() {
|
|||
_userpicEmpty = nullptr;
|
||||
}
|
||||
|
||||
void PeerData::checkTrustedPayForMessage() {
|
||||
if (!_checkedTrustedPayForMessage
|
||||
&& !starsPerMessage()
|
||||
&& session().local().peerTrustedPayForMessageRead()) {
|
||||
_checkedTrustedPayForMessage = 1;
|
||||
if (session().local().hasPeerTrustedPayForMessageEntry(id)) {
|
||||
session().local().clearPeerTrustedPayForMessage(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClickHandlerPtr PeerData::createOpenLink() {
|
||||
return std::make_shared<PeerClickHandler>(this);
|
||||
}
|
||||
|
@ -692,7 +726,9 @@ void PeerData::checkFolder(FolderId folderId) {
|
|||
|
||||
void PeerData::clearBusinessBot() {
|
||||
if (const auto details = _barDetails.get()) {
|
||||
if (details->requestChatDate) {
|
||||
if (details->requestChatDate
|
||||
|| details->paysPerMessage
|
||||
|| !details->phoneCountryCode.isEmpty()) {
|
||||
details->businessBot = nullptr;
|
||||
details->businessBotManageUrl = QString();
|
||||
} else {
|
||||
|
@ -735,12 +771,27 @@ void PeerData::saveTranslationDisabled(bool disabled) {
|
|||
|
||||
void PeerData::setBarSettings(const MTPPeerSettings &data) {
|
||||
data.match([&](const MTPDpeerSettings &data) {
|
||||
if (!data.vbusiness_bot_id() && !data.vrequest_chat_title()) {
|
||||
const auto wasPaysPerMessage = paysPerMessage();
|
||||
if (!data.vbusiness_bot_id()
|
||||
&& !data.vrequest_chat_title()
|
||||
&& !data.vcharge_paid_message_stars()
|
||||
&& !data.vphone_country()
|
||||
&& !data.vregistration_month()
|
||||
&& !data.vname_change_date()
|
||||
&& !data.vphoto_change_date()) {
|
||||
_barDetails = nullptr;
|
||||
} else if (!_barDetails) {
|
||||
_barDetails = std::make_unique<PeerBarDetails>();
|
||||
}
|
||||
if (_barDetails) {
|
||||
_barDetails->phoneCountryCode
|
||||
= qs(data.vphone_country().value_or_empty());
|
||||
_barDetails->registrationDate = ParseRegistrationDate(
|
||||
data.vregistration_month().value_or_empty());
|
||||
_barDetails->nameChangeDate
|
||||
= data.vname_change_date().value_or_empty();
|
||||
_barDetails->photoChangeDate
|
||||
= data.vphoto_change_date().value_or_empty();
|
||||
_barDetails->requestChatTitle
|
||||
= qs(data.vrequest_chat_title().value_or_empty());
|
||||
_barDetails->requestChatDate
|
||||
|
@ -750,6 +801,8 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) {
|
|||
: nullptr;
|
||||
_barDetails->businessBotManageUrl
|
||||
= qs(data.vbusiness_bot_manage_url().value_or_empty());
|
||||
_barDetails->paysPerMessage
|
||||
= data.vcharge_paid_message_stars().value_or_empty();
|
||||
}
|
||||
using Flag = PeerBarSetting;
|
||||
setBarSettings((data.is_add_contact() ? Flag::AddContact : Flag())
|
||||
|
@ -773,8 +826,35 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) {
|
|||
| (data.is_business_bot_can_reply()
|
||||
? Flag::BusinessBotCanReply
|
||||
: Flag()));
|
||||
if (wasPaysPerMessage != paysPerMessage()) {
|
||||
session().changes().peerUpdated(
|
||||
this,
|
||||
UpdateFlag::PaysPerMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int PeerData::paysPerMessage() const {
|
||||
return _barDetails ? _barDetails->paysPerMessage : 0;
|
||||
}
|
||||
|
||||
void PeerData::clearPaysPerMessage() {
|
||||
if (const auto details = _barDetails.get()) {
|
||||
if (details->paysPerMessage) {
|
||||
if (details->businessBot
|
||||
|| details->requestChatDate
|
||||
|| !details->phoneCountryCode.isEmpty()) {
|
||||
details->paysPerMessage = 0;
|
||||
} else {
|
||||
_barDetails = nullptr;
|
||||
}
|
||||
session().changes().peerUpdated(
|
||||
this,
|
||||
UpdateFlag::PaysPerMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString PeerData::requestChatTitle() const {
|
||||
return _barDetails ? _barDetails->requestChatTitle : QString();
|
||||
}
|
||||
|
@ -791,6 +871,28 @@ QString PeerData::businessBotManageUrl() const {
|
|||
return _barDetails ? _barDetails->businessBotManageUrl : QString();
|
||||
}
|
||||
|
||||
QString PeerData::phoneCountryCode() const {
|
||||
return _barDetails ? _barDetails->phoneCountryCode : QString();
|
||||
}
|
||||
|
||||
int PeerData::registrationMonth() const {
|
||||
return _barDetails
|
||||
? RegistrationMonth(_barDetails->registrationDate)
|
||||
: 0;
|
||||
}
|
||||
|
||||
int PeerData::registrationYear() const {
|
||||
return _barDetails ? RegistrationYear(_barDetails->registrationDate) : 0;
|
||||
}
|
||||
|
||||
TimeId PeerData::nameChangeDate() const {
|
||||
return _barDetails ? _barDetails->nameChangeDate : 0;
|
||||
}
|
||||
|
||||
TimeId PeerData::photoChangeDate() const {
|
||||
return _barDetails ? _barDetails->photoChangeDate : 0;
|
||||
}
|
||||
|
||||
bool PeerData::changeColorIndex(
|
||||
const tl::conditional<MTPint> &cloudColorIndex) {
|
||||
return cloudColorIndex
|
||||
|
@ -1301,7 +1403,7 @@ Data::RestrictionCheckResult PeerData::amRestricted(
|
|||
}
|
||||
};
|
||||
if (const auto user = asUser()) {
|
||||
if (user->meRequiresPremiumToWrite() && !user->session().premium()) {
|
||||
if (user->requiresPremiumToWrite() && !user->session().premium()) {
|
||||
return Result::Explicit();
|
||||
}
|
||||
return (right == ChatRestriction::SendVoiceMessages
|
||||
|
@ -1420,6 +1522,24 @@ bool PeerData::canManageGroupCall() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
int PeerData::starsPerMessage() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->starsPerMessage();
|
||||
} else if (const auto channel = asChannel()) {
|
||||
return channel->starsPerMessage();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PeerData::starsPerMessageChecked() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return (channel->adminRights() || channel->amCreator())
|
||||
? 0
|
||||
: channel->starsPerMessage();
|
||||
}
|
||||
return starsPerMessage();
|
||||
}
|
||||
|
||||
Data::GroupCall *PeerData::groupCall() const {
|
||||
if (const auto chat = asChat()) {
|
||||
return chat->groupCall();
|
||||
|
|
|
@ -173,10 +173,15 @@ inline constexpr bool is_flag_type(PeerBarSetting) { return true; };
|
|||
using PeerBarSettings = base::flags<PeerBarSetting>;
|
||||
|
||||
struct PeerBarDetails {
|
||||
QString phoneCountryCode;
|
||||
int registrationDate = 0; // YYYYMM or 0, YYYY > 2012, MM > 0.
|
||||
TimeId nameChangeDate = 0;
|
||||
TimeId photoChangeDate = 0;
|
||||
QString requestChatTitle;
|
||||
TimeId requestChatDate;
|
||||
UserData *businessBot = nullptr;
|
||||
QString businessBotManageUrl;
|
||||
int paysPerMessage = 0;
|
||||
};
|
||||
|
||||
class PeerData {
|
||||
|
@ -268,6 +273,9 @@ public:
|
|||
[[nodiscard]] int slowmodeSecondsLeft() const;
|
||||
[[nodiscard]] bool canManageGroupCall() const;
|
||||
|
||||
[[nodiscard]] int starsPerMessage() const;
|
||||
[[nodiscard]] int starsPerMessageChecked() const;
|
||||
|
||||
[[nodiscard]] UserData *asBot();
|
||||
[[nodiscard]] const UserData *asBot() const;
|
||||
[[nodiscard]] UserData *asUser();
|
||||
|
@ -409,11 +417,18 @@ public:
|
|||
? _barSettings.changes()
|
||||
: (_barSettings.value() | rpl::type_erased());
|
||||
}
|
||||
[[nodiscard]] int paysPerMessage() const;
|
||||
void clearPaysPerMessage();
|
||||
[[nodiscard]] QString requestChatTitle() const;
|
||||
[[nodiscard]] TimeId requestChatDate() const;
|
||||
[[nodiscard]] UserData *businessBot() const;
|
||||
[[nodiscard]] QString businessBotManageUrl() const;
|
||||
void clearBusinessBot();
|
||||
[[nodiscard]] QString phoneCountryCode() const;
|
||||
[[nodiscard]] int registrationMonth() const;
|
||||
[[nodiscard]] int registrationYear() const;
|
||||
[[nodiscard]] TimeId nameChangeDate() const;
|
||||
[[nodiscard]] TimeId photoChangeDate() const;
|
||||
|
||||
enum class TranslationFlag : uchar {
|
||||
Unknown,
|
||||
|
@ -501,6 +516,7 @@ protected:
|
|||
void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo);
|
||||
void clearUserpic();
|
||||
void invalidateEmptyUserpic();
|
||||
void checkTrustedPayForMessage();
|
||||
|
||||
private:
|
||||
void fillNames();
|
||||
|
@ -535,9 +551,10 @@ private:
|
|||
crl::time _lastFullUpdate = 0;
|
||||
|
||||
QString _name;
|
||||
uint32 _nameVersion : 30 = 1;
|
||||
uint32 _nameVersion : 29 = 1;
|
||||
uint32 _sensitiveContent : 1 = 0;
|
||||
uint32 _wallPaperOverriden : 1 = 0;
|
||||
uint32 _checkedTrustedPayForMessage : 1 = 0;
|
||||
|
||||
TimeId _ttlPeriod = 0;
|
||||
|
||||
|
|
|
@ -228,11 +228,11 @@ inline auto DefaultRestrictionValue(
|
|||
| ChatRestriction::SendVideoMessages);
|
||||
auto allowedAny = PeerFlagsValue(
|
||||
user,
|
||||
(UserDataFlag::Deleted | UserDataFlag::MeRequiresPremiumToWrite)
|
||||
(UserDataFlag::Deleted | UserDataFlag::RequiresPremiumToWrite)
|
||||
) | rpl::map([=](UserDataFlags flags) {
|
||||
return (flags & UserDataFlag::Deleted)
|
||||
? rpl::single(false)
|
||||
: !(flags & UserDataFlag::MeRequiresPremiumToWrite)
|
||||
: !(flags & UserDataFlag::RequiresPremiumToWrite)
|
||||
? rpl::single(true)
|
||||
: AmPremiumValue(&user->session());
|
||||
}) | rpl::flatten_latest();
|
||||
|
|
|
@ -542,14 +542,22 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
|
|||
| Flag::BotInlineGeo
|
||||
| Flag::Premium
|
||||
| Flag::Support
|
||||
| Flag::SomeRequirePremiumToWrite
|
||||
| Flag::RequirePremiumToWriteKnown
|
||||
| Flag::HasRequirePremiumToWrite
|
||||
| Flag::HasStarsPerMessage
|
||||
| Flag::MessageMoneyRestrictionsKnown
|
||||
| (!minimal
|
||||
? Flag::Contact
|
||||
| Flag::MutualContact
|
||||
| Flag::DiscardMinPhoto
|
||||
| Flag::StoriesHidden
|
||||
: Flag());
|
||||
const auto hasRequirePremiumToWrite
|
||||
= data.is_contact_require_premium();
|
||||
const auto hasStarsPerMessage
|
||||
= data.vsend_paid_messages_stars().has_value();
|
||||
if (!hasStarsPerMessage) {
|
||||
result->setStarsPerMessage(0);
|
||||
}
|
||||
const auto storiesState = minimal
|
||||
? std::optional<Data::Stories::PeerSourceState>()
|
||||
: data.is_stories_unavailable()
|
||||
|
@ -564,14 +572,25 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
|
|||
| (data.is_bot_inline_geo() ? Flag::BotInlineGeo : Flag())
|
||||
| (data.is_premium() ? Flag::Premium : Flag())
|
||||
| (data.is_support() ? Flag::Support : Flag())
|
||||
| (data.is_contact_require_premium()
|
||||
? (Flag::SomeRequirePremiumToWrite
|
||||
| (result->someRequirePremiumToWrite()
|
||||
? (result->requirePremiumToWriteKnown()
|
||||
? Flag::RequirePremiumToWriteKnown
|
||||
| (hasRequirePremiumToWrite
|
||||
? (Flag::HasRequirePremiumToWrite
|
||||
| (result->hasRequirePremiumToWrite()
|
||||
? (result->messageMoneyRestrictionsKnown()
|
||||
? Flag::MessageMoneyRestrictionsKnown
|
||||
: Flag())
|
||||
: Flag()))
|
||||
: Flag())
|
||||
| (hasStarsPerMessage
|
||||
? (Flag::HasStarsPerMessage
|
||||
| (result->hasStarsPerMessage()
|
||||
? (result->messageMoneyRestrictionsKnown()
|
||||
? Flag::MessageMoneyRestrictionsKnown
|
||||
: Flag())
|
||||
: Flag()))
|
||||
: Flag())
|
||||
| ((!hasRequirePremiumToWrite && !hasStarsPerMessage)
|
||||
? Flag::MessageMoneyRestrictionsKnown
|
||||
: Flag())
|
||||
| (!minimal
|
||||
? (data.is_contact() ? Flag::Contact : Flag())
|
||||
| (data.is_mutual_contact() ? Flag::MutualContact : Flag())
|
||||
|
@ -1009,6 +1028,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
|||
}
|
||||
|
||||
channel->setPhoto(data.vphoto());
|
||||
channel->setStarsPerMessage(
|
||||
data.vsend_paid_messages_stars().value_or_empty());
|
||||
|
||||
if (wasInChannel != channel->amIn()) {
|
||||
flags |= UpdateFlag::ChannelAmIn;
|
||||
|
@ -4689,7 +4710,8 @@ void Session::serviceNotification(
|
|||
MTPPeerColor(), // color
|
||||
MTPPeerColor(), // profile_color
|
||||
MTPint(), // bot_active_users
|
||||
MTPlong())); // bot_verification_icon
|
||||
MTPlong(), // bot_verification_icon
|
||||
MTPlong())); // send_paid_messages_stars
|
||||
}
|
||||
const auto history = this->history(PeerData::kServiceNotificationsId);
|
||||
const auto insert = [=] {
|
||||
|
@ -4748,7 +4770,8 @@ void Session::insertCheckedServiceNotification(
|
|||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong()), // paid_message_stars
|
||||
localFlags,
|
||||
NewMessageType::Unread);
|
||||
}
|
||||
|
|
|
@ -87,6 +87,8 @@ struct GiftUpdate {
|
|||
Convert,
|
||||
Transfer,
|
||||
Delete,
|
||||
Pin,
|
||||
Unpin,
|
||||
};
|
||||
|
||||
Data::SavedStarGiftId id;
|
||||
|
|
|
@ -132,6 +132,7 @@ struct SavedStarGift {
|
|||
TimeId date = 0;
|
||||
bool upgradable = false;
|
||||
bool anonymous = false;
|
||||
bool pinned = false;
|
||||
bool hidden = false;
|
||||
bool mine = false;
|
||||
};
|
||||
|
|
|
@ -349,6 +349,8 @@ enum class MessageFlag : uint64 {
|
|||
EstimatedDate = (1ULL << 49),
|
||||
|
||||
ReactionsAllowed = (1ULL << 50),
|
||||
|
||||
HideDisplayDate = (1ULL << 51),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "api/api_sensitive_content.h"
|
||||
#include "api/api_statistics.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_user_photos.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/business/data_business_common.h"
|
||||
|
@ -526,19 +527,23 @@ bool UserData::hasStoriesHidden() const {
|
|||
return (flags() & UserDataFlag::StoriesHidden);
|
||||
}
|
||||
|
||||
bool UserData::someRequirePremiumToWrite() const {
|
||||
return (flags() & UserDataFlag::SomeRequirePremiumToWrite);
|
||||
bool UserData::hasRequirePremiumToWrite() const {
|
||||
return (flags() & UserDataFlag::HasRequirePremiumToWrite);
|
||||
}
|
||||
|
||||
bool UserData::meRequiresPremiumToWrite() const {
|
||||
return !isSelf() && (flags() & UserDataFlag::MeRequiresPremiumToWrite);
|
||||
bool UserData::hasStarsPerMessage() const {
|
||||
return (flags() & UserDataFlag::HasStarsPerMessage);
|
||||
}
|
||||
|
||||
bool UserData::requirePremiumToWriteKnown() const {
|
||||
return (flags() & UserDataFlag::RequirePremiumToWriteKnown);
|
||||
bool UserData::requiresPremiumToWrite() const {
|
||||
return !isSelf() && (flags() & UserDataFlag::RequiresPremiumToWrite);
|
||||
}
|
||||
|
||||
bool UserData::canSendIgnoreRequirePremium() const {
|
||||
bool UserData::messageMoneyRestrictionsKnown() const {
|
||||
return (flags() & UserDataFlag::MessageMoneyRestrictionsKnown);
|
||||
}
|
||||
|
||||
bool UserData::canSendIgnoreMoneyRestrictions() const {
|
||||
return !isInaccessible() && !isRepliesChat() && !isVerifyCodes();
|
||||
}
|
||||
|
||||
|
@ -546,6 +551,18 @@ bool UserData::readDatesPrivate() const {
|
|||
return (flags() & UserDataFlag::ReadDatesPrivate);
|
||||
}
|
||||
|
||||
int UserData::starsPerMessage() const {
|
||||
return _starsPerMessage;
|
||||
}
|
||||
|
||||
void UserData::setStarsPerMessage(int stars) {
|
||||
if (_starsPerMessage != stars) {
|
||||
_starsPerMessage = stars;
|
||||
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
|
||||
}
|
||||
checkTrustedPayForMessage();
|
||||
}
|
||||
|
||||
bool UserData::canAddContact() const {
|
||||
return canShareThisContact() && !isContact();
|
||||
}
|
||||
|
@ -624,7 +641,6 @@ void UserData::setCallsStatus(CallsStatus callsStatus) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Data::Birthday UserData::birthday() const {
|
||||
return _birthday;
|
||||
}
|
||||
|
@ -699,6 +715,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
|||
if (const auto pinned = update.vpinned_msg_id()) {
|
||||
SetTopPinnedMessageId(user, pinned->v);
|
||||
}
|
||||
user->setStarsPerMessage(
|
||||
update.vsend_paid_messages_stars().value_or_empty());
|
||||
using Flag = UserDataFlag;
|
||||
const auto mask = Flag::Blocked
|
||||
| Flag::HasPhoneCalls
|
||||
|
@ -706,8 +724,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
|||
| Flag::CanPinMessages
|
||||
| Flag::VoiceMessagesForbidden
|
||||
| Flag::ReadDatesPrivate
|
||||
| Flag::RequirePremiumToWriteKnown
|
||||
| Flag::MeRequiresPremiumToWrite;
|
||||
| Flag::MessageMoneyRestrictionsKnown
|
||||
| Flag::RequiresPremiumToWrite;
|
||||
user->setFlags((user->flags() & ~mask)
|
||||
| (update.is_phone_calls_private()
|
||||
? Flag::PhoneCallsPrivate
|
||||
|
@ -719,9 +737,9 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
|||
? Flag::VoiceMessagesForbidden
|
||||
: Flag())
|
||||
| (update.is_read_dates_private() ? Flag::ReadDatesPrivate : Flag())
|
||||
| Flag::RequirePremiumToWriteKnown
|
||||
| Flag::MessageMoneyRestrictionsKnown
|
||||
| (update.is_contact_require_premium()
|
||||
? Flag::MeRequiresPremiumToWrite
|
||||
? Flag::RequiresPremiumToWrite
|
||||
: Flag()));
|
||||
user->setIsBlocked(update.is_blocked());
|
||||
user->setCallsStatus(update.is_phone_calls_private()
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "core/stars_amount.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_birthday.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_chat_participant_status.h"
|
||||
|
@ -109,10 +110,11 @@ enum class UserDataFlag : uint32 {
|
|||
StoriesHidden = (1 << 18),
|
||||
HasActiveStories = (1 << 19),
|
||||
HasUnreadStories = (1 << 20),
|
||||
MeRequiresPremiumToWrite = (1 << 21),
|
||||
SomeRequirePremiumToWrite = (1 << 22),
|
||||
RequirePremiumToWriteKnown = (1 << 23),
|
||||
ReadDatesPrivate = (1 << 24),
|
||||
RequiresPremiumToWrite = (1 << 21),
|
||||
HasRequirePremiumToWrite = (1 << 22),
|
||||
HasStarsPerMessage = (1 << 23),
|
||||
MessageMoneyRestrictionsKnown = (1 << 24),
|
||||
ReadDatesPrivate = (1 << 25),
|
||||
};
|
||||
inline constexpr bool is_flag_type(UserDataFlag) { return true; };
|
||||
using UserDataFlags = base::flags<UserDataFlag>;
|
||||
|
@ -173,12 +175,16 @@ public:
|
|||
[[nodiscard]] bool applyMinPhoto() const;
|
||||
[[nodiscard]] bool hasPersonalPhoto() const;
|
||||
[[nodiscard]] bool hasStoriesHidden() const;
|
||||
[[nodiscard]] bool someRequirePremiumToWrite() const;
|
||||
[[nodiscard]] bool meRequiresPremiumToWrite() const;
|
||||
[[nodiscard]] bool requirePremiumToWriteKnown() const;
|
||||
[[nodiscard]] bool canSendIgnoreRequirePremium() const;
|
||||
[[nodiscard]] bool hasRequirePremiumToWrite() const;
|
||||
[[nodiscard]] bool hasStarsPerMessage() const;
|
||||
[[nodiscard]] bool requiresPremiumToWrite() const;
|
||||
[[nodiscard]] bool messageMoneyRestrictionsKnown() const;
|
||||
[[nodiscard]] bool canSendIgnoreMoneyRestrictions() const;
|
||||
[[nodiscard]] bool readDatesPrivate() const;
|
||||
|
||||
void setStarsPerMessage(int stars);
|
||||
[[nodiscard]] int starsPerMessage() const;
|
||||
|
||||
[[nodiscard]] bool canShareThisContact() const;
|
||||
[[nodiscard]] bool canAddContact() const;
|
||||
|
||||
|
@ -268,6 +274,7 @@ private:
|
|||
Data::Birthday _birthday;
|
||||
int _commonChatsCount = 0;
|
||||
int _peerGiftsCount = 0;
|
||||
int _starsPerMessage = 0;
|
||||
ContactStatus _contactStatus = ContactStatus::Unknown;
|
||||
CallsStatus _callsStatus = CallsStatus::Unknown;
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "apiwrap.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_credits.h" // giftBoxByStarsStyle
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
@ -518,8 +519,8 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
|||
Ui::Text::CustomEmojiFactory CustomEmojiManager::factory(
|
||||
SizeTag tag,
|
||||
int sizeOverride) {
|
||||
return [=](QStringView data, Fn<void()> update) {
|
||||
return create(data, std::move(update), tag, sizeOverride);
|
||||
return [=](QStringView data, const Ui::Text::MarkedContext &context) {
|
||||
return create(data, context.repaint, tag, sizeOverride);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1027,6 +1028,14 @@ TextWithEntities CustomEmojiManager::creditsEmoji(QMargins padding) {
|
|||
false));
|
||||
}
|
||||
|
||||
TextWithEntities CustomEmojiManager::ministarEmoji(QMargins padding) {
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
registerInternalEmoji(
|
||||
Ui::GenerateStars(st::giftBoxByStarsStyle.font->height, 1),
|
||||
padding,
|
||||
false));
|
||||
}
|
||||
|
||||
QString CustomEmojiManager::registerInternalEmoji(
|
||||
QImage emoji,
|
||||
QMargins padding,
|
||||
|
@ -1136,8 +1145,9 @@ void InsertCustomEmoji(
|
|||
Ui::Text::CustomEmojiFactory ReactedMenuFactory(
|
||||
not_null<Main::Session*> session) {
|
||||
return [owner = &session->data()](
|
||||
QStringView data,
|
||||
Fn<void()> repaint) -> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
QStringView data,
|
||||
const Ui::Text::MarkedContext &context
|
||||
) -> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
const auto prefix = u"default:"_q;
|
||||
if (data.startsWith(prefix)) {
|
||||
const auto &list = owner->reactions().list(
|
||||
|
@ -1157,13 +1167,13 @@ Ui::Text::CustomEmojiFactory ReactedMenuFactory(
|
|||
std::make_unique<Ui::Text::ShiftedEmoji>(
|
||||
owner->customEmojiManager().create(
|
||||
document,
|
||||
std::move(repaint),
|
||||
context.repaint,
|
||||
tag,
|
||||
size),
|
||||
QPoint(skip, skip)));
|
||||
}
|
||||
}
|
||||
return owner->customEmojiManager().create(data, std::move(repaint));
|
||||
return owner->customEmojiManager().create(data, context.repaint);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@ public:
|
|||
[[nodiscard]] uint64 coloredSetId() const;
|
||||
|
||||
[[nodiscard]] TextWithEntities creditsEmoji(QMargins padding = {});
|
||||
[[nodiscard]] TextWithEntities ministarEmoji(QMargins padding = {});
|
||||
|
||||
private:
|
||||
static constexpr auto kSizeCount = int(SizeTag::kCount);
|
||||
|
|
|
@ -789,7 +789,3 @@ dialogsPopularAppsPadding: margins(10px, 8px, 10px, 12px);
|
|||
dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) {
|
||||
minWidth: 128px;
|
||||
}
|
||||
|
||||
foldersMenu: Menu(menuWithIcons) {
|
||||
itemPadding: margins(54px, 8px, 44px, 8px);
|
||||
}
|
||||
|
|
|
@ -4320,10 +4320,7 @@ QImage *InnerWidget::cacheChatsFilterTag(
|
|||
const auto color = Ui::EmptyUserpic::UserpicColor(colorIndex).color2;
|
||||
entry.context.color = color->c;
|
||||
entry.context.active = active;
|
||||
entry.context.textContext = Core::MarkedTextContext{
|
||||
.session = &session(),
|
||||
.customEmojiRepaint = [] {},
|
||||
};
|
||||
entry.context.textContext = Core::TextContext({ .session = &session() });
|
||||
entry.frame = Ui::ChatsFilterTag(roundedText, entry.context);
|
||||
return &entry.frame;
|
||||
}
|
||||
|
|
|
@ -60,11 +60,10 @@ namespace {
|
|||
st::dialogsSearchTagArrow,
|
||||
st::dialogsSearchTagArrowPadding));
|
||||
auto result = Ui::Text::String();
|
||||
const auto context = Core::MarkedTextContext{
|
||||
const auto context = Core::TextContext({
|
||||
.session = &owner->session(),
|
||||
.customEmojiRepaint = [] {},
|
||||
.customEmojiLoopLimit = 1,
|
||||
};
|
||||
});
|
||||
const auto attempt = [&](const auto &phrase) {
|
||||
result.setMarkedText(
|
||||
st::dialogsSearchTagPromo,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue