diff --git a/CMakeLists.txt b/CMakeLists.txt
index b4c265e24..2f60f982c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,14 +57,6 @@ include(cmake/validate_d3d_compiler.cmake)
include(cmake/target_prepare_qrc.cmake)
include(cmake/options.cmake)
-
-if (NOT DESKTOP_APP_USE_PACKAGED)
- if (WIN32)
- set(qt_version 5.15.13)
- elseif (APPLE)
- set(qt_version 6.2.8)
- endif()
-endif()
include(cmake/external/qt/package.cmake)
set(desktop_app_skip_libs
diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index b3a50984f..0bd3e89cc 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -195,6 +195,7 @@ PRIVATE
api/api_earn.h
api/api_editing.cpp
api/api_editing.h
+ api/api_filter_updates.h
api/api_global_privacy.cpp
api/api_global_privacy.h
api/api_hash.cpp
@@ -233,6 +234,10 @@ PRIVATE
api/api_single_message_search.h
api/api_statistics.cpp
api/api_statistics.h
+ api/api_statistics_data_deserialize.cpp
+ api/api_statistics_data_deserialize.h
+ api/api_statistics_sender.cpp
+ api/api_statistics_sender.h
api/api_text_entities.cpp
api/api_text_entities.h
api/api_toggling_media.cpp
@@ -791,8 +796,6 @@ PRIVATE
history/view/media/history_view_dice.h
history/view/media/history_view_document.cpp
history/view/media/history_view_document.h
- history/view/media/history_view_extended_preview.cpp
- history/view/media/history_view_extended_preview.h
history/view/media/history_view_file.cpp
history/view/media/history_view_file.h
history/view/media/history_view_game.cpp
@@ -956,6 +959,10 @@ PRIVATE
history/history_view_highlight_manager.h
history/history_widget.cpp
history/history_widget.h
+ info/bot/earn/info_earn_inner_widget.cpp
+ info/bot/earn/info_earn_inner_widget.h
+ info/bot/earn/info_earn_widget.cpp
+ info/bot/earn/info_earn_widget.h
info/channel_statistics/boosts/create_giveaway_box.cpp
info/channel_statistics/boosts/create_giveaway_box.h
info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp
@@ -1608,6 +1615,8 @@ PRIVATE
window/window_peer_menu.cpp
window/window_peer_menu.h
window/window_section_common.h
+ window/window_separate_id.cpp
+ window/window_separate_id.h
window/window_session_controller.cpp
window/window_session_controller.h
window/window_session_controller_link_info.h
@@ -1899,12 +1908,44 @@ if (WIN32)
/DELAYLOAD:uxtheme.dll
/DELAYLOAD:crypt32.dll
/DELAYLOAD:bcrypt.dll
- /DELAYLOAD:imm32.dll
/DELAYLOAD:netapi32.dll
+ /DELAYLOAD:imm32.dll
/DELAYLOAD:userenv.dll
/DELAYLOAD:wtsapi32.dll
/DELAYLOAD:propsys.dll
)
+ if (QT_VERSION GREATER 6)
+ target_link_options(Telegram
+ PRIVATE
+ /DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-Console-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-1.dll
+ /DELAYLOAD:API-MS-Win-Core-File-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-LibraryLoader-l1-2-0.dll
+ /DELAYLOAD:API-MS-Win-Core-Localization-l1-2-0.dll
+ /DELAYLOAD:API-MS-Win-Core-Memory-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-Memory-l1-1-1.dll
+ /DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib
+ /DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll
+ /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll
+ /DELAYLOAD:authz.dll # Authz.lib
+ /DELAYLOAD:comdlg32.dll
+ /DELAYLOAD:dwrite.dll # DWrite.lib
+ /DELAYLOAD:dxgi.dll # DXGI.lib
+ /DELAYLOAD:d3d9.dll # D3D9.lib
+ /DELAYLOAD:d3d11.dll # D3D11.lib
+ /DELAYLOAD:d3d12.dll # D3D12.lib
+ /DELAYLOAD:setupapi.dll # SetupAPI.lib
+ /DELAYLOAD:winhttp.dll
+ )
+ endif()
endif()
target_prepare_qrc(Telegram)
diff --git a/Telegram/Resources/icons/menu/passcode_finger.png b/Telegram/Resources/icons/menu/passcode_finger.png
new file mode 100644
index 000000000..ee267f708
Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_finger.png differ
diff --git a/Telegram/Resources/icons/menu/passcode_finger@2x.png b/Telegram/Resources/icons/menu/passcode_finger@2x.png
new file mode 100644
index 000000000..a0a34240b
Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_finger@2x.png differ
diff --git a/Telegram/Resources/icons/menu/passcode_finger@3x.png b/Telegram/Resources/icons/menu/passcode_finger@3x.png
new file mode 100644
index 000000000..291fc8eaf
Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_finger@3x.png differ
diff --git a/Telegram/Resources/icons/menu/passcode_winhello.png b/Telegram/Resources/icons/menu/passcode_winhello.png
new file mode 100644
index 000000000..e3effa23a
Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_winhello.png differ
diff --git a/Telegram/Resources/icons/menu/passcode_winhello@2x.png b/Telegram/Resources/icons/menu/passcode_winhello@2x.png
new file mode 100644
index 000000000..32bc819f5
Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_winhello@2x.png differ
diff --git a/Telegram/Resources/icons/menu/passcode_winhello@3x.png b/Telegram/Resources/icons/menu/passcode_winhello@3x.png
new file mode 100644
index 000000000..6ac7445c1
Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_winhello@3x.png differ
diff --git a/Telegram/Resources/icons/payments/small_star.png b/Telegram/Resources/icons/payments/small_star.png
new file mode 100644
index 000000000..94e3fc4c5
Binary files /dev/null and b/Telegram/Resources/icons/payments/small_star.png differ
diff --git a/Telegram/Resources/icons/payments/small_star@2x.png b/Telegram/Resources/icons/payments/small_star@2x.png
new file mode 100644
index 000000000..4dc7016fe
Binary files /dev/null and b/Telegram/Resources/icons/payments/small_star@2x.png differ
diff --git a/Telegram/Resources/icons/payments/small_star@3x.png b/Telegram/Resources/icons/payments/small_star@3x.png
new file mode 100644
index 000000000..8f7ae06da
Binary files /dev/null and b/Telegram/Resources/icons/payments/small_star@3x.png differ
diff --git a/Telegram/Resources/icons/settings/premium/effects.png b/Telegram/Resources/icons/settings/premium/effects.png
new file mode 100644
index 000000000..56af63063
Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/effects.png differ
diff --git a/Telegram/Resources/icons/settings/premium/effects@2x.png b/Telegram/Resources/icons/settings/premium/effects@2x.png
new file mode 100644
index 000000000..5f5e4ada6
Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/effects@2x.png differ
diff --git a/Telegram/Resources/icons/settings/premium/effects@3x.png b/Telegram/Resources/icons/settings/premium/effects@3x.png
new file mode 100644
index 000000000..30ec7d9ea
Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/effects@3x.png differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index c34c6406e..602acb977 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -683,6 +683,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_privacy_premium_link" = "Telegram Premium";
"lng_settings_passcode_disable" = "Disable Passcode";
"lng_settings_passcode_disable_sure" = "Are you sure you want to disable passcode?";
+"lng_settings_use_winhello" = "Unlock with Windows Hello";
+"lng_settings_use_winhello_about" = "You need to enter your passcode once before unlocking Telegram with Windows Hello.";
+"lng_settings_use_touchid" = "Unlock with Touch ID";
+"lng_settings_use_touchid_about" = "You need to enter your passcode once before unlocking Telegram with Touch ID.";
"lng_settings_password_disable" = "Disable Cloud Password";
"lng_settings_password_abort" = "Abort two-step verification setup";
"lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco";
@@ -897,6 +901,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_gift_premium_users_confirm" = "Proceed";
"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user.";
"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users.";
+"lng_settings_gift_premium_choose" = "Please choose at least one recipient.";
"lng_backgrounds_header" = "Choose Wallpaper";
"lng_theme_sure_keep" = "Keep this theme?";
@@ -991,6 +996,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passcode_ph" = "Your passcode";
"lng_passcode_submit" = "Submit";
"lng_passcode_logout" = "Log out";
+"lng_passcode_winhello" = "You need to enter your passcode\nbefore you can use Windows Hello.";
+"lng_passcode_touchid" = "You need to enter your passcode\nbefore you can use Touch ID.";
+"lng_passcode_winhello_unlock" = "Telegram wants to unlock with Windows Hello.";
+"lng_passcode_touchid_unlock" = "unlock";
"lng_passcode_create_button" = "Save Passcode";
"lng_passcode_check_button" = "Submit";
"lng_passcode_change_button" = "Save Passcode";
@@ -1560,6 +1569,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_bot_public_link" = "Public Link";
"lng_manage_peer_bot_public_links" = "Public Links";
+"lng_manage_peer_bot_balance" = "Balance";
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
@@ -2185,6 +2195,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_about_translation" = "Real-time translation of channels and chats into other languages.";
"lng_premium_summary_subtitle_business" = "Telegram Business";
"lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies.";
+"lng_premium_summary_subtitle_effects" = "Message Effects";
+"lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages.";
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
"lng_premium_summary_button" = "Subscribe for {cost} per month";
@@ -2325,15 +2337,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_media#one" = "Do you want to unlock {media} in {chat} for **{count} Star**?";
+"lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?";
+"lng_credits_box_out_photo" = "a photo";
+"lng_credits_box_out_photos#one" = "{count} photo";
+"lng_credits_box_out_photos#other" = "{count} photos";
+"lng_credits_box_out_video" = "a video";
+"lng_credits_box_out_videos#one" = "{count} video";
+"lng_credits_box_out_videos#other" = "{count} videos";
+"lng_credits_box_out_both" = "{photo} and {video}";
"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_media_done_title" = "Media Unlocked";
+"lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}.";
+"lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}.";
"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_via" = "Via";
+"lng_credits_box_history_entry_play_market" = "Play Market";
+"lng_credits_box_history_entry_app_store" = "App Store";
+"lng_credits_box_history_entry_fragment" = "Fragment";
+"lng_credits_box_history_entry_ads" = "Ads Platform";
"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_success_date" = "Transaction date";
+"lng_credits_box_history_entry_success_url" = "Transaction link";
+"lng_credits_box_history_entry_media" = "Media";
"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";
@@ -3305,6 +3337,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_spoiler_effect" = "Hide with Spoiler";
"lng_context_disable_spoiler" = "Remove Spoiler";
+"lng_context_make_paid" = "Make This Content Paid";
+"lng_context_change_price" = "Change Price";
"lng_factcheck_title" = "Fact Check";
"lng_factcheck_placeholder" = "Add Facts or Context";
@@ -3316,6 +3350,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_paid_title" = "Paid Content";
+"lng_paid_enter_cost" = "Enter Unlock Cost";
+"lng_paid_cost_placeholder" = "Stars to Unlock";
+"lng_paid_about" = "Users will have to transfer this amount of Stars to your channel in order to view this media. {link}";
+"lng_paid_about_link" = "More about stars >";
+"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars";
+"lng_paid_price" = "Unlock for {price}";
+
"lng_translate_show_original" = "Show Original";
"lng_translate_bar_to" = "Translate to {name}";
"lng_translate_bar_to_other" = "Translate to {name}";
@@ -3582,6 +3624,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_formatting_link_text" = "Text";
"lng_formatting_link_url" = "URL";
"lng_formatting_link_create" = "Create";
+"lng_formatting_code_title" = "Code Language";
+"lng_formatting_code_language" = "Language for syntax highlighting.";
+"lng_formatting_code_auto" = "Auto-Detect";
"lng_text_copied" = "Text copied to clipboard.";
"lng_code_copied" = "Block copied to clipboard.";
@@ -4132,6 +4177,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_restricted_send_polls_all" = "Posting polls isn't allowed in this group.";
"lng_restricted_send_public_polls" = "Sorry, public polls can't be forwarded to channels.";
+"lng_restricted_send_paid_media" = "Sorry, paid media can't be sent to this channel.";
"lng_restricted_send_voice_messages" = "{user} restricted sending of voice messages to them.";
"lng_restricted_send_video_messages" = "{user} restricted sending of video messages to them.";
@@ -5159,6 +5205,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_earn_history_return" = "Refund";
"lng_channel_earn_history_return_about" = "Refunded back";
"lng_channel_earn_history_pending" = "Pending";
+"lng_channel_earn_history_failed" = "Failed";
"lng_channel_earn_history_show_more#one" = "Show {count} More Transaction";
"lng_channel_earn_history_show_more#other" = "Show {count} More Transactions";
"lng_channel_earn_off" = "Switch Off Ads";
@@ -5181,6 +5228,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_earn_chart_revenue" = "Ad revenue";
"lng_channel_earn_chart_overriden_detail_currency" = "Revenue in TON";
"lng_channel_earn_chart_overriden_detail_usd" = "Revenue in USD";
+"lng_channel_earn_currency_history" = "TON Transactions";
+"lng_channel_earn_credits_history" = "Stars Transactions";
+"lng_channel_earn_out_check_password_about" = "You can withdraw only if you have:";
+
+"lng_bot_earn_title" = "Stars Balance";
+"lng_bot_earn_chart_revenue" = "Revenue";
+"lng_bot_earn_overview_title" = "Proceeds overview";
+"lng_bot_earn_available" = "Available balance";
+"lng_bot_earn_total" = "Total lifetime proceeds";
+"lng_bot_earn_balance_title" = "Available balance";
+"lng_bot_earn_balance_about" = "Stars from your total balance become available for spending on ads and rewards 21 days after they are earned.";
+"lng_bot_earn_balance_about_url" = "https://telegram.org/tos/stars";
+"lng_bot_earn_balance_button#one" = "Withdraw {emoji} {count}";
+"lng_bot_earn_balance_button#other" = "Withdraw {emoji} {count}";
+"lng_bot_earn_balance_button_all" = "Withdraw all stars";
+"lng_bot_earn_balance_button_locked" = "Withdraw";
+"lng_bot_earn_balance_button_buy_ads" = "Buy Ads";
+"lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}";
+"lng_bot_earn_out_ph" = "Enter amount to withdraw";
+"lng_bot_earn_balance_password_title" = "Two-step verification";
+"lng_bot_earn_balance_password_description" = "Please enter your password to collect.";
+"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less then {link}.";
+"lng_bot_earn_credits_out_minimal_link#one" = "{count} star";
+"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars";
"lng_contact_add" = "Add";
"lng_contact_send_message" = "message";
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index fed726359..6358fd7a8 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="5.2.0.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 7935257fe..a0add328c 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,1,7,0
- PRODUCTVERSION 5,1,7,0
+ FILEVERSION 5,2,0,0
+ PRODUCTVERSION 5,2,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "5.1.7.0"
+ VALUE "FileVersion", "5.2.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.1.7.0"
+ VALUE "ProductVersion", "5.2.0.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index abc321c45..f4d9012ed 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,1,7,0
- PRODUCTVERSION 5,1,7,0
+ FILEVERSION 5,2,0,0
+ PRODUCTVERSION 5,2,0,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.1.7.0"
+ VALUE "FileVersion", "5.2.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.1.7.0"
+ VALUE "ProductVersion", "5.2.0.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h
index efd92a9bc..cd8aa54e2 100644
--- a/Telegram/SourceFiles/api/api_common.h
+++ b/Telegram/SourceFiles/api/api_common.h
@@ -20,6 +20,7 @@ namespace Api {
inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
struct SendOptions {
+ uint64 price = 0;
PeerData *sendAs = nullptr;
TimeId scheduled = 0;
BusinessShortcutId shortcutId = 0;
diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp
index 44d274e58..356e2cffa 100644
--- a/Telegram/SourceFiles/api/api_credits.cpp
+++ b/Telegram/SourceFiles/api/api_credits.cpp
@@ -7,9 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_credits.h"
-#include "apiwrap.h"
+#include "api/api_statistics_data_deserialize.h"
#include "api/api_updates.h"
+#include "apiwrap.h"
#include "base/unixtime.h"
+#include "data/data_channel.h"
+#include "data/data_document.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_session.h"
@@ -20,25 +23,62 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
namespace {
+constexpr auto kTransactionsLimit = 100;
+
[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL(
const MTPStarsTransaction &tl,
not_null peer) {
using HistoryPeerTL = MTPDstarsTransactionPeer;
+ using namespace Data;
+ const auto owner = &peer->owner();
const auto photo = tl.data().vphoto()
- ? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation())
+ ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
: nullptr;
+ auto extended = std::vector();
+ if (const auto list = tl.data().vextended_media()) {
+ extended.reserve(list->v.size());
+ for (const auto &media : list->v) {
+ media.match([&](const MTPDmessageMediaPhoto &photo) {
+ if (const auto inner = photo.vphoto()) {
+ const auto photo = owner->processPhoto(*inner);
+ if (!photo->isNull()) {
+ extended.push_back(CreditsHistoryMedia{
+ .type = CreditsHistoryMediaType::Photo,
+ .id = photo->id,
+ });
+ }
+ }
+ }, [&](const MTPDmessageMediaDocument &document) {
+ if (const auto inner = document.vdocument()) {
+ const auto document = owner->processDocument(*inner);
+ if (document->isAnimation()
+ || document->isVideoFile()
+ || document->isGifv()) {
+ extended.push_back(CreditsHistoryMedia{
+ .type = CreditsHistoryMediaType::Video,
+ .id = document->id,
+ });
+ }
+ }
+ }, [&](const auto &) {});
+ }
+ }
+ const auto barePeerId = tl.data().vpeer().match([](
+ const HistoryPeerTL &p) {
+ return peerFromMTP(p.vpeer());
+ }, [](const auto &) {
+ return PeerId(0);
+ }).value;
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,
+ .extended = std::move(extended),
.credits = tl.data().vstars().v,
- .bareId = tl.data().vpeer().match([](const HistoryPeerTL &p) {
- return peerFromMTP(p.vpeer());
- }, [](const auto &) {
- return PeerId(0);
- }).value,
+ .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
+ .barePeerId = barePeerId,
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
@@ -51,8 +91,17 @@ namespace {
return Data::CreditsHistoryEntry::PeerType::Unsupported;
}, [](const MTPDstarsTransactionPeerPremiumBot &) {
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
+ }, [](const MTPDstarsTransactionPeerAds &) {
+ return Data::CreditsHistoryEntry::PeerType::Ads;
}),
.refunded = tl.data().is_refund(),
+ .pending = tl.data().is_pending(),
+ .failed = tl.data().is_failed(),
+ .successDate = tl.data().vtransaction_date()
+ ? base::unixtime::parse(tl.data().vtransaction_date()->v)
+ : QDateTime(),
+ .successLink = qs(tl.data().vtransaction_url().value_or_empty()),
+ .in = (int64(tl.data().vstars().v) >= 0),
};
}
@@ -152,7 +201,8 @@ void CreditsHistory::request(
_requestId = _api.request(MTPpayments_GetStarsTransactions(
MTP_flags(_flags),
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
- MTP_string(token)
+ MTP_string(token),
+ MTP_int(kTransactionsLimit)
)).done([=](const MTPpayments_StarsStatus &result) {
_requestId = 0;
done(StatusFromTL(result, _peer));
@@ -199,4 +249,58 @@ rpl::producer> PremiumPeerBot(
};
}
+CreditsEarnStatistics::CreditsEarnStatistics(not_null peer)
+: StatisticsRequestSender(peer)
+, _isUser(peer->isUser()) {
+}
+
+rpl::producer CreditsEarnStatistics::request() {
+ return [=](auto consumer) {
+ auto lifetime = rpl::lifetime();
+
+ const auto finish = [=](const QString &url) {
+ makeRequest(MTPpayments_GetStarsRevenueStats(
+ MTP_flags(0),
+ (_isUser ? user()->input : channel()->input)
+ )).done([=](const MTPpayments_StarsRevenueStats &result) {
+ const auto &data = result.data();
+ const auto &status = data.vstatus().data();
+ _data = Data::CreditsEarnStatistics{
+ .revenueGraph = StatisticalGraphFromTL(
+ data.vrevenue_graph()),
+ .currentBalance = status.vcurrent_balance().v,
+ .availableBalance = status.vavailable_balance().v,
+ .overallRevenue = status.voverall_revenue().v,
+ .usdRate = data.vusd_rate().v,
+ .isWithdrawalEnabled = status.is_withdrawal_enabled(),
+ .nextWithdrawalAt = status.vnext_withdrawal_at()
+ ? base::unixtime::parse(
+ status.vnext_withdrawal_at()->v)
+ : QDateTime(),
+ .buyAdsUrl = url,
+ };
+
+ consumer.put_done();
+ }).fail([=](const MTP::Error &error) {
+ consumer.put_error_copy(error.type());
+ }).send();
+ };
+
+ makeRequest(
+ MTPpayments_GetStarsRevenueAdsAccountUrl(
+ (_isUser ? user()->input : channel()->input))
+ ).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) {
+ finish(qs(result.data().vurl()));
+ }).fail([=](const MTP::Error &error) {
+ finish({});
+ }).send();
+
+ return lifetime;
+ };
+}
+
+Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
+ return _data;
+}
+
} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h
index 265e7b387..e63380734 100644
--- a/Telegram/SourceFiles/api/api_credits.h
+++ b/Telegram/SourceFiles/api/api_credits.h
@@ -7,13 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
+#include "api/api_statistics_sender.h"
#include "data/data_credits.h"
+#include "data/data_credits_earn.h"
#include "mtproto/sender.h"
namespace Main {
class Session;
} // namespace Main
+class UserData;
+
namespace Api {
class CreditsTopupOptions final {
@@ -68,6 +72,21 @@ private:
};
+class CreditsEarnStatistics final : public StatisticsRequestSender {
+public:
+ explicit CreditsEarnStatistics(not_null);
+
+ [[nodiscard]] rpl::producer request();
+ [[nodiscard]] Data::CreditsEarnStatistics data() const;
+
+private:
+ Data::CreditsEarnStatistics _data;
+ bool _isUser = false;
+
+ mtpRequestId _requestId = 0;
+
+};
+
[[nodiscard]] rpl::producer> PremiumPeerBot(
not_null session);
diff --git a/Telegram/SourceFiles/api/api_earn.cpp b/Telegram/SourceFiles/api/api_earn.cpp
index d6425ef69..f05e5f618 100644
--- a/Telegram/SourceFiles/api/api_earn.cpp
+++ b/Telegram/SourceFiles/api/api_earn.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_cloud_password.h"
#include "apiwrap.h"
+#include "ui/layers/generic_box.h"
#include "boxes/passcode_box.h"
#include "data/data_channel.h"
#include "data/data_session.h"
@@ -34,22 +35,33 @@ void RestrictSponsored(
}
void HandleWithdrawalButton(
- not_null channel,
+ RewardReceiver receiver,
not_null button,
std::shared_ptr show) {
+ Expects(receiver.currencyReceiver
+ || (receiver.creditsReceiver && receiver.creditsAmount));
struct State {
rpl::lifetime lifetime;
bool loading = false;
};
+ const auto channel = receiver.currencyReceiver;
+ const auto peer = receiver.creditsReceiver;
+
const auto state = button->lifetime().make_state();
- const auto session = &channel->session();
+ const auto session = (channel ? &channel->session() : &peer->session());
+
+ using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl;
+ using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl;
session->api().cloudPassword().reload();
- button->setClickedCallback([=] {
+ const auto processOut = [=] {
if (state->loading) {
return;
}
+ if (peer && !receiver.creditsAmount()) {
+ return;
+ }
state->loading = true;
state->lifetime = session->api().cloudPassword().state(
) | rpl::take(
@@ -58,10 +70,12 @@ void HandleWithdrawalButton(
state->loading = false;
auto fields = PasscodeBox::CloudFields::From(pass);
- fields.customTitle
- = tr::lng_channel_earn_balance_password_title();
- fields.customDescription
- = tr::lng_channel_earn_balance_password_description(tr::now);
+ fields.customTitle = channel
+ ? tr::lng_channel_earn_balance_password_title()
+ : tr::lng_bot_earn_balance_password_title();
+ fields.customDescription = channel
+ ? tr::lng_channel_earn_balance_password_description(tr::now)
+ : tr::lng_bot_earn_balance_password_description(tr::now);
fields.customSubmitButton = tr::lng_passcode_submit();
fields.customCheckCallback = crl::guard(button, [=](
const Core::CloudPasswordResult &result,
@@ -74,22 +88,63 @@ void HandleWithdrawalButton(
}
}
};
- const auto fail = [=](const QString &error) {
- show->showToast(error);
+ const auto fail = [=](const MTP::Error &error) {
+ show->showToast(error.type());
};
- session->api().request(
- MTPstats_GetBroadcastRevenueWithdrawalUrl(
- channel->inputChannel,
- result.result
- )).done([=](const MTPstats_BroadcastRevenueWithdrawalUrl &r) {
- done(qs(r.data().vurl()));
- }).fail([=](const MTP::Error &error) {
- fail(error.type());
- }).send();
+ if (channel) {
+ session->api().request(
+ MTPstats_GetBroadcastRevenueWithdrawalUrl(
+ channel->inputChannel,
+ result.result
+ )).done([=](const ChannelOutUrl &r) {
+ done(qs(r.data().vurl()));
+ }).fail(fail).send();
+ } else if (peer) {
+ session->api().request(
+ MTPpayments_GetStarsRevenueWithdrawalUrl(
+ peer->input,
+ MTP_long(receiver.creditsAmount()),
+ result.result
+ )).done([=](const CreditsOutUrl &r) {
+ done(qs(r.data().vurl()));
+ }).fail(fail).send();
+ }
});
show->show(Box(session, fields));
});
-
+ };
+ button->setClickedCallback([=] {
+ if (state->loading) {
+ return;
+ }
+ const auto fail = [=](const MTP::Error &error) {
+ auto box = PrePasswordErrorBox(
+ error.type(),
+ session,
+ TextWithEntities{
+ tr::lng_channel_earn_out_check_password_about(tr::now),
+ });
+ if (box) {
+ show->show(std::move(box));
+ state->loading = false;
+ } else {
+ processOut();
+ }
+ };
+ if (channel) {
+ session->api().request(
+ MTPstats_GetBroadcastRevenueWithdrawalUrl(
+ channel->inputChannel,
+ MTP_inputCheckPasswordEmpty()
+ )).fail(fail).send();
+ } else if (peer) {
+ session->api().request(
+ MTPpayments_GetStarsRevenueWithdrawalUrl(
+ peer->input,
+ MTP_long(std::numeric_limits::max()),
+ MTP_inputCheckPasswordEmpty()
+ )).fail(fail).send();
+ }
});
}
diff --git a/Telegram/SourceFiles/api/api_earn.h b/Telegram/SourceFiles/api/api_earn.h
index 93f2bf6eb..cbee5d25a 100644
--- a/Telegram/SourceFiles/api/api_earn.h
+++ b/Telegram/SourceFiles/api/api_earn.h
@@ -21,8 +21,14 @@ void RestrictSponsored(
bool restricted,
Fn failed);
+struct RewardReceiver final {
+ ChannelData *currencyReceiver = nullptr;
+ PeerData *creditsReceiver = nullptr;
+ Fn creditsAmount;
+};
+
void HandleWithdrawalButton(
- not_null channel,
+ RewardReceiver receiver,
not_null button,
std::shared_ptr show);
diff --git a/Telegram/SourceFiles/api/api_filter_updates.h b/Telegram/SourceFiles/api/api_filter_updates.h
new file mode 100644
index 000000000..0126d3736
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_filter_updates.h
@@ -0,0 +1,27 @@
+/*
+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
+
+namespace Api {
+
+template
+void PerformForUpdate(
+ const MTPUpdates &updates,
+ Fn callback) {
+ updates.match([&](const MTPDupdates &updates) {
+ for (const auto &update : updates.vupdates().v) {
+ update.match([&](const Type &d) {
+ callback(d);
+ }, [](const auto &) {
+ });
+ }
+ }, [](const auto &) {
+ });
+}
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp
index ce1966623..869d0bf8a 100644
--- a/Telegram/SourceFiles/api/api_statistics.cpp
+++ b/Telegram/SourceFiles/api/api_statistics.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_statistics.h"
+#include "api/api_statistics_data_deserialize.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/data_channel.h"
@@ -15,33 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_story.h"
#include "history/history.h"
#include "main/main_session.h"
-#include "statistics/statistics_data_deserialize.h"
namespace Api {
namespace {
-constexpr auto kCheckRequestsTimer = 10 * crl::time(1000);
-
-[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
- const MTPStatsGraph &tl) {
- return tl.match([&](const MTPDstatsGraph &d) {
- using namespace Statistic;
- const auto zoomToken = d.vzoom_token().has_value()
- ? qs(*d.vzoom_token()).toUtf8()
- : QByteArray();
- return Data::StatisticalGraph{
- StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()),
- zoomToken,
- };
- }, [&](const MTPDstatsGraphAsync &data) {
- return Data::StatisticalGraph{
- .zoomToken = qs(data.vtoken()).toUtf8(),
- };
- }, [&](const MTPDstatsGraphError &data) {
- return Data::StatisticalGraph{ .error = qs(data.verror()) };
- });
-}
-
[[nodiscard]] Data::StatisticalValue StatisticalValueFromTL(
const MTPStatsAbsValueAndPrev &tl) {
const auto current = tl.data().vcurrent().v;
@@ -223,61 +201,6 @@ Statistics::Statistics(not_null channel)
: StatisticsRequestSender(channel) {
}
-StatisticsRequestSender::StatisticsRequestSender(not_null channel)
-: _channel(channel)
-, _api(&_channel->session().api().instance())
-, _timer([=] { checkRequests(); }) {
-}
-
-StatisticsRequestSender::~StatisticsRequestSender() {
- for (const auto &[dcId, ids] : _requests) {
- for (const auto id : ids) {
- _channel->session().api().unregisterStatsRequest(dcId, id);
- }
- }
-}
-
-void StatisticsRequestSender::checkRequests() {
- for (auto i = begin(_requests); i != end(_requests);) {
- for (auto j = begin(i->second); j != end(i->second);) {
- if (_api.pending(*j)) {
- ++j;
- } else {
- _channel->session().api().unregisterStatsRequest(
- i->first,
- *j);
- j = i->second.erase(j);
- }
- }
- if (i->second.empty()) {
- i = _requests.erase(i);
- } else {
- ++i;
- }
- }
- if (_requests.empty()) {
- _timer.cancel();
- }
-}
-
-template
-auto StatisticsRequestSender::makeRequest(Request &&request) {
- const auto id = _api.allocateRequestId();
- const auto dcId = _channel->owner().statsDcId(_channel);
- if (dcId) {
- _channel->session().api().registerStatsRequest(dcId, id);
- _requests[dcId].emplace(id);
- if (!_timer.isActive()) {
- _timer.callEach(kCheckRequestsTimer);
- }
- }
- return std::move(_api.request(
- std::forward(request)
- ).toDC(
- dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0
- ).overrideId(id));
-}
-
rpl::producer Statistics::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
@@ -747,11 +670,11 @@ Data::BoostStatus Boosts::boostStatus() const {
return _boostStatus;
}
-EarnStatistics::EarnStatistics(not_null channel)
+ChannelEarnStatistics::ChannelEarnStatistics(not_null channel)
: StatisticsRequestSender(channel) {
}
-rpl::producer EarnStatistics::request() {
+rpl::producer ChannelEarnStatistics::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
@@ -795,7 +718,7 @@ rpl::producer EarnStatistics::request() {
};
}
-void EarnStatistics::requestHistory(
+void ChannelEarnStatistics::requestHistory(
const Data::EarnHistorySlice::OffsetToken &token,
Fn done) {
if (_requestId) {
@@ -865,7 +788,7 @@ void EarnStatistics::requestHistory(
}).send();
}
-Data::EarnStatistics EarnStatistics::data() const {
+Data::EarnStatistics ChannelEarnStatistics::data() const {
return _data;
}
diff --git a/Telegram/SourceFiles/api/api_statistics.h b/Telegram/SourceFiles/api/api_statistics.h
index f18cba71d..213ab9293 100644
--- a/Telegram/SourceFiles/api/api_statistics.h
+++ b/Telegram/SourceFiles/api/api_statistics.h
@@ -7,45 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
-#include "base/timer.h"
+#include "api/api_statistics_sender.h"
#include "data/data_boosts.h"
#include "data/data_channel_earn.h"
#include "data/data_statistics.h"
-#include "mtproto/sender.h"
class ChannelData;
class PeerData;
namespace Api {
-class StatisticsRequestSender {
-protected:
- explicit StatisticsRequestSender(not_null channel);
- ~StatisticsRequestSender();
-
- template <
- typename Request,
- typename = std::enable_if_t>,
- typename = typename Request::Unboxed>
- [[nodiscard]] auto makeRequest(Request &&request);
-
- [[nodiscard]] MTP::Sender &api() {
- return _api;
- }
- [[nodiscard]] not_null channel() {
- return _channel;
- }
-
-private:
- void checkRequests();
-
- const not_null _channel;
- MTP::Sender _api;
- base::Timer _timer;
- base::flat_map> _requests;
-
-};
-
class Statistics final : public StatisticsRequestSender {
public:
explicit Statistics(not_null channel);
@@ -108,9 +79,9 @@ private:
};
-class EarnStatistics final : public StatisticsRequestSender {
+class ChannelEarnStatistics final : public StatisticsRequestSender {
public:
- explicit EarnStatistics(not_null channel);
+ explicit ChannelEarnStatistics(not_null channel);
[[nodiscard]] rpl::producer request();
void requestHistory(
diff --git a/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp b/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp
new file mode 100644
index 000000000..9ce75af78
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp
@@ -0,0 +1,35 @@
+/*
+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_statistics_data_deserialize.h"
+
+#include "data/data_statistics_chart.h"
+#include "statistics/statistics_data_deserialize.h"
+
+namespace Api {
+
+Data::StatisticalGraph StatisticalGraphFromTL(const MTPStatsGraph &tl) {
+ return tl.match([&](const MTPDstatsGraph &d) {
+ using namespace Statistic;
+ const auto zoomToken = d.vzoom_token().has_value()
+ ? qs(*d.vzoom_token()).toUtf8()
+ : QByteArray();
+ return Data::StatisticalGraph{
+ StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()),
+ zoomToken,
+ };
+ }, [&](const MTPDstatsGraphAsync &data) {
+ return Data::StatisticalGraph{
+ .zoomToken = qs(data.vtoken()).toUtf8(),
+ };
+ }, [&](const MTPDstatsGraphError &data) {
+ return Data::StatisticalGraph{ .error = qs(data.verror()) };
+ });
+}
+
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_statistics_data_deserialize.h b/Telegram/SourceFiles/api/api_statistics_data_deserialize.h
new file mode 100644
index 000000000..385b99d17
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_statistics_data_deserialize.h
@@ -0,0 +1,19 @@
+/*
+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
+
+namespace Data {
+struct StatisticalGraph;
+} // namespace Data
+
+namespace Api {
+
+[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
+ const MTPStatsGraph &tl);
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_statistics_sender.cpp b/Telegram/SourceFiles/api/api_statistics_sender.cpp
new file mode 100644
index 000000000..21d068e3d
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_statistics_sender.cpp
@@ -0,0 +1,86 @@
+/*
+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_statistics_sender.h"
+
+#include "apiwrap.h"
+#include "data/data_peer.h"
+#include "data/data_session.h"
+#include "main/main_session.h"
+
+namespace Api {
+
+StatisticsRequestSender::StatisticsRequestSender(
+ not_null peer)
+: _peer(peer)
+, _channel(peer->asChannel())
+, _user(peer->asUser())
+, _api(&_peer->session().api().instance())
+, _timer([=] { checkRequests(); }) {
+}
+
+MTP::Sender &StatisticsRequestSender::api() {
+ return _api;
+}
+
+not_null StatisticsRequestSender::channel() {
+ Expects(_channel);
+ return _channel;
+}
+
+not_null StatisticsRequestSender::user() {
+ Expects(_user);
+ return _user;
+}
+
+void StatisticsRequestSender::checkRequests() {
+ for (auto i = begin(_requests); i != end(_requests);) {
+ for (auto j = begin(i->second); j != end(i->second);) {
+ if (_api.pending(*j)) {
+ ++j;
+ } else {
+ _peer->session().api().unregisterStatsRequest(
+ i->first,
+ *j);
+ j = i->second.erase(j);
+ }
+ }
+ if (i->second.empty()) {
+ i = _requests.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ if (_requests.empty()) {
+ _timer.cancel();
+ }
+}
+
+auto StatisticsRequestSender::ensureRequestIsRegistered()
+-> StatisticsRequestSender::Registered {
+ const auto id = _api.allocateRequestId();
+ const auto dcId = _peer->owner().statsDcId(_peer);
+ if (dcId) {
+ _peer->session().api().registerStatsRequest(dcId, id);
+ _requests[dcId].emplace(id);
+ if (!_timer.isActive()) {
+ constexpr auto kCheckRequestsTimer = 10 * crl::time(1000);
+ _timer.callEach(kCheckRequestsTimer);
+ }
+ }
+ return StatisticsRequestSender::Registered{ id, dcId };
+}
+
+StatisticsRequestSender::~StatisticsRequestSender() {
+ for (const auto &[dcId, ids] : _requests) {
+ for (const auto id : ids) {
+ _peer->session().api().unregisterStatsRequest(dcId, id);
+ }
+ }
+}
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_statistics_sender.h b/Telegram/SourceFiles/api/api_statistics_sender.h
new file mode 100644
index 000000000..2ad0b664d
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_statistics_sender.h
@@ -0,0 +1,58 @@
+/*
+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 "base/timer.h"
+#include "mtproto/sender.h"
+
+class ChannelData;
+class PeerData;
+class UserData;
+
+namespace Api {
+
+class StatisticsRequestSender {
+protected:
+ explicit StatisticsRequestSender(not_null peer);
+ ~StatisticsRequestSender();
+
+ template <
+ typename Request,
+ typename = std::enable_if_t>,
+ typename = typename Request::Unboxed>
+ [[nodiscard]] auto makeRequest(Request &&request) {
+ const auto [id, dcId] = ensureRequestIsRegistered();
+ return std::move(_api.request(
+ std::forward(request)
+ ).toDC(
+ dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0
+ ).overrideId(id));
+ }
+
+ [[nodiscard]] MTP::Sender &api();
+ [[nodiscard]] not_null channel();
+ [[nodiscard]] not_null user();
+
+private:
+ struct Registered final {
+ mtpRequestId id;
+ MTP::DcId dcId;
+ };
+ [[nodiscard]] Registered ensureRequestIsRegistered();
+ void checkRequests();
+
+ const not_null _peer;
+ ChannelData * const _channel;
+ UserData * const _user;
+ MTP::Sender _api;
+ base::Timer _timer;
+ base::flat_map> _requests;
+
+};
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp
index 4a9339dfe..e57ac7b24 100644
--- a/Telegram/SourceFiles/api/api_updates.cpp
+++ b/Telegram/SourceFiles/api/api_updates.cpp
@@ -1704,7 +1704,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
const auto peerId = peerFromMTP(d.vpeer());
const auto msgId = d.vmsg_id().v;
if (const auto item = session().data().message(peerId, msgId)) {
- item->applyEdition(d.vextended_media());
+ item->applyEdition(d.vextended_media().v);
}
} break;
@@ -2129,6 +2129,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
};
if (IsForceLogoutNotification(d)) {
Core::App().forceLogOut(&session().account(), text);
+ } else if (IsWithdrawalNotification(d)) {
+ return;
} else if (d.is_popup()) {
const auto &windows = session().windows();
if (!windows.empty()) {
@@ -2630,4 +2632,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
}
+bool IsWithdrawalNotification(const MTPDupdateServiceNotification &data) {
+ return qs(data.vtype()).startsWith(u"API_WITHDRAWAL_FEATURE_DISABLED_"_q);
+}
+
} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_updates.h b/Telegram/SourceFiles/api/api_updates.h
index f21baa964..c028ae75a 100644
--- a/Telegram/SourceFiles/api/api_updates.h
+++ b/Telegram/SourceFiles/api/api_updates.h
@@ -211,4 +211,7 @@ private:
};
+[[nodiscard]] bool IsWithdrawalNotification(
+ const MTPDupdateServiceNotification &);
+
} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_views.cpp b/Telegram/SourceFiles/api/api_views.cpp
index 6c8ec8df7..e80f56e21 100644
--- a/Telegram/SourceFiles/api/api_views.cpp
+++ b/Telegram/SourceFiles/api/api_views.cpp
@@ -55,7 +55,9 @@ void ViewsManager::removeIncremented(not_null peer) {
_incremented.remove(peer);
}
-void ViewsManager::pollExtendedMedia(not_null item) {
+void ViewsManager::pollExtendedMedia(
+ not_null item,
+ bool force) {
if (!item->isRegular()) {
return;
}
@@ -63,14 +65,20 @@ void ViewsManager::pollExtendedMedia(not_null item) {
const auto peer = item->history()->peer;
auto &request = _pollRequests[peer];
if (request.ids.contains(id) || request.sent.contains(id)) {
- return;
+ if (!force || request.forced) {
+ return;
+ }
}
request.ids.emplace(id);
- if (!request.id && !request.when) {
- request.when = crl::now() + kPollExtendedMediaPeriod;
+ if (force) {
+ request.forced = true;
}
- if (!_pollTimer.isActive()) {
- _pollTimer.callOnce(kPollExtendedMediaPeriod);
+ const auto delay = force ? 1 : kPollExtendedMediaPeriod;
+ if (!request.id && (!request.when || force)) {
+ request.when = crl::now() + delay;
+ }
+ if (!_pollTimer.isActive() || force) {
+ _pollTimer.callOnce(delay);
}
}
@@ -160,9 +168,12 @@ void ViewsManager::sendPollRequests(
if (i->second.ids.empty()) {
i = _pollRequests.erase(i);
} else {
- i->second.when = now + kPollExtendedMediaPeriod;
- if (!_pollTimer.isActive()) {
- _pollTimer.callOnce(kPollExtendedMediaPeriod);
+ const auto delay = i->second.forced
+ ? 1
+ : kPollExtendedMediaPeriod;
+ i->second.when = now + delay;
+ if (!_pollTimer.isActive() || i->second.forced) {
+ _pollTimer.callOnce(delay);
}
++i;
}
diff --git a/Telegram/SourceFiles/api/api_views.h b/Telegram/SourceFiles/api/api_views.h
index 759d3c6a8..f8d19d15b 100644
--- a/Telegram/SourceFiles/api/api_views.h
+++ b/Telegram/SourceFiles/api/api_views.h
@@ -26,7 +26,7 @@ public:
void scheduleIncrement(not_null item);
void removeIncremented(not_null peer);
- void pollExtendedMedia(not_null item);
+ void pollExtendedMedia(not_null item, bool force = false);
private:
struct PollExtendedMediaRequest {
@@ -34,6 +34,7 @@ private:
mtpRequestId id = 0;
base::flat_set ids;
base::flat_set sent;
+ bool forced = false;
};
void viewsIncrement();
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index dc7aa1b06..8a2fc0de7 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -2204,7 +2204,8 @@ void ApiWrap::saveDraftsToCloud() {
entities,
Data::WebPageForMTP(
cloudDraft->webpage,
- textWithTags.text.isEmpty())
+ textWithTags.text.isEmpty()),
+ MTP_long(0) // effect
)).done([=](const MTPBool &result, const MTP::Response &response) {
const auto requestId = response.requestId;
history->finishSavingCloudDraft(
@@ -4259,7 +4260,11 @@ void ApiWrap::sendMediaWithRandomId(
MTP_flags(flags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
- media,
+ (options.price
+ ? MTPInputMedia(MTP_inputMediaPaidMedia(
+ MTP_long(options.price),
+ MTP_vector(1, media)))
+ : media),
MTP_string(caption.text),
MTP_long(randomId),
MTPReplyMarkup(),
@@ -4281,6 +4286,82 @@ void ApiWrap::sendMediaWithRandomId(
});
}
+void ApiWrap::sendMultiPaidMedia(
+ not_null item,
+ not_null album,
+ Fn done) {
+ Expects(album->options.price > 0);
+
+ const auto groupId = album->groupId;
+ const auto &options = album->options;
+ const auto randomId = album->items.front().randomId;
+ auto medias = album->items | ranges::view::transform([](
+ const SendingAlbum::Item &part) {
+ Assert(part.media.has_value());
+ return MTPInputMedia(part.media->data().vmedia());
+ }) | ranges::to>();
+
+ const auto history = item->history();
+ const auto replyTo = item->replyTo();
+
+ auto caption = item->originalText();
+ TextUtilities::Trim(caption);
+ auto sentEntities = Api::EntitiesToMTP(
+ _session,
+ caption.entities,
+ Api::ConvertOption::SkipLocal);
+
+ using Flag = MTPmessages_SendMedia::Flag;
+ const auto flags = Flag(0)
+ | (replyTo ? Flag::f_reply_to : Flag(0))
+ | (ShouldSendSilent(history->peer, options)
+ ? Flag::f_silent
+ : Flag(0))
+ | (!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.effectId ? Flag::f_effect : Flag(0))
+ | (options.invertCaption ? Flag::f_invert_media : Flag(0));
+
+ auto &histories = history->owner().histories();
+ const auto peer = history->peer;
+ const auto itemId = item->fullId();
+ histories.sendPreparedMessage(
+ history,
+ replyTo,
+ randomId,
+ Data::Histories::PrepareMessage(
+ MTP_flags(flags),
+ peer->input,
+ Data::Histories::ReplyToPlaceholder(),
+ MTP_inputMediaPaidMedia(
+ MTP_long(options.price),
+ MTP_vector(std::move(medias))),
+ MTP_string(caption.text),
+ MTP_long(randomId),
+ MTPReplyMarkup(),
+ sentEntities,
+ MTP_int(options.scheduled),
+ (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
+ Data::ShortcutIdToMTP(_session, options.shortcutId),
+ MTP_long(options.effectId)
+ ), [=](const MTPUpdates &result, const MTP::Response &response) {
+ if (const auto album = _sendingAlbums.take(groupId)) {
+ const auto copy = (*album)->items;
+ for (const auto &part : copy) {
+ if (const auto item = history->owner().message(part.msgId)) {
+ item->destroy();
+ }
+ }
+ }
+ if (done) done(true);
+ }, [=](const MTP::Error &error, const MTP::Response &response) {
+ if (done) done(false);
+ sendMessageFail(error, peer, randomId, itemId);
+ });
+}
+
void ApiWrap::sendAlbumWithUploaded(
not_null item,
const MessageGroupId &groupId,
@@ -4334,8 +4415,11 @@ void ApiWrap::sendAlbumIfReady(not_null album) {
if (!sample) {
_sendingAlbums.remove(groupId);
return;
+ } else if (album->options.price > 0) {
+ sendMultiPaidMedia(sample, album);
+ return;
} else if (medias.size() < 2) {
- const auto &single = medias.front().c_inputSingleMedia();
+ const auto &single = medias.front().data();
sendMediaWithRandomId(
sample,
single.vmedia(),
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 9710ee1bd..15c0941c3 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -545,6 +545,10 @@ private:
Api::SendOptions options,
uint64 randomId,
Fn done = nullptr);
+ void sendMultiPaidMedia(
+ not_null item,
+ not_null album,
+ Fn done = nullptr);
void getTopPromotionDelayed(TimeId now, TimeId next);
void topPromotionDone(const MTPhelp_PromoData &proxy);
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 363a64079..096adc10d 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -237,7 +237,7 @@ shareColumnSkip: 6px;
shareActivateDuration: 150;
shareScrollDuration: 300;
shareComment: InputField(defaultInputField) {
- font: normalFont;
+ style: defaultTextStyle;
textMargins: margins(8px, 8px, 8px, 6px);
heightMin: 36px;
heightMax: 72px;
@@ -290,6 +290,29 @@ passcodeTextLine: 28px;
passcodeLittleSkip: 5px;
passcodeAboutSkip: 7px;
passcodeSkip: 23px;
+passcodeSystemUnlock: IconButton(defaultIconButton) {
+ width: 32px;
+ height: 36px;
+ iconPosition: point(4px, 4px);
+ rippleAreaSize: 32px;
+ rippleAreaPosition: point(0px, 0px);
+ ripple: RippleAnimation(defaultRippleAnimation) {
+ color: lightButtonBgOver;
+ }
+}
+passcodeSystemWinHello: IconButton(passcodeSystemUnlock) {
+ icon: icon{{ "menu/passcode_winhello", lightButtonFg }};
+ iconOver: icon{{ "menu/passcode_winhello", lightButtonFg }};
+}
+passcodeSystemTouchID: IconButton(passcodeSystemUnlock) {
+ icon: icon{{ "menu/passcode_finger", lightButtonFg }};
+ iconOver: icon{{ "menu/passcode_finger", lightButtonFg }};
+}
+passcodeSystemUnlockLater: FlatLabel(defaultFlatLabel) {
+ align: align(top);
+ textFg: windowSubTextFg;
+}
+passcodeSystemUnlockSkip: 12px;
newGroupAboutFg: windowSubTextFg;
newGroupPadding: margins(4px, 6px, 4px, 3px);
@@ -585,7 +608,7 @@ groupStickersRemovePosition: point(6px, 6px);
groupStickersFieldPadding: margins(8px, 6px, 8px, 6px);
groupStickersField: InputField(defaultMultiSelectSearchField) {
placeholderFont: boxTextFont;
- font: boxTextFont;
+ style: boxTextStyle;
placeholderMargins: margins(0px, 0px, 0px, 0px);
textMargins: margins(0px, 7px, 0px, 0px);
textBg: boxBg;
@@ -672,7 +695,6 @@ themesMenuToggle: IconButton(defaultIconButton) {
themesMenuPosition: point(-2px, 25px);
createPollField: InputField(defaultInputField) {
- font: boxTextFont;
textMargins: margins(0px, 4px, 0px, 4px);
textAlign: align(left);
heightMin: 36px;
@@ -877,7 +899,6 @@ scheduleDateField: InputField(defaultInputField) {
placeholderScale: 0.;
heightMin: 30px;
textAlign: align(top);
- font: font(14px);
}
scheduleTimeField: InputField(scheduleDateField) {
border: 0px;
@@ -905,7 +926,6 @@ muteBoxTimeField: InputField(scheduleDateField) {
placeholderScale: 0.;
heightMin: 30px;
textAlign: align(left);
- font: font(14px);
}
muteBoxTimeFieldPadding: margins(5px, 0px, 5px, 0px);
diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp
index bc61e9fe2..5af8577bf 100644
--- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp
+++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp
@@ -81,7 +81,7 @@ void ChangeFilterById(
MTP_int(filter.id()),
filter.tl()
)).done([=, chat = history->peer->name(), name = filter.title()] {
- const auto account = &history->session().account();
+ const auto account = not_null(&history->session().account());
if (const auto controller = Core::App().windowFor(account)) {
controller->showToast((add
? tr::lng_filters_toast_add
diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp
index 46138e3fd..9ff85e099 100644
--- a/Telegram/SourceFiles/boxes/create_poll_box.cpp
+++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp
@@ -1044,7 +1044,16 @@ not_null CreatePollBox::setupSolution(
solution->setInstantReplaces(Ui::InstantReplaces::Default());
solution->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
- solution->setMarkdownReplacesEnabled(true);
+ solution->setMarkdownReplacesEnabled(rpl::single(
+ Ui::MarkdownEnabledState{ Ui::MarkdownEnabled{ {
+ Ui::InputField::kTagBold,
+ Ui::InputField::kTagItalic,
+ Ui::InputField::kTagUnderline,
+ Ui::InputField::kTagStrikeOut,
+ Ui::InputField::kTagCode,
+ Ui::InputField::kTagSpoiler,
+ } } }
+ ));
solution->setEditLinkCallback(
DefaultEditLinkCallback(_controller->uiShow(), solution));
solution->customTab(true);
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
index d46121c2f..bd089a9b5 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
@@ -463,6 +463,7 @@ void EditCaptionBox::rebuildPreview() {
st::defaultComposeControls,
gifPaused,
file,
+ [] { return true; },
Ui::AttachControls::Type::EditOnly);
_isPhoto = (media && media->isPhoto());
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index ef87c6423..972a7a5f4 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -1011,14 +1011,16 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
}) | ranges::views::filter([](UserData *u) -> bool {
return u;
}) | ranges::to>>();
- if (!users.empty()) {
- const auto giftBox = show->show(
- Box(GiftsBox, _controller, users, api, ref));
- giftBox->boxClosing(
- ) | rpl::start_with_next([=] {
- _manyGiftsLifetime.destroy();
- }, giftBox->lifetime());
+ if (users.empty()) {
+ show->showToast(
+ tr::lng_settings_gift_premium_choose(tr::now));
}
+ const auto giftBox = show->show(
+ Box(GiftsBox, _controller, users, api, ref));
+ giftBox->boxClosing(
+ ) | rpl::start_with_next([=] {
+ _manyGiftsLifetime.destroy();
+ }, giftBox->lifetime());
(*ignoreClose) = true;
peersBox->closeBox();
};
@@ -1644,12 +1646,60 @@ void AddCreditsHistoryEntryTable(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
- if (entry.bareId) {
+ const auto peerId = PeerId(entry.barePeerId);
+ if (peerId) {
+ auto text = tr::lng_credits_box_history_entry_peer();
+ AddTableRow(table, std::move(text), controller, peerId);
+ }
+ if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
+ const auto session = &controller->session();
+ const auto peer = session->data().peer(peerId);
+ if (const auto channel = peer->asBroadcast()) {
+ const auto username = channel->username();
+ const auto base = username.isEmpty()
+ ? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
+ : username;
+ const auto query = base + '/' + QString::number(msgId.bare);
+ const auto link = session->createInternalLink(query);
+ auto label = object_ptr(
+ table,
+ rpl::single(Ui::Text::Link(link)),
+ st::giveawayGiftCodeValue);
+ label->setClickHandlerFilter([=](const auto &...) {
+ controller->showPeerHistory(channel, {}, msgId);
+ return false;
+ });
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_media(),
+ std::move(label),
+ st::giveawayGiftCodeValueMargin);
+ }
+ }
+ using Type = Data::CreditsHistoryEntry::PeerType;
+ if (entry.peerType == Type::AppStore) {
AddTableRow(
table,
- tr::lng_credits_box_history_entry_peer(),
- controller,
- PeerId(entry.bareId));
+ tr::lng_credits_box_history_entry_via(),
+ tr::lng_credits_box_history_entry_app_store(
+ Ui::Text::RichLangValue));
+ } else if (entry.peerType == Type::PlayMarket) {
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_via(),
+ tr::lng_credits_box_history_entry_play_market(
+ Ui::Text::RichLangValue));
+ } else if (entry.peerType == Type::Fragment) {
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_via(),
+ tr::lng_credits_box_history_entry_fragment(
+ Ui::Text::RichLangValue));
+ } else if (entry.peerType == Type::Ads) {
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_via(),
+ tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue));
}
if (!entry.id.isEmpty()) {
constexpr auto kOneLineCount = 18;
@@ -1680,4 +1730,17 @@ void AddCreditsHistoryEntryTable(
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
}
+ if (!entry.successDate.isNull()) {
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_success_date(),
+ rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
+ }
+ if (!entry.successLink.isEmpty()) {
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_success_url(),
+ rpl::single(
+ Ui::Text::Link(entry.successLink, entry.successLink)));
+ }
}
diff --git a/Telegram/SourceFiles/boxes/max_invite_box.cpp b/Telegram/SourceFiles/boxes/max_invite_box.cpp
index b6230b673..a7c1f8408 100644
--- a/Telegram/SourceFiles/boxes/max_invite_box.cpp
+++ b/Telegram/SourceFiles/boxes/max_invite_box.cpp
@@ -133,8 +133,8 @@ void MaxInviteBox::paintEvent(QPaintEvent *e) {
auto option = QTextOption(style::al_left);
option.setWrapMode(QTextOption::WrapAnywhere);
p.setFont(_linkOver
- ? st::defaultInputField.font->underline()
- : st::defaultInputField.font);
+ ? st::defaultInputField.style.font->underline()
+ : st::defaultInputField.style.font);
p.setPen(st::defaultLinkButton.color);
const auto inviteLinkText = _channel->inviteLink().isEmpty()
? tr::lng_group_invite_create(tr::now)
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index 99427aca0..5623bea94 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_info_box.h"
#include "apiwrap.h"
+#include "api/api_credits.h"
#include "api/api_peer_photo.h"
#include "api/api_user_names.h"
#include "main/main_session.h"
@@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_premium_limits.h"
#include "data/data_user.h"
#include "history/admin_log/history_admin_log_section.h"
+#include "info/bot/earn/info_earn_widget.h"
#include "info/channel_statistics/boosts/info_boosts_widget.h"
#include "info/profile/info_profile_values.h"
#include "info/info_memento.h"
@@ -52,6 +54,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/boost_box.h"
#include "ui/controls/emoji_button.h"
#include "ui/controls/userpic_button.h"
+#include "ui/effects/premium_graphics.h"
+#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "ui/vertical_list.h"
#include "ui/toast/toast.h"
@@ -71,6 +75,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
#include "styles/style_info.h"
+#include
+
namespace {
constexpr auto kBotManagerUsername = "BotFather"_cs;
@@ -343,6 +349,7 @@ private:
void fillPendingRequestsButton();
void fillBotUsernamesButton();
+ void fillBotBalanceButton();
void fillBotEditIntroButton();
void fillBotEditCommandsButton();
void fillBotEditSettingsButton();
@@ -1126,6 +1133,7 @@ void Controller::fillManageSection() {
::AddSkip(container, 0);
fillBotUsernamesButton();
+ fillBotBalanceButton();
fillBotEditIntroButton();
fillBotEditCommandsButton();
fillBotEditSettingsButton();
@@ -1536,6 +1544,84 @@ void Controller::fillBotUsernamesButton() {
{ &st::menuIconLinks });
}
+void Controller::fillBotBalanceButton() {
+ Expects(_isBot);
+
+ struct State final {
+ rpl::variable balance;
+ };
+
+ auto &lifetime = _controls.buttonsLayout->lifetime();
+ const auto state = lifetime.make_state();
+
+ const auto wrap = _controls.buttonsLayout->add(
+ object_ptr>(
+ _controls.buttonsLayout,
+ EditPeerInfoBox::CreateButton(
+ _controls.buttonsLayout,
+ tr::lng_manage_peer_bot_balance(),
+ state->balance.value(),
+ [controller = _navigation->parentController(), peer = _peer] {
+ controller->showSection(Info::BotEarn::Make(peer));
+ },
+ st::manageGroupButton,
+ {})));
+ wrap->toggle(false, anim::type::instant);
+
+ const auto button = wrap->entity();
+ {
+ const auto api = button->lifetime().make_state(
+ _peer);
+ api->request({}, [=](Data::CreditsStatusSlice data) {
+ if (data.balance) {
+ wrap->toggle(true, anim::type::normal);
+ }
+ state->balance = QString::number(data.balance);
+ });
+ }
+ {
+ constexpr auto kSizeShift = 3;
+ constexpr auto kStrokeWidth = 5;
+
+ const auto icon = Ui::CreateChild(button);
+ icon->resize(Size(st::menuIconLinks.width() - kSizeShift));
+
+ auto colorized = [&] {
+ auto f = QFile(Ui::Premium::Svg());
+ if (!f.open(QIODevice::ReadOnly)) {
+ return QString();
+ }
+ return QString::fromUtf8(
+ f.readAll()).replace(u"#fff"_q, u"#ffffff00"_q);
+ }();
+ colorized.replace(
+ u"stroke=\"none\""_q,
+ u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name()));
+ colorized.replace(
+ u"stroke-width=\"1\""_q,
+ u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
+ const auto svg = icon->lifetime().make_state(
+ colorized.toUtf8());
+ svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
+
+ const auto starSize = Size(icon->height());
+
+ icon->paintRequest(
+ ) | rpl::start_with_next([=] {
+ auto p = QPainter(icon);
+ svg->render(&p, Rect(starSize));
+ }, icon->lifetime());
+
+ button->sizeValue(
+ ) | rpl::start_with_next([=](const QSize &size) {
+ icon->moveToLeft(
+ button->st().iconLeft + kSizeShift / 2.,
+ (size.height() - icon->height()) / 2);
+ }, icon->lifetime());
+ }
+
+}
+
void Controller::fillBotEditIntroButton() {
Expects(_isBot);
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp
index 0873426e5..8905c33c1 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/event_filter.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
+#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_document.h"
@@ -351,8 +352,8 @@ object_ptr AddReactionsSelector(
const auto customEmojiPaused = [controller = args.controller] {
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
};
- raw->setCustomEmojiFactory([=](QStringView data, Fn update)
- -> std::unique_ptr {
+ auto factory = [=](QStringView data, Fn update)
+ -> std::unique_ptr {
const auto id = Data::ParseCustomEmojiData(data);
auto result = owner->customEmojiManager().create(
data,
@@ -364,7 +365,13 @@ object_ptr AddReactionsSelector(
}
using namespace Ui::Text;
return std::make_unique(std::move(result));
- }, std::move(customEmojiPaused));
+ };
+ raw->setCustomTextContext([=](Fn repaint) {
+ return std::any(Core::MarkedTextContext{
+ .session = session,
+ .customEmojiRepaint = std::move(repaint),
+ });
+ }, customEmojiPaused, customEmojiPaused, std::move(factory));
const auto callback = args.callback;
const auto isCustom = [=](DocumentId id) {
diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp
index 76a2a18b7..f163be612 100644
--- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp
+++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp
@@ -131,6 +131,8 @@ void PreloadSticker(const std::shared_ptr &media) {
return tr::lng_premium_summary_subtitle_translation();
case PremiumFeature::Business:
return tr::lng_premium_summary_subtitle_business();
+ case PremiumFeature::Effects:
+ return tr::lng_premium_summary_subtitle_effects();
case PremiumFeature::BusinessLocation:
return tr::lng_business_subtitle_location();
@@ -192,6 +194,8 @@ void PreloadSticker(const std::shared_ptr &media) {
return tr::lng_premium_summary_about_translation();
case PremiumFeature::Business:
return tr::lng_premium_summary_about_business();
+ case PremiumFeature::Effects:
+ return tr::lng_premium_summary_about_effects();
case PremiumFeature::BusinessLocation:
return tr::lng_business_about_location();
@@ -529,6 +533,7 @@ struct VideoPreviewDocument {
case PremiumFeature::Wallpapers: return "wallpapers";
case PremiumFeature::LastSeen: return "last_seen";
case PremiumFeature::MessagePrivacy: return "message_privacy";
+ case PremiumFeature::Effects: return "effects";
case PremiumFeature::BusinessLocation: return "business_location";
case PremiumFeature::BusinessHours: return "business_hours";
diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h
index 63b1bd1be..4dae5c30d 100644
--- a/Telegram/SourceFiles/boxes/premium_preview_box.h
+++ b/Telegram/SourceFiles/boxes/premium_preview_box.h
@@ -70,6 +70,7 @@ enum class PremiumFeature {
LastSeen,
MessagePrivacy,
Business,
+ Effects,
// Business features.
BusinessLocation,
diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp
index 8820c0c65..39d7983d7 100644
--- a/Telegram/SourceFiles/boxes/send_credits_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/data_credits.h"
+#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
@@ -39,6 +40,147 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_settings.h"
namespace Ui {
+namespace {
+
+struct PaidMediaData {
+ const Data::Invoice *invoice = nullptr;
+ HistoryItem *item = nullptr;
+ PeerData *peer = nullptr;
+ int photos = 0;
+ int videos = 0;
+
+ explicit operator bool() const {
+ return invoice && item && peer && (photos || videos);
+ }
+};
+
+[[nodiscard]] PaidMediaData LookupPaidMediaData(
+ not_null session,
+ not_null form) {
+ using namespace Payments;
+ const auto message = std::get_if(&form->id.value);
+ const auto item = message
+ ? session->data().message(message->peer, message->itemId)
+ : nullptr;
+ const auto media = item ? item->media() : nullptr;
+ const auto invoice = media ? media->invoice() : nullptr;
+ if (!invoice || !invoice->isPaidMedia) {
+ return {};
+ }
+
+ auto photos = 0;
+ auto videos = 0;
+ for (const auto &media : invoice->extendedMedia) {
+ const auto photo = media->photo();
+ if (photo && !photo->extendedMediaVideoDuration().has_value()) {
+ ++photos;
+ } else {
+ ++videos;
+ }
+ }
+
+ const auto sender = item->originalSender();
+ const auto broadcast = (sender && sender->isBroadcast())
+ ? sender
+ : message->peer.get();
+ return {
+ .invoice = invoice,
+ .item = item,
+ .peer = broadcast,
+ .photos = photos,
+ .videos = videos,
+ };
+}
+
+[[nodiscard]] rpl::producer SendCreditsConfirmText(
+ not_null session,
+ not_null form) {
+ if (const auto data = LookupPaidMediaData(session, form)) {
+ auto photos = 0;
+ auto videos = 0;
+ for (const auto &media : data.invoice->extendedMedia) {
+ const auto photo = media->photo();
+ if (photo && !photo->extendedMediaVideoDuration().has_value()) {
+ ++photos;
+ } else {
+ ++videos;
+ }
+ }
+
+ auto photosBold = tr::lng_credits_box_out_photos(
+ lt_count,
+ rpl::single(photos) | tr::to_count(),
+ Ui::Text::Bold);
+ auto videosBold = tr::lng_credits_box_out_videos(
+ lt_count,
+ rpl::single(videos) | tr::to_count(),
+ Ui::Text::Bold);
+ auto media = (!videos)
+ ? ((photos > 1)
+ ? std::move(photosBold)
+ : tr::lng_credits_box_out_photo(Ui::Text::WithEntities))
+ : (!photos)
+ ? ((videos > 1)
+ ? std::move(videosBold)
+ : tr::lng_credits_box_out_video(Ui::Text::WithEntities))
+ : tr::lng_credits_box_out_both(
+ lt_photo,
+ std::move(photosBold),
+ lt_video,
+ std::move(videosBold),
+ Ui::Text::WithEntities);
+ return tr::lng_credits_box_out_media(
+ lt_count,
+ rpl::single(form->invoice.amount) | tr::to_count(),
+ lt_media,
+ std::move(media),
+ lt_chat,
+ rpl::single(Ui::Text::Bold(data.peer->name())),
+ Ui::Text::RichLangValue);
+ }
+
+ const auto bot = session->data().user(form->botId);
+ return 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);
+}
+
+[[nodiscard]] object_ptr SendCreditsThumbnail(
+ not_null parent,
+ not_null session,
+ not_null form,
+ int photoSize) {
+ if (const auto data = LookupPaidMediaData(session, form)) {
+ const auto first = data.invoice->extendedMedia[0]->photo();
+ const auto second = (data.photos > 1)
+ ? data.invoice->extendedMedia[1]->photo()
+ : nullptr;
+ const auto totalCount = int(data.invoice->extendedMedia.size());
+ if (first && first->extendedMediaPreview()) {
+ return Settings::PaidMediaThumbnail(
+ parent,
+ first,
+ second,
+ totalCount,
+ photoSize);
+ }
+ }
+ if (form->photo) {
+ return Settings::HistoryEntryPhoto(parent, form->photo, photoSize);
+ }
+ const auto bot = session->data().user(form->botId);
+ return object_ptr(
+ parent,
+ bot,
+ st::defaultUserpicButton);
+}
+
+} // namespace
void SendCreditsBox(
not_null box,
@@ -89,22 +231,10 @@ void SendCreditsBox(
}, 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);
- }
+ const auto thumb = box->addRow(object_ptr>(
+ content,
+ SendCreditsThumbnail(content, session, form.get(), photoSize)));
+ thumb->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(content);
box->addRow(object_ptr>(
@@ -118,14 +248,7 @@ void SendCreditsBox(
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),
+ SendCreditsConfirmText(session, form.get()),
st::creditsBoxAbout)));
Ui::AddSkip(content);
Ui::AddSkip(content);
@@ -158,26 +281,16 @@ void SendCreditsBox(
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),
+ rpl::single(CreditsEmojiSmall(session)),
Ui::Text::RichLangValue);
const auto buttonLabel = Ui::CreateChild(
button,
rpl::single(QString()),
- st::defaultFlatLabel);
+ st::creditsBoxButtonLabel);
std::move(
buttonText
) | rpl::start_with_next([=](const TextWithEntities &text) {
@@ -247,4 +360,22 @@ void SendCreditsBox(
}
}
+TextWithEntities CreditsEmoji(not_null session) {
+ return Ui::Text::SingleCustomEmoji(
+ session->data().customEmojiManager().registerInternalEmoji(
+ st::settingsPremiumIconStar,
+ QMargins{ 0, -st::moderateBoxExpandInnerSkip, 0, 0 },
+ true),
+ QString(QChar(0x2B50)));
+}
+
+TextWithEntities CreditsEmojiSmall(not_null session) {
+ return Ui::Text::SingleCustomEmoji(
+ session->data().customEmojiManager().registerInternalEmoji(
+ st::starIconSmall,
+ st::starIconSmallPadding,
+ true),
+ QString(QChar(0x2B50)));
+}
+
} // namespace Ui
diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h
index 25ceb1d56..c0107a360 100644
--- a/Telegram/SourceFiles/boxes/send_credits_box.h
+++ b/Telegram/SourceFiles/boxes/send_credits_box.h
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem;
+namespace Main {
+class Session;
+} // namespace Main
+
namespace Payments {
struct CreditsFormData;
} // namespace Payments
@@ -22,4 +26,10 @@ void SendCreditsBox(
std::shared_ptr data,
Fn sent);
+[[nodiscard]] TextWithEntities CreditsEmoji(
+ not_null session);
+
+[[nodiscard]] TextWithEntities CreditsEmojiSmall(
+ not_null session);
+
} // namespace Ui
diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp
index 75345a44c..8150e20ee 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_files_box.cpp
@@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "storage/storage_media_prepare.h"
+#include "iv/iv_instance.h"
#include "mainwidget.h"
+#include "main/main_app_config.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "mtproto/mtproto_config.h"
@@ -24,11 +26,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/controls/history_view_characters_limit.h"
#include "history/view/history_view_schedule_box.h"
#include "core/mime_type.h"
+#include "core/ui_integration.h"
#include "base/event_filter.h"
#include "base/call_delayed.h"
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
+#include "boxes/send_credits_box.h"
#include "ui/effects/scroll_content_shadow.h"
+#include "ui/widgets/fields/number_input.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/popup_menu.h"
@@ -36,10 +41,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/attach/attach_single_file_preview.h"
#include "ui/chat/attach/attach_single_media_preview.h"
#include "ui/grouped_layout.h"
+#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/controls/emoji_button.h"
#include "ui/painter.h"
+#include "ui/vertical_list.h"
#include "lottie/lottie_single_player.h"
+#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_user.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue.
@@ -111,6 +119,84 @@ rpl::producer FieldPlaceholder(
: tr::lng_photos_comment();
}
+void EditPriceBox(
+ not_null box,
+ not_null session,
+ uint64 price,
+ Fn apply) {
+ box->setTitle(tr::lng_paid_title());
+ AddSubsectionTitle(
+ box->verticalLayout(),
+ tr::lng_paid_enter_cost(),
+ (st::boxRowPadding - QMargins(
+ st::defaultSubsectionTitlePadding.left(),
+ 0,
+ st::defaultSubsectionTitlePadding.right(),
+ 0)));
+ const auto limit = session->appConfig().get(
+ u"stars_paid_post_amount_max"_q,
+ 10'000);
+ const auto wrap = box->addRow(object_ptr(
+ box,
+ st::editTagField.heightMin));
+ auto owned = object_ptr(
+ wrap,
+ st::editTagField,
+ tr::lng_paid_cost_placeholder(),
+ price ? QString::number(price) : QString(),
+ limit);
+ const auto field = owned.data();
+ wrap->widthValue() | rpl::start_with_next([=](int width) {
+ field->move(0, 0);
+ field->resize(width, field->height());
+ wrap->resize(width, field->height());
+ }, wrap->lifetime());
+ field->selectAll();
+ box->setFocusCallback([=] {
+ field->setFocusFast();
+ });
+ const auto about = box->addRow(
+ object_ptr(
+ box,
+ tr::lng_paid_about(
+ lt_link,
+ tr::lng_paid_about_link() | Ui::Text::ToLink(),
+ Ui::Text::WithEntities),
+ st::paidAmountAbout),
+ st::boxRowPadding + QMargins(0, st::sendMediaRowSkip, 0, 0));
+ about->setClickHandlerFilter([=](const auto &...) {
+ Core::App().iv().openWithIvPreferred(
+ session,
+ tr::lng_paid_about_link_url(tr::now));
+ return false;
+ });
+
+ field->paintRequest() | rpl::start_with_next([=](QRect clip) {
+ auto p = QPainter(field);
+ st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
+ }, field->lifetime());
+
+ const auto save = [=] {
+ const auto now = field->getLastText().toULongLong();
+ if (now > limit) {
+ field->showError();
+ return;
+ }
+ const auto weak = Ui::MakeWeak(box);
+ apply(now);
+ if (const auto strong = weak.data()) {
+ strong->closeBox();
+ }
+ };
+
+ QObject::connect(field, &Ui::NumberInput::submitted, box, save);
+
+ box->addButton(tr::lng_settings_save(), save);
+ box->addButton(tr::lng_cancel(), [=] {
+ box->closeBox();
+ });
+}
+
} // namespace
SendFilesLimits DefaultLimitsForPeer(not_null peer) {
@@ -161,7 +247,8 @@ SendFilesBox::Block::Block(
int from,
int till,
Fn gifPaused,
- SendFilesWay way)
+ SendFilesWay way,
+ Fn canToggleSpoiler)
: _items(items)
, _from(from)
, _till(till) {
@@ -178,14 +265,16 @@ SendFilesBox::Block::Block(
parent.get(),
st,
my,
- way);
+ way,
+ std::move(canToggleSpoiler));
_preview.reset(preview);
} else {
const auto media = Ui::SingleMediaPreview::Create(
parent,
st,
gifPaused,
- first);
+ first,
+ std::move(canToggleSpoiler));
if (media) {
_isSingleMedia = true;
_preview.reset(media);
@@ -261,6 +350,14 @@ rpl::producer SendFilesBox::Block::itemModifyRequest() const {
}
}
+rpl::producer<> SendFilesBox::Block::orderUpdated() const {
+ if (_isAlbum) {
+ const auto album = static_cast(_preview.get());
+ return album->orderUpdated();
+ }
+ return rpl::never<>();
+}
+
void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) {
if (!_isAlbum) {
if (_isSingleMedia) {
@@ -329,6 +426,18 @@ void SendFilesBox::Block::applyChanges() {
}
}
+QImage SendFilesBox::Block::generatePriceTagBackground() const {
+ const auto preview = _preview.get();
+ if (_isAlbum) {
+ const auto album = static_cast(preview);
+ return album->generatePriceTagBackground();
+ } else if (_isSingleMedia) {
+ const auto media = static_cast(preview);
+ return media->generatePriceTagBackground();
+ }
+ return QImage();
+}
+
SendFilesBox::SendFilesBox(
QWidget*,
not_null controller,
@@ -393,6 +502,9 @@ Fn SendFilesBox::prepareSendMenuDetails(
: _invertCaption
? SendMenu::CaptionState::Above
: SendMenu::CaptionState::Below;
+ result.price = canChangePrice()
+ ? _price.current()
+ : std::optional();
return result;
});
}
@@ -406,6 +518,7 @@ auto SendFilesBox::prepareSendMenuCallback()
case Type::CaptionUp: _invertCaption = true; break;
case Type::SpoilerOn: toggleSpoilers(true); break;
case Type::SpoilerOff: toggleSpoilers(false); break;
+ case Type::ChangePrice: changePrice(); break;
default:
SendMenu::DefaultCallback(
_show,
@@ -596,14 +709,27 @@ void SendFilesBox::refreshButtons() {
addMenuButton();
}
-bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const {
+bool SendFilesBox::hasSendMenu(const MenuDetails &details) const {
return (details.type != SendMenu::Type::Disabled)
|| (details.spoiler != SendMenu::SpoilerState::None)
|| (details.caption != SendMenu::CaptionState::None);
}
bool SendFilesBox::hasSpoilerMenu() const {
- return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
+ return !hasPrice()
+ && _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
+}
+
+bool SendFilesBox::canChangePrice() const {
+ const auto way = _sendWay.current();
+ const auto broadcast = _captionToPeer
+ ? _captionToPeer->asBroadcast()
+ : nullptr;
+ return broadcast
+ && broadcast->canPostPaidMedia()
+ && _list.canChangePrice(
+ way.groupFiles() && way.sendImagesAsPhotos(),
+ way.sendImagesAsPhotos());
}
void SendFilesBox::applyBlockChanges() {
@@ -626,6 +752,118 @@ void SendFilesBox::toggleSpoilers(bool enabled) {
}
}
+void SendFilesBox::changePrice() {
+ const auto weak = Ui::MakeWeak(this);
+ const auto session = &_show->session();
+ const auto now = _price.current();
+ _show->show(Box(EditPriceBox, session, now, [=](uint64 price) {
+ if (weak && price != now) {
+ _price = price;
+ refreshPriceTag();
+ }
+ }));
+}
+
+bool SendFilesBox::hasPrice() const {
+ return canChangePrice() && _price.current() > 0;
+}
+
+void SendFilesBox::refreshPriceTag() {
+ const auto resetSpoilers = hasPrice() || _priceTag;
+ if (resetSpoilers) {
+ for (auto &file : _list.files) {
+ file.spoiler = false;
+ }
+ for (auto &block : _blocks) {
+ block.toggleSpoilers(hasPrice());
+ }
+ }
+ if (!hasPrice()) {
+ _priceTag = nullptr;
+ _priceTagBg = QImage();
+ } else if (!_priceTag) {
+ _priceTag = std::make_unique(_inner.data());
+ const auto raw = _priceTag.get();
+
+ raw->show();
+ raw->paintRequest() | rpl::start_with_next([=] {
+ if (_priceTagBg.isNull()) {
+ _priceTagBg = preparePriceTagBg(raw->size());
+ }
+ QPainter(raw).drawImage(0, 0, _priceTagBg);
+ }, raw->lifetime());
+
+ const auto session = &_show->session();
+ auto price = _price.value() | rpl::map([=](uint64 amount) {
+ auto result = Ui::Text::Colorized(Ui::CreditsEmoji(session));
+ result.append(Lang::FormatCountDecimal(amount));
+ return result;
+ });
+ auto text = tr::lng_paid_price(
+ lt_price,
+ std::move(price),
+ Ui::Text::WithEntities);
+ const auto label = Ui::CreateChild(
+ raw,
+ QString(),
+ st::paidTagLabel);
+ std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
+ label->setMarkedText(text, Core::MarkedTextContext{
+ .session = session,
+ .customEmojiRepaint = [=] { label->update(); },
+ });
+ }, label->lifetime());
+ label->show();
+ label->sizeValue() | rpl::start_with_next([=](QSize size) {
+ const auto inner = QRect(QPoint(), size);
+ const auto rect = inner.marginsAdded(st::paidTagPadding);
+ raw->resize(rect.size());
+ label->move(-rect.topLeft());
+ }, label->lifetime());
+ _inner->sizeValue() | rpl::start_with_next([=](QSize size) {
+ raw->move(
+ (size.width() - raw->width()) / 2,
+ (size.height() - raw->height()) / 2);
+ }, raw->lifetime());
+ } else {
+ _priceTag->raise();
+ _priceTag->update();
+ _priceTagBg = QImage();
+ }
+}
+
+QImage SendFilesBox::preparePriceTagBg(QSize size) const {
+ const auto ratio = style::DevicePixelRatio();
+ const auto outer = _blocks.empty()
+ ? size
+ : _inner->widgetAt(0)->geometry().size();
+ auto bg = _blocks.empty()
+ ? QImage()
+ : _blocks.front().generatePriceTagBackground();
+ if (bg.isNull()) {
+ bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
+ bg.fill(Qt::black);
+ }
+
+ auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
+ result.setDevicePixelRatio(ratio);
+ result.fill(Qt::black);
+ auto p = QPainter(&result);
+ auto hq = PainterHighQualityEnabler(p);
+ p.drawImage(
+ QRect(
+ (size.width() - outer.width()) / 2,
+ (size.height() - outer.height()) / 2,
+ outer.width(),
+ outer.height()),
+ bg);
+ p.fillRect(QRect(QPoint(), size), st::msgDateImgBg);
+ p.end();
+
+ const auto radius = std::min(size.width(), size.height()) / 2;
+ return Images::Round(std::move(result), Images::CornersMask(radius));
+}
+
void SendFilesBox::addMenuButton() {
const auto details = _sendMenuDetails();
if (!hasSendMenu(details)) {
@@ -719,6 +957,7 @@ void SendFilesBox::initSendWay() {
block.setSendWay(value);
}
refreshButtons();
+ refreshPriceTag();
if (was != hidden()) {
updateBoxSize();
updateControlsGeometry();
@@ -804,7 +1043,8 @@ void SendFilesBox::pushBlock(int from, int till) {
from,
till,
gifPaused,
- _sendWay.current());
+ _sendWay.current(),
+ [=] { return !hasPrice(); });
auto &block = _blocks.back();
const auto widget = _inner->add(
block.takeWidget(),
@@ -927,10 +1167,18 @@ void SendFilesBox::pushBlock(int from, int till) {
st::sendMediaPreviewSize,
[=] { refreshAllAfterChanges(from); });
}, widget->lifetime());
+
+ block.orderUpdated() | rpl::start_with_next([=]{
+ if (_priceTag) {
+ _priceTagBg = QImage();
+ _priceTag->update();
+ }
+ }, widget->lifetime());
}
void SendFilesBox::refreshControls(bool initial) {
refreshButtons();
+ refreshPriceTag();
refreshTitleText();
updateSendWayControls();
updateCaptionPlaceholder();
@@ -1499,6 +1747,7 @@ void SendFilesBox::send(
auto child = _sendMenuDetails();
child.spoiler = SendMenu::SpoilerState::None;
child.caption = SendMenu::CaptionState::None;
+ child.price = std::nullopt;
return SendMenu::DefaultCallback(_show, sendCallback())(
{ .type = SendMenu::ActionType::Schedule },
child);
@@ -1526,10 +1775,16 @@ void SendFilesBox::send(
auto caption = (_caption && !_caption->isHidden())
? _caption->getTextWithAppliedMarkdown()
: TextWithTags();
- options.invertCaption = _invertCaption;
if (!validateLength(caption.text)) {
return;
}
+ options.invertCaption = _invertCaption;
+ options.price = hasPrice() ? _price.current() : 0;
+ if (options.price > 0) {
+ for (auto &file : _list.files) {
+ file.spoiler = false;
+ }
+ }
_confirmedCallback(
std::move(_list),
_sendWay.current(),
diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h
index 1e95a1aa8..8f3432ebd 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.h
+++ b/Telegram/SourceFiles/boxes/send_files_box.h
@@ -149,7 +149,8 @@ private:
int from,
int till,
Fn gifPaused,
- Ui::SendFilesWay way);
+ Ui::SendFilesWay way,
+ Fn canToggleSpoiler);
Block(Block &&other) = default;
Block &operator=(Block &&other) = default;
@@ -160,11 +161,14 @@ private:
[[nodiscard]] rpl::producer itemDeleteRequest() const;
[[nodiscard]] rpl::producer itemReplaceRequest() const;
[[nodiscard]] rpl::producer itemModifyRequest() const;
+ [[nodiscard]] rpl::producer<> orderUpdated() const;
void setSendWay(Ui::SendFilesWay way);
void toggleSpoilers(bool enabled);
void applyChanges();
+ [[nodiscard]] QImage generatePriceTagBackground() const;
+
private:
base::unique_qptr _preview;
not_null*> _items;
@@ -190,6 +194,12 @@ private:
void addMenuButton();
void applyBlockChanges();
void toggleSpoilers(bool enabled);
+ void changePrice();
+
+ [[nodiscard]] bool canChangePrice() const;
+ [[nodiscard]] bool hasPrice() const;
+ void refreshPriceTag();
+ [[nodiscard]] QImage preparePriceTagBg(QSize size) const;
bool validateLength(const QString &text) const;
void refreshButtons();
@@ -251,6 +261,9 @@ private:
SendFilesCheck _check;
SendFilesConfirmed _confirmedCallback;
Fn _cancelledCallback;
+ rpl::variable _price = 0;
+ std::unique_ptr _priceTag;
+ QImage _priceTagBg;
bool _confirmed = false;
bool _invertCaption = false;
diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style
index 11fe1e637..4e13da57f 100644
--- a/Telegram/SourceFiles/calls/calls.style
+++ b/Telegram/SourceFiles/calls/calls.style
@@ -1393,7 +1393,6 @@ groupCallScheduleDateField: InputField(groupCallField) {
placeholderScale: 0.;
heightMin: 30px;
textAlign: align(top);
- font: font(14px);
}
groupCallScheduleTimeField: InputField(groupCallScheduleDateField) {
textBg: groupCallMembersBg;
diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp
index eaf85271e..83c4fe88e 100644
--- a/Telegram/SourceFiles/calls/calls_call.cpp
+++ b/Telegram/SourceFiles/calls/calls_call.cpp
@@ -706,7 +706,8 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
}
}
if (false && data.is_need_rating() && _id && _accessHash) {
- const auto window = Core::App().windowFor(_user);
+ const auto window = Core::App().windowFor(
+ Window::SeparateId(_user));
const auto session = &_user->session();
const auto callId = _id;
const auto callAccessHash = _accessHash;
@@ -1402,7 +1403,8 @@ void Call::handleRequestError(const QString &error) {
_user->name())
: QString();
if (!inform.isEmpty()) {
- if (const auto window = Core::App().windowFor(_user)) {
+ if (const auto window = Core::App().windowFor(
+ Window::SeparateId(_user))) {
window->show(Ui::MakeInformBox(inform));
} else {
Ui::show(Ui::MakeInformBox(inform));
@@ -1420,7 +1422,8 @@ void Call::handleControllerError(const QString &error) {
? tr::lng_call_error_audio_io(tr::now)
: QString();
if (!inform.isEmpty()) {
- if (const auto window = Core::App().windowFor(_user)) {
+ if (const auto window = Core::App().windowFor(
+ Window::SeparateId(_user))) {
window->show(Ui::MakeInformBox(inform));
} else {
Ui::show(Ui::MakeInformBox(inform));
diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
index 9e967b50e..84c336131 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
@@ -383,20 +383,14 @@ void Panel::initWindow() {
&& _fullScreenOrMaximized.current()) {
toggleFullScreen();
}
+ } else if (e->type() == QEvent::WindowStateChange && _call->rtmp()) {
+ const auto state = window()->windowState();
+ _fullScreenOrMaximized = (state & Qt::WindowFullScreen)
+ || (state & Qt::WindowMaximized);
}
return base::EventFilterResult::Continue;
});
- if (_call->rtmp()) {
- QObject::connect(
- window()->windowHandle(),
- &QWindow::windowStateChanged,
- [=](Qt::WindowState state) {
- _fullScreenOrMaximized = (state == Qt::WindowFullScreen)
- || (state == Qt::WindowMaximized);
- });
- }
-
window()->setBodyTitleArea([=](QPoint widgetPoint) {
using Flag = Ui::WindowTitleHitTestFlag;
const auto titleRect = QRect(
diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
index 93c5ecf1e..cf1375268 100644
--- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
+++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
@@ -585,7 +585,6 @@ void ChooseSourceProcess::setupSourcesGeometry() {
void ChooseSourceProcess::setupGeometryWithParent(
not_null parent) {
- _window->createWinId();
const auto parentScreen = [&] {
if (const auto screen = QGuiApplication::screenAt(
parent->geometry().center())) {
@@ -595,7 +594,12 @@ void ChooseSourceProcess::setupGeometryWithParent(
}();
const auto myScreen = _window->screen();
if (parentScreen && myScreen != parentScreen) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ _window->setScreen(parentScreen);
+#else // Qt >= 6.0.0
+ _window->createWinId();
_window->windowHandle()->setScreen(parentScreen);
+#endif // Qt < 6.0.0
}
_window->setFixedSize(_fixedSize);
_window->move(
diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index 270bed675..84fd1fdc8 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -71,6 +71,7 @@ ComposeIcons {
menuSpoilerOff: icon;
menuBelow: icon;
menuAbove: icon;
+ menuPrice: icon;
stripBubble: icon;
stripExpandPanel: icon;
@@ -610,6 +611,7 @@ defaultComposeIcons: ComposeIcons {
menuSpoilerOff: menuIconSpoilerOff;
menuBelow: menuIconBelow;
menuAbove: menuIconAbove;
+ menuPrice: menuIconEarn;
stripBubble: icon{
{ "chat/reactions_bubble_shadow", windowShadowFg },
@@ -989,8 +991,33 @@ historyUnreadReactions: TwoIconButton(historyToDown) {
}
historyUnreadThingsSkip: 4px;
+historyQuoteStyle: QuoteStyle(defaultQuoteStyle) {
+ padding: margins(10px, 2px, 4px, 2px);
+ verticalSkip: 4px;
+ outline: 3px;
+ outlineShift: 2px;
+ radius: 5px;
+}
+historyTextStyle: TextStyle(defaultTextStyle) {
+ blockquote: QuoteStyle(historyQuoteStyle) {
+ padding: margins(10px, 2px, 20px, 2px);
+ icon: icon{{ "chat/mini_quote", windowFg }};
+ iconPosition: point(4px, 4px);
+ expand: icon{{ "intro_country_dropdown", windowFg }};
+ expandPosition: point(6px, 4px);
+ collapse: icon{{ "intro_country_dropdown-flip_vertical", windowFg }};
+ collapsePosition: point(6px, 4px);
+ }
+ pre: QuoteStyle(historyQuoteStyle) {
+ header: 20px;
+ headerPosition: point(10px, 2px);
+ scrollable: true;
+ icon: icon{{ "chat/mini_copy", windowFg }};
+ iconPosition: point(4px, 2px);
+ }
+}
historyComposeField: InputField(defaultInputField) {
- font: normalFont;
+ style: historyTextStyle;
textMargins: margins(0px, 0px, 0px, 0px);
textAlign: align(left);
textFg: historyComposeAreaFg;
@@ -1381,3 +1408,18 @@ editTagField: InputField(defaultInputField) {
editTagLimit: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
+
+paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }};
+paidStarIconTop: 7px;
+paidAmountAbout: FlatLabel(defaultFlatLabel) {
+ minWidth: 256px;
+ textFg: windowSubTextFg;
+}
+paidTagLabel: FlatLabel(defaultFlatLabel) {
+ textFg: radialFg;
+ palette: TextPalette(defaultTextPalette) {
+ linkFg: creditsBg1;
+ }
+ style: semiboldTextStyle;
+}
+paidTagPadding: margins(16px, 6px, 16px, 6px);
diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp b/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp
index 01bc33b8c..ba904b77c 100644
--- a/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp
+++ b/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp
@@ -30,15 +30,15 @@ ResolveWindow ResolveWindowDefault() {
return (Window::SessionController*)nullptr;
};
auto &app = Core::App();
+ const auto account = not_null(&session->account());
if (const auto a = check(app.activeWindow())) {
return a;
} else if (const auto b = check(app.activePrimaryWindow())) {
return b;
- } else if (const auto c = check(app.windowFor(&session->account()))) {
+ } else if (const auto c = check(app.windowFor(account))) {
return c;
- } else if (const auto d = check(
- app.ensureSeparateWindowForAccount(
- &session->account()))) {
+ } else if (const auto d = check(app.ensureSeparateWindowFor(
+ account))) {
return d;
}
return nullptr;
diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp
index 1eedbbcf2..0326743b0 100644
--- a/Telegram/SourceFiles/chat_helpers/message_field.cpp
+++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp
@@ -14,11 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qthelp_regex.h"
#include "base/qthelp_url.h"
#include "base/event_filter.h"
+#include "ui/chat/chat_style.h"
#include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "core/shortcuts.h"
#include "core/application.h"
#include "core/core_settings.h"
+#include "core/ui_integration.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/wrap/vertical_layout.h"
@@ -40,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
+#include "styles/style_settings.h"
#include "base/qt/qt_common_adapters.h"
#include
@@ -58,6 +61,7 @@ using EditLinkSelection = Ui::InputField::EditLinkSelection;
constexpr auto kParseLinksTimeout = crl::time(500);
constexpr auto kTypesDuration = 4 * crl::time(1000);
+constexpr auto kCodeLanguageLimit = 32;
// For mention / custom emoji tags save and validate selfId,
// ignore tags for different users.
@@ -222,6 +226,51 @@ void EditLinkBox(
}, text->lifetime());
}
+void EditCodeLanguageBox(
+ not_null box,
+ QString now,
+ Fn save) {
+ Expects(save != nullptr);
+
+ box->setTitle(tr::lng_formatting_code_title());
+ box->addRow(object_ptr(
+ box,
+ tr::lng_formatting_code_language(),
+ st::settingsAddReplyLabel));
+ const auto field = box->addRow(object_ptr(
+ box,
+ st::settingsAddReplyField,
+ tr::lng_formatting_code_auto(),
+ now.trimmed()));
+ box->setFocusCallback([=] {
+ field->setFocusFast();
+ });
+ field->selectAll();
+ field->setMaxLength(kCodeLanguageLimit);
+
+ Ui::AddLengthLimitLabel(field, kCodeLanguageLimit);
+
+ const auto callback = [=] {
+ const auto name = field->getLastText().trimmed();
+ const auto check = QRegularExpression("^[a-zA-Z0-9\\+\\-]*$");
+ if (check.match(name).hasMatch()) {
+ auto weak = Ui::MakeWeak(box);
+ save(name);
+ if (const auto strong = weak.data()) {
+ strong->closeBox();
+ }
+ } else {
+ field->showError();
+ }
+ };
+ field->submits(
+ ) | rpl::start_with_next(callback, field->lifetime());
+ box->addButton(tr::lng_settings_save(), callback);
+ box->addButton(tr::lng_cancel(), [=] {
+ box->closeBox();
+ });
+}
+
TextWithEntities StripSupportHashtag(TextWithEntities text) {
static const auto expression = QRegularExpression(
u"\\n?#tsf[a-z0-9_-]*[\\s#a-z0-9_-]*$"_q,
@@ -274,15 +323,24 @@ TextWithTags PrepareEditText(not_null item) {
bool EditTextChanged(
not_null item,
- const TextWithTags &updated) {
+ TextWithTags updated) {
const auto original = PrepareEditText(item);
+ auto originalWithEntities = TextWithEntities{
+ std::move(original.text),
+ TextUtilities::ConvertTextTagsToEntities(original.tags)
+ };
+ auto updatedWithEntities = TextWithEntities{
+ std::move(updated.text),
+ TextUtilities::ConvertTextTagsToEntities(updated.tags)
+ };
+ TextUtilities::PrepareForSending(originalWithEntities, 0);
+ TextUtilities::PrepareForSending(updatedWithEntities, 0);
+
// Tags can be different for the same entities, because for
// animated emoji each tag contains a different random number.
// So we compare entities instead of tags.
- return (original.text != updated.text)
- || (TextUtilities::ConvertTextTagsToEntities(original.tags)
- != TextUtilities::ConvertTextTagsToEntities(updated.tags));
+ return originalWithEntities != updatedWithEntities;
}
Fn save)> DefaultEditLanguageCallback(
+ std::shared_ptr show) {
+ return [=](QString now, Fn save) {
+ show->showBox(Box(EditCodeLanguageBox, now, save));
+ };
+}
+
void InitMessageFieldHandlers(
not_null session,
std::shared_ptr show,
@@ -329,12 +394,16 @@ void InitMessageFieldHandlers(
const style::InputField *fieldStyle) {
field->setTagMimeProcessor(
FieldTagMimeProcessor(session, allowPremiumEmoji));
- const auto paused = [customEmojiPaused] {
+ field->setCustomTextContext([=](Fn repaint) {
+ return std::any(Core::MarkedTextContext{
+ .session = session,
+ .customEmojiRepaint = std::move(repaint),
+ });
+ }, [customEmojiPaused] {
return On(PowerSaving::kEmojiChat) || customEmojiPaused();
- };
- field->setCustomEmojiFactory(
- session->data().customEmojiManager().factory(),
- std::move(customEmojiPaused));
+ }, [customEmojiPaused] {
+ return On(PowerSaving::kChatSpoiler) || customEmojiPaused();
+ });
field->setInstantReplaces(Ui::InstantReplaces::Default());
field->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
@@ -342,8 +411,18 @@ void InitMessageFieldHandlers(
if (show) {
field->setEditLinkCallback(
DefaultEditLinkCallback(show, field, fieldStyle));
+ field->setEditLanguageCallback(DefaultEditLanguageCallback(show));
InitSpellchecker(show, field, fieldStyle != nullptr);
}
+ const auto style = field->lifetime().make_state(
+ session->colorIndicesValue());
+ field->setPreCache([=] {
+ return style->messageStyle(false, false).preCache.get();
+ });
+ field->setBlockquoteCache([=] {
+ const auto colorIndex = session->user()->colorIndex();
+ return style->coloredQuoteCache(false, colorIndex).get();
+ });
}
[[nodiscard]] bool IsGoodFactcheckUrl(QStringView url) {
@@ -569,12 +648,26 @@ void InitMessageFieldFade(
Ui::DestroyChild(b.data());
}, topFade->lifetime());
- topFade->show();
- bottomFade->showOn(
- field->scrollTop().value(
- ) | rpl::map([field, descent = field->st().font->descent](int scroll) {
- return (scroll + descent) < field->scrollTopMax();
- }) | rpl::distinct_until_changed());
+ const auto descent = field->st().style.font->descent;
+ rpl::merge(
+ field->changes(),
+ field->scrollTop().changes() | rpl::to_empty,
+ field->sizeValue() | rpl::to_empty
+ ) | rpl::start_with_next([=] {
+ // InputField::changes fires before the auto-resize is being applied,
+ // so for the scroll values to be accurate we enqueue the check.
+ InvokeQueued(field, [=] {
+ const auto topHidden = !field->scrollTop().current();
+ if (topFade->isHidden() != topHidden) {
+ topFade->setVisible(!topHidden);
+ }
+ const auto adjusted = field->scrollTop().current() + descent;
+ const auto bottomHidden = (adjusted >= field->scrollTopMax());
+ if (bottomFade->isHidden() != bottomHidden) {
+ bottomFade->setVisible(!bottomHidden);
+ }
+ });
+ }, topFade->lifetime());
}
InlineBotQuery ParseInlineBotQuery(
@@ -766,11 +859,7 @@ bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) {
const auto text = static_cast(event)->text();
if (!text.isEmpty() && text.size() < 3) {
const auto ch = text[0];
- if (false
- || ch == '\n'
- || ch == '\r'
- || ch.isSpace()
- || ch == QChar::LineSeparator) {
+ if (IsSpace(ch)) {
_timer.callOnce(0);
}
}
@@ -1097,7 +1186,9 @@ base::unique_qptr PremiumRequiredSendRestriction(
const auto margins = (st.textMargins + st.placeholderMargins);
const auto available = width - margins.left() - margins.right();
label->resizeToWidth(available);
- label->moveToLeft(margins.left(), margins.top(), width);
+ const auto height = label->height() + link->height();
+ const auto top = (raw->height() - height) / 2;
+ label->moveToLeft(margins.left(), top, width);
link->move(
(width - link->width()) / 2,
label->y() + label->height());
@@ -1117,8 +1208,8 @@ void SelectTextInFieldWithMargins(
auto textCursor = field->textCursor();
// Try to set equal margins for top and bottom sides.
const auto charsCountInLine = field->width()
- / field->st().font->width('W');
- const auto linesCount = (field->height() / field->st().font->height);
+ / field->st().style.font->width('W');
+ const auto linesCount = field->height() / field->st().style.font->height;
const auto selectedLines = (selection.to - selection.from)
/ charsCountInLine;
constexpr auto kMinDiff = ushort(3);
diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h
index 1e67642a7..041b54d66 100644
--- a/Telegram/SourceFiles/chat_helpers/message_field.h
+++ b/Telegram/SourceFiles/chat_helpers/message_field.h
@@ -35,13 +35,14 @@ class Show;
namespace Ui {
class PopupMenu;
+class Show;
} // namespace Ui
[[nodiscard]] QString PrepareMentionTag(not_null user);
[[nodiscard]] TextWithTags PrepareEditText(not_null item);
[[nodiscard]] bool EditTextChanged(
not_null item,
- const TextWithTags &updated);
+ TextWithTags updated);
Fn show,
not_null field,
const style::InputField *fieldStyle = nullptr);
+Fn save)> DefaultEditLanguageCallback(
+ std::shared_ptr show);
void InitMessageFieldHandlers(
not_null session,
std::shared_ptr show, // may be null
diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index 0109d946e..372aa8d21 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "data/data_abstract_structure.h"
+#include "data/data_forum.h"
#include "data/data_photo.h"
#include "data/data_document.h"
#include "data/data_session.h"
@@ -91,6 +92,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_checkout_process.h"
#include "export/export_manager.h"
#include "webrtc/webrtc_environment.h"
+#include "window/window_separate_id.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "boxes/abstract_box.h"
@@ -118,7 +120,7 @@ constexpr auto kFileOpenTimeoutMs = crl::time(1000);
LaunchState GlobalLaunchState/* = LaunchState::Running*/;
void SetCrashAnnotationsGL() {
-#ifdef Q_OS_WIN
+#ifdef DESKTOP_APP_USE_ANGLE
CrashReports::SetAnnotation("OpenGL ANGLE", [] {
if (Core::App().settings().disableOpenGL()) {
return "Disabled";
@@ -131,11 +133,11 @@ void SetCrashAnnotationsGL() {
}
Unexpected("Ui::GL::CurrentANGLE value in SetupANGLE.");
}());
-#else // Q_OS_WIN
+#else // DESKTOP_APP_USE_ANGLE
CrashReports::SetAnnotation(
"OpenGL",
Core::App().settings().disableOpenGL() ? "Disabled" : "Enabled");
-#endif // Q_OS_WIN
+#endif // DESKTOP_APP_USE_ANGLE
}
} // namespace
@@ -209,8 +211,7 @@ Application::~Application() {
setLastActiveWindow(nullptr);
_windowInSettings = _lastActivePrimaryWindow = nullptr;
_closingAsyncWindows.clear();
- _secondaryWindows.clear();
- _primaryWindows.clear();
+ _windows.clear();
_mediaView = nullptr;
_notifications->clearAllFast();
@@ -315,8 +316,8 @@ void Application::run() {
// Create mime database, so it won't be slow later.
QMimeDatabase().mimeTypeForName(u"text/plain"_q);
- _primaryWindows.emplace(nullptr, std::make_unique());
- setLastActiveWindow(_primaryWindows.front().second.get());
+ _windows.emplace(nullptr, std::make_unique());
+ setLastActiveWindow(_windows.front().second.get());
_windowInSettings = _lastActivePrimaryWindow = _lastActiveWindow;
_domain->activeChanges(
@@ -405,7 +406,7 @@ void Application::run() {
}
void Application::showAccount(not_null account) {
- if (const auto separate = separateWindowForAccount(account)) {
+ if (const auto separate = separateWindowFor(account)) {
_lastActivePrimaryWindow = separate;
separate->activate();
} else if (const auto last = activePrimaryWindow()) {
@@ -413,13 +414,13 @@ void Application::showAccount(not_null account) {
}
}
-void Application::checkWindowAccount(not_null window) {
- const auto account = window->maybeAccount();
- for (auto &[key, existing] : _primaryWindows) {
- if (existing.get() == window && key != account) {
+void Application::checkWindowId(not_null window) {
+ const auto id = window->id();
+ for (auto &[existingId, existing] : _windows) {
+ if (existing.get() == window && existingId != id) {
auto found = std::move(existing);
- _primaryWindows.remove(key);
- _primaryWindows.emplace(account, std::move(found));
+ _windows.remove(existingId);
+ _windows.emplace(id, std::move(found));
break;
}
}
@@ -495,10 +496,7 @@ void Application::startSystemDarkModeViewer() {
void Application::enumerateWindows(Fn)> callback) const {
- for (const auto &window : ranges::views::values(_primaryWindows)) {
- callback(window.get());
- }
- for (const auto &window : ranges::views::values(_secondaryWindows)) {
+ for (const auto &window : ranges::views::values(_windows)) {
callback(window.get());
}
}
@@ -607,10 +605,7 @@ void Application::clearEmojiSourceImages() {
}
bool Application::isActiveForTrayMenu() const {
- return ranges::any_of(ranges::views::values(_primaryWindows), [=](
- const std::unique_ptr &controller) {
- return controller->widget()->isActiveForTrayMenu();
- }) || ranges::any_of(ranges::views::values(_secondaryWindows), [=](
+ return ranges::any_of(ranges::views::values(_windows), [=](
const std::unique_ptr &controller) {
return controller->widget()->isActiveForTrayMenu();
});
@@ -1287,44 +1282,36 @@ Window::Controller *Application::activePrimaryWindow() const {
return _lastActivePrimaryWindow;
}
-Window::Controller *Application::separateWindowForAccount(
- not_null account) const {
- for (const auto &[openedAccount, window] : _primaryWindows) {
- if (openedAccount == account.get()) {
+Window::Controller *Application::separateWindowFor(
+ Window::SeparateId id) const {
+ for (const auto &[existingId, window] : _windows) {
+ if (existingId == id) {
return window.get();
}
}
return nullptr;
}
-Window::Controller *Application::separateWindowForPeer(
- not_null peer) const {
- for (const auto &[history, window] : _secondaryWindows) {
- if (history->peer == peer) {
- return window.get();
- }
- }
- return nullptr;
-}
-
-Window::Controller *Application::ensureSeparateWindowForPeer(
- not_null peer,
+Window::Controller *Application::ensureSeparateWindowFor(
+ Window::SeparateId id,
MsgId showAtMsgId) {
const auto activate = [&](not_null window) {
window->activate();
return window;
};
-
- if (const auto existing = separateWindowForPeer(peer)) {
- existing->sessionController()->showPeerHistory(
- peer,
- Window::SectionShow::Way::ClearStack,
- showAtMsgId);
+ if (const auto existing = separateWindowFor(id)) {
+ if (id.thread && id.type == Window::SeparateType::Chat) {
+ existing->sessionController()->showThread(
+ id.thread,
+ showAtMsgId,
+ Window::SectionShow::Way::ClearStack);
+ }
return activate(existing);
}
- const auto result = _secondaryWindows.emplace(
- peer->owner().history(peer),
- std::make_unique(peer, showAtMsgId)
+
+ const auto result = _windows.emplace(
+ id,
+ std::make_unique(id, showAtMsgId)
).first->second.get();
processCreatedWindow(result);
result->firstShow();
@@ -1332,55 +1319,63 @@ Window::Controller *Application::ensureSeparateWindowForPeer(
return activate(result);
}
-Window::Controller *Application::ensureSeparateWindowForAccount(
- not_null account) {
- const auto activate = [&](not_null window) {
- window->activate();
- return window;
- };
-
- if (const auto existing = separateWindowForAccount(account)) {
- return activate(existing);
- }
- const auto result = _primaryWindows.emplace(
- account,
- std::make_unique(account)
- ).first->second.get();
- processCreatedWindow(result);
- result->firstShow();
- result->finishFirstShow();
- return activate(result);
-}
-
-Window::Controller *Application::windowFor(not_null peer) const {
- if (const auto separate = separateWindowForPeer(peer)) {
- return separate;
- }
- return windowFor(&peer->account());
-}
-
-Window::Controller *Application::windowFor(
- not_null account) const {
- if (const auto separate = separateWindowForAccount(account)) {
+Window::Controller *Application::windowFor(Window::SeparateId id) const {
+ if (const auto separate = separateWindowFor(id)) {
return separate;
+ } else if (id && id.primary()) {
+ return windowFor(not_null(id.account));
}
return activePrimaryWindow();
}
+Window::Controller *Application::windowForShowingHistory(
+ not_null peer) const {
+ if (const auto separate = separateWindowFor(peer)) {
+ return separate;
+ }
+ auto result = (Window::Controller*)nullptr;
+ enumerateWindows([&](not_null window) {
+ if (const auto controller = window->sessionController()) {
+ const auto current = controller->activeChatCurrent();
+ if (const auto history = current.history()) {
+ if (history->peer == peer) {
+ result = window;
+ }
+ }
+ }
+ });
+ return result;
+}
+
+Window::Controller *Application::windowForShowingForum(
+ not_null forum) const {
+ const auto id = Window::SeparateId(
+ Window::SeparateType::Forum,
+ forum->history());
+ if (const auto separate = separateWindowFor(id)) {
+ return separate;
+ }
+ auto result = (Window::Controller*)nullptr;
+ enumerateWindows([&](not_null window) {
+ if (const auto controller = window->sessionController()) {
+ const auto current = controller->shownForum().current();
+ if (forum == current) {
+ result = window;
+ }
+ }
+ });
+ return result;
+}
+
Window::Controller *Application::findWindow(
not_null widget) const {
const auto window = widget->window();
if (_lastActiveWindow && _lastActiveWindow->widget() == window) {
return _lastActiveWindow;
}
- for (const auto &[account, primary] : _primaryWindows) {
- if (primary->widget() == window) {
- return primary.get();
- }
- }
- for (const auto &[history, secondary] : _secondaryWindows) {
- if (secondary->widget() == window) {
- return secondary.get();
+ for (const auto &[id, controller] : _windows) {
+ if (controller->widget() == window) {
+ return controller.get();
}
}
return nullptr;
@@ -1392,10 +1387,11 @@ Window::Controller *Application::activeWindow() const {
bool Application::closeNonLastAsync(not_null window) {
const auto hasOther = [&] {
- for (const auto &[account, primary] : _primaryWindows) {
- if (!_closingAsyncWindows.contains(primary.get())
- && primary.get() != window
- && primary->maybeSession()) {
+ for (const auto &[id, controller] : _windows) {
+ if (id.primary()
+ && !_closingAsyncWindows.contains(controller.get())
+ && controller.get() != window
+ && controller->maybeSession()) {
return true;
}
}
@@ -1457,10 +1453,10 @@ void Application::closeWindow(not_null window) {
: nullptr;
const auto next = nextFromStack
? nextFromStack
- : (_primaryWindows.front().second.get() != window)
- ? _primaryWindows.front().second.get()
- : (_primaryWindows.back().second.get() != window)
- ? _primaryWindows.back().second.get()
+ : (_windows.front().second.get() != window)
+ ? _windows.front().second.get()
+ : (_windows.back().second.get() != window)
+ ? _windows.back().second.get()
: nullptr;
Assert(next != window);
@@ -1481,20 +1477,12 @@ void Application::closeWindow(not_null window) {
}
}
_closingAsyncWindows.remove(window);
- for (auto i = begin(_primaryWindows); i != end(_primaryWindows);) {
+ for (auto i = begin(_windows); i != end(_windows);) {
if (i->second.get() == window) {
Assert(_lastActiveWindow != window);
Assert(_lastActivePrimaryWindow != window);
Assert(_windowInSettings != window);
- i = _primaryWindows.erase(i);
- } else {
- ++i;
- }
- }
- for (auto i = begin(_secondaryWindows); i != end(_secondaryWindows);) {
- if (i->second.get() == window) {
- Assert(_lastActiveWindow != window);
- i = _secondaryWindows.erase(i);
+ i = _windows.erase(i);
} else {
++i;
}
@@ -1502,36 +1490,34 @@ void Application::closeWindow(not_null window) {
const auto account = domain().started()
? &domain().active()
: nullptr;
- if (account && !_primaryWindows.contains(account) && _lastActiveWindow) {
+ if (account
+ && !_windows.contains(Window::SeparateId(account))
+ && _lastActiveWindow) {
domain().activate(&_lastActiveWindow->account());
}
}
void Application::closeChatFromWindows(not_null peer) {
- if (const auto window = windowFor(peer)
- ; window && !window->isPrimary()) {
- closeWindow(window);
- }
- for (const auto &[history, window] : _secondaryWindows) {
- if (const auto session = window->sessionController()) {
- if (session->activeChatCurrent().peer() == peer) {
- session->showPeerHistory(
- window->singlePeer()->id,
- Window::SectionShow::Way::ClearStack);
- }
- }
- }
- if (const auto window = windowFor(&peer->account())) {
- if (const auto primary = window->sessionController()) {
- if (primary->activeChatCurrent().peer() == peer) {
- primary->clearSectionStack();
- }
- if (const auto forum = primary->shownForum().current()) {
- if (peer->forum() == forum) {
- primary->closeForum();
+ const auto closeOne = [&] {
+ for (const auto &[id, window] : _windows) {
+ if (id.thread && id.thread->peer() == peer) {
+ closeWindow(window.get());
+ return true;
+ } else if (const auto controller = window->sessionController()) {
+ if (controller->activeChatCurrent().peer() == peer) {
+ controller->showByInitialId();
+ }
+ if (const auto forum = controller->shownForum().current()) {
+ if (peer->forum() == forum) {
+ controller->closeForum();
+ }
}
}
}
+ return false;
+ };
+
+ while (closeOne()) {
}
}
@@ -1737,11 +1723,8 @@ void Application::quitPreventFinished() {
}
void Application::quitDelayed() {
- for (const auto &[account, window] : _primaryWindows) {
- window->widget()->hide();
- }
- for (const auto &[history, window] : _secondaryWindows) {
- window->widget()->hide();
+ for (const auto &[id, controller] : _windows) {
+ controller->widget()->hide();
}
if (!_private->quitTimer.isActive()) {
_private->quitTimer.setCallback([] { Sandbox::QuitWhenStarted(); });
diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h
index 08a29a35e..77fe09b7f 100644
--- a/Telegram/SourceFiles/core/application.h
+++ b/Telegram/SourceFiles/core/application.h
@@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
+#include "base/timer.h"
#include "mtproto/mtproto_auth_key.h"
#include "mtproto/mtproto_proxy_data.h"
-#include "base/timer.h"
+#include "window/window_separate_id.h"
class History;
@@ -29,11 +30,9 @@ namespace Window {
class Controller;
} // namespace Window
-namespace Window {
-namespace Notifications {
+namespace Window::Notifications {
class System;
-} // namespace Notifications
-} // namespace Window
+} // namespace Window::Notifications
namespace ChatHelpers {
class EmojiKeywords;
@@ -170,19 +169,17 @@ public:
not_null widget) const;
[[nodiscard]] Window::Controller *activeWindow() const;
[[nodiscard]] Window::Controller *activePrimaryWindow() const;
- [[nodiscard]] Window::Controller *separateWindowForAccount(
- not_null account) const;
- [[nodiscard]] Window::Controller *separateWindowForPeer(
- not_null peer) const;
- Window::Controller *ensureSeparateWindowForPeer(
- not_null peer,
- MsgId showAtMsgId);
- Window::Controller *ensureSeparateWindowForAccount(
- not_null account);
+ [[nodiscard]] Window::Controller *separateWindowFor(
+ Window::SeparateId id) const;
+ Window::Controller *ensureSeparateWindowFor(
+ Window::SeparateId id,
+ MsgId showAtMsgId = 0);
[[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch.
+ Window::SeparateId id) const;
+ [[nodiscard]] Window::Controller *windowForShowingHistory(
not_null peer) const;
- [[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch.
- not_null account) const;
+ [[nodiscard]] Window::Controller *windowForShowingForum(
+ not_null forum) const;
[[nodiscard]] bool closeNonLastAsync(
not_null window);
void closeWindow(not_null window);
@@ -195,7 +192,7 @@ public:
void checkSystemDarkMode();
[[nodiscard]] bool isActiveForTrayMenu() const;
void closeChatFromWindows(not_null peer);
- void checkWindowAccount(not_null window);
+ void checkWindowId(not_null window);
void activate();
// Media view interface.
@@ -423,12 +420,9 @@ private:
const std::unique_ptr _calls;
const std::unique_ptr _iv;
base::flat_map<
- Main::Account*,
- std::unique_ptr> _primaryWindows;
+ Window::SeparateId,
+ std::unique_ptr> _windows;
base::flat_set> _closingAsyncWindows;
- base::flat_map<
- not_null,
- std::unique_ptr> _secondaryWindows;
std::vector> _windowStack;
Window::Controller *_lastActiveWindow = nullptr;
Window::Controller *_lastActivePrimaryWindow = nullptr;
diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp
index 1c24dc510..9b03e0b74 100644
--- a/Telegram/SourceFiles/core/click_handler_types.cpp
+++ b/Telegram/SourceFiles/core/click_handler_types.cpp
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "window/window_session_controller_link_info.h"
+#include "styles/style_calls.h" // groupCallBoxLabel
#include "styles/style_layers.h"
namespace {
@@ -121,7 +122,10 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
} else {
const auto parsedUrl = QUrl::fromUserInput(url);
if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) {
- Core::App().hideMediaView();
+ const auto my = context.value();
+ if (!my.show) {
+ Core::App().hideMediaView();
+ }
const auto displayed = parsedUrl.isValid()
? parsedUrl.toDisplayString()
: url;
@@ -130,7 +134,6 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
: parsedUrl.isValid()
? QString::fromUtf8(parsedUrl.toEncoded())
: ShowEncoded(displayed);
- const auto my = context.value();
const auto controller = my.sessionWindow.get();
const auto use = controller
? &controller->window()
@@ -140,8 +143,11 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
.text = (tr::lng_open_this_link(tr::now)),
.confirmed = [=](Fn hide) { hide(); open(); },
.confirmText = tr::lng_open_link(),
+ .labelStyle = my.dark ? &st::groupCallBoxLabel : nullptr,
});
- const auto &st = st::boxLabel;
+ const auto &st = my.dark
+ ? st::groupCallBoxLabel
+ : st::boxLabel;
box->addSkip(st.style.lineHeight - st::boxPadding.bottom());
const auto url = box->addRow(
object_ptr(box, displayUrl, st));
@@ -190,6 +196,7 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
_bot->name()),
.confirmed = callback,
.confirmText = tr::lng_allow_bot(),
+ .labelStyle = my.dark ? &st::groupCallBoxLabel : nullptr,
}));
}
}
diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h
index 20879a0ab..a63594195 100644
--- a/Telegram/SourceFiles/core/click_handler_types.h
+++ b/Telegram/SourceFiles/core/click_handler_types.h
@@ -47,6 +47,7 @@ struct ClickHandlerContext {
bool skipBotAutoLogin = false;
bool botStartAutoSubmit = false;
bool ignoreIv = false;
+ bool dark = false;
// Is filled from peer info.
PeerData *peer = nullptr;
};
diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp
index 96fca9a26..a74468cb8 100644
--- a/Telegram/SourceFiles/core/core_settings.cpp
+++ b/Telegram/SourceFiles/core/core_settings.cpp
@@ -224,7 +224,7 @@ QByteArray Settings::serialize() const {
+ Serialize::bytearraySize(ivPosition)
+ Serialize::stringSize(noWarningExtensions)
+ Serialize::stringSize(_customFontFamily)
- + sizeof(qint32);
+ + sizeof(qint32) * 2;
auto result = QByteArray();
result.reserve(size);
@@ -375,7 +375,8 @@ QByteArray Settings::serialize() const {
<< qint32(std::clamp(
qRound(_dialogsNoChatWidthRatio.current() * 1000000),
0,
- 1000000));
+ 1000000))
+ << qint32(_systemUnlockEnabled ? 1 : 0);
}
Ensures(result.size() == size);
@@ -497,6 +498,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 ttlVoiceClickTooltipHidden = _ttlVoiceClickTooltipHidden.current() ? 1 : 0;
QByteArray ivPosition;
QString customFontFamily = _customFontFamily;
+ qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0;
stream >> themesAccentColors;
if (!stream.atEnd()) {
@@ -794,6 +796,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
0.,
1.);
}
+ if (!stream.atEnd()) {
+ stream >> systemUnlockEnabled;
+ }
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@@ -1002,6 +1007,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_ivPosition = Deserialize(ivPosition);
}
_customFontFamily = customFontFamily;
+ _systemUnlockEnabled = (systemUnlockEnabled == 1);
}
QString Settings::getSoundPath(const QString &key) const {
diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h
index 442d34598..f362bce7d 100644
--- a/Telegram/SourceFiles/core/core_settings.h
+++ b/Telegram/SourceFiles/core/core_settings.h
@@ -890,6 +890,13 @@ public:
_customFontFamily = value;
}
+ [[nodiscard]] bool systemUnlockEnabled() const {
+ return _systemUnlockEnabled;
+ }
+ void setSystemUnlockEnabled(bool enabled) {
+ _systemUnlockEnabled = enabled;
+ }
+
[[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
@@ -1020,6 +1027,7 @@ private:
rpl::variable _ttlVoiceClickTooltipHidden = false;
WindowPosition _ivPosition;
QString _customFontFamily;
+ bool _systemUnlockEnabled = false;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream _tabbedReplacedWithInfoValue; // per-window
diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp
index 4110a73a7..41888829f 100644
--- a/Telegram/SourceFiles/core/sandbox.cpp
+++ b/Telegram/SourceFiles/core/sandbox.cpp
@@ -620,7 +620,7 @@ void Sandbox::processPostponedCalls(int level) {
bool Sandbox::nativeEventFilter(
const QByteArray &eventType,
void *message,
- base::NativeEventResult *result) {
+ native_event_filter_result *result) {
registerEnterFromEventLoop();
return false;
}
diff --git a/Telegram/SourceFiles/core/sandbox.h b/Telegram/SourceFiles/core/sandbox.h
index dfb1fe4a6..5dd5cea2b 100644
--- a/Telegram/SourceFiles/core/sandbox.h
+++ b/Telegram/SourceFiles/core/sandbox.h
@@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "mtproto/mtproto_proxy_data.h"
-#include "base/qt/qt_common_adapters.h"
#include
#include
@@ -87,7 +86,7 @@ private:
bool nativeEventFilter(
const QByteArray &eventType,
void *message,
- base::NativeEventResult *result) override;
+ native_event_filter_result *result) override;
void processPostponedCalls(int level);
void singleInstanceChecked();
void launchApplication();
diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp
index d47c7feb4..1e09dd3df 100644
--- a/Telegram/SourceFiles/core/ui_integration.cpp
+++ b/Telegram/SourceFiles/core/ui_integration.cpp
@@ -279,7 +279,7 @@ bool UiIntegration::copyPreOnClick(const QVariant &context) {
}
std::unique_ptr UiIntegration::createCustomEmoji(
- const QString &data,
+ QStringView data,
const std::any &context) {
const auto my = std::any_cast(&context);
if (!my || !my->session) {
diff --git a/Telegram/SourceFiles/core/ui_integration.h b/Telegram/SourceFiles/core/ui_integration.h
index 36ee45777..a745f0fce 100644
--- a/Telegram/SourceFiles/core/ui_integration.h
+++ b/Telegram/SourceFiles/core/ui_integration.h
@@ -58,7 +58,7 @@ public:
const Ui::Emoji::One *defaultEmojiVariant(
const Ui::Emoji::One *emoji) override;
std::unique_ptr createCustomEmoji(
- const QString &data,
+ QStringView data,
const std::any &context) override;
Fn createSpoilerRepaint(const std::any &context) override;
bool allowClickHandlerActivation(
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index d7cfbb940..b3a93d830 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
-constexpr auto AppVersion = 5001007;
-constexpr auto AppVersionStr = "5.1.7";
+constexpr auto AppVersion = 5002000;
+constexpr auto AppVersionStr = "5.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index 2ba8346d2..2f7694395 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -583,6 +583,10 @@ bool ChannelData::canDeleteStories() const {
|| (adminRights() & AdminRight::DeleteStories);
}
+bool ChannelData::canPostPaidMedia() const {
+ return canPostMessages() && (flags() & Flag::PaidMediaAllowed);
+}
+
bool ChannelData::anyoneCanAddMembers() const {
return !(defaultRestrictions() & Restriction::AddParticipants);
}
@@ -1084,7 +1088,8 @@ void ApplyChannelUpdate(
| Flag::ParticipantsHidden
| Flag::CanGetStatistics
| Flag::ViewAsMessages
- | Flag::CanViewRevenue;
+ | Flag::CanViewRevenue
+ | Flag::PaidMediaAllowed;
channel->setFlags((channel->flags() & ~mask)
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
| (update.is_can_view_participants()
@@ -1101,6 +1106,7 @@ void ApplyChannelUpdate(
| (update.is_view_forum_as_messages()
? Flag::ViewAsMessages
: Flag())
+ | (update.is_paid_media_allowed() ? Flag::PaidMediaAllowed : Flag())
| (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag()));
channel->setUserpicPhoto(update.vchat_photo());
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h
index 6dbe84329..d6f880bda 100644
--- a/Telegram/SourceFiles/data/data_channel.h
+++ b/Telegram/SourceFiles/data/data_channel.h
@@ -66,6 +66,7 @@ enum class ChannelDataFlag : uint64 {
ViewAsMessages = (1ULL << 30),
SimilarExpanded = (1ULL << 31),
CanViewRevenue = (1ULL << 32),
+ PaidMediaAllowed = (1ULL << 33),
};
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags;
@@ -357,6 +358,7 @@ public:
[[nodiscard]] bool canPostStories() const;
[[nodiscard]] bool canEditStories() const;
[[nodiscard]] bool canDeleteStories() const;
+ [[nodiscard]] bool canPostPaidMedia() const;
[[nodiscard]] bool hiddenPreHistory() const;
[[nodiscard]] bool canViewMembers() const;
[[nodiscard]] bool canViewAdmins() const;
diff --git a/Telegram/SourceFiles/data/data_cloud_file.cpp b/Telegram/SourceFiles/data/data_cloud_file.cpp
index 526dd8904..7b3e73828 100644
--- a/Telegram/SourceFiles/data/data_cloud_file.cpp
+++ b/Telegram/SourceFiles/data/data_cloud_file.cpp
@@ -170,6 +170,12 @@ void UpdateCloudFile(
if (data.progressivePartSize && !file.location.valid()) {
file.progressivePartSize = data.progressivePartSize;
}
+ if (data.location.width()
+ && data.location.height()
+ && !file.location.valid()
+ && !file.location.width()) {
+ file.location = data.location;
+ }
return;
}
diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h
index ddfd22a83..75da4db5b 100644
--- a/Telegram/SourceFiles/data/data_credits.h
+++ b/Telegram/SourceFiles/data/data_credits.h
@@ -19,6 +19,16 @@ struct CreditTopupOption final {
using CreditTopupOptions = std::vector;
+enum class CreditsHistoryMediaType {
+ Photo,
+ Video,
+};
+
+struct CreditsHistoryMedia {
+ CreditsHistoryMediaType type = CreditsHistoryMediaType::Photo;
+ uint64 id = 0;
+};
+
struct CreditsHistoryEntry final {
using PhotoId = uint64;
enum class PeerType {
@@ -28,16 +38,26 @@ struct CreditsHistoryEntry final {
Fragment,
Unsupported,
PremiumBot,
+ Ads,
};
+
QString id;
QString title;
QString description;
QDateTime date;
PhotoId photoId = 0;
+ std::vector extended;
uint64 credits = 0;
- uint64 bareId = 0;
+ uint64 bareMsgId = 0;
+ uint64 barePeerId = 0;
PeerType peerType;
bool refunded = false;
+ bool pending = false;
+ bool failed = false;
+ QDateTime successDate;
+ QString successLink;
+ bool in = false;
+
};
struct CreditsStatusSlice final {
diff --git a/Telegram/SourceFiles/data/data_credits_earn.h b/Telegram/SourceFiles/data/data_credits_earn.h
new file mode 100644
index 000000000..c82d58a74
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_credits_earn.h
@@ -0,0 +1,32 @@
+/*
+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_statistics_chart.h"
+
+#include
+
+namespace Data {
+
+using CreditsEarnInt = uint64;
+
+struct CreditsEarnStatistics final {
+ explicit operator bool() const {
+ return !!usdRate;
+ }
+ Data::StatisticalGraph revenueGraph;
+ CreditsEarnInt currentBalance = 0;
+ CreditsEarnInt availableBalance = 0;
+ CreditsEarnInt overallRevenue = 0;
+ float64 usdRate = 0.;
+ bool isWithdrawalEnabled = false;
+ QDateTime nextWithdrawalAt;
+ QString buyAdsUrl;
+};
+
+} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp
index 2fa5f05d4..29a1899e0 100644
--- a/Telegram/SourceFiles/data/data_download_manager.cpp
+++ b/Telegram/SourceFiles/data/data_download_manager.cpp
@@ -531,7 +531,7 @@ void DownloadManager::loadingStopWithConfirmation(
return;
}
const auto window = Core::App().windowFor(
- &item->history()->session().account());
+ not_null(&item->history()->session().account()));
if (!window) {
return;
}
diff --git a/Telegram/SourceFiles/data/data_file_origin.cpp b/Telegram/SourceFiles/data/data_file_origin.cpp
index c97d98a1a..76838edc3 100644
--- a/Telegram/SourceFiles/data/data_file_origin.cpp
+++ b/Telegram/SourceFiles/data/data_file_origin.cpp
@@ -76,6 +76,12 @@ struct FileReferenceAccumulator {
}, [](const auto &data) {
});
}
+ void push(const MTPMessageExtendedMedia &data) {
+ data.match([&](const MTPDmessageExtendedMediaPreview &data) {
+ }, [&](const MTPDmessageExtendedMedia &data) {
+ push(data.vmedia());
+ });
+ }
void push(const MTPMessageMedia &data) {
data.match([&](const MTPDmessageMediaPhoto &data) {
push(data.vphoto());
@@ -85,6 +91,10 @@ struct FileReferenceAccumulator {
push(data.vwebpage());
}, [&](const MTPDmessageMediaGame &data) {
push(data.vgame());
+ }, [&](const MTPDmessageMediaInvoice &data) {
+ push(data.vextended_media());
+ }, [&](const MTPDmessageMediaPaidMedia &data) {
+ push(data.vextended_media());
}, [](const auto &data) {
});
}
diff --git a/Telegram/SourceFiles/data/data_groups.cpp b/Telegram/SourceFiles/data/data_groups.cpp
index cf75482db..15bb0d820 100644
--- a/Telegram/SourceFiles/data/data_groups.cpp
+++ b/Telegram/SourceFiles/data/data_groups.cpp
@@ -81,6 +81,7 @@ void Groups::refreshMessage(
bool justRefreshViews) {
if (!isGrouped(item)) {
unregisterMessage(item);
+ _data->requestItemViewRefresh(item);
return;
}
if (!item->isRegular() && !item->isScheduled()) {
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index 4594b7fa2..15dfedecd 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -7,12 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_media_types.h"
+#include "base/random.h"
+#include "boxes/send_credits_box.h" // CreditsEmoji.
#include "history/history.h"
#include "history/history_item.h" // CreateMedia.
#include "history/history_location_manager.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_item_preview.h"
-#include "history/view/media/history_view_extended_preview.h"
#include "history/view/media/history_view_photo.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_gif.h"
@@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_giveaway.h"
#include "history/view/media/history_view_invoice.h"
#include "history/view/media/history_view_media_generic.h"
+#include "history/view/media/history_view_media_grouped.h"
#include "history/view/media/history_view_call.h"
#include "history/view/media/history_view_web_page.h"
#include "history/view/media/history_view_poll.h"
@@ -84,6 +86,13 @@ constexpr auto kLoadingStoryPhotoId = PhotoId(0x7FFF'DEAD'FFFF'FFFFULL);
using ItemPreview = HistoryView::ItemPreview;
using ItemPreviewImage = HistoryView::ItemPreviewImage;
+struct AlbumCounts {
+ int photos = 0;
+ int videos = 0;
+ int audios = 0;
+ int files = 0;
+};
+
[[nodiscard]] TextWithEntities WithCaptionNotificationText(
const QString &attachType,
const TextWithEntities &caption,
@@ -165,7 +174,7 @@ template
return (reinterpret_cast(data.get()) & ~1) | (spoiler ? 1 : 0);
}
-[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
+[[nodiscard]] ItemPreviewImage PreparePhotoPreviewImage(
not_null item,
const std::shared_ptr &media,
ImageRoundRadius radius,
@@ -181,14 +190,15 @@ template
}
const auto allowedToDownload = media->autoLoadThumbnailAllowed(
item->history()->peer);
- const auto cacheKey = allowedToDownload ? 0 : counted;
+ const auto spoilered = uint64(spoiler ? 1 : 0);
+ const auto cacheKey = allowedToDownload ? spoilered : counted;
if (allowedToDownload) {
media->owner()->load(PhotoSize::Small, item->fullId());
}
if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius, spoiler), cacheKey };
}
- return { QImage(), allowedToDownload ? 0 : cacheKey };
+ return { QImage(), allowedToDownload ? spoilered : cacheKey };
}
[[nodiscard]] ItemPreviewImage PrepareFilePreviewImage(
@@ -207,10 +217,11 @@ template
};
}
document->loadThumbnail(item->fullId());
+ const auto spoilered = uint64(spoiler ? 1 : 0);
if (const auto blurred = media->thumbnailInline()) {
- return { PreparePreviewImage(blurred, radius, spoiler), 0 };
+ return { PreparePreviewImage(blurred, radius, spoiler), spoilered };
}
- return { QImage(), 0 };
+ return { QImage(), spoilered };
}
[[nodiscard]] QImage PutPlayIcon(QImage preview) {
@@ -225,6 +236,18 @@ template
return preview;
}
+[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
+ not_null item,
+ const std::shared_ptr &media,
+ ImageRoundRadius radius,
+ bool spoiler) {
+ auto result = PreparePhotoPreviewImage(item, media, radius, spoiler);
+ if (media->owner()->extendedMediaVideoDuration().has_value()) {
+ result.data = PutPlayIcon(std::move(result.data));
+ }
+ return result;
+}
+
[[nodiscard]] ItemPreviewImage PrepareFilePreview(
not_null item,
const std::shared_ptr &media,
@@ -261,48 +284,82 @@ template
}
bool UpdateExtendedMedia(
- Invoice &invoice,
+ std::unique_ptr