diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 210c88d0d..b3a50984f 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -189,6 +189,8 @@ PRIVATE
api/api_common.h
api/api_confirm_phone.cpp
api/api_confirm_phone.h
+ api/api_credits.cpp
+ api/api_credits.h
api/api_earn.cpp
api/api_earn.h
api/api_editing.cpp
@@ -368,6 +370,8 @@ PRIVATE
boxes/ringtones_box.h
boxes/self_destruction_box.cpp
boxes/self_destruction_box.h
+ boxes/send_credits_box.cpp
+ boxes/send_credits_box.h
boxes/send_files_box.cpp
boxes/send_files_box.h
boxes/sessions_box.cpp
@@ -510,6 +514,8 @@ PRIVATE
core/launcher.h
core/local_url_handlers.cpp
core/local_url_handlers.h
+ core/phone_click_handler.cpp
+ core/phone_click_handler.h
core/sandbox.cpp
core/sandbox.h
core/shortcuts.cpp
@@ -531,6 +537,8 @@ PRIVATE
data/business/data_business_info.h
data/business/data_shortcut_messages.cpp
data/business/data_shortcut_messages.h
+ data/components/factchecks.cpp
+ data/components/factchecks.h
data/components/recent_peers.cpp
data/components/recent_peers.h
data/components/scheduled_messages.cpp
@@ -608,6 +616,8 @@ PRIVATE
data/data_groups.h
data/data_histories.cpp
data/data_histories.h
+ data/data_history_messages.cpp
+ data/data_history_messages.h
data/data_lastseen_status.h
data/data_location.cpp
data/data_location.h
@@ -850,6 +860,8 @@ PRIVATE
history/view/history_view_about_view.h
history/view/history_view_bottom_info.cpp
history/view/history_view_bottom_info.h
+ history/view/history_view_chat_preview.cpp
+ history/view/history_view_chat_preview.h
history/view/history_view_contact_status.cpp
history/view/history_view_contact_status.h
history/view/history_view_context_menu.cpp
@@ -864,6 +876,8 @@ PRIVATE
history/view/history_view_emoji_interactions.h
history/view/history_view_empty_list_bubble.cpp
history/view/history_view_empty_list_bubble.h
+ history/view/history_view_fake_items.cpp
+ history/view/history_view_fake_items.h
history/view/history_view_group_call_bar.cpp
history/view/history_view_group_call_bar.h
history/view/history_view_item_preview.h
@@ -894,14 +908,14 @@ PRIVATE
history/view/history_view_send_action.h
history/view/history_view_service_message.cpp
history/view/history_view_service_message.h
- history/view/history_view_spoiler_click_handler.cpp
- history/view/history_view_spoiler_click_handler.h
history/view/history_view_sponsored_click_handler.cpp
history/view/history_view_sponsored_click_handler.h
history/view/history_view_sticker_toast.cpp
history/view/history_view_sticker_toast.h
history/view/history_view_sublist_section.cpp
history/view/history_view_sublist_section.h
+ history/view/history_view_text_helper.cpp
+ history/view/history_view_text_helper.h
history/view/history_view_transcribe_button.cpp
history/view/history_view_transcribe_button.h
history/view/history_view_translate_bar.cpp
@@ -1278,6 +1292,8 @@ PRIVATE
payments/payments_checkout_process.h
payments/payments_form.cpp
payments/payments_form.h
+ payments/payments_non_panel_process.cpp
+ payments/payments_non_panel_process.h
platform/linux/file_utilities_linux.cpp
platform/linux/file_utilities_linux.h
platform/linux/launcher_linux.cpp
@@ -1423,6 +1439,10 @@ PRIVATE
settings/settings_codes.h
settings/settings_common_session.cpp
settings/settings_common_session.h
+ settings/settings_credits.cpp
+ settings/settings_credits.h
+ settings/settings_credits_graphics.cpp
+ settings/settings_credits_graphics.h
settings/settings_experimental.cpp
settings/settings_experimental.h
settings/settings_folders.cpp
@@ -1518,6 +1538,8 @@ PRIVATE
ui/controls/silent_toggle.h
ui/controls/userpic_button.cpp
ui/controls/userpic_button.h
+ ui/effects/credits_graphics.cpp
+ ui/effects/credits_graphics.h
ui/effects/emoji_fly_animation.cpp
ui/effects/emoji_fly_animation.h
ui/effects/message_sending_animation_common.h
@@ -1565,6 +1587,8 @@ PRIVATE
window/section_widget.h
window/window_adaptive.cpp
window/window_adaptive.h
+ window/window_chat_preview.cpp
+ window/window_chat_preview.h
window/window_connecting_widget.cpp
window/window_connecting_widget.h
window/window_controller.cpp
diff --git a/Telegram/Resources/icons/menu/factcheck.png b/Telegram/Resources/icons/menu/factcheck.png
new file mode 100644
index 000000000..e9115a4cf
Binary files /dev/null and b/Telegram/Resources/icons/menu/factcheck.png differ
diff --git a/Telegram/Resources/icons/menu/factcheck@2x.png b/Telegram/Resources/icons/menu/factcheck@2x.png
new file mode 100644
index 000000000..bcc80e45e
Binary files /dev/null and b/Telegram/Resources/icons/menu/factcheck@2x.png differ
diff --git a/Telegram/Resources/icons/menu/factcheck@3x.png b/Telegram/Resources/icons/menu/factcheck@3x.png
new file mode 100644
index 000000000..139e30eb8
Binary files /dev/null and b/Telegram/Resources/icons/menu/factcheck@3x.png differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 34e5cfaed..bd153b6e7 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -251,6 +251,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_caption_limit2#other" = "Make the caption shorter or subscribe to **Telegram Premium** to double the limit to **{count}** characters.";
"lng_caption_limit_reached#one" = "You've reached the media caption limit. Please make the caption shorter by {count} character.";
"lng_caption_limit_reached#other" = "You've reached the media caption limit. Please make the caption shorter by {count} characters.";
+"lng_caption_move_up" = "Move Caption Up";
+"lng_caption_move_down" = "Move Caption Down";
"lng_file_size_limit_title" = "File Too Large";
"lng_file_size_limit#one" = "{count} Gb";
@@ -302,6 +304,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sure_ban_admin" = "This user is an admin. Are you sure you want to go ahead and restrict them?";
"lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in the Settings (Connection Type).";
"lng_sure_enable" = "Enable";
+"lng_proxy_box_title" = "Enable proxy";
+"lng_proxy_box_server" = "Server";
+"lng_proxy_box_port" = "Port";
+"lng_proxy_box_secret" = "Secret";
+"lng_proxy_box_status" = "Status";
+"lng_proxy_box_username" = "Username";
+"lng_proxy_box_password" = "Password";
"lng_proxy_invalid" = "The proxy link is invalid.";
"lng_proxy_unsupported" = "Your Telegram Desktop version doesn't support this proxy type or the proxy link is invalid. Please update Telegram Desktop to the latest version.";
@@ -561,6 +570,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_reaction_invoice" = "{reaction} to your invoice";
"lng_reaction_gif" = "{reaction} to your GIF";
+"lng_effect_add_title" = "Add an animated effect";
+"lng_effect_stickers_title" = "Effects from stickers";
+"lng_effect_send" = "Send with Effect";
+"lng_effect_none" = "No effects found.";
+"lng_effect_premium" = "Subscribe to {link} to add this animated effect.";
+"lng_effect_premium_link" = "Telegram Premium";
+
"lng_languages" = "Languages";
"lng_languages_none" = "No languages found.";
"lng_languages_count#one" = "{count} language";
@@ -769,6 +785,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_power_chat" = "Animations in Chats";
"lng_settings_power_chat_background" = "Background rotation";
"lng_settings_power_chat_spoiler" = "Animated spoiler effect";
+"lng_settings_power_chat_effects" = "Effects in messages";
"lng_settings_power_calls" = "Animations in Calls";
"lng_settings_power_ui" = "Interface animations";
"lng_settings_power_auto" = "Save Power on Low Battery";
@@ -1053,6 +1070,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_proxy_sponsor" = "Proxy sponsor";
"lng_proxy_sponsor_about" = "This channel is shown by your proxy server.\nTo remove this channel from your chats list,\ndisable the proxy in Telegram Settings.";
"lng_proxy_sponsor_warning" = "This proxy may display a sponsored channel in your chat list. This doesn't reveal any of your Telegram traffic.";
+"lng_proxy_add_from_clipboard" = "Add proxy from clipboard";
+"lng_proxy_add_from_clipboard_good_toast" = "Proxy was added from clipboard.";
+"lng_proxy_add_from_clipboard_failed_toast" = "This is not a proxy link.";
+"lng_proxy_add_from_clipboard_existing_toast" = "This proxy is already in the list.";
"lng_badge_psa_default" = "PSA";
"lng_about_psa_default" = "This message provides you with a public service announcement. To remove it from your chats list, right click it and select **Hide**.";
"lng_tooltip_psa_default" = "This message provides you with a public service announcement.";
@@ -1522,8 +1543,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_link_invite" = "Invite link";
"lng_manage_peer_link_expired" = "Expired link";
"lng_manage_private_group_title" = "Private";
+"lng_manage_private_group_noforwards_title" = "Private restricted";
"lng_manage_public_group_title" = "Public";
"lng_manage_private_peer_title" = "Private";
+"lng_manage_private_peer_noforwards_title" = "Private restricted";
"lng_manage_public_peer_title" = "Public";
"lng_manage_peer_send_title" = "Who can send new messages?";
"lng_manage_peer_send_only_members" = "Only members";
@@ -2285,6 +2308,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_business_about_sponsored_link" = "Telegram Ad Platform {emoji}";
"lng_business_about_sponsored_url" = "https://ads.telegram.org";
+"lng_credits_summary_title" = "Telegram Stars";
+"lng_credits_summary_about" = "Buy Stars to unlock content and services in miniapps on Telegram.";
+"lng_credits_summary_options_subtitle" = "Choose package";
+"lng_credits_summary_options_credits#one" = "{count} Star";
+"lng_credits_summary_options_credits#other" = "{count} Stars";
+"lng_credits_summary_options_more" = "More Options";
+"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}.";
+"lng_credits_summary_options_about_link" = "Terms and Conditions";
+"lng_credits_summary_history_tab_full" = "All Transactions";
+"lng_credits_summary_history_tab_in" = "Incoming";
+"lng_credits_summary_history_tab_out" = "Outgoing";
+"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
+"lng_credits_summary_balance" = "Balance";
+"lng_credits_box_out_title" = "Confirm Your Purchase";
+"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
+"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
+"lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star";
+"lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars";
+"lng_credits_box_out_about" = "Review the {link} for Stars.";
+"lng_credits_summary_in_toast_title" = "Stars Acquired";
+"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance.";
+"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
+"lng_credits_box_history_entry_peer" = "Recipient";
+"lng_credits_box_history_entry_id" = "Transaction ID";
+"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
+"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
+"lng_credits_box_history_entry_about_link" = "here";
+"lng_credits_small_balance_title#one" = "{count} Star Needed";
+"lng_credits_small_balance_title#other" = "{count} Stars Needed";
+"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
+"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
+
"lng_location_title" = "Location";
"lng_location_about" = "Display the location of your business on your account.";
"lng_location_address" = "Enter Address";
@@ -3177,6 +3232,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_reply_msg" = "Reply";
"lng_context_quote_and_reply" = "Quote & Reply";
"lng_context_edit_msg" = "Edit";
+"lng_context_add_factcheck" = "Add Fact Check";
+"lng_context_edit_factcheck" = "Edit Fact Check";
"lng_context_forward_msg" = "Forward Message";
"lng_context_send_now_msg" = "Send now";
"lng_context_reschedule" = "Reschedule";
@@ -3248,6 +3305,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_spoiler_effect" = "Hide with Spoiler";
"lng_context_disable_spoiler" = "Remove Spoiler";
+"lng_factcheck_title" = "Fact Check";
+"lng_factcheck_placeholder" = "Add Facts or Context";
+"lng_factcheck_whats_this" = "what's this?";
+"lng_factcheck_about" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
+"lng_factcheck_add_done" = "Fact check added.";
+"lng_factcheck_edit_done" = "Fact check edited.";
+"lng_factcheck_remove_done" = "Fact check removed.";
+"lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
+"lng_factcheck_links" = "Only **t.me/** links are allowed.";
+
"lng_translate_show_original" = "Show Original";
"lng_translate_bar_to" = "Translate to {name}";
"lng_translate_bar_to_other" = "Translate to {name}";
@@ -3368,6 +3435,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_add_contact" = "Create";
"lng_add_contact_button" = "New contact";
"lng_contacts_header" = "Contacts";
+"lng_menu_not_contact" = "This number is not on Telegram";
"lng_contacts_hidden_stories" = "Hidden Stories";
"lng_contacts_stories_status#one" = "{count} story";
"lng_contacts_stories_status#other" = "{count} stories";
@@ -5161,6 +5229,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_font_system" = "System font";
"lng_font_not_found" = "Font not found.";
+"lng_search_tab_my_messages" = "My Messages";
+"lng_search_tab_this_topic" = "This Topic";
+"lng_search_tab_this_chat" = "This Chat";
+"lng_search_tab_this_channel" = "This Channel";
+"lng_search_tab_this_group" = "This Group";
+"lng_search_tab_public_posts" = "Public Posts";
+"lng_search_tab_no_results" = "No Results";
+"lng_search_tab_no_results_text" = "There were no results for \"{query}\".";
+"lng_search_tab_no_results_retry" = "Try another hashtag.";
+"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it.";
+
+"lng_contact_details_button" = "View Contact";
+"lng_contact_details_title" = "Contact details";
+"lng_contact_details_phone" = "Phone";
+"lng_contact_details_phone_main" = "Main Phone";
+"lng_contact_details_phone_home" = "Home Phone";
+"lng_contact_details_phone_mobile" = "Mobile Phone";
+"lng_contact_details_phone_work" = "Work Phone";
+"lng_contact_details_phone_other" = "Other Phone";
+"lng_contact_details_email" = "Email";
+"lng_contact_details_address" = "Address";
+"lng_contact_details_url" = "URL";
+"lng_contact_details_note" = "Note";
+"lng_contact_details_birthday" = "Birthday";
+"lng_contact_details_organization" = "Organization";
+
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index e22af137a..8068a8279 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="5.1.2.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 22f106414..e37585ba0 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,0,2,0
- PRODUCTVERSION 5,0,2,0
+ FILEVERSION 5,1,2,0
+ PRODUCTVERSION 5,1,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "5.0.2.0"
+ VALUE "FileVersion", "5.1.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.0.2.0"
+ VALUE "ProductVersion", "5.1.2.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 402c438bf..dccfed972 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,0,2,0
- PRODUCTVERSION 5,0,2,0
+ FILEVERSION 5,1,2,0
+ PRODUCTVERSION 5,1,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "5.0.2.0"
+ VALUE "FileVersion", "5.1.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.0.2.0"
+ VALUE "ProductVersion", "5.1.2.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp
index 0284ae719..cf4611563 100644
--- a/Telegram/SourceFiles/api/api_bot.cpp
+++ b/Telegram/SourceFiles/api/api_bot.cpp
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h"
#include "inline_bots/bot_attach_web_view.h"
#include "payments/payments_checkout_process.h"
+#include "payments/payments_non_panel_process.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "mainwindow.h"
@@ -335,7 +336,8 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
Payments::Mode::Payment,
crl::guard(controller, [=](auto) {
controller->widget()->activate();
- }));
+ }),
+ Payments::ProcessNonPanelPaymentFormFactory(controller, item));
} break;
case ButtonType::Url: {
diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h
index fe0d489b0..efd92a9bc 100644
--- a/Telegram/SourceFiles/api/api_common.h
+++ b/Telegram/SourceFiles/api/api_common.h
@@ -23,8 +23,10 @@ struct SendOptions {
PeerData *sendAs = nullptr;
TimeId scheduled = 0;
BusinessShortcutId shortcutId = 0;
+ EffectId effectId = 0;
bool silent = false;
bool handleSupportSwitch = false;
+ bool invertCaption = false;
bool hideViaBot = false;
crl::time ttlSeconds = 0;
};
diff --git a/Telegram/SourceFiles/api/api_confirm_phone.cpp b/Telegram/SourceFiles/api/api_confirm_phone.cpp
index f4788e128..3a42a793d 100644
--- a/Telegram/SourceFiles/api/api_confirm_phone.cpp
+++ b/Telegram/SourceFiles/api/api_confirm_phone.cpp
@@ -97,8 +97,10 @@ void ConfirmPhone::resolve(
box->resendRequests(
) | rpl::start_with_next([=] {
_api.request(MTPauth_ResendCode(
+ MTP_flags(0),
MTP_string(phone),
- MTP_string(phoneHash)
+ MTP_string(phoneHash),
+ MTPstring() // reason
)).done([=] {
if (boxWeak) {
boxWeak->callDone();
diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp
new file mode 100644
index 000000000..00f9e789f
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_credits.cpp
@@ -0,0 +1,222 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "api/api_credits.h"
+
+#include "apiwrap.h"
+#include "api/api_updates.h"
+#include "base/unixtime.h"
+#include "data/data_peer.h"
+#include "data/data_photo.h"
+#include "data/data_session.h"
+#include "data/data_user.h"
+#include "main/main_app_config.h"
+#include "main/main_session.h"
+
+namespace Api {
+namespace {
+
+[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL(
+ const MTPStarsTransaction &tl,
+ not_null peer) {
+ using HistoryPeerTL = MTPDstarsTransactionPeer;
+ const auto photo = tl.data().vphoto()
+ ? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation())
+ : nullptr;
+ return Data::CreditsHistoryEntry{
+ .id = qs(tl.data().vid()),
+ .title = qs(tl.data().vtitle().value_or_empty()),
+ .description = qs(tl.data().vdescription().value_or_empty()),
+ .date = base::unixtime::parse(tl.data().vdate().v),
+ .photoId = photo ? photo->id : 0,
+ .credits = tl.data().vstars().v,
+ .bareId = tl.data().vpeer().match([](const HistoryPeerTL &p) {
+ return peerFromMTP(p.vpeer());
+ }, [](const auto &) {
+ return PeerId(0);
+ }).value,
+ .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
+ return Data::CreditsHistoryEntry::PeerType::Peer;
+ }, [](const MTPDstarsTransactionPeerPlayMarket &) {
+ return Data::CreditsHistoryEntry::PeerType::PlayMarket;
+ }, [](const MTPDstarsTransactionPeerFragment &) {
+ return Data::CreditsHistoryEntry::PeerType::Fragment;
+ }, [](const MTPDstarsTransactionPeerAppStore &) {
+ return Data::CreditsHistoryEntry::PeerType::AppStore;
+ }, [](const MTPDstarsTransactionPeerUnsupported &) {
+ return Data::CreditsHistoryEntry::PeerType::Unsupported;
+ }, [](const MTPDstarsTransactionPeerPremiumBot &) {
+ return Data::CreditsHistoryEntry::PeerType::PremiumBot;
+ }),
+ .refunded = tl.data().is_refund(),
+ };
+}
+
+[[nodiscard]] Data::CreditsStatusSlice StatusFromTL(
+ const MTPpayments_StarsStatus &status,
+ not_null peer) {
+ peer->owner().processUsers(status.data().vusers());
+ peer->owner().processChats(status.data().vchats());
+ return Data::CreditsStatusSlice{
+ .list = ranges::views::all(
+ status.data().vhistory().v
+ ) | ranges::views::transform([&](const MTPStarsTransaction &tl) {
+ return HistoryFromTL(tl, peer);
+ }) | ranges::to_vector,
+ .balance = status.data().vbalance().v,
+ .allLoaded = !status.data().vnext_offset().has_value(),
+ .token = qs(status.data().vnext_offset().value_or_empty()),
+ };
+}
+
+} // namespace
+
+CreditsTopupOptions::CreditsTopupOptions(not_null peer)
+: _peer(peer)
+, _api(&peer->session().api().instance()) {
+}
+
+rpl::producer CreditsTopupOptions::request() {
+ return [=](auto consumer) {
+ auto lifetime = rpl::lifetime();
+
+ using TLOption = MTPStarsTopupOption;
+ _api.request(MTPpayments_GetStarsTopupOptions(
+ )).done([=](const MTPVector &result) {
+ _options = ranges::views::all(
+ result.v
+ ) | ranges::views::transform([](const TLOption &option) {
+ return Data::CreditTopupOption{
+ .credits = option.data().vstars().v,
+ .product = qs(
+ option.data().vstore_product().value_or_empty()),
+ .currency = qs(option.data().vcurrency()),
+ .amount = option.data().vamount().v,
+ .extended = option.data().is_extended(),
+ };
+ }) | ranges::to_vector;
+ consumer.put_done();
+ }).fail([=](const MTP::Error &error) {
+ consumer.put_error_copy(error.type());
+ }).send();
+
+ return lifetime;
+ };
+}
+
+CreditsStatus::CreditsStatus(not_null peer)
+: _peer(peer)
+, _api(&peer->session().api().instance()) {
+}
+
+void CreditsStatus::request(
+ const Data::CreditsStatusSlice::OffsetToken &token,
+ Fn done) {
+ if (_requestId) {
+ return;
+ }
+
+ using TLResult = MTPpayments_StarsStatus;
+
+ _requestId = _api.request(MTPpayments_GetStarsStatus(
+ _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
+ )).done([=](const TLResult &result) {
+ _requestId = 0;
+ done(StatusFromTL(result, _peer));
+ }).fail([=] {
+ _requestId = 0;
+ done({});
+ }).send();
+}
+
+CreditsHistory::CreditsHistory(not_null peer, bool in, bool out)
+: _peer(peer)
+, _flags((in == out)
+ ? HistoryTL::Flags(0)
+ : HistoryTL::Flags(0)
+ | (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0))
+ | (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0)))
+, _api(&peer->session().api().instance()) {
+}
+
+void CreditsHistory::request(
+ const Data::CreditsStatusSlice::OffsetToken &token,
+ Fn done) {
+ if (_requestId) {
+ return;
+ }
+ _requestId = _api.request(MTPpayments_GetStarsTransactions(
+ MTP_flags(_flags),
+ _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
+ MTP_string(token)
+ )).done([=](const MTPpayments_StarsStatus &result) {
+ _requestId = 0;
+ done(StatusFromTL(result, _peer));
+ }).fail([=] {
+ _requestId = 0;
+ done({});
+ }).send();
+}
+
+Data::CreditTopupOptions CreditsTopupOptions::options() const {
+ return _options;
+}
+
+rpl::producer> PremiumPeerBot(
+ not_null session) {
+ const auto username = session->appConfig().get(
+ u"premium_bot_username"_q,
+ QString());
+ if (username.isEmpty()) {
+ return rpl::never>();
+ }
+ if (const auto p = session->data().peerByUsername(username)) {
+ return rpl::single>(p);
+ }
+ return [=](auto consumer) {
+ auto lifetime = rpl::lifetime();
+
+ const auto api = lifetime.make_state(&session->mtp());
+
+ api->request(MTPcontacts_ResolveUsername(
+ MTP_string(username)
+ )).done([=](const MTPcontacts_ResolvedPeer &result) {
+ session->data().processUsers(result.data().vusers());
+ session->data().processChats(result.data().vchats());
+ const auto botPeer = session->data().peerLoaded(
+ peerFromMTP(result.data().vpeer()));
+ if (!botPeer) {
+ return consumer.put_done();
+ }
+ consumer.put_next(not_null{ botPeer });
+ }).send();
+
+ return lifetime;
+ };
+}
+
+void CreditsRefund(
+ not_null peer,
+ const QString &entryId,
+ Fn done,
+ Fn fail) {
+ const auto user = peer->asUser();
+ if (!user) {
+ return;
+ }
+ peer->session().api().request(MTPpayments_RefundStarsCharge(
+ user->inputUser,
+ MTP_string(entryId)
+ )).done([=](const MTPUpdates &result) {
+ peer->session().api().updates().applyUpdates(result);
+ done();
+ }).fail([=](const MTP::Error &error) {
+ fail(error.type());
+ }).send();
+}
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h
new file mode 100644
index 000000000..bd064a65b
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_credits.h
@@ -0,0 +1,80 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "data/data_credits.h"
+#include "mtproto/sender.h"
+
+namespace Main {
+class Session;
+} // namespace Main
+
+namespace Api {
+
+class CreditsTopupOptions final {
+public:
+ CreditsTopupOptions(not_null peer);
+
+ [[nodiscard]] rpl::producer request();
+ [[nodiscard]] Data::CreditTopupOptions options() const;
+
+private:
+ const not_null _peer;
+
+ Data::CreditTopupOptions _options;
+
+ MTP::Sender _api;
+
+};
+
+class CreditsStatus final {
+public:
+ CreditsStatus(not_null peer);
+
+ void request(
+ const Data::CreditsStatusSlice::OffsetToken &token,
+ Fn done);
+
+private:
+ const not_null _peer;
+
+ mtpRequestId _requestId = 0;
+
+ MTP::Sender _api;
+
+};
+
+class CreditsHistory final {
+public:
+ CreditsHistory(not_null peer, bool in, bool out);
+
+ void request(
+ const Data::CreditsStatusSlice::OffsetToken &token,
+ Fn done);
+
+private:
+ using HistoryTL = MTPpayments_GetStarsTransactions;
+ const not_null _peer;
+ const HistoryTL::Flags _flags;
+
+ mtpRequestId _requestId = 0;
+
+ MTP::Sender _api;
+
+};
+
+void CreditsRefund(
+ not_null peer,
+ const QString &entryId,
+ Fn done,
+ Fn fail);
+
+[[nodiscard]] rpl::producer> PremiumPeerBot(
+ not_null session);
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp
index 7dcd768cb..f326275fd 100644
--- a/Telegram/SourceFiles/api/api_editing.cpp
+++ b/Telegram/SourceFiles/api/api_editing.cpp
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_histories.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
+#include "history/view/controls/history_view_compose_media_edit_manager.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
@@ -81,7 +82,8 @@ mtpRequestId EditMessage(
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
- | ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
+ | (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
+ || options.invertCaption)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
@@ -203,6 +205,7 @@ void RescheduleMessage(
not_null item,
SendOptions options) {
const auto empty = [] {};
+ options.invertCaption = item->invertMedia();
EditMessage(item, options, empty, empty);
}
@@ -254,85 +257,85 @@ mtpRequestId EditTextMessage(
SendOptions options,
Fn done,
Fn fail,
- std::optional spoilerMediaOverride) {
- if (spoilerMediaOverride) {
- const auto spoiler = *spoilerMediaOverride;
- if (const auto media = item->media()) {
- auto takeInputMedia = Fn()>(nullptr);
- auto takeFileReference = Fn(nullptr);
- if (const auto photo = media->photo()) {
- using Flag = MTPDinputMediaPhoto::Flag;
- const auto flags = Flag()
- | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
- | (spoiler ? Flag::f_spoiler : Flag());
- takeInputMedia = [=] {
- return MTP_inputMediaPhoto(
- MTP_flags(flags),
- photo->mtpInput(),
- MTP_int(media->ttlSeconds()));
- };
- takeFileReference = [=] { return photo->fileReference(); };
- } else if (const auto document = media->document()) {
- using Flag = MTPDinputMediaDocument::Flag;
- const auto flags = Flag()
- | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
- | (spoiler ? Flag::f_spoiler : Flag());
- takeInputMedia = [=] {
- return MTP_inputMediaDocument(
- MTP_flags(flags),
- document->mtpInput(),
- MTP_int(media->ttlSeconds()),
- MTPstring()); // query
- };
- takeFileReference = [=] { return document->fileReference(); };
- }
-
- const auto usedFileReference = takeFileReference
- ? takeFileReference()
- : QByteArray();
- const auto origin = item->fullId();
- const auto api = &item->history()->session().api();
- const auto performRequest = [=](
- const auto &repeatRequest,
- mtpRequestId originalRequestId) -> mtpRequestId {
- const auto handleReference = [=](
- const QString &error,
- mtpRequestId requestId) {
- if (error.startsWith(u"FILE_REFERENCE_"_q)) {
- api->refreshFileReference(origin, [=](const auto &) {
- if (takeFileReference &&
- (takeFileReference() != usedFileReference)) {
- repeatRequest(
- repeatRequest,
- originalRequestId
- ? originalRequestId
- : requestId);
- } else {
- fail(error, requestId);
- }
- });
- } else {
- fail(error, requestId);
- }
- };
- const auto callback = [=](
- Fn applyUpdates,
- mtpRequestId requestId) {
- applyUpdates();
- done(originalRequestId ? originalRequestId : requestId);
- };
- const auto requestId = EditMessage(
- item,
- caption,
- webpage,
- options,
- callback,
- handleReference,
- takeInputMedia ? takeInputMedia() : std::nullopt);
- return originalRequestId ? originalRequestId : requestId;
+ bool spoilered) {
+ const auto media = item->media();
+ if (media
+ && HistoryView::MediaEditManager::CanBeSpoilered(item)
+ && spoilered != media->hasSpoiler()) {
+ auto takeInputMedia = Fn()>(nullptr);
+ auto takeFileReference = Fn(nullptr);
+ if (const auto photo = media->photo()) {
+ using Flag = MTPDinputMediaPhoto::Flag;
+ const auto flags = Flag()
+ | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
+ | (spoilered ? Flag::f_spoiler : Flag());
+ takeInputMedia = [=] {
+ return MTP_inputMediaPhoto(
+ MTP_flags(flags),
+ photo->mtpInput(),
+ MTP_int(media->ttlSeconds()));
};
- return performRequest(performRequest, 0);
+ takeFileReference = [=] { return photo->fileReference(); };
+ } else if (const auto document = media->document()) {
+ using Flag = MTPDinputMediaDocument::Flag;
+ const auto flags = Flag()
+ | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
+ | (spoilered ? Flag::f_spoiler : Flag());
+ takeInputMedia = [=] {
+ return MTP_inputMediaDocument(
+ MTP_flags(flags),
+ document->mtpInput(),
+ MTP_int(media->ttlSeconds()),
+ MTPstring()); // query
+ };
+ takeFileReference = [=] { return document->fileReference(); };
}
+
+ const auto usedFileReference = takeFileReference
+ ? takeFileReference()
+ : QByteArray();
+ const auto origin = item->fullId();
+ const auto api = &item->history()->session().api();
+ const auto performRequest = [=](
+ const auto &repeatRequest,
+ mtpRequestId originalRequestId) -> mtpRequestId {
+ const auto handleReference = [=](
+ const QString &error,
+ mtpRequestId requestId) {
+ if (error.startsWith(u"FILE_REFERENCE_"_q)) {
+ api->refreshFileReference(origin, [=](const auto &) {
+ if (takeFileReference &&
+ (takeFileReference() != usedFileReference)) {
+ repeatRequest(
+ repeatRequest,
+ originalRequestId
+ ? originalRequestId
+ : requestId);
+ } else {
+ fail(error, requestId);
+ }
+ });
+ } else {
+ fail(error, requestId);
+ }
+ };
+ const auto callback = [=](
+ Fn applyUpdates,
+ mtpRequestId requestId) {
+ applyUpdates();
+ done(originalRequestId ? originalRequestId : requestId);
+ };
+ const auto requestId = EditMessage(
+ item,
+ caption,
+ webpage,
+ options,
+ callback,
+ handleReference,
+ takeInputMedia ? takeInputMedia() : std::nullopt);
+ return originalRequestId ? originalRequestId : requestId;
+ };
+ return performRequest(performRequest, 0);
}
const auto callback = [=](Fn applyUpdates, mtpRequestId id) {
diff --git a/Telegram/SourceFiles/api/api_editing.h b/Telegram/SourceFiles/api/api_editing.h
index c8d0f6c50..630e1cd8d 100644
--- a/Telegram/SourceFiles/api/api_editing.h
+++ b/Telegram/SourceFiles/api/api_editing.h
@@ -56,6 +56,6 @@ mtpRequestId EditTextMessage(
SendOptions options,
Fn done,
Fn fail,
- std::optional spoilerMediaOverride);
+ bool spoilered);
} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp
index ef6f054a0..a56f25d0e 100644
--- a/Telegram/SourceFiles/api/api_polls.cpp
+++ b/Telegram/SourceFiles/api/api_polls.cpp
@@ -73,6 +73,9 @@ void Polls::create(
if (action.options.shortcutId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
+ if (action.options.effectId) {
+ sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
+ }
const auto sendAs = action.options.sendAs;
if (sendAs) {
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
@@ -94,7 +97,8 @@ void Polls::create(
MTPVector(),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
- Data::ShortcutIdToMTP(_session, action.options.shortcutId)
+ Data::ShortcutIdToMTP(_session, action.options.shortcutId),
+ MTP_long(action.options.effectId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index d91943fe0..ade419248 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -133,6 +133,13 @@ void SendExistingMedia(
flags |= MessageFlag::ShortcutMessage;
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
+ if (action.options.effectId) {
+ sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
+ }
+ if (action.options.invertCaption) {
+ flags |= MessageFlag::InvertMedia;
+ sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
+ }
session->data().registerMessageRandomId(randomId, newId);
@@ -144,6 +151,7 @@ void SendExistingMedia(
.date = HistoryItem::NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
+ .effectId = action.options.effectId,
}, media, caption);
const auto performRequest = [=](const auto &repeatRequest) -> void {
@@ -165,7 +173,8 @@ void SendExistingMedia(
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
- Data::ShortcutIdToMTP(session, action.options.shortcutId)
+ Data::ShortcutIdToMTP(session, action.options.shortcutId),
+ MTP_long(action.options.effectId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (error.code() == 400
@@ -306,6 +315,13 @@ bool SendDice(MessageToSend &message) {
flags |= MessageFlag::ShortcutMessage;
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
+ if (action.options.effectId) {
+ sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
+ }
+ if (action.options.invertCaption) {
+ flags |= MessageFlag::InvertMedia;
+ sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
+ }
session->data().registerMessageRandomId(randomId, newId);
@@ -317,6 +333,7 @@ bool SendDice(MessageToSend &message) {
.date = HistoryItem::NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
+ .effectId = action.options.effectId,
}, TextWithEntities(), MTP_messageMediaDice(
MTP_int(0),
MTP_string(emoji)));
@@ -335,7 +352,8 @@ bool SendDice(MessageToSend &message) {
MTP_vector(),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
- Data::ShortcutIdToMTP(session, action.options.shortcutId)
+ Data::ShortcutIdToMTP(session, action.options.shortcutId),
+ MTP_long(action.options.effectId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId, newId);
@@ -430,6 +448,9 @@ void SendConfirmedFile(
flags |= MessageFlag::MediaIsUnread;
}
}
+ if (file->to.options.invertCaption) {
+ flags |= MessageFlag::InvertMedia;
+ }
const auto messageFromId = file->to.options.sendAs
? file->to.options.sendAs->id
@@ -493,6 +514,7 @@ void SendConfirmedFile(
edition.ttl = 0;
edition.mtpMedia = &media;
edition.textWithEntities = caption;
+ edition.invertMedia = file->to.options.invertCaption;
edition.useSameViews = true;
edition.useSameForwards = true;
edition.useSameMarkup = true;
@@ -510,6 +532,7 @@ void SendConfirmedFile(
.shortcutId = file->to.options.shortcutId,
.postAuthor = messagePostAuthor,
.groupedId = groupId,
+ .effectId = file->to.options.effectId,
}, caption, media);
}
diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp
index 44a689ff4..067cc6c0c 100644
--- a/Telegram/SourceFiles/api/api_text_entities.cpp
+++ b/Telegram/SourceFiles/api/api_text_entities.cpp
@@ -178,7 +178,11 @@ EntitiesInText EntitiesFromMTP(
});
}
}, [&](const MTPDmessageEntityPhone &d) {
- // Skipping phones.
+ result.push_back({
+ EntityType::Phone,
+ d.voffset().v,
+ d.vlength().v,
+ });
}, [&](const MTPDmessageEntityCashtag &d) {
result.push_back({
EntityType::Cashtag,
@@ -217,6 +221,7 @@ EntitiesInText EntitiesFromMTP(
EntityType::Blockquote,
d.voffset().v,
d.vlength().v,
+ d.is_collapsed() ? u"1"_q : QString(),
});
});
}
@@ -265,6 +270,9 @@ MTPVector EntitiesToMTP(
case EntityType::Email: {
v.push_back(MTP_messageEntityEmail(offset, length));
} break;
+ case EntityType::Phone: {
+ v.push_back(MTP_messageEntityPhone(offset, length));
+ } break;
case EntityType::Hashtag: {
v.push_back(MTP_messageEntityHashtag(offset, length));
} break;
@@ -311,7 +319,13 @@ MTPVector EntitiesToMTP(
MTP_string(entity.data())));
} break;
case EntityType::Blockquote: {
- v.push_back(MTP_messageEntityBlockquote(offset, length));
+ using Flag = MTPDmessageEntityBlockquote::Flag;
+ const auto collapsed = !entity.data().isEmpty();
+ v.push_back(
+ MTP_messageEntityBlockquote(
+ MTP_flags(collapsed ? Flag::f_collapsed : Flag()),
+ offset,
+ length));
} break;
case EntityType::Spoiler: {
v.push_back(MTP_messageEntitySpoiler(offset, length));
diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp
index 8821bb245..4a9339dfe 100644
--- a/Telegram/SourceFiles/api/api_updates.cpp
+++ b/Telegram/SourceFiles/api/api_updates.cpp
@@ -1145,7 +1145,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTPMessageReactions(),
MTPVector(),
MTP_int(d.vttl_period().value_or_empty()),
- MTPint()), // quick_reply_shortcut_id
+ MTPint(), // quick_reply_shortcut_id
+ MTPlong(), // effect
+ MTPFactCheck()),
MessageFlags(),
NewMessageType::Unread);
} break;
@@ -1180,7 +1182,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTPMessageReactions(),
MTPVector(),
MTP_int(d.vttl_period().value_or_empty()),
- MTPint()), // quick_reply_shortcut_id
+ MTPint(), // quick_reply_shortcut_id
+ MTPlong(), // effect
+ MTPFactCheck()),
MessageFlags(),
NewMessageType::Unread);
} break;
@@ -2618,6 +2622,11 @@ void Updates::feedUpdate(const MTPUpdate &update) {
_session->data().stories().apply(data.vstealth_mode());
} break;
+ case mtpc_updateStarsBalance: {
+ const auto &data = update.c_updateStarsBalance();
+ _session->setCredits(data.vbalance().v);
+ } break;
+
}
}
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index eba067c85..dc7aa1b06 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_chat_filters.h"
#include "data/data_histories.h"
+#include "data/data_history_messages.h"
#include "core/core_cloud_password.h"
#include "core/application.h"
#include "base/unixtime.h"
@@ -3117,6 +3118,46 @@ void ApiWrap::resolveJumpToHistoryDate(
}
}
+void ApiWrap::requestHistory(
+ not_null history,
+ MsgId messageId,
+ SliceType slice) {
+ const auto peer = history->peer;
+ const auto key = HistoryRequest{
+ peer,
+ messageId,
+ slice,
+ };
+ if (_historyRequests.contains(key)) {
+ return;
+ }
+
+ const auto prepared = Api::PrepareHistoryRequest(peer, messageId, slice);
+ auto &histories = history->owner().histories();
+ const auto requestType = Data::Histories::RequestType::History;
+ histories.sendRequest(history, requestType, [=](Fn finish) {
+ return request(
+ std::move(prepared)
+ ).done([=](const Api::HistoryRequestResult &result) {
+ _historyRequests.remove(key);
+ auto parsed = Api::ParseHistoryResult(
+ peer,
+ messageId,
+ slice,
+ result);
+ history->messages().addSlice(
+ std::move(parsed.messageIds),
+ parsed.noSkipRange,
+ parsed.fullCount);
+ finish();
+ }).fail([=] {
+ _historyRequests.remove(key);
+ finish();
+ }).send();
+ });
+ _historyRequests.emplace(key);
+}
+
void ApiWrap::requestSharedMedia(
not_null peer,
MsgId topicRootId,
@@ -3388,6 +3429,9 @@ void ApiWrap::forwardMessages(
.date = HistoryItem::NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
+
+ // forwarded messages don't have effects
+ //.effectId = action.options.effectId,
}, item);
_session->data().registerMessageRandomId(randomId, newId);
if (!localIds) {
@@ -3488,6 +3532,7 @@ void ApiWrap::sendSharedContact(
.date = HistoryItem::NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
+ .effectId = action.options.effectId,
}, TextWithEntities(), MTP_messageMediaContact(
MTP_string(phone),
MTP_string(firstName),
@@ -3790,7 +3835,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
FillMessagePostFlags(action, peer, flags);
- if (exactWebPage && !ignoreWebPage && message.webPage.invert) {
+ if ((exactWebPage && !ignoreWebPage && message.webPage.invert)
+ || action.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media;
mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
@@ -3836,6 +3882,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sendFlags |= MTPmessages_SendMessage::Flag::f_quick_reply_shortcut;
mediaFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
+ if (action.options.effectId) {
+ sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
+ mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
+ }
lastMessage = history->addNewLocalMessage({
.id = newId.msg,
.flags = flags,
@@ -3844,6 +3894,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
.date = HistoryItem::NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
+ .effectId = action.options.effectId,
}, sending, media);
const auto done = [=](
const MTPUpdates &result,
@@ -3891,7 +3942,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
- mtpShortcut
+ mtpShortcut,
+ MTP_long(action.options.effectId)
), done, fail);
} else {
histories.sendPreparedMessage(
@@ -3908,7 +3960,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
- mtpShortcut
+ mtpShortcut,
+ MTP_long(action.options.effectId)
), done, fail);
}
isFirst = false;
@@ -4191,7 +4244,9 @@ void ApiWrap::sendMediaWithRandomId(
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
| (options.scheduled ? Flag::f_schedule_date : 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.invertCaption ? Flag::f_invert_media : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
@@ -4211,7 +4266,8 @@ void ApiWrap::sendMediaWithRandomId(
sentEntities,
MTP_int(options.scheduled),
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
- Data::ShortcutIdToMTP(_session, options.shortcutId)
+ Data::ShortcutIdToMTP(_session, options.shortcutId),
+ MTP_long(options.effectId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (done) done(true);
if (updateRecentStickers) {
@@ -4309,7 +4365,9 @@ void ApiWrap::sendAlbumIfReady(not_null album) {
| (sendAs ? Flag::f_send_as : Flag(0))
| (album->options.shortcutId
? Flag::f_quick_reply_shortcut
- : Flag(0));
+ : Flag(0))
+ | (album->options.effectId ? Flag::f_effect : Flag(0))
+ | (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
histories.sendPreparedMessage(
@@ -4323,7 +4381,8 @@ void ApiWrap::sendAlbumIfReady(not_null album) {
MTP_vector(medias),
MTP_int(album->options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
- Data::ShortcutIdToMTP(_session, album->options.shortcutId)
+ Data::ShortcutIdToMTP(_session, album->options.shortcutId),
+ MTP_long(album->options.effectId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
_sendingAlbums.remove(groupId);
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index b1c844702..9710ee1bd 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -274,6 +274,10 @@ public:
Fn, MsgId)> callback);
using SliceType = Data::LoadDirection;
+ void requestHistory(
+ not_null history,
+ MsgId messageId,
+ SliceType slice);
void requestSharedMedia(
not_null peer,
MsgId topicRootId,
@@ -511,7 +515,8 @@ private:
not_null peer,
bool justClear,
bool revoke);
- void applyAffectedMessages(const MTPmessages_AffectedMessages &result) const;
+ void applyAffectedMessages(
+ const MTPmessages_AffectedMessages &result) const;
void deleteAllFromParticipantSend(
not_null channel,
@@ -645,6 +650,17 @@ private:
};
base::flat_set _sharedMediaRequests;
+ struct HistoryRequest {
+ not_null peer;
+ MsgId aroundId = 0;
+ SliceType sliceType = {};
+
+ friend inline auto operator<=>(
+ const HistoryRequest&,
+ const HistoryRequest&) = default;
+ };
+ base::flat_set _historyRequests;
+
std::unique_ptr _dialogsLoadState;
TimeId _dialogsLoadTill = 0;
rpl::variable _dialogsLoadMayBlockByDate = false;
diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp
index 45d4b1e99..6b9a0852c 100644
--- a/Telegram/SourceFiles/boxes/add_contact_box.cpp
+++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp
@@ -192,20 +192,27 @@ void ShowAddParticipantsError(
&& channel
&& !channel->isMegagroup()
&& channel->canAddAdmins()) {
- const auto makeAdmin = [=] {
+ const auto makeAdmin = [=](Fn close) {
const auto user = forbidden.users.front();
const auto weak = std::make_shared>();
- const auto close = [=](auto&&...) {
- if (*weak) {
- (*weak)->closeBox();
+ const auto done = [=](auto&&...) {
+ if (const auto strong = weak->data()) {
+ strong->uiShow()->showToast(
+ tr::lng_box_done(tr::now));
+ strong->closeBox();
+ }
+ };
+ const auto fail = [=] {
+ if (const auto strong = weak->data()) {
+ strong->closeBox();
}
};
const auto saveCallback = SaveAdminCallback(
show,
channel,
user,
- close,
- close);
+ done,
+ fail);
auto box = Box(
channel,
user,
@@ -214,6 +221,7 @@ void ShowAddParticipantsError(
box->setSaveCallback(saveCallback);
*weak = box.data();
show->showBox(std::move(box));
+ close();
};
show->showBox(
Ui::MakeConfirmBox({
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 87fa1556f..363a64079 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -642,6 +642,10 @@ proxyDropdownUpPosition: point(-2px, 20px);
proxyAboutPadding: margins(22px, 7px, 22px, 14px);
proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px);
+proxyApplyBoxLabel : FlatLabel(defaultFlatLabel) {
+ maxHeight: 30px;
+}
+
markdownLinkFieldPadding: margins(22px, 0px, 22px, 10px);
termsContent: FlatLabel(defaultFlatLabel) {
diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp
index cf9d91f8c..9775ae058 100644
--- a/Telegram/SourceFiles/boxes/connection_box.cpp
+++ b/Telegram/SourceFiles/boxes/connection_box.cpp
@@ -7,32 +7,37 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/connection_box.h"
-#include "ui/boxes/confirm_box.h"
-#include "lang/lang_keys.h"
-#include "storage/localstorage.h"
-#include "base/qthelp_url.h"
#include "base/call_delayed.h"
+#include "base/qthelp_regex.h"
+#include "base/qthelp_url.h"
#include "core/application.h"
#include "core/core_settings.h"
+#include "core/local_url_handlers.h"
+#include "lang/lang_keys.h"
#include "main/main_account.h"
#include "mtproto/facade.h"
-#include "ui/widgets/checkbox.h"
+#include "storage/localstorage.h"
+#include "ui/basic_click_handlers.h"
+#include "ui/boxes/confirm_box.h"
+#include "ui/effects/animations.h"
+#include "ui/effects/radial_animation.h"
+#include "ui/painter.h"
+#include "ui/text/text_options.h"
+#include "ui/text/text_utilities.h"
+#include "ui/toast/toast.h"
#include "ui/widgets/buttons.h"
+#include "ui/widgets/checkbox.h"
+#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/number_input.h"
#include "ui/widgets/fields/password_input.h"
#include "ui/widgets/labels.h"
-#include "ui/widgets/dropdown_menu.h"
+#include "ui/widgets/popup_menu.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
-#include "ui/toast/toast.h"
-#include "ui/effects/animations.h"
-#include "ui/effects/radial_animation.h"
-#include "ui/text/text_options.h"
-#include "ui/text/text_utilities.h"
-#include "ui/basic_click_handlers.h"
-#include "ui/painter.h"
+#include "ui/vertical_list.h"
#include "boxes/abstract_box.h" // Ui::show().
+#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
@@ -48,6 +53,22 @@ constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000);
using ProxyData = MTP::ProxyData;
+[[nodiscard]] ProxyData ProxyDataFromFields(
+ ProxyData::Type type,
+ const QMap &fields) {
+ auto proxy = ProxyData();
+ proxy.type = type;
+ proxy.host = fields.value(u"server"_q);
+ proxy.port = fields.value(u"port"_q).toUInt();
+ if (type == ProxyData::Type::Socks5) {
+ proxy.user = fields.value(u"user"_q);
+ proxy.password = fields.value(u"pass"_q);
+ } else if (type == ProxyData::Type::Mtproto) {
+ proxy.password = fields.value(u"secret"_q);
+ }
+ return proxy;
+};
+
class HostInput : public Ui::MaskedInputField {
public:
HostInput(
@@ -203,6 +224,7 @@ protected:
private:
void setupContent();
+ void setupTopButton();
void createNoRowsLabel();
void addNewProxy();
void applyView(View &&view);
@@ -600,9 +622,80 @@ void ProxiesBox::prepare() {
addButton(tr::lng_proxy_add(), [=] { addNewProxy(); });
addButton(tr::lng_close(), [=] { closeBox(); });
+ setupTopButton();
setupContent();
}
+void ProxiesBox::setupTopButton() {
+ const auto top = addTopButton(st::infoTopBarMenu);
+ const auto menu
+ = top->lifetime().make_state>();
+ const auto callback = [=] {
+ const auto maybeUrl = QGuiApplication::clipboard()->text();
+ const auto local = Core::TryConvertUrlToLocal(maybeUrl);
+
+ const auto proxyString = u"proxy"_q;
+ const auto socksString = u"socks"_q;
+ const auto protocol = u"tg://"_q;
+ const auto command = base::StringViewMid(
+ local,
+ protocol.size(),
+ 8192);
+
+ if (local.startsWith(protocol + proxyString)
+ || local.startsWith(protocol + socksString)) {
+
+ using namespace qthelp;
+ const auto options = RegExOption::CaseInsensitive;
+ for (const auto &[expression, _] : Core::LocalUrlHandlers()) {
+ const auto midExpression = base::StringViewMid(
+ expression,
+ 1);
+ const auto isSocks = midExpression.startsWith(
+ socksString);
+ if (!midExpression.startsWith(proxyString)
+ && !isSocks) {
+ continue;
+ }
+ const auto match = regex_match(
+ expression,
+ command,
+ options);
+ if (!match) {
+ continue;
+ }
+ const auto type = isSocks
+ ? ProxyData::Type::Socks5
+ : ProxyData::Type::Mtproto;
+ const auto fields = url_parse_params(
+ match->captured(1),
+ qthelp::UrlParamNameTransform::ToLower);
+ const auto proxy = ProxyDataFromFields(type, fields);
+ const auto contains = _controller->contains(proxy);
+ const auto toast = (contains
+ ? tr::lng_proxy_add_from_clipboard_existing_toast
+ : tr::lng_proxy_add_from_clipboard_good_toast)(tr::now);
+ uiShow()->showToast(toast);
+ if (!contains) {
+ _controller->addNewItem(proxy);
+ }
+ break;
+ }
+ } else {
+ uiShow()->showToast(
+ tr::lng_proxy_add_from_clipboard_failed_toast(tr::now));
+ }
+ };
+ top->setClickedCallback([=] {
+ *menu = base::make_unique_q(top, st::defaultPopupMenu);
+ (*menu)->addAction(
+ tr::lng_proxy_add_from_clipboard(tr::now),
+ callback);
+ (*menu)->popup(QCursor::pos());
+ return true;
+ });
+}
+
void ProxiesBox::setupContent() {
const auto inner = setInnerWidget(object_ptr(this));
@@ -1094,70 +1187,84 @@ ProxiesBoxController::ProxiesBoxController(not_null account)
}
void ProxiesBoxController::ShowApplyConfirmation(
+ Window::SessionController *controller,
Type type,
const QMap &fields) {
- const auto server = fields.value(u"server"_q);
- const auto port = fields.value(u"port"_q).toUInt();
- auto proxy = ProxyData();
- proxy.type = type;
- proxy.host = server;
- proxy.port = port;
- if (type == Type::Socks5) {
- proxy.user = fields.value(u"user"_q);
- proxy.password = fields.value(u"pass"_q);
- } else if (type == Type::Mtproto) {
- proxy.password = fields.value(u"secret"_q);
+ const auto proxy = ProxyDataFromFields(type, fields);
+ if (!proxy) {
+ auto box = Ui::MakeInformBox(
+ (proxy.status() == ProxyData::Status::Unsupported
+ ? tr::lng_proxy_unsupported(tr::now)
+ : tr::lng_proxy_invalid(tr::now)));
+ if (controller) {
+ controller->uiShow()->showBox(std::move(box));
+ } else {
+ Ui::show(std::move(box));
+ }
+ return;
}
- if (proxy) {
- static const auto UrlStartRegExp = QRegularExpression(
- "^https://",
- QRegularExpression::CaseInsensitiveOption);
- static const auto UrlEndRegExp = QRegularExpression("/$");
- const auto displayed = "https://" + server + "/";
- const auto parsed = QUrl::fromUserInput(displayed);
- const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
- ? displayed
- : parsed.isValid()
- ? QString::fromUtf8(parsed.toEncoded())
- : UrlClickHandler::ShowEncoded(displayed);
- const auto displayServer = QString(
- displayUrl
- ).replace(
- UrlStartRegExp,
- QString()
- ).replace(UrlEndRegExp, QString());
- const auto text = tr::lng_sure_enable_socks(
- tr::now,
- lt_server,
- displayServer,
- lt_port,
- QString::number(port))
- + (proxy.type == Type::Mtproto
- ? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now)
- : QString());
- auto callback = [=](Fn &&close) {
+ static const auto UrlStartRegExp = QRegularExpression(
+ "^https://",
+ QRegularExpression::CaseInsensitiveOption);
+ static const auto UrlEndRegExp = QRegularExpression("/$");
+ const auto displayed = "https://" + proxy.host + "/";
+ const auto parsed = QUrl::fromUserInput(displayed);
+ const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
+ ? displayed
+ : parsed.isValid()
+ ? QString::fromUtf8(parsed.toEncoded())
+ : UrlClickHandler::ShowEncoded(displayed);
+ const auto displayServer = QString(
+ displayUrl
+ ).replace(
+ UrlStartRegExp,
+ QString()
+ ).replace(UrlEndRegExp, QString());
+ const auto box = [=](not_null box) {
+ box->setTitle(tr::lng_proxy_box_title());
+ if (type == Type::Mtproto) {
+ box->addRow(object_ptr(
+ box,
+ tr::lng_proxy_sponsor_warning(),
+ st::boxDividerLabel));
+ Ui::AddSkip(box->verticalLayout());
+ Ui::AddSkip(box->verticalLayout());
+ }
+ const auto &stL = st::proxyApplyBoxLabel;
+ const auto &stSubL = st::boxDividerLabel;
+ const auto add = [&](const QString &s, tr::phrase<> phrase) {
+ if (!s.isEmpty()) {
+ box->addRow(object_ptr(box, s, stL));
+ box->addRow(object_ptr(box, phrase(), stSubL));
+ Ui::AddSkip(box->verticalLayout());
+ Ui::AddSkip(box->verticalLayout());
+ }
+ };
+ if (!displayServer.isEmpty()) {
+ add(displayServer, tr::lng_proxy_box_server);
+ }
+ add(QString::number(proxy.port), tr::lng_proxy_box_port);
+ if (type == Type::Socks5) {
+ add(proxy.user, tr::lng_proxy_box_username);
+ add(proxy.password, tr::lng_proxy_box_password);
+ } else if (type == Type::Mtproto) {
+ add(proxy.password, tr::lng_proxy_box_secret);
+ }
+ box->addButton(tr::lng_sure_enable(), [=] {
auto &proxies = Core::App().settings().proxy().list();
if (!ranges::contains(proxies, proxy)) {
proxies.push_back(proxy);
}
- Core::App().setCurrentProxy(
- proxy,
- ProxyData::Settings::Enabled);
+ Core::App().setCurrentProxy(proxy, ProxyData::Settings::Enabled);
Local::writeSettings();
- close();
- };
- Ui::show(
- Ui::MakeConfirmBox({
- .text = text,
- .confirmed = std::move(callback),
- .confirmText = tr::lng_sure_enable(),
- }),
- Ui::LayerOption::KeepOther);
+ box->closeBox();
+ });
+ box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
+ };
+ if (controller) {
+ controller->uiShow()->showBox(Box(box));
} else {
- Ui::show(Ui::MakeInformBox(
- (proxy.status() == ProxyData::Status::Unsupported
- ? tr::lng_proxy_unsupported(tr::now)
- : tr::lng_proxy_invalid(tr::now))));
+ Ui::show(Box(box));
}
}
@@ -1448,6 +1555,14 @@ object_ptr ProxiesBoxController::addNewItemBox() {
});
}
+bool ProxiesBoxController::contains(const ProxyData &proxy) const {
+ const auto j = ranges::find(
+ _list,
+ proxy,
+ [](const Item &item) { return item.data; });
+ return (j != end(_list));
+}
+
void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
auto &proxies = _settings.list();
proxies.push_back(proxy);
diff --git a/Telegram/SourceFiles/boxes/connection_box.h b/Telegram/SourceFiles/boxes/connection_box.h
index 8c78ebf34..25da43458 100644
--- a/Telegram/SourceFiles/boxes/connection_box.h
+++ b/Telegram/SourceFiles/boxes/connection_box.h
@@ -30,6 +30,10 @@ namespace Main {
class Account;
} // namespace Main
+namespace Window {
+class SessionController;
+} // namespace Window
+
class ProxiesBoxController {
public:
using ProxyData = MTP::ProxyData;
@@ -38,6 +42,7 @@ public:
explicit ProxiesBoxController(not_null account);
static void ShowApplyConfirmation(
+ Window::SessionController *controller,
Type type,
const QMap &fields);
@@ -77,6 +82,9 @@ public:
void setTryIPv6(bool enabled);
rpl::producer proxySettingsValue() const;
+ [[nodiscard]] bool contains(const ProxyData &proxy) const;
+ void addNewItem(const ProxyData &proxy);
+
rpl::producer views() const;
~ProxiesBoxController();
@@ -109,7 +117,6 @@ private:
void replaceItemValue(
std::vector- ::iterator which,
const ProxyData &proxy);
- void addNewItem(const ProxyData &proxy);
const not_null _account;
Core::SettingsProxy &_settings;
diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp
index 1d02b5e52..46138e3fd 100644
--- a/Telegram/SourceFiles/boxes/create_poll_box.cpp
+++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp
@@ -910,12 +910,12 @@ CreatePollBox::CreatePollBox(
PollData::Flags chosen,
PollData::Flags disabled,
Api::SendType sendType,
- SendMenu::Type sendMenuType)
+ SendMenu::Details sendMenuDetails)
: _controller(controller)
, _chosen(chosen)
, _disabled(disabled)
, _sendType(sendType)
-, _sendMenuType(sendMenuType) {
+, _sendMenuDetails([result = sendMenuDetails] { return result; }) {
}
rpl::producer CreatePollBox::submitRequests() const {
@@ -1044,7 +1044,7 @@ not_null CreatePollBox::setupSolution(
solution->setInstantReplaces(Ui::InstantReplaces::Default());
solution->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
- solution->setMarkdownReplacesEnabled(rpl::single(true));
+ solution->setMarkdownReplacesEnabled(true);
solution->setEditLinkCallback(
DefaultEditLinkCallback(_controller->uiShow(), solution));
solution->customTab(true);
@@ -1288,19 +1288,9 @@ object_ptr CreatePollBox::setupContent() {
_submitRequests.fire({ collectResult(), sendOptions });
}
};
- const auto sendSilent = [=] {
- send({ .silent = true });
- };
- const auto sendScheduled = [=] {
- _controller->show(
- HistoryView::PrepareScheduleBox(
- this,
- SendMenu::Type::Scheduled,
- send));
- };
- const auto sendWhenOnline = [=] {
- send(Api::DefaultSendWhenOnlineOptions());
- };
+ const auto sendAction = SendMenu::DefaultCallback(
+ _controller->uiShow(),
+ crl::guard(this, send));
options->scrollToWidget(
) | rpl::start_with_next([=](not_null widget) {
@@ -1313,24 +1303,25 @@ object_ptr CreatePollBox::setupContent() {
}, lifetime());
const auto isNormal = (_sendType == Api::SendType::Normal);
-
+ const auto schedule = [=] {
+ sendAction(
+ { .type = SendMenu::ActionType::Schedule },
+ _sendMenuDetails());
+ };
const auto submit = addButton(
- isNormal
+ (isNormal
? tr::lng_polls_create_button()
- : tr::lng_schedule_button(),
- [=] { isNormal ? send({}) : sendScheduled(); });
- const auto sendMenuType = [=] {
+ : tr::lng_schedule_button()),
+ [=] { isNormal ? send({}) : schedule(); });
+ const auto sendMenuDetails = [=] {
collectError();
- return (*error)
- ? SendMenu::Type::Disabled
- : _sendMenuType;
+ return (*error) ? SendMenu::Details() : _sendMenuDetails();
};
SendMenu::SetupMenuAndShortcuts(
submit.data(),
- sendMenuType,
- sendSilent,
- sendScheduled,
- sendWhenOnline);
+ _controller->uiShow(),
+ sendMenuDetails,
+ sendAction);
addButton(tr::lng_cancel(), [=] { closeBox(); });
return result;
diff --git a/Telegram/SourceFiles/boxes/create_poll_box.h b/Telegram/SourceFiles/boxes/create_poll_box.h
index 91805f0d8..91fc290ca 100644
--- a/Telegram/SourceFiles/boxes/create_poll_box.h
+++ b/Telegram/SourceFiles/boxes/create_poll_box.h
@@ -27,7 +27,7 @@ class SessionController;
} // namespace Window
namespace SendMenu {
-enum class Type;
+struct Details;
} // namespace SendMenu
class CreatePollBox : public Ui::BoxContent {
@@ -43,7 +43,7 @@ public:
PollData::Flags chosen,
PollData::Flags disabled,
Api::SendType sendType,
- SendMenu::Type sendMenuType);
+ SendMenu::Details sendMenuDetails);
[[nodiscard]] rpl::producer submitRequests() const;
void submitFailed(const QString &error);
@@ -75,7 +75,7 @@ private:
const PollData::Flags _chosen = PollData::Flags();
const PollData::Flags _disabled = PollData::Flags();
const Api::SendType _sendType = Api::SendType();
- const SendMenu::Type _sendMenuType;
+ const Fn _sendMenuDetails;
base::unique_qptr _emojiPanel;
Fn _setInnerFocus;
Fn()> _dataIsValidValue;
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
index be4d4e7fa..d46121c2f 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
@@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "mainwidget.h" // controller->content() -> QWidget*
+#include "menu/menu_send.h"
#include "mtproto/mtproto_config.h"
#include "platform/platform_specific.h"
#include "storage/localimageloader.h" // SendMediaType
@@ -175,7 +176,7 @@ void ChooseReplacement(
void EditPhotoImage(
not_null controller,
std::shared_ptr media,
- bool wasSpoiler,
+ bool spoilered,
Fn done) {
const auto large = media
? media->image(Data::PhotoSize::Large)
@@ -198,7 +199,7 @@ void EditPhotoImage(
using ImageInfo = Ui::PreparedFileInformation::Image;
auto &file = list.files.front();
- file.spoiler = wasSpoiler;
+ file.spoiler = spoilered;
const auto image = std::get_if(&file.information->media);
image->modifications = mods;
@@ -225,25 +226,18 @@ void EditPhotoImage(
} // namespace
-EditCaptionBox::EditCaptionBox(
- QWidget*,
- not_null controller,
- not_null item)
-: EditCaptionBox({}, controller, item, PrepareEditText(item), {}, {}) {
-}
-
EditCaptionBox::EditCaptionBox(
QWidget*,
not_null controller,
not_null item,
TextWithTags &&text,
+ bool spoilered,
+ bool invertCaption,
Ui::PreparedList &&list,
Fn saved)
: _controller(controller)
, _historyItem(item)
-, _isAllowedEditMedia(item->media()
- ? item->media()->allowsEditMedia()
- : false)
+, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia())
, _albumType(ComputeAlbumType(item))
, _controls(base::make_unique_q(this))
, _scroll(base::make_unique_q(this, st::boxScroll))
@@ -261,6 +255,8 @@ EditCaptionBox::EditCaptionBox(
Expects(item->media() != nullptr);
Expects(item->media()->allowsEditCaption());
+ _mediaEditManager.start(item, spoilered, invertCaption);
+
_controller->session().data().itemRemoved(
_historyItem->fullId()
) | rpl::start_with_next([=] {
@@ -274,6 +270,8 @@ void EditCaptionBox::StartMediaReplace(
not_null controller,
FullMsgId itemId,
TextWithTags text,
+ bool spoilered,
+ bool invertCaption,
Fn saved) {
const auto session = &controller->session();
const auto item = session->data().message(itemId);
@@ -285,6 +283,8 @@ void EditCaptionBox::StartMediaReplace(
controller,
item,
std::move(text),
+ spoilered,
+ invertCaption,
std::move(list),
std::move(saved)));
};
@@ -299,6 +299,8 @@ void EditCaptionBox::StartMediaReplace(
FullMsgId itemId,
Ui::PreparedList &&list,
TextWithTags text,
+ bool spoilered,
+ bool invertCaption,
Fn saved) {
const auto session = &controller->session();
const auto item = session->data().message(itemId);
@@ -332,6 +334,8 @@ void EditCaptionBox::StartMediaReplace(
controller,
item,
std::move(text),
+ spoilered,
+ invertCaption,
std::move(list),
std::move(saved)));
}
@@ -342,14 +346,15 @@ void EditCaptionBox::StartPhotoEdit(
std::shared_ptr media,
FullMsgId itemId,
TextWithTags text,
+ bool spoilered,
+ bool invertCaption,
Fn saved) {
const auto session = &controller->session();
const auto item = session->data().message(itemId);
if (!item) {
return;
}
- const auto hasSpoiler = item->media() && item->media()->hasSpoiler();
- EditPhotoImage(controller, media, hasSpoiler, [=](
+ EditPhotoImage(controller, media, spoilered, [=](
Ui::PreparedList &&list) mutable {
const auto item = session->data().message(itemId);
if (!item) {
@@ -359,15 +364,48 @@ void EditCaptionBox::StartPhotoEdit(
controller,
item,
std::move(text),
+ spoilered,
+ invertCaption,
std::move(list),
std::move(saved)));
});
}
void EditCaptionBox::prepare() {
- addButton(tr::lng_settings_save(), [=] { save(); });
+ const auto button = addButton(tr::lng_settings_save(), [=] { save(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
+ const auto details = crl::guard(this, [=] {
+ auto result = SendMenu::Details();
+ const auto allWithSpoilers = ranges::all_of(
+ _preparedList.files,
+ &Ui::PreparedFile::spoiler);
+ result.spoiler = !_preparedList.hasSpoilerMenu(!_asFile)
+ ? SendMenu::SpoilerState::None
+ : allWithSpoilers
+ ? SendMenu::SpoilerState::Enabled
+ : SendMenu::SpoilerState::Possible;
+ const auto canMoveCaption = _preparedList.canMoveCaption(
+ false,
+ !_asFile
+ ) && _field && HasSendText(_field);
+ result.caption = !canMoveCaption
+ ? SendMenu::CaptionState::None
+ : _mediaEditManager.invertCaption()
+ ? SendMenu::CaptionState::Above
+ : SendMenu::CaptionState::Below;
+ return result;
+ });
+ const auto callback = [=](SendMenu::Action action, const auto &) {
+ _mediaEditManager.apply(action);
+ rebuildPreview();
+ };
+ SendMenu::SetupMenuAndShortcuts(
+ button,
+ nullptr,
+ details,
+ crl::guard(this, callback));
+
updateBoxSize();
setupField();
@@ -396,7 +434,6 @@ void EditCaptionBox::rebuildPreview() {
applyChanges();
- _previewHasSpoiler = nullptr;
if (_preparedList.files.empty()) {
const auto media = _historyItem->media();
const auto photo = media->photo();
@@ -430,7 +467,13 @@ void EditCaptionBox::rebuildPreview() {
_isPhoto = (media && media->isPhoto());
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
if (media && (!withCheckbox || !_asFile)) {
- _previewHasSpoiler = [media] { return media->hasSpoiler(); };
+ media->spoileredChanges(
+ ) | rpl::start_with_next([=](bool spoilered) {
+ _mediaEditManager.apply({ .type = spoilered
+ ? SendMenu::ActionType::SpoilerOn
+ : SendMenu::ActionType::SpoilerOff
+ });
+ }, media->lifetime());
_content.reset(media);
} else {
_content.reset(Ui::CreateChild(
@@ -757,10 +800,7 @@ bool EditCaptionBox::setPreparedList(Ui::PreparedList &&list) {
}
bool EditCaptionBox::hasSpoiler() const {
- return _preparedList.files.empty()
- ? (_historyItem->media()
- && _historyItem->media()->hasSpoiler())
- : _preparedList.files.front().spoiler;
+ return _mediaEditManager.spoilered();
}
void EditCaptionBox::captionResized() {
@@ -869,8 +909,8 @@ bool EditCaptionBox::validateLength(const QString &text) const {
}
void EditCaptionBox::applyChanges() {
- if (!_preparedList.files.empty() && _previewHasSpoiler) {
- _preparedList.files.front().spoiler = _previewHasSpoiler();
+ if (!_preparedList.files.empty()) {
+ _preparedList.files.front().spoiler = _mediaEditManager.spoilered();
}
}
@@ -899,6 +939,7 @@ void EditCaptionBox::save() {
auto options = Api::SendOptions();
options.scheduled = item->isScheduled() ? item->date() : 0;
options.shortcutId = item->shortcutId();
+ options.invertCaption = _mediaEditManager.invertCaption();
if (!_preparedList.files.empty()) {
if ((_albumType != Ui::AlbumType::None)
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h
index 110c0e588..af72a250f 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.h
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.h
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
+#include "history/view/controls/history_view_compose_media_edit_manager.h"
#include "ui/layers/box_content.h"
#include "ui/chat/attach/attach_prepare.h"
@@ -32,15 +33,13 @@ enum class AlbumType;
class EditCaptionBox final : public Ui::BoxContent {
public:
- EditCaptionBox(
- QWidget*,
- not_null controller,
- not_null item);
EditCaptionBox(
QWidget*,
not_null controller,
not_null item,
TextWithTags &&text,
+ bool spoilered,
+ bool invertCaption,
Ui::PreparedList &&list,
Fn saved);
~EditCaptionBox();
@@ -49,18 +48,24 @@ public:
not_null controller,
FullMsgId itemId,
TextWithTags text,
+ bool spoilered,
+ bool invertCaption,
Fn saved);
static void StartMediaReplace(
not_null controller,
FullMsgId itemId,
Ui::PreparedList &&list,
TextWithTags text,
+ bool spoilered,
+ bool invertCaption,
Fn saved);
static void StartPhotoEdit(
not_null controller,
std::shared_ptr media,
FullMsgId itemId,
TextWithTags text,
+ bool spoilered,
+ bool invertCaption,
Fn saved);
protected:
@@ -111,7 +116,6 @@ private:
const base::unique_qptr _emojiToggle;
base::unique_qptr _content;
- Fn _previewHasSpoiler;
base::unique_qptr _emojiPanel;
base::unique_qptr _emojiFilter;
@@ -122,6 +126,7 @@ private:
std::shared_ptr _photoMedia;
Ui::PreparedList _preparedList;
+ HistoryView::MediaEditManager _mediaEditManager;
mtpRequestId _saveRequestId = 0;
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index 10ad9409d..ef87c6423 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_boosts.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
+#include "data/data_credits.h"
#include "data/data_media_types.h" // Data::GiveawayStart.
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
#include "data/data_session.h"
@@ -1147,16 +1148,18 @@ void GiftCodeBox(
object_ptr(
box,
st::giveawayGiftCodeCover,
- nullptr,
- rpl::conditional(
- state->used.value(),
- tr::lng_gift_link_used_title(),
- tr::lng_gift_link_title()),
- rpl::conditional(
- state->used.value(),
- tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
- tr::lng_gift_link_about(Ui::Text::RichLangValue)),
- true));
+ Ui::Premium::TopBarDescriptor{
+ .clickContextOther = nullptr,
+ .title = rpl::conditional(
+ state->used.value(),
+ tr::lng_gift_link_used_title(),
+ tr::lng_gift_link_title()),
+ .about = rpl::conditional(
+ state->used.value(),
+ tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
+ tr::lng_gift_link_about(Ui::Text::RichLangValue)),
+ .light = true,
+ }));
const auto max = st::giveawayGiftCodeTopHeight;
bar->setMaximumHeight(max);
@@ -1283,13 +1286,15 @@ void GiftCodePendingBox(
object_ptr(
box,
st,
- clickContext,
- tr::lng_gift_link_title(),
- tr::lng_gift_link_pending_about(
- lt_user,
- rpl::single(Ui::Text::Link(resultToName)),
- Ui::Text::RichLangValue),
- true));
+ Ui::Premium::TopBarDescriptor{
+ .clickContextOther = clickContext,
+ .title = tr::lng_gift_link_title(),
+ .about = tr::lng_gift_link_pending_about(
+ lt_user,
+ rpl::single(Ui::Text::Link(resultToName)),
+ Ui::Text::RichLangValue),
+ .light = true,
+ }));
const auto max = st::giveawayGiftCodeTopHeight;
bar->setMaximumHeight(max);
@@ -1629,3 +1634,50 @@ void ResolveGiveawayInfo(
messageId,
crl::guard(controller, show));
}
+
+void AddCreditsHistoryEntryTable(
+ not_null controller,
+ not_null container,
+ const Data::CreditsHistoryEntry &entry) {
+ auto table = container->add(
+ object_ptr(
+ container,
+ st::giveawayGiftCodeTable),
+ st::giveawayGiftCodeTableMargin);
+ if (entry.bareId) {
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_peer(),
+ controller,
+ PeerId(entry.bareId));
+ }
+ if (!entry.id.isEmpty()) {
+ constexpr auto kOneLineCount = 18;
+ const auto oneLine = entry.id.length() <= kOneLineCount;
+ auto label = object_ptr(
+ table,
+ rpl::single(
+ Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})),
+ oneLine
+ ? st::giveawayGiftCodeValue
+ : st::giveawayGiftCodeValueMultiline);
+ label->setClickHandlerFilter([=](const auto &...) {
+ TextUtilities::SetClipboardText(
+ TextForMimeData::Simple(entry.id));
+ controller->showToast(
+ tr::lng_credits_box_history_entry_id_copied(tr::now));
+ return false;
+ });
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_id(),
+ std::move(label),
+ st::giveawayGiftCodeValueMargin);
+ }
+ if (!entry.date.isNull()) {
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_date(),
+ rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
+ }
+}
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h
index b054bc3a2..e3194b273 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.h
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.h
@@ -16,12 +16,14 @@ struct GiftCode;
} // namespace Api
namespace Data {
+struct CreditsHistoryEntry;
struct GiveawayStart;
struct GiveawayResults;
} // namespace Data
namespace Ui {
class GenericBox;
+class VerticalLayout;
} // namespace Ui
namespace Window {
@@ -71,3 +73,8 @@ void ResolveGiveawayInfo(
MsgId messageId,
std::optional start,
std::optional results);
+
+void AddCreditsHistoryEntryTable(
+ not_null controller,
+ not_null container,
+ const Data::CreditsHistoryEntry &entry);
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index dca4a065b..22cc40411 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_widgets.h"
#include // XXH64.
+#include
[[nodiscard]] PeerListRowId UniqueRowIdFromString(const QString &d) {
return XXH64(d.data(), d.size() * sizeof(ushort), 0);
@@ -1552,15 +1553,44 @@ void PeerListContent::handleMouseMove(QPoint globalPosition) {
&& *_lastMousePosition == globalPosition) {
return;
}
+ if (_trackPressStart
+ && ((*_trackPressStart - globalPosition).manhattanLength()
+ > QApplication::startDragDistance())) {
+ _trackPressStart = {};
+ _controller->rowTrackPressCancel();
+ }
+ if (!_controller->rowTrackPressSkipMouseSelection()) {
+ selectByMouse(globalPosition);
+ }
+}
+
+void PeerListContent::pressLeftToContextMenu(bool shown) {
+ if (shown) {
+ setContexted(_pressed);
+ setPressed(Selected());
+ } else {
+ setContexted(Selected());
+ }
+}
+
+bool PeerListContent::trackRowPressFromGlobal(QPoint globalPosition) {
selectByMouse(globalPosition);
+ if (const auto row = getRow(_selected.index)) {
+ if (_controller->rowTrackPress(row)) {
+ _trackPressStart = globalPosition;
+ return true;
+ }
+ }
+ return false;
}
void PeerListContent::mousePressEvent(QMouseEvent *e) {
_pressButton = e->button();
selectByMouse(e->globalPos());
setPressed(_selected);
- if (auto row = getRow(_selected.index)) {
- auto updateCallback = [this, row, hint = _selected.index] {
+ _trackPressStart = {};
+ if (const auto row = getRow(_selected.index)) {
+ const auto updateCallback = [this, row, hint = _selected.index] {
updateRow(row, hint);
};
if (_selected.element) {
@@ -1586,8 +1616,11 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) {
row->addRipple(_st.item, maskGenerator, point, std::move(updateCallback));
}
}
+ if (_pressButton == Qt::LeftButton && _controller->rowTrackPress(row)) {
+ _trackPressStart = e->globalPos();
+ }
}
- if (anim::Disabled() && !_selected.element) {
+ if (anim::Disabled() && !_trackPressStart && !_selected.element) {
mousePressReleased(e->button());
}
}
@@ -1597,6 +1630,9 @@ void PeerListContent::mouseReleaseEvent(QMouseEvent *e) {
}
void PeerListContent::mousePressReleased(Qt::MouseButton button) {
+ _trackPressStart = {};
+ _controller->rowTrackPressCancel();
+
updateRow(_pressed.index);
updateRow(_selected.index);
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h
index 4349fd8c6..aa24856e2 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.h
+++ b/Telegram/SourceFiles/boxes/peer_list_box.h
@@ -348,6 +348,9 @@ public:
virtual int peerListPartitionRows(Fn border) = 0;
virtual std::shared_ptr peerListUiShow() = 0;
+ virtual void peerListPressLeftToContextMenu(bool shown) = 0;
+ virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0;
+
template
void peerListAddSelectedPeers(PeerDataRange &&range) {
for (const auto peer : range) {
@@ -478,6 +481,15 @@ public:
}
}
+ virtual bool rowTrackPress(not_null row) {
+ return false;
+ }
+ virtual void rowTrackPressCancel() {
+ }
+ virtual bool rowTrackPressSkipMouseSelection() {
+ return false;
+ }
+
virtual void loadMoreRows() {
}
virtual void itemDeselectedHook(not_null peer) {
@@ -655,6 +667,8 @@ public:
void refreshRows();
void mouseLeftGeometry();
+ void pressLeftToContextMenu(bool shown);
+ bool trackRowPressFromGlobal(QPoint globalPosition);
void setSearchMode(PeerListSearchMode mode);
void changeCheckState(
@@ -829,6 +843,7 @@ private:
bool _mouseSelection = false;
std::optional _lastMousePosition;
Qt::MouseButton _pressButton = Qt::LeftButton;
+ std::optional _trackPressStart;
rpl::event_stream _scrollToRequests;
@@ -992,6 +1007,13 @@ public:
bool highlightRow,
Fn)> destroyed = nullptr) override;
+ void peerListPressLeftToContextMenu(bool shown) override {
+ _content->pressLeftToContextMenu(shown);
+ }
+ bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override {
+ return _content->trackRowPressFromGlobal(globalPosition);
+ }
+
protected:
not_null content() const {
return _content;
diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp
index 2a4882588..ed8a77787 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp
@@ -42,16 +42,14 @@ namespace {
constexpr auto kDefaultIconId = DocumentId(0x7FFF'FFFF'FFFF'FFFFULL);
-struct DefaultIcon {
- QString title;
- int32 colorId = 0;
-};
+using DefaultIcon = Data::TopicIconDescriptor;
class DefaultIconEmoji final : public Ui::Text::CustomEmoji {
public:
DefaultIconEmoji(
rpl::producer value,
- Fn repaint);
+ Fn repaint,
+ Data::CustomEmojiSizeTag tag);
int width() override;
QString entityData() override;
@@ -64,14 +62,17 @@ public:
private:
DefaultIcon _icon = {};
QImage _image;
+ Data::CustomEmojiSizeTag _tag = {};
rpl::lifetime _lifetime;
};
DefaultIconEmoji::DefaultIconEmoji(
- rpl::producer value,
- Fn repaint) {
+ rpl::producer value,
+ Fn repaint,
+ Data::CustomEmojiSizeTag tag)
+: _tag(tag) {
std::move(value) | rpl::start_with_next([=](DefaultIcon value) {
_icon = value;
_image = QImage();
@@ -88,15 +89,22 @@ QString DefaultIconEmoji::entityData() {
}
void DefaultIconEmoji::paint(QPainter &p, const Context &context) {
+ const auto &st = (_tag == Data::CustomEmojiSizeTag::Normal)
+ ? st::normalForumTopicIcon
+ : st::defaultForumTopicIcon;
if (_image.isNull()) {
- _image = Data::ForumTopicIconFrame(
- _icon.colorId,
- _icon.title,
- st::defaultForumTopicIcon);
+ _image = Data::IsForumGeneralIconTitle(_icon.title)
+ ? Data::ForumTopicGeneralIconFrame(
+ st.size,
+ Data::ParseForumGeneralIconColor(_icon.colorId))
+ : Data::ForumTopicIconFrame(_icon.colorId, _icon.title, st);
}
- const auto esize = Ui::Emoji::GetSizeLarge() / style::DevicePixelRatio();
+ const auto full = (_tag == Data::CustomEmojiSizeTag::Normal)
+ ? Ui::Emoji::GetSizeNormal()
+ : Ui::Emoji::GetSizeLarge();
+ const auto esize = full / style::DevicePixelRatio();
const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize);
- const auto skip = (customSize - st::defaultForumTopicIcon.size) / 2;
+ const auto skip = (customSize - st.size) / 2;
p.drawImage(context.position + QPoint(skip, skip), _image);
}
@@ -212,7 +220,7 @@ bool DefaultIconEmoji::readyInDefaultState() {
) | rpl::start_with_next([=] {
state->frame = Data::ForumTopicGeneralIconFrame(
st::largeForumTopicIcon.size,
- st::windowSubTextFg);
+ st::windowSubTextFg->c);
result->update();
}, result->lifetime());
@@ -261,7 +269,8 @@ struct IconSelector {
if (id == kDefaultIconId) {
return std::make_unique(
rpl::duplicate(defaultIcon),
- repaint);
+ std::move(repaint),
+ tag);
}
return manager->create(id, std::move(repaint), tag);
};
@@ -572,3 +581,13 @@ void EditForumTopicBox(
box->closeBox();
});
}
+
+std::unique_ptr MakeTopicIconEmoji(
+ Data::TopicIconDescriptor descriptor,
+ Fn repaint,
+ Data::CustomEmojiSizeTag tag) {
+ return std::make_unique(
+ rpl::single(descriptor),
+ std::move(repaint),
+ tag);
+}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h
index fddc3c052..81758e84c 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h
+++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h
@@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class History;
+namespace Data {
+struct TopicIconDescriptor;
+enum class CustomEmojiSizeTag : uchar;
+} // namespace Data
+
namespace Window {
class SessionController;
} // namespace Window
@@ -25,3 +30,8 @@ void EditForumTopicBox(
not_null controller,
not_null forum,
MsgId rootId);
+
+[[nodiscard]] std::unique_ptr MakeTopicIconEmoji(
+ Data::TopicIconDescriptor descriptor,
+ Fn repaint,
+ Data::CustomEmojiSizeTag tag);
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index c79ef325d..99427aca0 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -415,7 +415,12 @@ private:
std::deque> _saveStagesQueue;
Saving _savingData;
- const rpl::event_stream _privacyTypeUpdates;
+ struct PrivacyAndForwards {
+ Privacy privacy;
+ bool noForwards = false;
+ };
+
+ const rpl::event_stream _privacyTypeUpdates;
const rpl::event_stream _linkedChatUpdates;
mtpRequestId _linkedChatsRequestId = 0;
@@ -761,7 +766,7 @@ void Controller::refreshHistoryVisibility() {
void Controller::showEditPeerTypeBox(
std::optional> error) {
const auto boxCallback = crl::guard(this, [=](EditPeerTypeData data) {
- _privacyTypeUpdates.fire_copy(data.privacy);
+ _privacyTypeUpdates.fire({ data.privacy, data.noForwards });
_typeDataSavedValue = data;
refreshHistoryVisibility();
});
@@ -882,7 +887,8 @@ void Controller::fillPrivacyTypeButton() {
? tr::lng_manage_peer_group_type
: tr::lng_manage_peer_channel_type)(),
_privacyTypeUpdates.events(
- ) | rpl::map([=](Privacy flag) {
+ ) | rpl::map([=](PrivacyAndForwards data) {
+ const auto flag = data.privacy;
if (flag == Privacy::HasUsername) {
_peer->session().api().usernames().requestToCache(_peer);
}
@@ -894,14 +900,21 @@ void Controller::fillPrivacyTypeButton() {
: tr::lng_manage_public_peer_title)()
: (hasLocation
? tr::lng_manage_peer_link_invite
- : isGroup
+ : ((!data.noForwards) && isGroup)
? tr::lng_manage_private_group_title
- : tr::lng_manage_private_peer_title)();
+ : ((!data.noForwards) && !isGroup)
+ ? tr::lng_manage_private_peer_title
+ : isGroup
+ ? tr::lng_manage_private_group_noforwards_title
+ : tr::lng_manage_private_peer_noforwards_title)();
}) | rpl::flatten_latest(),
[=] { showEditPeerTypeBox(); },
{ &st::menuIconCustomize });
- _privacyTypeUpdates.fire_copy(_typeDataSavedValue->privacy);
+ _privacyTypeUpdates.fire_copy({
+ _typeDataSavedValue->privacy,
+ _typeDataSavedValue->noForwards,
+ });
}
void Controller::fillLinkedChatButton() {
diff --git a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp
index 47afccd2f..45d30a7ed 100644
--- a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp
@@ -398,7 +398,7 @@ void PeerShortInfoCover::paintRadial(QPainter &p) {
QImage PeerShortInfoCover::currentVideoFrame() const {
const auto size = QSize(_st.size, _st.size);
const auto request = Media::Streaming::FrameRequest{
- .resize = size * style::DevicePixelRatio(),
+ .resize = size,
.outer = size,
};
return (_videoInstance
diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp
index 30f312745..76a2a18b7 100644
--- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp
+++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp
@@ -286,7 +286,7 @@ void PreloadSticker(const std::shared_ptr &media) {
document,
media->videoThumbnailContent(),
QString(),
- true);
+ Stickers::EffectType::PremiumSticker);
const auto update = [=] {
if (!state->readyInvoked
diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp
index e33624403..8a71e395b 100644
--- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp
+++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp
@@ -16,8 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/admin_log/history_admin_log_item.h"
#include "history/history.h"
#include "history/history_item.h"
-#include "history/view/history_view_element.h"
#include "history/view/reactions/history_view_reactions_strip.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_fake_items.h"
#include "lang/lang_keys.h"
#include "boxes/premium_preview_box.h"
#include "main/main_session.h"
@@ -43,53 +44,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
-PeerId GenerateUser(not_null history, const QString &name) {
- Expects(history->peer->isUser());
-
- const auto peerId = Data::FakePeerIdForJustName(name);
- history->owner().processUser(MTP_user(
- MTP_flags(MTPDuser::Flag::f_first_name | MTPDuser::Flag::f_min),
- peerToBareMTPInt(peerId),
- MTP_long(0),
- MTP_string(tr::lng_settings_chat_message_reply_from(tr::now)),
- MTPstring(), // last name
- MTPstring(), // username
- MTPstring(), // phone
- MTPUserProfilePhoto(), // profile photo
- MTPUserStatus(), // status
- MTP_int(0), // bot info version
- MTPVector(), // restrictions
- MTPstring(), // bot placeholder
- MTPstring(), // lang code
- MTPEmojiStatus(),
- MTPVector(),
- MTPint(), // stories_max_id
- MTPPeerColor(), // color
- MTPPeerColor())); // profile_color
- return peerId;
-}
-
-AdminLog::OwnedItem GenerateItem(
- not_null delegate,
- not_null history,
- PeerId from,
- FullMsgId replyTo,
- const QString &text) {
- Expects(history->peer->isUser());
-
- const auto item = history->addNewLocalMessage({
- .id = history->nextNonHistoryEntryId(),
- .flags = (MessageFlag::FakeHistoryItem
- | MessageFlag::HasFromId
- | MessageFlag::HasReplyInfo),
- .from = from,
- .replyTo = FullReplyTo{ .messageId = replyTo },
- .date = base::unixtime::now(),
- }, TextWithEntities{ .text = text }, MTP_messageMediaEmpty());
-
- return AdminLog::OwnedItem(delegate, item);
-}
-
void AddMessage(
not_null container,
not_null controller,
@@ -135,15 +89,15 @@ void AddMessage(
const auto history = controller->session().data().history(
PeerData::kServiceNotificationsId);
- state->reply = GenerateItem(
+ state->reply = HistoryView::GenerateItem(
state->delegate.get(),
history,
- GenerateUser(
+ HistoryView::GenerateUser(
history,
tr::lng_settings_chat_message_reply_from(tr::now)),
FullMsgId(),
tr::lng_settings_chat_message_reply(tr::now));
- auto message = GenerateItem(
+ auto message = HistoryView::GenerateItem(
state->delegate.get(),
history,
history->peer->id,
diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp
new file mode 100644
index 000000000..8820c0c65
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp
@@ -0,0 +1,250 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "boxes/send_credits_box.h"
+
+#include "api/api_credits.h"
+#include "apiwrap.h"
+#include "core/ui_integration.h" // Core::MarkedTextContext.
+#include "data/data_credits.h"
+#include "data/data_session.h"
+#include "data/data_user.h"
+#include "data/stickers/data_custom_emoji.h"
+#include "history/history.h"
+#include "history/history_item.h"
+#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
+#include "lang/lang_keys.h"
+#include "main/main_session.h"
+#include "payments/payments_checkout_process.h"
+#include "payments/payments_form.h"
+#include "settings/settings_credits_graphics.h"
+#include "ui/controls/userpic_button.h"
+#include "ui/effects/premium_graphics.h"
+#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
+#include "ui/image/image_prepare.h"
+#include "ui/layers/generic_box.h"
+#include "ui/rect.h"
+#include "ui/text/text_utilities.h"
+#include "ui/vertical_list.h"
+#include "ui/widgets/buttons.h"
+#include "styles/style_boxes.h"
+#include "styles/style_credits.h"
+#include "styles/style_giveaway.h"
+#include "styles/style_layers.h"
+#include "styles/style_premium.h"
+#include "styles/style_settings.h"
+
+namespace Ui {
+
+void SendCreditsBox(
+ not_null box,
+ std::shared_ptr form,
+ Fn sent) {
+ if (!form) {
+ return;
+ }
+ struct State {
+ rpl::variable confirmButtonBusy = false;
+ };
+ const auto state = box->lifetime().make_state();
+ box->setStyle(st::giveawayGiftCodeBox);
+ box->setNoContentMargin(true);
+
+ const auto session = form->invoice.session;
+
+ const auto photoSize = st::defaultUserpicButton.photoSize;
+
+ const auto content = box->verticalLayout();
+ Ui::AddSkip(content, photoSize / 2);
+
+ {
+ const auto ministarsContainer = Ui::CreateChild(box);
+ const auto fullHeight = photoSize * 2;
+ using MiniStars = Ui::Premium::ColoredMiniStars;
+ const auto ministars = box->lifetime().make_state(
+ ministarsContainer,
+ false,
+ Ui::Premium::MiniStars::Type::BiStars);
+ ministars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
+
+ ministarsContainer->paintRequest(
+ ) | rpl::start_with_next([=] {
+ auto p = QPainter(ministarsContainer);
+ ministars->paint(p);
+ }, ministarsContainer->lifetime());
+
+ box->widthValue(
+ ) | rpl::start_with_next([=](int width) {
+ ministarsContainer->resize(width, fullHeight);
+ const auto w = fullHeight / 3 * 2;
+ ministars->setCenter(QRect(
+ (width - w) / 2,
+ (fullHeight - w) / 2,
+ w,
+ w));
+ }, ministarsContainer->lifetime());
+ }
+
+ const auto bot = session->data().user(form->botId);
+
+ if (form->photo) {
+ box->addRow(object_ptr>(
+ content,
+ Settings::HistoryEntryPhoto(content, form->photo, photoSize)));
+ } else {
+ const auto widget = box->addRow(
+ object_ptr>(
+ content,
+ object_ptr(
+ content,
+ bot,
+ st::defaultUserpicButton)));
+ widget->setAttribute(Qt::WA_TransparentForMouseEvents);
+ }
+
+ Ui::AddSkip(content);
+ box->addRow(object_ptr>(
+ box,
+ object_ptr(
+ box,
+ tr::lng_credits_box_out_title(),
+ st::settingsPremiumUserTitle)));
+ Ui::AddSkip(content);
+ box->addRow(object_ptr>(
+ box,
+ object_ptr(
+ box,
+ tr::lng_credits_box_out_sure(
+ lt_count,
+ rpl::single(form->invoice.amount) | tr::to_count(),
+ lt_text,
+ rpl::single(TextWithEntities{ form->title }),
+ lt_bot,
+ rpl::single(TextWithEntities{ bot->name() }),
+ Ui::Text::RichLangValue),
+ st::creditsBoxAbout)));
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+
+ const auto button = box->addButton(rpl::single(QString()), [=] {
+ if (state->confirmButtonBusy.current()) {
+ return;
+ }
+ state->confirmButtonBusy = true;
+ session->api().request(
+ MTPpayments_SendStarsForm(
+ MTP_flags(0),
+ MTP_long(form->formId),
+ form->inputInvoice)
+ ).done([=](auto result) {
+ state->confirmButtonBusy = false;
+ box->closeBox();
+ sent();
+ }).fail([=](const MTP::Error &error) {
+ state->confirmButtonBusy = false;
+ box->uiShow()->showToast(error.type());
+ }).send();
+ });
+ {
+ using namespace Info::Statistics;
+ const auto loadingAnimation = InfiniteRadialAnimationWidget(
+ button,
+ st::giveawayGiftCodeStartButton.height / 2);
+ AddChildToWidgetCenter(button.data(), loadingAnimation);
+ loadingAnimation->showOn(state->confirmButtonBusy.value());
+ }
+ {
+ const auto emojiMargin = QMargins(
+ 0,
+ -st::moderateBoxExpandInnerSkip,
+ 0,
+ 0);
+ const auto buttonEmoji = Ui::Text::SingleCustomEmoji(
+ session->data().customEmojiManager().registerInternalEmoji(
+ st::settingsPremiumIconStar,
+ emojiMargin,
+ true));
+ auto buttonText = tr::lng_credits_box_out_confirm(
+ lt_count,
+ rpl::single(form->invoice.amount) | tr::to_count(),
+ lt_emoji,
+ rpl::single(buttonEmoji),
+ Ui::Text::RichLangValue);
+ const auto buttonLabel = Ui::CreateChild(
+ button,
+ rpl::single(QString()),
+ st::defaultFlatLabel);
+ std::move(
+ buttonText
+ ) | rpl::start_with_next([=](const TextWithEntities &text) {
+ buttonLabel->setMarkedText(
+ text,
+ Core::MarkedTextContext{
+ .session = session,
+ .customEmojiRepaint = [=] { buttonLabel->update(); },
+ });
+ }, buttonLabel->lifetime());
+ buttonLabel->setTextColorOverride(
+ box->getDelegate()->style().button.textFg->c);
+ button->sizeValue(
+ ) | rpl::start_with_next([=](const QSize &size) {
+ buttonLabel->moveToLeft(
+ (size.width() - buttonLabel->width()) / 2,
+ (size.height() - buttonLabel->height()) / 2);
+ }, buttonLabel->lifetime());
+ buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
+ state->confirmButtonBusy.value(
+ ) | rpl::start_with_next([=](bool busy) {
+ buttonLabel->setVisible(!busy);
+ }, buttonLabel->lifetime());
+ }
+
+ const auto buttonWidth = st::boxWidth
+ - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
+ button->widthValue() | rpl::filter([=] {
+ return (button->widthNoMargins() != buttonWidth);
+ }) | rpl::start_with_next([=] {
+ button->resizeToWidth(buttonWidth);
+ }, button->lifetime());
+
+ {
+ const auto close = Ui::CreateChild(
+ box.get(),
+ st::boxTitleClose);
+ close->setClickedCallback([=] {
+ box->closeBox();
+ });
+ box->widthValue(
+ ) | rpl::start_with_next([=](int width) {
+ close->moveToRight(0, 0);
+ close->raise();
+ }, close->lifetime());
+ }
+
+ {
+ const auto balance = Settings::AddBalanceWidget(
+ content,
+ session->creditsValue(),
+ false);
+ const auto api = balance->lifetime().make_state(
+ session->user());
+ api->request({}, [=](Data::CreditsStatusSlice slice) {
+ session->setCredits(slice.balance);
+ });
+ rpl::combine(
+ balance->sizeValue(),
+ content->sizeValue()
+ ) | rpl::start_with_next([=](const QSize &, const QSize &) {
+ balance->moveToLeft(
+ st::creditsHistoryRightSkip * 2,
+ st::creditsHistoryRightSkip);
+ balance->update();
+ }, balance->lifetime());
+ }
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h
new file mode 100644
index 000000000..25ceb1d56
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/send_credits_box.h
@@ -0,0 +1,25 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+class HistoryItem;
+
+namespace Payments {
+struct CreditsFormData;
+} // namespace Payments
+
+namespace Ui {
+
+class GenericBox;
+
+void SendCreditsBox(
+ not_null box,
+ std::shared_ptr data,
+ Fn sent);
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp
index 199b65941..75345a44c 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_files_box.cpp
@@ -336,7 +336,7 @@ SendFilesBox::SendFilesBox(
const TextWithTags &caption,
not_null toPeer,
Api::SendType sendType,
- SendMenu::Type sendMenuType)
+ SendMenu::Details sendMenuDetails)
: SendFilesBox(nullptr, {
.show = controller->uiShow(),
.list = std::move(list),
@@ -345,7 +345,7 @@ SendFilesBox::SendFilesBox(
.limits = DefaultLimitsForPeer(toPeer),
.check = DefaultCheckForPeer(controller, toPeer),
.sendType = sendType,
- .sendMenuType = sendMenuType,
+ .sendMenuDetails = [=] { return sendMenuDetails; },
}) {
}
@@ -358,7 +358,8 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
, _titleHeight(st::boxTitleHeight)
, _list(std::move(descriptor.list))
, _limits(descriptor.limits)
-, _sendMenuType(descriptor.sendMenuType)
+, _sendMenuDetails(prepareSendMenuDetails(descriptor))
+, _sendMenuCallback(prepareSendMenuCallback())
, _captionToPeer(descriptor.captionToPeer)
, _check(std::move(descriptor.check))
, _confirmedCallback(std::move(descriptor.confirmed))
@@ -372,6 +373,50 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
enqueueNextPrepare();
}
+Fn SendFilesBox::prepareSendMenuDetails(
+ const SendFilesBoxDescriptor &descriptor) {
+ auto initial = descriptor.sendMenuDetails;
+ return crl::guard(this, [=] {
+ auto result = initial ? initial() : SendMenu::Details();
+ result.spoiler = !hasSpoilerMenu()
+ ? SendMenu::SpoilerState::None
+ : allWithSpoilers()
+ ? SendMenu::SpoilerState::Enabled
+ : SendMenu::SpoilerState::Possible;
+ const auto way = _sendWay.current();
+ const auto canMoveCaption = _list.canMoveCaption(
+ way.groupFiles() && way.sendImagesAsPhotos(),
+ way.sendImagesAsPhotos()
+ ) && _caption && HasSendText(_caption);
+ result.caption = !canMoveCaption
+ ? SendMenu::CaptionState::None
+ : _invertCaption
+ ? SendMenu::CaptionState::Above
+ : SendMenu::CaptionState::Below;
+ return result;
+ });
+}
+
+auto SendFilesBox::prepareSendMenuCallback()
+-> Fn {
+ return crl::guard(this, [=](MenuAction action, MenuDetails details) {
+ using Type = SendMenu::ActionType;
+ switch (action.type) {
+ case Type::CaptionDown: _invertCaption = false; break;
+ case Type::CaptionUp: _invertCaption = true; break;
+ case Type::SpoilerOn: toggleSpoilers(true); break;
+ case Type::SpoilerOff: toggleSpoilers(false); break;
+ default:
+ SendMenu::DefaultCallback(
+ _show,
+ sendCallback())(
+ action,
+ details);
+ break;
+ }
+ });
+}
+
void SendFilesBox::initPreview() {
using namespace rpl::mappers;
@@ -537,10 +582,9 @@ void SendFilesBox::refreshButtons() {
if (_sendType == Api::SendType::Normal) {
SendMenu::SetupMenuAndShortcuts(
_send,
- [=] { return _sendMenuType; },
- [=] { sendSilent(); },
- [=] { sendScheduled(); },
- [=] { sendWhenOnline(); });
+ _show,
+ _sendMenuDetails,
+ _sendMenuCallback);
}
addButton(tr::lng_cancel(), [=] { closeBox(); });
_addFile = addLeftButton(
@@ -552,21 +596,14 @@ void SendFilesBox::refreshButtons() {
addMenuButton();
}
-bool SendFilesBox::hasSendMenu() const {
- return (_sendMenuType != SendMenu::Type::Disabled);
+bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const {
+ return (details.type != SendMenu::Type::Disabled)
+ || (details.spoiler != SendMenu::SpoilerState::None)
+ || (details.caption != SendMenu::CaptionState::None);
}
bool SendFilesBox::hasSpoilerMenu() const {
- const auto allAreVideo = !ranges::any_of(_list.files, [](const auto &f) {
- using Type = Ui::PreparedFile::Type;
- return (f.type != Type::Video);
- });
- const auto allAreMedia = !ranges::any_of(_list.files, [](const auto &f) {
- using Type = Ui::PreparedFile::Type;
- return (f.type != Type::Photo) && (f.type != Type::Video);
- });
- return allAreVideo
- || (allAreMedia && _sendWay.current().sendImagesAsPhotos());
+ return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
}
void SendFilesBox::applyBlockChanges() {
@@ -590,36 +627,23 @@ void SendFilesBox::toggleSpoilers(bool enabled) {
}
void SendFilesBox::addMenuButton() {
- if (!hasSendMenu() && !hasSpoilerMenu()) {
+ const auto details = _sendMenuDetails();
+ if (!hasSendMenu(details)) {
return;
}
const auto top = addTopButton(_st.files.menu);
top->setClickedCallback([=] {
const auto &tabbed = _st.tabbed;
- const auto &icons = tabbed.icons;
_menu = base::make_unique_q(top, tabbed.menu);
- if (hasSpoilerMenu()) {
- const auto spoilered = allWithSpoilers();
- _menu->addAction(
- (spoilered
- ? tr::lng_context_disable_spoiler(tr::now)
- : tr::lng_context_spoiler_effect(tr::now)),
- [=] { toggleSpoilers(!spoilered); },
- spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
- if (hasSendMenu()) {
- _menu->addSeparator(&tabbed.expandedSeparator);
- }
- }
- if (hasSendMenu()) {
- SendMenu::FillSendMenu(
- _menu.get(),
- _sendMenuType,
- [=] { sendSilent(); },
- [=] { sendScheduled(); },
- [=] { sendWhenOnline(); },
- &_st.tabbed.icons);
- }
+ const auto position = QCursor::pos();
+ SendMenu::FillSendMenu(
+ _menu.get(),
+ _show,
+ _sendMenuDetails(),
+ _sendMenuCallback,
+ &_st.tabbed.icons,
+ position);
using ImageInfo = Ui::PreparedFileInformation::Image;
if (_list.files.size() == 1 && std::get_if(&_list.files[0].information->media)) {
@@ -650,10 +674,9 @@ void SendFilesBox::addMenuButton() {
},
&st::menuIconStickers);
}
- _menu->popup(QCursor::pos());
+ _menu->popup(position);
return true;
});
-
}
void SendFilesBox::initSendWay() {
@@ -695,9 +718,7 @@ void SendFilesBox::initSendWay() {
for (auto &block : _blocks) {
block.setSendWay(value);
}
- if (!hasSendMenu()) {
- refreshButtons();
- }
+ refreshButtons();
if (was != hidden()) {
updateBoxSize();
updateControlsGeometry();
@@ -909,9 +930,7 @@ void SendFilesBox::pushBlock(int from, int till) {
}
void SendFilesBox::refreshControls(bool initial) {
- if (initial || !hasSendMenu()) {
- refreshButtons();
- }
+ refreshButtons();
refreshTitleText();
updateSendWayControls();
updateCaptionPlaceholder();
@@ -1477,7 +1496,12 @@ void SendFilesBox::send(
if ((_sendType == Api::SendType::Scheduled
|| _sendType == Api::SendType::ScheduledToUser)
&& !options.scheduled) {
- return sendScheduled();
+ auto child = _sendMenuDetails();
+ child.spoiler = SendMenu::SpoilerState::None;
+ child.caption = SendMenu::CaptionState::None;
+ return SendMenu::DefaultCallback(_show, sendCallback())(
+ { .type = SendMenu::ActionType::Schedule },
+ child);
}
if (_preparing) {
_whenReadySend = [=] {
@@ -1502,6 +1526,7 @@ void SendFilesBox::send(
auto caption = (_caption && !_caption->isHidden())
? _caption->getTextWithAppliedMarkdown()
: TextWithTags();
+ options.invertCaption = _invertCaption;
if (!validateLength(caption.text)) {
return;
}
@@ -1515,25 +1540,10 @@ void SendFilesBox::send(
closeBox();
}
-void SendFilesBox::sendSilent() {
- send({ .silent = true });
-}
-
-void SendFilesBox::sendScheduled() {
- const auto type = (_sendType == Api::SendType::ScheduledToUser)
- ? SendMenu::Type::ScheduledToUser
- : _sendMenuType;
- const auto callback = [=](Api::SendOptions options) { send(options); };
- auto box = HistoryView::PrepareScheduleBox(this, type, callback);
- const auto weak = Ui::MakeWeak(box.data());
- _show->showBox(std::move(box));
- if (const auto strong = weak.data()) {
- strong->setCloseByOutsideClick(false);
- }
-}
-
-void SendFilesBox::sendWhenOnline() {
- send(Api::DefaultSendWhenOnlineOptions());
+Fn SendFilesBox::sendCallback() {
+ return crl::guard(this, [=](Api::SendOptions options) {
+ send(options, false);
+ });
}
SendFilesBox::~SendFilesBox() = default;
diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h
index af712eda1..1e95a1aa8 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.h
+++ b/Telegram/SourceFiles/boxes/send_files_box.h
@@ -47,7 +47,8 @@ class SessionController;
} // namespace Window
namespace SendMenu {
-enum class Type;
+struct Details;
+struct Action;
} // namespace SendMenu
namespace HistoryView::Controls {
@@ -96,7 +97,7 @@ struct SendFilesBoxDescriptor {
SendFilesLimits limits = {};
SendFilesCheck check;
Api::SendType sendType = {};
- SendMenu::Type sendMenuType = {};
+ Fn sendMenuDetails = nullptr;
const style::ComposeControls *stOverride = nullptr;
SendFilesConfirmed confirmed;
Fn cancelled;
@@ -115,7 +116,7 @@ public:
const TextWithTags &caption,
not_null toPeer,
Api::SendType sendType,
- SendMenu::Type sendMenuType);
+ SendMenu::Details sendMenuDetails);
SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor);
void setConfirmedCallback(SendFilesConfirmed callback) {
@@ -136,6 +137,9 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
+ using MenuAction = SendMenu::Action;
+ using MenuDetails = SendMenu::Details;
+
class Block final {
public:
Block(
@@ -173,7 +177,7 @@ private:
void initSendWay();
void initPreview();
- [[nodiscard]] bool hasSendMenu() const;
+ [[nodiscard]] bool hasSendMenu(const MenuDetails &details) const;
[[nodiscard]] bool hasSpoilerMenu() const;
[[nodiscard]] bool allWithSpoilers();
[[nodiscard]] bool checkWithWay(
@@ -202,9 +206,7 @@ private:
void generatePreviewFrom(int fromBlock);
void send(Api::SendOptions options, bool ctrlShiftEnter = false);
- void sendSilent();
- void sendScheduled();
- void sendWhenOnline();
+ [[nodiscard]] Fn sendCallback();
void captionResized();
void saveSendWaySettings();
@@ -227,6 +229,11 @@ private:
void checkCharsLimitation();
+ [[nodiscard]] Fn prepareSendMenuDetails(
+ const SendFilesBoxDescriptor &descriptor);
+ [[nodiscard]] auto prepareSendMenuCallback()
+ -> Fn;
+
const std::shared_ptr _show;
const style::ComposeControls &_st;
const Api::SendType _sendType = Api::SendType();
@@ -238,12 +245,14 @@ private:
std::optional _removingIndex;
SendFilesLimits _limits = {};
- SendMenu::Type _sendMenuType = {};
+ Fn _sendMenuDetails;
+ Fn _sendMenuCallback;
PeerData *_captionToPeer = nullptr;
SendFilesCheck _check;
SendFilesConfirmed _confirmedCallback;
Fn _cancelledCallback;
bool _confirmed = false;
+ bool _invertCaption = false;
object_ptr _caption = { nullptr };
TextWithTags _prefilledCaptionText;
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index f4d691cb9..d86e24510 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -478,15 +478,18 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
}
}
-SendMenu::Type ShareBox::sendMenuType() const {
+SendMenu::Details ShareBox::sendMenuDetails() const {
const auto selected = _inner->selected();
- return ranges::all_of(
+ const auto type = ranges::all_of(
selected | ranges::views::transform(&Data::Thread::peer),
HistoryView::CanScheduleUntilOnline)
? SendMenu::Type::ScheduledToUser
: (selected.size() == 1 && selected.front()->peer()->isSelf())
? SendMenu::Type::Reminder
: SendMenu::Type::Scheduled;
+
+ // We can't support effect here because we don't have ChatHelpers::Show.
+ return { .type = type, .effectAllowed = false };
}
void ShareBox::showMenu(not_null parent) {
@@ -523,15 +526,32 @@ void ShareBox::showMenu(not_null parent) {
_menu->addSeparator();
}
- const auto result = SendMenu::FillSendMenu(
+ using namespace SendMenu;
+ const auto sendAction = crl::guard(this, [=](Action action, Details) {
+ if (action.type == ActionType::Send) {
+ submit(action.options);
+ return;
+ }
+ uiShow()->showBox(
+ HistoryView::PrepareScheduleBox(
+ this,
+ nullptr, // ChatHelpers::Show for effect attachment.
+ sendMenuDetails(),
+ [=](Api::SendOptions options) { submit(options); },
+ action.options,
+ HistoryView::DefaultScheduleTime(),
+ _descriptor.scheduleBoxStyle));
+ });
+ _menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
+ const auto result = FillSendMenu(
_menu.get(),
- sendMenuType(),
- [=] { submitSilent(); },
- [=] { submitScheduled(); },
- [=] { submitWhenOnline(); });
- const auto success = (result == SendMenu::FillMenuResult::Success);
- if (_descriptor.forwardOptions.show || success) {
- _menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
+ nullptr, // showForEffect.
+ sendMenuDetails(),
+ sendAction);
+ if (result == SendMenu::FillMenuResult::Prepared) {
+ _menu->popupPrepared();
+ } else if (_descriptor.forwardOptions.show
+ && result != SendMenu::FillMenuResult::Failed) {
_menu->popup(QCursor::pos());
}
}
@@ -612,25 +632,6 @@ void ShareBox::submit(Api::SendOptions options) {
}
}
-void ShareBox::submitSilent() {
- submit({ .silent = true });
-}
-
-void ShareBox::submitScheduled() {
- const auto callback = [=](Api::SendOptions options) { submit(options); };
- uiShow()->showBox(
- HistoryView::PrepareScheduleBox(
- this,
- sendMenuType(),
- callback,
- HistoryView::DefaultScheduleTime(),
- _descriptor.scheduleBoxStyle));
-}
-
-void ShareBox::submitWhenOnline() {
- submit(Api::DefaultSendWhenOnlineOptions());
-}
-
void ShareBox::copyLink() const {
if (const auto onstack = _descriptor.copyCallback) {
onstack();
diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h
index 253a8228e..32e824b15 100644
--- a/Telegram/SourceFiles/boxes/share_box.h
+++ b/Telegram/SourceFiles/boxes/share_box.h
@@ -24,7 +24,7 @@ struct PeerList;
} // namespace style
namespace SendMenu {
-enum class Type;
+struct Details;
} // namespace SendMenu
namespace Window {
@@ -130,13 +130,10 @@ private:
void scrollAnimationCallback();
void submit(Api::SendOptions options);
- void submitSilent();
- void submitScheduled();
- void submitWhenOnline();
void copyLink() const;
bool searchByUsername(bool useCache = false);
- SendMenu::Type sendMenuType() const;
+ [[nodiscard]] SendMenu::Details sendMenuDetails() const;
void scrollTo(Ui::ScrollToRequest request);
void needSearchByUsername();
diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp
index 54fb913fb..a099fcfa6 100644
--- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp
+++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp
@@ -79,7 +79,9 @@ using Data::StickersSet;
using Data::StickersPack;
using SetFlag = Data::StickersSetFlag;
-[[nodiscard]] std::optional ComputeImageColor(const QImage &frame) {
+[[nodiscard]] std::optional ComputeImageColor(
+ const style::icon &lockIcon,
+ const QImage &frame) {
if (frame.isNull()
|| frame.format() != QImage::Format_ARGB32_Premultiplied) {
return {};
@@ -89,7 +91,7 @@ using SetFlag = Data::StickersSetFlag;
auto sb = int64();
auto sa = int64();
const auto factor = frame.devicePixelRatio();
- const auto size = st::stickersPremiumLock.size() * factor;
+ const auto size = lockIcon.size() * factor;
const auto width = std::min(frame.width(), size.width());
const auto height = std::min(frame.height(), size.height());
const auto skipx = (frame.width() - width) / 2;
@@ -116,22 +118,30 @@ using SetFlag = Data::StickersSetFlag;
}
-[[nodiscard]] QColor ComputeLockColor(const QImage &frame) {
- return ComputeImageColor(frame).value_or(st::windowSubTextFg->c);
+[[nodiscard]] QColor ComputeLockColor(
+ const style::icon &lockIcon,
+ const QImage &frame) {
+ return ComputeImageColor(
+ lockIcon,
+ frame
+ ).value_or(st::windowSubTextFg->c);
}
-void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
+void ValidatePremiumLockBg(
+ const style::icon &lockIcon,
+ QImage &image,
+ const QImage &frame) {
if (!image.isNull()) {
return;
}
const auto factor = style::DevicePixelRatio();
- const auto size = st::stickersPremiumLock.size();
+ const auto size = lockIcon.size();
image = QImage(
size * factor,
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(factor);
auto p = QPainter(&image);
- const auto color = ComputeLockColor(frame);
+ const auto color = ComputeLockColor(lockIcon, frame);
p.fillRect(
QRect(QPoint(), size),
anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
@@ -140,12 +150,12 @@ void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
image = Images::Circle(std::move(image));
}
-void ValidatePremiumStarFg(QImage &image) {
+void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) {
if (!image.isNull()) {
return;
}
const auto factor = style::DevicePixelRatio();
- const auto size = st::stickersPremiumLock.size();
+ const auto size = lockIcon.size();
image = QImage(
size * factor,
QImage::Format_ARGB32_Premultiplied);
@@ -182,7 +192,10 @@ void ValidatePremiumStarFg(QImage &image) {
} // namespace
-StickerPremiumMark::StickerPremiumMark(not_null session) {
+StickerPremiumMark::StickerPremiumMark(
+ not_null session,
+ const style::icon &lockIcon)
+: _lockIcon(lockIcon) {
style::PaletteChanged(
) | rpl::start_with_next([=] {
_lockGray = QImage();
@@ -208,16 +221,14 @@ void StickerPremiumMark::paint(
const auto factor = style::DevicePixelRatio();
const auto radius = st::roundRadiusSmall;
const auto point = position + QPoint(
- (_premium
- ? (singleSize.width() - (bg.width() / factor) - radius)
- : (singleSize.width() - (bg.width() / factor)) / 2),
+ (singleSize.width() - (bg.width() / factor) - radius),
singleSize.height() - (bg.height() / factor) - radius);
p.drawImage(point, bg);
if (_premium) {
validateStar();
p.drawImage(point, _star);
} else {
- st::stickersPremiumLock.paint(p, point, outerWidth);
+ _lockIcon.paint(p, point, outerWidth);
}
}
@@ -225,11 +236,11 @@ void StickerPremiumMark::validateLock(
const QImage &frame,
QImage &backCache) {
auto &image = frame.isNull() ? _lockGray : backCache;
- ValidatePremiumLockBg(image, frame);
+ ValidatePremiumLockBg(_lockIcon, image, frame);
}
void StickerPremiumMark::validateStar() {
- ValidatePremiumStarFg(_star);
+ ValidatePremiumStarFg(_lockIcon, _star);
}
class StickerSetBox::Inner final : public Ui::RpWidget {
@@ -708,7 +719,7 @@ StickerSetBox::Inner::Inner(
st::windowBgRipple,
st::windowBgOver,
[=] { repaintItems(); }))
-, _premiumMark(_session)
+, _premiumMark(_session, st::stickersPremiumLock)
, _updateItemsTimer([=] { updateItems(); })
, _input(set)
, _padding((type == Data::StickersType::Emoji)
@@ -1058,7 +1069,7 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
_menu = base::make_unique_q(
this,
st::popupMenuWithIcons);
- const auto type = _show->sendMenuType();
+ const auto details = _show->sendMenuDetails();
if (setType() == Data::StickersType::Emoji) {
if (const auto t = PrepareTextFromEmoji(_pack[index]); !t.empty()) {
_menu->addAction(tr::lng_mediaview_copy(tr::now), [=] {
@@ -1067,17 +1078,16 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
}
}, &st::menuIconCopy);
}
- } else if (type != SendMenu::Type::Disabled) {
+ } else if (details.type != SendMenu::Type::Disabled) {
const auto document = _pack[index];
- const auto sendSelected = [=](Api::SendOptions options) {
+ const auto send = crl::guard(this, [=](Api::SendOptions options) {
chosen(index, document, options);
- };
+ });
SendMenu::FillSendMenu(
_menu.get(),
- type,
- SendMenu::DefaultSilentCallback(sendSelected),
- SendMenu::DefaultScheduleCallback(_show, type, sendSelected),
- SendMenu::DefaultWhenOnlineCallback(sendSelected));
+ _show,
+ details,
+ SendMenu::DefaultCallback(_show, send));
const auto show = _show;
const auto toggleFavedSticker = [=] {
diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.h b/Telegram/SourceFiles/boxes/sticker_set_box.h
index c57a1f484..e718cc45e 100644
--- a/Telegram/SourceFiles/boxes/sticker_set_box.h
+++ b/Telegram/SourceFiles/boxes/sticker_set_box.h
@@ -23,10 +23,6 @@ namespace Data {
class StickersSet;
} // namespace Data
-namespace SendMenu {
-enum class Type;
-} // namespace SendMenu
-
namespace ChatHelpers {
struct FileChosen;
class Show;
@@ -34,7 +30,9 @@ class Show;
class StickerPremiumMark final {
public:
- explicit StickerPremiumMark(not_null session);
+ StickerPremiumMark(
+ not_null session,
+ const style::icon &lockIcon);
void paint(
QPainter &p,
@@ -48,6 +46,7 @@ private:
void validateLock(const QImage &frame, QImage &backCache);
void validateStar();
+ const style::icon &_lockIcon;
QImage _lockGray;
QImage _star;
bool _premium = false;
diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index 0631332f9..270bed675 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -69,6 +69,8 @@ ComposeIcons {
menuWhenOnline: icon;
menuSpoiler: icon;
menuSpoilerOff: icon;
+ menuBelow: icon;
+ menuAbove: icon;
stripBubble: icon;
stripExpandPanel: icon;
@@ -489,6 +491,7 @@ hashtagClose: IconButton {
stickerPanWidthMin: 64px;
stickerPanSize: size(stickerPanWidthMin, stickerPanWidthMin);
+stickerEffectWidthMin: 48px;
stickerPanPadding: 11px;
stickerPanDeleteIconBg: icon {{ "emoji/emoji_delete_bg", stickerPanDeleteBg }};
stickerPanDeleteIconFg: icon {{ "emoji/emoji_delete", stickerPanDeleteFg }};
@@ -605,6 +608,8 @@ defaultComposeIcons: ComposeIcons {
menuWhenOnline: menuIconWhenOnline;
menuSpoiler: menuIconSpoiler;
menuSpoilerOff: menuIconSpoilerOff;
+ menuBelow: menuIconBelow;
+ menuAbove: menuIconAbove;
stripBubble: icon{
{ "chat/reactions_bubble_shadow", windowShadowFg },
@@ -669,7 +674,7 @@ defaultEmojiPan: EmojiPan {
boxLabel: boxLabel;
icons: defaultComposeIcons;
about: defaultEmojiPanAbout;
- aboutPadding: margins(12px, 2px, 12px, 2px);
+ aboutPadding: margins(12px, 3px, 12px, 2px);
autocompleteBottomSkip: 0px;
}
statusEmojiPan: EmojiPan(defaultEmojiPan) {
@@ -753,6 +758,7 @@ inlineResultsMinWidth: 48px;
inlineDurationMargin: 3px;
stickersPremiumLock: icon{{ "emoji/premium_lock", premiumButtonFg }};
+emojiPremiumLock: icon{{ "chat/mini_lock", premiumButtonFg }};
reactStripExtend: margins(21px, 49px, 39px, 0px);
reactStripHeight: 40px;
@@ -924,7 +930,12 @@ historyPinnedBotButton: RoundButton(defaultActiveButton) {
textTop: 6px;
padding: margins(2px, 10px, 10px, 9px);
}
-historyPinnedBotButtonMaxWidth: 150px;
+historyPinnedBotLabel: FlatLabel(defaultFlatLabel) {
+ style: semiboldTextStyle;
+ align: align(center);
+ maxHeight: 30px;
+}
+historyPinnedBotButtonMaxWidth: 120px;
historyToDownPosition: point(12px, 10px);
historyToDownAbove: icon {{ "history_down_arrow", historyToDownFg }};
diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_show.h b/Telegram/SourceFiles/chat_helpers/compose/compose_show.h
index 7dcbab513..28fc7ff47 100644
--- a/Telegram/SourceFiles/chat_helpers/compose/compose_show.h
+++ b/Telegram/SourceFiles/chat_helpers/compose/compose_show.h
@@ -22,7 +22,7 @@ class SessionController;
} // namespace Window
namespace SendMenu {
-enum class Type;
+struct Details;
} // namespace SendMenu
namespace ChatHelpers {
@@ -57,7 +57,7 @@ public:
[[nodiscard]] virtual rpl::producer<> pauseChanged() const = 0;
[[nodiscard]] virtual rpl::producer adjustShadowLeft() const;
- [[nodiscard]] virtual SendMenu::Type sendMenuType() const = 0;
+ [[nodiscard]] virtual SendMenu::Details sendMenuDetails() const = 0;
virtual bool showMediaPreview(
Data::FileOrigin origin,
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
index e2b41060e..443592ea6 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
@@ -136,6 +136,7 @@ struct EmojiListWidget::CustomEmojiInstance {
struct EmojiListWidget::RecentOne {
Ui::Text::CustomEmoji *custom = nullptr;
RecentEmojiId id;
+ mutable QImage premiumLock;
};
EmojiColorPicker::EmojiColorPicker(
@@ -478,8 +479,12 @@ EmojiListWidget::EmojiListWidget(
, _localSetsManager(
std::make_unique(&session()))
, _customRecentFactory(std::move(descriptor.customRecentFactory))
+, _freeEffects(std::move(descriptor.freeEffects))
, _customTextColor(std::move(descriptor.customTextColor))
, _overBg(st::emojiPanRadius, st().overBg)
+, _premiumMark(std::make_unique(
+ &session(),
+ st::emojiPremiumLock))
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
, _picker(this, st())
, _showPickerTimer([=] { showPicker(); })
@@ -583,9 +588,18 @@ void EmojiListWidget::setupSearch() {
InvokeQueued(this, [=] {
applyNextSearchQuery();
});
+ _searchQueries.fire_copy(_nextSearchQuery);
}, session, type);
}
+rpl::producer> EmojiListWidget::searchQueries() const {
+ return _searchQueries.events();
+}
+
+rpl::producer EmojiListWidget::recentShownCount() const {
+ return _recentShownCount.value();
+}
+
void EmojiListWidget::applyNextSearchQuery() {
if (_searchQuery == _nextSearchQuery) {
return;
@@ -607,6 +621,9 @@ void EmojiListWidget::applyNextSearchQuery() {
_searchCustomIds.clear();
}
resizeToWidth(width());
+ _recentShownCount = searching
+ ? _searchResults.size()
+ : _recent.size();
update();
if (modeChanged) {
visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom());
@@ -834,7 +851,8 @@ void EmojiListWidget::unloadCustomIn(const SectionInfo &info) {
object_ptr EmojiListWidget::createFooter() {
Expects(_footer == nullptr);
- if (_mode == EmojiListMode::RecentReactions) {
+ if (_mode == EmojiListMode::RecentReactions
+ || _mode == EmojiListMode::MessageEffects) {
return { nullptr };
}
@@ -1018,9 +1036,10 @@ int EmojiListWidget::countDesiredHeight(int newWidth) {
const auto minimalLastHeight = std::max(
minimalHeight - padding.bottom(),
0);
- return qMax(
- minimalHeight,
- countResult(minimalLastHeight) + padding.bottom());
+ const auto result = countResult(minimalLastHeight);
+ return result
+ ? qMax(minimalHeight, result + padding.bottom())
+ : 0;
}
int EmojiListWidget::defaultMinimalHeight() const {
@@ -1104,7 +1123,7 @@ void EmojiListWidget::fillRecentFrom(const std::vector &list) {
}
base::unique_qptr EmojiListWidget::fillContextMenu(
- SendMenu::Type type) {
+ const SendMenu::Details &details) {
if (v::is_null(_selected)) {
return nullptr;
}
@@ -1285,6 +1304,8 @@ void EmojiListWidget::paint(
QRect clip) {
validateEmojiPaintContext(context);
+ _paintAsPremium = session().premium();
+
auto fromColumn = floorclamp(
clip.x() - _rowsLeft,
_singleSize.width(),
@@ -1449,16 +1470,44 @@ void EmojiListWidget::drawRecent(
QPoint position,
const RecentOne &recent) {
_recentPainted = true;
+ const auto locked = (_mode == Mode::MessageEffects)
+ && !_paintAsPremium
+ && v::is(recent.id.data)
+ && !_freeEffects.contains(
+ v::get(recent.id.data).id);
+ auto lockedPainted = false;
+ if (locked) {
+ if (_premiumMarkFrameCache.isNull()) {
+ const auto ratio = style::DevicePixelRatio();
+ _premiumMarkFrameCache = QImage(
+ QSize(_customSingleSize, _customSingleSize) * ratio,
+ QImage::Format_ARGB32_Premultiplied);
+ _premiumMarkFrameCache.setDevicePixelRatio(ratio);
+ }
+ _premiumMarkFrameCache.fill(Qt::transparent);
+ }
if (const auto custom = recent.custom) {
- _emojiPaintContext->scale = context.progress;
- _emojiPaintContext->position = position
+ const auto exactPosition = position
+ _innerPosition
+ _customPosition;
+ _emojiPaintContext->scale = context.progress;
if (_mode == Mode::ChannelStatus) {
_emojiPaintContext->internal.forceFirstFrame
= (recent.id == _recent.front().id);
}
- custom->paint(p, *_emojiPaintContext);
+ if (locked) {
+ lockedPainted = custom->ready();
+
+ auto q = Painter(&_premiumMarkFrameCache);
+ _emojiPaintContext->position = QPoint();
+ custom->paint(q, *_emojiPaintContext);
+ q.end();
+
+ p.drawImage(exactPosition, _premiumMarkFrameCache);
+ } else {
+ _emojiPaintContext->position = exactPosition;
+ custom->paint(p, *_emojiPaintContext);
+ }
} else if (const auto emoji = std::get_if(&recent.id.data)) {
if (_mode == Mode::EmojiStatus) {
position += QPoint(
@@ -1472,6 +1521,16 @@ void EmojiListWidget::drawRecent(
} else {
Unexpected("Empty custom emoji in EmojiListWidget::drawRecent.");
}
+
+ if (locked) {
+ _premiumMark->paint(
+ p,
+ lockedPainted ? _premiumMarkFrameCache : QImage(),
+ recent.premiumLock,
+ position,
+ _singleSize,
+ width());
+ }
}
void EmojiListWidget::drawEmoji(
@@ -2131,7 +2190,7 @@ void EmojiListWidget::refreshRecent() {
}
void EmojiListWidget::refreshCustom() {
- if (_mode == Mode::RecentReactions) {
+ if (_mode == Mode::RecentReactions || _mode == Mode::MessageEffects) {
return;
}
auto old = base::take(_custom);
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
index 3159d340e..3aef95ac0 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
+++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
@@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/round_rect.h"
#include "base/timer.h"
+class StickerPremiumMark;
+
namespace style {
struct EmojiPan;
} // namespace style
@@ -77,6 +79,7 @@ enum class EmojiListMode {
UserpicBuilder,
BackgroundEmoji,
PeerTitle,
+ MessageEffects,
};
struct EmojiListDescriptor {
@@ -88,6 +91,7 @@ struct EmojiListDescriptor {
Fn(
DocumentId,
Fn)> customRecentFactory;
+ base::flat_set freeEffects;
const style::EmojiPan *st = nullptr;
ComposeFeatures features;
};
@@ -144,7 +148,10 @@ public:
RectPart origin);
base::unique_qptr fillContextMenu(
- SendMenu::Type type) override;
+ const SendMenu::Details &details) override;
+
+ [[nodiscard]] rpl::producer> searchQueries() const;
+ [[nodiscard]] rpl::producer recentShownCount() const;
protected:
void visibleTopBottomUpdated(
@@ -397,10 +404,13 @@ private:
int _counts[kEmojiSectionCount];
std::vector _recent;
base::flat_set _recentCustomIds;
+ base::flat_set _freeEffects;
base::flat_set _repaintsScheduled;
+ rpl::variable _recentShownCount;
std::unique_ptr