Merge tag 'v5.12.1' into dev

This commit is contained in:
AlexeyZavar 2025-03-08 23:02:24 +03:00
commit adc691f516
271 changed files with 7375 additions and 2789 deletions

View file

@ -9,10 +9,7 @@
"--compile-commands-dir=${workspaceFolder}/out" "--compile-commands-dir=${workspaceFolder}/out"
], ],
"cmake.generator": "Ninja Multi-Config", "cmake.generator": "Ninja Multi-Config",
"cmake.buildDirectory": "${workspaceFolder}/out", "cmake.buildDirectory": "${workspaceFolder}/out"
"cmake.configureSettings": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
}, },
"extensions": [ "extensions": [
"ms-vscode.cpptools-extension-pack", "ms-vscode.cpptools-extension-pack",

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

View file

@ -1218,6 +1218,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_privacy_contacts" = "My contacts"; "lng_edit_privacy_contacts" = "My contacts";
"lng_edit_privacy_close_friends" = "Close friends"; "lng_edit_privacy_close_friends" = "Close friends";
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium"; "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_contacts_and_miniapps" = "Contacts & Mini Apps";
"lng_edit_privacy_nobody" = "Nobody"; "lng_edit_privacy_nobody" = "Nobody";
"lng_edit_privacy_premium" = "Premium users"; "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_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" = "Only subscribers of {link} can select this option.";
"lng_messages_privacy_premium_link" = "Telegram Premium"; "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_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."; "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_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_set_chat_intro" = "{from} added the message below for all empty chats. How?";
"lng_action_payment_refunded" = "{peer} refunded {amount}"; "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_title" = "Similar channels";
"lng_similar_channels_view_all" = "View all"; "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_history_entry_inner_in" = "In-App Purchase";
"lng_credits_summary_balance" = "Balance"; "lng_credits_summary_balance" = "Balance";
"lng_credits_commission" = "{amount} commission"; "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_more_options" = "More Options";
"lng_credits_balance_me" = "your balance"; "lng_credits_balance_me" = "your balance";
"lng_credits_buy_button" = "Buy More Stars"; "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_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_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_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_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_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
"lng_credits_enough" = "You have enough stars at the moment. {link}"; "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_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
"lng_gift_premium_features" = "See Features >"; "lng_gift_premium_features" = "See Features >";
"lng_gift_premium_label" = "Premium"; "lng_gift_premium_label" = "Premium";
"lng_gift_premium_by_stars" = "or {amount}";
"lng_gift_stars_subtitle" = "Gift Stars"; "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_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}";
"lng_gift_stars_link" = "What are Stars >"; "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_title" = "Send a Gift";
"lng_gift_send_message" = "Enter Message"; "lng_gift_send_message" = "Enter Message";
"lng_gift_send_anonymous" = "Hide My Name"; "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_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" = "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_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" = "Make Unique for {price}";
"lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}"; "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_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" = "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_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#one" = "You got **{count} Star** for this gift.";
"lng_gift_got_stars#other" = "You got **{count} Stars** 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."; "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_button_for" = "Transfer for {price}";
"lng_gift_transfer_wear" = "Wear"; "lng_gift_transfer_wear" = "Wear";
"lng_gift_transfer_take_off" = "Take Off"; "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_title" = "Wear {name}";
"lng_gift_wear_about" = "and get these benefits:"; "lng_gift_wear_about" = "and get these benefits:";
"lng_gift_wear_badge_title" = "Radiant Badge"; "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_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" = "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_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_channel" = "Response to your join request";
"lng_from_request_title_group" = "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}."; "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_send_anonymous_ph" = "Send anonymously...";
"lng_story_reply_ph" = "Reply privately..."; "lng_story_reply_ph" = "Reply privately...";
"lng_story_comment_ph" = "Comment story..."; "lng_story_comment_ph" = "Comment story...";
"lng_message_paid_ph" = "Message for {amount}";
"lng_send_text_no" = "Text not allowed."; "lng_send_text_no" = "Text not allowed.";
"lng_send_text_no_about" = "The admins of this group only allow sending {types}."; "lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
"lng_send_text_type_and_last" = "{types} and {last}"; "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_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" = "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_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_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."; "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#one" = "{count} second";
"lng_slowmode_seconds#other" = "{count} seconds"; "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_title" = "Broadcast group";
"lng_rights_gigagroup_convert" = "Convert to 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."; "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" = "**{user}** only accepts messages from contacts and {link} subscribers.";
"lng_send_non_premium_message_toast_link" = "Telegram Premium"; "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_exceptions_list_title" = "Exceptions";
"lng_removed_list_title" = "Removed users"; "lng_removed_list_title" = "Removed users";

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.11.1.0" /> Version="5.12.1.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,11,1,0 FILEVERSION 5,12,1,0
PRODUCTVERSION 5,11,1,0 PRODUCTVERSION 5,12,1,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.11.1.0" VALUE "FileVersion", "5.12.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.11.1.0" VALUE "ProductVersion", "5.12.1.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,11,1,0 FILEVERSION 5,12,1,0
PRODUCTVERSION 5,11,1,0 PRODUCTVERSION 5,12,1,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "5.11.1.0" VALUE "FileVersion", "5.12.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.11.1.0" VALUE "ProductVersion", "5.12.1.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -149,18 +149,14 @@ void InitFilterLinkHeader(
iconEmoji iconEmoji
).value_or(Ui::FilterIcon::Custom)).active; ).value_or(Ui::FilterIcon::Custom)).active;
const auto isStatic = title.isStatic; 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, { auto header = Ui::MakeFilterLinkHeader(box, {
.type = type, .type = type,
.title = TitleText(type)(tr::now), .title = TitleText(type)(tr::now),
.about = AboutText(type, title.text), .about = AboutText(type, title.text),
.makeAboutContext = makeContext, .aboutContext = Core::TextContext({
.session = &box->peerListUiShow()->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
.folderTitle = title.text, .folderTitle = title.text,
.folderIcon = icon, .folderIcon = icon,
.badge = (type == Ui::FilterLinkHeaderType::AddingChats .badge = (type == Ui::FilterLinkHeaderType::AddingChats
@ -560,16 +556,12 @@ void ShowImportToast(
text.append('\n').append(phrase(tr::now, lt_count, added)); text.append('\n').append(phrase(tr::now, lt_count, added));
} }
const auto isStatic = title.isStatic; 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({ strong->showToast({
.text = std::move(text), .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()); raw->setRealContentHeight(box->heightValue());
const auto isStatic = title.isStatic; 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( auto owned = Ui::FilterLinkProcessButton(
box, box,
type, type,
title.text, title.text,
makeContext, Core::TextContext({
.session = &strong->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
std::move(badge)); std::move(badge));
const auto button = owned.data(); const auto button = owned.data();
@ -873,18 +861,14 @@ void ProcessFilterRemove(
}, type, title, iconEmoji, rpl::single(0), horizontalFilters); }, type, title, iconEmoji, rpl::single(0), horizontalFilters);
const auto isStatic = title.isStatic; 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( auto owned = Ui::FilterLinkProcessButton(
box, box,
type, type,
title.text, title.text,
makeContext, Core::TextContext({
.session = &strong->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
std::move(badge)); std::move(badge));
const auto button = owned.data(); const auto button = owned.data();

View file

@ -25,6 +25,7 @@ struct SendOptions {
TimeId scheduled = 0; TimeId scheduled = 0;
BusinessShortcutId shortcutId = 0; BusinessShortcutId shortcutId = 0;
EffectId effectId = 0; EffectId effectId = 0;
int starsApproved = 0;
bool silent = false; bool silent = false;
bool handleSupportSwitch = false; bool handleSupportSwitch = false;
bool invertCaption = false; bool invertCaption = false;

View file

@ -90,7 +90,13 @@ constexpr auto kTransactionsLimit = 100;
? peerFromMTP(*tl.data().vstarref_peer()).value ? peerFromMTP(*tl.data().vstarref_peer()).value
: 0; : 0;
const auto incoming = (amount >= StarsAmount()); 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 const auto parsedGift = stargift
? FromTL(&peer->session(), *stargift) ? FromTL(&peer->session(), *stargift)
: std::optional<Data::StarGift>(); : std::optional<Data::StarGift>();
@ -110,9 +116,9 @@ constexpr auto kTransactionsLimit = 100;
.bareGiftStickerId = giftStickerId, .bareGiftStickerId = giftStickerId,
.bareActorId = saveActorId ? barePeerId : uint64(0), .bareActorId = saveActorId ? barePeerId : uint64(0),
.uniqueGift = parsedGift ? parsedGift->unique : nullptr, .uniqueGift = parsedGift ? parsedGift->unique : nullptr,
.starrefAmount = starrefAmount, .starrefAmount = paidMessagesCount ? StarsAmount() : starrefAmount,
.starrefCommission = starrefCommission, .starrefCommission = paidMessagesCount ? 0 : starrefCommission,
.starrefRecipientId = starrefBarePeerId, .starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId,
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) { .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer; return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) { }, [](const MTPDstarsTransactionPeerPlayMarket &) {
@ -138,9 +144,15 @@ constexpr auto kTransactionsLimit = 100;
? base::unixtime::parse(tl.data().vtransaction_date()->v) ? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(), : QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()), .successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.paidMessagesCount = paidMessagesCount,
.paidMessagesAmount = (paidMessagesCount
? starrefAmount
: StarsAmount()),
.paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
.starsConverted = int(nonUniqueGift .starsConverted = int(nonUniqueGift
? nonUniqueGift->vconvert_stars().v ? nonUniqueGift->vconvert_stars().v
: 0), : 0),
.premiumMonthsForStars = premiumMonthsForStars,
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)), .floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
.converted = stargift && incoming, .converted = stargift && incoming,
.stargift = stargift.has_value(), .stargift = stargift.has_value(),

View file

@ -114,7 +114,8 @@ void GlobalPrivacy::updateHideReadTime(bool hide) {
archiveAndMuteCurrent(), archiveAndMuteCurrent(),
unarchiveOnNewMessageCurrent(), unarchiveOnNewMessageCurrent(),
hide, hide,
newRequirePremiumCurrent()); newRequirePremiumCurrent(),
newChargeStarsCurrent());
} }
bool GlobalPrivacy::hideReadTimeCurrent() const { bool GlobalPrivacy::hideReadTimeCurrent() const {
@ -125,14 +126,6 @@ rpl::producer<bool> GlobalPrivacy::hideReadTime() const {
return _hideReadTime.value(); return _hideReadTime.value();
} }
void GlobalPrivacy::updateNewRequirePremium(bool value) {
update(
archiveAndMuteCurrent(),
unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(),
value);
}
bool GlobalPrivacy::newRequirePremiumCurrent() const { bool GlobalPrivacy::newRequirePremiumCurrent() const {
return _newRequirePremium.current(); return _newRequirePremium.current();
} }
@ -141,6 +134,25 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
return _newRequirePremium.value(); 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() { void GlobalPrivacy::loadPaidReactionShownPeer() {
if (_paidReactionShownPeerLoaded) { if (_paidReactionShownPeerLoaded) {
return; return;
@ -169,7 +181,8 @@ void GlobalPrivacy::updateArchiveAndMute(bool value) {
value, value,
unarchiveOnNewMessageCurrent(), unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(), hideReadTimeCurrent(),
newRequirePremiumCurrent()); newRequirePremiumCurrent(),
newChargeStarsCurrent());
} }
void GlobalPrivacy::updateUnarchiveOnNewMessage( void GlobalPrivacy::updateUnarchiveOnNewMessage(
@ -178,14 +191,16 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage(
archiveAndMuteCurrent(), archiveAndMuteCurrent(),
value, value,
hideReadTimeCurrent(), hideReadTimeCurrent(),
newRequirePremiumCurrent()); newRequirePremiumCurrent(),
newChargeStarsCurrent());
} }
void GlobalPrivacy::update( void GlobalPrivacy::update(
bool archiveAndMute, bool archiveAndMute,
UnarchiveOnNewMessage unarchiveOnNewMessage, UnarchiveOnNewMessage unarchiveOnNewMessage,
bool hideReadTime, bool hideReadTime,
bool newRequirePremium) { bool newRequirePremium,
int newChargeStars) {
using Flag = MTPDglobalPrivacySettings::Flag; using Flag = MTPDglobalPrivacySettings::Flag;
_api.request(_requestId).cancel(); _api.request(_requestId).cancel();
@ -204,35 +219,44 @@ void GlobalPrivacy::update(
| (hideReadTime ? Flag::f_hide_read_marks : Flag()) | (hideReadTime ? Flag::f_hide_read_marks : Flag())
| ((newRequirePremium && newRequirePremiumAllowed) | ((newRequirePremium && newRequirePremiumAllowed)
? Flag::f_new_noncontact_peers_require_premium ? Flag::f_new_noncontact_peers_require_premium
: Flag()); : Flag())
| Flag::f_noncontact_peers_paid_stars;
_requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings(
MTP_globalPrivacySettings(MTP_flags(flags)) MTP_globalPrivacySettings(
MTP_flags(flags),
MTP_long(newChargeStars))
)).done([=](const MTPGlobalPrivacySettings &result) { )).done([=](const MTPGlobalPrivacySettings &result) {
_requestId = 0; _requestId = 0;
apply(result); apply(result);
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_requestId = 0; _requestId = 0;
if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) { if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {}); update(
archiveAndMute,
unarchiveOnNewMessage,
hideReadTime,
false,
0);
} }
}).send(); }).send();
_archiveAndMute = archiveAndMute; _archiveAndMute = archiveAndMute;
_unarchiveOnNewMessage = unarchiveOnNewMessage; _unarchiveOnNewMessage = unarchiveOnNewMessage;
_hideReadTime = hideReadTime; _hideReadTime = hideReadTime;
_newRequirePremium = newRequirePremium; _newRequirePremium = newRequirePremium;
_newChargeStars = newChargeStars;
} }
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) {
data.match([&](const MTPDglobalPrivacySettings &data) { const auto &data = settings.data();
_archiveAndMute = data.is_archive_and_mute_new_noncontact_peers(); _archiveAndMute = data.is_archive_and_mute_new_noncontact_peers();
_unarchiveOnNewMessage = data.is_keep_archived_unmuted() _unarchiveOnNewMessage = data.is_keep_archived_unmuted()
? UnarchiveOnNewMessage::None ? UnarchiveOnNewMessage::None
: data.is_keep_archived_folders() : data.is_keep_archived_folders()
? UnarchiveOnNewMessage::NotInFoldersUnmuted ? UnarchiveOnNewMessage::NotInFoldersUnmuted
: UnarchiveOnNewMessage::AnyUnmuted; : UnarchiveOnNewMessage::AnyUnmuted;
_hideReadTime = data.is_hide_read_marks(); _hideReadTime = data.is_hide_read_marks();
_newRequirePremium = data.is_new_noncontact_peers_require_premium(); _newRequirePremium = data.is_new_noncontact_peers_require_premium();
}); _newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty();
} }
} // namespace Api } // namespace Api

View file

@ -49,23 +49,28 @@ public:
[[nodiscard]] bool hideReadTimeCurrent() const; [[nodiscard]] bool hideReadTimeCurrent() const;
[[nodiscard]] rpl::producer<bool> hideReadTime() const; [[nodiscard]] rpl::producer<bool> hideReadTime() const;
void updateNewRequirePremium(bool value);
[[nodiscard]] bool newRequirePremiumCurrent() const; [[nodiscard]] bool newRequirePremiumCurrent() const;
[[nodiscard]] rpl::producer<bool> newRequirePremium() 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 loadPaidReactionShownPeer();
void updatePaidReactionShownPeer(PeerId shownPeer); void updatePaidReactionShownPeer(PeerId shownPeer);
[[nodiscard]] PeerId paidReactionShownPeerCurrent() const; [[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
[[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const; [[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const;
private: private:
void apply(const MTPGlobalPrivacySettings &data); void apply(const MTPGlobalPrivacySettings &settings);
void update( void update(
bool archiveAndMute, bool archiveAndMute,
UnarchiveOnNewMessage unarchiveOnNewMessage, UnarchiveOnNewMessage unarchiveOnNewMessage,
bool hideReadTime, bool hideReadTime,
bool newRequirePremium); bool newRequirePremium,
int newChargeStars);
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
MTP::Sender _api; MTP::Sender _api;
@ -76,6 +81,7 @@ private:
rpl::variable<bool> _showArchiveAndMute = false; rpl::variable<bool> _showArchiveAndMute = false;
rpl::variable<bool> _hideReadTime = false; rpl::variable<bool> _hideReadTime = false;
rpl::variable<bool> _newRequirePremium = false; rpl::variable<bool> _newRequirePremium = false;
rpl::variable<int> _newChargeStars = 0;
rpl::variable<PeerId> _paidReactionShownPeer = false; rpl::variable<PeerId> _paidReactionShownPeer = false;
std::vector<Fn<void()>> _callbacks; std::vector<Fn<void()>> _callbacks;
bool _paidReactionShownPeerLoaded = false; bool _paidReactionShownPeerLoaded = false;

View file

@ -42,7 +42,7 @@ Polls::Polls(not_null<ApiWrap*> api)
void Polls::create( void Polls::create(
const PollData &data, const PollData &data,
const SendAction &action, SendAction action,
Fn<void()> done, Fn<void()> done,
Fn<void()> fail) { Fn<void()> fail) {
_session->api().sendAction(action); _session->api().sendAction(action);
@ -64,6 +64,9 @@ void Polls::create(
history->startSavingCloudDraft(topicRootId); history->startSavingCloudDraft(topicRootId);
} }
const auto silentPost = ShouldSendSilent(peer, action.options); const auto silentPost = ShouldSendSilent(peer, action.options);
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (silentPost) { if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent; sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
} }
@ -76,6 +79,10 @@ void Polls::create(
if (action.options.effectId) { if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect; 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; const auto sendAs = action.options.sendAs;
if (sendAs) { if (sendAs) {
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
@ -98,7 +105,8 @@ void Polls::create(
MTP_int(action.options.scheduled), MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId), 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 MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) { if (clearCloudDraft) {
history->finishSavingCloudDraft( history->finishSavingCloudDraft(

View file

@ -27,7 +27,7 @@ public:
void create( void create(
const PollData &data, const PollData &data,
const SendAction &action, SendAction action,
Fn<void()> done, Fn<void()> done,
Fn<void()> fail); Fn<void()> fail);
void sendVotes( void sendVotes(

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "base/random.h" #include "base/random.h"
#include "data/data_channel.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
@ -377,15 +378,15 @@ const Data::PremiumSubscriptionOptions &Premium::subscriptionOptions() const {
return _subscriptionOptions; return _subscriptionOptions;
} }
rpl::producer<> Premium::somePremiumRequiredResolved() const { rpl::producer<> Premium::someMessageMoneyRestrictionsResolved() const {
return _somePremiumRequiredResolved.events(); return _someMessageMoneyRestrictionsResolved.events();
} }
void Premium::resolvePremiumRequired(not_null<UserData*> user) { void Premium::resolveMessageMoneyRestrictions(not_null<UserData*> user) {
_resolvePremiumRequiredUsers.emplace(user); _resolveMessageMoneyRequiredUsers.emplace(user);
if (!_premiumRequiredRequestScheduled if (!_messageMoneyRequestScheduled
&& _resolvePremiumRequestedUsers.empty()) { && _resolveMessageMoneyRequestedUsers.empty()) {
_premiumRequiredRequestScheduled = true; _messageMoneyRequestScheduled = true;
crl::on_main(_session, [=] { crl::on_main(_session, [=] {
requestPremiumRequiredSlice(); requestPremiumRequiredSlice();
}); });
@ -393,50 +394,65 @@ void Premium::resolvePremiumRequired(not_null<UserData*> user) {
} }
void Premium::requestPremiumRequiredSlice() { void Premium::requestPremiumRequiredSlice() {
_premiumRequiredRequestScheduled = false; _messageMoneyRequestScheduled = false;
if (!_resolvePremiumRequestedUsers.empty() if (!_resolveMessageMoneyRequestedUsers.empty()
|| _resolvePremiumRequiredUsers.empty()) { || _resolveMessageMoneyRequiredUsers.empty()) {
return; return;
} }
constexpr auto kPerRequest = 100; constexpr auto kPerRequest = 100;
auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers auto users = MTP_vector_from_range(_resolveMessageMoneyRequiredUsers
| ranges::views::transform(&UserData::inputUser)); | ranges::views::transform(&UserData::inputUser));
if (users.v.size() > kPerRequest) { if (users.v.size() > kPerRequest) {
auto shortened = users.v; auto shortened = users.v;
shortened.resize(kPerRequest); shortened.resize(kPerRequest);
users = MTP_vector<MTPInputUser>(std::move(shortened)); users = MTP_vector<MTPInputUser>(std::move(shortened));
const auto from = begin(_resolvePremiumRequiredUsers); const auto from = begin(_resolveMessageMoneyRequiredUsers);
_resolvePremiumRequestedUsers = { from, from + kPerRequest }; _resolveMessageMoneyRequestedUsers = { from, from + kPerRequest };
_resolvePremiumRequiredUsers.erase(from, from + kPerRequest); _resolveMessageMoneyRequiredUsers.erase(from, from + kPerRequest);
} else { } else {
_resolvePremiumRequestedUsers _resolveMessageMoneyRequestedUsers
= base::take(_resolvePremiumRequiredUsers); = base::take(_resolveMessageMoneyRequiredUsers);
} }
const auto finish = [=](const QVector<MTPBool> &list) { const auto finish = [=](const QVector<MTPRequirementToContact> &list) {
constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite;
constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown;
constexpr auto mask = me | known;
auto index = 0; auto index = 0;
for (const auto &user : base::take(_resolvePremiumRequestedUsers)) { for (const auto &user : base::take(_resolveMessageMoneyRequestedUsers)) {
const auto require = (index < list.size()) const auto set = [&](bool requirePremium, int stars) {
&& mtpIsTrue(list[index++]); using Flag = UserDataFlag;
user->setFlags((user->flags() & ~mask) constexpr auto me = Flag::RequiresPremiumToWrite;
| known constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
| (require ? me : UserDataFlag())); 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 if (!_messageMoneyRequestScheduled
&& !_resolvePremiumRequiredUsers.empty()) { && !_resolveMessageMoneyRequiredUsers.empty()) {
_premiumRequiredRequestScheduled = true; _messageMoneyRequestScheduled = true;
crl::on_main(_session, [=] { crl::on_main(_session, [=] {
requestPremiumRequiredSlice(); requestPremiumRequiredSlice();
}); });
} }
_somePremiumRequiredResolved.fire({}); _someMessageMoneyRestrictionsResolved.fire({});
}; };
_session->api().request( _session->api().request(
MTPusers_GetIsPremiumRequiredToContact(std::move(users)) MTPusers_GetRequirementsToContact(std::move(users))
).done([=](const MTPVector<MTPBool> &result) { ).done([=](const MTPVector<MTPRequirementToContact> &result) {
finish(result.v); finish(result.v);
}).fail([=] { }).fail([=] {
finish({}); finish({});
@ -463,10 +479,14 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
for (const auto &tlOption : result.v) { for (const auto &tlOption : result.v) {
const auto &data = tlOption.data(); const auto &data = tlOption.data();
tlMapOptions[data.vusers().v].push_back(tlOption); tlMapOptions[data.vusers().v].push_back(tlOption);
if (qs(data.vcurrency()) == Ui::kCreditsCurrency) {
continue;
}
const auto token = Token{ data.vusers().v, data.vmonths().v }; const auto token = Token{ data.vusers().v, data.vmonths().v };
_stores[token] = Store{ _stores[token] = Store{
.amount = data.vamount().v, .amount = data.vamount().v,
.currency = qs(data.vcurrency()),
.product = qs(data.vstore_product().value_or_empty()), .product = qs(data.vstore_product().value_or_empty()),
.quantity = data.vstore_quantity().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) { for (const auto &[amount, tlOptions] : tlMapOptions) {
if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) { if (amount == 1 && _optionsForOnePerson.currencies.empty()) {
_optionsForOnePerson.currency = qs(
tlOptions.front().data().vcurrency());
for (const auto &option : tlOptions) { for (const auto &option : tlOptions) {
_optionsForOnePerson.months.push_back( _optionsForOnePerson.months.push_back(
option.data().vmonths().v); option.data().vmonths().v);
_optionsForOnePerson.totalCosts.push_back( _optionsForOnePerson.totalCosts.push_back(
option.data().vamount().v); option.data().vamount().v);
_optionsForOnePerson.currencies.push_back(
qs(option.data().vcurrency()));
} }
} }
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
@ -509,7 +529,7 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
_api.request(MTPpayments_LaunchPrepaidGiveaway( _api.request(MTPpayments_LaunchPrepaidGiveaway(
_peer->input, _peer->input,
MTP_long(prepaidId), MTP_long(prepaidId),
invoice.creditsAmount invoice.giveawayCredits
? Payments::InvoiceCreditsGiveawayToTL(invoice) ? Payments::InvoiceCreditsGiveawayToTL(invoice)
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice) : Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
@ -540,7 +560,7 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
const auto token = Token{ users, months }; const auto token = Token{ users, months };
const auto &store = _stores[token]; const auto &store = _stores[token];
return Payments::InvoicePremiumGiftCode{ return Payments::InvoicePremiumGiftCode{
.currency = _optionsForOnePerson.currency, .currency = store.currency,
.storeProduct = store.product, .storeProduct = store.product,
.randomId = randomId, .randomId = randomId,
.amount = store.amount, .amount = store.amount,
@ -553,14 +573,15 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const { std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
auto result = std::vector<GiftOptionData>(); auto result = std::vector<GiftOptionData>();
if (!_optionsForOnePerson.currency.isEmpty()) { if (!_optionsForOnePerson.currencies.empty()) {
const auto count = int(_optionsForOnePerson.months.size()); const auto count = int(_optionsForOnePerson.months.size());
result.reserve(count); result.reserve(count);
for (auto i = 0; i != count; ++i) { for (auto i = 0; i != count; ++i) {
Assert(i < _optionsForOnePerson.totalCosts.size()); Assert(i < _optionsForOnePerson.totalCosts.size());
Assert(i < _optionsForOnePerson.currencies.size());
result.push_back({ result.push_back({
.cost = _optionsForOnePerson.totalCosts[i], .cost = _optionsForOnePerson.totalCosts[i],
.currency = _optionsForOnePerson.currency, .currency = _optionsForOnePerson.currencies[i],
.months = _optionsForOnePerson.months[i], .months = _optionsForOnePerson.months[i],
}); });
} }
@ -581,7 +602,7 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
MTP_int(_optionsForOnePerson.months[i]), MTP_int(_optionsForOnePerson.months[i]),
MTPstring(), MTPstring(),
MTPint(), MTPint(),
MTP_string(_optionsForOnePerson.currency), MTP_string(_optionsForOnePerson.currencies[i]),
MTP_long(_optionsForOnePerson.totalCosts[i] * amount))); MTP_long(_optionsForOnePerson.totalCosts[i] * amount)));
} }
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); _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, not_null<PeerData*> peer,
History *maybeHistory) { History *maybeHistory) {
const auto user = peer->asUser(); if (const auto channel = peer->asChannel()) {
if (!user return {
|| !user->someRequirePremiumToWrite() .starsPerMessage = channel->starsPerMessageChecked(),
|| user->session().premium()) { .known = true,
return RequirePremiumState::No; };
} else if (user->requirePremiumToWriteKnown()) { }
return user->meRequiresPremiumToWrite() const auto user = peer->asUser();
? RequirePremiumState::Yes if (!user) {
: RequirePremiumState::No; return { .known = true };
} else if (user->flags() & UserDataFlag::MutualContact) { } else if (user->messageMoneyRestrictionsKnown()) {
return RequirePremiumState::No; return {
} else if (!maybeHistory) { .starsPerMessage = user->starsPerMessageChecked(),
return RequirePremiumState::Unknown; .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) { const auto update = [&](bool require) {
using Flag = UserDataFlag; using Flag = UserDataFlag;
constexpr auto known = Flag::RequirePremiumToWriteKnown; constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
constexpr auto me = Flag::MeRequiresPremiumToWrite; constexpr auto me = Flag::RequiresPremiumToWrite;
user->setFlags((user->flags() & ~me) user->setFlags((user->flags() & ~me)
| known | known
| (require ? me : Flag())); | (require ? me : Flag()));
@ -727,16 +758,19 @@ RequirePremiumState ResolveRequiresPremiumToWrite(
const auto item = view->data(); const auto item = view->data();
if (!item->out() && !item->isService()) { if (!item->out() && !item->isService()) {
update(false); update(false);
return RequirePremiumState::No; return { .known = true };
} }
} }
} }
if (user->isContact() // Here we know, that we're not in his contacts. if (user->isContact() // Here we know, that we're not in his contacts.
&& maybeHistory->loadedAtTop() // And no incoming messages. && maybeHistory->loadedAtTop() // And no incoming messages.
&& maybeHistory->loadedAtBottom()) { && maybeHistory->loadedAtBottom()) {
update(true); return {
.premiumRequired = !user->session().premium(),
.known = true,
};
} }
return RequirePremiumState::Unknown; return {};
} }
rpl::producer<DocumentData*> RandomHelloStickerValue( rpl::producer<DocumentData*> RandomHelloStickerValue(
@ -870,6 +904,7 @@ std::optional<Data::SavedStarGift> FromTL(
.date = data.vdate().v, .date = data.vdate().v,
.upgradable = data.is_can_upgrade(), .upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(), .anonymous = data.is_name_hidden(),
.pinned = data.is_pinned_to_top(),
.hidden = data.is_unsaved(), .hidden = data.is_unsaved(),
.mine = to->isSelf(), .mine = to->isSelf(),
}; };

View file

@ -116,8 +116,9 @@ public:
[[nodiscard]] auto subscriptionOptions() const [[nodiscard]] auto subscriptionOptions() const
-> const Data::PremiumSubscriptionOptions &; -> const Data::PremiumSubscriptionOptions &;
[[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const; [[nodiscard]] auto someMessageMoneyRestrictionsResolved() const
void resolvePremiumRequired(not_null<UserData*> user); -> rpl::producer<>;
void resolveMessageMoneyRestrictions(not_null<UserData*> user);
private: private:
void reloadPromo(); void reloadPromo();
@ -166,10 +167,10 @@ private:
Data::PremiumSubscriptionOptions _subscriptionOptions; Data::PremiumSubscriptionOptions _subscriptionOptions;
rpl::event_stream<> _somePremiumRequiredResolved; rpl::event_stream<> _someMessageMoneyRestrictionsResolved;
base::flat_set<not_null<UserData*>> _resolvePremiumRequiredUsers; base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequiredUsers;
base::flat_set<not_null<UserData*>> _resolvePremiumRequestedUsers; base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequestedUsers;
bool _premiumRequiredRequestScheduled = false; bool _messageMoneyRequestScheduled = false;
}; };
@ -208,6 +209,7 @@ private:
}; };
struct Store final { struct Store final {
uint64 amount = 0; uint64 amount = 0;
QString currency;
QString product; QString product;
int quantity = 0; int quantity = 0;
}; };
@ -218,7 +220,7 @@ private:
struct { struct {
std::vector<int> months; std::vector<int> months;
std::vector<int64> totalCosts; std::vector<int64> totalCosts;
QString currency; std::vector<QString> currencies;
} _optionsForOnePerson; } _optionsForOnePerson;
std::vector<int> _availablePresets; std::vector<int> _availablePresets;
@ -244,12 +246,20 @@ private:
}; };
enum class RequirePremiumState { struct MessageMoneyRestriction {
Unknown, int starsPerMessage = 0;
Yes, bool premiumRequired = false;
No, 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, not_null<PeerData*> peer,
History *maybeHistory); History *maybeHistory);

View file

@ -26,7 +26,7 @@ Data::PremiumSubscriptionOption CreateSubscriptionOption(
}(); }();
return { return {
.duration = Ui::FormatTTL(months * 86400 * 31), .duration = Ui::FormatTTL(months * 86400 * 31),
.discount = discount .discount = (discount > 0)
? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount) ? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount)
: QString(), : QString(),
.costPerMonth = Ui::FillAmountAndCurrency( .costPerMonth = Ui::FillAmountAndCurrency(

View file

@ -24,15 +24,26 @@ template<typename Option>
if (tlOpts.isEmpty()) { if (tlOpts.isEmpty()) {
return {}; return {};
} }
auto monthlyAmountPerCurrency = base::flat_map<QString, int>();
auto result = Data::PremiumSubscriptionOptions(); auto result = Data::PremiumSubscriptionOptions();
const auto monthlyAmount = [&] { const auto monthlyAmount = [&](const QString &currency) -> int {
const auto it = monthlyAmountPerCurrency.find(currency);
if (it != end(monthlyAmountPerCurrency)) {
return it->second;
}
const auto &min = ranges::min_element( const auto &min = ranges::min_element(
tlOpts, tlOpts,
ranges::less(), 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(); )->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()); result.reserve(tlOpts.size());
for (const auto &tlOption : tlOpts) { for (const auto &tlOption : tlOpts) {
const auto &option = tlOption.data(); const auto &option = tlOption.data();
@ -45,7 +56,7 @@ template<typename Option>
const auto currency = qs(option.vcurrency()); const auto currency = qs(option.vcurrency());
result.push_back(CreateSubscriptionOption( result.push_back(CreateSubscriptionOption(
months, months,
monthlyAmount, monthlyAmount(currency),
amount, amount,
currency, currency,
botUrl)); botUrl));

View file

@ -95,7 +95,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
const auto messagePostAuthor = peer->isBroadcast() const auto messagePostAuthor = peer->isBroadcast()
? session->user()->name() ? session->user()->name()
: QString(); : QString();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (action.options.scheduled) { if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled; flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
@ -111,6 +113,10 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
flags |= MessageFlag::InvertMedia; flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; 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(); auto &histories = history->owner().histories();
histories.sendPreparedMessage( histories.sendPreparedMessage(
@ -129,7 +135,8 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
MTP_int(action.options.scheduled), MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId), 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 MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId); api->sendMessageFail(error, peer, randomId);
@ -160,7 +167,7 @@ void SendExistingMedia(
? (*localMessageId) ? (*localMessageId)
: session->data().nextLocalMessageId()); : session->data().nextLocalMessageId());
const auto randomId = base::RandomValue<uint64>(); const auto randomId = base::RandomValue<uint64>();
const auto &action = message.action; auto &action = message.action;
auto flags = NewMessageFlags(peer); auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMedia::Flags(0); auto sendFlags = MTPmessages_SendMedia::Flags(0);
@ -190,7 +197,9 @@ void SendExistingMedia(
sendFlags |= MTPmessages_SendMedia::Flag::f_entities; sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
} }
const auto captionText = caption.text; const auto captionText = caption.text;
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (action.options.scheduled) { if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled; flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
@ -206,6 +215,10 @@ void SendExistingMedia(
flags |= MessageFlag::InvertMedia; flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; 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); session->data().registerMessageRandomId(randomId, newId);
@ -216,6 +229,7 @@ void SendExistingMedia(
.replyTo = action.replyTo, .replyTo = action.replyTo,
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId, .effectId = action.options.effectId,
}, media, caption); }, media, caption);
@ -240,7 +254,8 @@ void SendExistingMedia(
MTP_int(action.options.scheduled), MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId), 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 MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) {
if (error.code() == 400 if (error.code() == 400
@ -341,7 +356,7 @@ bool SendDice(MessageToSend &message) {
message.action.generateLocal = true; message.action.generateLocal = true;
const auto &action = message.action; auto &action = message.action;
api->sendAction(action); api->sendAction(action);
const auto newId = FullMsgId( const auto newId = FullMsgId(
@ -380,6 +395,13 @@ bool SendDice(MessageToSend &message) {
flags |= MessageFlag::InvertMedia; flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; 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); session->data().registerMessageRandomId(randomId, newId);
@ -390,6 +412,7 @@ bool SendDice(MessageToSend &message) {
.replyTo = action.replyTo, .replyTo = action.replyTo,
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId, .effectId = action.options.effectId,
}, TextWithEntities(), MTP_messageMediaDice( }, TextWithEntities(), MTP_messageMediaDice(
@ -411,7 +434,8 @@ bool SendDice(MessageToSend &message) {
MTP_int(action.options.scheduled), MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId), 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 MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId, newId); api->sendMessageFail(error, peer, randomId, newId);
@ -610,6 +634,9 @@ void SendConfirmedFile(
.replyTo = file->to.replyTo, .replyTo = file->to.replyTo,
.date = NewMessageDate(file->to.options), .date = NewMessageDate(file->to.options),
.shortcutId = file->to.options.shortcutId, .shortcutId = file->to.options.shortcutId,
.starsPaid = std::min(
history->peer->starsPerMessageChecked(),
file->to.options.starsApproved),
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
.groupedId = groupId, .groupedId = groupId,
.effectId = file->to.options.effectId, .effectId = file->to.options.effectId,

View file

@ -1227,7 +1227,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTPint(), // quick_reply_shortcut_id MTPint(), // quick_reply_shortcut_id
MTPlong(), // effect MTPlong(), // effect
MTPFactCheck(), MTPFactCheck(),
MTPint()), // report_delivery_until_date MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
MessageFlags(), MessageFlags(),
NewMessageType::Unread); NewMessageType::Unread);
} break; } break;
@ -1265,7 +1266,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTPint(), // quick_reply_shortcut_id MTPint(), // quick_reply_shortcut_id
MTPlong(), // effect MTPlong(), // effect
MTPFactCheck(), MTPFactCheck(),
MTPint()), // report_delivery_until_date MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
MessageFlags(), MessageFlags(),
NewMessageType::Unread); NewMessageType::Unread);
} break; } break;

View file

@ -210,6 +210,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
case Key::About: return MTP_inputPrivacyKeyAbout(); case Key::About: return MTP_inputPrivacyKeyAbout();
case Key::Birthday: return MTP_inputPrivacyKeyBirthday(); case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave(); case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages();
} }
Unexpected("Key in Api::UserPrivacy::KetToTL."); 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_inputPrivacyKeyBirthday: return Key::Birthday;
case mtpc_privacyKeyStarGiftsAutoSave: case mtpc_privacyKeyStarGiftsAutoSave:
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave; case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
case mtpc_privacyKeyNoPaidMessages:
case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages;
} }
return std::nullopt; return std::nullopt;
} }

View file

@ -32,6 +32,7 @@ public:
About, About,
Birthday, Birthday,
GiftsAutoSave, GiftsAutoSave,
NoPaidMessages,
}; };
enum class Option { enum class Option {
Everyone, Everyone,

View file

@ -546,6 +546,7 @@ void ApiWrap::sendMessageFail(
uint64 randomId, uint64 randomId,
FullMsgId itemId) { FullMsgId itemId) {
const auto show = ShowForPeer(peer); const auto show = ShowForPeer(peer);
const auto paidStarsPrefix = u"ALLOW_PAYMENT_REQUIRED_"_q;
if (show && error == u"PEER_FLOOD"_q) { if (show && error == u"PEER_FLOOD"_q) {
show->showBox( show->showBox(
Ui::MakeInformBox( Ui::MakeInformBox(
@ -600,6 +601,19 @@ void ApiWrap::sendMessageFail(
if (show) { if (show) {
show->showToast(tr::lng_error_schedule_limit(tr::now)); 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)) { if (const auto item = _session->data().message(itemId)) {
Assert(randomId != 0); Assert(randomId != 0);
@ -3342,7 +3356,7 @@ void ApiWrap::finishForwarding(const SendAction &action) {
void ApiWrap::forwardMessages( void ApiWrap::forwardMessages(
Data::ResolvedForwardDraft &&draft, Data::ResolvedForwardDraft &&draft,
const SendAction &action, SendAction action,
FnMut<void()> &&successCallback) { FnMut<void()> &&successCallback) {
Expects(!draft.items.empty()); Expects(!draft.items.empty());
@ -3417,9 +3431,17 @@ void ApiWrap::forwardMessages(
const auto requestType = Data::Histories::RequestType::Send; const auto requestType = Data::Histories::RequestType::Send;
const auto idsCopy = localIds; const auto idsCopy = localIds;
const auto scheduled = action.options.scheduled; 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) { histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_ForwardMessages( history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags), MTP_flags(oneFlags),
forwardFrom->input, forwardFrom->input,
MTP_vector<MTPint>(ids), MTP_vector<MTPint>(ids),
MTP_vector<MTPlong>(randomIds), MTP_vector<MTPlong>(randomIds),
@ -3428,7 +3450,8 @@ void ApiWrap::forwardMessages(
MTP_int(action.options.scheduled), MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId), Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTPint() // video_timestamp MTPint(), // video_timestamp
MTP_long(starsPaid)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
if (!scheduled) { if (!scheduled) {
this->updates().checkForSentToScheduled(result); this->updates().checkForSentToScheduled(result);
@ -3480,6 +3503,7 @@ void ApiWrap::forwardMessages(
.replyTo = { .topicRootId = topMsgId }, .replyTo = { .topicRootId = topMsgId },
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = action.options.starsApproved,
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
// forwarded messages don't have effects // forwarded messages don't have effects
@ -3573,6 +3597,7 @@ void ApiWrap::sendSharedContact(
.replyTo = action.replyTo, .replyTo = action.replyTo,
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = action.options.starsApproved,
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId, .effectId = action.options.effectId,
}, TextWithEntities(), MTP_messageMediaContact( }, TextWithEntities(), MTP_messageMediaContact(
@ -3944,6 +3969,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sendFlags |= MTPmessages_SendMessage::Flag::f_effect; sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
mediaFlags |= MTPmessages_SendMedia::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({ lastMessage = history->addNewLocalMessage({
.id = newId.msg, .id = newId.msg,
.flags = flags, .flags = flags,
@ -3951,6 +3984,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
.replyTo = action.replyTo, .replyTo = action.replyTo,
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId, .effectId = action.options.effectId,
}, sending, media); }, sending, media);
@ -4001,7 +4035,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTP_int(action.options.scheduled), MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
mtpShortcut, mtpShortcut,
MTP_long(action.options.effectId) MTP_long(action.options.effectId),
MTP_long(starsPaid)
), done, fail); ), done, fail);
} else { } else {
histories.sendPreparedMessage( histories.sendPreparedMessage(
@ -4019,7 +4054,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTP_int(action.options.scheduled), MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
mtpShortcut, mtpShortcut,
MTP_long(action.options.effectId) MTP_long(action.options.effectId),
MTP_long(starsPaid)
), done, fail); ), done, fail);
} }
isFirst = false; isFirst = false;
@ -4084,7 +4120,7 @@ void ApiWrap::sendBotStart(
void ApiWrap::sendInlineResult( void ApiWrap::sendInlineResult(
not_null<UserData*> bot, not_null<UserData*> bot,
not_null<InlineBots::Result*> data, not_null<InlineBots::Result*> data,
const SendAction &action, SendAction action,
std::optional<MsgId> localMessageId, std::optional<MsgId> localMessageId,
Fn<void(bool)> done) { Fn<void(bool)> done) {
sendAction(action); sendAction(action);
@ -4124,6 +4160,13 @@ void ApiWrap::sendInlineResult(
if (action.options.hideViaBot) { if (action.options.hideViaBot) {
sendFlags |= SendFlag::f_hide_via; 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; const auto sendAs = action.options.sendAs;
if (sendAs) { if (sendAs) {
@ -4138,6 +4181,7 @@ void ApiWrap::sendInlineResult(
.replyTo = action.replyTo, .replyTo = action.replyTo,
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.viaBotId = ((bot && !action.options.hideViaBot) .viaBotId = ((bot && !action.options.hideViaBot)
? peerToUser(bot->id) ? peerToUser(bot->id)
: UserId()), : UserId()),
@ -4161,7 +4205,8 @@ void ApiWrap::sendInlineResult(
MTP_string(data->getId()), MTP_string(data->getId()),
MTP_int(action.options.scheduled), MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (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) { ), [=](const MTPUpdates &result, const MTP::Response &response) {
history->finishSavingCloudDraft( history->finishSavingCloudDraft(
topicRootId, topicRootId,
@ -4298,6 +4343,7 @@ void ApiWrap::sendMediaWithRandomId(
const auto history = item->history(); const auto history = item->history();
const auto replyTo = item->replyTo(); const auto replyTo = item->replyTo();
const auto peer = history->peer;
auto caption = item->originalText(); auto caption = item->originalText();
TextUtilities::Trim(caption); TextUtilities::Trim(caption);
@ -4307,6 +4353,12 @@ void ApiWrap::sendMediaWithRandomId(
Api::ConvertOption::SkipLocal); Api::ConvertOption::SkipLocal);
const auto updateRecentStickers = Api::HasAttachedStickers(media); 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; using Flag = MTPmessages_SendMedia::Flag;
const auto flags = Flag(0) const auto flags = Flag(0)
@ -4319,10 +4371,10 @@ void ApiWrap::sendMediaWithRandomId(
| (options.sendAs ? Flag::f_send_as : Flag(0)) | (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
| (options.effectId ? Flag::f_effect : 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(); auto &histories = history->owner().histories();
const auto peer = history->peer;
const auto itemId = item->fullId(); const auto itemId = item->fullId();
histories.sendPreparedMessage( histories.sendPreparedMessage(
history, history,
@ -4346,7 +4398,8 @@ void ApiWrap::sendMediaWithRandomId(
MTP_int(options.scheduled), MTP_int(options.scheduled),
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, options.shortcutId), Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId) MTP_long(options.effectId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) { ), [=](const MTPUpdates &result, const MTP::Response &response) {
if (done) done(true); if (done) done(true);
if (updateRecentStickers) { if (updateRecentStickers) {
@ -4367,7 +4420,7 @@ void ApiWrap::sendMultiPaidMedia(
Expects(album->options.price > 0); Expects(album->options.price > 0);
const auto groupId = album->groupId; const auto groupId = album->groupId;
const auto &options = album->options; auto &options = album->options;
const auto randomId = album->items.front().randomId; const auto randomId = album->items.front().randomId;
auto medias = album->items | ranges::view::transform([]( auto medias = album->items | ranges::view::transform([](
const SendingAlbum::Item &part) { const SendingAlbum::Item &part) {
@ -4377,6 +4430,7 @@ void ApiWrap::sendMultiPaidMedia(
const auto history = item->history(); const auto history = item->history();
const auto replyTo = item->replyTo(); const auto replyTo = item->replyTo();
const auto peer = history->peer;
auto caption = item->originalText(); auto caption = item->originalText();
TextUtilities::Trim(caption); TextUtilities::Trim(caption);
@ -4384,6 +4438,12 @@ void ApiWrap::sendMultiPaidMedia(
_session, _session,
caption.entities, caption.entities,
Api::ConvertOption::SkipLocal); Api::ConvertOption::SkipLocal);
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
options.starsApproved);
if (starsPaid) {
options.starsApproved -= starsPaid;
}
using Flag = MTPmessages_SendMedia::Flag; using Flag = MTPmessages_SendMedia::Flag;
const auto flags = Flag(0) const auto flags = Flag(0)
@ -4396,10 +4456,10 @@ void ApiWrap::sendMultiPaidMedia(
| (options.sendAs ? Flag::f_send_as : Flag(0)) | (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
| (options.effectId ? Flag::f_effect : 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(); auto &histories = history->owner().histories();
const auto peer = history->peer;
const auto itemId = item->fullId(); const auto itemId = item->fullId();
album->sent = true; album->sent = true;
histories.sendPreparedMessage( histories.sendPreparedMessage(
@ -4422,7 +4482,8 @@ void ApiWrap::sendMultiPaidMedia(
MTP_int(options.scheduled), MTP_int(options.scheduled),
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, options.shortcutId), Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId) MTP_long(options.effectId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) { ), [=](const MTPUpdates &result, const MTP::Response &response) {
if (const auto album = _sendingAlbums.take(groupId)) { if (const auto album = _sendingAlbums.take(groupId)) {
const auto copy = (*album)->items; const auto copy = (*album)->items;
@ -4518,6 +4579,12 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
const auto history = sample->history(); const auto history = sample->history();
const auto replyTo = sample->replyTo(); const auto replyTo = sample->replyTo();
const auto sendAs = album->options.sendAs; 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; using Flag = MTPmessages_SendMultiMedia::Flag;
const auto flags = Flag(0) const auto flags = Flag(0)
| (replyTo ? Flag::f_reply_to : 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::f_quick_reply_shortcut
: Flag(0)) : Flag(0))
| (album->options.effectId ? Flag::f_effect : 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(); auto &histories = history->owner().histories();
const auto peer = history->peer; const auto peer = history->peer;
album->sent = true; album->sent = true;
@ -4546,7 +4614,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
MTP_int(album->options.scheduled), MTP_int(album->options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, album->options.shortcutId), 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) { ), [=](const MTPUpdates &result, const MTP::Response &response) {
_sendingAlbums.remove(groupId); _sendingAlbums.remove(groupId);

View file

@ -306,7 +306,7 @@ public:
void finishForwarding(const SendAction &action); void finishForwarding(const SendAction &action);
void forwardMessages( void forwardMessages(
Data::ResolvedForwardDraft &&draft, Data::ResolvedForwardDraft &&draft,
const SendAction &action, SendAction action,
FnMut<void()> &&successCallback = nullptr); FnMut<void()> &&successCallback = nullptr);
void shareContact( void shareContact(
const QString &phone, const QString &phone,
@ -368,7 +368,7 @@ public:
void sendInlineResult( void sendInlineResult(
not_null<UserData*> bot, not_null<UserData*> bot,
not_null<InlineBots::Result*> data, not_null<InlineBots::Result*> data,
const SendAction &action, SendAction action,
std::optional<MsgId> localMessageId, std::optional<MsgId> localMessageId,
Fn<void(bool)> done = nullptr); Fn<void(bool)> done = nullptr);
void sendMessageFail( void sendMessageFail(

View file

@ -1078,7 +1078,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
? tr::lng_background_other_group(tr::now) ? tr::lng_background_other_group(tr::now)
: forChannel() : forChannel()
? tr::lng_background_other_channel(tr::now) ? tr::lng_background_other_channel(tr::now)
: (_forPeer && !_fromMessageId) : (_forPeer
&& !_fromMessageId
&& !_forPeer->starsPerMessageChecked())
? tr::lng_background_other_info( ? tr::lng_background_other_info(
tr::now, tr::now,
lt_user, lt_user,

View file

@ -1122,3 +1122,10 @@ profileQrBackgroundRadius: 12px;
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }}; profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px); profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px); profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);
foldersMenu: PopupMenu(popupMenuWithIcons) {
maxHeight: 320px;
menu: Menu(menuWithIcons) {
itemPadding: margins(54px, 8px, 44px, 8px);
}
}

View file

@ -172,13 +172,6 @@ void ChangeFilterById(
const auto account = not_null(&history->session().account()); const auto account = not_null(&history->session().account());
if (const auto controller = Core::App().windowFor(account)) { if (const auto controller = Core::App().windowFor(account)) {
const auto isStatic = name.isStatic; 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({ controller->showToast({
.text = (add .text = (add
? tr::lng_filters_toast_add ? tr::lng_filters_toast_add
@ -189,7 +182,10 @@ void ChangeFilterById(
lt_folder, lt_folder,
Ui::Text::Wrapped(name.text, EntityType::Bold), Ui::Text::Wrapped(name.text, EntityType::Bold),
Ui::Text::WithEntities), Ui::Text::WithEntities),
.textContext = textContext, .textContext = Core::TextContext({
.session = &history->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
}); });
} }
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
@ -290,19 +286,17 @@ void FillChooseFilterMenu(
const auto title = filter.title(); const auto title = filter.title();
auto item = base::make_unique_q<FilterAction>( auto item = base::make_unique_q<FilterAction>(
menu.get(), menu.get(),
st::foldersMenu, menu->st().menu,
Ui::Menu::CreateAction( Ui::Menu::CreateAction(
menu.get(), menu.get(),
Ui::Text::FixAmpersandInAction(title.text.text), Ui::Text::FixAmpersandInAction(title.text.text),
std::move(callback)), std::move(callback)),
contains ? &st::mediaPlayerMenuCheck : nullptr, contains ? &st::mediaPlayerMenuCheck : nullptr,
contains ? &st::mediaPlayerMenuCheck : nullptr); contains ? &st::mediaPlayerMenuCheck : nullptr);
const auto context = Core::MarkedTextContext{ item->setMarkedText(title.text, QString(), Core::TextContext({
.session = &history->session(), .session = &history->session(),
.customEmojiRepaint = [raw = item.get()] { raw->update(); },
.customEmojiLoopLimit = title.isStatic ? -1 : 0, .customEmojiLoopLimit = title.isStatic ? -1 : 0,
}; }));
item->setMarkedText(title.text, QString(), context);
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({}))); item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
const auto action = menu->addAction(std::move(item)); const auto action = menu->addAction(std::move(item));

View file

@ -817,13 +817,15 @@ CreatePollBox::CreatePollBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
PollData::Flags chosen, PollData::Flags chosen,
PollData::Flags disabled, PollData::Flags disabled,
rpl::producer<int> starsRequired,
Api::SendType sendType, Api::SendType sendType,
SendMenu::Details sendMenuDetails) SendMenu::Details sendMenuDetails)
: _controller(controller) : _controller(controller)
, _chosen(chosen) , _chosen(chosen)
, _disabled(disabled) , _disabled(disabled)
, _sendType(sendType) , _sendType(sendType)
, _sendMenuDetails([result = sendMenuDetails] { return result; }) { , _sendMenuDetails([result = sendMenuDetails] { return result; })
, _starsRequired(std::move(starsRequired)) {
} }
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const { rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
@ -1226,10 +1228,11 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
_sendMenuDetails()); _sendMenuDetails());
}; };
const auto submit = addButton( const auto submit = addButton(
(isNormal tr::lng_polls_create_button(),
? tr::lng_polls_create_button()
: tr::lng_schedule_button()),
[=] { isNormal ? send({}) : schedule(); }); [=] { isNormal ? send({}) : schedule(); });
submit->setText(PaidSendButtonText(_starsRequired.value(), isNormal
? tr::lng_polls_create_button()
: tr::lng_schedule_button()));
const auto sendMenuDetails = [=] { const auto sendMenuDetails = [=] {
collectError(); collectError();
return (*error) ? SendMenu::Details() : _sendMenuDetails(); return (*error) ? SendMenu::Details() : _sendMenuDetails();

View file

@ -42,6 +42,7 @@ public:
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
PollData::Flags chosen, PollData::Flags chosen,
PollData::Flags disabled, PollData::Flags disabled,
rpl::producer<int> starsRequired,
Api::SendType sendType, Api::SendType sendType,
SendMenu::Details sendMenuDetails); SendMenu::Details sendMenuDetails);
@ -76,6 +77,7 @@ private:
const PollData::Flags _disabled = PollData::Flags(); const PollData::Flags _disabled = PollData::Flags();
const Api::SendType _sendType = Api::SendType(); const Api::SendType _sendType = Api::SendType();
const Fn<SendMenu::Details()> _sendMenuDetails; const Fn<SendMenu::Details()> _sendMenuDetails;
rpl::variable<int> _starsRequired;
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel; base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
Fn<void()> _setInnerFocus; Fn<void()> _setInnerFocus;
Fn<rpl::producer<bool>()> _dataIsValidValue; Fn<rpl::producer<bool>()> _dataIsValidValue;

View file

@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_graphics.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
@ -21,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" #include "history/history.h"
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "settings/settings_privacy_controllers.h"
#include "settings/settings_privacy_security.h" #include "settings/settings_privacy_security.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -42,6 +45,8 @@ namespace {
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value; constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value; constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
constexpr auto kStarsMin = 1;
constexpr auto kDefaultChargeStars = 10;
using Exceptions = Api::UserPrivacy::Exceptions; using Exceptions = Api::UserPrivacy::Exceptions;
@ -452,6 +457,143 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
return result; 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 } // namespace
bool EditPrivacyController::hasOption(Option option) const { bool EditPrivacyController::hasOption(Option option) const {
@ -812,19 +954,27 @@ void EditMessagesPrivacyBox(
constexpr auto kOptionAll = 0; constexpr auto kOptionAll = 0;
constexpr auto kOptionPremium = 1; constexpr auto kOptionPremium = 1;
constexpr auto kOptionCharge = 2;
const auto session = &controller->session();
const auto allowed = [=] { const auto allowed = [=] {
return controller->session().premium() return session->premium()
|| controller->session().appConfig().newRequirePremiumFree(); || session->appConfig().newRequirePremiumFree();
}; };
const auto privacy = &controller->session().api().globalPrivacy(); const auto privacy = &session->api().globalPrivacy();
const auto inner = box->verticalLayout(); const auto inner = box->verticalLayout();
inner->add(object_ptr<Ui::PlainShadow>(box)); inner->add(object_ptr<Ui::PlainShadow>(box));
Ui::AddSkip(inner, st::messagePrivacyTopSkip); Ui::AddSkip(inner, st::messagePrivacyTopSkip);
Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle()); Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle());
const auto group = std::make_shared<Ui::RadiobuttonGroup>( const auto group = std::make_shared<Ui::RadiobuttonGroup>(
privacy->newRequirePremiumCurrent() ? kOptionPremium : kOptionAll); (!allowed()
? kOptionAll
: privacy->newRequirePremiumCurrent()
? kOptionPremium
: privacy->newChargeStarsCurrent()
? kOptionCharge
: kOptionAll));
inner->add( inner->add(
object_ptr<Ui::Radiobutton>( object_ptr<Ui::Radiobutton>(
inner, inner,
@ -846,6 +996,92 @@ void EditMessagesPrivacyBox(
0, 0,
st::messagePrivacyBottomSkip)); 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>; using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
const auto toast = std::make_shared<WeakToast>(); const auto toast = std::make_shared<WeakToast>();
const auto showToast = [=] { const auto showToast = [=] {
@ -875,19 +1111,20 @@ void EditMessagesPrivacyBox(
}), }),
}); });
}; };
if (!allowed()) { if (!allowed()) {
CreateRadiobuttonLock(restricted, st::messagePrivacyCheck); CreateRadiobuttonLock(restricted, st::messagePrivacyCheck);
if (charged) {
CreateRadiobuttonLock(charged, st::messagePrivacyCheck);
}
group->setChangedCallback([=](int value) { group->setChangedCallback([=](int value) {
if (value == kOptionPremium) { if (value == kOptionPremium || value == kOptionCharge) {
group->setValue(kOptionAll); group->setValue(kOptionAll);
showToast(); showToast();
} }
}); });
}
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
if (!allowed()) {
Ui::AddSkip(inner); Ui::AddSkip(inner);
Settings::AddButtonWithIcon( Settings::AddButtonWithIcon(
inner, inner,
@ -907,8 +1144,12 @@ void EditMessagesPrivacyBox(
} else { } else {
box->addButton(tr::lng_settings_save(), [=] { box->addButton(tr::lng_settings_save(), [=] {
if (allowed()) { if (allowed()) {
privacy->updateNewRequirePremium( const auto value = group->current();
group->current() == kOptionPremium); const auto premiumRequired = (value == kOptionPremium);
const auto chargeStars = (value == kOptionCharge)
? state->stars.current()
: 0;
privacy->updateMessagesPrivacy(premiumRequired, chargeStars);
box->closeBox(); box->closeBox();
} else { } else {
showToast(); 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();
}

View file

@ -169,3 +169,8 @@ private:
void EditMessagesPrivacyBox( void EditMessagesPrivacyBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller); not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<int> SetupChargeSlider(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
int savedValue);

View file

@ -441,13 +441,10 @@ void EditFilterBox(
using namespace Window; using namespace Window;
return window->isGifPausedAtLeastFor(GifPauseReason::Layer); return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
}; };
name->setCustomTextContext([=](Fn<void()> repaint) { name->setCustomTextContext(Core::TextContext({
return std::any(Core::MarkedTextContext{ .session = session,
.session = session, .customEmojiLoopLimit = value ? -1 : 0,
.customEmojiRepaint = std::move(repaint), }), [paused] {
.customEmojiLoopLimit = value ? -1 : 0,
});
}, [paused] {
return On(PowerSaving::kEmojiChat) || paused(); return On(PowerSaving::kEmojiChat) || paused();
}, [paused] { }, [paused] {
return On(PowerSaving::kChatSpoiler) || paused(); return On(PowerSaving::kChatSpoiler) || paused();
@ -609,10 +606,7 @@ void EditFilterBox(
float64 alpha = 1.; float64 alpha = 1.;
}; };
const auto tag = preview->lifetime().make_state<TagState>(); const auto tag = preview->lifetime().make_state<TagState>();
tag->context.textContext = Core::MarkedTextContext{ tag->context.textContext = Core::TextContext({ .session = session });
.session = session,
.customEmojiRepaint = [] {},
};
preview->paintRequest() | rpl::start_with_next([=] { preview->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(preview); auto p = QPainter(preview);
p.setOpacity(tag->alpha); p.setOpacity(tag->alpha);

View file

@ -163,10 +163,10 @@ ExceptionRow::ExceptionRow(
st::defaultTextStyle, st::defaultTextStyle,
filters, filters,
kMarkupTextOptions, kMarkupTextOptions,
Core::MarkedTextContext{ Core::TextContext({
.session = &history->session(), .session = &history->session(),
.customEmojiRepaint = repaint, .repaint = repaint,
}); }));
} else if (peer()->isSelf()) { } else if (peer()->isSelf()) {
setCustomStatus(tr::lng_saved_forward_here(tr::now)); setCustomStatus(tr::lng_saved_forward_here(tr::now));
} }

View file

@ -537,13 +537,6 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
verticalLayout->add(std::move(icon.widget)); verticalLayout->add(std::move(icon.widget));
const auto isStatic = _filterTitle.isStatic; 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( verticalLayout->add(
object_ptr<Ui::CenterWrap<>>( object_ptr<Ui::CenterWrap<>>(
verticalLayout, verticalLayout,
@ -559,7 +552,10 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
Ui::Text::WithEntities)), Ui::Text::WithEntities)),
st::settingsFilterDividerLabel, st::settingsFilterDividerLabel,
st::defaultPopupMenu, st::defaultPopupMenu,
makeContext)), Core::TextContext({
.session = &_window->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}))),
st::filterLinkDividerLabelPadding); st::filterLinkDividerLabelPadding);
verticalLayout->geometryValue( verticalLayout->geometryValue(

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_credits.h" #include "api/api_credits.h"
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "core/ui_integration.h" // TextContext.
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -67,14 +68,9 @@ void GiftCreditsBox(
2.); 2.);
{ {
Ui::AddSkip(content); 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( auto link = tr::lng_credits_box_history_entry_gift_about_link(
lt_emoji, lt_emoji,
rpl::single(arrow), rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
Ui::Text::RichLangValue Ui::Text::RichLangValue
) | rpl::map([](TextWithEntities text) { ) | rpl::map([](TextWithEntities text) {
return Ui::Text::Link( return Ui::Text::Link(
@ -92,7 +88,7 @@ void GiftCreditsBox(
lt_link, lt_link,
std::move(link), std::move(link),
Ui::Text::RichLangValue), Ui::Text::RichLangValue),
{ .session = &peer->session() }, Core::TextContext({ .session = &peer->session() }),
st::creditsBoxAbout)), st::creditsBoxAbout)),
st::boxRowPadding); st::boxRowPadding);
} }

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox. #include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
#include "boxes/star_gift_box.h" // ShowStarGiftBox. #include "boxes/star_gift_box.h" // ShowStarGiftBox.
#include "boxes/transfer_gift_box.h" // ShowTransferGiftBox. #include "boxes/transfer_gift_box.h" // ShowTransferGiftBox.
#include "core/ui_integration.h"
#include "data/data_boosts.h" #include "data/data_boosts.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.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/toast/toast.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h" #include "ui/widgets/gradient_round_button.h"
#include "ui/widgets/label_with_custom_emoji.h"
#include "ui/widgets/tooltip.h" #include "ui/widgets/tooltip.h"
#include "ui/wrap/padding_wrap.h" #include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_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_peer_menu.h" // ShowChooseRecipientBox.
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_credits.h"
#include "styles/style_giveaway.h" #include "styles/style_giveaway.h"
#include "styles/style_info.h" #include "styles/style_info.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
@ -516,13 +517,13 @@ not_null<Ui::FlatLabel*> AddTableRow(
not_null<Ui::TableLayout*> table, not_null<Ui::TableLayout*> table,
rpl::producer<QString> label, rpl::producer<QString> label,
rpl::producer<TextWithEntities> value, rpl::producer<TextWithEntities> value,
const Fn<std::any(Fn<void()>)> &makeContext = nullptr) { const Ui::Text::MarkedContext &context = {}) {
auto widget = object_ptr<Ui::FlatLabel>( auto widget = object_ptr<Ui::FlatLabel>(
table, table,
std::move(value), std::move(value),
table->st().defaultValue, table->st().defaultValue,
st::defaultPopupMenu, st::defaultPopupMenu,
std::move(makeContext)); context);
const auto result = widget.data(); const auto result = widget.data();
AddTableRow( AddTableRow(
table, table,
@ -1272,8 +1273,8 @@ void AddStarGiftTable(
const auto selfBareId = session->userPeerId().value; const auto selfBareId = session->userPeerId().value;
const auto giftToSelf = (peerId == session->userPeerId()) const auto giftToSelf = (peerId == session->userPeerId())
&& (entry.in || entry.bareGiftOwnerId == selfBareId); && (entry.in || entry.bareGiftOwnerId == selfBareId);
const auto giftToChannel = entry.giftSavedId const auto giftToChannel = entry.giftChannelSavedId
&& peerIsChannel(PeerId(entry.bareGiftListPeerId)); && peerIsChannel(PeerId(entry.bareEntryOwnerId));
const auto raw = std::make_shared<Ui::ImportantTooltip*>(nullptr); const auto raw = std::make_shared<Ui::ImportantTooltip*>(nullptr);
const auto showTooltip = [=]( const auto showTooltip = [=](
@ -1394,14 +1395,14 @@ void AddStarGiftTable(
? MakePeerTableValue(table, show, PeerId(entry.bareActorId)) ? MakePeerTableValue(table, show, PeerId(entry.bareActorId))
: MakeHiddenPeerTableValue(table)), : MakeHiddenPeerTableValue(table)),
st::giveawayGiftCodePeerMargin); st::giveawayGiftCodePeerMargin);
if (entry.bareGiftListPeerId) { if (entry.bareEntryOwnerId) {
AddTableRow( AddTableRow(
table, table,
tr::lng_credits_box_history_entry_peer(), tr::lng_credits_box_history_entry_peer(),
MakePeerTableValue( MakePeerTableValue(
table, table,
show, show,
PeerId(entry.bareGiftListPeerId)), PeerId(entry.bareEntryOwnerId)),
st::giveawayGiftCodePeerMargin); st::giveawayGiftCodePeerMargin);
} }
} else if (peerId && !giftToSelf) { } else if (peerId && !giftToSelf) {
@ -1526,12 +1527,6 @@ void AddStarGiftTable(
: nullptr; : nullptr;
const auto date = base::unixtime::parse(original.date).date(); const auto date = base::unixtime::parse(original.date).date();
const auto dateText = TextWithEntities{ langDayOfMonth(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>( auto label = object_ptr<Ui::FlatLabel>(
table, table,
(from (from
@ -1573,7 +1568,7 @@ void AddStarGiftTable(
? *st.tableValueMessage ? *st.tableValueMessage
: st::giveawayGiftMessage), : st::giveawayGiftMessage),
st::defaultPopupMenu, st::defaultPopupMenu,
makeContext); Core::TextContext({ .session = session }));
const auto showBoxLink = [=](not_null<PeerData*> peer) { const auto showBoxLink = [=](not_null<PeerData*> peer) {
return std::make_shared<LambdaClickHandler>([=] { return std::make_shared<LambdaClickHandler>([=] {
show->showBox(PrepareShortInfoBox(peer, show)); show->showBox(PrepareShortInfoBox(peer, show));
@ -1591,12 +1586,6 @@ void AddStarGiftTable(
st::giveawayGiftCodeValueMargin); st::giveawayGiftCodeValueMargin);
} }
} else if (!entry.description.empty()) { } 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>( auto label = object_ptr<Ui::FlatLabel>(
table, table,
rpl::single(entry.description), rpl::single(entry.description),
@ -1604,7 +1593,7 @@ void AddStarGiftTable(
? *st.tableValueMessage ? *st.tableValueMessage
: st::giveawayGiftMessage), : st::giveawayGiftMessage),
st::defaultPopupMenu, st::defaultPopupMenu,
makeContext); Core::TextContext({ .session = session }));
label->setSelectable(true); label->setSelectable(true);
table->addRow( table->addRow(
nullptr, nullptr,
@ -1775,6 +1764,25 @@ void AddCreditsHistoryEntryTable(
tr::lng_credits_box_history_entry_subscription( tr::lng_credits_box_history_entry_subscription(
Ui::Text::WithEntities)); 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()) { if (!entry.id.isEmpty()) {
auto label = MakeMaybeMultilineTokenValue(table, entry.id, st); auto label = MakeMaybeMultilineTokenValue(table, entry.id, st);
label->setClickHandlerFilter([=](const auto &...) { label->setClickHandlerFilter([=](const auto &...) {

View file

@ -453,10 +453,7 @@ void CreateModerateMessagesBox(
) | rpl::start_with_next([=](const TextWithEntities &text) { ) | rpl::start_with_next([=](const TextWithEntities &text) {
raw->setMarkedText( raw->setMarkedText(
Ui::Text::Link(text, u"internal:"_q), Ui::Text::Link(text, u"internal:"_q),
Core::MarkedTextContext{ Core::TextContext({ .session = session }));
.session = session,
.customEmojiRepaint = [=] { raw->update(); },
});
}, label->lifetime()); }, label->lifetime());
Ui::AddSkip(inner); Ui::AddSkip(inner);

View file

@ -1154,8 +1154,7 @@ RecoverBox::RecoverBox(
rpl::single(Ui::Text::WrapEmailPattern(pattern)), rpl::single(Ui::Text::WrapEmailPattern(pattern)),
Ui::Text::WithEntities), Ui::Text::WithEntities),
st::termsContent, st::termsContent,
st::defaultPopupMenu, st::defaultPopupMenu)
[=](Fn<void()> update) { return CommonTextContext{ std::move(update) }; })
, _closeParent(std::move(closeParent)) { , _closeParent(std::move(closeParent)) {
_patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents); _patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
if (_cloudFields.pendingResetDate != 0 || !session) { if (_cloudFields.pendingResetDate != 0 || !session) {

View file

@ -883,6 +883,7 @@ void PeerListRow::paintUserpic(
} else if (const auto callback = generatePaintUserpicCallback(false)) { } else if (const auto callback = generatePaintUserpicCallback(false)) {
callback(p, x, y, outerWidth, st.photoSize); callback(p, x, y, outerWidth, st.photoSize);
} }
paintUserpicOverlay(p, st, x, y, outerWidth);
} }
// Emulates Ui::RoundImageCheckbox::paint() in a checked state. // Emulates Ui::RoundImageCheckbox::paint() in a checked state.

View file

@ -95,6 +95,13 @@ public:
[[nodiscard]] virtual QString generateShortName(); [[nodiscard]] virtual QString generateShortName();
[[nodiscard]] virtual auto generatePaintUserpicCallback( [[nodiscard]] virtual auto generatePaintUserpicCallback(
bool forceRound) -> PaintRoundImageCallback; bool forceRound) -> PaintRoundImageCallback;
virtual void paintUserpicOverlay(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int outerWidth) {
}
[[nodiscard]] virtual auto generateNameFirstLetters() const [[nodiscard]] virtual auto generateNameFirstLetters() const
-> const base::flat_set<QChar> &; -> const base::flat_set<QChar> &;

View file

@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "api/api_chat_participants.h" #include "api/api_chat_participants.h"
#include "api/api_premium.h" #include "api/api_premium.h" // MessageMoneyRestriction.
#include "base/random.h" #include "base/random.h"
#include "boxes/filters/edit_filter_chats_list.h" #include "boxes/filters/edit_filter_chats_list.h"
#include "settings/settings_premium.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.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "dialogs/dialogs_main_list.h" #include "dialogs/dialogs_main_list.h"
#include "payments/ui/payments_reaction_box.h"
#include "ui/effects/outline_segments.h" #include "ui/effects/outline_segments.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "window/window_separate_id.h" #include "window/window_separate_id.h"
@ -275,40 +276,71 @@ bool PeerListGlobalSearchController::isLoading() {
return _timer.isActive() || _requestId; return _timer.isActive() || _requestId;
} }
struct RecipientRow::Restriction {
Api::MessageMoneyRestriction value;
RestrictionBadgeCache cache;
};
RecipientRow::RecipientRow( RecipientRow::RecipientRow(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const style::PeerListItem *maybeLockedSt, const style::PeerListItem *maybeLockedSt,
History *maybeHistory) History *maybeHistory)
: PeerListRow(peer) : PeerListRow(peer)
, _maybeHistory(maybeHistory) , _maybeHistory(maybeHistory)
, _resolvePremiumRequired(maybeLockedSt != nullptr) { , _maybeLockedSt(maybeLockedSt) {
if (maybeLockedSt if (_maybeLockedSt) {
&& (Api::ResolveRequiresPremiumToWrite(peer, maybeHistory) setRestriction(Api::ResolveMessageMoneyRestrictions(
== Api::RequirePremiumState::Yes)) { peer,
_lockedSt = maybeLockedSt; maybeHistory));
} }
} }
PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback( Api::MessageMoneyRestriction RecipientRow::restriction() const {
bool forceRound) { return _restriction
auto result = PeerListRow::generatePaintUserpicCallback(forceRound); ? _restriction->value
if (const auto st = _lockedSt) { : Api::MessageMoneyRestriction();
return [=](Painter &p, int x, int y, int outerWidth, int size) { }
result(p, x, y, outerWidth, size);
PaintPremiumRequiredLock(p, st, x, y, outerWidth, size); 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( bool RecipientRow::refreshLock(
not_null<const style::PeerListItem*> maybeLockedSt) { not_null<const style::PeerListItem*> maybeLockedSt) {
if (const auto user = peer()->asUser()) { if (const auto user = peer()->asUser()) {
const auto locked = _resolvePremiumRequired using Restriction = Api::MessageMoneyRestriction;
&& (Api::ResolveRequiresPremiumToWrite(user, _maybeHistory) const auto r = _maybeLockedSt
== Api::RequirePremiumState::Yes); ? Api::ResolveMessageMoneyRestrictions(
if (this->locked() != locked) { user,
setLocked(locked ? maybeLockedSt.get() : nullptr); _maybeHistory)
: Restriction();
if ((_restriction ? _restriction->value : Restriction()) != r) {
setRestriction(r);
return true; return true;
} }
} }
@ -318,22 +350,30 @@ bool RecipientRow::refreshLock(
void RecipientRow::preloadUserpic() { void RecipientRow::preloadUserpic() {
PeerListRow::preloadUserpic(); PeerListRow::preloadUserpic();
if (!_resolvePremiumRequired) { if (!_maybeLockedSt) {
return; return;
} else if (Api::ResolveRequiresPremiumToWrite(peer(), _maybeHistory) }
== Api::RequirePremiumState::Unknown) { const auto peer = this->peer();
const auto user = peer()->asUser(); const auto known = Api::ResolveMessageMoneyRestrictions(
user->session().api().premium().resolvePremiumRequired(user); 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, not_null<PeerListController*> controller,
rpl::lifetime &lifetime) { rpl::lifetime &lifetime) {
const auto session = &controller->session(); const auto session = &controller->session();
rpl::merge( rpl::merge(
Data::AmPremiumValue(session) | rpl::to_empty, Data::AmPremiumValue(session) | rpl::to_empty,
session->api().premium().somePremiumRequiredResolved() session->api().premium().someMessageMoneyRestrictionsResolved()
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto st = &controller->computeListSt().item; const auto st = &controller->computeListSt().item;
const auto delegate = controller->delegate(); const auto delegate = controller->delegate();
@ -726,7 +766,7 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
return std::make_unique<PeerListRow>(user); return std::make_unique<PeerListRow>(user);
} }
RecipientPremiumRequiredError WritePremiumRequiredError( RecipientMoneyRestrictionError WriteMoneyRestrictionError(
not_null<UserData*> user) { not_null<UserData*> user) {
return { return {
.text = tr::lng_send_non_premium_message_toast( .text = tr::lng_send_non_premium_message_toast(
@ -759,7 +799,7 @@ ChooseRecipientBoxController::ChooseRecipientBoxController(
, _session(args.session) , _session(args.session)
, _callback(std::move(args.callback)) , _callback(std::move(args.callback))
, _filter(std::move(args.filter)) , _filter(std::move(args.filter))
, _premiumRequiredError(std::move(args.premiumRequiredError)) { , _moneyRestrictionError(std::move(args.moneyRestrictionError)) {
} }
Main::Session &ChooseRecipientBoxController::session() const { Main::Session &ChooseRecipientBoxController::session() const {
@ -769,14 +809,17 @@ Main::Session &ChooseRecipientBoxController::session() const {
void ChooseRecipientBoxController::prepareViewHook() { void ChooseRecipientBoxController::prepareViewHook() {
delegate()->peerListSetTitle(tr::lng_forward_choose()); delegate()->peerListSetTitle(tr::lng_forward_choose());
if (_premiumRequiredError) { if (_moneyRestrictionError) {
TrackPremiumRequiredChanges(this, lifetime()); TrackMessageMoneyRestrictionsChanges(this, lifetime());
} }
} }
bool ChooseRecipientBoxController::showLockedError( bool ChooseRecipientBoxController::showLockedError(
not_null<PeerListRow*> row) { not_null<PeerListRow*> row) {
return RecipientRow::ShowLockedError(this, row, _premiumRequiredError); return RecipientRow::ShowLockedError(
this,
row,
_moneyRestrictionError);
} }
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) { void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
@ -836,8 +879,9 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
bool RecipientRow::ShowLockedError( bool RecipientRow::ShowLockedError(
not_null<PeerListController*> controller, not_null<PeerListController*> controller,
not_null<PeerListRow*> row, not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error) { Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error) {
if (!static_cast<RecipientRow*>(row.get())->locked()) { const auto recipient = static_cast<RecipientRow*>(row.get());
if (!recipient->restriction().premiumRequired) {
return false; return false;
} }
::Settings::ShowPremiumPromoToast( ::Settings::ShowPremiumPromoToast(
@ -860,15 +904,15 @@ auto ChooseRecipientBoxController::createRow(
: ((peer->isBroadcast() && !Data::CanSendAnything(peer)) : ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|| peer->isRepliesChat() || peer->isRepliesChat()
|| peer->isVerifyCodes() || peer->isVerifyCodes()
|| (peer->isUser() && (_premiumRequiredError || (peer->isUser() && (_moneyRestrictionError
? !peer->asUser()->canSendIgnoreRequirePremium() ? !peer->asUser()->canSendIgnoreMoneyRestrictions()
: !Data::CanSendAnything(peer)))); : !Data::CanSendAnything(peer))));
if (skip) { if (skip) {
return nullptr; return nullptr;
} }
auto result = std::make_unique<Row>( auto result = std::make_unique<Row>(
history, history,
_premiumRequiredError ? &computeListSt().item : nullptr); _moneyRestrictionError ? &computeListSt().item : nullptr);
return result; return result;
} }
@ -1093,25 +1137,61 @@ auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
return skip ? nullptr : std::make_unique<Row>(topic); return skip ? nullptr : std::make_unique<Row>(topic);
}; };
void PaintPremiumRequiredLock( void PaintRestrictionBadge(
Painter &p, Painter &p,
not_null<const style::PeerListItem*> st, not_null<const style::PeerListItem*> st,
int stars,
RestrictionBadgeCache &cache,
int x, int x,
int y, int y,
int outerWidth, int outerWidth,
int size) { 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; const auto &check = st->checkbox.check;
auto pen = check.border->p; const auto add = check.width;
pen.setWidthF(check.width); if (!good) {
p.setPen(pen); cache.stars = stars;
p.setBrush(st::premiumButtonBg2); cache.paletteVersion = paletteVersion;
const auto &icon = st::stickersPremiumLock; if (stars) {
const auto width = icon.width(); const auto text = (stars >= 1000)
const auto height = icon.height(); ? (QString::number(stars / 1000) + 'K')
const auto rect = QRect( : QString::number(stars);
QPoint(x + size - width, y + size - height), cache.badge = Ui::GenerateSmallBadgeImage(
icon.size()); text,
p.drawEllipse(rect); st::paidReactTopStarIcon,
icon.paintInCenter(p, rect); 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);
} }

View file

@ -19,6 +19,10 @@ namespace style {
struct PeerListItem; struct PeerListItem;
} // namespace style } // namespace style
namespace Api {
struct MessageMoneyRestriction;
} // namespace Api
namespace Data { namespace Data {
class Thread; class Thread;
class Forum; class Forum;
@ -93,13 +97,28 @@ private:
}; };
struct RecipientPremiumRequiredError { struct RecipientMoneyRestrictionError {
TextWithEntities text; TextWithEntities text;
}; };
[[nodiscard]] RecipientPremiumRequiredError WritePremiumRequiredError( [[nodiscard]] RecipientMoneyRestrictionError WriteMoneyRestrictionError(
not_null<UserData*> user); 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 { class RecipientRow : public PeerListRow {
public: public:
explicit RecipientRow( explicit RecipientRow(
@ -112,30 +131,33 @@ public:
[[nodiscard]] static bool ShowLockedError( [[nodiscard]] static bool ShowLockedError(
not_null<PeerListController*> controller, not_null<PeerListController*> controller,
not_null<PeerListRow*> row, not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error); Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error);
[[nodiscard]] History *maybeHistory() const { [[nodiscard]] History *maybeHistory() const {
return _maybeHistory; return _maybeHistory;
} }
[[nodiscard]] bool locked() const { void paintUserpicOverlay(
return _lockedSt != nullptr; Painter &p,
} const style::PeerListItem &st,
void setLocked(const style::PeerListItem *lockedSt) { int x,
_lockedSt = lockedSt; int y,
} int outerWidth) override;
PaintRoundImageCallback generatePaintUserpicCallback(
bool forceRound) override;
void preloadUserpic() override; void preloadUserpic() override;
[[nodiscard]] Api::MessageMoneyRestriction restriction() const;
void setRestriction(Api::MessageMoneyRestriction restriction);
private: private:
struct Restriction;
History *_maybeHistory = nullptr; History *_maybeHistory = nullptr;
const style::PeerListItem *_lockedSt = nullptr; const style::PeerListItem *_maybeLockedSt = nullptr;
bool _resolvePremiumRequired = false; std::shared_ptr<Restriction> _restriction;
}; };
void TrackPremiumRequiredChanges( void TrackMessageMoneyRestrictionsChanges(
not_null<PeerListController*> controller, not_null<PeerListController*> controller,
rpl::lifetime &lifetime); rpl::lifetime &lifetime);
@ -261,8 +283,8 @@ struct ChooseRecipientArgs {
FnMut<void(not_null<Data::Thread*>)> callback; FnMut<void(not_null<Data::Thread*>)> callback;
Fn<bool(not_null<Data::Thread*>)> filter; Fn<bool(not_null<Data::Thread*>)> filter;
using PremiumRequiredError = RecipientPremiumRequiredError; using MoneyRestrictionError = RecipientMoneyRestrictionError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError; Fn<MoneyRestrictionError(not_null<UserData*>)> moneyRestrictionError;
}; };
class ChooseRecipientBoxController class ChooseRecipientBoxController
@ -290,8 +312,8 @@ private:
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
FnMut<void(not_null<Data::Thread*>)> _callback; FnMut<void(not_null<Data::Thread*>)> _callback;
Fn<bool(not_null<Data::Thread*>)> _filter; Fn<bool(not_null<Data::Thread*>)> _filter;
Fn<RecipientPremiumRequiredError( Fn<RecipientMoneyRestrictionError(
not_null<UserData*>)> _premiumRequiredError; not_null<UserData*>)> _moneyRestrictionError;
}; };
@ -371,11 +393,3 @@ private:
Fn<bool(not_null<Data::ForumTopic*>)> _filter; 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);

View file

@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h" #include "api/api_chat_participants.h"
#include "api/api_invite_links.h" #include "api/api_invite_links.h"
#include "api/api_premium.h"
#include "boxes/peers/edit_participant_box.h" #include "boxes/peers/edit_participant_box.h"
#include "boxes/peers/edit_peer_type_box.h" #include "boxes/peers/edit_peer_type_box.h"
#include "boxes/peers/replace_boost_box.h" #include "boxes/peers/replace_boost_box.h"
#include "boxes/max_invite_box.h" #include "boxes/max_invite_box.h"
#include "chat_helpers/message_field.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.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_changes.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item_helpers.h"
#include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_indexed_list.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/boxes/show_or_premium_box.h" #include "ui/boxes/show_or_premium_box.h"
@ -52,16 +55,39 @@ constexpr auto kUserpicsLimit = 3;
class ForbiddenRow final : public PeerListRow { class ForbiddenRow final : public PeerListRow {
public: public:
ForbiddenRow(not_null<PeerData*> peer, bool locked); ForbiddenRow(
not_null<PeerData*> peer,
not_null<const style::PeerListItem*> lockSt,
bool locked);
PaintRoundImageCallback generatePaintUserpicCallback( PaintRoundImageCallback generatePaintUserpicCallback(
bool forceRound) override; 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: private:
struct Restriction {
Api::MessageMoneyRestriction value;
RestrictionBadgeCache cache;
};
const bool _locked = false; const bool _locked = false;
const not_null<const style::PeerListItem*> _lockSt;
QImage _disabledFrame; QImage _disabledFrame;
InMemoryKey _userpicKey; InMemoryKey _userpicKey;
int _paletteVersion = 0; int _paletteVersion = 0;
std::shared_ptr<Restriction> _restriction;
}; };
@ -81,6 +107,9 @@ public:
[[nodiscard]] rpl::producer<int> selectedValue() const { [[nodiscard]] rpl::producer<int> selectedValue() const {
return _selected.value(); return _selected.value();
} }
[[nodiscard]] rpl::producer<int> starsToSend() const {
return _starsToSend.value();
}
void send( void send(
std::vector<not_null<PeerData*>> list, std::vector<not_null<PeerData*>> list,
@ -89,10 +118,16 @@ public:
private: private:
void appendRow(not_null<UserData*> user); void appendRow(not_null<UserData*> user);
[[nodiscard]] std::unique_ptr<PeerListRow> createRow( [[nodiscard]] std::unique_ptr<ForbiddenRow> createRow(
not_null<UserData*> user) const; not_null<UserData*> user) const;
[[nodiscard]] bool canInvite(not_null<PeerData*> peer) 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 setSimpleCover();
void setComplexCover(); void setComplexCover();
@ -101,8 +136,11 @@ private:
const std::vector<not_null<UserData*>> &_users; const std::vector<not_null<UserData*>> &_users;
const bool _can = false; const bool _can = false;
rpl::variable<int> _selected; rpl::variable<int> _selected;
rpl::variable<int> _starsToSend;
bool _sending = false; bool _sending = false;
rpl::lifetime _paymentCheckLifetime;
}; };
base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) { base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
@ -256,11 +294,17 @@ Main::Session &InviteForbiddenController::session() const {
return _peer->session(); 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) : PeerListRow(peer)
, _locked(locked) { , _locked(locked)
, _lockSt(lockSt) {
if (_locked) { if (_locked) {
setCustomStatus(tr::lng_invite_status_disabled(tr::now)); 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() { void InviteForbiddenController::setSimpleCover() {
delegate()->peerListSetTitle( delegate()->peerListSetTitle(
_can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant()); _can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant());
@ -435,6 +549,30 @@ void InviteForbiddenController::setComplexCover() {
} }
void InviteForbiddenController::prepare() { 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() if (session().premium()
|| (_forbidden.premiumAllowsInvite.empty() || (_forbidden.premiumAllowsInvite.empty()
&& _forbidden.premiumAllowsWrite.empty())) { && _forbidden.premiumAllowsWrite.empty())) {
@ -464,6 +602,11 @@ void InviteForbiddenController::rowClicked(not_null<PeerListRow*> row) {
const auto checked = row->checked(); const auto checked = row->checked();
delegate()->peerListSetRowChecked(row, !checked); delegate()->peerListSetRowChecked(row, !checked);
_selected = _selected.current() + (checked ? -1 : 1); _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) { void InviteForbiddenController::appendRow(not_null<UserData*> user) {
@ -473,6 +616,9 @@ void InviteForbiddenController::appendRow(not_null<UserData*> user) {
delegate()->peerListAppendRow(std::move(row)); delegate()->peerListAppendRow(std::move(row));
if (canInvite(user)) { if (canInvite(user)) {
delegate()->peerListSetRowChecked(raw, true); 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, std::vector<not_null<PeerData*>> list,
Ui::ShowPtr show, Ui::ShowPtr show,
Fn<void()> close) { 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; return;
} }
_sending = true; _sending = true;
@ -492,12 +695,18 @@ void InviteForbiddenController::send(
if (link.isEmpty()) { if (link.isEmpty()) {
return false; return false;
} }
auto full = options;
auto &api = _peer->session().api(); auto &api = _peer->session().api();
auto options = Api::SendOptions();
for (const auto &to : list) { 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); const auto history = to->owner().history(to);
auto message = Api::MessageToSend( auto message = Api::MessageToSend(
Api::SendAction(history, options)); Api::SendAction(history, copy));
message.textWithTags = { link }; message.textWithTags = { link };
message.action.clearDraft = false; message.action.clearDraft = false;
api.sendMessage(std::move(message)); 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 { not_null<UserData*> user) const {
const auto locked = _can && !canInvite(user); 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 } // namespace
@ -584,8 +794,8 @@ void AddParticipantsBoxController::subscribeToMigration() {
} }
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) { void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto premiumRequiredError = WritePremiumRequiredError; const auto moneyRestrictionError = WriteMoneyRestrictionError;
if (RecipientRow::ShowLockedError(this, row, premiumRequiredError)) { if (RecipientRow::ShowLockedError(this, row, moneyRestrictionError)) {
return; return;
} }
const auto &serverConfig = session().serverConfig(); const auto &serverConfig = session().serverConfig();
@ -614,7 +824,7 @@ void AddParticipantsBoxController::itemDeselectedHook(
void AddParticipantsBoxController::prepareViewHook() { void AddParticipantsBoxController::prepareViewHook() {
updateTitle(); updateTitle();
TrackPremiumRequiredChanges(this, lifetime()); TrackMessageMoneyRestrictionsChanges(this, lifetime());
} }
int AddParticipantsBoxController::alreadyInCount() const { int AddParticipantsBoxController::alreadyInCount() const {
@ -929,12 +1139,15 @@ bool ChatInviteForbidden(
) | rpl::start_with_next([=](bool has) { ) | rpl::start_with_next([=](bool has) {
box->clearButtons(); box->clearButtons();
if (has) { if (has) {
box->addButton(tr::lng_via_link_send(), [=] { const auto send = box->addButton(tr::lng_via_link_send(), [=] {
weak->send( weak->send(
box->collectSelectedRows(), box->collectSelectedRows(),
box->uiShow(), box->uiShow(),
crl::guard(box, [=] { box->closeBox(); })); crl::guard(box, [=] { box->closeBox(); }));
}); });
send->setText(PaidSendButtonText(
weak->starsToSend(),
tr::lng_via_link_send()));
} }
box->addButton(tr::lng_create_group_skip(), [=] { box->addButton(tr::lng_create_group_skip(), [=] {
box->closeBox(); box->closeBox();

View file

@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/background_box.h" #include "boxes/background_box.h"
#include "boxes/stickers_box.h" #include "boxes/stickers_box.h"
#include "chat_helpers/compose/compose_show.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_custom_emoji.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/data_changes.h" #include "data/data_changes.h"
@ -165,7 +165,7 @@ private:
const uint32 _level; const uint32 _level;
const TextWithEntities _icon; const TextWithEntities _icon;
const Core::MarkedTextContext _context; const Ui::Text::MarkedContext _context;
Ui::Text::String _text; Ui::Text::String _text;
bool _minimal = false; bool _minimal = false;
@ -466,7 +466,10 @@ LevelBadge::LevelBadge(
st::settingsLevelBadgeLock, st::settingsLevelBadgeLock,
QMargins(0, st::settingsLevelBadgeLockSkip, 0, 0), QMargins(0, st::settingsLevelBadgeLockSkip, 0, 0),
false))) false)))
, _context({ .session = session }) { , _context(Core::TextContext({
.session = session,
.repaint = [this] { update(); },
})) {
updateText(); updateText();
} }

View file

@ -219,6 +219,33 @@ void SaveSlowmodeSeconds(
api->registerModifyRequest(key, requestId); 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( void SaveBoostsUnrestrict(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
int boostsUnrestrict, int boostsUnrestrict,
@ -271,6 +298,7 @@ void ShowEditPermissions(
channel, channel,
result.boostsUnrestrict, result.boostsUnrestrict,
close); close);
SaveStarsPerMessage(channel, result.starsPerMessage, close);
} }
}; };
auto done = [=](EditPeerPermissionsBoxResult result) { auto done = [=](EditPeerPermissionsBoxResult result) {
@ -282,7 +310,9 @@ void ShowEditPermissions(
const auto saveFor = peer->migrateToOrMe(); const auto saveFor = peer->migrateToOrMe();
const auto chat = saveFor->asChat(); const auto chat = saveFor->asChat();
if (!chat if (!chat
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) { || (!result.slowmodeSeconds
&& !result.boostsUnrestrict
&& !result.starsPerMessage)) {
save(saveFor, result); save(saveFor, result);
return; return;
} }
@ -2689,3 +2719,9 @@ bool EditPeerInfoBox::Available(not_null<PeerData*> peer) {
return false; return false;
} }
} }
void ShowEditChatPermissions(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) {
ShowEditPermissions(navigation, peer);
}

View file

@ -56,3 +56,7 @@ private:
not_null<PeerData*> _peer; not_null<PeerData*> _peer;
}; };
void ShowEditChatPermissions(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer);

View file

@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "core/application.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/components/credits.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
@ -740,10 +740,10 @@ void Controller::setupAboveJoinedWidget() {
{ QString::number(current.subscription.credits) }, { QString::number(current.subscription.credits) },
Ui::Text::WithEntities), Ui::Text::WithEntities),
kMarkupTextOptions, kMarkupTextOptions,
Core::MarkedTextContext{ Core::TextContext({
.session = &session(), .session = &session(),
.customEmojiRepaint = [=] { widget->update(); }, .repaint = [=] { widget->update(); },
}); }));
auto &lifetime = widget->lifetime(); auto &lifetime = widget->lifetime();
const auto rateValue = lifetime.make_state<rpl::variable<float64>>( const auto rateValue = lifetime.make_state<rpl::variable<float64>>(
session().credits().rateValue(_peer)); session().credits().rateValue(_peer));
@ -994,10 +994,7 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
lt_cost, lt_cost,
{ QString::number(data.subscription.credits) }, { QString::number(data.subscription.credits) },
Ui::Text::WithEntities), Ui::Text::WithEntities),
Core::MarkedTextContext{ Core::TextContext({ .session = session }));
.session = session,
.customEmojiRepaint = [=] { subtitle1->update(); },
});
const auto subtitle2 = box->addRow( const auto subtitle2 = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>( object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box, box,
@ -1484,8 +1481,12 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
? tr::lng_group_invite_copied(tr::now) ? tr::lng_group_invite_copied(tr::now)
: copied); : copied);
}; };
auto countMessagesCallback = [=](const TextWithTags &comment) {
return 1;
};
auto submitCallback = [=]( auto submitCallback = [=](
std::vector<not_null<Data::Thread*>> &&result, std::vector<not_null<Data::Thread*>> &&result,
Fn<bool()> checkPaid,
TextWithTags &&comment, TextWithTags &&comment,
Api::SendOptions options, Api::SendOptions options,
Data::ForwardOptions) { Data::ForwardOptions) {
@ -1503,6 +1504,8 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
result.size() > 1)); result.size() > 1));
} }
return; return;
} else if (!checkPaid()) {
return;
} }
*sending = true; *sending = true;
@ -1530,7 +1533,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
}; };
auto filterCallback = [](not_null<Data::Thread*> thread) { auto filterCallback = [](not_null<Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) { if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) { if (user->canSendIgnoreMoneyRestrictions()) {
return true; return true;
} }
} }
@ -1539,9 +1542,10 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
auto object = Box<ShareBox>(ShareBox::Descriptor{ auto object = Box<ShareBox>(ShareBox::Descriptor{
.session = session, .session = session,
.copyCallback = std::move(copyCallback), .copyCallback = std::move(copyCallback),
.countMessagesCallback = std::move(countMessagesCallback),
.submitCallback = std::move(submitCallback), .submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback), .filterCallback = std::move(filterCallback),
.premiumRequiredError = SharePremiumRequiredError(), .moneyRestrictionError = ShareMessageMoneyRestrictionError(),
}); });
*box = Ui::MakeWeak(object.data()); *box = Ui::MakeWeak(object.data());
return object; return object;

View file

@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_values.h" #include "info/profile/info_profile_values.h"
#include "boxes/peers/edit_participants_box.h" #include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_info_box.h" #include "boxes/peers/edit_peer_info_box.h"
#include "boxes/edit_privacy_box.h"
#include "settings/settings_power_saving.h" #include "settings/settings_power_saving.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/window_controller.h" #include "window/window_controller.h"
@ -891,11 +892,10 @@ void AddBoostsUnrestrictLabels(
manager->registerInternalEmoji( manager->registerInternalEmoji(
st::boostsMessageIcon, st::boostsMessageIcon,
st::boostsMessageIconPadding)); st::boostsMessageIconPadding));
const auto context = Core::MarkedTextContext{ const auto context = Core::TextContext({
.session = session, .session = session,
.customEmojiRepaint = [] {},
.customEmojiLoopLimit = 1, .customEmojiLoopLimit = 1,
}; });
for (auto i = 0; i != kBoostsUnrestrictValues; ++i) { for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
const auto label = Ui::CreateChild<Ui::FlatLabel>( const auto label = Ui::CreateChild<Ui::FlatLabel>(
labels, labels,
@ -942,9 +942,7 @@ rpl::producer<int> AddBoostsUnrestrictSlider(
const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>( const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
channel ? channel->boostsUnrestrict() : 0); channel ? channel->boostsUnrestrict() : 0);
container->add( Ui::AddSkip(container);
object_ptr<Ui::BoxContentDivider>(container),
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
auto enabled = boostsUnrestrict->value( auto enabled = boostsUnrestrict->value(
) | rpl::map(_1 > 0); ) | rpl::map(_1 > 0);
@ -1008,19 +1006,20 @@ rpl::producer<int> AddBoostsUnrestrictWrapped(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container, container,
object_ptr<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(); wrap->finishAnimating();
auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer); const auto inner = wrap->entity();
const auto divider = container->add(
auto result = AddBoostsUnrestrictSlider(inner, peer);
const auto skip = st::defaultVerticalListSkip;
const auto divider = inner->add(
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>( object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
container, inner,
object_ptr<Ui::BoxContentDivider>(container), object_ptr<Ui::BoxContentDivider>(inner),
QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip })); QMargins{ 0, skip, 0, skip }));
divider->toggleOn(rpl::combine( divider->toggleOn(rpl::duplicate(result) | rpl::map(!rpl::mappers::_1));
std::move(shown),
rpl::duplicate(result),
!rpl::mappers::_1 || !rpl::mappers::_2));
divider->finishAnimating(); divider->finishAnimating();
return result; return result;
@ -1159,7 +1158,43 @@ void ShowEditPeerPermissionsBox(
rpl::variable<int> slowmodeSeconds; rpl::variable<int> slowmodeSeconds;
rpl::variable<int> boostsUnrestrict; rpl::variable<int> boostsUnrestrict;
rpl::variable<bool> hasSendRestrictions; 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 static constexpr auto kSendRestrictions = Flag::EmbedLinks
| Flag::SendGames | Flag::SendGames
| Flag::SendGifs | Flag::SendGifs
@ -1173,7 +1208,6 @@ void ShowEditPeerPermissionsBox(
| Flag::SendVoiceMessages | Flag::SendVoiceMessages
| Flag::SendFiles | Flag::SendFiles
| Flag::SendOther; | Flag::SendOther;
const auto state = inner->lifetime().make_state<State>();
state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0) state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
|| (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0); || (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
state->boostsUnrestrict = AddBoostsUnrestrictWrapped( state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
@ -1214,10 +1248,14 @@ void ShowEditPeerPermissionsBox(
const auto boostsUnrestrict = hasRestrictions const auto boostsUnrestrict = hasRestrictions
? state->boostsUnrestrict.current() ? state->boostsUnrestrict.current()
: 0; : 0;
const auto starsPerMessage = (charging && charging->toggled())
? state->starsPerMessage.current()
: 0;
done({ done({
restrictions, restrictions,
slowmodeSeconds, slowmodeSeconds,
boostsUnrestrict, boostsUnrestrict,
starsPerMessage,
}); });
}); });
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });

View file

@ -39,6 +39,7 @@ struct EditPeerPermissionsBoxResult final {
ChatRestrictions rights; ChatRestrictions rights;
int slowmodeSeconds = 0; int slowmodeSeconds = 0;
int boostsUnrestrict = 0; int boostsUnrestrict = 0;
int starsPerMessage = 0;
}; };
void ShowEditPeerPermissionsBox( void ShowEditPeerPermissionsBox(

View file

@ -363,12 +363,15 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
const auto customEmojiPaused = [controller = args.controller] { const auto customEmojiPaused = [controller = args.controller] {
return controller->isGifPausedAtLeastFor(PauseReason::Layer); return controller->isGifPausedAtLeastFor(PauseReason::Layer);
}; };
auto factory = [=](QStringView data, Fn<void()> update) auto context = Core::TextContext({
-> std::unique_ptr<Ui::Text::CustomEmoji> { .session = session,
});
context.customEmojiFactory = [=](
QStringView data,
const Ui::Text::MarkedContext &context
) -> std::unique_ptr<Ui::Text::CustomEmoji> {
const auto id = Data::ParseCustomEmojiData(data); const auto id = Data::ParseCustomEmojiData(data);
auto result = owner->customEmojiManager().create( auto result = Ui::Text::MakeCustomEmoji(data, context);
data,
std::move(update));
if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) { if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) {
return std::make_unique<MaybeDisabledEmoji>( return std::make_unique<MaybeDisabledEmoji>(
std::move(result), std::move(result),
@ -377,12 +380,10 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
using namespace Ui::Text; using namespace Ui::Text;
return std::make_unique<FirstFrameEmoji>(std::move(result)); return std::make_unique<FirstFrameEmoji>(std::move(result));
}; };
raw->setCustomTextContext([=](Fn<void()> repaint) { raw->setCustomTextContext(
return std::any(Core::MarkedTextContext{ std::move(context),
.session = session, customEmojiPaused,
.customEmojiRepaint = std::move(repaint), customEmojiPaused);
});
}, customEmojiPaused, customEmojiPaused, std::move(factory));
const auto callback = args.callback; const auto callback = args.callback;
const auto isCustom = [=](DocumentId id) { const auto isCustom = [=](DocumentId id) {

View file

@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_credits.h" #include "api/api_credits.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "core/ui_integration.h" // Core::MarkedTextContext. #include "core/ui_integration.h" // TextContext
#include "data/components/credits.h" #include "data/components/credits.h"
#include "data/data_credits.h" #include "data/data_credits.h"
#include "data/data_photo.h" #include "data/data_photo.h"
@ -511,32 +511,28 @@ TextWithEntities CreditsEmoji(not_null<Main::Session*> session) {
} }
TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) { TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
return Ui::Text::SingleCustomEmoji( return Ui::Text::IconEmoji(
session->data().customEmojiManager().registerInternalEmoji( &st::starIconEmoji,
st::starIconSmall,
st::starIconSmallPadding,
true),
QString(QChar(0x2B50))); QString(QChar(0x2B50)));
} }
not_null<FlatLabel*> SetButtonMarkedLabel( not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<RpWidget*> button, not_null<RpWidget*> button,
rpl::producer<TextWithEntities> text, rpl::producer<TextWithEntities> text,
Fn<std::any(Fn<void()> update)> context, Text::MarkedContext context,
const style::FlatLabel &st, const style::FlatLabel &st,
const style::color *textFg) { const style::color *textFg) {
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>( const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
button, button,
rpl::single(QString()), rpl::single(QString()),
st); st);
context.repaint = [=] { buttonLabel->update(); };
rpl::duplicate( rpl::duplicate(
text text
) | rpl::filter([=](const TextWithEntities &text) { ) | rpl::filter([=](const TextWithEntities &text) {
return !text.text.isEmpty(); return !text.text.isEmpty();
}) | rpl::start_with_next([=](const TextWithEntities &text) { }) | rpl::start_with_next([=](const TextWithEntities &text) {
buttonLabel->setMarkedText( buttonLabel->setMarkedText(text, context);
text,
context([=] { buttonLabel->update(); }));
}, buttonLabel->lifetime()); }, buttonLabel->lifetime());
if (textFg) { if (textFg) {
buttonLabel->setTextColorOverride((*textFg)->c); buttonLabel->setTextColorOverride((*textFg)->c);
@ -565,15 +561,12 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const style::FlatLabel &st, const style::FlatLabel &st,
const style::color *textFg) { const style::color *textFg) {
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) { return SetButtonMarkedLabel(button, text, Core::TextContext({
return Core::MarkedTextContext{ .session = session,
.session = session, }), st, textFg);
.customEmojiRepaint = update,
};
}, st, textFg);
} }
void SendStarGift( void SendStarsForm(
not_null<Main::Session*> session, not_null<Main::Session*> session,
std::shared_ptr<Payments::CreditsFormData> data, std::shared_ptr<Payments::CreditsFormData> data,
Fn<void(std::optional<QString>)> done) { Fn<void(std::optional<QString>)> done) {

View file

@ -41,7 +41,7 @@ void SendCreditsBox(
not_null<FlatLabel*> SetButtonMarkedLabel( not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<RpWidget*> button, not_null<RpWidget*> button,
rpl::producer<TextWithEntities> text, rpl::producer<TextWithEntities> text,
Fn<std::any(Fn<void()> update)> context, Text::MarkedContext context,
const style::FlatLabel &st, const style::FlatLabel &st,
const style::color *textFg = nullptr); const style::color *textFg = nullptr);
@ -52,7 +52,7 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
const style::FlatLabel &st, const style::FlatLabel &st,
const style::color *textFg = nullptr); const style::color *textFg = nullptr);
void SendStarGift( void SendStarsForm(
not_null<Main::Session*> session, not_null<Main::Session*> session,
std::shared_ptr<Payments::CreditsFormData> data, std::shared_ptr<Payments::CreditsFormData> data,
Fn<void(std::optional<QString>)> done); Fn<void(std::optional<QString>)> done);

View file

@ -59,9 +59,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include <QtCore/QMimeData> #include <QtCore/QMimeData>
@ -722,6 +722,18 @@ void SendFilesBox::openDialogToAddFileToAlbum() {
crl::guard(this, callback)); 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() { void SendFilesBox::refreshButtons() {
clearButtons(); clearButtons();
@ -730,6 +742,15 @@ void SendFilesBox::refreshButtons() {
? tr::lng_send_button() ? tr::lng_send_button()
: tr::lng_create_group_next()), : tr::lng_create_group_next()),
[=] { send({}); }); [=] { 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) { if (_sendType == Api::SendType::Normal) {
SendMenu::SetupMenuAndShortcuts( SendMenu::SetupMenuAndShortcuts(
_send, _send,
@ -846,10 +867,9 @@ void SendFilesBox::refreshPriceTag() {
QString(), QString(),
st::paidTagLabel); st::paidTagLabel);
std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) { std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
label->setMarkedText(text, Core::MarkedTextContext{ label->setMarkedText(text, Core::TextContext({
.session = session, .session = session,
.customEmojiRepaint = [=] { label->update(); }, }));
});
}, label->lifetime()); }, label->lifetime());
label->show(); label->show();
label->sizeValue() | rpl::start_with_next([=](QSize size) { label->sizeValue() | rpl::start_with_next([=](QSize size) {
@ -1489,6 +1509,7 @@ void SendFilesBox::setupCaption() {
_caption->changes() _caption->changes()
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
checkCharsLimitation(); checkCharsLimitation();
refreshMessagesCount();
}, _caption->lifetime()); }, _caption->lifetime());
} }

View file

@ -246,6 +246,7 @@ private:
void addPreparedAsyncFile(Ui::PreparedFile &&file); void addPreparedAsyncFile(Ui::PreparedFile &&file);
void checkCharsLimitation(); void checkCharsLimitation();
void refreshMessagesCount();
[[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails( [[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails(
const SendFilesBoxDescriptor &descriptor); const SendFilesBoxDescriptor &descriptor);
@ -261,6 +262,7 @@ private:
Ui::PreparedList _list; Ui::PreparedList _list;
std::optional<int> _removingIndex; std::optional<int> _removingIndex;
rpl::variable<int> _messagesCount;
SendFilesLimits _limits = {}; SendFilesLimits _limits = {};
Fn<MenuDetails()> _sendMenuDetails; Fn<MenuDetails()> _sendMenuDetails;

View file

@ -123,12 +123,13 @@ private:
Ui::RoundImageCheckbox checkbox; Ui::RoundImageCheckbox checkbox;
Ui::Text::String name; Ui::Text::String name;
Ui::Animations::Simple nameActive; Ui::Animations::Simple nameActive;
bool locked = false; Api::MessageMoneyRestriction restriction;
RestrictionBadgeCache badgeCache;
}; };
void invalidateCache(); void invalidateCache();
bool showLockedError(not_null<Chat*> chat); bool showLockedError(not_null<Chat*> chat);
void refreshLockedRows(); void refreshRestrictedRows();
[[nodiscard]] int displayedChatsCount() const; [[nodiscard]] int displayedChatsCount() const;
[[nodiscard]] not_null<Data::Thread*> chatThread( [[nodiscard]] not_null<Data::Thread*> chatThread(
@ -137,7 +138,7 @@ private:
void paintChat(Painter &p, not_null<Chat*> chat, int index); void paintChat(Painter &p, not_null<Chat*> chat, int index);
void updateChat(not_null<PeerData*> peer); void updateChat(not_null<PeerData*> peer);
void updateChatName(not_null<Chat*> chat); void updateChatName(not_null<Chat*> chat);
void initChatLocked(not_null<Chat*> chat); void initChatRestriction(not_null<Chat*> chat);
void repaintChat(not_null<PeerData*> peer); void repaintChat(not_null<PeerData*> peer);
int chatIndex(not_null<PeerData*> peer) const; int chatIndex(not_null<PeerData*> peer) const;
void repaintChatAtIndex(int index); void repaintChatAtIndex(int index);
@ -517,9 +518,19 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
SendMenu::Details ShareBox::sendMenuDetails() const { SendMenu::Details ShareBox::sendMenuDetails() const {
const auto selected = _inner->selected(); const auto selected = _inner->selected();
const auto type = ranges::all_of( const auto hasPaid = [&] {
selected | ranges::views::transform(&Data::Thread::peer), for (const auto &thread : selected) {
HistoryView::CanScheduleUntilOnline) 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 ? SendMenu::Type::ScheduledToUser
: (selected.size() == 1 && selected.front()->peer()->isSelf()) : (selected.size() == 1 && selected.front()->peer()->isSelf())
? SendMenu::Type::Reminder ? SendMenu::Type::Reminder
@ -614,6 +625,9 @@ void ShareBox::createButtons() {
showMenu(send); showMenu(send);
} }
}, send->lifetime()); }, send->lifetime());
send->setText(PaidSendButtonText(
_starsToSend.value(),
tr::lng_share_confirm()));
} else if (_descriptor.copyCallback) { } else if (_descriptor.copyCallback) {
addButton(_copyLinkText.value(), [=] { copyLink(); }); addButton(_copyLinkText.value(), [=] { copyLink(); });
} }
@ -657,6 +671,73 @@ void ShareBox::innerSelectedChanged(
} }
void ShareBox::submit(Api::SendOptions options) { 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) { if (const auto onstack = _descriptor.submitCallback) {
const auto forwardOptions = (_forwardOptions.captionsCount const auto forwardOptions = (_forwardOptions.captionsCount
&& _forwardOptions.dropCaptions) && _forwardOptions.dropCaptions)
@ -665,8 +746,9 @@ void ShareBox::submit(Api::SendOptions options) {
? Data::ForwardOptions::NoSenderNames ? Data::ForwardOptions::NoSenderNames
: Data::ForwardOptions::PreserveInfo; : Data::ForwardOptions::PreserveInfo;
onstack( onstack(
_inner->selected(), std::move(threads),
_comment->entity()->getTextWithAppliedMarkdown(), checkPaid,
std::move(comment),
options, options,
forwardOptions); forwardOptions);
} }
@ -686,9 +768,23 @@ void ShareBox::selectedChanged() {
_comment->toggle(_hasSelected, anim::type::normal); _comment->toggle(_hasSelected, anim::type::normal);
_comment->resizeToWidth(st::boxWideWidth); _comment->resizeToWidth(st::boxWideWidth);
} }
computeStarsCount();
update(); 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) { void ShareBox::scrollTo(Ui::ScrollToRequest request) {
scrollToY(request.ymin, request.ymax); scrollToY(request.ymin, request.ymax);
//auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height(); //auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height();
@ -726,13 +822,13 @@ ShareBox::Inner::Inner(
_rowHeight = st::shareRowHeight; _rowHeight = st::shareRowHeight;
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
if (_descriptor.premiumRequiredError) { if (_descriptor.moneyRestrictionError) {
const auto session = _descriptor.session; const auto session = _descriptor.session;
rpl::merge( rpl::merge(
Data::AmPremiumValue(session) | rpl::to_empty, Data::AmPremiumValue(session) | rpl::to_empty,
session->api().premium().somePremiumRequiredResolved() session->api().premium().someMessageMoneyRestrictionsResolved()
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
refreshLockedRows(); refreshRestrictedRows();
}, lifetime()); }, lifetime());
} }
@ -793,38 +889,36 @@ void ShareBox::Inner::invalidateCache() {
} }
bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) { bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) {
if (!chat->locked) { if (!chat->restriction.premiumRequired) {
return false; return false;
} }
::Settings::ShowPremiumPromoToast( ::Settings::ShowPremiumPromoToast(
Main::MakeSessionShow(_show, _descriptor.session), Main::MakeSessionShow(_show, _descriptor.session),
ChatHelpers::ResolveWindowDefault(), ChatHelpers::ResolveWindowDefault(),
_descriptor.premiumRequiredError(chat->peer->asUser()).text, _descriptor.moneyRestrictionError(chat->peer->asUser()).text,
u"require_premium"_q); u"require_premium"_q);
return true; return true;
} }
void ShareBox::Inner::refreshLockedRows() { void ShareBox::Inner::refreshRestrictedRows() {
auto changed = false; auto changed = false;
for (const auto &[peer, data] : _dataMap) { for (const auto &[peer, data] : _dataMap) {
const auto history = data->history; const auto history = data->history;
const auto locked = (Api::ResolveRequiresPremiumToWrite( const auto restriction = Api::ResolveMessageMoneyRestrictions(
history->peer, history->peer,
history history);
) == Api::RequirePremiumState::Yes); if (data->restriction != restriction) {
if (data->locked != locked) { data->restriction = restriction;
data->locked = locked;
changed = true; changed = true;
} }
} }
for (const auto &data : d_byUsernameFiltered) { for (const auto &data : d_byUsernameFiltered) {
const auto history = data->history; const auto history = data->history;
const auto locked = (Api::ResolveRequiresPremiumToWrite( const auto restriction = Api::ResolveMessageMoneyRestrictions(
history->peer, history->peer,
history history);
) == Api::RequirePremiumState::Yes); if (data->restriction != restriction) {
if (data->locked != locked) { data->restriction = restriction;
data->locked = locked;
changed = true; changed = true;
} }
} }
@ -891,14 +985,14 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions()); chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
} }
void ShareBox::Inner::initChatLocked(not_null<Chat*> chat) { void ShareBox::Inner::initChatRestriction(not_null<Chat*> chat) {
if (_descriptor.premiumRequiredError) { if (_descriptor.moneyRestrictionError) {
const auto history = chat->history; const auto history = chat->history;
if (Api::ResolveRequiresPremiumToWrite( const auto restriction = Api::ResolveMessageMoneyRestrictions(
history->peer, history->peer,
history history);
) == Api::RequirePremiumState::Yes) { if (restriction || restriction.known) {
chat->locked = true; chat->restriction = restriction;
} }
} }
} }
@ -1024,14 +1118,15 @@ void ShareBox::Inner::loadProfilePhotos() {
void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) { void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
entry->chatListPreloadData(); entry->chatListPreloadData();
const auto history = entry->asHistory(); const auto history = entry->asHistory();
if (!_descriptor.premiumRequiredError || !history) { if (!_descriptor.moneyRestrictionError || !history) {
return; return;
} else if (Api::ResolveRequiresPremiumToWrite( } else if (!Api::ResolveMessageMoneyRestrictions(
history->peer, history->peer,
history history).known) {
) == Api::RequirePremiumState::Unknown) { if (const auto user = history->peer->asUser()) {
const auto user = history->peer->asUser(); const auto api = &_descriptor.session->api();
_descriptor.session->api().premium().resolvePremiumRequired(user); api->premium().resolveMessageMoneyRestrictions(user);
}
} }
} }
@ -1054,7 +1149,7 @@ auto ShareBox::Inner::getChat(not_null<Dialogs::Row*> row)
repaintChat(peer); repaintChat(peer);
})); }));
updateChatName(i->second.get()); updateChatName(i->second.get());
initChatLocked(i->second.get()); initChatRestriction(i->second.get());
row->attached = i->second.get(); row->attached = i->second.get();
return i->second.get(); return i->second.get();
} }
@ -1088,10 +1183,12 @@ void ShareBox::Inner::paintChat(
auto photoTop = st::sharePhotoTop; auto photoTop = st::sharePhotoTop;
chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth); chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
if (chat->locked) { if (chat->restriction) {
PaintPremiumRequiredLock( PaintRestrictionBadge(
p, p,
&_st.item, &_st.item,
chat->restriction.starsPerMessage,
chat->badgeCache,
x + photoLeft, x + photoLeft,
y + photoTop, y + photoTop,
outerWidth, outerWidth,
@ -1446,7 +1543,7 @@ void ShareBox::Inner::peopleReceived(
_st.item, _st.item,
[=] { repaintChat(peer); })); [=] { repaintChat(peer); }));
updateChatName(d_byUsernameFiltered.back().get()); 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( ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,
not_null<History*> history, not_null<History*> history,
@ -1510,12 +1616,14 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
const auto state = std::make_shared<State>(); const auto state = std::make_shared<State>();
return [=]( return [=](
std::vector<not_null<Data::Thread*>> &&result, std::vector<not_null<Data::Thread*>> &&result,
TextWithTags &&comment, Fn<bool()> checkPaid,
TextWithTags comment,
Api::SendOptions options, Api::SendOptions options,
Data::ForwardOptions forwardOptions) { Data::ForwardOptions forwardOptions) {
if (!state->requests.empty()) { if (!state->requests.empty()) {
return; // Share clicked already. return; // Share clicked already.
} }
const auto items = history->owner().idsToItems(msgIds); const auto items = history->owner().idsToItems(msgIds);
const auto existingIds = history->owner().itemsToIds(items); const auto existingIds = history->owner().itemsToIds(items);
if (existingIds.empty() || result.empty()) { if (existingIds.empty() || result.empty()) {
@ -1528,6 +1636,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
if (error.error) { if (error.error) {
show->showBox(MakeSendErrorBox(error, result.size() > 1)); show->showBox(MakeSendErrorBox(error, result.size() > 1));
return; return;
} else if (!checkPaid()) {
return;
} }
using Flag = MTPmessages_ForwardMessages::Flag; using Flag = MTPmessages_ForwardMessages::Flag;
@ -1576,6 +1686,12 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
: topicRootId; : topicRootId;
const auto peer = thread->peer(); const auto peer = thread->peer();
const auto threadHistory = thread->owningHistory(); const auto threadHistory = thread->owningHistory();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
options.starsApproved);
if (starsPaid) {
options.starsApproved -= starsPaid;
}
histories.sendRequest(threadHistory, requestType, [=]( histories.sendRequest(threadHistory, requestType, [=](
Fn<void()> finish) { Fn<void()> finish) {
const auto session = &threadHistory->session(); const auto session = &threadHistory->session();
@ -1587,7 +1703,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
: Flag(0)) : Flag(0))
| (options.shortcutId | (options.shortcutId
? Flag::f_quick_reply_shortcut ? Flag::f_quick_reply_shortcut
: Flag(0)); : Flag(0))
| (starsPaid ? Flag::f_allow_paid_stars : Flag());
threadHistory->sendRequestId = api.request( threadHistory->sendRequestId = api.request(
MTPmessages_ForwardMessages( MTPmessages_ForwardMessages(
MTP_flags(sendFlags), MTP_flags(sendFlags),
@ -1599,7 +1716,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
MTP_int(options.scheduled), MTP_int(options.scheduled),
MTP_inputPeerEmpty(), // send_as MTP_inputPeerEmpty(), // send_as
Data::ShortcutIdToMTP(session, options.shortcutId), 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) { )).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
threadHistory->session().api().applyUpdates(updates); threadHistory->session().api().applyUpdates(updates);
state->requests.remove(reqId); state->requests.remove(reqId);
@ -1621,7 +1739,11 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
finish(); finish();
}).fail([=](const MTP::Error &error) { }).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( show->showToast(
tr::lng_restricted_send_voice_messages( tr::lng_restricted_send_voice_messages(
tr::now, tr::now,
@ -1660,6 +1782,7 @@ ShareBoxStyleOverrides DarkShareBoxStyle() {
.comment = &st::groupCallShareBoxComment, .comment = &st::groupCallShareBoxComment,
.peerList = &st::groupCallShareBoxList, .peerList = &st::groupCallShareBoxList,
.label = &st::groupCallField, .label = &st::groupCallField,
.checkbox = &st::groupCallCheckbox,
.scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()), .scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()),
}; };
} }
@ -1716,7 +1839,7 @@ void FastShareMessage(
const auto requiresInline = item->requiresSendInlineRight(); const auto requiresInline = item->requiresSendInlineRight();
auto filterCallback = [=](not_null<Data::Thread*> thread) { auto filterCallback = [=](not_null<Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) { if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) { if (user->canSendIgnoreMoneyRestrictions()) {
return true; return true;
} }
} }
@ -1731,6 +1854,9 @@ void FastShareMessage(
show->show(Box<ShareBox>(ShareBox::Descriptor{ show->show(Box<ShareBox>(ShareBox::Descriptor{
.session = session, .session = session,
.copyCallback = std::move(copyLinkCallback), .copyCallback = std::move(copyLinkCallback),
.countMessagesCallback = ShareBox::DefaultForwardCountMessages(
history,
msgIds),
.submitCallback = ShareBox::DefaultForwardCallback( .submitCallback = ShareBox::DefaultForwardCallback(
show, show,
history, history,
@ -1742,7 +1868,7 @@ void FastShareMessage(
.captionsCount = ItemsForwardCaptionsCount(items), .captionsCount = ItemsForwardCaptionsCount(items),
.show = !hasOnlyForcedForwardedInfo, .show = !hasOnlyForcedForwardedInfo,
}, },
.premiumRequiredError = SharePremiumRequiredError(), .moneyRestrictionError = ShareMessageMoneyRestrictionError(),
}), Ui::LayerOption::CloseOther); }), Ui::LayerOption::CloseOther);
} }
@ -1770,8 +1896,12 @@ void FastShareLink(
QGuiApplication::clipboard()->setText(url); QGuiApplication::clipboard()->setText(url);
show->showToast(tr::lng_background_link_copied(tr::now)); show->showToast(tr::lng_background_link_copied(tr::now));
}; };
auto countMessagesCallback = [=](const TextWithTags &comment) {
return 1;
};
auto submitCallback = [=]( auto submitCallback = [=](
std::vector<not_null<::Data::Thread*>> &&result, std::vector<not_null<::Data::Thread*>> &&result,
Fn<bool()> checkPaid,
TextWithTags &&comment, TextWithTags &&comment,
Api::SendOptions options, Api::SendOptions options,
::Data::ForwardOptions) { ::Data::ForwardOptions) {
@ -1788,6 +1918,8 @@ void FastShareLink(
MakeSendErrorBox(error, result.size() > 1)); MakeSendErrorBox(error, result.size() > 1));
} }
return; return;
} else if (!checkPaid()) {
return;
} }
*sending = true; *sending = true;
@ -1815,7 +1947,7 @@ void FastShareLink(
}; };
auto filterCallback = [](not_null<::Data::Thread*> thread) { auto filterCallback = [](not_null<::Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) { if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) { if (user->canSendIgnoreMoneyRestrictions()) {
return true; return true;
} }
} }
@ -1825,16 +1957,17 @@ void FastShareLink(
Box<ShareBox>(ShareBox::Descriptor{ Box<ShareBox>(ShareBox::Descriptor{
.session = &show->session(), .session = &show->session(),
.copyCallback = std::move(copyCallback), .copyCallback = std::move(copyCallback),
.countMessagesCallback = std::move(countMessagesCallback),
.submitCallback = std::move(submitCallback), .submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback), .filterCallback = std::move(filterCallback),
.st = st, .st = st,
.premiumRequiredError = SharePremiumRequiredError(), .moneyRestrictionError = ShareMessageMoneyRestrictionError(),
}), }),
Ui::LayerOption::KeepOther, Ui::LayerOption::KeepOther,
anim::type::normal); anim::type::normal);
} }
auto SharePremiumRequiredError() auto ShareMessageMoneyRestrictionError()
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> { -> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> {
return WritePremiumRequiredError; return WriteMoneyRestrictionError;
} }

View file

@ -66,6 +66,7 @@ struct ShareBoxStyleOverrides {
const style::InputField *comment = nullptr; const style::InputField *comment = nullptr;
const style::PeerList *peerList = nullptr; const style::PeerList *peerList = nullptr;
const style::InputField *label = nullptr; const style::InputField *label = nullptr;
const style::Checkbox *checkbox = nullptr;
std::shared_ptr<HistoryView::ScheduleBoxStyleArgs> scheduleBox; std::shared_ptr<HistoryView::ScheduleBoxStyleArgs> scheduleBox;
}; };
[[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle(); [[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle();
@ -87,20 +88,25 @@ void FastShareLink(
const QString &url, const QString &url,
ShareBoxStyleOverrides st = {}); ShareBoxStyleOverrides st = {});
struct RecipientPremiumRequiredError; struct RecipientMoneyRestrictionError;
[[nodiscard]] auto SharePremiumRequiredError() [[nodiscard]] auto ShareMessageMoneyRestrictionError()
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)>; -> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)>;
class ShareBox final : public Ui::BoxContent { class ShareBox final : public Ui::BoxContent {
public: public:
using CopyCallback = Fn<void()>; using CopyCallback = Fn<void()>;
using CountMessagesCallback = Fn<int(const TextWithTags&)>;
using SubmitCallback = Fn<void( using SubmitCallback = Fn<void(
std::vector<not_null<Data::Thread*>>&&, std::vector<not_null<Data::Thread*>>&&,
Fn<bool()> checkPaid,
TextWithTags&&, TextWithTags&&,
Api::SendOptions, Api::SendOptions,
Data::ForwardOptions)>; Data::ForwardOptions)>;
using FilterCallback = Fn<bool(not_null<Data::Thread*>)>; using FilterCallback = Fn<bool(not_null<Data::Thread*>)>;
[[nodiscard]] static auto DefaultForwardCountMessages(
not_null<History*> history,
MessageIdsList msgIds) -> CountMessagesCallback;
[[nodiscard]] static SubmitCallback DefaultForwardCallback( [[nodiscard]] static SubmitCallback DefaultForwardCallback(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,
not_null<History*> history, not_null<History*> history,
@ -110,6 +116,7 @@ public:
struct Descriptor { struct Descriptor {
not_null<Main::Session*> session; not_null<Main::Session*> session;
CopyCallback copyCallback; CopyCallback copyCallback;
CountMessagesCallback countMessagesCallback;
SubmitCallback submitCallback; SubmitCallback submitCallback;
FilterCallback filterCallback; FilterCallback filterCallback;
object_ptr<Ui::RpWidget> bottomWidget = { nullptr }; object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
@ -123,8 +130,9 @@ public:
bool show = false; bool show = false;
} forwardOptions; } forwardOptions;
using PremiumRequiredError = RecipientPremiumRequiredError; using MoneyRestrictionError = RecipientMoneyRestrictionError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError; Fn<MoneyRestrictionError(
not_null<UserData*>)> moneyRestrictionError;
}; };
ShareBox(QWidget*, Descriptor &&descriptor); ShareBox(QWidget*, Descriptor &&descriptor);
@ -149,6 +157,7 @@ private:
void needSearchByUsername(); void needSearchByUsername();
void applyFilterUpdate(const QString &query); void applyFilterUpdate(const QString &query);
void selectedChanged(); void selectedChanged();
void computeStarsCount();
void createButtons(); void createButtons();
int getTopScrollSkip() const; int getTopScrollSkip() const;
int getBottomScrollSkip() const; int getBottomScrollSkip() const;
@ -180,6 +189,7 @@ private:
bool _hasSelected = false; bool _hasSelected = false;
rpl::variable<QString> _copyLinkText; rpl::variable<QString> _copyLinkText;
rpl::variable<int> _starsToSend;
base::Timer _searchTimer; base::Timer _searchTimer;
QString _peopleQuery; QString _peopleQuery;
@ -195,5 +205,6 @@ private:
PeopleQueries _peopleQueries; PeopleQueries _peopleQueries;
Ui::Animations::Simple _scrollAnimation; Ui::Animations::Simple _scrollAnimation;
rpl::lifetime _submitLifetime;
}; };

View file

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_credits.h" #include "data/data_credits.h"
#include "data/data_document.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/checkbox.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/wrap/slide_wrap.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/section_widget.h" #include "window/section_widget.h"
#include "window/window_session_controller.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 kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
constexpr auto kCrossfadeDuration = crl::time(400); constexpr auto kCrossfadeDuration = crl::time(400);
constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000); constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000);
constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000);
using namespace HistoryView; using namespace HistoryView;
using namespace Info::PeerGifts; using namespace Info::PeerGifts;
@ -126,6 +129,7 @@ struct GiftDetails {
uint64 randomId = 0; uint64 randomId = 0;
bool anonymous = false; bool anonymous = false;
bool upgraded = false; bool upgraded = false;
bool byStars = false;
}; };
class PreviewDelegate final : public DefaultElementDelegate { class PreviewDelegate final : public DefaultElementDelegate {
@ -227,7 +231,7 @@ auto GenerateGiftMedia(
TextWithEntities text, TextWithEntities text,
QMargins margins = {}, QMargins margins = {},
const base::flat_map<uint16, ClickHandlerPtr> &links = {}, const base::flat_map<uint16, ClickHandlerPtr> &links = {},
const std::any &context = {}) { Ui::Text::MarkedContext context = {}) {
if (text.empty()) { if (text.empty()) {
return; return;
} }
@ -236,7 +240,7 @@ auto GenerateGiftMedia(
margins, margins,
st::defaultTextStyle, st::defaultTextStyle,
links, links,
context)); std::move(context)));
}; };
const auto sticker = [=] { const auto sticker = [=] {
@ -306,10 +310,10 @@ auto GenerateGiftMedia(
auto description = data.text.empty() auto description = data.text.empty()
? std::move(textFallback) ? std::move(textFallback)
: data.text; : data.text;
const auto context = Core::MarkedTextContext{ const auto context = Core::TextContext({
.session = &parent->history()->session(), .session = &parent->history()->session(),
.customEmojiRepaint = [parent] { parent->repaint(); }, .repaint = [parent] { parent->repaint(); },
}; });
pushText( pushText(
std::move(title), std::move(title),
st::giftBoxPreviewTitlePadding, st::giftBoxPreviewTitlePadding,
@ -495,7 +499,14 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
std::move(details) | rpl::start_with_next([=](GiftDetails details) { std::move(details) | rpl::start_with_next([=](GiftDetails details) {
const auto &descriptor = details.descriptor; const auto &descriptor = details.descriptor;
const auto cost = v::match(descriptor, [&](GiftTypePremium data) { 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) { }, [&](GiftTypeStars data) {
const auto stars = data.info.stars const auto stars = data.info.stars
+ (details.upgraded ? data.info.starsToUpgrade : 0); + (details.upgraded ? data.info.starsToUpgrade : 0);
@ -622,14 +633,27 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
list.reserve(options.size()); list.reserve(options.size());
auto minMonthsGift = GiftTypePremium(); auto minMonthsGift = GiftTypePremium();
for (const auto &option : options) { for (const auto &option : options) {
list.push_back({ if (option.currency != kCreditsCurrency) {
.cost = option.cost, list.push_back({
.currency = option.currency, .cost = option.cost,
.months = option.months, .currency = option.currency,
}); .months = option.months,
if (!minMonthsGift.months });
|| option.months < minMonthsGift.months) { if (!minMonthsGift.months
minMonthsGift = list.back(); || 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) { for (auto &gift : list) {
@ -735,15 +759,11 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
} }
auto &manager = session->data().customEmojiManager(); auto &manager = session->data().customEmojiManager();
auto result = Text::String(); auto result = Text::String();
const auto context = Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [] {},
};
result.setMarkedText( result.setMarkedText(
st::semiboldTextStyle, st::semiboldTextStyle,
manager.creditsEmoji().append(QString::number(price)), manager.creditsEmoji().append(QString::number(price)),
kMarkupTextOptions, kMarkupTextOptions,
context); Core::TextContext({ .session = session }));
return result; return result;
} }
@ -1103,16 +1123,35 @@ void SendGift(
std::shared_ptr<Api::PremiumGiftCodeOptions> api, std::shared_ptr<Api::PremiumGiftCodeOptions> api,
const GiftDetails &details, const GiftDetails &details,
Fn<void(Payments::CheckoutResult)> done) { Fn<void(Payments::CheckoutResult)> done) {
const auto processNonPanelPaymentFormFactory
= Payments::ProcessNonPanelPaymentFormFactory(window, done);
v::match(details.descriptor, [&](const GiftTypePremium &gift) { v::match(details.descriptor, [&](const GiftTypePremium &gift) {
auto invoice = api->invoice(1, gift.months); if (details.byStars && gift.stars) {
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ auto invoice = Payments::InvoicePremiumGiftCode{
.users = { peer->asUser() }, .purpose = Payments::InvoicePremiumGiftCodeUsers{
.message = details.text, .users = { peer->asUser() },
}; .message = details.text,
Payments::CheckoutProcess::Start(std::move(invoice), done); },
.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 GiftTypeStars &gift) {
const auto processNonPanelPaymentFormFactory
= Payments::ProcessNonPanelPaymentFormFactory(window, done);
Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{ Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{
.giftId = gift.info.id, .giftId = gift.info.id,
.randomId = details.randomId, .randomId = details.randomId,
@ -1279,12 +1318,6 @@ void AddUpgradeButton(
button->toggleOn(rpl::single(false))->toggledValue( button->toggleOn(rpl::single(false))->toggledValue(
) | rpl::start_with_next(toggled, button->lifetime()); ) | 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(); auto star = session->data().customEmojiManager().creditsEmoji();
const auto label = Ui::CreateChild<Ui::FlatLabel>( const auto label = Ui::CreateChild<Ui::FlatLabel>(
button, button,
@ -1296,7 +1329,7 @@ void AddUpgradeButton(
Text::WithEntities), Text::WithEntities),
st::boxLabel, st::boxLabel,
st::defaultPopupMenu, st::defaultPopupMenu,
std::move(makeContext)); Core::TextContext({ .session = session }));
label->show(); label->show();
label->setAttribute(Qt::WA_TransparentForMouseEvents); label->setAttribute(Qt::WA_TransparentForMouseEvents);
button->widthValue() | rpl::start_with_next([=](int outer) { button->widthValue() | rpl::start_with_next([=](int outer) {
@ -1424,6 +1457,7 @@ void SendGiftBox(
struct State { struct State {
rpl::variable<GiftDetails> details; rpl::variable<GiftDetails> details;
rpl::variable<bool> messageAllowed;
std::shared_ptr<Data::DocumentMedia> media; std::shared_ptr<Data::DocumentMedia> media;
bool submitting = false; bool submitting = false;
}; };
@ -1432,13 +1466,25 @@ void SendGiftBox(
.descriptor = descriptor, .descriptor = descriptor,
.randomId = base::RandomValue<uint64>(), .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( auto cost = state->details.value(
) | rpl::map([session](const GiftDetails &details) { ) | rpl::map([session](const GiftDetails &details) {
return v::match(details.descriptor, [&](const GiftTypePremium &data) { 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( return CreditsEmojiSmall(session).append(
Lang::FormatCountDecimal(std::abs(data.cost))); Lang::FormatCountDecimal(std::abs(stars)));
} }
return TextWithEntities{ return TextWithEntities{
FillAmountAndCurrency(data.cost, data.currency), FillAmountAndCurrency(data.cost, data.currency),
@ -1462,10 +1508,17 @@ void SendGiftBox(
peer, peer,
state->details.value())); 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 limit = StarGiftMessageLimit(session);
const auto text = AddPartInput( const auto text = AddPartInput(
window, window,
container, messageInner,
box->getDelegate()->outerContainer(), box->getDelegate()->outerContainer(),
tr::lng_gift_send_message(), tr::lng_gift_send_message(),
QString(), QString(),
@ -1509,7 +1562,6 @@ void SendGiftBox(
text, text,
session, session,
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }); { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
if (stars) { if (stars) {
const auto cost = stars->info.starsToUpgrade; const auto cost = stars->info.starsToUpgrade;
if (cost > 0 && !peer->isSelf()) { if (cost > 0 && !peer->isSelf()) {
@ -1551,20 +1603,73 @@ void SendGiftBox(
}, container->lifetime()); }, container->lifetime());
AddSkip(container); AddSkip(container);
} }
v::match(descriptor, [&](const GiftTypePremium &) { v::match(descriptor, [&](const GiftTypePremium &data) {
AddDividerText(container, tr::lng_gift_send_premium_about( AddDividerText(messageInner, tr::lng_gift_send_premium_about(
lt_user, lt_user,
rpl::single(peer->shortName()))); 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 &) { }, [&](const GiftTypeStars &) {
AddDividerText(container, peer->isSelf() AddDividerText(container, peer->isSelf()
? tr::lng_gift_send_anonymous_self() ? tr::lng_gift_send_anonymous_self()
: peer->isBroadcast() : peer->isBroadcast()
? tr::lng_gift_send_anonymous_about_channel() ? tr::lng_gift_send_anonymous_about_channel()
: tr::lng_gift_send_anonymous_about( : rpl::conditional(
lt_user, state->messageAllowed.value(),
rpl::single(peer->shortName()), tr::lng_gift_send_anonymous_about(
lt_recipient, lt_user,
rpl::single(peer->shortName()))); 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 const auto buttonWidth = st::boxWideWidth
@ -1575,13 +1680,20 @@ void SendGiftBox(
return; return;
} }
state->submitting = true; 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 weak = MakeWeak(box);
const auto done = [=](Payments::CheckoutResult result) { const auto done = [=](Payments::CheckoutResult result) {
if (result == Payments::CheckoutResult::Paid) { if (result == Payments::CheckoutResult::Paid) {
if (details.byStars
|| v::is<GiftTypeStars>(details.descriptor)) {
window->session().credits().load(true);
}
const auto copy = state->media; const auto copy = state->media;
window->showPeerHistory(peer); window->showPeerHistory(peer);
ShowSentToast(window, descriptor, details); ShowSentToast(window, details.descriptor, details);
} }
if (const auto strong = weak.data()) { if (const auto strong = weak.data()) {
box->closeBox(); box->closeBox();
@ -1814,6 +1926,8 @@ void GiftBox(
box->setCustomCornersFilling(RectPart::FullTop); box->setCustomCornersFilling(RectPart::FullTop);
box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); }); box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
window->session().credits().load();
FillBg(box); FillBg(box);
const auto &stUser = st::premiumGiftsUserpicButton; const auto &stUser = st::premiumGiftsUserpicButton;
@ -2070,7 +2184,66 @@ void ChooseStarGiftRecipient(
void ShowStarGiftBox( void ShowStarGiftBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<PeerData*> peer) { 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( void AddUniqueGiftCover(

View file

@ -150,10 +150,7 @@ void TranslateBox(
original->entity()->setAnimationsPausedCallback(animationsPaused); original->entity()->setAnimationsPausedCallback(animationsPaused);
original->entity()->setMarkedText( original->entity()->setMarkedText(
text, text,
Core::MarkedTextContext{ Core::TextContext({ .session = &peer->session() }));
.session = &peer->session(),
.customEmojiRepaint = [=] { original->entity()->update(); },
});
original->setMinimalHeight(lineHeight); original->setMinimalHeight(lineHeight);
original->hide(anim::type::instant); original->hide(anim::type::instant);
@ -221,10 +218,7 @@ void TranslateBox(
const auto label = translated->entity(); const auto label = translated->entity();
label->setMarkedText( label->setMarkedText(
text, text,
Core::MarkedTextContext{ Core::TextContext({ .session = &peer->session() }));
.session = &peer->session(),
.customEmojiRepaint = [=] { label->update(); },
});
translated->show(anim::type::instant); translated->show(anim::type::instant);
loading->hide(anim::type::instant); loading->hide(anim::type::instant);
}; };

View file

@ -132,8 +132,12 @@ object_ptr<ShareBox> ShareInviteLinkBox(
QGuiApplication::clipboard()->setText(currentLink()); QGuiApplication::clipboard()->setText(currentLink());
show->showToast(tr::lng_group_invite_copied(tr::now)); show->showToast(tr::lng_group_invite_copied(tr::now));
}; };
auto countMessagesCallback = [=](const TextWithTags &comment) {
return 1;
};
auto submitCallback = [=]( auto submitCallback = [=](
std::vector<not_null<Data::Thread*>> &&result, std::vector<not_null<Data::Thread*>> &&result,
Fn<bool()> checkPaid,
TextWithTags &&comment, TextWithTags &&comment,
Api::SendOptions options, Api::SendOptions options,
Data::ForwardOptions) { Data::ForwardOptions) {
@ -150,6 +154,8 @@ object_ptr<ShareBox> ShareInviteLinkBox(
MakeSendErrorBox(error, result.size() > 1)); MakeSendErrorBox(error, result.size() > 1));
} }
return; return;
} else if (!checkPaid()) {
return;
} }
*sending = true; *sending = true;
@ -178,7 +184,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
}; };
auto filterCallback = [](not_null<Data::Thread*> thread) { auto filterCallback = [](not_null<Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) { if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) { if (user->canSendIgnoreMoneyRestrictions()) {
return true; return true;
} }
} }
@ -189,6 +195,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
auto result = Box<ShareBox>(ShareBox::Descriptor{ auto result = Box<ShareBox>(ShareBox::Descriptor{
.session = &peer->session(), .session = &peer->session(),
.copyCallback = std::move(copyCallback), .copyCallback = std::move(copyCallback),
.countMessagesCallback = std::move(countMessagesCallback),
.submitCallback = std::move(submitCallback), .submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback), .filterCallback = std::move(filterCallback),
.bottomWidget = std::move(bottom), .bottomWidget = std::move(bottom),
@ -199,7 +206,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
tr::lng_group_call_copy_speaker_link(), tr::lng_group_call_copy_speaker_link(),
tr::lng_group_call_copy_listener_link()), tr::lng_group_call_copy_listener_link()),
.st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(), .st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(),
.premiumRequiredError = SharePremiumRequiredError(), .moneyRestrictionError = ShareMessageMoneyRestrictionError(),
}); });
*box = result.data(); *box = result.data();
return result; return result;

View file

@ -149,6 +149,7 @@ EmojiButton {
SendButton { SendButton {
inner: IconButton; inner: IconButton;
stars: RoundButton;
record: icon; record: icon;
recordOver: icon; recordOver: icon;
round: icon; round: icon;
@ -855,6 +856,10 @@ historyComposeButton: FlatButton {
color: historyComposeButtonBgRipple; color: historyComposeButtonBgRipple;
} }
} }
historyComposeButtonText: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
textFg: windowActiveTextFg;
}
historyGiftToChannel: IconButton(defaultIconButton) { historyGiftToChannel: IconButton(defaultIconButton) {
width: 46px; width: 46px;
height: 46px; height: 46px;
@ -913,6 +918,10 @@ historyBusinessBotSettings: IconButton(defaultIconButton) {
height: 58px; height: 58px;
width: 48px; width: 48px;
} }
paysStatusLabel: FlatLabel(historyBusinessBotStatus) {
align: align(top);
minWidth: 240px;
}
historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }}; historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }};
historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }}; historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }};
@ -1289,6 +1298,12 @@ historySend: SendButton {
icon: historySendIcon; icon: historySendIcon;
iconOver: historySendIconOver; iconOver: historySendIconOver;
} }
stars: RoundButton(defaultActiveButton) {
height: 28px;
padding: margins(0px, 0px, 6px, 0px);
textTop: 5px;
width: -8px;
}
record: historyRecordVoice; record: historyRecordVoice;
recordOver: historyRecordVoiceOver; recordOver: historyRecordVoiceOver;
round: historyRecordRound; round: historyRecordRound;

View file

@ -1747,7 +1747,8 @@ void InitFieldAutocomplete(
&& peer->isUser() && peer->isUser()
&& !peer->asUser()->isBot() && !peer->asUser()->isBot()
&& (!shortcutMessages && (!shortcutMessages
|| shortcutMessages->shortcuts().list.empty())) { || shortcutMessages->shortcuts().list.empty()
|| peer->starsPerMessageChecked() != 0)) {
parsed = {}; parsed = {};
} }
raw->showFiltered(peer, parsed.query, parsed.fromStart); raw->showFiltered(peer, parsed.query, parsed.fromStart);

View file

@ -695,14 +695,15 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(
} }
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult( GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(
not_null<InlineResult*> result) { std::shared_ptr<InlineResult> result) {
auto it = _inlineLayouts.find(result); const auto raw = result.get();
auto it = _inlineLayouts.find(raw);
if (it == _inlineLayouts.cend()) { if (it == _inlineLayouts.cend()) {
if (auto layout = LayoutItem::createLayout( if (auto layout = LayoutItem::createLayout(
this, this,
result, std::move(result),
_inlineWithThumb)) { _inlineWithThumb)) {
it = _inlineLayouts.emplace(result, std::move(layout)).first; it = _inlineLayouts.emplace(raw, std::move(layout)).first;
it->second->initDimensions(); it->second->initDimensions();
} else { } else {
return nullptr; return nullptr;
@ -775,8 +776,8 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result
from, from,
count count
) | ranges::views::transform([&]( ) | ranges::views::transform([&](
const std::unique_ptr<InlineBots::Result> &r) { const std::shared_ptr<InlineBots::Result> &r) {
return layoutPrepareInlineResult(r.get()); return layoutPrepareInlineResult(r);
}) | ranges::views::filter([](const LayoutItem *item) { }) | ranges::views::filter([](const LayoutItem *item) {
return item != nullptr; return item != nullptr;
}) | ranges::to<std::vector<not_null<LayoutItem*>>>; }) | ranges::to<std::vector<not_null<LayoutItem*>>>;
@ -799,7 +800,7 @@ int GifsListWidget::validateExistingInlineRows(const InlineResults &results) {
const auto until = _mosaic.validateExistingRows([&]( const auto until = _mosaic.validateExistingRows([&](
not_null<const LayoutItem*> item, not_null<const LayoutItem*> item,
int untilIndex) { int untilIndex) {
return item->getResult() != results[untilIndex].get(); return item->getResult().get() != results[untilIndex].get();
}, results.size()); }, results.size());
if (_mosaic.empty()) { if (_mosaic.empty()) {

View file

@ -131,7 +131,7 @@ private:
}; };
using InlineResult = InlineBots::Result; 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; using LayoutItem = InlineBots::Layout::ItemBase;
struct InlineCacheEntry { struct InlineCacheEntry {
@ -162,7 +162,8 @@ private:
void clearInlineRows(bool resultsDeleted); void clearInlineRows(bool resultsDeleted);
LayoutItem *layoutPrepareSavedGif(not_null<DocumentData*> document); LayoutItem *layoutPrepareSavedGif(not_null<DocumentData*> document);
LayoutItem *layoutPrepareInlineResult(not_null<InlineResult*> result); LayoutItem *layoutPrepareInlineResult(
std::shared_ptr<InlineResult> result);
void deleteUnusedGifLayouts(); void deleteUnusedGifLayouts();

View file

@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_credits.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
#include "base/qt/qt_common_adapters.h" #include "base/qt/qt_common_adapters.h"
@ -432,12 +433,9 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
const auto session = args.session; const auto session = args.session;
field->setTagMimeProcessor( field->setTagMimeProcessor(
FieldTagMimeProcessor(session, args.allowPremiumEmoji)); FieldTagMimeProcessor(session, args.allowPremiumEmoji));
field->setCustomTextContext([=](Fn<void()> repaint) { field->setCustomTextContext(Core::TextContext({
return std::any(Core::MarkedTextContext{ .session = session
.session = session, }), [paused] {
.customEmojiRepaint = std::move(repaint),
});
}, [paused] {
return On(PowerSaving::kEmojiChat) || paused(); return On(PowerSaving::kEmojiChat) || paused();
}, [paused] { }, [paused] {
return On(PowerSaving::kChatSpoiler) || paused(); return On(PowerSaving::kChatSpoiler) || paused();
@ -1280,3 +1278,26 @@ void SelectTextInFieldWithMargins(
textCursor.setPosition(selection.to, QTextCursor::KeepAnchor); textCursor.setPosition(selection.to, QTextCursor::KeepAnchor);
field->setTextCursor(textCursor); 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);
});
}

View file

@ -19,6 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QClipboard> #include <QtGui/QClipboard>
namespace tr {
struct now_t;
} // namespace tr
namespace Main { namespace Main {
class Session; class Session;
class SessionShow; class SessionShow;
@ -169,3 +173,8 @@ private:
void SelectTextInFieldWithMargins( void SelectTextInFieldWithMargins(
not_null<Ui::InputField*> field, not_null<Ui::InputField*> field,
const TextSelection &selection); 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);

View file

@ -37,7 +37,7 @@ std::map<int, const char*> BetaLogs() {
"- Nice looking code blocks with syntax highlight.\n" "- Nice looking code blocks with syntax highlight.\n"
"- Copy full code block by click on its header.\n" "- Copy full code block by click on its header.\n"
"- Send a highlighted code block using ```language syntax.\n" "- Send a highlighted code block using ```language syntax.\n"
} }
}; };

View file

@ -205,13 +205,13 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
}); });
}; };
if (_bot->isVerified() if (_bot->isVerified()
|| _bot->session().local().isBotTrustedOpenGame(_bot->id)) { || _bot->session().local().isPeerTrustedOpenGame(_bot->id)) {
openGame(); openGame();
} else { } else {
if (const auto controller = my.sessionWindow.get()) { if (const auto controller = my.sessionWindow.get()) {
const auto callback = [=, bot = _bot](Fn<void()> close) { const auto callback = [=, bot = _bot](Fn<void()> close) {
close(); close();
bot->session().local().markBotTrustedOpenGame(bot->id); bot->session().local().markPeerTrustedOpenGame(bot->id);
openGame(); openGame();
}; };
controller->show(Ui::MakeConfirmBox({ controller->show(Ui::MakeConfirmBox({

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/edit_birthday_box.h" #include "ui/boxes/edit_birthday_box.h"
#include "ui/integration.h" #include "ui/integration.h"
#include "payments/payments_non_panel_process.h" #include "payments/payments_non_panel_process.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "boxes/connection_box.h" #include "boxes/connection_box.h"
#include "boxes/gift_premium_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_domain.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.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 "inline_bots/bot_attach_web_view.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
@ -1018,6 +1021,45 @@ bool CopyUsername(
return true; 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( bool ShowStarsExamples(
Window::SessionController *controller, Window::SessionController *controller,
const Match &match, const Match &match,
@ -1529,6 +1571,14 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q, u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
CopyUsername, CopyUsername,
}, },
{
u"^edit_paid_messages_fee/([0-9]+)$"_q,
EditPaidMessagesFee,
},
{
u"^common_groups/([0-9]+)$"_q,
ShowCommonGroups,
},
{ {
u"^stars_examples$"_q, u"^stars_examples$"_q,
ShowStarsExamples, ShowStarsExamples,

View file

@ -76,6 +76,14 @@ const auto CommandByName = base::flat_map<QString, Command>{
{ u"first_chat"_q , Command::ChatFirst }, { u"first_chat"_q , Command::ChatFirst },
{ u"last_chat"_q , Command::ChatLast }, { u"last_chat"_q , Command::ChatLast },
{ u"self_chat"_q , Command::ChatSelf }, { 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"previous_folder"_q , Command::FolderPrevious },
{ u"next_folder"_q , Command::FolderNext }, { u"next_folder"_q , Command::FolderNext },
@ -168,6 +176,7 @@ private:
void set(const QKeySequence &result, Command command, bool replace); void set(const QKeySequence &result, Command command, bool replace);
void remove(const QString &keys); void remove(const QString &keys);
void remove(const QKeySequence &keys); void remove(const QKeySequence &keys);
void remove(const QKeySequence &keys, Command command);
void unregister(base::unique_qptr<QAction> shortcut); void unregister(base::unique_qptr<QAction> shortcut);
void pruneListened(); void pruneListened();
@ -293,7 +302,7 @@ void Manager::change(
Command command, Command command,
std::optional<Command> restore) { std::optional<Command> restore) {
if (!was.isEmpty()) { if (!was.isEmpty()) {
remove(was); remove(was, command);
} }
if (!now.isEmpty()) { if (!now.isEmpty()) {
set(now, command, true); set(now, command, true);
@ -397,6 +406,7 @@ bool Manager::readCustomFile() {
const auto entry = (*i).toObject(); const auto entry = (*i).toObject();
const auto keys = entry.constFind(u"keys"_q); const auto keys = entry.constFind(u"keys"_q);
const auto command = entry.constFind(u"command"_q); const auto command = entry.constFind(u"command"_q);
const auto removed = entry.constFind(u"removed"_q);
if (keys == entry.constEnd() if (keys == entry.constEnd()
|| command == entry.constEnd() || command == entry.constEnd()
|| !(*keys).isString() || !(*keys).isString()
@ -410,7 +420,11 @@ bool Manager::readCustomFile() {
const auto name = (*command).toString(); const auto name = (*command).toString();
const auto i = CommandByName.find(name); const auto i = CommandByName.find(name);
if (i != end(CommandByName)) { 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 { } else {
LOG(("Shortcut Warning: " LOG(("Shortcut Warning: "
"could not find shortcut command handler '%1'" "could not find shortcut command handler '%1'"
@ -565,12 +579,36 @@ void Manager::writeCustomFile() {
} }
} }
} }
for (const auto &[sequence, command] : _defaults) { const auto has = [&](not_null<QObject*> shortcut, Command command) {
if (!_shortcuts.contains(sequence)) { 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; QJsonObject entry;
entry.insert(u"keys"_q, sequence.toString().toLower()); entry.insert(u"keys"_q, sequence.toString().toLower());
entry.insert(u"command"_q, QJsonValue()); entry.insert(u"command"_q, QJsonValue());
shortcuts.append(entry); 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) { void Manager::unregister(base::unique_qptr<QAction> shortcut) {
if (shortcut) { if (shortcut) {
_commandByObject.removeAll(shortcut.get()); _commandByObject.removeAll(shortcut.get());

View file

@ -60,6 +60,11 @@ public:
normalize(); normalize();
return *this; return *this;
} }
inline StarsAmount operator-() const {
auto result = *this;
result *= -1;
return result;
}
friend inline auto operator<=>(StarsAmount, StarsAmount) = default; friend inline auto operator<=>(StarsAmount, StarsAmount) = default;
friend inline bool operator==(StarsAmount, StarsAmount) = default; friend inline bool operator==(StarsAmount, StarsAmount) = default;
@ -97,3 +102,7 @@ private:
[[nodiscard]] inline StarsAmount operator*(StarsAmount a, int64 b) { [[nodiscard]] inline StarsAmount operator*(StarsAmount a, int64 b) {
return a *= b; return a *= b;
} }
[[nodiscard]] inline StarsAmount operator*(int64 a, StarsAmount b) {
return b *= a;
}

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "iv/iv_instance.h" #include "iv/iv_instance.h"
#include "ui/text/text_custom_emoji.h" #include "ui/text/text_custom_emoji.h"
#include "ui/text/text_utilities.h"
#include "ui/basic_click_handlers.h" #include "ui/basic_click_handlers.h"
#include "ui/emoji_config.h" #include "ui/emoji_config.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -112,6 +113,40 @@ const auto kBadPrefix = u"http://"_q;
} // namespace } // 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) { void UiIntegration::postponeCall(FnMut<void()> &&callable) {
Sandbox::Instance().postponeCall(std::move(callable)); Sandbox::Instance().postponeCall(std::move(callable));
} }
@ -152,8 +187,8 @@ bool UiIntegration::screenIsLocked() {
std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler( std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
const EntityLinkData &data, const EntityLinkData &data,
const std::any &context) { const Ui::Text::MarkedContext &context) {
const auto my = std::any_cast<MarkedTextContext>(&context); const auto my = std::any_cast<Core::TextContextDetails>(&context.other);
switch (data.type) { switch (data.type) {
case EntityType::Url: case EntityType::Url:
return (!data.data.isEmpty() return (!data.data.isEmpty()
@ -170,7 +205,7 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
return std::make_shared<BotCommandClickHandler>(data.data); return std::make_shared<BotCommandClickHandler>(data.data);
case EntityType::Hashtag: case EntityType::Hashtag:
using HashtagMentionType = MarkedTextContext::HashtagMentionType; using HashtagMentionType = TextContextDetails::HashtagMentionType;
if (my && my->type == HashtagMentionType::Twitter) { if (my && my->type == HashtagMentionType::Twitter) {
return std::make_shared<UrlClickHandler>( return std::make_shared<UrlClickHandler>(
(u"https://twitter.com/hashtag/"_q (u"https://twitter.com/hashtag/"_q
@ -190,7 +225,7 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
return std::make_shared<CashtagClickHandler>(data.data); return std::make_shared<CashtagClickHandler>(data.data);
case EntityType::Mention: case EntityType::Mention:
using HashtagMentionType = MarkedTextContext::HashtagMentionType; using HashtagMentionType = TextContextDetails::HashtagMentionType;
if (my && my->type == HashtagMentionType::Twitter) { if (my && my->type == HashtagMentionType::Twitter) {
return std::make_shared<UrlClickHandler>( return std::make_shared<UrlClickHandler>(
u"https://twitter.com/"_q + data.data.mid(1), u"https://twitter.com/"_q + data.data.mid(1),
@ -222,7 +257,9 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
case EntityType::Pre: case EntityType::Pre:
return std::make_shared<MonospaceClickHandler>(data.text, data.type); return std::make_shared<MonospaceClickHandler>(data.text, data.type);
case EntityType::Phone: 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); return Integration::createLinkHandler(data, context);
} }
@ -280,36 +317,6 @@ bool UiIntegration::copyPreOnClick(const QVariant &context) {
return true; 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() { rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
return Core::App().passcodeLockChanges() | rpl::to_empty; return Core::App().passcodeLockChanges() | rpl::to_empty;
} }

View file

@ -19,7 +19,7 @@ class ElementDelegate;
namespace Core { namespace Core {
struct MarkedTextContext { struct TextContextDetails {
enum class HashtagMentionType : uchar { enum class HashtagMentionType : uchar {
Telegram, Telegram,
Twitter, Twitter,
@ -28,9 +28,15 @@ struct MarkedTextContext {
Main::Session *session = nullptr; Main::Session *session = nullptr;
HashtagMentionType type = HashtagMentionType::Telegram; HashtagMentionType type = HashtagMentionType::Telegram;
Fn<void()> customEmojiRepaint; };
struct TextContextArgs {
not_null<Main::Session*> session;
TextContextDetails details;
Fn<void()> repaint;
int customEmojiLoopLimit = 0; int customEmojiLoopLimit = 0;
}; };
[[nodiscard]] Ui::Text::MarkedContext TextContext(TextContextArgs &&args);
class UiIntegration final : public Ui::Integration { class UiIntegration final : public Ui::Integration {
public: public:
@ -49,7 +55,7 @@ public:
std::shared_ptr<ClickHandler> createLinkHandler( std::shared_ptr<ClickHandler> createLinkHandler(
const EntityLinkData &data, const EntityLinkData &data,
const std::any &context) override; const Ui::Text::MarkedContext &context) override;
bool handleUrlClick( bool handleUrlClick(
const QString &url, const QString &url,
const QVariant &context) override; const QVariant &context) override;
@ -57,10 +63,6 @@ public:
rpl::producer<> forcePopupMenuHideRequests() override; rpl::producer<> forcePopupMenuHideRequests() override;
const Ui::Emoji::One *defaultEmojiVariant( const Ui::Emoji::One *defaultEmojiVariant(
const Ui::Emoji::One *emoji) override; 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 phraseContextCopyText() override;
QString phraseContextCopyEmail() override; QString phraseContextCopyEmail() override;

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 5011001; constexpr auto AppVersion = 5012001;
constexpr auto AppVersionStr = "5.11.1"; constexpr auto AppVersionStr = "5.12.1";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -91,7 +91,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
MTP_int(shortcutId), MTP_int(shortcutId),
MTP_long(data.veffect().value_or_empty()), MTP_long(data.veffect().value_or_empty()),
(data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()), (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()));
}); });
} }

View file

@ -37,10 +37,7 @@ void Credits::apply(const MTPDupdateStarsBalance &data) {
rpl::producer<float64> Credits::rateValue( rpl::producer<float64> Credits::rateValue(
not_null<PeerData*> ownedBotOrChannel) { not_null<PeerData*> ownedBotOrChannel) {
return rpl::single( return rpl::single(_session->appConfig().starsWithdrawRate());
_session->appConfig().get<float64>(
u"stars_usd_withdraw_rate_x1000"_q,
1200) / 1000.);
} }
void Credits::load(bool force) { void Credits::load(bool force) {

View file

@ -95,7 +95,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
MTPint(), // quick_reply_shortcut_id MTPint(), // quick_reply_shortcut_id
MTP_long(data.veffect().value_or_empty()), // effect MTP_long(data.veffect().value_or_empty()), // effect
data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck(), 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 MTPint(), // quick_reply_shortcut_id
MTP_long(local->effectId()), // effect MTP_long(local->effectId()), // effect
MTPFactCheck(), MTPFactCheck(),
MTPint()), // report_delivery_until_date MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
localFlags, localFlags,
NewMessageType::Unread); NewMessageType::Unread);

View file

@ -75,46 +75,48 @@ struct PeerUpdate {
BackgroundEmoji = (1ULL << 15), BackgroundEmoji = (1ULL << 15),
StoriesState = (1ULL << 16), StoriesState = (1ULL << 16),
VerifyInfo = (1ULL << 17), VerifyInfo = (1ULL << 17),
StarsPerMessage = (1ULL << 18),
// For users // For users
CanShareContact = (1ULL << 18), CanShareContact = (1ULL << 19),
IsContact = (1ULL << 19), IsContact = (1ULL << 20),
PhoneNumber = (1ULL << 20), PhoneNumber = (1ULL << 21),
OnlineStatus = (1ULL << 21), OnlineStatus = (1ULL << 22),
BotCommands = (1ULL << 22), BotCommands = (1ULL << 23),
BotCanBeInvited = (1ULL << 23), BotCanBeInvited = (1ULL << 24),
BotStartToken = (1ULL << 24), BotStartToken = (1ULL << 25),
CommonChats = (1ULL << 25), CommonChats = (1ULL << 26),
PeerGifts = (1ULL << 26), PeerGifts = (1ULL << 27),
HasCalls = (1ULL << 27), HasCalls = (1ULL << 28),
SupportInfo = (1ULL << 28), SupportInfo = (1ULL << 29),
IsBot = (1ULL << 29), IsBot = (1ULL << 30),
EmojiStatus = (1ULL << 30), EmojiStatus = (1ULL << 31),
BusinessDetails = (1ULL << 31), BusinessDetails = (1ULL << 32),
Birthday = (1ULL << 32), Birthday = (1ULL << 33),
PersonalChannel = (1ULL << 33), PersonalChannel = (1ULL << 34),
StarRefProgram = (1ULL << 34), StarRefProgram = (1ULL << 35),
PaysPerMessage = (1ULL << 36),
// For chats and channels // For chats and channels
InviteLinks = (1ULL << 35), InviteLinks = (1ULL << 37),
Members = (1ULL << 36), Members = (1ULL << 38),
Admins = (1ULL << 37), Admins = (1ULL << 39),
BannedUsers = (1ULL << 38), BannedUsers = (1ULL << 40),
Rights = (1ULL << 39), Rights = (1ULL << 41),
PendingRequests = (1ULL << 40), PendingRequests = (1ULL << 42),
Reactions = (1ULL << 41), Reactions = (1ULL << 43),
// For channels // For channels
ChannelAmIn = (1ULL << 42), ChannelAmIn = (1ULL << 44),
StickersSet = (1ULL << 43), StickersSet = (1ULL << 45),
EmojiSet = (1ULL << 44), EmojiSet = (1ULL << 46),
ChannelLinkedChat = (1ULL << 45), ChannelLinkedChat = (1ULL << 47),
ChannelLocation = (1ULL << 46), ChannelLocation = (1ULL << 48),
Slowmode = (1ULL << 47), Slowmode = (1ULL << 49),
GroupCall = (1ULL << 48), GroupCall = (1ULL << 50),
// For iteration // For iteration
LastUsedBit = (1ULL << 48), LastUsedBit = (1ULL << 50),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h" #include "data/data_channel.h"
#include "api/api_global_privacy.h" #include "api/api_global_privacy.h"
#include "data/components/credits.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel_admins.h" #include "data/data_channel_admins.h"
#include "data/data_user.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_chat_invite.h"
#include "api/api_invite_links.h" #include "api/api_invite_links.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "storage/storage_account.h"
#include "ui/unread_badge.h" #include "ui/unread_badge.h"
#include "window/notifications_manager.h" #include "window/notifications_manager.h"
@ -859,6 +861,21 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) {
session().changes().peerUpdated(this, UpdateFlag::Slowmode); 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 { int ChannelData::peerGiftsCount() const {
return _peerGiftsCount; return _peerGiftsCount;
} }
@ -1150,7 +1167,8 @@ void ApplyChannelUpdate(
| Flag::CanViewRevenue | Flag::CanViewRevenue
| Flag::PaidMediaAllowed | Flag::PaidMediaAllowed
| Flag::CanViewCreditsRevenue | Flag::CanViewCreditsRevenue
| Flag::StargiftsAvailable; | Flag::StargiftsAvailable
| Flag::PaidMessagesAvailable;
channel->setFlags((channel->flags() & ~mask) channel->setFlags((channel->flags() & ~mask)
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) | (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
| (update.is_can_view_participants() | (update.is_can_view_participants()
@ -1174,6 +1192,9 @@ void ApplyChannelUpdate(
: Flag()) : Flag())
| (update.is_stargifts_available() | (update.is_stargifts_available()
? Flag::StargiftsAvailable ? Flag::StargiftsAvailable
: Flag())
| (update.is_paid_messages_available()
? Flag::PaidMessagesAvailable
: Flag())); : Flag()));
channel->setUserpicPhoto(update.vchat_photo()); channel->setUserpicPhoto(update.vchat_photo());
if (const auto migratedFrom = update.vmigrated_from_chat_id()) { if (const auto migratedFrom = update.vmigrated_from_chat_id()) {

View file

@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer_bot_commands.h" #include "data/data_peer_bot_commands.h"
#include "data/data_user_names.h" #include "data/data_user_names.h"
class ChannelData;
struct ChannelLocation { struct ChannelLocation {
QString address; QString address;
Data::LocationPoint point; Data::LocationPoint point;
@ -70,6 +72,7 @@ enum class ChannelDataFlag : uint64 {
CanViewCreditsRevenue = (1ULL << 34), CanViewCreditsRevenue = (1ULL << 34),
SignatureProfiles = (1ULL << 35), SignatureProfiles = (1ULL << 35),
StargiftsAvailable = (1ULL << 36), StargiftsAvailable = (1ULL << 36),
PaidMessagesAvailable = (1ULL << 37),
}; };
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags<ChannelDataFlag>; using ChannelDataFlags = base::flags<ChannelDataFlag>;
@ -150,6 +153,9 @@ private:
ChannelLocation _location; ChannelLocation _location;
Data::ChatBotCommands _botCommands; Data::ChatBotCommands _botCommands;
std::unique_ptr<Data::Forum> _forum; std::unique_ptr<Data::Forum> _forum;
int _starsPerMessage = 0;
friend class ChannelData;
}; };
@ -257,6 +263,9 @@ public:
[[nodiscard]] bool stargiftsAvailable() const { [[nodiscard]] bool stargiftsAvailable() const {
return flags() & Flag::StargiftsAvailable; return flags() & Flag::StargiftsAvailable;
} }
[[nodiscard]] bool paidMessagesAvailable() const {
return flags() & Flag::PaidMessagesAvailable;
}
[[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights( [[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights(
not_null<PeerData*> participant); not_null<PeerData*> participant);
@ -456,6 +465,9 @@ public:
[[nodiscard]] TimeId slowmodeLastMessage() const; [[nodiscard]] TimeId slowmodeLastMessage() const;
void growSlowmodeLastMessage(TimeId when); void growSlowmodeLastMessage(TimeId when);
void setStarsPerMessage(int stars);
[[nodiscard]] int starsPerMessage() const;
[[nodiscard]] int peerGiftsCount() const; [[nodiscard]] int peerGiftsCount() const;
void setPeerGiftsCount(int count); void setPeerGiftsCount(int count);

View file

@ -17,10 +17,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h" #include "data/data_user.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "ui/boxes/confirm_box.h"
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_widgets.h"
namespace { namespace {
@ -120,7 +123,7 @@ bool CanSendAnyOf(
|| user->isRepliesChat() || user->isRepliesChat()
|| user->isVerifyCodes()) { || user->isVerifyCodes()) {
return false; return false;
} else if (user->meRequiresPremiumToWrite() } else if (user->requiresPremiumToWrite()
&& !user->session().premium()) { && !user->session().premium()) {
return false; return false;
} else if (rights } else if (rights
@ -177,7 +180,7 @@ SendError RestrictionError(
using Flag = ChatRestriction; using Flag = ChatRestriction;
if (const auto restricted = peer->amRestricted(restriction)) { if (const auto restricted = peer->amRestricted(restriction)) {
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
if (user->meRequiresPremiumToWrite() if (user->requiresPremiumToWrite()
&& !user->session().premium()) { && !user->session().premium()) {
return SendError({ return SendError({
.text = tr::lng_restricted_send_non_premium( .text = tr::lng_restricted_send_non_premium(

View file

@ -66,10 +66,11 @@ struct CreditsHistoryEntry final {
uint64 bareGiftStickerId = 0; uint64 bareGiftStickerId = 0;
uint64 bareGiftOwnerId = 0; uint64 bareGiftOwnerId = 0;
uint64 bareActorId = 0; uint64 bareActorId = 0;
uint64 bareGiftListPeerId = 0; uint64 bareEntryOwnerId = 0;
uint64 giftSavedId = 0; uint64 giftChannelSavedId = 0;
uint64 stargiftId = 0; uint64 stargiftId = 0;
std::shared_ptr<UniqueGift> uniqueGift; std::shared_ptr<UniqueGift> uniqueGift;
Fn<std::vector<CreditsHistoryEntry>()> pinnedSavedGifts;
StarsAmount starrefAmount; StarsAmount starrefAmount;
int starrefCommission = 0; int starrefCommission = 0;
uint64 starrefRecipientId = 0; uint64 starrefRecipientId = 0;
@ -77,11 +78,15 @@ struct CreditsHistoryEntry final {
QDateTime subscriptionUntil; QDateTime subscriptionUntil;
QDateTime successDate; QDateTime successDate;
QString successLink; QString successLink;
int paidMessagesCount = 0;
StarsAmount paidMessagesAmount;
int paidMessagesCommission = 0;
int limitedCount = 0; int limitedCount = 0;
int limitedLeft = 0; int limitedLeft = 0;
int starsConverted = 0; int starsConverted = 0;
int starsToUpgrade = 0; int starsToUpgrade = 0;
int starsUpgradedBySender = 0; int starsUpgradedBySender = 0;
int premiumMonthsForStars = 0;
int floodSkip = 0; int floodSkip = 0;
bool converted : 1 = false; bool converted : 1 = false;
bool anonymous : 1 = false; bool anonymous : 1 = false;
@ -89,6 +94,7 @@ struct CreditsHistoryEntry final {
bool giftTransferred : 1 = false; bool giftTransferred : 1 = false;
bool giftRefunded : 1 = false; bool giftRefunded : 1 = false;
bool giftUpgraded : 1 = false; bool giftUpgraded : 1 = false;
bool giftPinned : 1 = false;
bool savedToProfile : 1 = false; bool savedToProfile : 1 = false;
bool fromGiftsList : 1 = false; bool fromGiftsList : 1 = false;
bool fromGiftSlug : 1 = false; bool fromGiftSlug : 1 = false;

View file

@ -16,7 +16,10 @@ namespace Data {
struct CreditsEarnStatistics final { struct CreditsEarnStatistics final {
explicit operator bool() const { explicit operator bool() const {
return !!usdRate; return usdRate
&& currentBalance
&& availableBalance
&& overallRevenue;
} }
Data::StatisticalGraph revenueGraph; Data::StatisticalGraph revenueGraph;
StarsAmount currentBalance; StarsAmount currentBalance;

View file

@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "storage/file_download.h" #include "storage/file_download.h"
#include "storage/storage_account.h"
#include "storage/storage_facade.h" #include "storage/storage_facade.h"
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
@ -65,6 +66,28 @@ using UpdateFlag = Data::PeerUpdate::Flag;
return session->appConfig().ignoredRestrictionReasons(); 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
namespace Data { namespace Data {
@ -311,6 +334,17 @@ void PeerData::invalidateEmptyUserpic() {
_userpicEmpty = nullptr; _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() { ClickHandlerPtr PeerData::createOpenLink() {
return std::make_shared<PeerClickHandler>(this); return std::make_shared<PeerClickHandler>(this);
} }
@ -692,7 +726,9 @@ void PeerData::checkFolder(FolderId folderId) {
void PeerData::clearBusinessBot() { void PeerData::clearBusinessBot() {
if (const auto details = _barDetails.get()) { if (const auto details = _barDetails.get()) {
if (details->requestChatDate) { if (details->requestChatDate
|| details->paysPerMessage
|| !details->phoneCountryCode.isEmpty()) {
details->businessBot = nullptr; details->businessBot = nullptr;
details->businessBotManageUrl = QString(); details->businessBotManageUrl = QString();
} else { } else {
@ -735,12 +771,27 @@ void PeerData::saveTranslationDisabled(bool disabled) {
void PeerData::setBarSettings(const MTPPeerSettings &data) { void PeerData::setBarSettings(const MTPPeerSettings &data) {
data.match([&](const MTPDpeerSettings &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; _barDetails = nullptr;
} else if (!_barDetails) { } else if (!_barDetails) {
_barDetails = std::make_unique<PeerBarDetails>(); _barDetails = std::make_unique<PeerBarDetails>();
} }
if (_barDetails) { 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 _barDetails->requestChatTitle
= qs(data.vrequest_chat_title().value_or_empty()); = qs(data.vrequest_chat_title().value_or_empty());
_barDetails->requestChatDate _barDetails->requestChatDate
@ -750,6 +801,8 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) {
: nullptr; : nullptr;
_barDetails->businessBotManageUrl _barDetails->businessBotManageUrl
= qs(data.vbusiness_bot_manage_url().value_or_empty()); = qs(data.vbusiness_bot_manage_url().value_or_empty());
_barDetails->paysPerMessage
= data.vcharge_paid_message_stars().value_or_empty();
} }
using Flag = PeerBarSetting; using Flag = PeerBarSetting;
setBarSettings((data.is_add_contact() ? Flag::AddContact : Flag()) setBarSettings((data.is_add_contact() ? Flag::AddContact : Flag())
@ -773,8 +826,35 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) {
| (data.is_business_bot_can_reply() | (data.is_business_bot_can_reply()
? Flag::BusinessBotCanReply ? Flag::BusinessBotCanReply
: Flag())); : 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 { QString PeerData::requestChatTitle() const {
return _barDetails ? _barDetails->requestChatTitle : QString(); return _barDetails ? _barDetails->requestChatTitle : QString();
} }
@ -791,6 +871,28 @@ QString PeerData::businessBotManageUrl() const {
return _barDetails ? _barDetails->businessBotManageUrl : QString(); 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( bool PeerData::changeColorIndex(
const tl::conditional<MTPint> &cloudColorIndex) { const tl::conditional<MTPint> &cloudColorIndex) {
return cloudColorIndex return cloudColorIndex
@ -1301,7 +1403,7 @@ Data::RestrictionCheckResult PeerData::amRestricted(
} }
}; };
if (const auto user = asUser()) { if (const auto user = asUser()) {
if (user->meRequiresPremiumToWrite() && !user->session().premium()) { if (user->requiresPremiumToWrite() && !user->session().premium()) {
return Result::Explicit(); return Result::Explicit();
} }
return (right == ChatRestriction::SendVoiceMessages return (right == ChatRestriction::SendVoiceMessages
@ -1420,6 +1522,24 @@ bool PeerData::canManageGroupCall() const {
return false; 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 { Data::GroupCall *PeerData::groupCall() const {
if (const auto chat = asChat()) { if (const auto chat = asChat()) {
return chat->groupCall(); return chat->groupCall();

View file

@ -173,10 +173,15 @@ inline constexpr bool is_flag_type(PeerBarSetting) { return true; };
using PeerBarSettings = base::flags<PeerBarSetting>; using PeerBarSettings = base::flags<PeerBarSetting>;
struct PeerBarDetails { struct PeerBarDetails {
QString phoneCountryCode;
int registrationDate = 0; // YYYYMM or 0, YYYY > 2012, MM > 0.
TimeId nameChangeDate = 0;
TimeId photoChangeDate = 0;
QString requestChatTitle; QString requestChatTitle;
TimeId requestChatDate; TimeId requestChatDate;
UserData *businessBot = nullptr; UserData *businessBot = nullptr;
QString businessBotManageUrl; QString businessBotManageUrl;
int paysPerMessage = 0;
}; };
class PeerData { class PeerData {
@ -268,6 +273,9 @@ public:
[[nodiscard]] int slowmodeSecondsLeft() const; [[nodiscard]] int slowmodeSecondsLeft() const;
[[nodiscard]] bool canManageGroupCall() const; [[nodiscard]] bool canManageGroupCall() const;
[[nodiscard]] int starsPerMessage() const;
[[nodiscard]] int starsPerMessageChecked() const;
[[nodiscard]] UserData *asBot(); [[nodiscard]] UserData *asBot();
[[nodiscard]] const UserData *asBot() const; [[nodiscard]] const UserData *asBot() const;
[[nodiscard]] UserData *asUser(); [[nodiscard]] UserData *asUser();
@ -409,11 +417,18 @@ public:
? _barSettings.changes() ? _barSettings.changes()
: (_barSettings.value() | rpl::type_erased()); : (_barSettings.value() | rpl::type_erased());
} }
[[nodiscard]] int paysPerMessage() const;
void clearPaysPerMessage();
[[nodiscard]] QString requestChatTitle() const; [[nodiscard]] QString requestChatTitle() const;
[[nodiscard]] TimeId requestChatDate() const; [[nodiscard]] TimeId requestChatDate() const;
[[nodiscard]] UserData *businessBot() const; [[nodiscard]] UserData *businessBot() const;
[[nodiscard]] QString businessBotManageUrl() const; [[nodiscard]] QString businessBotManageUrl() const;
void clearBusinessBot(); 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 { enum class TranslationFlag : uchar {
Unknown, Unknown,
@ -501,6 +516,7 @@ protected:
void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo); void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo);
void clearUserpic(); void clearUserpic();
void invalidateEmptyUserpic(); void invalidateEmptyUserpic();
void checkTrustedPayForMessage();
private: private:
void fillNames(); void fillNames();
@ -535,9 +551,10 @@ private:
crl::time _lastFullUpdate = 0; crl::time _lastFullUpdate = 0;
QString _name; QString _name;
uint32 _nameVersion : 30 = 1; uint32 _nameVersion : 29 = 1;
uint32 _sensitiveContent : 1 = 0; uint32 _sensitiveContent : 1 = 0;
uint32 _wallPaperOverriden : 1 = 0; uint32 _wallPaperOverriden : 1 = 0;
uint32 _checkedTrustedPayForMessage : 1 = 0;
TimeId _ttlPeriod = 0; TimeId _ttlPeriod = 0;

View file

@ -228,11 +228,11 @@ inline auto DefaultRestrictionValue(
| ChatRestriction::SendVideoMessages); | ChatRestriction::SendVideoMessages);
auto allowedAny = PeerFlagsValue( auto allowedAny = PeerFlagsValue(
user, user,
(UserDataFlag::Deleted | UserDataFlag::MeRequiresPremiumToWrite) (UserDataFlag::Deleted | UserDataFlag::RequiresPremiumToWrite)
) | rpl::map([=](UserDataFlags flags) { ) | rpl::map([=](UserDataFlags flags) {
return (flags & UserDataFlag::Deleted) return (flags & UserDataFlag::Deleted)
? rpl::single(false) ? rpl::single(false)
: !(flags & UserDataFlag::MeRequiresPremiumToWrite) : !(flags & UserDataFlag::RequiresPremiumToWrite)
? rpl::single(true) ? rpl::single(true)
: AmPremiumValue(&user->session()); : AmPremiumValue(&user->session());
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();

View file

@ -542,14 +542,22 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
| Flag::BotInlineGeo | Flag::BotInlineGeo
| Flag::Premium | Flag::Premium
| Flag::Support | Flag::Support
| Flag::SomeRequirePremiumToWrite | Flag::HasRequirePremiumToWrite
| Flag::RequirePremiumToWriteKnown | Flag::HasStarsPerMessage
| Flag::MessageMoneyRestrictionsKnown
| (!minimal | (!minimal
? Flag::Contact ? Flag::Contact
| Flag::MutualContact | Flag::MutualContact
| Flag::DiscardMinPhoto | Flag::DiscardMinPhoto
| Flag::StoriesHidden | Flag::StoriesHidden
: Flag()); : 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 const auto storiesState = minimal
? std::optional<Data::Stories::PeerSourceState>() ? std::optional<Data::Stories::PeerSourceState>()
: data.is_stories_unavailable() : 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_bot_inline_geo() ? Flag::BotInlineGeo : Flag())
| (data.is_premium() ? Flag::Premium : Flag()) | (data.is_premium() ? Flag::Premium : Flag())
| (data.is_support() ? Flag::Support : Flag()) | (data.is_support() ? Flag::Support : Flag())
| (data.is_contact_require_premium() | (hasRequirePremiumToWrite
? (Flag::SomeRequirePremiumToWrite ? (Flag::HasRequirePremiumToWrite
| (result->someRequirePremiumToWrite() | (result->hasRequirePremiumToWrite()
? (result->requirePremiumToWriteKnown() ? (result->messageMoneyRestrictionsKnown()
? Flag::RequirePremiumToWriteKnown ? Flag::MessageMoneyRestrictionsKnown
: Flag()) : Flag())
: Flag())) : Flag()))
: Flag()) : Flag())
| (hasStarsPerMessage
? (Flag::HasStarsPerMessage
| (result->hasStarsPerMessage()
? (result->messageMoneyRestrictionsKnown()
? Flag::MessageMoneyRestrictionsKnown
: Flag())
: Flag()))
: Flag())
| ((!hasRequirePremiumToWrite && !hasStarsPerMessage)
? Flag::MessageMoneyRestrictionsKnown
: Flag())
| (!minimal | (!minimal
? (data.is_contact() ? Flag::Contact : Flag()) ? (data.is_contact() ? Flag::Contact : Flag())
| (data.is_mutual_contact() ? Flag::MutualContact : 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->setPhoto(data.vphoto());
channel->setStarsPerMessage(
data.vsend_paid_messages_stars().value_or_empty());
if (wasInChannel != channel->amIn()) { if (wasInChannel != channel->amIn()) {
flags |= UpdateFlag::ChannelAmIn; flags |= UpdateFlag::ChannelAmIn;
@ -4689,7 +4710,8 @@ void Session::serviceNotification(
MTPPeerColor(), // color MTPPeerColor(), // color
MTPPeerColor(), // profile_color MTPPeerColor(), // profile_color
MTPint(), // bot_active_users 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 history = this->history(PeerData::kServiceNotificationsId);
const auto insert = [=] { const auto insert = [=] {
@ -4748,7 +4770,8 @@ void Session::insertCheckedServiceNotification(
MTPint(), // quick_reply_shortcut_id MTPint(), // quick_reply_shortcut_id
MTPlong(), // effect MTPlong(), // effect
MTPFactCheck(), MTPFactCheck(),
MTPint()), // report_delivery_until_date MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
localFlags, localFlags,
NewMessageType::Unread); NewMessageType::Unread);
} }

View file

@ -87,6 +87,8 @@ struct GiftUpdate {
Convert, Convert,
Transfer, Transfer,
Delete, Delete,
Pin,
Unpin,
}; };
Data::SavedStarGiftId id; Data::SavedStarGiftId id;

View file

@ -132,6 +132,7 @@ struct SavedStarGift {
TimeId date = 0; TimeId date = 0;
bool upgradable = false; bool upgradable = false;
bool anonymous = false; bool anonymous = false;
bool pinned = false;
bool hidden = false; bool hidden = false;
bool mine = false; bool mine = false;
}; };

View file

@ -349,6 +349,8 @@ enum class MessageFlag : uint64 {
EstimatedDate = (1ULL << 49), EstimatedDate = (1ULL << 49),
ReactionsAllowed = (1ULL << 50), ReactionsAllowed = (1ULL << 50),
HideDisplayDate = (1ULL << 51),
}; };
inline constexpr bool is_flag_type(MessageFlag) { return true; } inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>; using MessageFlags = base::flags<MessageFlag>;

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_sensitive_content.h" #include "api/api_sensitive_content.h"
#include "api/api_statistics.h" #include "api/api_statistics.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "storage/storage_account.h"
#include "storage/storage_user_photos.h" #include "storage/storage_user_photos.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "data/business/data_business_common.h" #include "data/business/data_business_common.h"
@ -526,19 +527,23 @@ bool UserData::hasStoriesHidden() const {
return (flags() & UserDataFlag::StoriesHidden); return (flags() & UserDataFlag::StoriesHidden);
} }
bool UserData::someRequirePremiumToWrite() const { bool UserData::hasRequirePremiumToWrite() const {
return (flags() & UserDataFlag::SomeRequirePremiumToWrite); return (flags() & UserDataFlag::HasRequirePremiumToWrite);
} }
bool UserData::meRequiresPremiumToWrite() const { bool UserData::hasStarsPerMessage() const {
return !isSelf() && (flags() & UserDataFlag::MeRequiresPremiumToWrite); return (flags() & UserDataFlag::HasStarsPerMessage);
} }
bool UserData::requirePremiumToWriteKnown() const { bool UserData::requiresPremiumToWrite() const {
return (flags() & UserDataFlag::RequirePremiumToWriteKnown); 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(); return !isInaccessible() && !isRepliesChat() && !isVerifyCodes();
} }
@ -546,6 +551,18 @@ bool UserData::readDatesPrivate() const {
return (flags() & UserDataFlag::ReadDatesPrivate); 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 { bool UserData::canAddContact() const {
return canShareThisContact() && !isContact(); return canShareThisContact() && !isContact();
} }
@ -624,7 +641,6 @@ void UserData::setCallsStatus(CallsStatus callsStatus) {
} }
} }
Data::Birthday UserData::birthday() const { Data::Birthday UserData::birthday() const {
return _birthday; return _birthday;
} }
@ -699,6 +715,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
if (const auto pinned = update.vpinned_msg_id()) { if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(user, pinned->v); SetTopPinnedMessageId(user, pinned->v);
} }
user->setStarsPerMessage(
update.vsend_paid_messages_stars().value_or_empty());
using Flag = UserDataFlag; using Flag = UserDataFlag;
const auto mask = Flag::Blocked const auto mask = Flag::Blocked
| Flag::HasPhoneCalls | Flag::HasPhoneCalls
@ -706,8 +724,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
| Flag::CanPinMessages | Flag::CanPinMessages
| Flag::VoiceMessagesForbidden | Flag::VoiceMessagesForbidden
| Flag::ReadDatesPrivate | Flag::ReadDatesPrivate
| Flag::RequirePremiumToWriteKnown | Flag::MessageMoneyRestrictionsKnown
| Flag::MeRequiresPremiumToWrite; | Flag::RequiresPremiumToWrite;
user->setFlags((user->flags() & ~mask) user->setFlags((user->flags() & ~mask)
| (update.is_phone_calls_private() | (update.is_phone_calls_private()
? Flag::PhoneCallsPrivate ? Flag::PhoneCallsPrivate
@ -719,9 +737,9 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
? Flag::VoiceMessagesForbidden ? Flag::VoiceMessagesForbidden
: Flag()) : Flag())
| (update.is_read_dates_private() ? Flag::ReadDatesPrivate : Flag()) | (update.is_read_dates_private() ? Flag::ReadDatesPrivate : Flag())
| Flag::RequirePremiumToWriteKnown | Flag::MessageMoneyRestrictionsKnown
| (update.is_contact_require_premium() | (update.is_contact_require_premium()
? Flag::MeRequiresPremiumToWrite ? Flag::RequiresPremiumToWrite
: Flag())); : Flag()));
user->setIsBlocked(update.is_blocked()); user->setIsBlocked(update.is_blocked());
user->setCallsStatus(update.is_phone_calls_private() user->setCallsStatus(update.is_phone_calls_private()

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "core/stars_amount.h" #include "core/stars_amount.h"
#include "data/components/credits.h"
#include "data/data_birthday.h" #include "data/data_birthday.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_chat_participant_status.h" #include "data/data_chat_participant_status.h"
@ -109,10 +110,11 @@ enum class UserDataFlag : uint32 {
StoriesHidden = (1 << 18), StoriesHidden = (1 << 18),
HasActiveStories = (1 << 19), HasActiveStories = (1 << 19),
HasUnreadStories = (1 << 20), HasUnreadStories = (1 << 20),
MeRequiresPremiumToWrite = (1 << 21), RequiresPremiumToWrite = (1 << 21),
SomeRequirePremiumToWrite = (1 << 22), HasRequirePremiumToWrite = (1 << 22),
RequirePremiumToWriteKnown = (1 << 23), HasStarsPerMessage = (1 << 23),
ReadDatesPrivate = (1 << 24), MessageMoneyRestrictionsKnown = (1 << 24),
ReadDatesPrivate = (1 << 25),
}; };
inline constexpr bool is_flag_type(UserDataFlag) { return true; }; inline constexpr bool is_flag_type(UserDataFlag) { return true; };
using UserDataFlags = base::flags<UserDataFlag>; using UserDataFlags = base::flags<UserDataFlag>;
@ -173,12 +175,16 @@ public:
[[nodiscard]] bool applyMinPhoto() const; [[nodiscard]] bool applyMinPhoto() const;
[[nodiscard]] bool hasPersonalPhoto() const; [[nodiscard]] bool hasPersonalPhoto() const;
[[nodiscard]] bool hasStoriesHidden() const; [[nodiscard]] bool hasStoriesHidden() const;
[[nodiscard]] bool someRequirePremiumToWrite() const; [[nodiscard]] bool hasRequirePremiumToWrite() const;
[[nodiscard]] bool meRequiresPremiumToWrite() const; [[nodiscard]] bool hasStarsPerMessage() const;
[[nodiscard]] bool requirePremiumToWriteKnown() const; [[nodiscard]] bool requiresPremiumToWrite() const;
[[nodiscard]] bool canSendIgnoreRequirePremium() const; [[nodiscard]] bool messageMoneyRestrictionsKnown() const;
[[nodiscard]] bool canSendIgnoreMoneyRestrictions() const;
[[nodiscard]] bool readDatesPrivate() const; [[nodiscard]] bool readDatesPrivate() const;
void setStarsPerMessage(int stars);
[[nodiscard]] int starsPerMessage() const;
[[nodiscard]] bool canShareThisContact() const; [[nodiscard]] bool canShareThisContact() const;
[[nodiscard]] bool canAddContact() const; [[nodiscard]] bool canAddContact() const;
@ -268,6 +274,7 @@ private:
Data::Birthday _birthday; Data::Birthday _birthday;
int _commonChatsCount = 0; int _commonChatsCount = 0;
int _peerGiftsCount = 0; int _peerGiftsCount = 0;
int _starsPerMessage = 0;
ContactStatus _contactStatus = ContactStatus::Unknown; ContactStatus _contactStatus = ContactStatus::Unknown;
CallsStatus _callsStatus = CallsStatus::Unknown; CallsStatus _callsStatus = CallsStatus::Unknown;

View file

@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_credits.h" // giftBoxByStarsStyle
namespace Data { namespace Data {
namespace { namespace {
@ -518,8 +519,8 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
Ui::Text::CustomEmojiFactory CustomEmojiManager::factory( Ui::Text::CustomEmojiFactory CustomEmojiManager::factory(
SizeTag tag, SizeTag tag,
int sizeOverride) { int sizeOverride) {
return [=](QStringView data, Fn<void()> update) { return [=](QStringView data, const Ui::Text::MarkedContext &context) {
return create(data, std::move(update), tag, sizeOverride); return create(data, context.repaint, tag, sizeOverride);
}; };
} }
@ -1027,6 +1028,14 @@ TextWithEntities CustomEmojiManager::creditsEmoji(QMargins padding) {
false)); false));
} }
TextWithEntities CustomEmojiManager::ministarEmoji(QMargins padding) {
return Ui::Text::SingleCustomEmoji(
registerInternalEmoji(
Ui::GenerateStars(st::giftBoxByStarsStyle.font->height, 1),
padding,
false));
}
QString CustomEmojiManager::registerInternalEmoji( QString CustomEmojiManager::registerInternalEmoji(
QImage emoji, QImage emoji,
QMargins padding, QMargins padding,
@ -1136,8 +1145,9 @@ void InsertCustomEmoji(
Ui::Text::CustomEmojiFactory ReactedMenuFactory( Ui::Text::CustomEmojiFactory ReactedMenuFactory(
not_null<Main::Session*> session) { not_null<Main::Session*> session) {
return [owner = &session->data()]( return [owner = &session->data()](
QStringView data, QStringView data,
Fn<void()> repaint) -> std::unique_ptr<Ui::Text::CustomEmoji> { const Ui::Text::MarkedContext &context
) -> std::unique_ptr<Ui::Text::CustomEmoji> {
const auto prefix = u"default:"_q; const auto prefix = u"default:"_q;
if (data.startsWith(prefix)) { if (data.startsWith(prefix)) {
const auto &list = owner->reactions().list( const auto &list = owner->reactions().list(
@ -1157,13 +1167,13 @@ Ui::Text::CustomEmojiFactory ReactedMenuFactory(
std::make_unique<Ui::Text::ShiftedEmoji>( std::make_unique<Ui::Text::ShiftedEmoji>(
owner->customEmojiManager().create( owner->customEmojiManager().create(
document, document,
std::move(repaint), context.repaint,
tag, tag,
size), size),
QPoint(skip, skip))); QPoint(skip, skip)));
} }
} }
return owner->customEmojiManager().create(data, std::move(repaint)); return owner->customEmojiManager().create(data, context.repaint);
}; };
} }

View file

@ -100,6 +100,7 @@ public:
[[nodiscard]] uint64 coloredSetId() const; [[nodiscard]] uint64 coloredSetId() const;
[[nodiscard]] TextWithEntities creditsEmoji(QMargins padding = {}); [[nodiscard]] TextWithEntities creditsEmoji(QMargins padding = {});
[[nodiscard]] TextWithEntities ministarEmoji(QMargins padding = {});
private: private:
static constexpr auto kSizeCount = int(SizeTag::kCount); static constexpr auto kSizeCount = int(SizeTag::kCount);

View file

@ -789,7 +789,3 @@ dialogsPopularAppsPadding: margins(10px, 8px, 10px, 12px);
dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) { dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) {
minWidth: 128px; minWidth: 128px;
} }
foldersMenu: Menu(menuWithIcons) {
itemPadding: margins(54px, 8px, 44px, 8px);
}

View file

@ -4320,10 +4320,7 @@ QImage *InnerWidget::cacheChatsFilterTag(
const auto color = Ui::EmptyUserpic::UserpicColor(colorIndex).color2; const auto color = Ui::EmptyUserpic::UserpicColor(colorIndex).color2;
entry.context.color = color->c; entry.context.color = color->c;
entry.context.active = active; entry.context.active = active;
entry.context.textContext = Core::MarkedTextContext{ entry.context.textContext = Core::TextContext({ .session = &session() });
.session = &session(),
.customEmojiRepaint = [] {},
};
entry.frame = Ui::ChatsFilterTag(roundedText, entry.context); entry.frame = Ui::ChatsFilterTag(roundedText, entry.context);
return &entry.frame; return &entry.frame;
} }

View file

@ -60,11 +60,10 @@ namespace {
st::dialogsSearchTagArrow, st::dialogsSearchTagArrow,
st::dialogsSearchTagArrowPadding)); st::dialogsSearchTagArrowPadding));
auto result = Ui::Text::String(); auto result = Ui::Text::String();
const auto context = Core::MarkedTextContext{ const auto context = Core::TextContext({
.session = &owner->session(), .session = &owner->session(),
.customEmojiRepaint = [] {},
.customEmojiLoopLimit = 1, .customEmojiLoopLimit = 1,
}; });
const auto attempt = [&](const auto &phrase) { const auto attempt = [&](const auto &phrase) {
result.setMarkedText( result.setMarkedText(
st::dialogsSearchTagPromo, st::dialogsSearchTagPromo,

Some files were not shown because too many files have changed in this diff Show more